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
|
@@ -0,0 +1,1724 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from datetime import datetime, timezone
|
|
3
|
+
from typing import List
|
|
4
|
+
|
|
5
|
+
from investing_algorithm_framework.services import ConfigurationService, \
|
|
6
|
+
MarketCredentialService, OrderService, PortfolioConfigurationService, \
|
|
7
|
+
PortfolioService, PositionService, TradeService, DataProviderService, \
|
|
8
|
+
TradeStopLossService, TradeTakeProfitService
|
|
9
|
+
from investing_algorithm_framework.domain import OrderStatus, OrderType, \
|
|
10
|
+
OrderSide, OperationalException, Portfolio, RoundingService, \
|
|
11
|
+
BACKTESTING_FLAG, INDEX_DATETIME, Order, \
|
|
12
|
+
Position, Trade, TradeStatus, MarketCredential, TradeStopLoss, \
|
|
13
|
+
TradeTakeProfit
|
|
14
|
+
|
|
15
|
+
logger = logging.getLogger("investing_algorithm_framework")
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class Context:
|
|
19
|
+
"""
|
|
20
|
+
Context class to store the state of the algorithm and
|
|
21
|
+
give access to objects such as orders, positions, trades and
|
|
22
|
+
portfolio.
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
def __init__(
|
|
26
|
+
self,
|
|
27
|
+
configuration_service: ConfigurationService,
|
|
28
|
+
portfolio_configuration_service: PortfolioConfigurationService,
|
|
29
|
+
portfolio_service: PortfolioService,
|
|
30
|
+
position_service: PositionService,
|
|
31
|
+
order_service: OrderService,
|
|
32
|
+
market_credential_service: MarketCredentialService,
|
|
33
|
+
trade_service: TradeService,
|
|
34
|
+
trade_stop_loss_service: TradeStopLossService,
|
|
35
|
+
trade_take_profit_service: TradeTakeProfitService,
|
|
36
|
+
data_provider_service: DataProviderService
|
|
37
|
+
):
|
|
38
|
+
self.configuration_service: ConfigurationService = \
|
|
39
|
+
configuration_service
|
|
40
|
+
self.portfolio_configuration_service: PortfolioConfigurationService = \
|
|
41
|
+
portfolio_configuration_service
|
|
42
|
+
self.portfolio_service: PortfolioService = portfolio_service
|
|
43
|
+
self.position_service: PositionService = position_service
|
|
44
|
+
self.order_service: OrderService = order_service
|
|
45
|
+
self.market_credential_service: MarketCredentialService = \
|
|
46
|
+
market_credential_service
|
|
47
|
+
self.data_provider_service: DataProviderService = data_provider_service
|
|
48
|
+
self.trade_service: TradeService = trade_service
|
|
49
|
+
self.trade_stop_loss_service: TradeStopLossService = \
|
|
50
|
+
trade_stop_loss_service
|
|
51
|
+
self.trade_take_profit_service: TradeTakeProfitService = \
|
|
52
|
+
trade_take_profit_service
|
|
53
|
+
|
|
54
|
+
@property
|
|
55
|
+
def config(self):
|
|
56
|
+
"""
|
|
57
|
+
Function to get a config instance. This allows users when
|
|
58
|
+
having access to the algorithm instance also to read the
|
|
59
|
+
configs of the app.
|
|
60
|
+
"""
|
|
61
|
+
return self.configuration_service.get_config()
|
|
62
|
+
|
|
63
|
+
def get_config(self):
|
|
64
|
+
"""
|
|
65
|
+
Function to get a config instance. This allows users when
|
|
66
|
+
having access to the algorithm instance also to read the
|
|
67
|
+
configs of the app.
|
|
68
|
+
"""
|
|
69
|
+
return self.configuration_service.get_config()
|
|
70
|
+
|
|
71
|
+
def create_order(
|
|
72
|
+
self,
|
|
73
|
+
target_symbol,
|
|
74
|
+
price,
|
|
75
|
+
order_type,
|
|
76
|
+
order_side,
|
|
77
|
+
amount,
|
|
78
|
+
market=None,
|
|
79
|
+
execute=True,
|
|
80
|
+
validate=True,
|
|
81
|
+
sync=True
|
|
82
|
+
) -> Order:
|
|
83
|
+
"""
|
|
84
|
+
Function to create an order. This function will create an order
|
|
85
|
+
and execute it if the execute parameter is set to True. If the
|
|
86
|
+
validate parameter is set to True, the order will be validated
|
|
87
|
+
|
|
88
|
+
Args:
|
|
89
|
+
target_symbol: The symbol of the asset to trade
|
|
90
|
+
price: The price of the asset
|
|
91
|
+
order_type: The type of the order
|
|
92
|
+
order_side: The side of the order
|
|
93
|
+
amount: The amount of the asset to trade
|
|
94
|
+
market: The market to trade the asset
|
|
95
|
+
execute: If set to True, the order will be executed
|
|
96
|
+
validate: If set to True, the order will be validated
|
|
97
|
+
sync: If set to True, the created order will be synced
|
|
98
|
+
with the portfolio of the algorithm.
|
|
99
|
+
|
|
100
|
+
Returns:
|
|
101
|
+
The order created
|
|
102
|
+
"""
|
|
103
|
+
portfolio = self.portfolio_service.find({"market": market})
|
|
104
|
+
order_data = {
|
|
105
|
+
"target_symbol": target_symbol,
|
|
106
|
+
"price": price,
|
|
107
|
+
"amount": amount,
|
|
108
|
+
"order_type": order_type,
|
|
109
|
+
"order_side": order_side,
|
|
110
|
+
"portfolio_id": portfolio.id,
|
|
111
|
+
"status": OrderStatus.CREATED.value,
|
|
112
|
+
"trading_symbol": portfolio.trading_symbol,
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
if BACKTESTING_FLAG in self.configuration_service.config \
|
|
116
|
+
and self.configuration_service.config[BACKTESTING_FLAG]:
|
|
117
|
+
order_data["created_at"] = \
|
|
118
|
+
self.configuration_service.config[INDEX_DATETIME]
|
|
119
|
+
|
|
120
|
+
return self.order_service.create(
|
|
121
|
+
order_data, execute=execute, validate=validate, sync=sync
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
def has_balance(self, symbol, amount, market=None):
|
|
125
|
+
"""
|
|
126
|
+
Function to check if the portfolio has enough balance to
|
|
127
|
+
create an order. This function will return True if the
|
|
128
|
+
portfolio has enough balance to create an order, False
|
|
129
|
+
otherwise.
|
|
130
|
+
|
|
131
|
+
Parameters:
|
|
132
|
+
symbol: The symbol of the asset
|
|
133
|
+
amount: The amount of the asset
|
|
134
|
+
market: The market of the asset
|
|
135
|
+
|
|
136
|
+
Returns:
|
|
137
|
+
Boolean: True if the portfolio has enough balance
|
|
138
|
+
"""
|
|
139
|
+
|
|
140
|
+
portfolio = self.portfolio_service.find({"market": market})
|
|
141
|
+
position = self.position_service.find(
|
|
142
|
+
{"portfolio": portfolio.id, "symbol": symbol}
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
if position is None:
|
|
146
|
+
return False
|
|
147
|
+
|
|
148
|
+
return position.get_amount() >= amount
|
|
149
|
+
|
|
150
|
+
def create_limit_order(
|
|
151
|
+
self,
|
|
152
|
+
target_symbol,
|
|
153
|
+
price,
|
|
154
|
+
order_side,
|
|
155
|
+
amount=None,
|
|
156
|
+
amount_trading_symbol=None,
|
|
157
|
+
percentage=None,
|
|
158
|
+
percentage_of_portfolio=None,
|
|
159
|
+
percentage_of_position=None,
|
|
160
|
+
precision=None,
|
|
161
|
+
market=None,
|
|
162
|
+
execute=True,
|
|
163
|
+
validate=True,
|
|
164
|
+
sync=True
|
|
165
|
+
) -> Order:
|
|
166
|
+
"""
|
|
167
|
+
Function to create a limit order. This function will create a limit
|
|
168
|
+
order and execute it if the execute parameter is set to True. If the
|
|
169
|
+
validate parameter is set to True, the order will be validated
|
|
170
|
+
|
|
171
|
+
Args:
|
|
172
|
+
target_symbol: The symbol of the asset to trade
|
|
173
|
+
price: The price of the asset
|
|
174
|
+
order_side: The side of the order
|
|
175
|
+
amount (optional): The amount of the asset to trade
|
|
176
|
+
amount_trading_symbol (optional): The amount of the
|
|
177
|
+
trading symbol to trade
|
|
178
|
+
percentage (optional): The percentage of the portfolio
|
|
179
|
+
to allocate to the
|
|
180
|
+
order
|
|
181
|
+
percentage_of_portfolio (optional): The percentage
|
|
182
|
+
of the portfolio to allocate to the order
|
|
183
|
+
percentage_of_position (optional): The percentage
|
|
184
|
+
of the position to allocate to
|
|
185
|
+
the order. (Only supported for SELL orders)
|
|
186
|
+
precision (optional): The precision of the amount
|
|
187
|
+
market (optional): The market to trade the asset
|
|
188
|
+
execute (optional): Default True. If set to True,
|
|
189
|
+
the order will be executed
|
|
190
|
+
validate (optional): Default True. If set to
|
|
191
|
+
True, the order will be validated
|
|
192
|
+
sync (optional): Default True. If set to True,
|
|
193
|
+
the created order will be synced with the
|
|
194
|
+
portfolio of the algorithm
|
|
195
|
+
|
|
196
|
+
Returns:
|
|
197
|
+
Order: Instance of the order created
|
|
198
|
+
"""
|
|
199
|
+
portfolio = self.portfolio_service.find({"market": market})
|
|
200
|
+
|
|
201
|
+
if percentage_of_portfolio is not None:
|
|
202
|
+
if not OrderSide.BUY.equals(order_side):
|
|
203
|
+
raise OperationalException(
|
|
204
|
+
"Percentage of portfolio is only supported for BUY orders."
|
|
205
|
+
)
|
|
206
|
+
|
|
207
|
+
net_size = portfolio.get_net_size()
|
|
208
|
+
size = net_size * (percentage_of_portfolio / 100)
|
|
209
|
+
amount = size / price
|
|
210
|
+
|
|
211
|
+
elif percentage_of_position is not None:
|
|
212
|
+
|
|
213
|
+
if not OrderSide.SELL.equals(order_side):
|
|
214
|
+
raise OperationalException(
|
|
215
|
+
"Percentage of position is only supported for SELL orders."
|
|
216
|
+
)
|
|
217
|
+
|
|
218
|
+
position = self.position_service.find(
|
|
219
|
+
{
|
|
220
|
+
"symbol": target_symbol,
|
|
221
|
+
"portfolio": portfolio.id
|
|
222
|
+
}
|
|
223
|
+
)
|
|
224
|
+
amount = position.get_amount() * (percentage_of_position / 100)
|
|
225
|
+
|
|
226
|
+
elif percentage is not None:
|
|
227
|
+
net_size = portfolio.get_net_size()
|
|
228
|
+
size = net_size * (percentage / 100)
|
|
229
|
+
amount = size / price
|
|
230
|
+
|
|
231
|
+
if precision is not None:
|
|
232
|
+
amount = RoundingService.round_down(amount, precision)
|
|
233
|
+
|
|
234
|
+
if amount_trading_symbol is not None:
|
|
235
|
+
amount = amount_trading_symbol / price
|
|
236
|
+
|
|
237
|
+
if amount is None:
|
|
238
|
+
raise OperationalException(
|
|
239
|
+
"The amount parameter is required to create a limit order." +
|
|
240
|
+
"Either the amount, amount_trading_symbol, percentage, " +
|
|
241
|
+
"percentage_of_portfolio or percentage_of_position "
|
|
242
|
+
"parameter must be specified."
|
|
243
|
+
)
|
|
244
|
+
|
|
245
|
+
logger.info(
|
|
246
|
+
f"Creating limit order: {target_symbol} "
|
|
247
|
+
f"{order_side} {amount} @ {price}"
|
|
248
|
+
)
|
|
249
|
+
order_data = {
|
|
250
|
+
"target_symbol": target_symbol,
|
|
251
|
+
"price": price,
|
|
252
|
+
"amount": amount,
|
|
253
|
+
"order_type": OrderType.LIMIT.value,
|
|
254
|
+
"order_side": OrderSide.from_value(order_side).value,
|
|
255
|
+
"portfolio_id": portfolio.id,
|
|
256
|
+
"status": OrderStatus.CREATED.value,
|
|
257
|
+
"trading_symbol": portfolio.trading_symbol,
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
if BACKTESTING_FLAG in self.configuration_service.config \
|
|
261
|
+
and self.configuration_service.config[BACKTESTING_FLAG]:
|
|
262
|
+
order_data["created_at"] = \
|
|
263
|
+
self.configuration_service.config[INDEX_DATETIME]
|
|
264
|
+
|
|
265
|
+
return self.order_service.create(
|
|
266
|
+
order_data, execute=execute, validate=validate, sync=sync
|
|
267
|
+
)
|
|
268
|
+
|
|
269
|
+
def create_limit_sell_order(
|
|
270
|
+
self,
|
|
271
|
+
target_symbol,
|
|
272
|
+
price,
|
|
273
|
+
amount=None,
|
|
274
|
+
percentage_of_position=None,
|
|
275
|
+
market=None,
|
|
276
|
+
portfolio_id=None
|
|
277
|
+
) -> Order:
|
|
278
|
+
"""
|
|
279
|
+
Function to create a limit sell order. This function will create
|
|
280
|
+
a limit sell order. If the amount parameter is specified, the
|
|
281
|
+
order will be created with the specified amount. If the
|
|
282
|
+
percentage_of_position parameter is specified, the order will be
|
|
283
|
+
created with the percentage of the position specified. If neither
|
|
284
|
+
the amount nor the percentage_of_position parameter is specified,
|
|
285
|
+
an OperationalException will be raised.
|
|
286
|
+
|
|
287
|
+
Args:
|
|
288
|
+
target_symbol (str): The symbol of the asset to sell
|
|
289
|
+
price (float): The price at which to sell the asset
|
|
290
|
+
amount (float, optional): The amount of the asset to sell
|
|
291
|
+
percentage_of_position (float, optional): The percentage of the
|
|
292
|
+
position to sell.
|
|
293
|
+
market (str, optional): the portfolio corresponding to the market
|
|
294
|
+
to sell the asset
|
|
295
|
+
portfolio_id: (str, optional): The ID of the portfolio to sell
|
|
296
|
+
the asset from.
|
|
297
|
+
|
|
298
|
+
Returns:
|
|
299
|
+
Order: The order created
|
|
300
|
+
"""
|
|
301
|
+
if amount is None and percentage_of_position is None:
|
|
302
|
+
raise OperationalException(
|
|
303
|
+
"Either amount or percentage_of_position must be specified "
|
|
304
|
+
"to create a limit sell order."
|
|
305
|
+
)
|
|
306
|
+
|
|
307
|
+
if portfolio_id is not None:
|
|
308
|
+
portfolio = self.portfolio_service.get(portfolio_id)
|
|
309
|
+
elif market is not None:
|
|
310
|
+
portfolio = self.portfolio_service.find({"market": market})
|
|
311
|
+
else:
|
|
312
|
+
portfolio = self.portfolio_service.get_all()[0]
|
|
313
|
+
|
|
314
|
+
if percentage_of_position is not None:
|
|
315
|
+
position = self.position_service.find(
|
|
316
|
+
{
|
|
317
|
+
"symbol": target_symbol,
|
|
318
|
+
"portfolio": portfolio.id
|
|
319
|
+
}
|
|
320
|
+
)
|
|
321
|
+
amount = position.get_amount() * (percentage_of_position / 100)
|
|
322
|
+
|
|
323
|
+
logger.info(
|
|
324
|
+
f"Creating limit order: {target_symbol} "
|
|
325
|
+
f"SELL {amount} @ {price}"
|
|
326
|
+
)
|
|
327
|
+
order_data = {
|
|
328
|
+
"target_symbol": target_symbol,
|
|
329
|
+
"price": price,
|
|
330
|
+
"amount": amount,
|
|
331
|
+
"order_type": OrderType.LIMIT.value,
|
|
332
|
+
"order_side": OrderSide.SELL.value,
|
|
333
|
+
"portfolio_id": portfolio.id,
|
|
334
|
+
"status": OrderStatus.CREATED.value,
|
|
335
|
+
"trading_symbol": portfolio.trading_symbol,
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
if BACKTESTING_FLAG in self.configuration_service.config \
|
|
339
|
+
and self.configuration_service.config[BACKTESTING_FLAG]:
|
|
340
|
+
order_data["created_at"] = \
|
|
341
|
+
self.configuration_service.config[INDEX_DATETIME]
|
|
342
|
+
|
|
343
|
+
return self.order_service.create(order_data)
|
|
344
|
+
|
|
345
|
+
def create_limit_buy_order(
|
|
346
|
+
self,
|
|
347
|
+
target_symbol,
|
|
348
|
+
price,
|
|
349
|
+
amount=None,
|
|
350
|
+
percentage_of_portfolio=None,
|
|
351
|
+
market=None,
|
|
352
|
+
portfolio_id=None
|
|
353
|
+
) -> Order:
|
|
354
|
+
"""
|
|
355
|
+
Function to create a limit buy order. This function will create
|
|
356
|
+
a limit buy order. If the amount parameter is specified, the
|
|
357
|
+
order will be created with the specified amount. If the
|
|
358
|
+
percentage_of_portfolio parameter is specified, the order will be
|
|
359
|
+
created with the percentage of the portfolio specified. If neither
|
|
360
|
+
the amount nor the percentage_of_portfolio parameter is specified,
|
|
361
|
+
an OperationalException will be raised.
|
|
362
|
+
|
|
363
|
+
Args:
|
|
364
|
+
target_symbol (str): The symbol of the asset to buy
|
|
365
|
+
price (float): The price at which to buy the asset
|
|
366
|
+
amount (float, optional): The amount of the asset to buy
|
|
367
|
+
percentage_of_portfolio (float, optional): The percentage of the
|
|
368
|
+
portfolio to buy.
|
|
369
|
+
market (str, optional): the portfolio corresponding to the market
|
|
370
|
+
to buy the asset
|
|
371
|
+
portfolio_id (str, optional): The ID of the portfolio to buy
|
|
372
|
+
the asset from.
|
|
373
|
+
|
|
374
|
+
Returns:
|
|
375
|
+
Order: The order created
|
|
376
|
+
"""
|
|
377
|
+
|
|
378
|
+
if amount is None and percentage_of_portfolio is None:
|
|
379
|
+
raise OperationalException(
|
|
380
|
+
"Either amount or percentage_of_portfolio must be specified "
|
|
381
|
+
"to create a limit buy order."
|
|
382
|
+
)
|
|
383
|
+
|
|
384
|
+
if portfolio_id is not None:
|
|
385
|
+
portfolio = self.portfolio_service.get(portfolio_id)
|
|
386
|
+
elif market is not None:
|
|
387
|
+
portfolio = self.portfolio_service.find({"market": market})
|
|
388
|
+
else:
|
|
389
|
+
portfolio = self.portfolio_service.get_all()[0]
|
|
390
|
+
|
|
391
|
+
if percentage_of_portfolio is not None:
|
|
392
|
+
net_size = portfolio.get_net_size()
|
|
393
|
+
size = net_size * (percentage_of_portfolio / 100)
|
|
394
|
+
amount = size / price
|
|
395
|
+
logger.info(
|
|
396
|
+
f"Creating limit order: {target_symbol} "
|
|
397
|
+
f"BUY {amount} @ {price}"
|
|
398
|
+
)
|
|
399
|
+
order_data = {
|
|
400
|
+
"target_symbol": target_symbol,
|
|
401
|
+
"price": price,
|
|
402
|
+
"amount": amount,
|
|
403
|
+
"order_type": OrderType.LIMIT.value,
|
|
404
|
+
"order_side": OrderSide.BUY.value,
|
|
405
|
+
"portfolio_id": portfolio.id,
|
|
406
|
+
"status": OrderStatus.CREATED.value,
|
|
407
|
+
"trading_symbol": portfolio.trading_symbol,
|
|
408
|
+
}
|
|
409
|
+
if BACKTESTING_FLAG in self.configuration_service.config \
|
|
410
|
+
and self.configuration_service.config[BACKTESTING_FLAG]:
|
|
411
|
+
order_data["created_at"] = \
|
|
412
|
+
self.configuration_service.config[INDEX_DATETIME]
|
|
413
|
+
return self.order_service.create(
|
|
414
|
+
order_data, execute=True, validate=True, sync=True
|
|
415
|
+
)
|
|
416
|
+
|
|
417
|
+
def get_portfolio(self, market=None) -> Portfolio:
|
|
418
|
+
"""
|
|
419
|
+
Function to get the portfolio of the algorithm. This function
|
|
420
|
+
will return the portfolio of the algorithm. If the market
|
|
421
|
+
parameter is specified, the portfolio of the specified market
|
|
422
|
+
will be returned.
|
|
423
|
+
|
|
424
|
+
Parameters:
|
|
425
|
+
market: The market of the portfolio
|
|
426
|
+
|
|
427
|
+
Returns:
|
|
428
|
+
Portfolio: The portfolio of the algorithm
|
|
429
|
+
"""
|
|
430
|
+
|
|
431
|
+
if market is None:
|
|
432
|
+
portfolio = self.portfolio_service.get_all()[0]
|
|
433
|
+
else:
|
|
434
|
+
portfolio = self.portfolio_service.find({"market": market})
|
|
435
|
+
|
|
436
|
+
# Retrieve positions
|
|
437
|
+
positions = self.position_service.get_all(
|
|
438
|
+
{"portfolio": portfolio.id}
|
|
439
|
+
)
|
|
440
|
+
|
|
441
|
+
if BACKTESTING_FLAG in self.configuration_service.config \
|
|
442
|
+
and self.configuration_service.config[BACKTESTING_FLAG]:
|
|
443
|
+
date = self.configuration_service.config[INDEX_DATETIME]
|
|
444
|
+
else:
|
|
445
|
+
date = datetime.now(tz=timezone.utc)
|
|
446
|
+
|
|
447
|
+
allocated = 0.0
|
|
448
|
+
|
|
449
|
+
for position in positions:
|
|
450
|
+
|
|
451
|
+
if position.symbol != portfolio.trading_symbol:
|
|
452
|
+
ticker = self.data_provider_service.get_ticker_data(
|
|
453
|
+
symbol=f"{position.symbol}/{portfolio.trading_symbol}",
|
|
454
|
+
market=portfolio.market,
|
|
455
|
+
date=date
|
|
456
|
+
)
|
|
457
|
+
if ticker is not None and "bid" in ticker:
|
|
458
|
+
allocated += position.get_amount() * ticker["bid"]
|
|
459
|
+
|
|
460
|
+
portfolio.allocated = allocated
|
|
461
|
+
return portfolio
|
|
462
|
+
|
|
463
|
+
def get_latest_price(self, symbol, market=None):
|
|
464
|
+
|
|
465
|
+
if BACKTESTING_FLAG in self.configuration_service.config \
|
|
466
|
+
and self.configuration_service.config[BACKTESTING_FLAG]:
|
|
467
|
+
date = self.configuration_service.config[INDEX_DATETIME]
|
|
468
|
+
else:
|
|
469
|
+
date = datetime.now(tz=timezone.utc)
|
|
470
|
+
|
|
471
|
+
ticker = self.data_provider_service.get_ticker_data(
|
|
472
|
+
symbol=symbol,
|
|
473
|
+
market=market,
|
|
474
|
+
date=date
|
|
475
|
+
)
|
|
476
|
+
|
|
477
|
+
return ticker['bid'] if ticker and 'bid' in ticker else None
|
|
478
|
+
|
|
479
|
+
def get_portfolios(self):
|
|
480
|
+
"""
|
|
481
|
+
Function to get all portfolios of the algorithm. This function
|
|
482
|
+
will return all portfolios of the algorithm.
|
|
483
|
+
|
|
484
|
+
Returns:
|
|
485
|
+
List[Portfolio]: A list of all portfolios of the algorithm
|
|
486
|
+
"""
|
|
487
|
+
return self.portfolio_service.get_all()
|
|
488
|
+
|
|
489
|
+
def get_unallocated(self, market=None) -> float:
|
|
490
|
+
"""
|
|
491
|
+
Function to get the unallocated balance of the portfolio. This
|
|
492
|
+
function will return the unallocated balance of the portfolio.
|
|
493
|
+
If the market parameter is specified, the unallocated balance
|
|
494
|
+
of the specified market will be returned.
|
|
495
|
+
|
|
496
|
+
Args:
|
|
497
|
+
market: The market of the portfolio
|
|
498
|
+
|
|
499
|
+
Returns:
|
|
500
|
+
float: The unallocated balance of the portfolio
|
|
501
|
+
"""
|
|
502
|
+
|
|
503
|
+
if market:
|
|
504
|
+
portfolio = self.portfolio_service.find({{"market": market}})
|
|
505
|
+
else:
|
|
506
|
+
portfolio = self.portfolio_service.get_all()[0]
|
|
507
|
+
|
|
508
|
+
trading_symbol = portfolio.trading_symbol
|
|
509
|
+
return self.position_service.find(
|
|
510
|
+
{"portfolio": portfolio.id, "symbol": trading_symbol}
|
|
511
|
+
).get_amount()
|
|
512
|
+
|
|
513
|
+
def get_total_size(self):
|
|
514
|
+
"""
|
|
515
|
+
Returns the total size of the portfolio.
|
|
516
|
+
|
|
517
|
+
The total size of the portfolio is the unallocated balance and the
|
|
518
|
+
allocated balance of the portfolio.
|
|
519
|
+
|
|
520
|
+
Returns:
|
|
521
|
+
float: The total size of the portfolio
|
|
522
|
+
"""
|
|
523
|
+
return self.get_unallocated() + self.get_allocated()
|
|
524
|
+
|
|
525
|
+
def get_order(
|
|
526
|
+
self,
|
|
527
|
+
reference_id=None,
|
|
528
|
+
market=None,
|
|
529
|
+
target_symbol=None,
|
|
530
|
+
trading_symbol=None,
|
|
531
|
+
order_side=None,
|
|
532
|
+
order_type=None
|
|
533
|
+
) -> Order:
|
|
534
|
+
"""
|
|
535
|
+
Function to retrieve an order.
|
|
536
|
+
|
|
537
|
+
Exception is thrown when no param has been provided.
|
|
538
|
+
|
|
539
|
+
Args:
|
|
540
|
+
reference_id [optional] (int): id given by the external
|
|
541
|
+
market or exchange.
|
|
542
|
+
market [optional] (str): the market that the order was
|
|
543
|
+
executed on.
|
|
544
|
+
target_symbol [optional] (str): the symbol of the asset
|
|
545
|
+
that the order was executed
|
|
546
|
+
"""
|
|
547
|
+
query_params = {}
|
|
548
|
+
|
|
549
|
+
if reference_id:
|
|
550
|
+
query_params["reference_id"] = reference_id
|
|
551
|
+
|
|
552
|
+
if target_symbol:
|
|
553
|
+
query_params["target_symbol"] = target_symbol
|
|
554
|
+
|
|
555
|
+
if trading_symbol:
|
|
556
|
+
query_params["trading_symbol"] = trading_symbol
|
|
557
|
+
|
|
558
|
+
if order_side:
|
|
559
|
+
query_params["order_side"] = order_side
|
|
560
|
+
|
|
561
|
+
if order_type:
|
|
562
|
+
query_params["order_type"] = order_type
|
|
563
|
+
|
|
564
|
+
if market:
|
|
565
|
+
portfolio = self.portfolio_service.find({"market": market})
|
|
566
|
+
positions = self.position_service.get_all(
|
|
567
|
+
{"portfolio": portfolio.id}
|
|
568
|
+
)
|
|
569
|
+
query_params["position"] = [position.id for position in positions]
|
|
570
|
+
|
|
571
|
+
if not query_params:
|
|
572
|
+
raise OperationalException(
|
|
573
|
+
"No parameters provided to get order."
|
|
574
|
+
)
|
|
575
|
+
|
|
576
|
+
return self.order_service.find(query_params)
|
|
577
|
+
|
|
578
|
+
def get_orders(
|
|
579
|
+
self,
|
|
580
|
+
target_symbol=None,
|
|
581
|
+
status=None,
|
|
582
|
+
order_type=None,
|
|
583
|
+
order_side=None,
|
|
584
|
+
market=None
|
|
585
|
+
) -> List[Order]:
|
|
586
|
+
|
|
587
|
+
if market is None:
|
|
588
|
+
portfolio = self.portfolio_service.get_all()[0]
|
|
589
|
+
else:
|
|
590
|
+
portfolio = self.portfolio_service.find({"market": market})
|
|
591
|
+
|
|
592
|
+
positions = self.position_service.get_all({"portfolio": portfolio.id})
|
|
593
|
+
return self.order_service.get_all(
|
|
594
|
+
{
|
|
595
|
+
"position": [position.id for position in positions],
|
|
596
|
+
"target_symbol": target_symbol,
|
|
597
|
+
"status": status,
|
|
598
|
+
"order_type": order_type,
|
|
599
|
+
"order_side": order_side
|
|
600
|
+
}
|
|
601
|
+
)
|
|
602
|
+
|
|
603
|
+
def get_positions(
|
|
604
|
+
self,
|
|
605
|
+
market=None,
|
|
606
|
+
identifier=None,
|
|
607
|
+
amount_gt=None,
|
|
608
|
+
amount_gte=None,
|
|
609
|
+
amount_lt=None,
|
|
610
|
+
amount_lte=None
|
|
611
|
+
) -> List[Position]:
|
|
612
|
+
"""
|
|
613
|
+
Function to get all positions. This function will return all
|
|
614
|
+
positions that match the specified query parameters. If the
|
|
615
|
+
market parameter is specified, the positions of the specified
|
|
616
|
+
market will be returned. If the identifier parameter is
|
|
617
|
+
specified, the positions of the specified portfolio will be
|
|
618
|
+
returned. If the amount_gt parameter is specified, the positions
|
|
619
|
+
with an amount greater than the specified amount will be returned.
|
|
620
|
+
If the amount_gte parameter is specified, the positions with an
|
|
621
|
+
amount greater than or equal to the specified amount will be
|
|
622
|
+
returned. If the amount_lt parameter is specified, the positions
|
|
623
|
+
with an amount less than the specified amount will be returned.
|
|
624
|
+
If the amount_lte parameter is specified, the positions with an
|
|
625
|
+
amount less than or equal to the specified amount will be returned.
|
|
626
|
+
|
|
627
|
+
Parameters:
|
|
628
|
+
market: The market of the portfolio where the positions are
|
|
629
|
+
identifier: The identifier of the portfolio
|
|
630
|
+
amount_gt: The amount of the asset must be greater than this
|
|
631
|
+
amount_gte: The amount of the asset must be greater than or
|
|
632
|
+
equal to this
|
|
633
|
+
amount_lt: The amount of the asset must be less than this
|
|
634
|
+
amount_lte: The amount of the asset must be less than or equal
|
|
635
|
+
to this
|
|
636
|
+
|
|
637
|
+
Returns:
|
|
638
|
+
List[Position]: A list of positions that match the query parameters
|
|
639
|
+
"""
|
|
640
|
+
query_params = {}
|
|
641
|
+
|
|
642
|
+
if market is not None:
|
|
643
|
+
query_params["market"] = market
|
|
644
|
+
|
|
645
|
+
if identifier is not None:
|
|
646
|
+
query_params["identifier"] = identifier
|
|
647
|
+
|
|
648
|
+
if amount_gt is not None:
|
|
649
|
+
query_params["amount_gt"] = amount_gt
|
|
650
|
+
|
|
651
|
+
if amount_gte is not None:
|
|
652
|
+
query_params["amount_gte"] = amount_gte
|
|
653
|
+
|
|
654
|
+
if amount_lt is not None:
|
|
655
|
+
query_params["amount_lt"] = amount_lt
|
|
656
|
+
|
|
657
|
+
if amount_lte is not None:
|
|
658
|
+
query_params["amount_lte"] = amount_lte
|
|
659
|
+
|
|
660
|
+
portfolios = self.portfolio_service.get_all(query_params)
|
|
661
|
+
|
|
662
|
+
if not portfolios:
|
|
663
|
+
raise OperationalException("No portfolio found.")
|
|
664
|
+
|
|
665
|
+
portfolio = portfolios[0]
|
|
666
|
+
return self.position_service.get_all(
|
|
667
|
+
{"portfolio": portfolio.id}
|
|
668
|
+
)
|
|
669
|
+
|
|
670
|
+
def get_position(self, symbol, market=None, identifier=None) -> Position:
|
|
671
|
+
"""
|
|
672
|
+
Function to get a position. This function will return the
|
|
673
|
+
position that matches the specified query parameters. If the
|
|
674
|
+
market parameter is specified, the position of the specified
|
|
675
|
+
market will be returned. If the identifier parameter is
|
|
676
|
+
specified, the position of the specified portfolio will be
|
|
677
|
+
returned.
|
|
678
|
+
|
|
679
|
+
Parameters:
|
|
680
|
+
symbol: The symbol of the asset that represents the position
|
|
681
|
+
market: The market of the portfolio where the position is located
|
|
682
|
+
identifier: The identifier of the portfolio
|
|
683
|
+
|
|
684
|
+
Returns:
|
|
685
|
+
Position: The position that matches the query parameters
|
|
686
|
+
"""
|
|
687
|
+
|
|
688
|
+
query_params = {}
|
|
689
|
+
|
|
690
|
+
if market is not None:
|
|
691
|
+
query_params["market"] = market
|
|
692
|
+
|
|
693
|
+
if identifier is not None:
|
|
694
|
+
query_params["identifier"] = identifier
|
|
695
|
+
|
|
696
|
+
portfolios = self.portfolio_service.get_all(query_params)
|
|
697
|
+
|
|
698
|
+
if not portfolios:
|
|
699
|
+
raise OperationalException("No portfolio found.")
|
|
700
|
+
|
|
701
|
+
portfolio = portfolios[0]
|
|
702
|
+
|
|
703
|
+
try:
|
|
704
|
+
return self.position_service.find(
|
|
705
|
+
{"portfolio": portfolio.id, "symbol": symbol}
|
|
706
|
+
)
|
|
707
|
+
except OperationalException:
|
|
708
|
+
return None
|
|
709
|
+
|
|
710
|
+
def has_position(
|
|
711
|
+
self,
|
|
712
|
+
symbol,
|
|
713
|
+
market=None,
|
|
714
|
+
identifier=None,
|
|
715
|
+
amount_gt=0,
|
|
716
|
+
amount_gte=None,
|
|
717
|
+
amount_lt=None,
|
|
718
|
+
amount_lte=None
|
|
719
|
+
):
|
|
720
|
+
"""
|
|
721
|
+
Function to check if a position exists. This function will return
|
|
722
|
+
True if a position exists, False otherwise. This function will check
|
|
723
|
+
if the amount > 0 condition by default.
|
|
724
|
+
|
|
725
|
+
Parameters:
|
|
726
|
+
param symbol: The symbol of the asset
|
|
727
|
+
param market: The market of the asset
|
|
728
|
+
param identifier: The identifier of the portfolio
|
|
729
|
+
param amount_gt: The amount of the asset must be greater than this
|
|
730
|
+
param amount_gte: The amount of the asset must be greater than
|
|
731
|
+
or equal to this
|
|
732
|
+
param amount_lt: The amount of the asset must be less than this
|
|
733
|
+
param amount_lte: The amount of the asset must be less than
|
|
734
|
+
or equal to this
|
|
735
|
+
|
|
736
|
+
Returns:
|
|
737
|
+
Boolean: True if a position exists, False otherwise
|
|
738
|
+
"""
|
|
739
|
+
|
|
740
|
+
return self.position_exists(
|
|
741
|
+
symbol=symbol,
|
|
742
|
+
market=market,
|
|
743
|
+
identifier=identifier,
|
|
744
|
+
amount_gt=amount_gt,
|
|
745
|
+
amount_gte=amount_gte,
|
|
746
|
+
amount_lt=amount_lt,
|
|
747
|
+
amount_lte=amount_lte
|
|
748
|
+
)
|
|
749
|
+
|
|
750
|
+
def position_exists(
|
|
751
|
+
self,
|
|
752
|
+
symbol,
|
|
753
|
+
market=None,
|
|
754
|
+
identifier=None,
|
|
755
|
+
amount_gt=None,
|
|
756
|
+
amount_gte=None,
|
|
757
|
+
amount_lt=None,
|
|
758
|
+
amount_lte=None
|
|
759
|
+
) -> bool:
|
|
760
|
+
"""
|
|
761
|
+
Function to check if a position exists. This function will return
|
|
762
|
+
True if a position exists, False otherwise. This function will
|
|
763
|
+
not check the amount > 0 condition by default. If you want to
|
|
764
|
+
check if a position exists with an amount greater than 0, you
|
|
765
|
+
can use the amount_gt parameter. If you want to check if a
|
|
766
|
+
position exists with an amount greater than or equal to a
|
|
767
|
+
certain amount, you can use the amount_gte parameter. If you
|
|
768
|
+
want to check if a position exists with an amount less than a
|
|
769
|
+
certain amount, you can use the amount_lt parameter. If you want
|
|
770
|
+
to check if a position exists with an amount less than or equal
|
|
771
|
+
to a certain amount, you can use the amount_lte parameter.
|
|
772
|
+
|
|
773
|
+
It is not recommended to use this method directly because it can
|
|
774
|
+
have adverse effects on the algorithm. It is recommended to use
|
|
775
|
+
the has_position method instead.
|
|
776
|
+
|
|
777
|
+
param symbol: The symbol of the asset
|
|
778
|
+
param market: The market of the asset
|
|
779
|
+
param identifier: The identifier of the portfolio
|
|
780
|
+
param amount_gt: The amount of the asset must be greater than this
|
|
781
|
+
param amount_gte: The amount of the asset must be greater than
|
|
782
|
+
or equal to this
|
|
783
|
+
param amount_lt: The amount of the asset must be less than this
|
|
784
|
+
param amount_lte: The amount of the asset must be less than
|
|
785
|
+
or equal to this
|
|
786
|
+
|
|
787
|
+
return: True if a position exists, False otherwise
|
|
788
|
+
"""
|
|
789
|
+
query_params = {}
|
|
790
|
+
|
|
791
|
+
if market is not None:
|
|
792
|
+
query_params["market"] = market
|
|
793
|
+
|
|
794
|
+
if identifier is not None:
|
|
795
|
+
query_params["identifier"] = identifier
|
|
796
|
+
|
|
797
|
+
if amount_gt is not None:
|
|
798
|
+
query_params["amount_gt"] = amount_gt
|
|
799
|
+
|
|
800
|
+
if amount_gte is not None:
|
|
801
|
+
query_params["amount_gte"] = amount_gte
|
|
802
|
+
|
|
803
|
+
if amount_lt is not None:
|
|
804
|
+
query_params["amount_lt"] = amount_lt
|
|
805
|
+
|
|
806
|
+
if amount_lte is not None:
|
|
807
|
+
query_params["amount_lte"] = amount_lte
|
|
808
|
+
|
|
809
|
+
query_params["symbol"] = symbol
|
|
810
|
+
return self.position_service.exists(query_params)
|
|
811
|
+
|
|
812
|
+
def get_position_percentage_of_portfolio_by_net_size(
|
|
813
|
+
self, symbol, market=None, identifier=None
|
|
814
|
+
) -> float:
|
|
815
|
+
"""
|
|
816
|
+
Returns the percentage of the portfolio that is allocated to a
|
|
817
|
+
position. This is calculated by dividing the cost of the position
|
|
818
|
+
by the total net size of the portfolio.
|
|
819
|
+
|
|
820
|
+
The total net size of the portfolio is the initial balance of the
|
|
821
|
+
portfolio plus the all the net gains of your trades.
|
|
822
|
+
"""
|
|
823
|
+
query_params = {}
|
|
824
|
+
|
|
825
|
+
if market is not None:
|
|
826
|
+
query_params["market"] = market
|
|
827
|
+
|
|
828
|
+
if identifier is not None:
|
|
829
|
+
query_params["identifier"] = identifier
|
|
830
|
+
|
|
831
|
+
portfolios = self.portfolio_service.get_all(query_params)
|
|
832
|
+
|
|
833
|
+
if not portfolios:
|
|
834
|
+
raise OperationalException("No portfolio found.")
|
|
835
|
+
|
|
836
|
+
portfolio = portfolios[0]
|
|
837
|
+
position = self.position_service.find(
|
|
838
|
+
{"portfolio": portfolio.id, "symbol": symbol}
|
|
839
|
+
)
|
|
840
|
+
net_size = portfolio.get_net_size()
|
|
841
|
+
return (position.cost / net_size) * 100
|
|
842
|
+
|
|
843
|
+
def close_position(
|
|
844
|
+
self,
|
|
845
|
+
position=None,
|
|
846
|
+
symbol=None,
|
|
847
|
+
portfolio=None,
|
|
848
|
+
precision=None,
|
|
849
|
+
price=None
|
|
850
|
+
) -> Order:
|
|
851
|
+
"""
|
|
852
|
+
Function to close a position. This function will close a position
|
|
853
|
+
by creating a market order to sell the position. If the precision
|
|
854
|
+
parameter is specified, the amount of the order will be rounded
|
|
855
|
+
down to the specified precision.
|
|
856
|
+
|
|
857
|
+
Args:
|
|
858
|
+
position (Optional): The position to close
|
|
859
|
+
symbol (Optional): The symbol of the asset
|
|
860
|
+
portfolio (Optional): The portfolio where the position is located
|
|
861
|
+
precision (Optional): The precision of the amount
|
|
862
|
+
price (Optional[Float]): The price with which the position needs
|
|
863
|
+
to be closed.
|
|
864
|
+
|
|
865
|
+
Returns:
|
|
866
|
+
Order: The order created to close the position
|
|
867
|
+
"""
|
|
868
|
+
query_params = {}
|
|
869
|
+
|
|
870
|
+
if position is None and (symbol is None and portfolio is None):
|
|
871
|
+
raise OperationalException(
|
|
872
|
+
"Either position or symbol and portfolio parameters must "
|
|
873
|
+
"be specified to close a position."
|
|
874
|
+
)
|
|
875
|
+
|
|
876
|
+
if position is not None:
|
|
877
|
+
query_params["id"] = position.id
|
|
878
|
+
query_params["symbol"] = position.symbol
|
|
879
|
+
|
|
880
|
+
if symbol is not None:
|
|
881
|
+
query_params["symbol"] = symbol
|
|
882
|
+
|
|
883
|
+
if portfolio is not None:
|
|
884
|
+
query_params["portfolio"] = portfolio.id
|
|
885
|
+
|
|
886
|
+
position = self.position_service.find(query_params)
|
|
887
|
+
portfolio = self.portfolio_service.get(position.portfolio_id)
|
|
888
|
+
|
|
889
|
+
if position.get_amount() == 0:
|
|
890
|
+
logger.warning("Cannot close position. Amount is 0.")
|
|
891
|
+
return None
|
|
892
|
+
|
|
893
|
+
if position.get_symbol() == portfolio.get_trading_symbol():
|
|
894
|
+
raise OperationalException(
|
|
895
|
+
"Cannot close position. The position is the same as the "
|
|
896
|
+
"trading symbol of the portfolio."
|
|
897
|
+
)
|
|
898
|
+
|
|
899
|
+
for order in self.order_service \
|
|
900
|
+
.get_all(
|
|
901
|
+
{
|
|
902
|
+
"position": position.id,
|
|
903
|
+
"status": OrderStatus.OPEN.value
|
|
904
|
+
}
|
|
905
|
+
):
|
|
906
|
+
self.order_service.cancel_order(order)
|
|
907
|
+
|
|
908
|
+
target_symbol = position.get_symbol()
|
|
909
|
+
symbol = f"{target_symbol.upper()}/{portfolio.trading_symbol.upper()}"
|
|
910
|
+
|
|
911
|
+
if price is None:
|
|
912
|
+
ticker = self.data_provider_service.get_ticker_data(
|
|
913
|
+
symbol=symbol,
|
|
914
|
+
market=portfolio.market,
|
|
915
|
+
date=self.config[INDEX_DATETIME]
|
|
916
|
+
)
|
|
917
|
+
price = ticker["bid"]
|
|
918
|
+
|
|
919
|
+
logger.info(
|
|
920
|
+
f"Closing position {position.symbol} "
|
|
921
|
+
f"with amount {position.get_amount()} "
|
|
922
|
+
f"at price {price}"
|
|
923
|
+
)
|
|
924
|
+
return self.create_limit_order(
|
|
925
|
+
target_symbol=position.symbol,
|
|
926
|
+
amount=position.get_amount(),
|
|
927
|
+
order_side=OrderSide.SELL.value,
|
|
928
|
+
price=price,
|
|
929
|
+
precision=precision,
|
|
930
|
+
)
|
|
931
|
+
|
|
932
|
+
def get_allocated(self, market=None, identifier=None) -> float:
|
|
933
|
+
|
|
934
|
+
if self.portfolio_configuration_service.count() > 1 \
|
|
935
|
+
and identifier is None and market is None:
|
|
936
|
+
raise OperationalException(
|
|
937
|
+
"Multiple portfolios found. Please specify a "
|
|
938
|
+
"portfolio identifier."
|
|
939
|
+
)
|
|
940
|
+
|
|
941
|
+
if market is not None and identifier is not None:
|
|
942
|
+
portfolio_configurations = self.portfolio_configuration_service \
|
|
943
|
+
.get_all()
|
|
944
|
+
|
|
945
|
+
else:
|
|
946
|
+
query_params = {"market": market, "identifier": identifier}
|
|
947
|
+
portfolio_configuration = self.portfolio_configuration_service \
|
|
948
|
+
.find(query_params)
|
|
949
|
+
|
|
950
|
+
if not portfolio_configuration:
|
|
951
|
+
raise OperationalException("No portfolio found.")
|
|
952
|
+
|
|
953
|
+
portfolio_configurations = [portfolio_configuration]
|
|
954
|
+
|
|
955
|
+
if len(portfolio_configurations) == 0:
|
|
956
|
+
raise OperationalException("No portfolio found.")
|
|
957
|
+
|
|
958
|
+
portfolios = []
|
|
959
|
+
|
|
960
|
+
for portfolio_configuration in portfolio_configurations:
|
|
961
|
+
portfolio = self.portfolio_service.find(
|
|
962
|
+
{"identifier": portfolio_configuration.identifier}
|
|
963
|
+
)
|
|
964
|
+
portfolio.configuration = portfolio_configuration
|
|
965
|
+
portfolios.append(portfolio)
|
|
966
|
+
|
|
967
|
+
allocated = 0
|
|
968
|
+
|
|
969
|
+
for portfolio in portfolios:
|
|
970
|
+
positions = self.position_service.get_all(
|
|
971
|
+
{"portfolio": portfolio.id}
|
|
972
|
+
)
|
|
973
|
+
|
|
974
|
+
for position in positions:
|
|
975
|
+
if portfolio.trading_symbol == position.symbol:
|
|
976
|
+
continue
|
|
977
|
+
|
|
978
|
+
symbol = f"{position.symbol.upper()}/" \
|
|
979
|
+
f"{portfolio.trading_symbol.upper()}"
|
|
980
|
+
current_date = self.config[INDEX_DATETIME]
|
|
981
|
+
ticker = self.data_provider_service.get_ticker_data(
|
|
982
|
+
symbol=symbol, market=portfolio.market, date=current_date
|
|
983
|
+
)
|
|
984
|
+
allocated = allocated + \
|
|
985
|
+
(position.get_amount() * ticker["bid"])
|
|
986
|
+
|
|
987
|
+
return allocated
|
|
988
|
+
|
|
989
|
+
def get_unfilled(self, market=None, identifier=None) -> float:
|
|
990
|
+
|
|
991
|
+
if self.portfolio_configuration_service.count() > 1 \
|
|
992
|
+
and identifier is None and market is None:
|
|
993
|
+
raise OperationalException(
|
|
994
|
+
"Multiple portfolios found. Please specify a "
|
|
995
|
+
"portfolio identifier."
|
|
996
|
+
)
|
|
997
|
+
|
|
998
|
+
if market is not None and identifier is not None:
|
|
999
|
+
portfolio_configurations = self.portfolio_configuration_service \
|
|
1000
|
+
.get_all()
|
|
1001
|
+
|
|
1002
|
+
else:
|
|
1003
|
+
query_params = {
|
|
1004
|
+
"market": market,
|
|
1005
|
+
"identifier": identifier
|
|
1006
|
+
}
|
|
1007
|
+
portfolio_configurations = [self.portfolio_configuration_service
|
|
1008
|
+
.find(query_params)]
|
|
1009
|
+
|
|
1010
|
+
portfolios = []
|
|
1011
|
+
|
|
1012
|
+
for portfolio_configuration in portfolio_configurations:
|
|
1013
|
+
portfolio = self.portfolio_service.find(
|
|
1014
|
+
{"identifier": portfolio_configuration.identifier}
|
|
1015
|
+
)
|
|
1016
|
+
portfolios.append(portfolio)
|
|
1017
|
+
|
|
1018
|
+
unfilled = 0
|
|
1019
|
+
|
|
1020
|
+
for portfolio in portfolios:
|
|
1021
|
+
orders = self.order_service.get_all(
|
|
1022
|
+
{"status": OrderStatus.OPEN.value, "portfolio": portfolio.id}
|
|
1023
|
+
)
|
|
1024
|
+
unfilled = unfilled + sum(
|
|
1025
|
+
[order.get_amount() * order.get_price() for order in orders]
|
|
1026
|
+
)
|
|
1027
|
+
|
|
1028
|
+
return unfilled
|
|
1029
|
+
|
|
1030
|
+
def get_portfolio_configurations(self):
|
|
1031
|
+
return self.portfolio_configuration_service.get_all()
|
|
1032
|
+
|
|
1033
|
+
def has_open_buy_orders(self, target_symbol, identifier=None, market=None):
|
|
1034
|
+
query_params = {}
|
|
1035
|
+
|
|
1036
|
+
if identifier is not None:
|
|
1037
|
+
portfolio = self.portfolio_service.find(
|
|
1038
|
+
{"identifier": identifier}
|
|
1039
|
+
)
|
|
1040
|
+
query_params["portfolio"] = portfolio.id
|
|
1041
|
+
|
|
1042
|
+
if market is not None:
|
|
1043
|
+
portfolio = self.portfolio_service.find(
|
|
1044
|
+
{"market": market}
|
|
1045
|
+
)
|
|
1046
|
+
query_params["portfolio"] = portfolio.id
|
|
1047
|
+
|
|
1048
|
+
query_params["target_symbol"] = target_symbol
|
|
1049
|
+
query_params["order_side"] = OrderSide.BUY.value
|
|
1050
|
+
query_params["status"] = OrderStatus.OPEN.value
|
|
1051
|
+
return self.order_service.exists(query_params)
|
|
1052
|
+
|
|
1053
|
+
def get_sell_orders(self, target_symbol, identifier=None, market=None):
|
|
1054
|
+
query_params = {}
|
|
1055
|
+
|
|
1056
|
+
if identifier is not None:
|
|
1057
|
+
portfolio = self.portfolio_service.find(
|
|
1058
|
+
{"identifier": identifier}
|
|
1059
|
+
)
|
|
1060
|
+
query_params["portfolio"] = portfolio.id
|
|
1061
|
+
|
|
1062
|
+
if market is not None:
|
|
1063
|
+
portfolio = self.portfolio_service.find(
|
|
1064
|
+
{"market": market}
|
|
1065
|
+
)
|
|
1066
|
+
query_params["portfolio"] = portfolio.id
|
|
1067
|
+
|
|
1068
|
+
query_params["target_symbol"] = target_symbol
|
|
1069
|
+
query_params["order_side"] = OrderSide.SELL.value
|
|
1070
|
+
return self.order_service.get_all(query_params)
|
|
1071
|
+
|
|
1072
|
+
def get_open_orders(
|
|
1073
|
+
self, target_symbol=None, identifier=None, market=None
|
|
1074
|
+
) -> List[Order]:
|
|
1075
|
+
"""
|
|
1076
|
+
Function to get all open orders. This function will return all
|
|
1077
|
+
open orders that match the specified query parameters.
|
|
1078
|
+
|
|
1079
|
+
Args:
|
|
1080
|
+
target_symbol (str): the symbol of the asset
|
|
1081
|
+
identifier (str): the identifier of the portfolio
|
|
1082
|
+
market (str): the market of the asset
|
|
1083
|
+
|
|
1084
|
+
Returns:
|
|
1085
|
+
List[Order]: A list of open orders that match the query parameters
|
|
1086
|
+
"""
|
|
1087
|
+
query_params = {}
|
|
1088
|
+
|
|
1089
|
+
if identifier is not None:
|
|
1090
|
+
portfolio = self.portfolio_service.find(
|
|
1091
|
+
{"identifier": identifier}
|
|
1092
|
+
)
|
|
1093
|
+
query_params["portfolio"] = portfolio.id
|
|
1094
|
+
|
|
1095
|
+
if market is not None:
|
|
1096
|
+
portfolio = self.portfolio_service.find(
|
|
1097
|
+
{"market": market}
|
|
1098
|
+
)
|
|
1099
|
+
query_params["portfolio"] = portfolio.id
|
|
1100
|
+
|
|
1101
|
+
if target_symbol is not None:
|
|
1102
|
+
query_params["target_symbol"] = target_symbol
|
|
1103
|
+
|
|
1104
|
+
query_params["status"] = OrderStatus.OPEN.value
|
|
1105
|
+
return self.order_service.get_all(query_params)
|
|
1106
|
+
|
|
1107
|
+
def get_closed_orders(
|
|
1108
|
+
self, target_symbol=None, identifier=None, market=None, order_side=None
|
|
1109
|
+
) -> List[Order]:
|
|
1110
|
+
"""
|
|
1111
|
+
Function to get all closed orders. This function will return all
|
|
1112
|
+
closed orders that match the specified query parameters.
|
|
1113
|
+
|
|
1114
|
+
Args:
|
|
1115
|
+
target_symbol (str): the symbol of the asset
|
|
1116
|
+
identifier (str): the identifier of the portfolio
|
|
1117
|
+
market (str): the market of the asset
|
|
1118
|
+
order_side (str): the side of the order
|
|
1119
|
+
|
|
1120
|
+
Returns:
|
|
1121
|
+
List[Order]: A list of closed orders that
|
|
1122
|
+
match the query parameters
|
|
1123
|
+
"""
|
|
1124
|
+
query_params = {}
|
|
1125
|
+
|
|
1126
|
+
if identifier is not None:
|
|
1127
|
+
portfolio = self.portfolio_service.find(
|
|
1128
|
+
{"identifier": identifier}
|
|
1129
|
+
)
|
|
1130
|
+
query_params["portfolio"] = portfolio.id
|
|
1131
|
+
|
|
1132
|
+
if order_side is not None:
|
|
1133
|
+
query_params["order_side"] = order_side
|
|
1134
|
+
|
|
1135
|
+
if market is not None:
|
|
1136
|
+
portfolio = self.portfolio_service.find(
|
|
1137
|
+
{"market": market}
|
|
1138
|
+
)
|
|
1139
|
+
query_params["portfolio"] = portfolio.id
|
|
1140
|
+
|
|
1141
|
+
if target_symbol is not None:
|
|
1142
|
+
query_params["target_symbol"] = target_symbol
|
|
1143
|
+
|
|
1144
|
+
query_params["status"] = OrderStatus.CLOSED.value
|
|
1145
|
+
return self.order_service.get_all(query_params)
|
|
1146
|
+
|
|
1147
|
+
def has_open_sell_orders(self, target_symbol, identifier=None,
|
|
1148
|
+
market=None):
|
|
1149
|
+
query_params = {}
|
|
1150
|
+
|
|
1151
|
+
if identifier is not None:
|
|
1152
|
+
portfolio = self.portfolio_service.find(
|
|
1153
|
+
{"identifier": identifier}
|
|
1154
|
+
)
|
|
1155
|
+
query_params["portfolio"] = portfolio.id
|
|
1156
|
+
|
|
1157
|
+
if market is not None:
|
|
1158
|
+
portfolio = self.portfolio_service.find(
|
|
1159
|
+
{"market": market}
|
|
1160
|
+
)
|
|
1161
|
+
query_params["portfolio"] = portfolio.id
|
|
1162
|
+
|
|
1163
|
+
query_params["target_symbol"] = target_symbol
|
|
1164
|
+
query_params["order_side"] = OrderSide.SELL.value
|
|
1165
|
+
query_params["status"] = OrderStatus.OPEN.value
|
|
1166
|
+
return self.order_service.exists(query_params)
|
|
1167
|
+
|
|
1168
|
+
def has_open_orders(
|
|
1169
|
+
self, target_symbol=None, identifier=None, market=None
|
|
1170
|
+
):
|
|
1171
|
+
query_params = {}
|
|
1172
|
+
|
|
1173
|
+
if identifier is not None:
|
|
1174
|
+
portfolio = self.portfolio_service.find(
|
|
1175
|
+
{"identifier": identifier}
|
|
1176
|
+
)
|
|
1177
|
+
query_params["portfolio"] = portfolio.id
|
|
1178
|
+
|
|
1179
|
+
if market is not None:
|
|
1180
|
+
portfolio = self.portfolio_service.find(
|
|
1181
|
+
{"market": market}
|
|
1182
|
+
)
|
|
1183
|
+
query_params["portfolio"] = portfolio.id
|
|
1184
|
+
|
|
1185
|
+
if target_symbol is not None:
|
|
1186
|
+
query_params["target_symbol"] = target_symbol
|
|
1187
|
+
|
|
1188
|
+
query_params["status"] = OrderStatus.OPEN.value
|
|
1189
|
+
return self.order_service.exists(query_params)
|
|
1190
|
+
|
|
1191
|
+
def get_trade(
|
|
1192
|
+
self,
|
|
1193
|
+
target_symbol=None,
|
|
1194
|
+
trading_symbol=None,
|
|
1195
|
+
market=None,
|
|
1196
|
+
portfolio=None,
|
|
1197
|
+
status=None,
|
|
1198
|
+
order_id=None
|
|
1199
|
+
) -> Trade:
|
|
1200
|
+
"""
|
|
1201
|
+
Function to retrieve a trade. This function will return the first
|
|
1202
|
+
trade that matches the specified query parameters.
|
|
1203
|
+
|
|
1204
|
+
Args:
|
|
1205
|
+
market: The market of the asset
|
|
1206
|
+
portfolio: The portfolio of the asset
|
|
1207
|
+
status: The status of the trade
|
|
1208
|
+
order_id: The order id of the trade
|
|
1209
|
+
target_symbol: The symbol of the asset
|
|
1210
|
+
trading_symbol: The trading symbol of the asset
|
|
1211
|
+
|
|
1212
|
+
Returns:
|
|
1213
|
+
Trade: A instance of a trade that matches the query parameters
|
|
1214
|
+
"""
|
|
1215
|
+
query_params = {}
|
|
1216
|
+
|
|
1217
|
+
if market is not None:
|
|
1218
|
+
query_params["market"] = market
|
|
1219
|
+
|
|
1220
|
+
if portfolio is not None:
|
|
1221
|
+
query_params["portfolio"] = portfolio
|
|
1222
|
+
|
|
1223
|
+
if status is not None:
|
|
1224
|
+
query_params["status"] = status
|
|
1225
|
+
|
|
1226
|
+
if order_id is not None:
|
|
1227
|
+
query_params["order_id"] = order_id
|
|
1228
|
+
|
|
1229
|
+
if target_symbol is not None:
|
|
1230
|
+
query_params["target_symbol"] = target_symbol
|
|
1231
|
+
|
|
1232
|
+
if trading_symbol is not None:
|
|
1233
|
+
query_params["trading_symbol"] = trading_symbol
|
|
1234
|
+
|
|
1235
|
+
return self.trade_service.find(query_params)
|
|
1236
|
+
|
|
1237
|
+
def get_trades(
|
|
1238
|
+
self,
|
|
1239
|
+
target_symbol=None,
|
|
1240
|
+
trading_symbol=None,
|
|
1241
|
+
market=None,
|
|
1242
|
+
portfolio=None,
|
|
1243
|
+
status=None,
|
|
1244
|
+
) -> List[Trade]:
|
|
1245
|
+
"""
|
|
1246
|
+
Function to get all trades. This function will return all trades
|
|
1247
|
+
that match the specified query parameters. If the market parameter
|
|
1248
|
+
is specified, the trades with the specified market will be returned.
|
|
1249
|
+
|
|
1250
|
+
Args:
|
|
1251
|
+
market: The market of the asset
|
|
1252
|
+
portfolio: The portfolio of the asset
|
|
1253
|
+
status: The status of the trade
|
|
1254
|
+
target_symbol: The symbol of the asset
|
|
1255
|
+
trading_symbol: The trading symbol of the asset
|
|
1256
|
+
|
|
1257
|
+
Returns:
|
|
1258
|
+
List[Trade]: A list of trades that match the query parameters
|
|
1259
|
+
"""
|
|
1260
|
+
|
|
1261
|
+
query_params = {}
|
|
1262
|
+
|
|
1263
|
+
if market is not None:
|
|
1264
|
+
query_params["market"] = market
|
|
1265
|
+
|
|
1266
|
+
if portfolio is not None:
|
|
1267
|
+
query_params["portfolio"] = portfolio
|
|
1268
|
+
|
|
1269
|
+
if status is not None:
|
|
1270
|
+
query_params["status"] = status
|
|
1271
|
+
|
|
1272
|
+
if target_symbol is not None:
|
|
1273
|
+
query_params["target_symbol"] = target_symbol
|
|
1274
|
+
|
|
1275
|
+
if trading_symbol is not None:
|
|
1276
|
+
query_params["trading_symbol"] = trading_symbol
|
|
1277
|
+
|
|
1278
|
+
return self.trade_service.get_all({"market": market})
|
|
1279
|
+
|
|
1280
|
+
def get_closed_trades(self) -> List[Trade]:
|
|
1281
|
+
"""
|
|
1282
|
+
Function to get all closed trades. This function will return all
|
|
1283
|
+
closed trades of the algorithm.
|
|
1284
|
+
|
|
1285
|
+
Returns:
|
|
1286
|
+
List[Trade]: A list of closed trades
|
|
1287
|
+
"""
|
|
1288
|
+
return self.trade_service.get_all({"status": TradeStatus.CLOSED.value})
|
|
1289
|
+
|
|
1290
|
+
def count_trades(
|
|
1291
|
+
self,
|
|
1292
|
+
target_symbol=None,
|
|
1293
|
+
trading_symbol=None,
|
|
1294
|
+
market=None,
|
|
1295
|
+
portfolio=None
|
|
1296
|
+
) -> int:
|
|
1297
|
+
"""
|
|
1298
|
+
Function to count trades. This function will return the number of
|
|
1299
|
+
trades that match the specified query parameters.
|
|
1300
|
+
|
|
1301
|
+
Args:
|
|
1302
|
+
target_symbol: The symbol of the asset
|
|
1303
|
+
trading_symbol: The trading symbol of the asset
|
|
1304
|
+
market: The market of the asset
|
|
1305
|
+
portfolio: The portfolio of the asset
|
|
1306
|
+
|
|
1307
|
+
Returns:
|
|
1308
|
+
int: The number of trades that match the query parameters
|
|
1309
|
+
"""
|
|
1310
|
+
|
|
1311
|
+
query_params = {}
|
|
1312
|
+
|
|
1313
|
+
if market is not None:
|
|
1314
|
+
query_params["market"] = market
|
|
1315
|
+
|
|
1316
|
+
if portfolio is not None:
|
|
1317
|
+
query_params["portfolio"] = portfolio
|
|
1318
|
+
|
|
1319
|
+
if target_symbol is not None:
|
|
1320
|
+
query_params["target_symbol"] = target_symbol
|
|
1321
|
+
|
|
1322
|
+
if trading_symbol is not None:
|
|
1323
|
+
query_params["trading_symbol"] = trading_symbol
|
|
1324
|
+
|
|
1325
|
+
return self.trade_service.count(query_params)
|
|
1326
|
+
|
|
1327
|
+
def get_pending_trades(
|
|
1328
|
+
self, target_symbol=None, market=None
|
|
1329
|
+
) -> List[Trade]:
|
|
1330
|
+
"""
|
|
1331
|
+
Function to get all pending trades. This function will return all
|
|
1332
|
+
pending trades that match the specified query parameters. If the
|
|
1333
|
+
target_symbol parameter is specified, the pending trades with the
|
|
1334
|
+
specified target symbol will be returned. If the market parameter
|
|
1335
|
+
is specified, the pending trades with the specified market will be
|
|
1336
|
+
returned.
|
|
1337
|
+
|
|
1338
|
+
Args:
|
|
1339
|
+
target_symbol: The symbol of the asset
|
|
1340
|
+
market: The market of the asset
|
|
1341
|
+
|
|
1342
|
+
Returns:
|
|
1343
|
+
List[Trade]: A list of pending trades that match
|
|
1344
|
+
the query parameters
|
|
1345
|
+
"""
|
|
1346
|
+
return self.trade_service.get_all(
|
|
1347
|
+
{
|
|
1348
|
+
"status": TradeStatus.CREATED.value,
|
|
1349
|
+
"target_symbol": target_symbol,
|
|
1350
|
+
"market": market
|
|
1351
|
+
}
|
|
1352
|
+
)
|
|
1353
|
+
|
|
1354
|
+
def get_open_trades(self, target_symbol=None, market=None) -> List[Trade]:
|
|
1355
|
+
"""
|
|
1356
|
+
Function to get all open trades. This function will return all
|
|
1357
|
+
open trades that match the specified query parameters. If the
|
|
1358
|
+
target_symbol parameter is specified, the open trades with the
|
|
1359
|
+
specified target symbol will be returned. If the market parameter
|
|
1360
|
+
is specified, the open trades with the specified market will be
|
|
1361
|
+
returned.
|
|
1362
|
+
|
|
1363
|
+
Args:
|
|
1364
|
+
target_symbol: The symbol of the asset
|
|
1365
|
+
market: The market of the asset
|
|
1366
|
+
|
|
1367
|
+
Returns:
|
|
1368
|
+
List[Trade]: A list of open trades that match the query parameters
|
|
1369
|
+
"""
|
|
1370
|
+
return self.trade_service.get_all(
|
|
1371
|
+
{
|
|
1372
|
+
"status": TradeStatus.OPEN.value,
|
|
1373
|
+
"target_symbol": target_symbol,
|
|
1374
|
+
"market": market
|
|
1375
|
+
}
|
|
1376
|
+
)
|
|
1377
|
+
|
|
1378
|
+
def add_stop_loss(
|
|
1379
|
+
self,
|
|
1380
|
+
trade: Trade,
|
|
1381
|
+
percentage: float,
|
|
1382
|
+
trailing: bool = False,
|
|
1383
|
+
sell_percentage: float = 100,
|
|
1384
|
+
created_at: datetime = None,
|
|
1385
|
+
) -> TradeStopLoss:
|
|
1386
|
+
"""
|
|
1387
|
+
Function to add a stop loss to a trade.
|
|
1388
|
+
|
|
1389
|
+
Example of fixed stop loss:
|
|
1390
|
+
* You buy BTC at $40,000.
|
|
1391
|
+
* You set a SL of 5% → SL level at $38,000 (40,000 - 5%).
|
|
1392
|
+
* BTC price increases to $42,000 → SL level remains at $38,000.
|
|
1393
|
+
* BTC price drops to $38,000 → SL level reached, trade closes.
|
|
1394
|
+
|
|
1395
|
+
Example of trailing stop loss:
|
|
1396
|
+
* You buy BTC at $40,000.
|
|
1397
|
+
* You set a TSL of 5%, setting the sell price at $38,000.
|
|
1398
|
+
* BTC price increases to $42,000 → New TSL level
|
|
1399
|
+
at $39,900 (42,000 - 5%).
|
|
1400
|
+
* BTC price drops to $39,900 → SL level reached, trade closes.
|
|
1401
|
+
|
|
1402
|
+
Args:
|
|
1403
|
+
trade (Trade): Trade object representing the trade
|
|
1404
|
+
percentage (float): float representing the percentage
|
|
1405
|
+
of the open price that the stop loss should
|
|
1406
|
+
be set at. This must be a positive
|
|
1407
|
+
number, e.g. 5 for 5%, or 10 for 10%.
|
|
1408
|
+
trailing (bool): Whether the stop loss should be trailing
|
|
1409
|
+
or fixed.
|
|
1410
|
+
sell_percentage (float): float representing the
|
|
1411
|
+
percentage of the trade that should be sold if the
|
|
1412
|
+
stop loss is triggered
|
|
1413
|
+
created_at: datetime: The date and time when the stop loss
|
|
1414
|
+
was created. If not specified, the current date and time
|
|
1415
|
+
will be used.
|
|
1416
|
+
|
|
1417
|
+
Returns:
|
|
1418
|
+
None
|
|
1419
|
+
"""
|
|
1420
|
+
return self.trade_service.add_stop_loss(
|
|
1421
|
+
trade,
|
|
1422
|
+
percentage=percentage,
|
|
1423
|
+
trailing=trailing,
|
|
1424
|
+
sell_percentage=sell_percentage,
|
|
1425
|
+
created_at=created_at,
|
|
1426
|
+
)
|
|
1427
|
+
|
|
1428
|
+
def add_take_profit(
|
|
1429
|
+
self,
|
|
1430
|
+
trade: Trade,
|
|
1431
|
+
percentage: float,
|
|
1432
|
+
trailing: bool = False,
|
|
1433
|
+
sell_percentage: float = 100,
|
|
1434
|
+
created_at: datetime = None,
|
|
1435
|
+
) -> TradeTakeProfit:
|
|
1436
|
+
"""
|
|
1437
|
+
Function to add a take profit to a trade. This function will add a
|
|
1438
|
+
take profit to the specified trade. If the take profit is triggered,
|
|
1439
|
+
the trade will be closed.
|
|
1440
|
+
|
|
1441
|
+
Example of take profit:
|
|
1442
|
+
* You buy BTC at $40,000.
|
|
1443
|
+
* You set a TP of 5% → TP level at $42,000 (40,000 + 5%).
|
|
1444
|
+
* BTC rises to $42,000 → TP level reached, trade
|
|
1445
|
+
closes, securing profit.
|
|
1446
|
+
|
|
1447
|
+
Example of trailing take profit:
|
|
1448
|
+
* You buy BTC at $40,000
|
|
1449
|
+
* You set a TTP of 5%, setting the sell price at $42,000.
|
|
1450
|
+
* BTC rises to $42,000 → TTP level stays at $42,000.
|
|
1451
|
+
* BTC rises to $45,000 → New TTP level at $42,750.
|
|
1452
|
+
* BTC drops to $42,750 → Trade closes, securing profit.
|
|
1453
|
+
|
|
1454
|
+
Args:
|
|
1455
|
+
trade (Trade): Trade object representing the trade
|
|
1456
|
+
percentage (float): float representing the percentage
|
|
1457
|
+
of the open price that the stop loss should
|
|
1458
|
+
be set at. This must be a positive
|
|
1459
|
+
number, e.g. 5 for 5%, or 10 for 10%.
|
|
1460
|
+
trailing (bool): Whether the take profit should be trailing
|
|
1461
|
+
or fixed.
|
|
1462
|
+
sell_percentage (float): float representing the
|
|
1463
|
+
percentage of the trade that should be sold if the
|
|
1464
|
+
stop loss is triggered
|
|
1465
|
+
created_at: datetime: The date and time when the take profit
|
|
1466
|
+
was created. If not specified, the current date and time
|
|
1467
|
+
will be used.
|
|
1468
|
+
|
|
1469
|
+
Returns:
|
|
1470
|
+
None
|
|
1471
|
+
"""
|
|
1472
|
+
return self.trade_service.add_take_profit(
|
|
1473
|
+
trade,
|
|
1474
|
+
percentage=percentage,
|
|
1475
|
+
trailing=trailing,
|
|
1476
|
+
sell_percentage=sell_percentage,
|
|
1477
|
+
created_at=created_at,
|
|
1478
|
+
)
|
|
1479
|
+
|
|
1480
|
+
def close_trade(self, trade, precision=None) -> None:
|
|
1481
|
+
"""
|
|
1482
|
+
Function to close a trade. This function will close a trade by
|
|
1483
|
+
creating a market order to sell the position. If the precision
|
|
1484
|
+
parameter is specified, the amount of the order will be rounded
|
|
1485
|
+
down to the specified precision.
|
|
1486
|
+
|
|
1487
|
+
Args:
|
|
1488
|
+
trade: Trade - The trade to close
|
|
1489
|
+
precision: int - The precision of the amount
|
|
1490
|
+
|
|
1491
|
+
Returns:
|
|
1492
|
+
None
|
|
1493
|
+
"""
|
|
1494
|
+
trade = self.trade_service.get(trade.id)
|
|
1495
|
+
|
|
1496
|
+
if TradeStatus.CLOSED.equals(trade.status):
|
|
1497
|
+
raise OperationalException("Trade already closed.")
|
|
1498
|
+
|
|
1499
|
+
if trade.available_amount <= 0:
|
|
1500
|
+
raise OperationalException("Trade has no amount to close.")
|
|
1501
|
+
|
|
1502
|
+
position_id = trade.orders[0].position_id
|
|
1503
|
+
portfolio = self.portfolio_service.find({"position": position_id})
|
|
1504
|
+
position = self.position_service.find(
|
|
1505
|
+
{"portfolio": portfolio.id, "symbol": trade.target_symbol}
|
|
1506
|
+
)
|
|
1507
|
+
amount = trade.available_amount
|
|
1508
|
+
|
|
1509
|
+
if precision is not None:
|
|
1510
|
+
amount = RoundingService.round_down(amount, precision)
|
|
1511
|
+
|
|
1512
|
+
if position.get_amount() < amount:
|
|
1513
|
+
logger.warning(
|
|
1514
|
+
f"Order amount {amount} is larger then amount "
|
|
1515
|
+
f"of available {position.symbol} "
|
|
1516
|
+
f"position: {position.get_amount()}, "
|
|
1517
|
+
f"changing order amount to size of position"
|
|
1518
|
+
)
|
|
1519
|
+
amount = position.get_amount()
|
|
1520
|
+
|
|
1521
|
+
ticker = self.data_provider_service.get_ticker_data(
|
|
1522
|
+
symbol=trade.symbol,
|
|
1523
|
+
market=portfolio.market,
|
|
1524
|
+
date=self.config[INDEX_DATETIME]
|
|
1525
|
+
)
|
|
1526
|
+
logger.info(f"Closing trade {trade.id} {trade.symbol}")
|
|
1527
|
+
self.order_service.create(
|
|
1528
|
+
{
|
|
1529
|
+
"portfolio_id": portfolio.id,
|
|
1530
|
+
"trading_symbol": trade.trading_symbol,
|
|
1531
|
+
"target_symbol": trade.target_symbol,
|
|
1532
|
+
"amount": amount,
|
|
1533
|
+
"order_side": OrderSide.SELL.value,
|
|
1534
|
+
"order_type": OrderType.LIMIT.value,
|
|
1535
|
+
"price": ticker["bid"],
|
|
1536
|
+
}
|
|
1537
|
+
)
|
|
1538
|
+
|
|
1539
|
+
def get_number_of_positions(self):
|
|
1540
|
+
"""
|
|
1541
|
+
Returns the number of positions that have a positive amount.
|
|
1542
|
+
|
|
1543
|
+
Returns:
|
|
1544
|
+
int: The number of positions
|
|
1545
|
+
"""
|
|
1546
|
+
return self.position_service.count({"amount_gt": 0})
|
|
1547
|
+
|
|
1548
|
+
def has_trading_symbol_position_available(
|
|
1549
|
+
self,
|
|
1550
|
+
amount_gt=None,
|
|
1551
|
+
amount_gte=None,
|
|
1552
|
+
percentage_of_portfolio=None,
|
|
1553
|
+
market=None
|
|
1554
|
+
):
|
|
1555
|
+
"""
|
|
1556
|
+
Checks if there is a position available for the trading symbol of the
|
|
1557
|
+
portfolio. If the amount_gt or amount_gte parameters are specified,
|
|
1558
|
+
the amount of the position must be greater than the specified amount.
|
|
1559
|
+
If the percentage_of_portfolio parameter is specified, the amount of
|
|
1560
|
+
the position must be greater than the net_size of the
|
|
1561
|
+
portfolio.
|
|
1562
|
+
|
|
1563
|
+
Parameters:
|
|
1564
|
+
amount_gt: The amount of the position must be greater than this
|
|
1565
|
+
amount.
|
|
1566
|
+
:param amount_gte: The amount of the position must be greater than
|
|
1567
|
+
or equal to this amount.
|
|
1568
|
+
:param percentage_of_portfolio: The amount of the position must be
|
|
1569
|
+
greater than the net_size of the portfolio.
|
|
1570
|
+
:param market: The market of the portfolio.
|
|
1571
|
+
:return: True if there is a trading symbol position available with the
|
|
1572
|
+
specified parameters, False otherwise.
|
|
1573
|
+
"""
|
|
1574
|
+
portfolio = self.portfolio_service.find({"market": market})
|
|
1575
|
+
position = self.position_service.find(
|
|
1576
|
+
{"portfolio": portfolio.id, "symbol": portfolio.trading_symbol}
|
|
1577
|
+
)
|
|
1578
|
+
|
|
1579
|
+
if amount_gt is not None:
|
|
1580
|
+
return position.get_amount() > amount_gt
|
|
1581
|
+
|
|
1582
|
+
if amount_gte is not None:
|
|
1583
|
+
return position.get_amount() >= amount_gte
|
|
1584
|
+
|
|
1585
|
+
if percentage_of_portfolio is not None:
|
|
1586
|
+
net_size = portfolio.get_net_size()
|
|
1587
|
+
return position.get_amount() >= net_size \
|
|
1588
|
+
* percentage_of_portfolio / 100
|
|
1589
|
+
|
|
1590
|
+
return position.get_amount() > 0
|
|
1591
|
+
|
|
1592
|
+
def get_pending_orders(
|
|
1593
|
+
self, order_side=None, target_symbol=None, portfolio_id=None
|
|
1594
|
+
):
|
|
1595
|
+
"""
|
|
1596
|
+
Function to get all pending orders of the algorithm. If the
|
|
1597
|
+
portfolio_id parameter is specified, the function will return
|
|
1598
|
+
all pending orders of the portfolio with the specified id.
|
|
1599
|
+
"""
|
|
1600
|
+
query_params = {}
|
|
1601
|
+
|
|
1602
|
+
if portfolio_id:
|
|
1603
|
+
query_params["portfolio"] = portfolio_id
|
|
1604
|
+
|
|
1605
|
+
if target_symbol:
|
|
1606
|
+
query_params["target_symbol"] = target_symbol
|
|
1607
|
+
|
|
1608
|
+
if order_side:
|
|
1609
|
+
query_params["order_side"] = order_side
|
|
1610
|
+
|
|
1611
|
+
return self.order_service.get_all({"status": OrderStatus.OPEN.value})
|
|
1612
|
+
|
|
1613
|
+
def get_unfilled_buy_value(self):
|
|
1614
|
+
"""
|
|
1615
|
+
Returns the total value of all unfilled buy orders.
|
|
1616
|
+
"""
|
|
1617
|
+
pending_orders = self.get_pending_orders(
|
|
1618
|
+
order_side=OrderSide.BUY.value
|
|
1619
|
+
)
|
|
1620
|
+
|
|
1621
|
+
return sum(
|
|
1622
|
+
[order.get_remaining() * order.get_price()
|
|
1623
|
+
for order in pending_orders]
|
|
1624
|
+
)
|
|
1625
|
+
|
|
1626
|
+
def get_unfilled_sell_value(self):
|
|
1627
|
+
"""
|
|
1628
|
+
Returns the total value of all unfilled buy orders.
|
|
1629
|
+
"""
|
|
1630
|
+
pending_orders = self.get_pending_orders(
|
|
1631
|
+
order_side=OrderSide.SELL.value
|
|
1632
|
+
)
|
|
1633
|
+
|
|
1634
|
+
return sum(
|
|
1635
|
+
[order.get_remaining() * order.get_price()
|
|
1636
|
+
for order in pending_orders]
|
|
1637
|
+
)
|
|
1638
|
+
|
|
1639
|
+
def get_market_credential(self, market) -> MarketCredential:
|
|
1640
|
+
"""
|
|
1641
|
+
Function to get the market credential for a given market.
|
|
1642
|
+
|
|
1643
|
+
Args:
|
|
1644
|
+
market: The market to get the credential for
|
|
1645
|
+
|
|
1646
|
+
Returns:
|
|
1647
|
+
MarketCredential: The market credential for the given market
|
|
1648
|
+
"""
|
|
1649
|
+
return self.market_credential_service.get(market)
|
|
1650
|
+
|
|
1651
|
+
def get_market_credentials(self) -> List[MarketCredential]:
|
|
1652
|
+
"""
|
|
1653
|
+
Function to get all market credentials.
|
|
1654
|
+
|
|
1655
|
+
Returns:
|
|
1656
|
+
List[MarketCredential]: A list of all market credentials
|
|
1657
|
+
"""
|
|
1658
|
+
return self.market_credential_service.get_all()
|
|
1659
|
+
|
|
1660
|
+
def get_trading_symbol(self, portfolio_id=None):
|
|
1661
|
+
"""
|
|
1662
|
+
Function to get the trading symbol of a portfolio. If the
|
|
1663
|
+
portfolio_id parameter is specified, the function will return
|
|
1664
|
+
the trading symbol of the portfolio with the specified id.
|
|
1665
|
+
|
|
1666
|
+
Args:
|
|
1667
|
+
portfolio_id: The id of the portfolio to get the trading symbol for
|
|
1668
|
+
|
|
1669
|
+
Returns:
|
|
1670
|
+
str: The trading symbol of the portfolio
|
|
1671
|
+
"""
|
|
1672
|
+
if portfolio_id is None:
|
|
1673
|
+
if self.portfolio_service.count() > 1:
|
|
1674
|
+
raise OperationalException(
|
|
1675
|
+
"Multiple portfolios found. Please specify a "
|
|
1676
|
+
"portfolio identifier."
|
|
1677
|
+
)
|
|
1678
|
+
portfolio = self.portfolio_service.get_all()[0]
|
|
1679
|
+
else:
|
|
1680
|
+
portfolio = self.portfolio_service.get(portfolio_id)
|
|
1681
|
+
|
|
1682
|
+
return portfolio.trading_symbol
|
|
1683
|
+
|
|
1684
|
+
def get_take_profits(
|
|
1685
|
+
self, triggered: bool = None
|
|
1686
|
+
) -> List[TradeTakeProfit]:
|
|
1687
|
+
"""
|
|
1688
|
+
Function to get all take profits. If the triggered parameter
|
|
1689
|
+
is specified, the function will return all take profits that
|
|
1690
|
+
match the triggered status.
|
|
1691
|
+
|
|
1692
|
+
Args:
|
|
1693
|
+
triggered (bool): The triggered status of the take profits
|
|
1694
|
+
|
|
1695
|
+
Returns:
|
|
1696
|
+
List[TradeTakeProfit]: A list of take profits
|
|
1697
|
+
"""
|
|
1698
|
+
query_params = {}
|
|
1699
|
+
|
|
1700
|
+
if triggered is not None:
|
|
1701
|
+
query_params["triggered"] = triggered
|
|
1702
|
+
|
|
1703
|
+
return self.trade_take_profit_service.get_all(query_params)
|
|
1704
|
+
|
|
1705
|
+
def get_stop_losses(
|
|
1706
|
+
self, triggered: bool = None
|
|
1707
|
+
) -> List[TradeStopLoss]:
|
|
1708
|
+
"""
|
|
1709
|
+
Function to get all stop losses. If the triggered parameter
|
|
1710
|
+
is specified, the function will return all stop losses that
|
|
1711
|
+
match the triggered status.
|
|
1712
|
+
|
|
1713
|
+
Args:
|
|
1714
|
+
triggered (bool): The triggered status of the stop losses
|
|
1715
|
+
|
|
1716
|
+
Returns:
|
|
1717
|
+
List[TradeStopLoss]: A list of stop losses
|
|
1718
|
+
"""
|
|
1719
|
+
query_params = {}
|
|
1720
|
+
|
|
1721
|
+
if triggered is not None:
|
|
1722
|
+
query_params["triggered"] = triggered
|
|
1723
|
+
|
|
1724
|
+
return self.trade_stop_loss_service.get_all(query_params)
|