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,11 +1,12 @@
|
|
|
1
|
-
from
|
|
1
|
+
from datetime import datetime
|
|
2
|
+
from typing import List, Dict, Any, Union
|
|
3
|
+
|
|
2
4
|
import pandas as pd
|
|
3
|
-
from datetime import datetime, timezone
|
|
4
5
|
|
|
5
|
-
from investing_algorithm_framework.domain import OperationalException,
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
6
|
+
from investing_algorithm_framework.domain import OperationalException, \
|
|
7
|
+
Position, PositionSize, TimeUnit, StrategyProfile, Trade, \
|
|
8
|
+
DataSource, OrderSide, StopLossRule, TakeProfitRule, Order, \
|
|
9
|
+
INDEX_DATETIME
|
|
9
10
|
from .context import Context
|
|
10
11
|
|
|
11
12
|
|
|
@@ -23,40 +24,62 @@ class TradingStrategy:
|
|
|
23
24
|
worker_id (optional): str - the id of the worker
|
|
24
25
|
strategy_id (optional): str - the id of the strategy
|
|
25
26
|
decorated (optional): function - the decorated function
|
|
26
|
-
|
|
27
|
-
sources to use for the strategy.
|
|
28
|
-
|
|
27
|
+
data_sources (List[DataSource] optional): the list of data
|
|
28
|
+
sources to use for the strategy. The data sources will be used
|
|
29
|
+
to indentify data providers that will be called to gather data
|
|
30
|
+
and pass to the strategy before its run.
|
|
31
|
+
metadata (optional): Dict[str, Any] - a dictionary
|
|
32
|
+
containing metadata about the strategy. This can be used to
|
|
33
|
+
store additional information about the strategy, such as its
|
|
34
|
+
author, version, description, params etc.
|
|
29
35
|
"""
|
|
30
|
-
time_unit:
|
|
36
|
+
time_unit: TimeUnit = None
|
|
31
37
|
interval: int = None
|
|
32
38
|
worker_id: str = None
|
|
33
39
|
strategy_id: str = None
|
|
34
40
|
decorated = None
|
|
35
|
-
|
|
41
|
+
data_sources: List[DataSource] = []
|
|
36
42
|
traces = None
|
|
37
43
|
context: Context = None
|
|
44
|
+
metadata: Dict[str, Any] = None
|
|
45
|
+
position_sizes: List[PositionSize] = []
|
|
46
|
+
stop_loss_rules: List[StopLossRule] = []
|
|
47
|
+
take_profit_rules: List[TakeProfitRule] = []
|
|
48
|
+
symbols: List[str] = []
|
|
49
|
+
trading_symbol: str = None
|
|
38
50
|
|
|
39
51
|
def __init__(
|
|
40
52
|
self,
|
|
41
53
|
strategy_id=None,
|
|
42
54
|
time_unit=None,
|
|
43
55
|
interval=None,
|
|
44
|
-
|
|
56
|
+
data_sources=None,
|
|
57
|
+
metadata=None,
|
|
58
|
+
position_sizes=None,
|
|
59
|
+
symbols=None,
|
|
60
|
+
trading_symbol=None,
|
|
45
61
|
worker_id=None,
|
|
46
62
|
decorated=None
|
|
47
63
|
):
|
|
48
|
-
|
|
49
64
|
if time_unit is not None:
|
|
50
65
|
self.time_unit = TimeUnit.from_value(time_unit)
|
|
66
|
+
else:
|
|
67
|
+
# Check if time_unit is None
|
|
68
|
+
if self.time_unit is None:
|
|
69
|
+
raise OperationalException(
|
|
70
|
+
f"Time unit attribute not set for "
|
|
71
|
+
f"strategy instance {self.strategy_id}"
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
self.time_unit = TimeUnit.from_value(self.time_unit)
|
|
51
75
|
|
|
52
76
|
if interval is not None:
|
|
53
77
|
self.interval = interval
|
|
54
78
|
|
|
55
|
-
if
|
|
56
|
-
self.
|
|
79
|
+
if data_sources is not None:
|
|
80
|
+
self.data_sources = data_sources
|
|
57
81
|
|
|
58
|
-
|
|
59
|
-
self.market_data_sources = market_data_sources
|
|
82
|
+
self.metadata = metadata
|
|
60
83
|
|
|
61
84
|
if decorated is not None:
|
|
62
85
|
self.decorated = decorated
|
|
@@ -73,12 +96,14 @@ class TradingStrategy:
|
|
|
73
96
|
else:
|
|
74
97
|
self.strategy_id = self.worker_id
|
|
75
98
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
99
|
+
if position_sizes is not None:
|
|
100
|
+
self.position_sizes = position_sizes
|
|
101
|
+
|
|
102
|
+
if symbols is not None:
|
|
103
|
+
self.symbols = symbols
|
|
104
|
+
|
|
105
|
+
if trading_symbol is not None:
|
|
106
|
+
self.trading_symbol = trading_symbol
|
|
82
107
|
|
|
83
108
|
# Check if interval is None
|
|
84
109
|
if self.interval is None:
|
|
@@ -89,12 +114,97 @@ class TradingStrategy:
|
|
|
89
114
|
# context initialization
|
|
90
115
|
self._context = None
|
|
91
116
|
self._last_run = None
|
|
117
|
+
self.stop_loss_rules_lookup = {}
|
|
118
|
+
self.take_profit_rules_lookup = {}
|
|
119
|
+
self.position_sizes_lookup = {}
|
|
92
120
|
|
|
93
|
-
def
|
|
121
|
+
def generate_buy_signals(
|
|
122
|
+
self, data: Dict[str, Any]
|
|
123
|
+
) -> Dict[str, pd.Series]:
|
|
94
124
|
"""
|
|
95
|
-
|
|
125
|
+
Function that needs to be implemented by the user.
|
|
126
|
+
This function should return a pandas Series containing the buy signals.
|
|
127
|
+
|
|
128
|
+
Args:
|
|
129
|
+
data (Dict[str, Any]): All the data that matched the
|
|
130
|
+
data sources of the strategy.
|
|
131
|
+
|
|
132
|
+
Returns:
|
|
133
|
+
Dict[str, Series]: A dictionary where the keys are the
|
|
134
|
+
symbols and the values are pandas Series containing
|
|
135
|
+
the buy signals. The series must be a pandas Series with
|
|
136
|
+
a boolean value for each row in the data source, e.g.
|
|
137
|
+
pd.Series([True, False, False, True, ...], index=data.index)
|
|
138
|
+
Also the return dictionary must look like:
|
|
139
|
+
{
|
|
140
|
+
"BTC": pd.Series([...]),
|
|
141
|
+
"ETH": pd.Series([...]),
|
|
142
|
+
...
|
|
143
|
+
}
|
|
144
|
+
where the symbols are exactly the same as defined in the
|
|
145
|
+
symbols attribute of the strategy.
|
|
146
|
+
"""
|
|
147
|
+
raise NotImplementedError(
|
|
148
|
+
"generate_buy_signals method not implemented"
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
def generate_sell_signals(
|
|
152
|
+
self, data: Dict[str, Any]
|
|
153
|
+
) -> Dict[str, pd.Series]:
|
|
154
|
+
"""
|
|
155
|
+
Function that needs to be implemented by the user.
|
|
156
|
+
This function should return a pandas Series containing
|
|
157
|
+
the sell signals.
|
|
158
|
+
|
|
159
|
+
Args:
|
|
160
|
+
data (Dict[str, Any]): All the data that is defined in the
|
|
161
|
+
data sources of the strategy. E.g. if there is a data source
|
|
162
|
+
defined as DataSource(identifier="bitvavo_btc_eur_1h",
|
|
163
|
+
symbol="BTC/EUR", time_frame="1h", data_type=DataType.OHLCV,
|
|
164
|
+
window_size=100, market="BITVAVO"), the data dictionary
|
|
165
|
+
will contain a key "bitvavo_btc_eur_1h"
|
|
166
|
+
with the corresponding data as a polars DataFrame.
|
|
167
|
+
|
|
168
|
+
Returns:
|
|
169
|
+
Dict[str, Series]: A dictionary where the keys are the
|
|
170
|
+
symbols and the values are pandas Series containing
|
|
171
|
+
the sell signals. The series must be a pandas Series with
|
|
172
|
+
a boolean value for each row in the data source, e.g.
|
|
173
|
+
pd.Series([True, False, False, True, ...], index=data.index)
|
|
174
|
+
Also the return dictionary must look like:
|
|
175
|
+
{
|
|
176
|
+
"BTC": pd.Series([...]),
|
|
177
|
+
"ETH": pd.Series([...]),
|
|
178
|
+
...
|
|
179
|
+
}
|
|
180
|
+
where the symbols are exactly the same as defined in the
|
|
181
|
+
symbols attribute of the strategy.
|
|
182
|
+
"""
|
|
183
|
+
raise NotImplementedError(
|
|
184
|
+
"generate_sell_signals method not implemented"
|
|
185
|
+
)
|
|
186
|
+
|
|
187
|
+
def run_strategy(self, context: Context, data: Dict[str, Any]):
|
|
188
|
+
"""
|
|
189
|
+
Main function for running the strategy. This function will be called
|
|
96
190
|
by the framework when the trigger of your strategy is met.
|
|
97
191
|
|
|
192
|
+
The flow of this function is as follows:
|
|
193
|
+
1. Loop through all the symbols defined in the strategy.
|
|
194
|
+
2. For each symbol, check if there are any open orders.
|
|
195
|
+
A. If there are open orders, skip to the next symbol.
|
|
196
|
+
3. If there is no open position, generate buy signals
|
|
197
|
+
A. Generate buy signals
|
|
198
|
+
B. If there is a buy signal, retrieve the position size
|
|
199
|
+
defined for the symbol.
|
|
200
|
+
C. If there is a take profit or stop loss rule defined
|
|
201
|
+
for the symbol, register them for the trade that
|
|
202
|
+
has been created as part of the order execution.
|
|
203
|
+
4. If there is an open position, generate sell signals
|
|
204
|
+
A. Generate sell signals
|
|
205
|
+
B. If there is a sell signal, create a limit order to
|
|
206
|
+
sell the position.
|
|
207
|
+
|
|
98
208
|
During execution of this function, the context and market data
|
|
99
209
|
will be passed to the function. The context is an instance of
|
|
100
210
|
the Context class, this class has various methods to do operations
|
|
@@ -103,40 +213,129 @@ class TradingStrategy:
|
|
|
103
213
|
The market data is a dictionary containing all the data retrieved
|
|
104
214
|
from the specified data sources.
|
|
105
215
|
|
|
216
|
+
When buy or sell signals are generated, the strategy will create
|
|
217
|
+
limit orders to buy or sell the assets based on the generated signals.
|
|
218
|
+
For each symbol a corresponding position size must be defined. If
|
|
219
|
+
no position size is defined, an OperationalException will be raised.
|
|
220
|
+
|
|
221
|
+
Before creating new orders, the strategy will check if there are any
|
|
222
|
+
stop losses or take profits for symbol registered. It will
|
|
223
|
+
use the function get_stop_losses and get_take_profits, these functions
|
|
224
|
+
can be overridden by the user to provide custom stop losses and
|
|
225
|
+
take profits logic. The default functions will return the stop losses
|
|
226
|
+
and take profits that are registered for the symbol if any.
|
|
227
|
+
|
|
106
228
|
Args:
|
|
107
229
|
context (Context): The context of the strategy. This is an instance
|
|
108
230
|
of the Context class, this class has various methods to do
|
|
109
231
|
operations with your portfolio, orders, trades, positions and
|
|
110
232
|
other components.
|
|
111
|
-
|
|
233
|
+
data (Dict[str, Any]): The data for the strategy.
|
|
112
234
|
This is a dictionary containing all the data retrieved from the
|
|
113
|
-
specified data sources.
|
|
235
|
+
specified data sources. The keys are either the
|
|
236
|
+
identifiers of the data sources or a generated key, usually
|
|
237
|
+
<target_symbol>_<trading_symbol>_<time_frame> e.g. BTC-EUR_1h.
|
|
114
238
|
|
|
115
239
|
Returns:
|
|
116
240
|
None
|
|
117
241
|
"""
|
|
118
242
|
self.context = context
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
243
|
+
index_datetime = context.config[INDEX_DATETIME]
|
|
244
|
+
buy_signals = self.generate_buy_signals(data)
|
|
245
|
+
sell_signals = self.generate_sell_signals(data)
|
|
246
|
+
|
|
247
|
+
for symbol in self.symbols:
|
|
248
|
+
|
|
249
|
+
if self.has_open_orders(symbol):
|
|
250
|
+
continue
|
|
251
|
+
|
|
252
|
+
if not self.has_position(symbol):
|
|
253
|
+
|
|
254
|
+
if symbol not in buy_signals:
|
|
255
|
+
continue
|
|
256
|
+
|
|
257
|
+
signals = buy_signals[symbol]
|
|
258
|
+
last_row = signals.iloc[-1]
|
|
259
|
+
|
|
260
|
+
if last_row:
|
|
261
|
+
position_size = self.get_position_size(symbol)
|
|
262
|
+
full_symbol = (f"{symbol}/"
|
|
263
|
+
f"{self.context.get_trading_symbol()}")
|
|
264
|
+
price = self.context.get_latest_price(full_symbol)
|
|
265
|
+
amount = position_size.get_size(
|
|
266
|
+
self.context.get_portfolio(), price
|
|
267
|
+
)
|
|
268
|
+
order_amount = amount / price
|
|
269
|
+
order = self.create_limit_order(
|
|
270
|
+
target_symbol=symbol,
|
|
271
|
+
order_side=OrderSide.BUY,
|
|
272
|
+
amount=order_amount,
|
|
273
|
+
price=price,
|
|
274
|
+
execute=True,
|
|
275
|
+
validate=True,
|
|
276
|
+
sync=True
|
|
277
|
+
)
|
|
278
|
+
|
|
279
|
+
# Retrieve stop loss and take profit rules if any
|
|
280
|
+
stop_loss_rule = self.get_stop_loss_rule(symbol)
|
|
281
|
+
take_profit_rule = self.get_take_profit_rule(symbol)
|
|
282
|
+
|
|
283
|
+
if stop_loss_rule is not None:
|
|
284
|
+
trade = self.context.get_trade(
|
|
285
|
+
order_id=order.id
|
|
286
|
+
)
|
|
287
|
+
self.context.add_stop_loss(
|
|
288
|
+
trade=trade,
|
|
289
|
+
percentage=stop_loss_rule.percentage_threshold,
|
|
290
|
+
trailing=stop_loss_rule.trailing,
|
|
291
|
+
sell_percentage=stop_loss_rule.sell_percentage,
|
|
292
|
+
created_at=index_datetime
|
|
293
|
+
)
|
|
294
|
+
|
|
295
|
+
if take_profit_rule is not None:
|
|
296
|
+
trade = self.context.get_trade(
|
|
297
|
+
order_id=order.id
|
|
298
|
+
)
|
|
299
|
+
self.context.add_take_profit(
|
|
300
|
+
trade=trade,
|
|
301
|
+
percentage=take_profit_rule.percentage_threshold,
|
|
302
|
+
trailing=take_profit_rule.trailing,
|
|
303
|
+
sell_percentage=take_profit_rule.sell_percentage,
|
|
304
|
+
created_at=index_datetime
|
|
305
|
+
)
|
|
306
|
+
else:
|
|
307
|
+
# Check in the last row if there is a sell signal
|
|
308
|
+
if symbol not in sell_signals:
|
|
309
|
+
continue
|
|
310
|
+
|
|
311
|
+
signals = sell_signals[symbol]
|
|
312
|
+
last_row = signals.iloc[-1]
|
|
313
|
+
|
|
314
|
+
if last_row:
|
|
315
|
+
position = self.get_position(symbol)
|
|
316
|
+
|
|
317
|
+
if position is None:
|
|
318
|
+
raise OperationalException(
|
|
319
|
+
f"No position found for symbol {symbol} "
|
|
320
|
+
f"in strategy {self.strategy_id}"
|
|
321
|
+
)
|
|
322
|
+
|
|
323
|
+
full_symbol = (f"{symbol}/"
|
|
324
|
+
f"{self.context.get_trading_symbol()}")
|
|
325
|
+
price = self.context.get_latest_price(full_symbol)
|
|
326
|
+
self.create_limit_order(
|
|
327
|
+
target_symbol=symbol,
|
|
328
|
+
order_side=OrderSide.SELL,
|
|
329
|
+
amount=position.amount,
|
|
330
|
+
execute=True,
|
|
331
|
+
validate=True,
|
|
332
|
+
sync=True,
|
|
333
|
+
price=price
|
|
334
|
+
)
|
|
335
|
+
|
|
336
|
+
def apply_strategy(self, context, data):
|
|
138
337
|
if self.decorated:
|
|
139
|
-
self.decorated(context=context,
|
|
338
|
+
self.decorated(context=context, data=data)
|
|
140
339
|
else:
|
|
141
340
|
raise NotImplementedError("Apply strategy is not implemented")
|
|
142
341
|
|
|
@@ -146,44 +345,84 @@ class TradingStrategy:
|
|
|
146
345
|
strategy_id=self.worker_id,
|
|
147
346
|
interval=self.interval,
|
|
148
347
|
time_unit=self.time_unit,
|
|
149
|
-
|
|
348
|
+
data_sources=self.data_sources
|
|
150
349
|
)
|
|
151
350
|
|
|
152
|
-
def
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
.update_trades_with_market_data(market_data)
|
|
351
|
+
def get_take_profit_rule(self, symbol: str) -> Union[TakeProfitRule, None]:
|
|
352
|
+
"""
|
|
353
|
+
Get the take profit definition for a given symbol.
|
|
156
354
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
self.context.trade_service\
|
|
160
|
-
.update_trades_with_market_data(market_data)
|
|
355
|
+
Args:
|
|
356
|
+
symbol (str): The symbol of the asset.
|
|
161
357
|
|
|
162
|
-
|
|
358
|
+
Returns:
|
|
359
|
+
Union[TakeProfitRule, None]: The take profit rule if found,
|
|
360
|
+
None otherwise.
|
|
163
361
|
"""
|
|
164
|
-
|
|
362
|
+
|
|
363
|
+
if len(self.take_profit_rules) == 0:
|
|
364
|
+
return None
|
|
365
|
+
|
|
366
|
+
if self.take_profit_rules_lookup == {}:
|
|
367
|
+
for tp in self.take_profit_rules:
|
|
368
|
+
self.take_profit_rules_lookup[tp.symbol] = tp
|
|
369
|
+
|
|
370
|
+
return self.take_profit_rules_lookup.get(symbol, None)
|
|
371
|
+
|
|
372
|
+
def get_stop_loss_rule(self, symbol: str) -> Union[StopLossRule, None]:
|
|
165
373
|
"""
|
|
166
|
-
|
|
374
|
+
Get the stop loss definition for a given symbol.
|
|
167
375
|
|
|
168
|
-
|
|
169
|
-
|
|
376
|
+
Args:
|
|
377
|
+
symbol (str): The symbol of the asset.
|
|
378
|
+
|
|
379
|
+
Returns:
|
|
380
|
+
Union[StopLossRule, None]: The stop loss rule if found,
|
|
381
|
+
None otherwise.
|
|
382
|
+
"""
|
|
170
383
|
|
|
171
|
-
|
|
384
|
+
if len(self.stop_loss_rules) == 0:
|
|
385
|
+
return None
|
|
172
386
|
|
|
173
|
-
|
|
174
|
-
|
|
387
|
+
if self.stop_loss_rules_lookup == {}:
|
|
388
|
+
for sl in self.stop_loss_rules:
|
|
389
|
+
self.stop_loss_rules_lookup[sl.symbol] = sl
|
|
175
390
|
|
|
176
|
-
|
|
391
|
+
return self.stop_loss_rules_lookup.get(symbol, None)
|
|
392
|
+
|
|
393
|
+
def get_position_size(self, symbol: str) -> Union[PositionSize, None]:
|
|
177
394
|
"""
|
|
178
|
-
|
|
395
|
+
Get the position size definition for a given symbol.
|
|
396
|
+
|
|
397
|
+
Args:
|
|
398
|
+
symbol (str): The symbol of the asset.
|
|
399
|
+
|
|
400
|
+
Returns:
|
|
401
|
+
Union[PositionSize, None]: The position size if found,
|
|
402
|
+
None otherwise.
|
|
179
403
|
"""
|
|
180
|
-
trade_service = self.context.trade_service
|
|
181
|
-
take_profit_orders_data = trade_service.\
|
|
182
|
-
get_triggered_take_profit_orders()
|
|
183
|
-
order_service = self.context.order_service
|
|
184
404
|
|
|
185
|
-
|
|
186
|
-
|
|
405
|
+
if len(self.position_sizes) == 0:
|
|
406
|
+
raise OperationalException(
|
|
407
|
+
f"No position size defined for symbol "
|
|
408
|
+
f"{symbol} in strategy "
|
|
409
|
+
f"{self.strategy_id}"
|
|
410
|
+
)
|
|
411
|
+
|
|
412
|
+
if self.position_sizes_lookup == {}:
|
|
413
|
+
for ps in self.position_sizes:
|
|
414
|
+
self.position_sizes_lookup[ps.symbol] = ps
|
|
415
|
+
|
|
416
|
+
position_size = self.position_sizes_lookup.get(symbol, None)
|
|
417
|
+
|
|
418
|
+
if position_size is None:
|
|
419
|
+
raise OperationalException(
|
|
420
|
+
f"No position size defined for symbol "
|
|
421
|
+
f"{symbol} in strategy "
|
|
422
|
+
f"{self.strategy_id}"
|
|
423
|
+
)
|
|
424
|
+
|
|
425
|
+
return position_size
|
|
187
426
|
|
|
188
427
|
def on_trade_closed(self, context: Context, trade: Trade):
|
|
189
428
|
pass
|
|
@@ -240,67 +479,6 @@ class TradingStrategy:
|
|
|
240
479
|
|
|
241
480
|
return self.worker_id
|
|
242
481
|
|
|
243
|
-
def add_trace(
|
|
244
|
-
self,
|
|
245
|
-
symbol: str,
|
|
246
|
-
data,
|
|
247
|
-
drop_duplicates=True
|
|
248
|
-
) -> None:
|
|
249
|
-
"""
|
|
250
|
-
Add data to the straces object for a given symbol
|
|
251
|
-
|
|
252
|
-
Args:
|
|
253
|
-
symbol (str): The symbol
|
|
254
|
-
data (pd.DataFrame): The data to add to the tracing
|
|
255
|
-
drop_duplicates (bool): Drop duplicates
|
|
256
|
-
|
|
257
|
-
Returns:
|
|
258
|
-
None
|
|
259
|
-
"""
|
|
260
|
-
|
|
261
|
-
# Check if data is a DataFrame
|
|
262
|
-
if not isinstance(data, pd.DataFrame):
|
|
263
|
-
raise ValueError(
|
|
264
|
-
"Currently only pandas DataFrames are "
|
|
265
|
-
"supported as tracing data objects."
|
|
266
|
-
)
|
|
267
|
-
|
|
268
|
-
data: pd.DataFrame = data
|
|
269
|
-
|
|
270
|
-
# Check if index is a datetime object
|
|
271
|
-
if not isinstance(data.index, pd.DatetimeIndex):
|
|
272
|
-
raise ValueError("Dataframe Index must be a datetime object.")
|
|
273
|
-
|
|
274
|
-
if self.traces is None:
|
|
275
|
-
self.traces = {}
|
|
276
|
-
|
|
277
|
-
# Check if the key is already in the context dictionary
|
|
278
|
-
if symbol in self.traces:
|
|
279
|
-
# If the key is already in the context dictionary,
|
|
280
|
-
# append the new data to the existing data
|
|
281
|
-
combined = pd.concat([self.traces[symbol], data])
|
|
282
|
-
else:
|
|
283
|
-
# If the key is not in the context dictionary,
|
|
284
|
-
# add the new data to the context dictionary
|
|
285
|
-
combined = data
|
|
286
|
-
|
|
287
|
-
if drop_duplicates:
|
|
288
|
-
# Drop duplicates and sort the data by the index
|
|
289
|
-
combined = combined[~combined.index.duplicated(keep='first')]
|
|
290
|
-
|
|
291
|
-
# Set the datetime column as the index
|
|
292
|
-
combined.set_index(pd.DatetimeIndex(combined.index), inplace=True)
|
|
293
|
-
self.traces[symbol] = combined
|
|
294
|
-
|
|
295
|
-
def get_traces(self) -> dict:
|
|
296
|
-
"""
|
|
297
|
-
Get the traces object
|
|
298
|
-
|
|
299
|
-
Returns:
|
|
300
|
-
dict: The traces object
|
|
301
|
-
"""
|
|
302
|
-
return self.traces
|
|
303
|
-
|
|
304
482
|
def has_open_orders(
|
|
305
483
|
self, target_symbol=None, identifier=None, market=None
|
|
306
484
|
) -> bool:
|
|
@@ -335,7 +513,7 @@ class TradingStrategy:
|
|
|
335
513
|
execute=True,
|
|
336
514
|
validate=True,
|
|
337
515
|
sync=True
|
|
338
|
-
):
|
|
516
|
+
) -> Order:
|
|
339
517
|
"""
|
|
340
518
|
Function to create a limit order. This function will create
|
|
341
519
|
a limit order and execute it if the execute parameter is set to True.
|
|
@@ -367,7 +545,7 @@ class TradingStrategy:
|
|
|
367
545
|
Returns:
|
|
368
546
|
Order: Instance of the order created
|
|
369
547
|
"""
|
|
370
|
-
self.context.create_limit_order(
|
|
548
|
+
return self.context.create_limit_order(
|
|
371
549
|
target_symbol=target_symbol,
|
|
372
550
|
price=price,
|
|
373
551
|
order_side=order_side,
|
|
@@ -383,47 +561,9 @@ class TradingStrategy:
|
|
|
383
561
|
sync=sync
|
|
384
562
|
)
|
|
385
563
|
|
|
386
|
-
def create_market_order(
|
|
387
|
-
self,
|
|
388
|
-
target_symbol,
|
|
389
|
-
order_side,
|
|
390
|
-
amount,
|
|
391
|
-
market=None,
|
|
392
|
-
execute=False,
|
|
393
|
-
validate=False,
|
|
394
|
-
sync=True
|
|
395
|
-
):
|
|
396
|
-
"""
|
|
397
|
-
Function to create a market order. This function will create a market
|
|
398
|
-
order and execute it if the execute parameter is set to True. If the
|
|
399
|
-
validate parameter is set to True, the order will be validated
|
|
400
|
-
|
|
401
|
-
Args:
|
|
402
|
-
target_symbol: The symbol of the asset to trade
|
|
403
|
-
order_side: The side of the order
|
|
404
|
-
amount: The amount of the asset to trade
|
|
405
|
-
market: The market to trade the asset
|
|
406
|
-
execute: If set to True, the order will be executed
|
|
407
|
-
validate: If set to True, the order will be validated
|
|
408
|
-
sync: If set to True, the created order will be synced with the
|
|
409
|
-
portfolio of the context
|
|
410
|
-
|
|
411
|
-
Returns:
|
|
412
|
-
Order: Instance of the order created
|
|
413
|
-
"""
|
|
414
|
-
self.context.create_market_order(
|
|
415
|
-
target_symbol=target_symbol,
|
|
416
|
-
order_side=order_side,
|
|
417
|
-
amount=amount,
|
|
418
|
-
market=market,
|
|
419
|
-
execute=execute,
|
|
420
|
-
validate=validate,
|
|
421
|
-
sync=sync
|
|
422
|
-
)
|
|
423
|
-
|
|
424
564
|
def close_position(
|
|
425
565
|
self, symbol, market=None, identifier=None, precision=None
|
|
426
|
-
):
|
|
566
|
+
) -> Order:
|
|
427
567
|
"""
|
|
428
568
|
Function to close a position. This function will close a position
|
|
429
569
|
by creating a market order to sell the position. If the precision
|
|
@@ -439,7 +579,7 @@ class TradingStrategy:
|
|
|
439
579
|
Returns:
|
|
440
580
|
None
|
|
441
581
|
"""
|
|
442
|
-
self.context.close_position(
|
|
582
|
+
return self.context.close_position(
|
|
443
583
|
symbol=symbol,
|
|
444
584
|
market=market,
|
|
445
585
|
identifier=identifier,
|
|
@@ -31,9 +31,11 @@ class Task:
|
|
|
31
31
|
else:
|
|
32
32
|
self.worker_id = self.__class__.__name__
|
|
33
33
|
|
|
34
|
-
def run(self,
|
|
34
|
+
def run(self, context):
|
|
35
35
|
|
|
36
36
|
if self.decorated:
|
|
37
|
-
self.decorated(
|
|
37
|
+
self.decorated(context=context)
|
|
38
38
|
else:
|
|
39
|
-
raise NotImplementedError(
|
|
39
|
+
raise NotImplementedError(
|
|
40
|
+
"run method must be implemented in the subclass"
|
|
41
|
+
)
|