investing-algorithm-framework 1.3.1__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 +195 -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 +31 -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 +2233 -264
- 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/stop_loss_table.py +0 -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/stateless/exception_handler.py +1 -1
- investing_algorithm_framework/app/strategy.py +873 -52
- 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 +4 -3
- investing_algorithm_framework/app/web/controllers/portfolio.py +1 -1
- investing_algorithm_framework/app/web/controllers/positions.py +3 -3
- investing_algorithm_framework/app/web/create_app.py +4 -2
- investing_algorithm_framework/app/web/error_handler.py +1 -1
- investing_algorithm_framework/app/web/schemas/order.py +2 -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 +43 -9
- investing_algorithm_framework/dependency_container.py +121 -33
- investing_algorithm_framework/domain/__init__.py +109 -22
- 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 +60 -138
- investing_algorithm_framework/domain/constants.py +23 -34
- investing_algorithm_framework/domain/data_provider.py +334 -0
- investing_algorithm_framework/domain/data_structures.py +42 -0
- investing_algorithm_framework/domain/decimal_parsing.py +40 -0
- investing_algorithm_framework/domain/exceptions.py +51 -1
- investing_algorithm_framework/domain/models/__init__.py +29 -14
- investing_algorithm_framework/domain/models/app_mode.py +34 -0
- investing_algorithm_framework/domain/models/base_model.py +3 -1
- 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 +243 -86
- 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 +7 -2
- investing_algorithm_framework/domain/models/portfolio/portfolio.py +134 -1
- investing_algorithm_framework/domain/models/portfolio/portfolio_configuration.py +37 -37
- investing_algorithm_framework/domain/models/portfolio/portfolio_snapshot.py +208 -0
- investing_algorithm_framework/domain/models/position/__init__.py +3 -2
- investing_algorithm_framework/domain/models/position/position.py +29 -0
- investing_algorithm_framework/domain/models/position/position_size.py +41 -0
- investing_algorithm_framework/domain/models/position/{position_cost.py → position_snapshot.py} +16 -8
- 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 +33 -0
- 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 +111 -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 +16 -4
- 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 +39 -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 +87 -13
- investing_algorithm_framework/infrastructure/models/__init__.py +13 -4
- investing_algorithm_framework/infrastructure/models/decimal_parser.py +14 -0
- investing_algorithm_framework/infrastructure/models/order/__init__.py +2 -2
- investing_algorithm_framework/infrastructure/models/order/order.py +73 -73
- 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 +3 -2
- investing_algorithm_framework/infrastructure/models/portfolio/portfolio_snapshot.py +37 -0
- investing_algorithm_framework/infrastructure/models/portfolio/{portfolio.py → sql_portfolio.py} +57 -3
- investing_algorithm_framework/infrastructure/models/position/__init__.py +2 -2
- investing_algorithm_framework/infrastructure/models/position/position.py +16 -11
- investing_algorithm_framework/infrastructure/models/position/position_snapshot.py +23 -0
- 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 +13 -5
- investing_algorithm_framework/infrastructure/repositories/order_metadata_repository.py +17 -0
- investing_algorithm_framework/infrastructure/repositories/order_repository.py +32 -19
- investing_algorithm_framework/infrastructure/repositories/portfolio_repository.py +2 -2
- investing_algorithm_framework/infrastructure/repositories/portfolio_snapshot_repository.py +56 -0
- investing_algorithm_framework/infrastructure/repositories/position_repository.py +47 -4
- investing_algorithm_framework/infrastructure/repositories/position_snapshot_repository.py +21 -0
- investing_algorithm_framework/infrastructure/repositories/repository.py +85 -31
- 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 -2
- 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 +127 -10
- investing_algorithm_framework/services/configuration_service.py +95 -0
- 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/positions/position_snapshot_service.py +18 -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.3.1.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 -410
- 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 -76
- 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/trading_data_types.py +0 -47
- investing_algorithm_framework/domain/models/trading_time_frame.py +0 -205
- investing_algorithm_framework/domain/singleton.py +0 -9
- investing_algorithm_framework/infrastructure/models/order/order_fee.py +0 -21
- investing_algorithm_framework/infrastructure/models/position/position_cost.py +0 -32
- investing_algorithm_framework/infrastructure/repositories/order_fee_repository.py +0 -15
- investing_algorithm_framework/infrastructure/repositories/position_cost_repository.py +0 -16
- investing_algorithm_framework/infrastructure/services/market_service.py +0 -422
- investing_algorithm_framework/services/market_data_service.py +0 -75
- investing_algorithm_framework/services/order_service.py +0 -464
- investing_algorithm_framework/services/portfolio_service.py +0 -105
- investing_algorithm_framework/services/position_cost_service.py +0 -5
- investing_algorithm_framework/services/position_service.py +0 -50
- investing_algorithm_framework/services/strategy_orchestrator_service.py +0 -219
- investing_algorithm_framework/setup_logging.py +0 -40
- investing_algorithm_framework-1.3.1.dist-info/AUTHORS.md +0 -8
- investing_algorithm_framework-1.3.1.dist-info/METADATA +0 -172
- investing_algorithm_framework-1.3.1.dist-info/RECORD +0 -103
- investing_algorithm_framework-1.3.1.dist-info/top_level.txt +0 -1
- {investing_algorithm_framework-1.3.1.dist-info → investing_algorithm_framework-7.25.6.dist-info}/LICENSE +0 -0
|
@@ -0,0 +1,389 @@
|
|
|
1
|
+
from dateutil.parser import parse
|
|
2
|
+
from datetime import timezone
|
|
3
|
+
|
|
4
|
+
from investing_algorithm_framework.domain.models.base_model import BaseModel
|
|
5
|
+
from investing_algorithm_framework.domain.models.order import OrderSide, Order
|
|
6
|
+
from investing_algorithm_framework.domain.models.trade.trade_status import \
|
|
7
|
+
TradeStatus
|
|
8
|
+
from investing_algorithm_framework.domain.models.trade.trade_stop_loss import \
|
|
9
|
+
TradeStopLoss
|
|
10
|
+
from investing_algorithm_framework.domain.models.trade\
|
|
11
|
+
.trade_take_profit import TradeTakeProfit
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class Trade(BaseModel):
|
|
15
|
+
"""
|
|
16
|
+
Trade model
|
|
17
|
+
|
|
18
|
+
A trade is a combination of a buy and sell order that has been opened or
|
|
19
|
+
closed.
|
|
20
|
+
|
|
21
|
+
A trade is considered opened when a buy order is executed and there is
|
|
22
|
+
no corresponding sell order. A trade is considered closed when a sell
|
|
23
|
+
order is executed and the amount of the sell order is equal or larger
|
|
24
|
+
to the amount of the buy order.
|
|
25
|
+
|
|
26
|
+
A single sell order can close multiple buy orders. Also, a single
|
|
27
|
+
buy order can be closed by multiple sell orders.
|
|
28
|
+
|
|
29
|
+
Attributes:
|
|
30
|
+
orders: str, the id of the buy order
|
|
31
|
+
target_symbol: str, the target symbol of the trade
|
|
32
|
+
trading_symbol: str, the trading symbol of the trade
|
|
33
|
+
closed_at: datetime, the datetime when the trade was closed
|
|
34
|
+
amount: float, the amount of the trade
|
|
35
|
+
available_amount: float, the available amount of the trade
|
|
36
|
+
remaining: float, the remaining amount that is not filled by the
|
|
37
|
+
buy order that opened the trade.
|
|
38
|
+
filled_amount: float, the filled amount of the trade by the buy
|
|
39
|
+
order that opened the trade.
|
|
40
|
+
net_gain: float, the net gain of the trade
|
|
41
|
+
last_reported_price: float, the last reported price of the trade
|
|
42
|
+
last_reported_price_datetime: datetime, the datetime when the last
|
|
43
|
+
reported price was reported
|
|
44
|
+
updated_at: datetime, the datetime when the trade was last updated
|
|
45
|
+
status: str, the status of the trade
|
|
46
|
+
metadata: dict, the metadata of the trade, this can be used to store
|
|
47
|
+
additional information about the trade.
|
|
48
|
+
"""
|
|
49
|
+
|
|
50
|
+
def __init__(
|
|
51
|
+
self,
|
|
52
|
+
id,
|
|
53
|
+
orders,
|
|
54
|
+
target_symbol,
|
|
55
|
+
trading_symbol,
|
|
56
|
+
closed_at,
|
|
57
|
+
opened_at,
|
|
58
|
+
open_price,
|
|
59
|
+
amount,
|
|
60
|
+
available_amount,
|
|
61
|
+
cost,
|
|
62
|
+
remaining,
|
|
63
|
+
filled_amount,
|
|
64
|
+
status,
|
|
65
|
+
net_gain=0,
|
|
66
|
+
last_reported_price=None,
|
|
67
|
+
last_reported_price_datetime=None,
|
|
68
|
+
high_water_mark=None,
|
|
69
|
+
high_water_mark_datetime=None,
|
|
70
|
+
updated_at=None,
|
|
71
|
+
stop_losses=None,
|
|
72
|
+
take_profits=None,
|
|
73
|
+
metadata=None,
|
|
74
|
+
):
|
|
75
|
+
self.id = id
|
|
76
|
+
self.orders = orders
|
|
77
|
+
self.target_symbol = target_symbol
|
|
78
|
+
self.trading_symbol = trading_symbol
|
|
79
|
+
self.closed_at = closed_at
|
|
80
|
+
self.opened_at = opened_at
|
|
81
|
+
self.open_price = open_price
|
|
82
|
+
self.amount = amount
|
|
83
|
+
self.available_amount = available_amount
|
|
84
|
+
self.cost = cost
|
|
85
|
+
self.remaining = remaining
|
|
86
|
+
self.filled_amount = filled_amount
|
|
87
|
+
self.net_gain = net_gain
|
|
88
|
+
self.last_reported_price = last_reported_price
|
|
89
|
+
self.last_reported_price_datetime = last_reported_price_datetime
|
|
90
|
+
self.high_water_mark = high_water_mark
|
|
91
|
+
self.high_water_mark_datetime = high_water_mark_datetime
|
|
92
|
+
self.status = TradeStatus.from_value(status).value
|
|
93
|
+
self.updated_at = updated_at
|
|
94
|
+
self.stop_losses = stop_losses
|
|
95
|
+
self.take_profits = take_profits
|
|
96
|
+
self.metadata = metadata if metadata is not None else {}
|
|
97
|
+
|
|
98
|
+
def update(self, data):
|
|
99
|
+
|
|
100
|
+
if "status" in data:
|
|
101
|
+
self.status = TradeStatus.from_value(data["status"]).value
|
|
102
|
+
|
|
103
|
+
if TradeStatus.CLOSED.equals(self.status):
|
|
104
|
+
|
|
105
|
+
# Set all stop losses to inactive
|
|
106
|
+
if self.stop_losses is not None:
|
|
107
|
+
for stop_loss in self.stop_losses:
|
|
108
|
+
stop_loss.active = False
|
|
109
|
+
|
|
110
|
+
# set all take profits to inactive
|
|
111
|
+
if self.take_profits is not None:
|
|
112
|
+
for take_profit in self.take_profits:
|
|
113
|
+
take_profit.active = False
|
|
114
|
+
|
|
115
|
+
if "last_reported_price" in data:
|
|
116
|
+
self.last_reported_price = data["last_reported_price"]
|
|
117
|
+
|
|
118
|
+
if self.high_water_mark is None:
|
|
119
|
+
self.high_water_mark = data["last_reported_price"]
|
|
120
|
+
self.high_water_mark_datetime = \
|
|
121
|
+
data["last_reported_price_datetime"]
|
|
122
|
+
else:
|
|
123
|
+
|
|
124
|
+
if data["last_reported_price"] > self.high_water_mark:
|
|
125
|
+
self.high_water_mark = data["last_reported_price"]
|
|
126
|
+
self.high_water_mark_datetime = \
|
|
127
|
+
data["last_reported_price_datetime"]
|
|
128
|
+
|
|
129
|
+
return super().update(data)
|
|
130
|
+
|
|
131
|
+
@property
|
|
132
|
+
def closed_prices(self):
|
|
133
|
+
return [
|
|
134
|
+
order.price for order in self.orders
|
|
135
|
+
if order.order_side == OrderSide.SELL.value
|
|
136
|
+
]
|
|
137
|
+
|
|
138
|
+
@property
|
|
139
|
+
def buy_order(self):
|
|
140
|
+
|
|
141
|
+
if self.orders is None:
|
|
142
|
+
return
|
|
143
|
+
|
|
144
|
+
return [
|
|
145
|
+
order for order in self.orders
|
|
146
|
+
if order.order_side == OrderSide.BUY.value
|
|
147
|
+
][0]
|
|
148
|
+
|
|
149
|
+
@property
|
|
150
|
+
def symbol(self):
|
|
151
|
+
return f"{self.target_symbol.upper()}/{self.trading_symbol.upper()}"
|
|
152
|
+
|
|
153
|
+
@property
|
|
154
|
+
def duration(self):
|
|
155
|
+
"""
|
|
156
|
+
Calculate the duration of the trade in hours.
|
|
157
|
+
|
|
158
|
+
Returns:
|
|
159
|
+
float: The duration of the trade in hours.
|
|
160
|
+
"""
|
|
161
|
+
if TradeStatus.CLOSED.equals(self.status):
|
|
162
|
+
# Get the total hours between the closed and opened datetime
|
|
163
|
+
diff = self.closed_at - self.opened_at
|
|
164
|
+
return diff.total_seconds() / 3600
|
|
165
|
+
|
|
166
|
+
if self.opened_at is None:
|
|
167
|
+
return None
|
|
168
|
+
|
|
169
|
+
if self.updated_at is None:
|
|
170
|
+
return None
|
|
171
|
+
|
|
172
|
+
diff = self.updated_at - self.opened_at
|
|
173
|
+
return diff.total_seconds() / 3600
|
|
174
|
+
|
|
175
|
+
@property
|
|
176
|
+
def size(self):
|
|
177
|
+
return self.amount * self.open_price
|
|
178
|
+
|
|
179
|
+
@property
|
|
180
|
+
def change(self):
|
|
181
|
+
"""
|
|
182
|
+
Property to calculate the change in value of the trade.
|
|
183
|
+
|
|
184
|
+
This is the difference between the current value of the trade
|
|
185
|
+
and the cost of the trade.
|
|
186
|
+
"""
|
|
187
|
+
if TradeStatus.CLOSED.equals(self.status):
|
|
188
|
+
return self.net_gain
|
|
189
|
+
|
|
190
|
+
if self.last_reported_price is None:
|
|
191
|
+
return 0
|
|
192
|
+
|
|
193
|
+
if self.remaining is None or self.remaining == 0:
|
|
194
|
+
amount = self.amount
|
|
195
|
+
else:
|
|
196
|
+
amount = self.amount - self.remaining
|
|
197
|
+
|
|
198
|
+
cost = amount * self.open_price
|
|
199
|
+
gain = (amount * self.last_reported_price) - cost
|
|
200
|
+
return gain
|
|
201
|
+
|
|
202
|
+
@property
|
|
203
|
+
def net_gain_absolute(self):
|
|
204
|
+
|
|
205
|
+
if TradeStatus.CLOSED.equals(self.status):
|
|
206
|
+
return self.net_gain
|
|
207
|
+
else:
|
|
208
|
+
gain = 0
|
|
209
|
+
|
|
210
|
+
if self.last_reported_price is not None:
|
|
211
|
+
gain = (
|
|
212
|
+
self.available_amount *
|
|
213
|
+
(self.last_reported_price - self.open_price)
|
|
214
|
+
)
|
|
215
|
+
|
|
216
|
+
gain += self.net_gain
|
|
217
|
+
return gain
|
|
218
|
+
|
|
219
|
+
@property
|
|
220
|
+
def net_gain_percentage(self):
|
|
221
|
+
|
|
222
|
+
if TradeStatus.CLOSED.equals(self.status):
|
|
223
|
+
|
|
224
|
+
if self.cost != 0:
|
|
225
|
+
return (self.net_gain / self.cost) * 100
|
|
226
|
+
|
|
227
|
+
else:
|
|
228
|
+
gain = 0
|
|
229
|
+
|
|
230
|
+
if self.last_reported_price is not None:
|
|
231
|
+
gain = (
|
|
232
|
+
self.available_amount *
|
|
233
|
+
(self.last_reported_price - self.open_price)
|
|
234
|
+
)
|
|
235
|
+
|
|
236
|
+
gain += self.net_gain
|
|
237
|
+
|
|
238
|
+
if self.cost != 0:
|
|
239
|
+
return (gain / self.cost) * 100
|
|
240
|
+
|
|
241
|
+
return 0
|
|
242
|
+
|
|
243
|
+
@property
|
|
244
|
+
def percentage_change(self):
|
|
245
|
+
|
|
246
|
+
if TradeStatus.CLOSED.equals(self.status):
|
|
247
|
+
|
|
248
|
+
if self.cost != 0:
|
|
249
|
+
return (self.net_gain / self.cost) * 100
|
|
250
|
+
|
|
251
|
+
if self.last_reported_price is None:
|
|
252
|
+
return 0
|
|
253
|
+
|
|
254
|
+
cost = self.available_amount * self.open_price
|
|
255
|
+
gain = (self.available_amount * self.last_reported_price) - cost
|
|
256
|
+
gain += self.net_gain
|
|
257
|
+
|
|
258
|
+
if cost != 0:
|
|
259
|
+
return (gain / cost) * 100
|
|
260
|
+
|
|
261
|
+
return 0
|
|
262
|
+
|
|
263
|
+
def to_dict(self, datetime_format=None):
|
|
264
|
+
def ensure_iso(value):
|
|
265
|
+
if hasattr(value, "isoformat"):
|
|
266
|
+
if value.tzinfo is None:
|
|
267
|
+
value = value.replace(tzinfo=timezone.utc)
|
|
268
|
+
return value.isoformat()
|
|
269
|
+
return value
|
|
270
|
+
|
|
271
|
+
opened_at = ensure_iso(self.opened_at) if self.opened_at else None
|
|
272
|
+
closed_at = ensure_iso(self.closed_at) if self.closed_at else None
|
|
273
|
+
updated_at = ensure_iso(self.updated_at) if self.updated_at else None
|
|
274
|
+
|
|
275
|
+
# Ensure status is a string
|
|
276
|
+
self.status = TradeStatus.from_value(self.status).value
|
|
277
|
+
|
|
278
|
+
return {
|
|
279
|
+
"id": self.id,
|
|
280
|
+
"orders": [
|
|
281
|
+
order.to_dict(datetime_format=datetime_format)
|
|
282
|
+
for order in self.orders
|
|
283
|
+
],
|
|
284
|
+
"target_symbol": self.target_symbol,
|
|
285
|
+
"trading_symbol": self.trading_symbol,
|
|
286
|
+
"status": self.status,
|
|
287
|
+
"amount": self.amount,
|
|
288
|
+
"remaining": self.remaining if self.remaining is not None else 0,
|
|
289
|
+
"open_price": self.open_price,
|
|
290
|
+
"last_reported_price": self.last_reported_price,
|
|
291
|
+
"opened_at": opened_at,
|
|
292
|
+
"closed_at": closed_at,
|
|
293
|
+
"updated_at": updated_at,
|
|
294
|
+
"net_gain": self.net_gain if self.net_gain is not None else 0,
|
|
295
|
+
"cost": self.cost if self.cost is not None else 0,
|
|
296
|
+
"stop_losses": [
|
|
297
|
+
stop_loss.to_dict(datetime_format=datetime_format)
|
|
298
|
+
for stop_loss in self.stop_losses
|
|
299
|
+
] if self.stop_losses else None,
|
|
300
|
+
"take_profits": [
|
|
301
|
+
take_profit.to_dict(datetime_format=datetime_format)
|
|
302
|
+
for take_profit in self.take_profits
|
|
303
|
+
] if self.take_profits else None,
|
|
304
|
+
"filled_amount": self.filled_amount,
|
|
305
|
+
"available_amount": self.available_amount,
|
|
306
|
+
"metadata": self.metadata if self.metadata else {},
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
@staticmethod
|
|
310
|
+
def from_dict(data):
|
|
311
|
+
opened_at = None
|
|
312
|
+
closed_at = None
|
|
313
|
+
updated_at = None
|
|
314
|
+
stop_losses = None
|
|
315
|
+
take_profits = None
|
|
316
|
+
orders = None
|
|
317
|
+
|
|
318
|
+
if "opened_at" in data and data["opened_at"] is not None:
|
|
319
|
+
opened_at = parse(data["opened_at"])
|
|
320
|
+
|
|
321
|
+
if "closed_at" in data and data["closed_at"] is not None:
|
|
322
|
+
closed_at = parse(data["closed_at"])
|
|
323
|
+
|
|
324
|
+
if "updated_at" in data and data["updated_at"] is not None:
|
|
325
|
+
updated_at = parse(data["updated_at"])
|
|
326
|
+
|
|
327
|
+
if "stop_losses" in data and data["stop_losses"] is not None:
|
|
328
|
+
stop_losses = [
|
|
329
|
+
TradeStopLoss.from_dict(stop_loss)
|
|
330
|
+
for stop_loss in data["stop_losses"]
|
|
331
|
+
]
|
|
332
|
+
|
|
333
|
+
if "take_profits" in data and data["take_profits"] is not None:
|
|
334
|
+
take_profits = [
|
|
335
|
+
TradeTakeProfit.from_dict(take_profit)
|
|
336
|
+
for take_profit in data["take_profits"]
|
|
337
|
+
]
|
|
338
|
+
|
|
339
|
+
if "orders" in data and data["orders"] is not None:
|
|
340
|
+
orders = [
|
|
341
|
+
Order.from_dict(order)
|
|
342
|
+
for order in data["orders"]
|
|
343
|
+
]
|
|
344
|
+
|
|
345
|
+
return Trade(
|
|
346
|
+
id=data.get("id", None),
|
|
347
|
+
orders=orders,
|
|
348
|
+
target_symbol=data.get("target_symbol", ""),
|
|
349
|
+
trading_symbol=data.get("trading_symbol", ""),
|
|
350
|
+
amount=data.get("amount", 0),
|
|
351
|
+
open_price=data.get("open_price", 0),
|
|
352
|
+
opened_at=opened_at,
|
|
353
|
+
closed_at=closed_at,
|
|
354
|
+
filled_amount=data.get("filled_amount", 0),
|
|
355
|
+
available_amount=data.get("available_amount", 0),
|
|
356
|
+
remaining=data.get("remaining", 0),
|
|
357
|
+
net_gain=data.get("net_gain", 0),
|
|
358
|
+
last_reported_price=data.get("last_reported_price"),
|
|
359
|
+
status=TradeStatus.from_value(data.get("status", "OPEN")).value,
|
|
360
|
+
cost=data.get("cost", 0),
|
|
361
|
+
updated_at=updated_at,
|
|
362
|
+
stop_losses=stop_losses,
|
|
363
|
+
take_profits=take_profits,
|
|
364
|
+
metadata=data.get("metadata", {}),
|
|
365
|
+
)
|
|
366
|
+
|
|
367
|
+
def __repr__(self):
|
|
368
|
+
return self.repr(
|
|
369
|
+
id=self.id,
|
|
370
|
+
symbol=self.symbol,
|
|
371
|
+
target_symbol=self.target_symbol,
|
|
372
|
+
trading_symbol=self.trading_symbol,
|
|
373
|
+
status=self.status,
|
|
374
|
+
amount=self.amount,
|
|
375
|
+
available_amount=self.available_amount,
|
|
376
|
+
filled_amount=self.filled_amount,
|
|
377
|
+
remaining=self.remaining,
|
|
378
|
+
open_price=self.open_price,
|
|
379
|
+
opened_at=self.opened_at,
|
|
380
|
+
closed_at=self.closed_at,
|
|
381
|
+
net_gain=self.net_gain,
|
|
382
|
+
last_reported_price=self.last_reported_price,
|
|
383
|
+
updated_at=self.updated_at,
|
|
384
|
+
metadata=self.metadata,
|
|
385
|
+
)
|
|
386
|
+
|
|
387
|
+
def __lt__(self, other):
|
|
388
|
+
# Define the less-than comparison based on created_at attribute
|
|
389
|
+
return self.opened_at < other.opened_at
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
from enum import Enum
|
|
2
|
+
from investing_algorithm_framework.domain.exceptions import \
|
|
3
|
+
OperationalException
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class TradeStatus(Enum):
|
|
7
|
+
CREATED = "CREATED"
|
|
8
|
+
OPEN = "OPEN"
|
|
9
|
+
CLOSED = "CLOSED"
|
|
10
|
+
|
|
11
|
+
@staticmethod
|
|
12
|
+
def from_string(value: str):
|
|
13
|
+
|
|
14
|
+
if isinstance(value, str):
|
|
15
|
+
for status in TradeStatus:
|
|
16
|
+
|
|
17
|
+
if value.upper() == status.value:
|
|
18
|
+
return status
|
|
19
|
+
|
|
20
|
+
raise OperationalException(
|
|
21
|
+
f"Could not convert value: '{value}' to TradeStatus"
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
@staticmethod
|
|
25
|
+
def from_value(value):
|
|
26
|
+
|
|
27
|
+
if isinstance(value, TradeStatus):
|
|
28
|
+
for status in TradeStatus:
|
|
29
|
+
|
|
30
|
+
if value == status:
|
|
31
|
+
return status
|
|
32
|
+
elif isinstance(value, str):
|
|
33
|
+
return TradeStatus.from_string(value)
|
|
34
|
+
|
|
35
|
+
raise OperationalException(
|
|
36
|
+
f"Could not convert value: {value} to TradeStatus"
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
def equals(self, other):
|
|
40
|
+
return TradeStatus.from_value(other) == self
|