Skip to content

Commit 898280a

Browse files
committed
Add metadata specification
1 parent 38953c1 commit 898280a

File tree

4 files changed

+59
-23
lines changed

4 files changed

+59
-23
lines changed

investing_algorithm_framework/domain/backtesting/backtest_run.py

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -293,14 +293,23 @@ def get_trades(self, target_symbol=None, trade_status=None) -> List[Trade]:
293293

294294
def get_portfolio_snapshots(
295295
self,
296-
created_at_lt: Optional[datetime] = None
296+
created_at_lt: Optional[datetime] = None,
297+
created_at_lte: Optional[datetime] = None,
298+
created_at_gt: Optional[datetime] = None,
299+
created_at_gte: Optional[datetime] = None
297300
) -> List[PortfolioSnapshot]:
298301
"""
299302
Get the portfolio snapshots of the backtest report
300303
301304
Args:
302305
created_at_lt (datetime): The created_at date to filter
303306
the snapshots
307+
created_at_lte (datetime): The created_at date to filter
308+
the snapshots
309+
created_at_gt (datetime): The created_at date to filter
310+
the snapshots
311+
created_at_gte (datetime): The created_at date to filter
312+
the snapshots
304313
305314
Returns:
306315
list: The portfolio snapshots of the backtest report
@@ -313,6 +322,24 @@ def get_portfolio_snapshots(
313322
if snapshot.created_at < created_at_lt
314323
]
315324

325+
if created_at_lte is not None:
326+
selection = [
327+
snapshot for snapshot in selection
328+
if snapshot.created_at <= created_at_lte
329+
]
330+
331+
if created_at_gt is not None:
332+
selection = [
333+
snapshot for snapshot in selection
334+
if snapshot.created_at > created_at_gt
335+
]
336+
337+
if created_at_gte is not None:
338+
selection = [
339+
snapshot for snapshot in selection
340+
if snapshot.created_at >= created_at_gte
341+
]
342+
316343
return selection
317344

318345
def get_orders(

investing_algorithm_framework/domain/models/portfolio/portfolio_snapshot.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,7 @@ def __repr__(self):
128128
total_revenue=self.total_revenue,
129129
total_cost=self.total_cost,
130130
cash_flow=self.cash_flow,
131+
metadata=self.metadata,
131132
)
132133

133134
def to_dict(self, datetime_format=None):

investing_algorithm_framework/services/backtesting/backtest_service.py

Lines changed: 16 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -259,7 +259,7 @@ def create_vector_backtest(
259259
# If we are not in a position, and we get a buy signal
260260
if current_signal == 1 and last_trade is None:
261261
amount = float(capital_for_trade / current_price)
262-
order = Order(
262+
buy_order = Order(
263263
id=uuid4(),
264264
target_symbol=symbol,
265265
trading_symbol=trading_symbol,
@@ -271,12 +271,10 @@ def create_vector_backtest(
271271
updated_at=current_date,
272272
order_side=OrderSide.BUY
273273
)
274-
orders.append(order)
274+
orders.append(buy_order)
275275
trade = Trade(
276276
id=uuid4(),
277-
orders=[
278-
order
279-
],
277+
orders=[buy_order],
280278
target_symbol=symbol,
281279
trading_symbol=trading_symbol,
282280
available_amount=amount,
@@ -286,11 +284,9 @@ def create_vector_backtest(
286284
opened_at=current_date,
287285
closed_at=None,
288286
amount=amount,
289-
net_gain=0,
290287
status=TradeStatus.OPEN.value,
291288
cost=capital_for_trade
292289
)
293-
trade.updated_at = current_date
294290
last_trade = trade
295291
trades.append(trade)
296292

@@ -299,11 +295,7 @@ def create_vector_backtest(
299295
net_gain_val = (
300296
current_price - last_trade.open_price
301297
) * last_trade.available_amount
302-
last_trade.closed_at = current_date
303-
last_trade.updated_at = current_date
304-
last_trade.net_gain = net_gain_val
305-
last_trade.status = TradeStatus.CLOSED.value
306-
order = Order(
298+
sell_order = Order(
307299
id=uuid4(),
308300
target_symbol=symbol,
309301
trading_symbol=trading_symbol,
@@ -315,8 +307,18 @@ def create_vector_backtest(
315307
updated_at=current_date,
316308
order_side=OrderSide.SELL
317309
)
318-
orders.append(order)
319-
last_trade.orders.append(order)
310+
orders.append(sell_order)
311+
trade_orders = last_trade.orders
312+
trade_orders.append(sell_order)
313+
last_trade.update(
314+
{
315+
"orders": trade_orders,
316+
"closed_at": current_date,
317+
"trade_status": TradeStatus.CLOSED,
318+
"updated_at": current_date,
319+
"net_gain": net_gain_val
320+
}
321+
)
320322
last_trade = None
321323

322324
# Create portfolio snapshots

investing_algorithm_framework/services/metrics/trades.py

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -201,7 +201,8 @@ def get_average_trade_return(trades: List[Trade]) -> Tuple[float, float]:
201201
"Trades list is empty, cannot compute average return."
202202
)
203203

204-
closed_trades = [t for t in trades if t.status == TradeStatus.CLOSED]
204+
closed_trades = [t for t in trades if TradeStatus.CLOSED.equals(t.status)]
205+
205206
if not closed_trades:
206207
return 0.0, 0.0
207208

@@ -265,15 +266,22 @@ def get_average_trade_loss(trades: List[Trade]) -> Tuple[float, float]:
265266
"Trades list is empty or None, cannot calculate average loss."
266267
)
267268

268-
losses = [t.net_gain for t in trades if t.net_gain < 0]
269-
cost = sum(t.cost for t in trades if t.net_gain < 0)
269+
closed_trades = [t for t in trades if TradeStatus.CLOSED.equals(t.status)]
270+
losing_trades = [t for t in closed_trades if t.net_gain < 0]
270271

271-
if not losses:
272+
if not losing_trades or len(losing_trades) == 0:
272273
return 0.0, 0.0
273274

275+
losses = [t.net_gain for t in losing_trades]
274276
average_loss = sum(losses) / len(losses)
275-
percentage = (average_loss / cost) if cost > 0 else 0.0
276-
return average_loss, percentage
277+
percentage_returns = [
278+
(t.net_gain / t.cost) * 100.0 for t in losing_trades if t.cost > 0
279+
]
280+
average_return_percentage = (
281+
sum(percentage_returns) / len(percentage_returns)
282+
if percentage_returns else 0.0
283+
)
284+
return average_loss, average_return_percentage
277285

278286

279287
def get_median_trade_return(trades: List[Trade]) -> Tuple[float, float]:
@@ -339,5 +347,3 @@ def get_worst_trade(trades: List[Trade]) -> Trade:
339347
return None
340348

341349
return min(trades, key=lambda t: t.net_gain)
342-
343-

0 commit comments

Comments
 (0)