Skip to content

Commit

Permalink
Add backtesting service
Browse files Browse the repository at this point in the history
  • Loading branch information
MDUYN committed Aug 1, 2023
1 parent ea78e7b commit 07cd7a3
Show file tree
Hide file tree
Showing 9 changed files with 167 additions and 10 deletions.
6 changes: 3 additions & 3 deletions examples/app_with_class_based_strategies.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

class MyTradingStrategy(TradingStrategy):
time_unit = TimeUnit.SECOND
interval = 5
interval = 3
trading_data_type = TradingDataType.OHLCV
trading_time_frame_start_date = datetime.utcnow() - timedelta(days=1)
trading_time_frame = TradingTimeFrame.ONE_MINUTE
Expand All @@ -26,10 +26,10 @@ def apply_strategy(
app = create_app()
app.add_portfolio_configuration(
PortfolioConfiguration(
market="<your_market>",
market="bitvavo",
api_key="<your_api_key>",
secret_key="<your_secret_key>",
trading_symbol="<your_trading_symbol>"
trading_symbol="EUR"
)
)
app.add_strategy(MyTradingStrategy)
Expand Down
58 changes: 58 additions & 0 deletions examples/backtesting/backtesting_with_ccxt_data.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import pathlib
from datetime import datetime, timedelta

from investing_algorithm_framework import create_app, PortfolioConfiguration, \
TimeUnit, TradingTimeFrame, TradingDataType, TradingStrategy, \
RESOURCE_DIRECTORY


class MyTradingStrategy(TradingStrategy):
time_unit = TimeUnit.SECOND
interval = 3
trading_data_type = TradingDataType.OHLCV
trading_time_frame_start_date = datetime.utcnow() - timedelta(days=1)
trading_time_frame = TradingTimeFrame.ONE_MINUTE
market = "BITVAVO"
symbols = ["BTC/EUR"]

def apply_strategy(
self,
algorithm,
market_data,
):
print(len(algorithm.get_orders()))
print(market_data)


# No resource directory specified, so an in-memory database will be used
app = create_app({RESOURCE_DIRECTORY: pathlib.Path(__file__).parent.resolve()})
app.add_portfolio_configuration(
PortfolioConfiguration(
market="bitvavo",
api_key="<your_api_key>",
secret_key="<your_secret_key>",
trading_symbol="EUR"
)
)
app.add_strategy(MyTradingStrategy)

if __name__ == "__main__":
report = app.backtest(
start_datetime=datetime.utcnow() - timedelta(days=1),
end_datetime=datetime.utcnow(),
source="ccxt"
)
# pretty_print_report(report)
# print(report.get_orders())
# print(report.get_trades())
# print(report.get_positions())
#
# report = app.backtest(
# start_datetime=datetime.utcnow() - timedelta(days=1),
# end_datetime=datetime.utcnow(),
# source="ccxt",
# unallocated=10000,
# commission=0.001,
# commission_currency="EUR"
# )
# pretty_print_report(report)
17 changes: 16 additions & 1 deletion investing_algorithm_framework/app/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,21 @@ def run(
except KeyboardInterrupt:
exit(0)

def backtest(self, start_datetime, end_datetime, source="ccxt"):
self.algorithm = self.container.algorithm()
self.algorithm.add_strategies(self.strategies)
portfolio_configuration_service = self.container\
.portfolio_configuration_service()

if portfolio_configuration_service.count() == 0:
raise OperationalException("No portfolios configured")

self.create_portfolios(backtesting=True)
backtest_service = self.container.backtest_service()
return backtest_service.backtest(
start_datetime, end_datetime, self.strategies, self.config
)

def start_algorithm(self):
self.algorithm.start()

Expand Down Expand Up @@ -278,7 +293,7 @@ def sync_portfolios(self):
portfolio_service = self.container.portfolio_service()
portfolio_service.sync_portfolios()

def create_portfolios(self):
def create_portfolios(self, backtesting=False):
portfolio_configuration_service = self.container\
.portfolio_configuration_service()
market_service = self.container.market_service()
Expand Down
20 changes: 20 additions & 0 deletions investing_algorithm_framework/app/strategy.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,3 +84,23 @@ def apply_strategy(
self.decorated(algorithm=algorithm, market_data=market_data)
else:
raise NotImplementedError("Apply strategy is not implemented")

@property
def trade_profile(self):
trading_data_types = []

if self.trading_data_type is not None:
trading_data_types.append(self.trading_data_type)

if self.trading_data_types is not None:
trading_data_types.extend(self.trading_data_types)

return {
"time_unit": self.time_unit,
"interval": self.interval,
"market": self.market,
"symbols": self.symbols,
"trading_data_types": trading_data_types,
"trading_time_frame": self.trading_time_frame,
"trading_time_frame_start_date": self.trading_time_frame_start_date,
}
7 changes: 6 additions & 1 deletion investing_algorithm_framework/dependency_container.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@
SQLPositionCostRepository, SQLOrderFeeRepository
from investing_algorithm_framework.services import OrderService, \
PositionService, PortfolioService, StrategyOrchestratorService, \
PortfolioConfigurationService, MarketDataService, PositionCostService
PortfolioConfigurationService, MarketDataService, PositionCostService, \
BackTestService
from investing_algorithm_framework.app.algorithm import Algorithm


Expand Down Expand Up @@ -75,3 +76,7 @@ class DependencyContainer(containers.DeclarativeContainer):
strategy_orchestrator_service=strategy_orchestrator_service,
market_data_service=market_data_service
)
backtest_service = providers.Factory(
BackTestService,
market_data_service=market_data_service,
)
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,7 @@ def get_orders(self, symbol, since: datetime = None):
else:
try:
ccxt_orders = self.exchange.fetchOrders(symbol)
print(ccxt_orders)
return [Order.from_ccxt_order(order) for order in ccxt_orders]
except Exception as e:
logger.exception(e)
Expand Down Expand Up @@ -368,15 +369,20 @@ def get_ohclv(self, symbol, time_unit, since):

return OHLCV.from_dict({"symbol": symbol, "data": data})

def get_ohclvs(self, symbols, time_frame, from_timestamp):
def get_ohclvs(self, symbols, time_frame, from_timestamp, to_timestamp=None):

if not self.exchange.has['fetchOHLCV']:
raise OperationalException(
f"Market service {self.market} does not support "
f"functionality get_ohclvs"
)

now = self.exchange.milliseconds()
if to_timestamp is None:
to_timestamp = self.exchange.milliseconds()
else:
to_timestamp = self.exchange.parse8601(
from_timestamp.strftime(":%Y-%m-%d %H:%M:%S")
)
ohlcvs = {}

for symbol in symbols:
Expand All @@ -387,7 +393,7 @@ def get_ohclvs(self, symbols, time_frame, from_timestamp):
)
dfs = []

while time_stamp < now:
while time_stamp < to_timestamp:

ohlcv = self.exchange.fetch_ohlcv(
symbol, time_frame.to_ccxt_string(), time_stamp
Expand Down
4 changes: 3 additions & 1 deletion investing_algorithm_framework/services/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from .strategy_orchestrator_service import StrategyOrchestratorService
from .portfolio_configuration_service import PortfolioConfigurationService
from .market_data_service import MarketDataService
from .backtest_service import BackTestService

__all__ = [
"StrategyOrchestratorService",
Expand All @@ -15,5 +16,6 @@
"PositionService",
"PortfolioConfigurationService",
"MarketDataService",
"PositionCostService"
"PositionCostService",
"BackTestService",
]
50 changes: 50 additions & 0 deletions investing_algorithm_framework/services/backtest_service.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import os
from investing_algorithm_framework.domain import RESOURCE_DIRECTORY, \
OperationalException


class BackTestService:

def __init__(self, market_data_service):
self._market_data_service = market_data_service

def backtest(self, start_date, end_date, strategies, config):
self._create_test_data_csv(
start_date,
end_date,
[strategy.trade_profile for strategy in strategies],
config
)
self._create_test_data(
start_date,
end_date,
[strategy.trade_profile for strategy in strategies]
)

def _create_test_data(self, start_date, end_date, trade_profiles):

for trade_profile in trade_profiles:
data = self._market_data_service.get_market_data(
trade_profile.market,
trade_profile.trading_symbol,
start_date,
end_date
)

def _create_test_data_csv(self, start_date, end_date, trade_profiles, config):
print(config)

if RESOURCE_DIRECTORY not in config \
or config.get(RESOURCE_DIRECTORY) is None:
raise OperationalException(
"The resource directory is not configured. Please configure "
"the resource directory before backtesting."
)
trading_test_data_path = os.path.join(
config[RESOURCE_DIRECTORY],
"ohclv_trading_test_data.csv"
)

if not os.path.exists(trading_test_data_path):
with open(trading_test_data_path, 'w') as file:
pass
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ class MarketDataService:
def __init__(self, market_service):
self.market_service = market_service

def get_data_for_strategy(self, strategy):
def get_data_for_strategy(self, strategy, start_date=None, end_date=None):
data = {}

if strategy.market and \
Expand Down Expand Up @@ -73,3 +73,4 @@ def get_data_for_strategy(self, strategy):
)

return data

0 comments on commit 07cd7a3

Please sign in to comment.