investing-algorithm-framework 6.9.1__py3-none-any.whl → 7.19.15__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of investing-algorithm-framework might be problematic. Click here for more details.
- investing_algorithm_framework/__init__.py +147 -44
- investing_algorithm_framework/app/__init__.py +23 -6
- investing_algorithm_framework/app/algorithm/algorithm.py +5 -41
- investing_algorithm_framework/app/algorithm/algorithm_factory.py +17 -10
- investing_algorithm_framework/app/analysis/__init__.py +15 -0
- investing_algorithm_framework/app/analysis/backtest_data_ranges.py +121 -0
- investing_algorithm_framework/app/analysis/backtest_utils.py +107 -0
- investing_algorithm_framework/app/analysis/permutation.py +116 -0
- investing_algorithm_framework/app/analysis/ranking.py +297 -0
- investing_algorithm_framework/app/app.py +1322 -707
- investing_algorithm_framework/app/context.py +196 -88
- investing_algorithm_framework/app/eventloop.py +590 -0
- investing_algorithm_framework/app/reporting/__init__.py +16 -5
- investing_algorithm_framework/app/reporting/ascii.py +57 -202
- investing_algorithm_framework/app/reporting/backtest_report.py +284 -170
- investing_algorithm_framework/app/reporting/charts/__init__.py +10 -2
- investing_algorithm_framework/app/reporting/charts/entry_exist_signals.py +66 -0
- investing_algorithm_framework/app/reporting/charts/equity_curve.py +37 -0
- investing_algorithm_framework/app/reporting/charts/equity_curve_drawdown.py +11 -26
- investing_algorithm_framework/app/reporting/charts/line_chart.py +11 -0
- investing_algorithm_framework/app/reporting/charts/ohlcv_data_completeness.py +51 -0
- investing_algorithm_framework/app/reporting/charts/rolling_sharp_ratio.py +1 -1
- investing_algorithm_framework/app/reporting/generate.py +100 -114
- investing_algorithm_framework/app/reporting/tables/key_metrics_table.py +40 -32
- investing_algorithm_framework/app/reporting/tables/time_metrics_table.py +34 -27
- investing_algorithm_framework/app/reporting/tables/trade_metrics_table.py +23 -19
- investing_algorithm_framework/app/reporting/tables/trades_table.py +1 -1
- investing_algorithm_framework/app/reporting/tables/utils.py +1 -0
- investing_algorithm_framework/app/reporting/templates/report_template.html.j2 +10 -16
- investing_algorithm_framework/app/strategy.py +315 -175
- investing_algorithm_framework/app/task.py +5 -3
- investing_algorithm_framework/cli/cli.py +30 -12
- investing_algorithm_framework/cli/deploy_to_aws_lambda.py +131 -34
- investing_algorithm_framework/cli/initialize_app.py +20 -1
- investing_algorithm_framework/cli/templates/app_aws_lambda_function.py.template +18 -6
- investing_algorithm_framework/cli/templates/aws_lambda_dockerfile.template +22 -0
- investing_algorithm_framework/cli/templates/aws_lambda_dockerignore.template +92 -0
- investing_algorithm_framework/cli/templates/aws_lambda_requirements.txt.template +2 -2
- investing_algorithm_framework/cli/templates/azure_function_requirements.txt.template +1 -1
- investing_algorithm_framework/create_app.py +3 -5
- investing_algorithm_framework/dependency_container.py +25 -39
- investing_algorithm_framework/domain/__init__.py +45 -38
- investing_algorithm_framework/domain/backtesting/__init__.py +21 -0
- investing_algorithm_framework/domain/backtesting/backtest.py +503 -0
- investing_algorithm_framework/domain/backtesting/backtest_date_range.py +96 -0
- investing_algorithm_framework/domain/backtesting/backtest_evaluation_focuss.py +242 -0
- investing_algorithm_framework/domain/backtesting/backtest_metrics.py +459 -0
- investing_algorithm_framework/domain/backtesting/backtest_permutation_test.py +275 -0
- investing_algorithm_framework/domain/backtesting/backtest_run.py +605 -0
- investing_algorithm_framework/domain/backtesting/backtest_summary_metrics.py +162 -0
- investing_algorithm_framework/domain/backtesting/combine_backtests.py +280 -0
- investing_algorithm_framework/domain/config.py +27 -0
- investing_algorithm_framework/domain/constants.py +6 -34
- investing_algorithm_framework/domain/data_provider.py +200 -56
- investing_algorithm_framework/domain/exceptions.py +34 -1
- investing_algorithm_framework/domain/models/__init__.py +10 -19
- investing_algorithm_framework/domain/models/base_model.py +0 -6
- investing_algorithm_framework/domain/models/data/__init__.py +7 -0
- investing_algorithm_framework/domain/models/data/data_source.py +214 -0
- investing_algorithm_framework/domain/models/{market_data_type.py → data/data_type.py} +7 -7
- investing_algorithm_framework/domain/models/market/market_credential.py +6 -0
- investing_algorithm_framework/domain/models/order/order.py +34 -13
- investing_algorithm_framework/domain/models/order/order_status.py +1 -1
- investing_algorithm_framework/domain/models/order/order_type.py +1 -1
- investing_algorithm_framework/domain/models/portfolio/portfolio.py +14 -1
- investing_algorithm_framework/domain/models/portfolio/portfolio_configuration.py +5 -1
- investing_algorithm_framework/domain/models/portfolio/portfolio_snapshot.py +51 -11
- investing_algorithm_framework/domain/models/position/__init__.py +2 -1
- investing_algorithm_framework/domain/models/position/position.py +9 -0
- investing_algorithm_framework/domain/models/position/position_size.py +41 -0
- investing_algorithm_framework/domain/models/risk_rules/__init__.py +7 -0
- investing_algorithm_framework/domain/models/risk_rules/stop_loss_rule.py +51 -0
- investing_algorithm_framework/domain/models/risk_rules/take_profit_rule.py +55 -0
- investing_algorithm_framework/domain/models/snapshot_interval.py +0 -1
- investing_algorithm_framework/domain/models/strategy_profile.py +19 -151
- investing_algorithm_framework/domain/models/time_frame.py +7 -0
- investing_algorithm_framework/domain/models/time_interval.py +33 -0
- investing_algorithm_framework/domain/models/time_unit.py +63 -1
- investing_algorithm_framework/domain/models/trade/__init__.py +0 -2
- investing_algorithm_framework/domain/models/trade/trade.py +56 -32
- investing_algorithm_framework/domain/models/trade/trade_status.py +8 -2
- investing_algorithm_framework/domain/models/trade/trade_stop_loss.py +106 -41
- investing_algorithm_framework/domain/models/trade/trade_take_profit.py +161 -99
- investing_algorithm_framework/domain/order_executor.py +19 -0
- investing_algorithm_framework/domain/portfolio_provider.py +20 -1
- investing_algorithm_framework/domain/services/__init__.py +0 -13
- investing_algorithm_framework/domain/strategy.py +1 -29
- investing_algorithm_framework/domain/utils/__init__.py +5 -1
- investing_algorithm_framework/domain/utils/custom_tqdm.py +22 -0
- investing_algorithm_framework/domain/utils/jupyter_notebook_detection.py +19 -0
- investing_algorithm_framework/domain/utils/polars.py +17 -14
- investing_algorithm_framework/download_data.py +40 -10
- investing_algorithm_framework/infrastructure/__init__.py +13 -25
- investing_algorithm_framework/infrastructure/data_providers/__init__.py +7 -4
- investing_algorithm_framework/infrastructure/data_providers/ccxt.py +811 -546
- investing_algorithm_framework/infrastructure/data_providers/csv.py +433 -122
- investing_algorithm_framework/infrastructure/data_providers/pandas.py +599 -0
- investing_algorithm_framework/infrastructure/database/__init__.py +6 -2
- investing_algorithm_framework/infrastructure/database/sql_alchemy.py +81 -0
- investing_algorithm_framework/infrastructure/models/__init__.py +0 -13
- investing_algorithm_framework/infrastructure/models/order/order.py +9 -3
- investing_algorithm_framework/infrastructure/models/trades/trade_stop_loss.py +27 -8
- investing_algorithm_framework/infrastructure/models/trades/trade_take_profit.py +21 -7
- investing_algorithm_framework/infrastructure/order_executors/__init__.py +2 -0
- investing_algorithm_framework/infrastructure/order_executors/backtest_oder_executor.py +28 -0
- investing_algorithm_framework/infrastructure/repositories/repository.py +16 -2
- investing_algorithm_framework/infrastructure/repositories/trade_repository.py +2 -2
- investing_algorithm_framework/infrastructure/repositories/trade_stop_loss_repository.py +6 -0
- investing_algorithm_framework/infrastructure/repositories/trade_take_profit_repository.py +6 -0
- investing_algorithm_framework/infrastructure/services/__init__.py +0 -4
- investing_algorithm_framework/services/__init__.py +105 -8
- investing_algorithm_framework/services/backtesting/backtest_service.py +536 -476
- investing_algorithm_framework/services/configuration_service.py +14 -4
- investing_algorithm_framework/services/data_providers/__init__.py +5 -0
- investing_algorithm_framework/services/data_providers/data_provider_service.py +850 -0
- investing_algorithm_framework/{app/reporting → services}/metrics/__init__.py +48 -17
- investing_algorithm_framework/{app/reporting → services}/metrics/drawdown.py +10 -10
- investing_algorithm_framework/{app/reporting → services}/metrics/equity_curve.py +2 -2
- investing_algorithm_framework/{app/reporting → services}/metrics/exposure.py +60 -2
- investing_algorithm_framework/services/metrics/generate.py +358 -0
- investing_algorithm_framework/{app/reporting → services}/metrics/profit_factor.py +36 -0
- investing_algorithm_framework/{app/reporting → services}/metrics/recovery.py +2 -2
- investing_algorithm_framework/{app/reporting → services}/metrics/returns.py +146 -147
- investing_algorithm_framework/services/metrics/risk_free_rate.py +28 -0
- investing_algorithm_framework/{app/reporting/metrics/sharp_ratio.py → services/metrics/sharpe_ratio.py} +6 -10
- investing_algorithm_framework/{app/reporting → services}/metrics/sortino_ratio.py +3 -7
- investing_algorithm_framework/services/metrics/trades.py +500 -0
- investing_algorithm_framework/services/metrics/volatility.py +97 -0
- investing_algorithm_framework/{app/reporting → services}/metrics/win_rate.py +70 -3
- investing_algorithm_framework/services/order_service/order_backtest_service.py +21 -31
- investing_algorithm_framework/services/order_service/order_service.py +9 -71
- investing_algorithm_framework/services/portfolios/portfolio_provider_lookup.py +0 -2
- investing_algorithm_framework/services/portfolios/portfolio_service.py +3 -13
- investing_algorithm_framework/services/portfolios/portfolio_snapshot_service.py +62 -96
- investing_algorithm_framework/services/portfolios/portfolio_sync_service.py +0 -3
- investing_algorithm_framework/services/repository_service.py +5 -2
- investing_algorithm_framework/services/trade_order_evaluator/__init__.py +9 -0
- investing_algorithm_framework/services/trade_order_evaluator/backtest_trade_oder_evaluator.py +113 -0
- investing_algorithm_framework/services/trade_order_evaluator/default_trade_order_evaluator.py +51 -0
- investing_algorithm_framework/services/trade_order_evaluator/trade_order_evaluator.py +80 -0
- investing_algorithm_framework/services/trade_service/__init__.py +7 -1
- investing_algorithm_framework/services/trade_service/trade_service.py +51 -29
- investing_algorithm_framework/services/trade_service/trade_stop_loss_service.py +39 -0
- investing_algorithm_framework/services/trade_service/trade_take_profit_service.py +41 -0
- investing_algorithm_framework-7.19.15.dist-info/METADATA +537 -0
- {investing_algorithm_framework-6.9.1.dist-info → investing_algorithm_framework-7.19.15.dist-info}/RECORD +159 -148
- investing_algorithm_framework/app/reporting/evaluation.py +0 -243
- investing_algorithm_framework/app/reporting/metrics/risk_free_rate.py +0 -8
- investing_algorithm_framework/app/reporting/metrics/volatility.py +0 -69
- investing_algorithm_framework/cli/templates/requirements_azure_function.txt.template +0 -3
- investing_algorithm_framework/domain/models/backtesting/__init__.py +0 -9
- investing_algorithm_framework/domain/models/backtesting/backtest_date_range.py +0 -47
- investing_algorithm_framework/domain/models/backtesting/backtest_position.py +0 -120
- investing_algorithm_framework/domain/models/backtesting/backtest_reports_evaluation.py +0 -0
- investing_algorithm_framework/domain/models/backtesting/backtest_results.py +0 -440
- investing_algorithm_framework/domain/models/data_source.py +0 -21
- investing_algorithm_framework/domain/models/date_range.py +0 -64
- investing_algorithm_framework/domain/models/trade/trade_risk_type.py +0 -34
- investing_algorithm_framework/domain/models/trading_data_types.py +0 -48
- investing_algorithm_framework/domain/models/trading_time_frame.py +0 -223
- investing_algorithm_framework/domain/services/market_data_sources.py +0 -543
- investing_algorithm_framework/domain/services/market_service.py +0 -153
- investing_algorithm_framework/domain/services/observable.py +0 -51
- investing_algorithm_framework/domain/services/observer.py +0 -19
- investing_algorithm_framework/infrastructure/models/market_data_sources/__init__.py +0 -16
- investing_algorithm_framework/infrastructure/models/market_data_sources/ccxt.py +0 -746
- investing_algorithm_framework/infrastructure/models/market_data_sources/csv.py +0 -270
- investing_algorithm_framework/infrastructure/models/market_data_sources/pandas.py +0 -312
- investing_algorithm_framework/infrastructure/services/market_service/__init__.py +0 -5
- investing_algorithm_framework/infrastructure/services/market_service/ccxt_market_service.py +0 -471
- investing_algorithm_framework/infrastructure/services/performance_service/__init__.py +0 -7
- investing_algorithm_framework/infrastructure/services/performance_service/backtest_performance_service.py +0 -2
- investing_algorithm_framework/infrastructure/services/performance_service/performance_service.py +0 -322
- investing_algorithm_framework/services/market_data_source_service/__init__.py +0 -10
- investing_algorithm_framework/services/market_data_source_service/backtest_market_data_source_service.py +0 -269
- investing_algorithm_framework/services/market_data_source_service/data_provider_service.py +0 -350
- investing_algorithm_framework/services/market_data_source_service/market_data_source_service.py +0 -377
- investing_algorithm_framework/services/strategy_orchestrator_service.py +0 -296
- investing_algorithm_framework-6.9.1.dist-info/METADATA +0 -440
- /investing_algorithm_framework/{app/reporting → services}/metrics/alpha.py +0 -0
- /investing_algorithm_framework/{app/reporting → services}/metrics/beta.py +0 -0
- /investing_algorithm_framework/{app/reporting → services}/metrics/cagr.py +0 -0
- /investing_algorithm_framework/{app/reporting → services}/metrics/calmar_ratio.py +0 -0
- /investing_algorithm_framework/{app/reporting → services}/metrics/mean_daily_return.py +0 -0
- /investing_algorithm_framework/{app/reporting → services}/metrics/price_efficiency.py +0 -0
- /investing_algorithm_framework/{app/reporting → services}/metrics/standard_deviation.py +0 -0
- /investing_algorithm_framework/{app/reporting → services}/metrics/treynor_ratio.py +0 -0
- /investing_algorithm_framework/{app/reporting → services}/metrics/ulcer.py +0 -0
- /investing_algorithm_framework/{app/reporting → services}/metrics/value_at_risk.py +0 -0
- {investing_algorithm_framework-6.9.1.dist-info → investing_algorithm_framework-7.19.15.dist-info}/LICENSE +0 -0
- {investing_algorithm_framework-6.9.1.dist-info → investing_algorithm_framework-7.19.15.dist-info}/WHEEL +0 -0
- {investing_algorithm_framework-6.9.1.dist-info → investing_algorithm_framework-7.19.15.dist-info}/entry_points.txt +0 -0
|
@@ -1,440 +0,0 @@
|
|
|
1
|
-
from dataclasses import dataclass, field
|
|
2
|
-
from datetime import datetime, timezone
|
|
3
|
-
from logging import getLogger
|
|
4
|
-
from typing import List, Optional
|
|
5
|
-
|
|
6
|
-
from investing_algorithm_framework.domain.constants import DATETIME_FORMAT
|
|
7
|
-
from investing_algorithm_framework.domain.models \
|
|
8
|
-
.backtesting.backtest_date_range import BacktestDateRange
|
|
9
|
-
from investing_algorithm_framework.domain.models.base_model import BaseModel
|
|
10
|
-
from investing_algorithm_framework.domain.models.order import Order
|
|
11
|
-
from investing_algorithm_framework.domain.models.order import OrderSide, \
|
|
12
|
-
OrderStatus
|
|
13
|
-
from investing_algorithm_framework.domain.models.portfolio \
|
|
14
|
-
.portfolio_snapshot import PortfolioSnapshot
|
|
15
|
-
from investing_algorithm_framework.domain.models.position import Position
|
|
16
|
-
from investing_algorithm_framework.domain.models.trade import Trade, \
|
|
17
|
-
TradeStatus
|
|
18
|
-
|
|
19
|
-
logger = getLogger(__name__)
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
@dataclass
|
|
23
|
-
class BacktestResult(BaseModel):
|
|
24
|
-
"""
|
|
25
|
-
Class that represents a backtest result. The backtest result
|
|
26
|
-
contains information about the trades, positions, portfolio and
|
|
27
|
-
trades of backtest.
|
|
28
|
-
|
|
29
|
-
Attributes:
|
|
30
|
-
backtest_date_range (BacktestDateRange): The date range of
|
|
31
|
-
the backtest.
|
|
32
|
-
trading_symbol (str): The trading symbol of the backtest.
|
|
33
|
-
name (str): The name of the backtest.
|
|
34
|
-
initial_unallocated (float): The initial unallocated amount
|
|
35
|
-
of the backtest.
|
|
36
|
-
symbols (List[str]): The symbols of the backtest.
|
|
37
|
-
number_of_runs (int): The number of strategy runs of the backtest.
|
|
38
|
-
portfolio_snapshots (List[PortfolioSnapshot]): The portfolio
|
|
39
|
-
snapshots of the backtest.
|
|
40
|
-
trades (List[Trade]): All trades of the backtest.
|
|
41
|
-
orders (List[Order]): All orders of the backtest.
|
|
42
|
-
positions (List[Position]): All positions of the backtest.
|
|
43
|
-
created_at (Optional[datetime]): The date and time when the
|
|
44
|
-
backtest was created.
|
|
45
|
-
backtest_start_date (Optional[datetime]): The start date of the
|
|
46
|
-
backtest.
|
|
47
|
-
backtest_end_date (Optional[datetime]): The end date of the
|
|
48
|
-
backtest.
|
|
49
|
-
number_of_days (int): The number of days of the backtest.
|
|
50
|
-
number_of_trades (int): The number of trades of the backtest.
|
|
51
|
-
number_of_trades_closed (int): The number of closed trades
|
|
52
|
-
of the backtest.
|
|
53
|
-
number_of_trades_open (int): The number of open trades
|
|
54
|
-
of the backtest.
|
|
55
|
-
percentage_positive_trades (float): The percentage of positive
|
|
56
|
-
trades of the backtest.
|
|
57
|
-
percentage_negative_trades (float): The percentage of negative
|
|
58
|
-
trades of the backtest.
|
|
59
|
-
total_net_gain_percentage (float): The total net gain percentage
|
|
60
|
-
of the backtest.
|
|
61
|
-
growth (float): The growth of the backtest.
|
|
62
|
-
growth_percentage (float): The growth percentage of the backtest.
|
|
63
|
-
total_net_gain (float): The total net gain of the backtest.
|
|
64
|
-
total_cost (float): The total cost of the backtest.
|
|
65
|
-
total_value (float): The total value of the backtest.
|
|
66
|
-
average_trade_duration (float): The average trade duration
|
|
67
|
-
of the backtest in hours.
|
|
68
|
-
average_trade_size (float): The average trade size of the
|
|
69
|
-
backtest in the trading symbol.
|
|
70
|
-
"""
|
|
71
|
-
backtest_date_range: BacktestDateRange
|
|
72
|
-
trading_symbol: str
|
|
73
|
-
name: str
|
|
74
|
-
initial_unallocated: float
|
|
75
|
-
number_of_runs: int
|
|
76
|
-
portfolio_snapshots: List[PortfolioSnapshot]
|
|
77
|
-
trades: List[Trade]
|
|
78
|
-
orders: List[Order]
|
|
79
|
-
positions: List[Position]
|
|
80
|
-
created_at: Optional[datetime] = field(default=None)
|
|
81
|
-
backtest_start_date: Optional[datetime] = field(default=None)
|
|
82
|
-
backtest_end_date: Optional[datetime] = field(default=None)
|
|
83
|
-
symbols: List[str] = field(default_factory=list)
|
|
84
|
-
number_of_days: int = 0
|
|
85
|
-
number_of_trades: int = 0
|
|
86
|
-
number_of_trades_closed: int = 0
|
|
87
|
-
number_of_trades_open: int = 0
|
|
88
|
-
number_of_orders: int = 0
|
|
89
|
-
number_of_positions: int = 0
|
|
90
|
-
percentage_positive_trades: float = 0.0
|
|
91
|
-
percentage_negative_trades: float = 0.0
|
|
92
|
-
total_net_gain_percentage: float = 0.0
|
|
93
|
-
growth: float = 0.0
|
|
94
|
-
growth_percentage: float = 0.0
|
|
95
|
-
total_net_gain: float = 0.0
|
|
96
|
-
total_cost: float = 0.0
|
|
97
|
-
total_value: float = 0.0
|
|
98
|
-
average_trade_duration: float = 0.0
|
|
99
|
-
average_trade_size: float = 0.0
|
|
100
|
-
|
|
101
|
-
def __post_init__(self):
|
|
102
|
-
if self.created_at is None:
|
|
103
|
-
self.created_at = datetime.now(tz=timezone.utc)
|
|
104
|
-
|
|
105
|
-
self.number_of_days = (
|
|
106
|
-
self.backtest_date_range.end_date
|
|
107
|
-
- self.backtest_date_range.start_date
|
|
108
|
-
).days
|
|
109
|
-
self.backtest_start_date = self.backtest_date_range.start_date
|
|
110
|
-
self.backtest_end_date = self.backtest_date_range.end_date
|
|
111
|
-
self.growth = self.portfolio_snapshots[-1].total_value \
|
|
112
|
-
- self.portfolio_snapshots[0].total_value
|
|
113
|
-
self.growth_percentage = self.growth / self.initial_unallocated * 100.0
|
|
114
|
-
self.number_of_orders = \
|
|
115
|
-
len(self.orders) if self.orders is not None else 0
|
|
116
|
-
self.number_of_positions = \
|
|
117
|
-
len(self.positions) if self.positions is not None else 0
|
|
118
|
-
last_portfolio_snapshot = \
|
|
119
|
-
self.portfolio_snapshots[-1] if self.portfolio_snapshots else None
|
|
120
|
-
self.total_value = last_portfolio_snapshot.total_value \
|
|
121
|
-
if last_portfolio_snapshot is not None else 0.0
|
|
122
|
-
|
|
123
|
-
number_of_negative_trades = 0.0
|
|
124
|
-
number_of_positive_trades = 0.0
|
|
125
|
-
number_of_trades_closed = 0
|
|
126
|
-
number_of_trades_open = 0
|
|
127
|
-
total_duration = 0
|
|
128
|
-
total_trade_size = 0.0
|
|
129
|
-
|
|
130
|
-
if self.trades is not None:
|
|
131
|
-
for trade in self.trades:
|
|
132
|
-
self.total_net_gain += trade.net_gain
|
|
133
|
-
self.total_cost += trade.cost
|
|
134
|
-
total_duration += \
|
|
135
|
-
((trade.closed_at - trade.opened_at).total_seconds() /
|
|
136
|
-
3600) if trade.closed_at else 0
|
|
137
|
-
total_trade_size += trade.size
|
|
138
|
-
if trade.status == TradeStatus.CLOSED.value:
|
|
139
|
-
number_of_trades_closed += 1
|
|
140
|
-
|
|
141
|
-
if trade.status == TradeStatus.OPEN.value:
|
|
142
|
-
number_of_trades_open += 1
|
|
143
|
-
|
|
144
|
-
if trade.net_gain > 0:
|
|
145
|
-
number_of_positive_trades += 1
|
|
146
|
-
elif trade.net_gain < 0:
|
|
147
|
-
number_of_negative_trades += 1
|
|
148
|
-
|
|
149
|
-
self.total_net_gain_percentage = \
|
|
150
|
-
(self.total_net_gain / self.initial_unallocated) * 100.0 \
|
|
151
|
-
if self.initial_unallocated > 0 else 0.0
|
|
152
|
-
self.percentage_positive_trades = \
|
|
153
|
-
(number_of_positive_trades / len(self.trades)) * 100.0 \
|
|
154
|
-
if len(self.trades) > 0 else 0.0
|
|
155
|
-
self.percentage_negative_trades = \
|
|
156
|
-
(number_of_negative_trades / len(self.trades)) * 100.0 \
|
|
157
|
-
if len(self.trades) > 0 else 0.0
|
|
158
|
-
self.number_of_trades_closed = number_of_trades_closed
|
|
159
|
-
self.number_of_trades_open = number_of_trades_open
|
|
160
|
-
self.number_of_trades = len(self.trades)
|
|
161
|
-
self.average_trade_duration = \
|
|
162
|
-
total_duration / self.number_of_trades \
|
|
163
|
-
if self.number_of_trades > 0 else 0.0
|
|
164
|
-
self.average_trade_size = \
|
|
165
|
-
total_trade_size / self.number_of_trades \
|
|
166
|
-
if self.number_of_trades > 0 else 0.0
|
|
167
|
-
|
|
168
|
-
# Determine all the symbols that are being traded in the backtest
|
|
169
|
-
if self.symbols is None:
|
|
170
|
-
self.symbols = []
|
|
171
|
-
|
|
172
|
-
for order in self.orders:
|
|
173
|
-
if order.target_symbol not in self.symbols:
|
|
174
|
-
self.symbols.append(order.target_symbol)
|
|
175
|
-
|
|
176
|
-
def to_dict(self):
|
|
177
|
-
"""
|
|
178
|
-
Convert the backtest report to a dictionary. So it can be
|
|
179
|
-
saved to a file.
|
|
180
|
-
|
|
181
|
-
Returns:
|
|
182
|
-
dict: The backtest report as a dictionary
|
|
183
|
-
"""
|
|
184
|
-
|
|
185
|
-
return {
|
|
186
|
-
"name": self.name,
|
|
187
|
-
"backtest_date_range_identifier": self.backtest_date_range.name,
|
|
188
|
-
"backtest_start_date": self.backtest_date_range.start_date
|
|
189
|
-
.strftime(DATETIME_FORMAT),
|
|
190
|
-
"backtest_end_date": self.backtest_date_range.end_date
|
|
191
|
-
.strftime(DATETIME_FORMAT),
|
|
192
|
-
"number_of_runs": self.number_of_runs,
|
|
193
|
-
"symbols": self.symbols,
|
|
194
|
-
"number_of_days": self.number_of_days,
|
|
195
|
-
"number_of_orders": self.number_of_orders,
|
|
196
|
-
"number_of_positions": self.number_of_positions,
|
|
197
|
-
"percentage_positive_trades": self.percentage_positive_trades,
|
|
198
|
-
"percentage_negative_trades": self.percentage_negative_trades,
|
|
199
|
-
"number_of_trades_closed": self.number_of_trades_closed,
|
|
200
|
-
"number_of_trades_open": self.number_of_trades_open,
|
|
201
|
-
"total_cost": self.total_cost,
|
|
202
|
-
"growth_percentage": self.growth_percentage,
|
|
203
|
-
"growth": self.growth,
|
|
204
|
-
"initial_unallocated": self.initial_unallocated,
|
|
205
|
-
"trading_symbol": self.trading_symbol,
|
|
206
|
-
"total_net_gain_percentage": self.total_net_gain_percentage,
|
|
207
|
-
"total_net_gain": self.total_net_gain,
|
|
208
|
-
"total_value": self.total_value,
|
|
209
|
-
"average_trade_duration": self.average_trade_duration,
|
|
210
|
-
"average_trade_size": self.average_trade_size,
|
|
211
|
-
# "positions": [position.to_dict() for position in self.positions],
|
|
212
|
-
"trades": [
|
|
213
|
-
trade.to_dict(datetime_format=DATETIME_FORMAT)
|
|
214
|
-
for trade in self.trades
|
|
215
|
-
],
|
|
216
|
-
"orders": [
|
|
217
|
-
order.to_dict(datetime_format=DATETIME_FORMAT)
|
|
218
|
-
for order in self.orders
|
|
219
|
-
],
|
|
220
|
-
"portfolio_snapshots": [
|
|
221
|
-
snapshot.to_dict(datetime_format=DATETIME_FORMAT)
|
|
222
|
-
for snapshot in self.portfolio_snapshots
|
|
223
|
-
],
|
|
224
|
-
"created_at": self.created_at.strftime(DATETIME_FORMAT),
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
@staticmethod
|
|
228
|
-
def from_dict(data):
|
|
229
|
-
"""
|
|
230
|
-
Factory method to create a backtest report from a dictionary.
|
|
231
|
-
"""
|
|
232
|
-
|
|
233
|
-
backtest_date_range = BacktestDateRange(
|
|
234
|
-
start_date=datetime.strptime(
|
|
235
|
-
data["backtest_start_date"], DATETIME_FORMAT),
|
|
236
|
-
end_date=datetime.strptime(
|
|
237
|
-
data["backtest_end_date"], DATETIME_FORMAT)
|
|
238
|
-
)
|
|
239
|
-
portfolio_snapshots_data = data.get("portfolio_snapshots", None)
|
|
240
|
-
|
|
241
|
-
if portfolio_snapshots_data is None:
|
|
242
|
-
portfolio_snapshots = []
|
|
243
|
-
else:
|
|
244
|
-
portfolio_snapshots = [
|
|
245
|
-
PortfolioSnapshot.from_dict(snapshot)
|
|
246
|
-
for snapshot in portfolio_snapshots_data
|
|
247
|
-
]
|
|
248
|
-
|
|
249
|
-
positions_data = data.get("positions", None)
|
|
250
|
-
|
|
251
|
-
if positions_data is not None:
|
|
252
|
-
positions = [
|
|
253
|
-
Position.from_dict(position) for position in positions_data
|
|
254
|
-
]
|
|
255
|
-
else:
|
|
256
|
-
positions = []
|
|
257
|
-
|
|
258
|
-
trades_data = data.get("trades", None)
|
|
259
|
-
|
|
260
|
-
if trades_data is not None:
|
|
261
|
-
trades = [Trade.from_dict(trade) for trade in trades_data]
|
|
262
|
-
else:
|
|
263
|
-
trades = []
|
|
264
|
-
|
|
265
|
-
orders_data = data.get("orders", None)
|
|
266
|
-
|
|
267
|
-
if orders_data is not None:
|
|
268
|
-
orders = [Order.from_dict(order) for order in orders_data]
|
|
269
|
-
else:
|
|
270
|
-
orders = []
|
|
271
|
-
|
|
272
|
-
report = BacktestResult(
|
|
273
|
-
name=data["name"],
|
|
274
|
-
number_of_runs=data["number_of_runs"],
|
|
275
|
-
backtest_date_range=backtest_date_range,
|
|
276
|
-
symbols=data["symbols"],
|
|
277
|
-
number_of_orders=data["number_of_orders"],
|
|
278
|
-
number_of_positions=data["number_of_positions"],
|
|
279
|
-
percentage_positive_trades=data["percentage_positive_trades"],
|
|
280
|
-
percentage_negative_trades=data["percentage_negative_trades"],
|
|
281
|
-
number_of_trades_closed=data["number_of_trades_closed"],
|
|
282
|
-
number_of_trades_open=data["number_of_trades_open"],
|
|
283
|
-
total_cost=float(data["total_cost"]),
|
|
284
|
-
growth_percentage=float(data["growth_percentage"]),
|
|
285
|
-
growth=float(data["growth"]),
|
|
286
|
-
initial_unallocated=float(data["initial_unallocated"]),
|
|
287
|
-
trading_symbol=data["trading_symbol"],
|
|
288
|
-
total_net_gain_percentage=float(data["total_net_gain_percentage"]),
|
|
289
|
-
total_net_gain=float(data["total_net_gain"]),
|
|
290
|
-
total_value=float(data["total_value"]),
|
|
291
|
-
average_trade_duration=data["average_trade_duration"],
|
|
292
|
-
average_trade_size=float(data["average_trade_size"]),
|
|
293
|
-
created_at=datetime.strptime(
|
|
294
|
-
data["created_at"], DATETIME_FORMAT
|
|
295
|
-
),
|
|
296
|
-
backtest_start_date=datetime.strptime(
|
|
297
|
-
data["backtest_start_date"], DATETIME_FORMAT
|
|
298
|
-
),
|
|
299
|
-
backtest_end_date=datetime.strptime(
|
|
300
|
-
data["backtest_end_date"], DATETIME_FORMAT
|
|
301
|
-
),
|
|
302
|
-
number_of_days=data["number_of_days"],
|
|
303
|
-
portfolio_snapshots=portfolio_snapshots,
|
|
304
|
-
# position_snapshots=position_snapshots,
|
|
305
|
-
trades=trades,
|
|
306
|
-
orders=orders,
|
|
307
|
-
positions=positions,
|
|
308
|
-
)
|
|
309
|
-
|
|
310
|
-
return report
|
|
311
|
-
|
|
312
|
-
def get_orders(
|
|
313
|
-
self,
|
|
314
|
-
target_symbol=None,
|
|
315
|
-
order_side=None,
|
|
316
|
-
order_status=None,
|
|
317
|
-
created_at_lt=None,
|
|
318
|
-
) -> List[Order]:
|
|
319
|
-
"""
|
|
320
|
-
Get the orders of a backtest report
|
|
321
|
-
|
|
322
|
-
Args:
|
|
323
|
-
target_symbol (str): The target_symbol
|
|
324
|
-
order_side (str): The order side
|
|
325
|
-
status (str): The order status
|
|
326
|
-
created_at_lt (datetime): The created_at date to filter the orders
|
|
327
|
-
|
|
328
|
-
Returns:
|
|
329
|
-
list: The orders of the backtest report
|
|
330
|
-
"""
|
|
331
|
-
selection = self.orders
|
|
332
|
-
|
|
333
|
-
if created_at_lt is not None:
|
|
334
|
-
selection = [
|
|
335
|
-
order for order in selection
|
|
336
|
-
if order.created_at < created_at_lt
|
|
337
|
-
]
|
|
338
|
-
|
|
339
|
-
if target_symbol is not None:
|
|
340
|
-
selection = [
|
|
341
|
-
order for order in selection
|
|
342
|
-
if order.target_symbol == target_symbol
|
|
343
|
-
]
|
|
344
|
-
|
|
345
|
-
if order_side is not None:
|
|
346
|
-
order_side = OrderSide.from_value(order_side)
|
|
347
|
-
selection = [
|
|
348
|
-
order for order in selection
|
|
349
|
-
if order.order_side == order_side.value
|
|
350
|
-
]
|
|
351
|
-
|
|
352
|
-
if order_status is not None:
|
|
353
|
-
status = OrderStatus.from_value(order_status)
|
|
354
|
-
selection = [
|
|
355
|
-
order for order in selection
|
|
356
|
-
if order.status == status.value
|
|
357
|
-
]
|
|
358
|
-
|
|
359
|
-
return selection
|
|
360
|
-
|
|
361
|
-
def get_trades(self, target_symbol=None, trade_status=None) -> List[Trade]:
|
|
362
|
-
"""
|
|
363
|
-
Get the trades of a backtest report
|
|
364
|
-
|
|
365
|
-
Args:
|
|
366
|
-
target_symbol (str): The target_symbol
|
|
367
|
-
trade_status: The trade_status
|
|
368
|
-
|
|
369
|
-
Returns:
|
|
370
|
-
list: The trades of the backtest report
|
|
371
|
-
"""
|
|
372
|
-
selection = self.trades
|
|
373
|
-
|
|
374
|
-
if target_symbol is not None:
|
|
375
|
-
selection = [
|
|
376
|
-
trade for trade in selection
|
|
377
|
-
if trade.target_symbol == target_symbol
|
|
378
|
-
]
|
|
379
|
-
|
|
380
|
-
if trade_status is not None:
|
|
381
|
-
trade_status = TradeStatus.from_value(trade_status)
|
|
382
|
-
selection = [
|
|
383
|
-
trade for trade in selection
|
|
384
|
-
if trade.status == trade_status.value
|
|
385
|
-
]
|
|
386
|
-
|
|
387
|
-
return selection
|
|
388
|
-
|
|
389
|
-
def get_positions(self, symbol=None) -> List[Position]:
|
|
390
|
-
"""
|
|
391
|
-
Get the positions of the backtest report
|
|
392
|
-
|
|
393
|
-
Args:
|
|
394
|
-
symbol (str): The symbol
|
|
395
|
-
|
|
396
|
-
Returns:
|
|
397
|
-
list: The positions of the backtest report
|
|
398
|
-
"""
|
|
399
|
-
|
|
400
|
-
# Get the last portfolio snapshot
|
|
401
|
-
last_portfolio_snapshot = \
|
|
402
|
-
self.portfolio_snapshots[-1] if self.portfolio_snapshots else None
|
|
403
|
-
selection = []
|
|
404
|
-
|
|
405
|
-
if last_portfolio_snapshot is not None:
|
|
406
|
-
positions = last_portfolio_snapshot.position_snapshots
|
|
407
|
-
|
|
408
|
-
if symbol is not None:
|
|
409
|
-
selection = [
|
|
410
|
-
position for position in positions
|
|
411
|
-
if position.symbol == symbol
|
|
412
|
-
]
|
|
413
|
-
|
|
414
|
-
return selection
|
|
415
|
-
|
|
416
|
-
return selection
|
|
417
|
-
|
|
418
|
-
def get_portfolio_snapshots(
|
|
419
|
-
self,
|
|
420
|
-
created_at_lt: Optional[datetime] = None
|
|
421
|
-
) -> List[PortfolioSnapshot]:
|
|
422
|
-
"""
|
|
423
|
-
Get the portfolio snapshots of the backtest report
|
|
424
|
-
|
|
425
|
-
Args:
|
|
426
|
-
created_at_lt (datetime): The created_at date to filter
|
|
427
|
-
the snapshots
|
|
428
|
-
|
|
429
|
-
Returns:
|
|
430
|
-
list: The portfolio snapshots of the backtest report
|
|
431
|
-
"""
|
|
432
|
-
selection = self.portfolio_snapshots
|
|
433
|
-
|
|
434
|
-
if created_at_lt is not None:
|
|
435
|
-
selection = [
|
|
436
|
-
snapshot for snapshot in selection
|
|
437
|
-
if snapshot.created_at < created_at_lt
|
|
438
|
-
]
|
|
439
|
-
|
|
440
|
-
return selection
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
class DataSource:
|
|
2
|
-
"""
|
|
3
|
-
Base class for data sources.
|
|
4
|
-
"""
|
|
5
|
-
|
|
6
|
-
def __init__(
|
|
7
|
-
self,
|
|
8
|
-
data_type: str,
|
|
9
|
-
symbol: str,
|
|
10
|
-
market: str,
|
|
11
|
-
time_frame: str,
|
|
12
|
-
window_size: int,
|
|
13
|
-
key: str, name: str
|
|
14
|
-
):
|
|
15
|
-
self.name = name
|
|
16
|
-
self.key = key
|
|
17
|
-
self.data_type = data_type
|
|
18
|
-
self.symbol = symbol
|
|
19
|
-
self.market = market
|
|
20
|
-
self.time_frame = time_frame
|
|
21
|
-
self.window_size = window_size
|
|
@@ -1,64 +0,0 @@
|
|
|
1
|
-
from datetime import datetime
|
|
2
|
-
from typing import Union
|
|
3
|
-
from .backtesting.backtest_date_range import BacktestDateRange
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
class DateRange:
|
|
7
|
-
"""
|
|
8
|
-
DateRange class. This class is used to define a date range and the name of
|
|
9
|
-
the range. Also, it can be used to store trading metadata such as
|
|
10
|
-
classification of the trend (Up or Down).
|
|
11
|
-
"""
|
|
12
|
-
|
|
13
|
-
def __init__(
|
|
14
|
-
self,
|
|
15
|
-
start_date: datetime,
|
|
16
|
-
end_date: datetime,
|
|
17
|
-
name: str,
|
|
18
|
-
up_trend: bool = False,
|
|
19
|
-
down_trend: bool = False
|
|
20
|
-
):
|
|
21
|
-
self.start_date = start_date
|
|
22
|
-
self.end_date = end_date
|
|
23
|
-
self.name = name
|
|
24
|
-
self._up_trend = up_trend
|
|
25
|
-
self._down_trend = down_trend
|
|
26
|
-
|
|
27
|
-
@property
|
|
28
|
-
def up_trend(self) -> Union[bool, None]:
|
|
29
|
-
|
|
30
|
-
if self._up_trend and not self._down_trend:
|
|
31
|
-
return True
|
|
32
|
-
else:
|
|
33
|
-
return None
|
|
34
|
-
|
|
35
|
-
@up_trend.setter
|
|
36
|
-
def up_trend(self, value: bool):
|
|
37
|
-
self._up_trend = value
|
|
38
|
-
|
|
39
|
-
@property
|
|
40
|
-
def down_trend(self) -> Union[bool, None]:
|
|
41
|
-
|
|
42
|
-
if self._down_trend and not self._up_trend:
|
|
43
|
-
return True
|
|
44
|
-
else:
|
|
45
|
-
return None
|
|
46
|
-
|
|
47
|
-
@down_trend.setter
|
|
48
|
-
def down_trend(self, value: bool):
|
|
49
|
-
self._down_trend = value
|
|
50
|
-
|
|
51
|
-
def __str__(self):
|
|
52
|
-
return f"DateRange({self.start_date}, {self.end_date}, {self.name})"
|
|
53
|
-
|
|
54
|
-
def __repr__(self):
|
|
55
|
-
return f"DateRange(Name: {self.name} " + \
|
|
56
|
-
f"Start date: {self.start_date} " + \
|
|
57
|
-
f"End date: {self.end_date})"
|
|
58
|
-
|
|
59
|
-
def to_backtest_date_range(self):
|
|
60
|
-
return BacktestDateRange(
|
|
61
|
-
start_date=self.start_date,
|
|
62
|
-
end_date=self.end_date,
|
|
63
|
-
name=self.name
|
|
64
|
-
)
|
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
from enum import Enum
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
class TradeRiskType(Enum):
|
|
5
|
-
FIXED = "FIXED"
|
|
6
|
-
TRAILING = "TRAILING"
|
|
7
|
-
|
|
8
|
-
@staticmethod
|
|
9
|
-
def from_string(value: str):
|
|
10
|
-
|
|
11
|
-
if isinstance(value, str):
|
|
12
|
-
for status in TradeRiskType:
|
|
13
|
-
|
|
14
|
-
if value.upper() == status.value:
|
|
15
|
-
return status
|
|
16
|
-
|
|
17
|
-
raise ValueError("Could not convert value to TradeRiskType")
|
|
18
|
-
|
|
19
|
-
@staticmethod
|
|
20
|
-
def from_value(value):
|
|
21
|
-
|
|
22
|
-
if isinstance(value, TradeRiskType):
|
|
23
|
-
for risk_type in TradeRiskType:
|
|
24
|
-
|
|
25
|
-
if value == risk_type:
|
|
26
|
-
return risk_type
|
|
27
|
-
|
|
28
|
-
elif isinstance(value, str):
|
|
29
|
-
return TradeRiskType.from_string(value)
|
|
30
|
-
|
|
31
|
-
raise ValueError("Could not convert value to TradeRiskType")
|
|
32
|
-
|
|
33
|
-
def equals(self, other):
|
|
34
|
-
return TradeRiskType.from_value(other) == self
|
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
from enum import Enum
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
class TradingDataType(Enum):
|
|
5
|
-
TICKER = 'TICKER'
|
|
6
|
-
ORDER_BOOK = 'ORDER_BOOK'
|
|
7
|
-
OHLCV = "OHLCV"
|
|
8
|
-
CUSTOM = "CUSTOM"
|
|
9
|
-
|
|
10
|
-
@staticmethod
|
|
11
|
-
def from_value(value):
|
|
12
|
-
|
|
13
|
-
if isinstance(value, TradingDataType):
|
|
14
|
-
for trading_data_type in TradingDataType:
|
|
15
|
-
|
|
16
|
-
if value == trading_data_type:
|
|
17
|
-
return trading_data_type
|
|
18
|
-
|
|
19
|
-
elif isinstance(value, str):
|
|
20
|
-
return TradingDataType.from_string(value)
|
|
21
|
-
|
|
22
|
-
raise ValueError(
|
|
23
|
-
"Could not convert value to trading data type"
|
|
24
|
-
)
|
|
25
|
-
|
|
26
|
-
@staticmethod
|
|
27
|
-
def from_string(value: str):
|
|
28
|
-
|
|
29
|
-
if isinstance(value, str):
|
|
30
|
-
for order_type in TradingDataType:
|
|
31
|
-
|
|
32
|
-
if value.upper() == order_type.value:
|
|
33
|
-
return order_type
|
|
34
|
-
|
|
35
|
-
raise ValueError(
|
|
36
|
-
"Could not convert value to trading data type"
|
|
37
|
-
)
|
|
38
|
-
|
|
39
|
-
def equals(self, other):
|
|
40
|
-
|
|
41
|
-
if other is None:
|
|
42
|
-
return False
|
|
43
|
-
|
|
44
|
-
if isinstance(other, Enum):
|
|
45
|
-
return self.value == other.value
|
|
46
|
-
|
|
47
|
-
else:
|
|
48
|
-
return TradingDataType.from_string(other) == self
|