|
1 | 1 | from typing import List, Tuple |
2 | 2 |
|
3 | 3 | from investing_algorithm_framework.domain import Trade, TradeStatus, \ |
4 | | - OperationalException |
| 4 | + OperationalException, BacktestRun |
5 | 5 |
|
6 | 6 |
|
7 | 7 | def get_positive_trades( |
@@ -163,6 +163,41 @@ def get_average_trade_duration( |
163 | 163 | return total_duration / number_of_trades if number_of_trades > 0 else 0.0 |
164 | 164 |
|
165 | 165 |
|
| 166 | +def get_current_average_trade_duration( |
| 167 | + trades: List[Trade], backtest_run: BacktestRun |
| 168 | +) -> float: |
| 169 | + """ |
| 170 | + Calculate the average duration of currently closed and open trades |
| 171 | + in hours. |
| 172 | +
|
| 173 | + Args: |
| 174 | + trades (List[Trade]): List of Trade objects. |
| 175 | + backtest_run (BacktestRun): The backtest run containing trades. |
| 176 | +
|
| 177 | + Returns: |
| 178 | + float: The average trade duration in hours. |
| 179 | + """ |
| 180 | + if trades is None or len(trades) == 0: |
| 181 | + raise OperationalException( |
| 182 | + "Trades list is None, cannot compute average trade duration." |
| 183 | + ) |
| 184 | + |
| 185 | + total_duration = 0.0 |
| 186 | + |
| 187 | + for trade in trades: |
| 188 | + |
| 189 | + if TradeStatus.CLOSED.equals(trade.status): |
| 190 | + total_duration += (trade.closed_at - trade.opened_at)\ |
| 191 | + .total_seconds() / 3600 |
| 192 | + else: |
| 193 | + total_duration += ( |
| 194 | + backtest_run.backtest_end_date - trade.opened_at |
| 195 | + ).total_seconds() / 3600 |
| 196 | + |
| 197 | + number_of_trades = len(trades) |
| 198 | + return total_duration / number_of_trades if number_of_trades > 0 else 0.0 |
| 199 | + |
| 200 | + |
166 | 201 | def get_average_trade_size( |
167 | 202 | trades: List[Trade] |
168 | 203 | ) -> float: |
@@ -220,6 +255,39 @@ def get_average_trade_return(trades: List[Trade]) -> Tuple[float, float]: |
220 | 255 | return average_return, average_return_percentage |
221 | 256 |
|
222 | 257 |
|
| 258 | +def get_current_average_trade_return( |
| 259 | + trades: List[Trade] |
| 260 | +) -> Tuple[float, float]: |
| 261 | + """ |
| 262 | + Calculate the average return (absolute PnL) and |
| 263 | + average return percentage (per trade) of closed and open trades. |
| 264 | +
|
| 265 | + Args: |
| 266 | + trades (List[Trade]): List of trades. |
| 267 | +
|
| 268 | + Returns: |
| 269 | + Tuple[float, float]: The average return |
| 270 | + percentage of the average return |
| 271 | + """ |
| 272 | + if not trades or len(trades) == 0: |
| 273 | + raise OperationalException( |
| 274 | + "Trades list is empty, cannot compute average return." |
| 275 | + ) |
| 276 | + |
| 277 | + total_return = sum(t.net_gain for t in trades) |
| 278 | + average_return = total_return / len(trades) |
| 279 | + |
| 280 | + percentage_returns = [ |
| 281 | + (t.net_gain / t.cost) * 100.0 for t in trades if t.cost > 0 |
| 282 | + ] |
| 283 | + average_return_percentage = ( |
| 284 | + sum(percentage_returns) / len(percentage_returns) |
| 285 | + if percentage_returns else 0.0 |
| 286 | + ) |
| 287 | + |
| 288 | + return average_return, average_return_percentage |
| 289 | + |
| 290 | + |
223 | 291 | def get_average_trade_gain(trades: List[Trade]) -> Tuple[float, float]: |
224 | 292 | """ |
225 | 293 | Calculate the average gain from a list of trades. |
@@ -249,6 +317,37 @@ def get_average_trade_gain(trades: List[Trade]) -> Tuple[float, float]: |
249 | 317 | return average_gain, percentage |
250 | 318 |
|
251 | 319 |
|
| 320 | +def get_current_average_trade_gain( |
| 321 | + trades: List[Trade] |
| 322 | +) -> Tuple[float, float]: |
| 323 | + """ |
| 324 | + Calculate the average gain from a list of trades, |
| 325 | + including both closed and open trades. |
| 326 | +
|
| 327 | + The average gain is calculated as the mean of all positive returns. |
| 328 | +
|
| 329 | + Args: |
| 330 | + trades (List[Trade]): List of trades. |
| 331 | + Returns: |
| 332 | + Tuple[float, float]: The average gain |
| 333 | + percentage of the average loss |
| 334 | + """ |
| 335 | + if trades is None or len(trades) == 0: |
| 336 | + raise OperationalException( |
| 337 | + "Trades list is empty or None, cannot calculate average gain." |
| 338 | + ) |
| 339 | + |
| 340 | + gains = [t.net_gain for t in trades if t.net_gain > 0] |
| 341 | + cost = sum(t.cost for t in trades if t.net_gain > 0) |
| 342 | + |
| 343 | + if not gains: |
| 344 | + return 0.0, 0.0 |
| 345 | + |
| 346 | + average_gain = sum(gains) / len(gains) |
| 347 | + percentage = (average_gain / cost) if cost > 0 else 0.0 |
| 348 | + return average_gain, percentage |
| 349 | + |
| 350 | + |
252 | 351 | def get_average_trade_loss(trades: List[Trade]) -> Tuple[float, float]: |
253 | 352 | """ |
254 | 353 | Calculate the average loss from a list of trades. |
@@ -284,6 +383,44 @@ def get_average_trade_loss(trades: List[Trade]) -> Tuple[float, float]: |
284 | 383 | return average_loss, average_return_percentage |
285 | 384 |
|
286 | 385 |
|
| 386 | +def get_current_average_trade_loss( |
| 387 | + trades: List[Trade] |
| 388 | +) -> Tuple[float, float]: |
| 389 | + """ |
| 390 | + Calculate the average loss from a list of trades, |
| 391 | + including both closed and open trades. |
| 392 | +
|
| 393 | + The average loss is calculated as the mean of all negative returns. |
| 394 | +
|
| 395 | + Args: |
| 396 | + trades (List[Trade]): List of trades. |
| 397 | +
|
| 398 | + Returns: |
| 399 | + Tuple[float, float]: The average loss |
| 400 | + percentage of the average loss |
| 401 | + """ |
| 402 | + if trades is None or len(trades) == 0: |
| 403 | + raise OperationalException( |
| 404 | + "Trades list is empty or None, cannot calculate average loss." |
| 405 | + ) |
| 406 | + |
| 407 | + losing_trades = [t for t in trades if t.net_gain < 0] |
| 408 | + |
| 409 | + if not losing_trades or len(losing_trades) == 0: |
| 410 | + return 0.0, 0.0 |
| 411 | + |
| 412 | + losses = [t.net_gain for t in losing_trades] |
| 413 | + average_loss = sum(losses) / len(losses) |
| 414 | + percentage_returns = [ |
| 415 | + (t.net_gain / t.cost) * 100.0 for t in losing_trades if t.cost > 0 |
| 416 | + ] |
| 417 | + average_return_percentage = ( |
| 418 | + sum(percentage_returns) / len(percentage_returns) |
| 419 | + if percentage_returns else 0.0 |
| 420 | + ) |
| 421 | + return average_loss, average_return_percentage |
| 422 | + |
| 423 | + |
287 | 424 | def get_median_trade_return(trades: List[Trade]) -> Tuple[float, float]: |
288 | 425 | """ |
289 | 426 | Calculate the median return from a list of trades. |
|
0 commit comments