investing-algorithm-framework 1.5__py3-none-any.whl → 7.25.6__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.
- investing_algorithm_framework/__init__.py +192 -16
- investing_algorithm_framework/analysis/__init__.py +16 -0
- investing_algorithm_framework/analysis/backtest_data_ranges.py +202 -0
- investing_algorithm_framework/analysis/data.py +170 -0
- investing_algorithm_framework/analysis/markdown.py +91 -0
- investing_algorithm_framework/analysis/ranking.py +298 -0
- investing_algorithm_framework/app/__init__.py +29 -4
- investing_algorithm_framework/app/algorithm/__init__.py +7 -0
- investing_algorithm_framework/app/algorithm/algorithm.py +193 -0
- investing_algorithm_framework/app/algorithm/algorithm_factory.py +118 -0
- investing_algorithm_framework/app/app.py +2220 -379
- investing_algorithm_framework/app/app_hook.py +28 -0
- investing_algorithm_framework/app/context.py +1724 -0
- investing_algorithm_framework/app/eventloop.py +620 -0
- investing_algorithm_framework/app/reporting/__init__.py +27 -0
- investing_algorithm_framework/app/reporting/ascii.py +921 -0
- investing_algorithm_framework/app/reporting/backtest_report.py +349 -0
- investing_algorithm_framework/app/reporting/charts/__init__.py +19 -0
- 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 +74 -0
- investing_algorithm_framework/app/reporting/charts/line_chart.py +11 -0
- investing_algorithm_framework/app/reporting/charts/monthly_returns_heatmap.py +70 -0
- investing_algorithm_framework/app/reporting/charts/ohlcv_data_completeness.py +51 -0
- investing_algorithm_framework/app/reporting/charts/rolling_sharp_ratio.py +79 -0
- investing_algorithm_framework/app/reporting/charts/yearly_returns_barchart.py +55 -0
- investing_algorithm_framework/app/reporting/generate.py +185 -0
- investing_algorithm_framework/app/reporting/tables/__init__.py +11 -0
- investing_algorithm_framework/app/reporting/tables/key_metrics_table.py +217 -0
- investing_algorithm_framework/app/reporting/tables/time_metrics_table.py +80 -0
- investing_algorithm_framework/app/reporting/tables/trade_metrics_table.py +147 -0
- investing_algorithm_framework/app/reporting/tables/trades_table.py +75 -0
- investing_algorithm_framework/app/reporting/tables/utils.py +29 -0
- investing_algorithm_framework/app/reporting/templates/report_template.html.j2 +154 -0
- investing_algorithm_framework/app/stateless/action_handlers/__init__.py +6 -3
- investing_algorithm_framework/app/stateless/action_handlers/action_handler_strategy.py +1 -1
- investing_algorithm_framework/app/stateless/action_handlers/check_online_handler.py +2 -1
- investing_algorithm_framework/app/stateless/action_handlers/run_strategy_handler.py +14 -7
- investing_algorithm_framework/app/strategy.py +867 -60
- investing_algorithm_framework/app/task.py +5 -3
- investing_algorithm_framework/app/web/__init__.py +2 -1
- investing_algorithm_framework/app/web/controllers/__init__.py +2 -2
- investing_algorithm_framework/app/web/controllers/orders.py +3 -2
- investing_algorithm_framework/app/web/controllers/positions.py +2 -2
- investing_algorithm_framework/app/web/create_app.py +4 -2
- investing_algorithm_framework/app/web/schemas/position.py +1 -0
- investing_algorithm_framework/cli/__init__.py +0 -0
- investing_algorithm_framework/cli/cli.py +231 -0
- investing_algorithm_framework/cli/deploy_to_aws_lambda.py +501 -0
- investing_algorithm_framework/cli/deploy_to_azure_function.py +718 -0
- investing_algorithm_framework/cli/initialize_app.py +603 -0
- investing_algorithm_framework/cli/templates/.gitignore.template +178 -0
- investing_algorithm_framework/cli/templates/app.py.template +18 -0
- investing_algorithm_framework/cli/templates/app_aws_lambda_function.py.template +48 -0
- investing_algorithm_framework/cli/templates/app_azure_function.py.template +14 -0
- investing_algorithm_framework/cli/templates/app_web.py.template +18 -0
- 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_readme.md.template +110 -0
- investing_algorithm_framework/cli/templates/aws_lambda_requirements.txt.template +2 -0
- investing_algorithm_framework/cli/templates/azure_function_function_app.py.template +65 -0
- investing_algorithm_framework/cli/templates/azure_function_host.json.template +15 -0
- investing_algorithm_framework/cli/templates/azure_function_local.settings.json.template +8 -0
- investing_algorithm_framework/cli/templates/azure_function_requirements.txt.template +3 -0
- investing_algorithm_framework/cli/templates/data_providers.py.template +17 -0
- investing_algorithm_framework/cli/templates/env.example.template +2 -0
- investing_algorithm_framework/cli/templates/env_azure_function.example.template +4 -0
- investing_algorithm_framework/cli/templates/market_data_providers.py.template +9 -0
- investing_algorithm_framework/cli/templates/readme.md.template +135 -0
- investing_algorithm_framework/cli/templates/requirements.txt.template +2 -0
- investing_algorithm_framework/cli/templates/run_backtest.py.template +20 -0
- investing_algorithm_framework/cli/templates/strategy.py.template +124 -0
- investing_algorithm_framework/cli/validate_backtest_checkpoints.py +197 -0
- investing_algorithm_framework/create_app.py +40 -7
- investing_algorithm_framework/dependency_container.py +100 -47
- investing_algorithm_framework/domain/__init__.py +97 -30
- investing_algorithm_framework/domain/algorithm_id.py +69 -0
- investing_algorithm_framework/domain/backtesting/__init__.py +25 -0
- investing_algorithm_framework/domain/backtesting/backtest.py +548 -0
- investing_algorithm_framework/domain/backtesting/backtest_date_range.py +113 -0
- investing_algorithm_framework/domain/backtesting/backtest_evaluation_focuss.py +241 -0
- investing_algorithm_framework/domain/backtesting/backtest_metrics.py +470 -0
- investing_algorithm_framework/domain/backtesting/backtest_permutation_test.py +275 -0
- investing_algorithm_framework/domain/backtesting/backtest_run.py +663 -0
- investing_algorithm_framework/domain/backtesting/backtest_summary_metrics.py +162 -0
- investing_algorithm_framework/domain/backtesting/backtest_utils.py +198 -0
- investing_algorithm_framework/domain/backtesting/combine_backtests.py +392 -0
- investing_algorithm_framework/domain/config.py +59 -136
- investing_algorithm_framework/domain/constants.py +18 -37
- investing_algorithm_framework/domain/data_provider.py +334 -0
- investing_algorithm_framework/domain/data_structures.py +42 -0
- investing_algorithm_framework/domain/exceptions.py +51 -1
- investing_algorithm_framework/domain/models/__init__.py +26 -19
- investing_algorithm_framework/domain/models/app_mode.py +34 -0
- investing_algorithm_framework/domain/models/data/__init__.py +7 -0
- investing_algorithm_framework/domain/models/data/data_source.py +222 -0
- investing_algorithm_framework/domain/models/data/data_type.py +46 -0
- investing_algorithm_framework/domain/models/event.py +35 -0
- investing_algorithm_framework/domain/models/market/__init__.py +5 -0
- investing_algorithm_framework/domain/models/market/market_credential.py +88 -0
- investing_algorithm_framework/domain/models/order/__init__.py +3 -4
- investing_algorithm_framework/domain/models/order/order.py +198 -65
- investing_algorithm_framework/domain/models/order/order_status.py +2 -2
- investing_algorithm_framework/domain/models/order/order_type.py +1 -3
- investing_algorithm_framework/domain/models/portfolio/__init__.py +6 -2
- investing_algorithm_framework/domain/models/portfolio/portfolio.py +98 -3
- investing_algorithm_framework/domain/models/portfolio/portfolio_configuration.py +37 -43
- investing_algorithm_framework/domain/models/portfolio/portfolio_snapshot.py +108 -11
- investing_algorithm_framework/domain/models/position/__init__.py +2 -1
- investing_algorithm_framework/domain/models/position/position.py +20 -0
- investing_algorithm_framework/domain/models/position/position_size.py +41 -0
- investing_algorithm_framework/domain/models/position/position_snapshot.py +0 -2
- 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 +45 -0
- investing_algorithm_framework/domain/models/strategy_profile.py +19 -141
- investing_algorithm_framework/domain/models/time_frame.py +94 -98
- investing_algorithm_framework/domain/models/time_interval.py +33 -0
- investing_algorithm_framework/domain/models/time_unit.py +66 -2
- investing_algorithm_framework/domain/models/tracing/__init__.py +0 -0
- investing_algorithm_framework/domain/models/tracing/trace.py +23 -0
- investing_algorithm_framework/domain/models/trade/__init__.py +11 -0
- investing_algorithm_framework/domain/models/trade/trade.py +389 -0
- investing_algorithm_framework/domain/models/trade/trade_status.py +40 -0
- investing_algorithm_framework/domain/models/trade/trade_stop_loss.py +332 -0
- investing_algorithm_framework/domain/models/trade/trade_take_profit.py +365 -0
- investing_algorithm_framework/domain/order_executor.py +112 -0
- investing_algorithm_framework/domain/portfolio_provider.py +118 -0
- investing_algorithm_framework/domain/services/__init__.py +11 -0
- investing_algorithm_framework/domain/services/market_credential_service.py +37 -0
- investing_algorithm_framework/domain/services/portfolios/__init__.py +5 -0
- investing_algorithm_framework/domain/services/portfolios/portfolio_sync_service.py +9 -0
- investing_algorithm_framework/domain/services/rounding_service.py +27 -0
- investing_algorithm_framework/domain/services/state_handler.py +38 -0
- investing_algorithm_framework/domain/strategy.py +1 -29
- investing_algorithm_framework/domain/utils/__init__.py +15 -5
- investing_algorithm_framework/domain/utils/csv.py +22 -0
- investing_algorithm_framework/domain/utils/custom_tqdm.py +22 -0
- investing_algorithm_framework/domain/utils/dates.py +57 -0
- investing_algorithm_framework/domain/utils/jupyter_notebook_detection.py +19 -0
- investing_algorithm_framework/domain/utils/polars.py +53 -0
- investing_algorithm_framework/domain/utils/random.py +29 -0
- investing_algorithm_framework/download_data.py +244 -0
- investing_algorithm_framework/infrastructure/__init__.py +37 -11
- investing_algorithm_framework/infrastructure/data_providers/__init__.py +36 -0
- investing_algorithm_framework/infrastructure/data_providers/ccxt.py +1152 -0
- investing_algorithm_framework/infrastructure/data_providers/csv.py +568 -0
- 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 +86 -12
- investing_algorithm_framework/infrastructure/models/__init__.py +7 -3
- investing_algorithm_framework/infrastructure/models/order/__init__.py +2 -2
- investing_algorithm_framework/infrastructure/models/order/order.py +53 -53
- investing_algorithm_framework/infrastructure/models/order/order_metadata.py +44 -0
- investing_algorithm_framework/infrastructure/models/order_trade_association.py +10 -0
- investing_algorithm_framework/infrastructure/models/portfolio/__init__.py +1 -1
- investing_algorithm_framework/infrastructure/models/portfolio/portfolio_snapshot.py +8 -2
- investing_algorithm_framework/infrastructure/models/portfolio/{portfolio.py → sql_portfolio.py} +17 -6
- investing_algorithm_framework/infrastructure/models/position/position_snapshot.py +3 -1
- investing_algorithm_framework/infrastructure/models/trades/__init__.py +9 -0
- investing_algorithm_framework/infrastructure/models/trades/trade.py +130 -0
- investing_algorithm_framework/infrastructure/models/trades/trade_stop_loss.py +59 -0
- investing_algorithm_framework/infrastructure/models/trades/trade_take_profit.py +55 -0
- investing_algorithm_framework/infrastructure/order_executors/__init__.py +21 -0
- investing_algorithm_framework/infrastructure/order_executors/backtest_oder_executor.py +28 -0
- investing_algorithm_framework/infrastructure/order_executors/ccxt_order_executor.py +200 -0
- investing_algorithm_framework/infrastructure/portfolio_providers/__init__.py +19 -0
- investing_algorithm_framework/infrastructure/portfolio_providers/ccxt_portfolio_provider.py +199 -0
- investing_algorithm_framework/infrastructure/repositories/__init__.py +10 -4
- investing_algorithm_framework/infrastructure/repositories/order_metadata_repository.py +17 -0
- investing_algorithm_framework/infrastructure/repositories/order_repository.py +16 -5
- investing_algorithm_framework/infrastructure/repositories/portfolio_repository.py +2 -2
- investing_algorithm_framework/infrastructure/repositories/position_repository.py +11 -0
- investing_algorithm_framework/infrastructure/repositories/repository.py +84 -30
- investing_algorithm_framework/infrastructure/repositories/trade_repository.py +71 -0
- investing_algorithm_framework/infrastructure/repositories/trade_stop_loss_repository.py +29 -0
- investing_algorithm_framework/infrastructure/repositories/trade_take_profit_repository.py +29 -0
- investing_algorithm_framework/infrastructure/services/__init__.py +9 -4
- investing_algorithm_framework/infrastructure/services/aws/__init__.py +6 -0
- investing_algorithm_framework/infrastructure/services/aws/state_handler.py +193 -0
- investing_algorithm_framework/infrastructure/services/azure/__init__.py +5 -0
- investing_algorithm_framework/infrastructure/services/azure/state_handler.py +158 -0
- investing_algorithm_framework/infrastructure/services/backtesting/__init__.py +9 -0
- investing_algorithm_framework/infrastructure/services/backtesting/backtest_service.py +2596 -0
- investing_algorithm_framework/infrastructure/services/backtesting/event_backtest_service.py +285 -0
- investing_algorithm_framework/infrastructure/services/backtesting/vector_backtest_service.py +468 -0
- investing_algorithm_framework/services/__init__.py +123 -15
- investing_algorithm_framework/services/configuration_service.py +77 -11
- investing_algorithm_framework/services/data_providers/__init__.py +5 -0
- investing_algorithm_framework/services/data_providers/data_provider_service.py +1058 -0
- investing_algorithm_framework/services/market_credential_service.py +40 -0
- investing_algorithm_framework/services/metrics/__init__.py +119 -0
- investing_algorithm_framework/services/metrics/alpha.py +0 -0
- investing_algorithm_framework/services/metrics/beta.py +0 -0
- investing_algorithm_framework/services/metrics/cagr.py +60 -0
- investing_algorithm_framework/services/metrics/calmar_ratio.py +40 -0
- investing_algorithm_framework/services/metrics/drawdown.py +218 -0
- investing_algorithm_framework/services/metrics/equity_curve.py +24 -0
- investing_algorithm_framework/services/metrics/exposure.py +210 -0
- investing_algorithm_framework/services/metrics/generate.py +358 -0
- investing_algorithm_framework/services/metrics/mean_daily_return.py +84 -0
- investing_algorithm_framework/services/metrics/price_efficiency.py +57 -0
- investing_algorithm_framework/services/metrics/profit_factor.py +165 -0
- investing_algorithm_framework/services/metrics/recovery.py +113 -0
- investing_algorithm_framework/services/metrics/returns.py +452 -0
- investing_algorithm_framework/services/metrics/risk_free_rate.py +28 -0
- investing_algorithm_framework/services/metrics/sharpe_ratio.py +137 -0
- investing_algorithm_framework/services/metrics/sortino_ratio.py +74 -0
- investing_algorithm_framework/services/metrics/standard_deviation.py +156 -0
- investing_algorithm_framework/services/metrics/trades.py +473 -0
- investing_algorithm_framework/services/metrics/treynor_ratio.py +0 -0
- investing_algorithm_framework/services/metrics/ulcer.py +0 -0
- investing_algorithm_framework/services/metrics/value_at_risk.py +0 -0
- investing_algorithm_framework/services/metrics/volatility.py +118 -0
- investing_algorithm_framework/services/metrics/win_rate.py +177 -0
- investing_algorithm_framework/services/order_service/__init__.py +9 -0
- investing_algorithm_framework/services/order_service/order_backtest_service.py +178 -0
- investing_algorithm_framework/services/order_service/order_executor_lookup.py +110 -0
- investing_algorithm_framework/services/order_service/order_service.py +826 -0
- investing_algorithm_framework/services/portfolios/__init__.py +16 -0
- investing_algorithm_framework/services/portfolios/backtest_portfolio_service.py +54 -0
- investing_algorithm_framework/services/{portfolio_configuration_service.py → portfolios/portfolio_configuration_service.py} +27 -12
- investing_algorithm_framework/services/portfolios/portfolio_provider_lookup.py +106 -0
- investing_algorithm_framework/services/portfolios/portfolio_service.py +188 -0
- investing_algorithm_framework/services/portfolios/portfolio_snapshot_service.py +136 -0
- investing_algorithm_framework/services/portfolios/portfolio_sync_service.py +182 -0
- investing_algorithm_framework/services/positions/__init__.py +7 -0
- investing_algorithm_framework/services/positions/position_service.py +210 -0
- investing_algorithm_framework/services/repository_service.py +8 -2
- investing_algorithm_framework/services/trade_order_evaluator/__init__.py +9 -0
- investing_algorithm_framework/services/trade_order_evaluator/backtest_trade_oder_evaluator.py +117 -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 +9 -0
- investing_algorithm_framework/services/trade_service/trade_service.py +1099 -0
- 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.25.6.dist-info/METADATA +535 -0
- investing_algorithm_framework-7.25.6.dist-info/RECORD +268 -0
- {investing_algorithm_framework-1.5.dist-info → investing_algorithm_framework-7.25.6.dist-info}/WHEEL +1 -2
- investing_algorithm_framework-7.25.6.dist-info/entry_points.txt +3 -0
- investing_algorithm_framework/app/algorithm.py +0 -630
- investing_algorithm_framework/domain/models/backtest_profile.py +0 -414
- investing_algorithm_framework/domain/models/market_data/__init__.py +0 -11
- investing_algorithm_framework/domain/models/market_data/asset_price.py +0 -50
- investing_algorithm_framework/domain/models/market_data/ohlcv.py +0 -105
- investing_algorithm_framework/domain/models/market_data/order_book.py +0 -63
- investing_algorithm_framework/domain/models/market_data/ticker.py +0 -92
- investing_algorithm_framework/domain/models/order/order_fee.py +0 -45
- investing_algorithm_framework/domain/models/trade.py +0 -78
- investing_algorithm_framework/domain/models/trading_data_types.py +0 -47
- investing_algorithm_framework/domain/models/trading_time_frame.py +0 -223
- investing_algorithm_framework/domain/singleton.py +0 -9
- investing_algorithm_framework/domain/utils/backtesting.py +0 -82
- investing_algorithm_framework/infrastructure/models/order/order_fee.py +0 -21
- investing_algorithm_framework/infrastructure/repositories/order_fee_repository.py +0 -15
- investing_algorithm_framework/infrastructure/services/market_backtest_service.py +0 -360
- investing_algorithm_framework/infrastructure/services/market_service.py +0 -410
- investing_algorithm_framework/infrastructure/services/performance_service.py +0 -192
- investing_algorithm_framework/services/backtest_service.py +0 -268
- investing_algorithm_framework/services/market_data_service.py +0 -77
- investing_algorithm_framework/services/order_backtest_service.py +0 -122
- investing_algorithm_framework/services/order_service.py +0 -752
- investing_algorithm_framework/services/portfolio_service.py +0 -164
- investing_algorithm_framework/services/portfolio_snapshot_service.py +0 -68
- investing_algorithm_framework/services/position_cost_service.py +0 -5
- investing_algorithm_framework/services/position_service.py +0 -63
- investing_algorithm_framework/services/strategy_orchestrator_service.py +0 -225
- investing_algorithm_framework-1.5.dist-info/AUTHORS.md +0 -8
- investing_algorithm_framework-1.5.dist-info/METADATA +0 -230
- investing_algorithm_framework-1.5.dist-info/RECORD +0 -119
- investing_algorithm_framework-1.5.dist-info/top_level.txt +0 -1
- /investing_algorithm_framework/{infrastructure/services/performance_backtest_service.py → app/reporting/tables/stop_loss_table.py} +0 -0
- /investing_algorithm_framework/services/{position_snapshot_service.py → positions/position_snapshot_service.py} +0 -0
- {investing_algorithm_framework-1.5.dist-info → investing_algorithm_framework-7.25.6.dist-info}/LICENSE +0 -0
|
@@ -1,100 +1,907 @@
|
|
|
1
|
-
|
|
2
|
-
from
|
|
3
|
-
|
|
1
|
+
import logging
|
|
2
|
+
from datetime import datetime
|
|
3
|
+
from typing import List, Dict, Any, Union
|
|
4
|
+
|
|
5
|
+
import pandas as pd
|
|
6
|
+
|
|
7
|
+
from investing_algorithm_framework.domain import OperationalException, \
|
|
8
|
+
Position, PositionSize, TimeUnit, StrategyProfile, Trade, \
|
|
9
|
+
DataSource, OrderSide, StopLossRule, TakeProfitRule, Order, \
|
|
10
|
+
INDEX_DATETIME
|
|
11
|
+
from .context import Context
|
|
12
|
+
|
|
13
|
+
logger = logging.getLogger(__name__)
|
|
4
14
|
|
|
5
15
|
|
|
6
16
|
class TradingStrategy:
|
|
7
|
-
|
|
8
|
-
|
|
17
|
+
"""
|
|
18
|
+
TradingStrategy is the base class for all trading strategies. A trading
|
|
19
|
+
strategy is a set of rules that defines when to buy or sell an asset.
|
|
20
|
+
|
|
21
|
+
Attributes:
|
|
22
|
+
algorithm_id (string): the unique id for your
|
|
23
|
+
combined strategy instances. An algorithm consists out of one or
|
|
24
|
+
more strategy instances that run together. The algorithm_id
|
|
25
|
+
is used to uniquely indentify the combined strategy instances.
|
|
26
|
+
This is id is used in various places in the framework, e.g. for
|
|
27
|
+
backtesting results, logging, monitoring etc.
|
|
28
|
+
time_unit (TimeUnit): the time unit of the strategy that defines
|
|
29
|
+
when the strategy should run e.g. HOUR, DAY, WEEK, MONTH
|
|
30
|
+
interval (int): the interval of the strategy that defines how often
|
|
31
|
+
the strategy should run within the time unit e.g. every 5 hours,
|
|
32
|
+
every 2 days, every 3 weeks, every 4 months
|
|
33
|
+
worker_id ((optional) str): the id of the worker
|
|
34
|
+
strategy_id ((optional) str): the id of the strategy
|
|
35
|
+
decorated ((optional) bool): the decorated function
|
|
36
|
+
data_sources (List[DataSource] optional): the list of data
|
|
37
|
+
sources to use for the strategy. The data sources will be used
|
|
38
|
+
to indentify data providers that will be called to gather data
|
|
39
|
+
and pass to the strategy before its run.
|
|
40
|
+
metadata (optional): Dict[str, Any] - a dictionary
|
|
41
|
+
containing metadata about the strategy. This can be used to
|
|
42
|
+
store additional information about the strategy, such as its
|
|
43
|
+
author, version, description, params etc.
|
|
44
|
+
"""
|
|
45
|
+
algorithm_id: str
|
|
46
|
+
time_unit: TimeUnit = None
|
|
9
47
|
interval: int = None
|
|
10
|
-
|
|
11
|
-
trading_data_type = None
|
|
12
|
-
trading_data_types: list = None
|
|
13
|
-
trading_time_frame = None
|
|
14
|
-
trading_time_frame_start_date = None
|
|
15
|
-
worker_id: str = None
|
|
48
|
+
strategy_id: str = None
|
|
16
49
|
decorated = None
|
|
50
|
+
data_sources: List[DataSource] = []
|
|
51
|
+
traces = None
|
|
52
|
+
context: Context = None
|
|
53
|
+
metadata: Dict[str, Any] = None
|
|
54
|
+
position_sizes: List[PositionSize] = []
|
|
55
|
+
stop_losses: List[StopLossRule] = []
|
|
56
|
+
take_profits: List[TakeProfitRule] = []
|
|
57
|
+
symbols: List[str] = []
|
|
58
|
+
trading_symbol: str = None
|
|
17
59
|
|
|
18
60
|
def __init__(
|
|
19
61
|
self,
|
|
62
|
+
algorithm_id=None,
|
|
63
|
+
strategy_id=None,
|
|
20
64
|
time_unit=None,
|
|
21
65
|
interval=None,
|
|
22
|
-
|
|
66
|
+
data_sources=None,
|
|
67
|
+
metadata=None,
|
|
68
|
+
position_sizes=None,
|
|
69
|
+
stop_losses=None,
|
|
70
|
+
take_profits=None,
|
|
23
71
|
symbols=None,
|
|
24
|
-
|
|
25
|
-
trading_data_types=None,
|
|
26
|
-
trading_time_frame=None,
|
|
27
|
-
trading_time_frame_start_date=None,
|
|
28
|
-
worker_id=None,
|
|
72
|
+
trading_symbol=None,
|
|
29
73
|
decorated=None
|
|
30
74
|
):
|
|
75
|
+
if metadata is None:
|
|
76
|
+
metadata = {}
|
|
77
|
+
|
|
78
|
+
self.metadata = metadata
|
|
79
|
+
|
|
80
|
+
if strategy_id is not None:
|
|
81
|
+
self.strategy_id = strategy_id
|
|
82
|
+
else:
|
|
83
|
+
self.strategy_id = self.__class__.__name__
|
|
84
|
+
|
|
85
|
+
# Initialize algorithm_id: use provided value, fall back to class
|
|
86
|
+
# attribute if set, otherwise None
|
|
87
|
+
if algorithm_id is not None:
|
|
88
|
+
self.algorithm_id = algorithm_id
|
|
89
|
+
elif "algorithm_id" in self.metadata:
|
|
90
|
+
self.algorithm_id = self.metadata["algorithm_id"]
|
|
91
|
+
else:
|
|
92
|
+
# Check if class has algorithm_id defined as an actual value
|
|
93
|
+
# (not just a type hint). Type hints result in the type being
|
|
94
|
+
# returned (e.g., str, int), so we check for that.
|
|
95
|
+
class_algorithm_id = getattr(self.__class__, 'algorithm_id', None)
|
|
96
|
+
|
|
97
|
+
# If it's a type (like str, int) or None, it's just a type hint
|
|
98
|
+
# In that case, use the class name as the algorithm_id
|
|
99
|
+
if (class_algorithm_id is None
|
|
100
|
+
or isinstance(class_algorithm_id, type)):
|
|
101
|
+
self.algorithm_id = None
|
|
102
|
+
else:
|
|
103
|
+
self.algorithm_id = class_algorithm_id
|
|
31
104
|
|
|
32
105
|
if time_unit is not None:
|
|
33
106
|
self.time_unit = TimeUnit.from_value(time_unit)
|
|
107
|
+
else:
|
|
108
|
+
# Check if time_unit is None
|
|
109
|
+
if self.time_unit is None:
|
|
110
|
+
raise OperationalException(
|
|
111
|
+
f"Time unit attribute not set for "
|
|
112
|
+
f"strategy instance {self.strategy_id}"
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
self.time_unit = TimeUnit.from_value(self.time_unit)
|
|
34
116
|
|
|
35
117
|
if interval is not None:
|
|
36
118
|
self.interval = interval
|
|
37
119
|
|
|
38
|
-
|
|
39
|
-
|
|
120
|
+
# Initialize data_sources as a new list per instance
|
|
121
|
+
# to avoid sharing the class-level mutable default
|
|
122
|
+
if data_sources is not None:
|
|
123
|
+
self.data_sources = list(data_sources)
|
|
124
|
+
else:
|
|
125
|
+
# Check if class has data_sources defined, copy them
|
|
126
|
+
class_data_sources = getattr(self.__class__, 'data_sources', [])
|
|
127
|
+
self.data_sources = list(class_data_sources) \
|
|
128
|
+
if class_data_sources else []
|
|
40
129
|
|
|
41
|
-
if
|
|
42
|
-
self.
|
|
130
|
+
if decorated is not None:
|
|
131
|
+
self.decorated = decorated
|
|
43
132
|
|
|
133
|
+
# Initialize position_sizes as a new list per instance
|
|
134
|
+
if position_sizes is not None:
|
|
135
|
+
self.position_sizes = list(position_sizes)
|
|
136
|
+
else:
|
|
137
|
+
class_position_sizes = getattr(
|
|
138
|
+
self.__class__, 'position_sizes', []
|
|
139
|
+
)
|
|
140
|
+
self.position_sizes = list(class_position_sizes) \
|
|
141
|
+
if class_position_sizes else []
|
|
142
|
+
|
|
143
|
+
# Initialize symbols as a new list per instance
|
|
44
144
|
if symbols is not None:
|
|
45
|
-
self.symbols = symbols
|
|
145
|
+
self.symbols = list(symbols)
|
|
146
|
+
else:
|
|
147
|
+
class_symbols = getattr(self.__class__, 'symbols', [])
|
|
148
|
+
self.symbols = list(class_symbols) if class_symbols else []
|
|
46
149
|
|
|
47
|
-
if
|
|
48
|
-
self.
|
|
150
|
+
if trading_symbol is not None:
|
|
151
|
+
self.trading_symbol = trading_symbol
|
|
49
152
|
|
|
50
|
-
if
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
153
|
+
# Check if interval is None
|
|
154
|
+
if self.interval is None:
|
|
155
|
+
raise OperationalException(
|
|
156
|
+
f"Interval not set for strategy instance {self.strategy_id}"
|
|
157
|
+
)
|
|
54
158
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
]
|
|
159
|
+
# Initialize stop_losses as a new list per instance
|
|
160
|
+
if stop_losses is not None:
|
|
161
|
+
self.stop_losses = list(stop_losses)
|
|
162
|
+
else:
|
|
163
|
+
class_stop_losses = getattr(self.__class__, 'stop_losses', [])
|
|
164
|
+
self.stop_losses = list(class_stop_losses) \
|
|
165
|
+
if class_stop_losses else []
|
|
60
166
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
167
|
+
# Initialize take_profits as a new list per instance
|
|
168
|
+
if take_profits is not None:
|
|
169
|
+
self.take_profits = list(take_profits)
|
|
170
|
+
else:
|
|
171
|
+
class_take_profits = getattr(self.__class__, 'take_profits', [])
|
|
172
|
+
self.take_profits = list(class_take_profits) \
|
|
173
|
+
if class_take_profits else []
|
|
64
174
|
|
|
65
|
-
|
|
66
|
-
|
|
175
|
+
# context initialization
|
|
176
|
+
self._context = None
|
|
177
|
+
self._last_run = None
|
|
178
|
+
self.stop_loss_rules_lookup = {}
|
|
179
|
+
self.take_profit_rules_lookup = {}
|
|
180
|
+
self.position_sizes_lookup = {}
|
|
67
181
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
182
|
+
def generate_buy_signals(
|
|
183
|
+
self, data: Dict[str, Any]
|
|
184
|
+
) -> Dict[str, pd.Series]:
|
|
185
|
+
"""
|
|
186
|
+
Function that needs to be implemented by the user.
|
|
187
|
+
This function should return a pandas Series containing the buy signals.
|
|
74
188
|
|
|
75
|
-
|
|
76
|
-
|
|
189
|
+
Args:
|
|
190
|
+
data (Dict[str, Any]): All the data that matched the
|
|
191
|
+
data sources of the strategy.
|
|
77
192
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
193
|
+
Returns:
|
|
194
|
+
Dict[str, Series]: A dictionary where the keys are the
|
|
195
|
+
symbols and the values are pandas Series containing
|
|
196
|
+
the buy signals. The series must be a pandas Series with
|
|
197
|
+
a boolean value for each row in the data source, e.g.
|
|
198
|
+
pd.Series([True, False, False, True, ...], index=data.index)
|
|
199
|
+
Also the return dictionary must look like:
|
|
200
|
+
{
|
|
201
|
+
"BTC": pd.Series([...]),
|
|
202
|
+
"ETH": pd.Series([...]),
|
|
203
|
+
...
|
|
204
|
+
}
|
|
205
|
+
where the symbols are exactly the same as defined in the
|
|
206
|
+
symbols attribute of the strategy.
|
|
207
|
+
"""
|
|
208
|
+
raise NotImplementedError(
|
|
209
|
+
"generate_buy_signals method not implemented"
|
|
210
|
+
)
|
|
211
|
+
|
|
212
|
+
def generate_sell_signals(
|
|
213
|
+
self, data: Dict[str, Any]
|
|
214
|
+
) -> Dict[str, pd.Series]:
|
|
215
|
+
"""
|
|
216
|
+
Function that needs to be implemented by the user.
|
|
217
|
+
This function should return a pandas Series containing
|
|
218
|
+
the sell signals.
|
|
219
|
+
|
|
220
|
+
Args:
|
|
221
|
+
data (Dict[str, Any]): All the data that is defined in the
|
|
222
|
+
data sources of the strategy. E.g. if there is a data source
|
|
223
|
+
defined as DataSource(identifier="bitvavo_btc_eur_1h",
|
|
224
|
+
symbol="BTC/EUR", time_frame="1h", data_type=DataType.OHLCV,
|
|
225
|
+
window_size=100, market="BITVAVO"), the data dictionary
|
|
226
|
+
will contain a key "bitvavo_btc_eur_1h"
|
|
227
|
+
with the corresponding data as a polars DataFrame.
|
|
228
|
+
|
|
229
|
+
Returns:
|
|
230
|
+
Dict[str, Series]: A dictionary where the keys are the
|
|
231
|
+
symbols and the values are pandas Series containing
|
|
232
|
+
the sell signals. The series must be a pandas Series with
|
|
233
|
+
a boolean value for each row in the data source, e.g.
|
|
234
|
+
pd.Series([True, False, False, True, ...], index=data.index)
|
|
235
|
+
Also the return dictionary must look like:
|
|
236
|
+
{
|
|
237
|
+
"BTC": pd.Series([...]),
|
|
238
|
+
"ETH": pd.Series([...]),
|
|
239
|
+
...
|
|
240
|
+
}
|
|
241
|
+
where the symbols are exactly the same as defined in the
|
|
242
|
+
symbols attribute of the strategy.
|
|
243
|
+
"""
|
|
244
|
+
raise NotImplementedError(
|
|
245
|
+
"generate_sell_signals method not implemented"
|
|
246
|
+
)
|
|
247
|
+
|
|
248
|
+
def run_strategy(self, context: Context, data: Dict[str, Any]):
|
|
249
|
+
"""
|
|
250
|
+
Main function for running the strategy. This function will be called
|
|
251
|
+
by the framework when the trigger of your strategy is met.
|
|
252
|
+
|
|
253
|
+
The flow of this function is as follows:
|
|
254
|
+
1. Loop through all the symbols defined in the strategy.
|
|
255
|
+
2. For each symbol, check if there are any open orders.
|
|
256
|
+
A. If there are open orders, skip to the next symbol.
|
|
257
|
+
3. If there is no open position, generate buy signals
|
|
258
|
+
A. Generate buy signals
|
|
259
|
+
B. If there is a buy signal, retrieve the position size
|
|
260
|
+
defined for the symbol.
|
|
261
|
+
C. If there is a take profit or stop loss rule defined
|
|
262
|
+
for the symbol, register them for the trade that
|
|
263
|
+
has been created as part of the order execution.
|
|
264
|
+
4. If there is an open position, generate sell signals
|
|
265
|
+
A. Generate sell signals
|
|
266
|
+
B. If there is a sell signal, create a limit order to
|
|
267
|
+
sell the position.
|
|
268
|
+
|
|
269
|
+
During execution of this function, the context and market data
|
|
270
|
+
will be passed to the function. The context is an instance of
|
|
271
|
+
the Context class, this class has various methods to do operations
|
|
272
|
+
with your portfolio, orders, trades, positions and other components.
|
|
273
|
+
|
|
274
|
+
The market data is a dictionary containing all the data retrieved
|
|
275
|
+
from the specified data sources.
|
|
276
|
+
|
|
277
|
+
When buy or sell signals are generated, the strategy will create
|
|
278
|
+
limit orders to buy or sell the assets based on the generated signals.
|
|
279
|
+
For each symbol a corresponding position size must be defined. If
|
|
280
|
+
no position size is defined, an OperationalException will be raised.
|
|
281
|
+
|
|
282
|
+
Before creating new orders, the strategy will check if there are any
|
|
283
|
+
stop losses or take profits for symbol registered. It will
|
|
284
|
+
use the function get_stop_losses and get_take_profits, these functions
|
|
285
|
+
can be overridden by the user to provide custom stop losses and
|
|
286
|
+
take profits logic. The default functions will return the stop losses
|
|
287
|
+
and take profits that are registered for the symbol if any.
|
|
288
|
+
|
|
289
|
+
Args:
|
|
290
|
+
context (Context): The context of the strategy. This is an instance
|
|
291
|
+
of the Context class, this class has various methods to do
|
|
292
|
+
operations with your portfolio, orders, trades, positions and
|
|
293
|
+
other components.
|
|
294
|
+
data (Dict[str, Any]): The data for the strategy.
|
|
295
|
+
This is a dictionary containing all the data retrieved from the
|
|
296
|
+
specified data sources. The keys are either the
|
|
297
|
+
identifiers of the data sources or a generated key, usually
|
|
298
|
+
<target_symbol>_<trading_symbol>_<time_frame> e.g. BTC-EUR_1h.
|
|
299
|
+
|
|
300
|
+
Returns:
|
|
301
|
+
None
|
|
302
|
+
"""
|
|
303
|
+
self.context = context
|
|
304
|
+
index_datetime = context.config[INDEX_DATETIME]
|
|
305
|
+
buy_signals = self.generate_buy_signals(data)
|
|
306
|
+
sell_signals = self.generate_sell_signals(data)
|
|
307
|
+
|
|
308
|
+
# Phase 1: Collect all pending buy orders
|
|
309
|
+
pending_buy_orders = []
|
|
310
|
+
portfolio = self.context.get_portfolio()
|
|
311
|
+
available_funds = self.context.get_unallocated()
|
|
312
|
+
|
|
313
|
+
for symbol in self.symbols:
|
|
314
|
+
|
|
315
|
+
if self.has_open_orders(symbol):
|
|
316
|
+
continue
|
|
317
|
+
|
|
318
|
+
if not self.has_position(symbol):
|
|
319
|
+
|
|
320
|
+
if symbol not in buy_signals:
|
|
321
|
+
continue
|
|
322
|
+
|
|
323
|
+
signals = buy_signals[symbol]
|
|
324
|
+
last_row = signals.iloc[-1]
|
|
325
|
+
|
|
326
|
+
if last_row:
|
|
327
|
+
position_size = self.get_position_size(symbol)
|
|
328
|
+
full_symbol = (f"{symbol}/"
|
|
329
|
+
f"{self.context.get_trading_symbol()}")
|
|
330
|
+
price = self.context.get_latest_price(full_symbol)
|
|
331
|
+
amount = position_size.get_size(portfolio, price)
|
|
332
|
+
|
|
333
|
+
pending_buy_orders.append({
|
|
334
|
+
'symbol': symbol,
|
|
335
|
+
'full_symbol': full_symbol,
|
|
336
|
+
'price': price,
|
|
337
|
+
'amount': amount,
|
|
338
|
+
})
|
|
339
|
+
|
|
340
|
+
# Phase 2: Scale orders proportionally if total exceeds available
|
|
341
|
+
total_required = sum(o['amount'] for o in pending_buy_orders)
|
|
342
|
+
|
|
343
|
+
if total_required > available_funds and total_required > 0:
|
|
344
|
+
scale_factor = available_funds / total_required
|
|
345
|
+
logger.warning(
|
|
346
|
+
f"Total allocation ({total_required:.2f}) exceeds available "
|
|
347
|
+
f"funds ({available_funds:.2f}). Scaling all orders by "
|
|
348
|
+
f"{scale_factor:.2%} to maintain proportional allocation."
|
|
349
|
+
)
|
|
350
|
+
for order in pending_buy_orders:
|
|
351
|
+
order['amount'] *= scale_factor
|
|
352
|
+
|
|
353
|
+
# Phase 3: Execute all pending buy orders
|
|
354
|
+
for order_data in pending_buy_orders:
|
|
355
|
+
symbol = order_data['symbol']
|
|
356
|
+
amount = order_data['amount']
|
|
357
|
+
price = order_data['price']
|
|
358
|
+
|
|
359
|
+
# Skip if amount is too small after scaling
|
|
360
|
+
if amount <= 0.01:
|
|
361
|
+
logger.warning(
|
|
362
|
+
f"Skipping buy order for {symbol}: "
|
|
363
|
+
f"amount too small after scaling ({amount:.4f})"
|
|
364
|
+
)
|
|
365
|
+
continue
|
|
366
|
+
|
|
367
|
+
order_amount = amount / price
|
|
368
|
+
order = self.create_limit_order(
|
|
369
|
+
target_symbol=symbol,
|
|
370
|
+
order_side=OrderSide.BUY,
|
|
371
|
+
amount=order_amount,
|
|
372
|
+
price=price,
|
|
373
|
+
execute=True,
|
|
374
|
+
validate=True,
|
|
375
|
+
sync=True
|
|
376
|
+
)
|
|
377
|
+
|
|
378
|
+
# Retrieve and apply stop loss and take profit rules
|
|
379
|
+
stop_loss_rule = self.get_stop_loss_rule(symbol)
|
|
380
|
+
take_profit_rule = self.get_take_profit_rule(symbol)
|
|
381
|
+
|
|
382
|
+
if stop_loss_rule is not None:
|
|
383
|
+
trade = self.context.get_trade(order_id=order.id)
|
|
384
|
+
self.context.add_stop_loss(
|
|
385
|
+
trade=trade,
|
|
386
|
+
percentage=stop_loss_rule.percentage_threshold,
|
|
387
|
+
trailing=stop_loss_rule.trailing,
|
|
388
|
+
sell_percentage=stop_loss_rule.sell_percentage,
|
|
389
|
+
created_at=index_datetime
|
|
390
|
+
)
|
|
391
|
+
|
|
392
|
+
if take_profit_rule is not None:
|
|
393
|
+
trade = self.context.get_trade(order_id=order.id)
|
|
394
|
+
self.context.add_take_profit(
|
|
395
|
+
trade=trade,
|
|
396
|
+
percentage=take_profit_rule.percentage_threshold,
|
|
397
|
+
trailing=take_profit_rule.trailing,
|
|
398
|
+
sell_percentage=take_profit_rule.sell_percentage,
|
|
399
|
+
created_at=index_datetime
|
|
400
|
+
)
|
|
401
|
+
|
|
402
|
+
# Phase 4: Process sell signals
|
|
403
|
+
for symbol in self.symbols:
|
|
404
|
+
|
|
405
|
+
if self.has_open_orders(symbol):
|
|
406
|
+
continue
|
|
407
|
+
|
|
408
|
+
if self.has_position(symbol):
|
|
409
|
+
# Check in the last row if there is a sell signal
|
|
410
|
+
if symbol not in sell_signals:
|
|
411
|
+
continue
|
|
412
|
+
|
|
413
|
+
signals = sell_signals[symbol]
|
|
414
|
+
last_row = signals.iloc[-1]
|
|
415
|
+
|
|
416
|
+
if last_row:
|
|
417
|
+
position = self.get_position(symbol)
|
|
418
|
+
|
|
419
|
+
if position is None:
|
|
420
|
+
raise OperationalException(
|
|
421
|
+
f"No position found for symbol {symbol} "
|
|
422
|
+
f"in strategy {self.strategy_id}"
|
|
423
|
+
)
|
|
424
|
+
|
|
425
|
+
full_symbol = (f"{symbol}/"
|
|
426
|
+
f"{self.context.get_trading_symbol()}")
|
|
427
|
+
price = self.context.get_latest_price(full_symbol)
|
|
428
|
+
self.create_limit_order(
|
|
429
|
+
target_symbol=symbol,
|
|
430
|
+
order_side=OrderSide.SELL,
|
|
431
|
+
amount=position.amount,
|
|
432
|
+
execute=True,
|
|
433
|
+
validate=True,
|
|
434
|
+
sync=True,
|
|
435
|
+
price=price
|
|
436
|
+
)
|
|
437
|
+
|
|
438
|
+
def apply_strategy(self, context, data):
|
|
83
439
|
if self.decorated:
|
|
84
|
-
self.decorated(
|
|
440
|
+
self.decorated(context=context, data=data)
|
|
85
441
|
else:
|
|
86
442
|
raise NotImplementedError("Apply strategy is not implemented")
|
|
87
443
|
|
|
88
444
|
@property
|
|
89
|
-
def
|
|
445
|
+
def strategy_profile(self):
|
|
90
446
|
return StrategyProfile(
|
|
91
|
-
strategy_id=self.
|
|
447
|
+
strategy_id=self.strategy_id,
|
|
92
448
|
interval=self.interval,
|
|
93
449
|
time_unit=self.time_unit,
|
|
94
|
-
|
|
95
|
-
trading_time_frame_start_date=self.trading_time_frame_start_date,
|
|
96
|
-
symbols=self.symbols,
|
|
97
|
-
market=self.market,
|
|
98
|
-
trading_data_type=self.trading_data_type,
|
|
99
|
-
trading_data_types=self.trading_data_types,
|
|
450
|
+
data_sources=self.data_sources
|
|
100
451
|
)
|
|
452
|
+
|
|
453
|
+
def get_take_profit_rule(self, symbol: str) -> Union[TakeProfitRule, None]:
|
|
454
|
+
"""
|
|
455
|
+
Get the take profit definition for a given symbol.
|
|
456
|
+
|
|
457
|
+
Args:
|
|
458
|
+
symbol (str): The symbol of the asset.
|
|
459
|
+
|
|
460
|
+
Returns:
|
|
461
|
+
Union[TakeProfitRule, None]: The take profit rule if found,
|
|
462
|
+
None otherwise.
|
|
463
|
+
"""
|
|
464
|
+
|
|
465
|
+
if self.take_profits is None or len(self.take_profits) == 0:
|
|
466
|
+
return None
|
|
467
|
+
|
|
468
|
+
if self.take_profit_rules_lookup == {}:
|
|
469
|
+
for tp in self.take_profits:
|
|
470
|
+
self.take_profit_rules_lookup[tp.symbol] = tp
|
|
471
|
+
|
|
472
|
+
return self.take_profit_rules_lookup.get(symbol, None)
|
|
473
|
+
|
|
474
|
+
def get_stop_loss_rule(self, symbol: str) -> Union[StopLossRule, None]:
|
|
475
|
+
"""
|
|
476
|
+
Get the stop loss definition for a given symbol.
|
|
477
|
+
|
|
478
|
+
Args:
|
|
479
|
+
symbol (str): The symbol of the asset.
|
|
480
|
+
|
|
481
|
+
Returns:
|
|
482
|
+
Union[StopLossRule, None]: The stop loss rule if found,
|
|
483
|
+
None otherwise.
|
|
484
|
+
"""
|
|
485
|
+
|
|
486
|
+
if self.stop_losses is None or len(self.stop_losses) == 0:
|
|
487
|
+
return None
|
|
488
|
+
|
|
489
|
+
if self.stop_loss_rules_lookup == {}:
|
|
490
|
+
for sl in self.stop_losses:
|
|
491
|
+
self.stop_loss_rules_lookup[sl.symbol] = sl
|
|
492
|
+
|
|
493
|
+
return self.stop_loss_rules_lookup.get(symbol, None)
|
|
494
|
+
|
|
495
|
+
def get_position_size(self, symbol: str) -> Union[PositionSize, None]:
|
|
496
|
+
"""
|
|
497
|
+
Get the position size definition for a given symbol.
|
|
498
|
+
|
|
499
|
+
Args:
|
|
500
|
+
symbol (str): The symbol of the asset.
|
|
501
|
+
|
|
502
|
+
Returns:
|
|
503
|
+
Union[PositionSize, None]: The position size if found,
|
|
504
|
+
None otherwise.
|
|
505
|
+
"""
|
|
506
|
+
|
|
507
|
+
if self.position_sizes is not None and len(self.position_sizes) == 0:
|
|
508
|
+
raise OperationalException(
|
|
509
|
+
f"No position size defined for symbol "
|
|
510
|
+
f"{symbol} in strategy "
|
|
511
|
+
f"{self.strategy_id}"
|
|
512
|
+
)
|
|
513
|
+
|
|
514
|
+
if self.position_sizes_lookup == {}:
|
|
515
|
+
for ps in self.position_sizes:
|
|
516
|
+
self.position_sizes_lookup[ps.symbol] = ps
|
|
517
|
+
|
|
518
|
+
position_size = self.position_sizes_lookup.get(symbol, None)
|
|
519
|
+
|
|
520
|
+
if position_size is None:
|
|
521
|
+
raise OperationalException(
|
|
522
|
+
f"No position size defined for symbol "
|
|
523
|
+
f"{symbol} in strategy "
|
|
524
|
+
f"{self.strategy_id}"
|
|
525
|
+
)
|
|
526
|
+
|
|
527
|
+
return position_size
|
|
528
|
+
|
|
529
|
+
def on_trade_closed(self, context: Context, trade: Trade):
|
|
530
|
+
pass
|
|
531
|
+
|
|
532
|
+
def on_trade_updated(self, context: Context, trade: Trade):
|
|
533
|
+
pass
|
|
534
|
+
|
|
535
|
+
def on_trade_created(self, context: Context, trade: Trade):
|
|
536
|
+
pass
|
|
537
|
+
|
|
538
|
+
def on_trade_opened(self, context: Context, trade: Trade):
|
|
539
|
+
pass
|
|
540
|
+
|
|
541
|
+
def on_trade_stop_loss_triggered(self, context: Context, trade: Trade):
|
|
542
|
+
pass
|
|
543
|
+
|
|
544
|
+
def on_trade_trailing_stop_loss_triggered(
|
|
545
|
+
self, context: Context, trade: Trade
|
|
546
|
+
):
|
|
547
|
+
pass
|
|
548
|
+
|
|
549
|
+
def on_trade_take_profit_triggered(
|
|
550
|
+
self, context: Context, trade: Trade
|
|
551
|
+
):
|
|
552
|
+
pass
|
|
553
|
+
|
|
554
|
+
def on_trade_stop_loss_updated(self, context: Context, trade: Trade):
|
|
555
|
+
pass
|
|
556
|
+
|
|
557
|
+
def on_trade_trailing_stop_loss_updated(
|
|
558
|
+
self, context: Context, trade: Trade
|
|
559
|
+
):
|
|
560
|
+
pass
|
|
561
|
+
|
|
562
|
+
def on_trade_take_profit_updated(self, context: Context, trade: Trade):
|
|
563
|
+
pass
|
|
564
|
+
|
|
565
|
+
def on_trade_stop_loss_created(self, context: Context, trade: Trade):
|
|
566
|
+
pass
|
|
567
|
+
|
|
568
|
+
def on_trade_trailing_stop_loss_created(
|
|
569
|
+
self, context: Context, trade: Trade
|
|
570
|
+
):
|
|
571
|
+
pass
|
|
572
|
+
|
|
573
|
+
def on_trade_take_profit_created(self, context: Context, trade: Trade):
|
|
574
|
+
pass
|
|
575
|
+
|
|
576
|
+
@property
|
|
577
|
+
def strategy_identifier(self):
|
|
578
|
+
|
|
579
|
+
if self.strategy_id is not None:
|
|
580
|
+
return self.strategy_id
|
|
581
|
+
|
|
582
|
+
return self.worker_id
|
|
583
|
+
|
|
584
|
+
def has_open_orders(
|
|
585
|
+
self, target_symbol=None, identifier=None, market=None
|
|
586
|
+
) -> bool:
|
|
587
|
+
"""
|
|
588
|
+
Check if there are open orders for a given symbol
|
|
589
|
+
|
|
590
|
+
Args:
|
|
591
|
+
target_symbol (str): The symbol of the asset e.g BTC if the
|
|
592
|
+
asset is BTC/USDT
|
|
593
|
+
identifier (str): The identifier of the portfolio
|
|
594
|
+
market (str): The market of the asset
|
|
595
|
+
|
|
596
|
+
Returns:
|
|
597
|
+
bool: True if there are open orders, False otherwise
|
|
598
|
+
"""
|
|
599
|
+
return self.context.has_open_orders(
|
|
600
|
+
target_symbol=target_symbol, identifier=identifier, market=market
|
|
601
|
+
)
|
|
602
|
+
|
|
603
|
+
def create_limit_order(
|
|
604
|
+
self,
|
|
605
|
+
target_symbol,
|
|
606
|
+
price,
|
|
607
|
+
order_side,
|
|
608
|
+
amount=None,
|
|
609
|
+
amount_trading_symbol=None,
|
|
610
|
+
percentage=None,
|
|
611
|
+
percentage_of_portfolio=None,
|
|
612
|
+
percentage_of_position=None,
|
|
613
|
+
precision=None,
|
|
614
|
+
market=None,
|
|
615
|
+
execute=True,
|
|
616
|
+
validate=True,
|
|
617
|
+
sync=True
|
|
618
|
+
) -> Order:
|
|
619
|
+
"""
|
|
620
|
+
Function to create a limit order. This function will create
|
|
621
|
+
a limit order and execute it if the execute parameter is set to True.
|
|
622
|
+
If the validate parameter is set to True, the order will be validated
|
|
623
|
+
|
|
624
|
+
Args:
|
|
625
|
+
target_symbol: The symbol of the asset to trade
|
|
626
|
+
price: The price of the asset
|
|
627
|
+
order_side: The side of the order
|
|
628
|
+
amount (optional): The amount of the asset to trade
|
|
629
|
+
amount_trading_symbol (optional): The amount of the trading
|
|
630
|
+
symbol to trade
|
|
631
|
+
percentage (optional): The percentage of the portfolio to
|
|
632
|
+
allocate to the order
|
|
633
|
+
percentage_of_portfolio (optional): The percentage of
|
|
634
|
+
the portfolio to allocate to the order
|
|
635
|
+
percentage_of_position (optional): The percentage of
|
|
636
|
+
the position to allocate to the order.
|
|
637
|
+
(Only supported for SELL orders)
|
|
638
|
+
precision (optional): The precision of the amount
|
|
639
|
+
market (optional): The market to trade the asset
|
|
640
|
+
execute (optional): Default True. If set to True, the order
|
|
641
|
+
will be executed
|
|
642
|
+
validate (optional): Default True. If set to True, the order
|
|
643
|
+
will be validated
|
|
644
|
+
sync (optional): Default True. If set to True, the created
|
|
645
|
+
order will be synced with the portfolio of the context
|
|
646
|
+
|
|
647
|
+
Returns:
|
|
648
|
+
Order: Instance of the order created
|
|
649
|
+
"""
|
|
650
|
+
return self.context.create_limit_order(
|
|
651
|
+
target_symbol=target_symbol,
|
|
652
|
+
price=price,
|
|
653
|
+
order_side=order_side,
|
|
654
|
+
amount=amount,
|
|
655
|
+
amount_trading_symbol=amount_trading_symbol,
|
|
656
|
+
percentage=percentage,
|
|
657
|
+
percentage_of_portfolio=percentage_of_portfolio,
|
|
658
|
+
percentage_of_position=percentage_of_position,
|
|
659
|
+
precision=precision,
|
|
660
|
+
market=market,
|
|
661
|
+
execute=execute,
|
|
662
|
+
validate=validate,
|
|
663
|
+
sync=sync
|
|
664
|
+
)
|
|
665
|
+
|
|
666
|
+
def close_position(
|
|
667
|
+
self, symbol, market=None, identifier=None, precision=None
|
|
668
|
+
) -> Order:
|
|
669
|
+
"""
|
|
670
|
+
Function to close a position. This function will close a position
|
|
671
|
+
by creating a market order to sell the position. If the precision
|
|
672
|
+
parameter is specified, the amount of the order will be rounded
|
|
673
|
+
down to the specified precision.
|
|
674
|
+
|
|
675
|
+
Args:
|
|
676
|
+
symbol: The symbol of the asset
|
|
677
|
+
market: The market of the asset
|
|
678
|
+
identifier: The identifier of the portfolio
|
|
679
|
+
precision: The precision of the amount
|
|
680
|
+
|
|
681
|
+
Returns:
|
|
682
|
+
None
|
|
683
|
+
"""
|
|
684
|
+
return self.context.close_position(
|
|
685
|
+
symbol=symbol,
|
|
686
|
+
market=market,
|
|
687
|
+
identifier=identifier,
|
|
688
|
+
precision=precision
|
|
689
|
+
)
|
|
690
|
+
|
|
691
|
+
def get_positions(
|
|
692
|
+
self,
|
|
693
|
+
market=None,
|
|
694
|
+
identifier=None,
|
|
695
|
+
amount_gt=None,
|
|
696
|
+
amount_gte=None,
|
|
697
|
+
amount_lt=None,
|
|
698
|
+
amount_lte=None
|
|
699
|
+
) -> List[Position]:
|
|
700
|
+
"""
|
|
701
|
+
Function to get all positions. This function will return all
|
|
702
|
+
positions that match the specified query parameters. If the
|
|
703
|
+
market parameter is specified, the positions of the specified
|
|
704
|
+
market will be returned. If the identifier parameter is
|
|
705
|
+
specified, the positions of the specified portfolio will be
|
|
706
|
+
returned. If the amount_gt parameter is specified, the positions
|
|
707
|
+
with an amount greater than the specified amount will be returned.
|
|
708
|
+
If the amount_gte parameter is specified, the positions with an
|
|
709
|
+
amount greater than or equal to the specified amount will be
|
|
710
|
+
returned. If the amount_lt parameter is specified, the positions
|
|
711
|
+
with an amount less than the specified amount will be returned.
|
|
712
|
+
If the amount_lte parameter is specified, the positions with an
|
|
713
|
+
amount less than or equal to the specified amount will be returned.
|
|
714
|
+
|
|
715
|
+
Args:
|
|
716
|
+
market: The market of the portfolio where the positions are
|
|
717
|
+
identifier: The identifier of the portfolio
|
|
718
|
+
amount_gt: The amount of the asset must be greater than this
|
|
719
|
+
amount_gte: The amount of the asset must be greater than or
|
|
720
|
+
equal to this
|
|
721
|
+
amount_lt: The amount of the asset must be less than this
|
|
722
|
+
amount_lte: The amount of the asset must be less than or equal
|
|
723
|
+
to this
|
|
724
|
+
|
|
725
|
+
Returns:
|
|
726
|
+
List[Position]: A list of positions that match the query parameters
|
|
727
|
+
"""
|
|
728
|
+
return self.context.get_positions(
|
|
729
|
+
market=market,
|
|
730
|
+
identifier=identifier,
|
|
731
|
+
amount_gt=amount_gt,
|
|
732
|
+
amount_gte=amount_gte,
|
|
733
|
+
amount_lt=amount_lt,
|
|
734
|
+
amount_lte=amount_lte
|
|
735
|
+
)
|
|
736
|
+
|
|
737
|
+
def get_trades(self, market=None) -> List[Trade]:
|
|
738
|
+
"""
|
|
739
|
+
Function to get all trades. This function will return all trades
|
|
740
|
+
that match the specified query parameters. If the market parameter
|
|
741
|
+
is specified, the trades with the specified market will be returned.
|
|
742
|
+
|
|
743
|
+
Args:
|
|
744
|
+
market: The market of the asset
|
|
745
|
+
|
|
746
|
+
Returns:
|
|
747
|
+
List[Trade]: A list of trades that match the query parameters
|
|
748
|
+
"""
|
|
749
|
+
return self.context.get_trades(market)
|
|
750
|
+
|
|
751
|
+
def get_closed_trades(self) -> List[Trade]:
|
|
752
|
+
"""
|
|
753
|
+
Function to get all closed trades. This function will return all
|
|
754
|
+
closed trades of the context.
|
|
755
|
+
|
|
756
|
+
Returns:
|
|
757
|
+
List[Trade]: A list of closed trades
|
|
758
|
+
"""
|
|
759
|
+
return self.context.get_closed_trades()
|
|
760
|
+
|
|
761
|
+
def get_open_trades(self, target_symbol=None, market=None) -> List[Trade]:
|
|
762
|
+
"""
|
|
763
|
+
Function to get all open trades. This function will return all
|
|
764
|
+
open trades that match the specified query parameters. If the
|
|
765
|
+
target_symbol parameter is specified, the open trades with the
|
|
766
|
+
specified target symbol will be returned. If the market parameter
|
|
767
|
+
is specified, the open trades with the specified market will be
|
|
768
|
+
returned.
|
|
769
|
+
|
|
770
|
+
Args:
|
|
771
|
+
target_symbol: The symbol of the asset
|
|
772
|
+
market: The market of the asset
|
|
773
|
+
|
|
774
|
+
Returns:
|
|
775
|
+
List[Trade]: A list of open trades that match the query parameters
|
|
776
|
+
"""
|
|
777
|
+
return self.context.get_open_trades(target_symbol, market)
|
|
778
|
+
|
|
779
|
+
def close_trade(self, trade, market=None, precision=None) -> None:
|
|
780
|
+
"""
|
|
781
|
+
Function to close a trade. This function will close a trade by
|
|
782
|
+
creating a market order to sell the position. If the precision
|
|
783
|
+
parameter is specified, the amount of the order will be rounded
|
|
784
|
+
down to the specified precision.
|
|
785
|
+
|
|
786
|
+
Args:
|
|
787
|
+
trade: Trade - The trade to close
|
|
788
|
+
market: str - The market of the trade
|
|
789
|
+
precision: float - The precision of the amount
|
|
790
|
+
|
|
791
|
+
Returns:
|
|
792
|
+
None
|
|
793
|
+
"""
|
|
794
|
+
self.context.close_trade(
|
|
795
|
+
trade=trade, market=market, precision=precision
|
|
796
|
+
)
|
|
797
|
+
|
|
798
|
+
def get_number_of_positions(self):
|
|
799
|
+
"""
|
|
800
|
+
Returns the number of positions that have a positive amount.
|
|
801
|
+
|
|
802
|
+
Returns:
|
|
803
|
+
int: The number of positions
|
|
804
|
+
"""
|
|
805
|
+
return self.context.get_number_of_positions()
|
|
806
|
+
|
|
807
|
+
def get_position(
|
|
808
|
+
self, symbol, market=None, identifier=None
|
|
809
|
+
) -> Position:
|
|
810
|
+
"""
|
|
811
|
+
Function to get a position. This function will return the
|
|
812
|
+
position that matches the specified query parameters. If the
|
|
813
|
+
market parameter is specified, the position of the specified
|
|
814
|
+
market will be returned. If the identifier parameter is
|
|
815
|
+
specified, the position of the specified portfolio will be
|
|
816
|
+
returned.
|
|
817
|
+
|
|
818
|
+
Args:
|
|
819
|
+
symbol: The symbol of the asset that represents the position
|
|
820
|
+
market: The market of the portfolio where the position is located
|
|
821
|
+
identifier: The identifier of the portfolio
|
|
822
|
+
|
|
823
|
+
Returns:
|
|
824
|
+
Position: The position that matches the query parameters
|
|
825
|
+
"""
|
|
826
|
+
return self.context.get_position(
|
|
827
|
+
symbol=symbol,
|
|
828
|
+
market=market,
|
|
829
|
+
identifier=identifier
|
|
830
|
+
)
|
|
831
|
+
|
|
832
|
+
def has_position(
|
|
833
|
+
self,
|
|
834
|
+
symbol,
|
|
835
|
+
market=None,
|
|
836
|
+
identifier=None,
|
|
837
|
+
amount_gt=0,
|
|
838
|
+
amount_gte=None,
|
|
839
|
+
amount_lt=None,
|
|
840
|
+
amount_lte=None
|
|
841
|
+
):
|
|
842
|
+
"""
|
|
843
|
+
Function to check if a position exists. This function will return
|
|
844
|
+
True if a position exists, False otherwise. This function will check
|
|
845
|
+
if the amount > 0 condition by default.
|
|
846
|
+
|
|
847
|
+
Args:
|
|
848
|
+
param symbol: The symbol of the asset
|
|
849
|
+
param market: The market of the asset
|
|
850
|
+
param identifier: The identifier of the portfolio
|
|
851
|
+
param amount_gt: The amount of the asset must be greater than this
|
|
852
|
+
param amount_gte: The amount of the asset must be greater than
|
|
853
|
+
or equal to this
|
|
854
|
+
param amount_lt: The amount of the asset must be less than this
|
|
855
|
+
param amount_lte: The amount of the asset must be less than
|
|
856
|
+
or equal to this
|
|
857
|
+
|
|
858
|
+
Returns:
|
|
859
|
+
Boolean: True if a position exists, False otherwise
|
|
860
|
+
"""
|
|
861
|
+
return self.context.has_position(
|
|
862
|
+
symbol=symbol,
|
|
863
|
+
market=market,
|
|
864
|
+
identifier=identifier,
|
|
865
|
+
amount_gt=amount_gt,
|
|
866
|
+
amount_gte=amount_gte,
|
|
867
|
+
amount_lt=amount_lt,
|
|
868
|
+
amount_lte=amount_lte
|
|
869
|
+
)
|
|
870
|
+
|
|
871
|
+
def has_balance(self, symbol, amount, market=None):
|
|
872
|
+
"""
|
|
873
|
+
Function to check if the portfolio has enough balance to
|
|
874
|
+
create an order. This function will return True if the
|
|
875
|
+
portfolio has enough balance to create an order, False
|
|
876
|
+
otherwise.
|
|
877
|
+
|
|
878
|
+
Args:
|
|
879
|
+
symbol: The symbol of the asset
|
|
880
|
+
amount: The amount of the asset
|
|
881
|
+
market: The market of the asset
|
|
882
|
+
|
|
883
|
+
Returns:
|
|
884
|
+
Boolean: True if the portfolio has enough balance
|
|
885
|
+
"""
|
|
886
|
+
return self.context.has_balance(symbol, amount, market)
|
|
887
|
+
|
|
888
|
+
def last_run(self) -> datetime:
|
|
889
|
+
"""
|
|
890
|
+
Function to get the last run of the strategy
|
|
891
|
+
|
|
892
|
+
Returns:
|
|
893
|
+
DateTime: The last run of the strategy
|
|
894
|
+
"""
|
|
895
|
+
return self.context.last_run()
|
|
896
|
+
|
|
897
|
+
def get_data_sources(self):
|
|
898
|
+
"""
|
|
899
|
+
Function to get the data sources of the strategy
|
|
900
|
+
|
|
901
|
+
Returns:
|
|
902
|
+
List[DataSource]: The data sources of the strategy
|
|
903
|
+
"""
|
|
904
|
+
return self.data_sources
|
|
905
|
+
|
|
906
|
+
def __repr__(self):
|
|
907
|
+
return f"<TradingStrategy(strategy_id={self.strategy_id})>"
|