1313from investing_algorithm_framework .domain import BacktestRun , OrderType , \
1414 TimeUnit , Trade , OperationalException , BacktestDateRange , TimeFrame , \
1515 Backtest , TradeStatus , PortfolioSnapshot , Order , OrderStatus , OrderSide , \
16- Portfolio , DataType , generate_backtest_summary_metrics
16+ Portfolio , DataType , generate_backtest_summary_metrics , \
17+ PortfolioConfiguration
1718from investing_algorithm_framework .services .data_providers import \
1819 DataProviderService
1920from investing_algorithm_framework .services .portfolios import \
@@ -114,8 +115,10 @@ def create_vector_backtest(
114115 self ,
115116 strategy ,
116117 backtest_date_range : BacktestDateRange ,
117- initial_amount : float ,
118118 risk_free_rate : float = 0.027 ,
119+ initial_amount : float = None ,
120+ trading_symbol : str = None ,
121+ market : str = None ,
119122 ) -> BacktestRun :
120123 """
121124 Vectorized backtest for multiple assets using strategy
@@ -124,9 +127,16 @@ def create_vector_backtest(
124127 Args:
125128 strategy: The strategy to backtest.
126129 backtest_date_range: The date range for the backtest.
127- initial_amount: The initial amount to use for the backtest.
128130 risk_free_rate: The risk-free rate to use for the backtest
129131 metrics. Default is 0.027 (2.7%).
132+ initial_amount: The initial amount to use for the backtest.
133+ If None, the initial amount will be taken from the first
134+ portfolio configuration.
135+ trading_symbol: The trading symbol to use for the backtest.
136+ If None, the trading symbol will be taken from the first
137+ portfolio configuration.
138+ market: The market to use for the backtest. If None, the market
139+ will be taken from the first portfolio configuration.
130140
131141 Returns:
132142 BacktestRun: The backtest run containing the results and metrics.
@@ -135,12 +145,26 @@ def create_vector_backtest(
135145 .get_all ()
136146
137147 if (portfolio_configurations is None
138- or len (portfolio_configurations ) == 0 ):
148+ or len (portfolio_configurations ) == 0
149+ and initial_amount is None
150+ or trading_symbol is None
151+ or market is None ):
139152 raise OperationalException (
140153 "No portfolio configurations found, please register a "
141154 "portfolio configuration before running a backtest."
142155 )
143156
157+ if portfolio_configurations is None \
158+ or len (portfolio_configurations ) == 0 :
159+ portfolio_configurations = []
160+ portfolio_configurations .append (
161+ PortfolioConfiguration (
162+ market = market ,
163+ trading_symbol = trading_symbol ,
164+ initial_balance = initial_amount
165+ )
166+ )
167+
144168 trading_symbol = portfolio_configurations [0 ].trading_symbol
145169
146170 # Load vectorized backtest data
@@ -188,8 +212,6 @@ def create_vector_backtest(
188212 total_net_gain = 0.0
189213 )
190214 ]
191- unallocated = initial_amount
192- total_values = pd .Series (0.0 , index = index )
193215
194216 for symbol in buy_signals .keys ():
195217 full_symbol = f"{ symbol } /{ trading_symbol } "
@@ -221,7 +243,6 @@ def create_vector_backtest(
221243 returns = close .pct_change ().fillna (0 )
222244 returns = returns .astype (float )
223245 signal = signal .astype (float )
224- strategy_returns = signal * returns
225246
226247 if pos_size_obj is None :
227248 raise OperationalException (
@@ -242,9 +263,6 @@ def create_vector_backtest(
242263 asset_price = close .iloc [0 ]
243264 )
244265
245- holdings = (strategy_returns + 1 ).cumprod () * capital_for_trade
246- total_values += holdings
247-
248266 # Trade generation
249267 last_trade = None
250268
@@ -296,7 +314,6 @@ def create_vector_backtest(
296314 )
297315 last_trade = trade
298316 trades .append (trade )
299- unallocated -= capital_for_trade
300317
301318 # If we are in a position, and we get a sell signal
302319 if current_signal == - 1 and last_trade is not None :
@@ -327,42 +344,50 @@ def create_vector_backtest(
327344 "net_gain" : net_gain_val
328345 }
329346 )
330- unallocated += last_trade .available_amount * current_price
331347 last_trade = None
332348
349+ unallocated = initial_amount
350+ total_net_gain = 0.0
351+ open_trades = []
352+
333353 # Create portfolio snapshots
334354 for ts in index :
335- invested_value = 0.0
355+ allocated = 0
356+ interval_datetime = pd .Timestamp (ts ).to_pydatetime ()
357+ interval_datetime = interval_datetime .replace (tzinfo = timezone .utc )
336358
337359 for trade in trades :
338- if trade .opened_at <= ts and (
339- trade .closed_at is None or trade .closed_at >= ts ):
340-
341- # Trade is still open at this time
342- ohlcv = granular_ohlcv_data_order_by_symbol [trade .symbol ]
343-
344- # Datetime is the index for pandas DataFrame, find the
345- # closest timestamp that is less than or equal to ts
346- # prices = ohlcv.loc[ohlcv.index <= ts, "Close"].values
347- #
348- # if len(prices) == 0:
349- # # No price data for this timestamp
350- # price = trade.open_price
351- # else:
352- # price = prices[-1]
353- try :
354- price = ohlcv .loc [:ts , "Close" ].iloc [- 1 ]
355- except IndexError :
356- continue # skip if no price yet
357-
358- invested_value += trade .filled_amount * price
359- total_value = invested_value + unallocated
360- total_net_gain = total_value - initial_amount
360+
361+ if trade .opened_at == interval_datetime :
362+ # Snapshot taken at the moment a trade is opened
363+ unallocated -= trade .cost
364+ open_trades .append (trade )
365+
366+ if trade .closed_at == interval_datetime :
367+ # Snapshot taken at the moment a trade is closed
368+ unallocated += trade .cost + trade .net_gain
369+ total_net_gain += trade .net_gain
370+ open_trades .remove (trade )
371+
372+ for open_trade in open_trades :
373+ ohlcv = granular_ohlcv_data_order_by_symbol [
374+ f"{ open_trade .target_symbol } /{ trading_symbol } "
375+ ]
376+
377+ try :
378+ price = ohlcv .loc [:ts , "Close" ].iloc [- 1 ]
379+ except IndexError :
380+ continue # skip if no price yet
381+
382+ allocated += open_trade .filled_amount * price
383+
384+ # total_value = invested_value + unallocated
385+ # total_net_gain = total_value - initial_amount
361386 snapshots .append (
362387 PortfolioSnapshot (
363- created_at = pd . Timestamp ( ts ) ,
388+ created_at = interval_datetime ,
364389 unallocated = unallocated ,
365- total_value = total_value ,
390+ total_value = unallocated + allocated ,
366391 total_net_gain = total_net_gain
367392 )
368393 )
0 commit comments