Browse Source

feat: backtest

pull/14/head
QuentinN42 10 months ago
parent
commit
3fdca03d8c
Signed by: number42 GPG Key ID: 2CD7D563712B3A50
  1. 74
      auto_trading/broker/backtest.py
  2. 18
      auto_trading/errors.py
  3. 4
      auto_trading/interfaces.py
  4. 6
      auto_trading/orders.py
  5. 2
      main.py
  6. 0
      tests/broker/__init__.py
  7. 31
      tests/broker/test_backtest.py

74
auto_trading/broker/backtest.py

@ -1,22 +1,68 @@
import datetime
import pandas as pd # type: ignore
from ..interfaces import Broker
from ..interfaces import DataBroker, CandlesProperties
class Backtest(Broker):
class Backtest(DataBroker):
"""
Backtest Broker
"""
def __init__(self, f_name: str, start=0, **kwargs) -> None:
def __init__(self, f_name: str, start: int = 0, **kwargs) -> None:
"""Read the csv file and store it into a dataframe"""
self.data = pd.read_csv(f_name, **kwargs).fillna(method="ffill")
self.curcor = start
def __bool__(self):
return self.curcor < len(self.data)
def next(self):
"""Return the next row of the dataframe"""
self.curcor += 1
return self.data.iloc[:self.curcor].copy()
super().__init__()
self.data = self._prepare_data(f_name, **kwargs)
self.dates = self.data.index.levels[0]
self._date0 = self.dates[0]
self.step = self.calc_step()
self._cursor = start
self.start = start - 1
def calc_step(self) -> datetime.datetime:
return min(self.dates[i] - self.dates[i - 1] for i in range(1, 10))
@property
def cursor(self) -> datetime.datetime:
return self.dates[self._cursor]
@staticmethod
def _prepare_data(f_name: str, **kwargs) -> pd.DataFrame:
data = pd.read_csv(f_name, index_col=[0, 1], parse_dates=True, **kwargs)
data.sort_index(inplace=True)
data.fillna(method="ffill", inplace=True)
return data
@property
def properties(self) -> CandlesProperties:
"""Return the properties of the candles for this broker."""
return CandlesProperties(period=self.step)
@property
def current_change(self) -> pd.DataFrame:
"""Return the current change for each money."""
return self.data.loc[(self.cursor,)]["close"].to_dict()
def __iter__(self) -> "DataBroker":
"""Initialise the iterator."""
self._cursor = self.start
return self
@property
def __next__(self) -> pd.DataFrame:
"""Next values.
Return the dataframe of all stock history for the strategy / indicators.
Returns:
DataFrame: Time-Stock valuated candlestick data.
For each time and each stock give (high, low, open, close).
"""
self._cursor += 1
try:
return self.data.loc[(self._date0,) : (self.cursor,)].copy # type: ignore
except IndexError:
raise StopIteration

18
auto_trading/errors.py

@ -1,21 +1,27 @@
"""Some errors to catch."""
class TradingException(Exception):
""" An error connected to the trading logic. """
"""An error connected to the trading logic."""
message: str
def __init__(self, message):
super().__init__(message)
self.message = message
class OrderException(TradingException):
""" An order execution was unsuccessfull. """
"""An order execution was unsuccessfull."""
class UnknowOrder(OrderException):
""" The exectuor can't process this order. """
"""The exectuor can't process this order."""
class OrderFails(OrderException):
""" The order placement failed. """
"""The order placement failed."""
class PTFException(TradingException):
""" An error occur inside the PTF. """
"""An error occur inside the PTF."""

4
auto_trading/interfaces.py

@ -6,7 +6,7 @@ import logging
from abc import ABC, abstractmethod, abstractproperty
from dataclasses import dataclass, field
from datetime import timedelta, datetime
from pandas import DataFrame # type: ignore
from pandas import DataFrame, Timedelta # type: ignore
from typing import Dict, List, Optional, Any, Callable
from .errors import OrderException, UnknowOrder, PTFException
@ -36,7 +36,7 @@ class Order:
@dataclass
class CandlesProperties:
period: timedelta
period: Timedelta
class DataBroker(ABC):

6
auto_trading/orders.py

@ -7,10 +7,11 @@ from .interfaces import Order
@dataclass
class Long(Order):
"""Buy stock."""
stock: str
amount: float
price: float
@property
def amount_usd(self) -> float:
"""The amount in $"""
@ -20,10 +21,11 @@ class Long(Order):
@dataclass
class Short(Order):
"""Buy stock."""
stock: str
amount: float
price: float
@property
def amount_usd(self) -> float:
"""The amount in $"""

2
main.py

@ -15,7 +15,7 @@ pd.options.plotting.backend = "plotly"
if __name__ == "__main__":
csv = "data/gold.csv"
bt = Backtest(csv, start=10, index_col=0)
bt = Backtest(csv, start=10)
start = {name: 0 for name in bt.data.columns}
start["USD"] = 10_000

0
tests/broker/__init__.py

31
tests/broker/test_backtest.py

@ -0,0 +1,31 @@
import pytest
import datetime
import json
from pandas import DataFrame # type: ignore
from auto_trading.broker.backtest import Backtest
@pytest.fixture
def backtest():
return Backtest("./data/NYSE_smallest.csv")
@pytest.fixture
def first_change():
return {"GOOG": 524.812404, "GOOGL": 529.549988}
def test_nyse_backtest(backtest, first_change):
"""Test the backtest over a simple csv."""
step = datetime.timedelta(days=1)
first_day = datetime.datetime.strptime("2015-01-02", "%Y-%m-%d")
assert backtest.cursor == first_day
assert backtest.current_change == first_change
assert backtest.properties.period == step
assert backtest.step == step
k = first_day
for data in backtest:
k += step
assert set(backtest.current_change.keys()) == set(first_change.keys())
Loading…
Cancel
Save