investing-algorithm-framework 3.7.0__py3-none-any.whl → 7.19.15__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of investing-algorithm-framework might be problematic. Click here for more details.
- investing_algorithm_framework/__init__.py +168 -45
- investing_algorithm_framework/app/__init__.py +32 -1
- investing_algorithm_framework/app/algorithm/__init__.py +7 -0
- investing_algorithm_framework/app/algorithm/algorithm.py +239 -0
- investing_algorithm_framework/app/algorithm/algorithm_factory.py +114 -0
- investing_algorithm_framework/app/analysis/__init__.py +15 -0
- investing_algorithm_framework/app/analysis/backtest_data_ranges.py +121 -0
- investing_algorithm_framework/app/analysis/backtest_utils.py +107 -0
- investing_algorithm_framework/app/analysis/permutation.py +116 -0
- investing_algorithm_framework/app/analysis/ranking.py +297 -0
- investing_algorithm_framework/app/app.py +1933 -589
- investing_algorithm_framework/app/app_hook.py +28 -0
- investing_algorithm_framework/app/context.py +1725 -0
- investing_algorithm_framework/app/eventloop.py +590 -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 +4 -2
- investing_algorithm_framework/app/stateless/action_handlers/action_handler_strategy.py +1 -1
- investing_algorithm_framework/app/stateless/action_handlers/check_online_handler.py +1 -1
- investing_algorithm_framework/app/stateless/action_handlers/run_strategy_handler.py +14 -7
- investing_algorithm_framework/app/strategy.py +664 -84
- investing_algorithm_framework/app/task.py +5 -3
- investing_algorithm_framework/app/web/__init__.py +2 -1
- investing_algorithm_framework/app/web/create_app.py +4 -2
- investing_algorithm_framework/cli/__init__.py +0 -0
- investing_algorithm_framework/cli/cli.py +226 -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/create_app.py +40 -6
- investing_algorithm_framework/dependency_container.py +72 -56
- investing_algorithm_framework/domain/__init__.py +71 -47
- investing_algorithm_framework/domain/backtesting/__init__.py +21 -0
- investing_algorithm_framework/domain/backtesting/backtest.py +503 -0
- investing_algorithm_framework/domain/backtesting/backtest_date_range.py +96 -0
- investing_algorithm_framework/domain/backtesting/backtest_evaluation_focuss.py +242 -0
- investing_algorithm_framework/domain/backtesting/backtest_metrics.py +459 -0
- investing_algorithm_framework/domain/backtesting/backtest_permutation_test.py +275 -0
- investing_algorithm_framework/domain/backtesting/backtest_run.py +605 -0
- investing_algorithm_framework/domain/backtesting/backtest_summary_metrics.py +162 -0
- investing_algorithm_framework/domain/backtesting/combine_backtests.py +280 -0
- investing_algorithm_framework/domain/config.py +59 -91
- investing_algorithm_framework/domain/constants.py +13 -38
- investing_algorithm_framework/domain/data_provider.py +334 -0
- investing_algorithm_framework/domain/data_structures.py +3 -2
- investing_algorithm_framework/domain/exceptions.py +51 -1
- investing_algorithm_framework/domain/models/__init__.py +17 -12
- investing_algorithm_framework/domain/models/data/__init__.py +7 -0
- investing_algorithm_framework/domain/models/data/data_source.py +214 -0
- investing_algorithm_framework/domain/models/data/data_type.py +46 -0
- investing_algorithm_framework/domain/models/event.py +35 -0
- investing_algorithm_framework/domain/models/market/market_credential.py +55 -1
- investing_algorithm_framework/domain/models/order/order.py +77 -83
- 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/portfolio.py +81 -3
- investing_algorithm_framework/domain/models/portfolio/portfolio_configuration.py +26 -3
- 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 +12 -0
- investing_algorithm_framework/domain/models/position/position_size.py +41 -0
- investing_algorithm_framework/domain/models/risk_rules/__init__.py +7 -0
- investing_algorithm_framework/domain/models/risk_rules/stop_loss_rule.py +51 -0
- investing_algorithm_framework/domain/models/risk_rules/take_profit_rule.py +55 -0
- investing_algorithm_framework/domain/models/snapshot_interval.py +45 -0
- investing_algorithm_framework/domain/models/strategy_profile.py +19 -151
- investing_algorithm_framework/domain/models/time_frame.py +37 -0
- 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/trade/__init__.py +8 -1
- investing_algorithm_framework/domain/models/trade/trade.py +295 -171
- investing_algorithm_framework/domain/models/trade/trade_status.py +9 -2
- 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 +2 -9
- investing_algorithm_framework/domain/services/portfolios/portfolio_sync_service.py +0 -6
- 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 +12 -7
- 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 +108 -0
- investing_algorithm_framework/infrastructure/__init__.py +31 -18
- investing_algorithm_framework/infrastructure/data_providers/__init__.py +36 -0
- investing_algorithm_framework/infrastructure/data_providers/ccxt.py +1143 -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 +6 -11
- investing_algorithm_framework/infrastructure/models/order/__init__.py +2 -1
- investing_algorithm_framework/infrastructure/models/order/order.py +35 -49
- 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 -0
- investing_algorithm_framework/infrastructure/models/portfolio/{portfolio.py → sql_portfolio.py} +17 -5
- 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 +8 -0
- investing_algorithm_framework/infrastructure/repositories/order_metadata_repository.py +17 -0
- investing_algorithm_framework/infrastructure/repositories/order_repository.py +5 -0
- investing_algorithm_framework/infrastructure/repositories/portfolio_repository.py +1 -1
- investing_algorithm_framework/infrastructure/repositories/position_repository.py +11 -0
- investing_algorithm_framework/infrastructure/repositories/repository.py +81 -27
- 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 +4 -4
- investing_algorithm_framework/infrastructure/services/aws/__init__.py +6 -0
- investing_algorithm_framework/infrastructure/services/aws/state_handler.py +113 -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/services/__init__.py +113 -16
- investing_algorithm_framework/services/backtesting/__init__.py +0 -7
- investing_algorithm_framework/services/backtesting/backtest_service.py +566 -359
- 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 +850 -0
- investing_algorithm_framework/services/market_credential_service.py +16 -1
- investing_algorithm_framework/services/metrics/__init__.py +114 -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 +181 -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 +83 -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 +157 -0
- investing_algorithm_framework/services/metrics/trades.py +500 -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 +97 -0
- investing_algorithm_framework/services/metrics/win_rate.py +177 -0
- investing_algorithm_framework/services/order_service/__init__.py +3 -1
- investing_algorithm_framework/services/order_service/order_backtest_service.py +76 -89
- investing_algorithm_framework/services/order_service/order_executor_lookup.py +110 -0
- investing_algorithm_framework/services/order_service/order_service.py +407 -326
- investing_algorithm_framework/services/portfolios/__init__.py +3 -1
- investing_algorithm_framework/services/portfolios/backtest_portfolio_service.py +37 -3
- investing_algorithm_framework/services/portfolios/portfolio_configuration_service.py +22 -8
- investing_algorithm_framework/services/portfolios/portfolio_provider_lookup.py +106 -0
- investing_algorithm_framework/services/portfolios/portfolio_service.py +96 -28
- investing_algorithm_framework/services/portfolios/portfolio_snapshot_service.py +97 -28
- investing_algorithm_framework/services/portfolios/portfolio_sync_service.py +116 -313
- 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 +113 -0
- investing_algorithm_framework/services/trade_order_evaluator/default_trade_order_evaluator.py +51 -0
- investing_algorithm_framework/services/trade_order_evaluator/trade_order_evaluator.py +80 -0
- investing_algorithm_framework/services/trade_service/__init__.py +7 -1
- investing_algorithm_framework/services/trade_service/trade_service.py +1013 -315
- investing_algorithm_framework/services/trade_service/trade_stop_loss_service.py +39 -0
- investing_algorithm_framework/services/trade_service/trade_take_profit_service.py +41 -0
- investing_algorithm_framework-7.19.15.dist-info/METADATA +537 -0
- investing_algorithm_framework-7.19.15.dist-info/RECORD +263 -0
- investing_algorithm_framework-7.19.15.dist-info/entry_points.txt +3 -0
- investing_algorithm_framework/app/algorithm.py +0 -1105
- investing_algorithm_framework/domain/graphs.py +0 -382
- investing_algorithm_framework/domain/metrics/__init__.py +0 -6
- investing_algorithm_framework/domain/models/backtesting/__init__.py +0 -11
- investing_algorithm_framework/domain/models/backtesting/backtest_date_range.py +0 -43
- investing_algorithm_framework/domain/models/backtesting/backtest_position.py +0 -120
- investing_algorithm_framework/domain/models/backtesting/backtest_report.py +0 -580
- investing_algorithm_framework/domain/models/backtesting/backtest_reports_evaluation.py +0 -243
- 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/services/market_data_sources.py +0 -344
- investing_algorithm_framework/domain/services/market_service.py +0 -153
- investing_algorithm_framework/domain/singleton.py +0 -9
- investing_algorithm_framework/domain/utils/backtesting.py +0 -472
- investing_algorithm_framework/infrastructure/models/market_data_sources/__init__.py +0 -12
- investing_algorithm_framework/infrastructure/models/market_data_sources/ccxt.py +0 -559
- investing_algorithm_framework/infrastructure/models/market_data_sources/csv.py +0 -254
- investing_algorithm_framework/infrastructure/models/market_data_sources/us_treasury_yield.py +0 -47
- investing_algorithm_framework/infrastructure/services/market_service/__init__.py +0 -5
- investing_algorithm_framework/infrastructure/services/market_service/ccxt_market_service.py +0 -455
- investing_algorithm_framework/infrastructure/services/performance_service/__init__.py +0 -7
- investing_algorithm_framework/infrastructure/services/performance_service/backtest_performance_service.py +0 -2
- investing_algorithm_framework/infrastructure/services/performance_service/performance_service.py +0 -350
- investing_algorithm_framework/services/backtesting/backtest_report_writer_service.py +0 -53
- investing_algorithm_framework/services/backtesting/graphs.py +0 -61
- investing_algorithm_framework/services/market_data_source_service/__init__.py +0 -8
- investing_algorithm_framework/services/market_data_source_service/backtest_market_data_source_service.py +0 -150
- investing_algorithm_framework/services/market_data_source_service/market_data_source_service.py +0 -189
- investing_algorithm_framework/services/position_service.py +0 -31
- investing_algorithm_framework/services/strategy_orchestrator_service.py +0 -264
- investing_algorithm_framework-3.7.0.dist-info/METADATA +0 -339
- investing_algorithm_framework-3.7.0.dist-info/RECORD +0 -147
- /investing_algorithm_framework/{domain → services}/metrics/price_efficiency.py +0 -0
- /investing_algorithm_framework/services/{position_snapshot_service.py → positions/position_snapshot_service.py} +0 -0
- {investing_algorithm_framework-3.7.0.dist-info → investing_algorithm_framework-7.19.15.dist-info}/LICENSE +0 -0
- {investing_algorithm_framework-3.7.0.dist-info → investing_algorithm_framework-7.19.15.dist-info}/WHEEL +0 -0
|
@@ -0,0 +1,349 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import os
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
import webbrowser
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
from dataclasses import field
|
|
7
|
+
from typing import List, Union
|
|
8
|
+
|
|
9
|
+
import pandas as pd
|
|
10
|
+
from IPython import get_ipython
|
|
11
|
+
from IPython.display import display, HTML
|
|
12
|
+
from jinja2 import Environment, FileSystemLoader
|
|
13
|
+
|
|
14
|
+
from investing_algorithm_framework.domain import TimeFrame, Backtest, \
|
|
15
|
+
OperationalException, BacktestDateRange
|
|
16
|
+
from .charts import get_equity_curve_with_drawdown_chart, \
|
|
17
|
+
get_rolling_sharpe_ratio_chart, get_monthly_returns_heatmap_chart, \
|
|
18
|
+
get_yearly_returns_bar_chart, get_ohlcv_data_completeness_chart
|
|
19
|
+
from .tables import create_html_time_metrics_table, \
|
|
20
|
+
create_html_trade_metrics_table, create_html_key_metrics_table, \
|
|
21
|
+
create_html_trades_table
|
|
22
|
+
|
|
23
|
+
logger = logging.getLogger("investing_algorithm_framework")
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def get_symbol_from_file_name(file_name: str) -> str:
|
|
27
|
+
"""
|
|
28
|
+
Extract the symbol from the file name.
|
|
29
|
+
|
|
30
|
+
Args:
|
|
31
|
+
file_name (str): The file name from which to extract the symbol.
|
|
32
|
+
|
|
33
|
+
Returns:
|
|
34
|
+
str: The extracted symbol.
|
|
35
|
+
"""
|
|
36
|
+
# Assuming the file name format is "symbol_timeframe.csv"
|
|
37
|
+
return file_name.split('_')[0].upper()
|
|
38
|
+
|
|
39
|
+
def get_market_from_file_name(file_name: str) -> str:
|
|
40
|
+
"""
|
|
41
|
+
Extract the market from the file name.
|
|
42
|
+
|
|
43
|
+
Args:
|
|
44
|
+
file_name (str): The file name from which to extract the market.
|
|
45
|
+
|
|
46
|
+
Returns:
|
|
47
|
+
str: The extracted market.
|
|
48
|
+
"""
|
|
49
|
+
# Assuming the file name format is "symbol_market_timeframe.csv"
|
|
50
|
+
parts = file_name.split('_')
|
|
51
|
+
if len(parts) < 2:
|
|
52
|
+
raise ValueError("File name does not contain a valid market.")
|
|
53
|
+
return parts[1].upper()
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def get_time_frame_from_file_name(file_name: str) -> TimeFrame:
|
|
57
|
+
"""
|
|
58
|
+
Extract the time frame from the file name.
|
|
59
|
+
|
|
60
|
+
Args:
|
|
61
|
+
file_name (str): The file name from which to extract the time frame.
|
|
62
|
+
|
|
63
|
+
Returns:
|
|
64
|
+
TimeFrame: The extracted time frame.
|
|
65
|
+
"""
|
|
66
|
+
parts = file_name.split('_')
|
|
67
|
+
|
|
68
|
+
if len(parts) < 3:
|
|
69
|
+
raise ValueError(
|
|
70
|
+
"File name does not contain a valid time frame."
|
|
71
|
+
)
|
|
72
|
+
time_frame_str = parts[3]
|
|
73
|
+
|
|
74
|
+
try:
|
|
75
|
+
return TimeFrame.from_string(time_frame_str)
|
|
76
|
+
except ValueError:
|
|
77
|
+
raise ValueError(
|
|
78
|
+
f"Could not extract time frame from file name: {file_path}. "
|
|
79
|
+
f"Expected format 'OHLCV_<SYMBOL>_<MARKET>_<TIME_FRAME>_<START_DATE>_<END_DATE>.csv', "
|
|
80
|
+
f"got '{time_frame_str}'."
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
@dataclass
|
|
85
|
+
class BacktestReport:
|
|
86
|
+
"""
|
|
87
|
+
A class to represent a backtest report. The backtest report contains
|
|
88
|
+
the results of a backtest, including metrics and an HTML report. Also,
|
|
89
|
+
it stores the path to the used strategy directory, which can be used to
|
|
90
|
+
load the strategy code later.
|
|
91
|
+
|
|
92
|
+
Attributes:
|
|
93
|
+
html_report (str): The HTML report content.
|
|
94
|
+
html_report_path (str): The file path where the HTML report is saved.
|
|
95
|
+
metrics (dict): A dictionary containing various metrics from the backtest.
|
|
96
|
+
results (BacktestResult): An instance of BacktestResult containing
|
|
97
|
+
the results of the backtest.
|
|
98
|
+
risk_free_rate (float): The risk-free rate used in the backtest.
|
|
99
|
+
strategy_path (str): The path to the strategy directory used in the
|
|
100
|
+
backtest.
|
|
101
|
+
"""
|
|
102
|
+
backtest: Backtest = None
|
|
103
|
+
html_report: str = None
|
|
104
|
+
html_report_path: str = None
|
|
105
|
+
|
|
106
|
+
def show(
|
|
107
|
+
self,
|
|
108
|
+
backtest_date_range: BacktestDateRange,
|
|
109
|
+
browser: bool = False
|
|
110
|
+
):
|
|
111
|
+
"""
|
|
112
|
+
Display the HTML report in a Jupyter notebook cell.
|
|
113
|
+
"""
|
|
114
|
+
|
|
115
|
+
if not self.html_report:
|
|
116
|
+
# If the HTML report is not created, create it
|
|
117
|
+
self._create_html_report(backtest_date_range)
|
|
118
|
+
|
|
119
|
+
# Save the html report to a tmp location
|
|
120
|
+
path = "/tmp/backtest_report.html"
|
|
121
|
+
with open(path, "w") as html_file:
|
|
122
|
+
html_file.write(self.html_report)
|
|
123
|
+
|
|
124
|
+
if browser:
|
|
125
|
+
webbrowser.open(f"file://{path}")
|
|
126
|
+
|
|
127
|
+
def in_jupyter_notebook():
|
|
128
|
+
try:
|
|
129
|
+
shell = get_ipython().__class__.__name__
|
|
130
|
+
return shell == 'ZMQInteractiveShell'
|
|
131
|
+
except (NameError, ImportError):
|
|
132
|
+
return False
|
|
133
|
+
|
|
134
|
+
if in_jupyter_notebook():
|
|
135
|
+
display(HTML(self.html_report))
|
|
136
|
+
else:
|
|
137
|
+
webbrowser.open(f"file://{path}")
|
|
138
|
+
|
|
139
|
+
def _create_html_report(self, backtest_date_range: BacktestDateRange):
|
|
140
|
+
"""
|
|
141
|
+
Create an HTML report from the backtest metrics and results.
|
|
142
|
+
|
|
143
|
+
This method generates various charts and tables from the backtest
|
|
144
|
+
metrics and results, and renders them into an HTML template using
|
|
145
|
+
Jinja2.
|
|
146
|
+
|
|
147
|
+
Raises:
|
|
148
|
+
OperationalException: If no backtests are available to
|
|
149
|
+
create a report.
|
|
150
|
+
|
|
151
|
+
Returns:
|
|
152
|
+
None
|
|
153
|
+
"""
|
|
154
|
+
# Get the first backtest
|
|
155
|
+
if not self.backtest:
|
|
156
|
+
raise OperationalException(
|
|
157
|
+
"No backtest available to create a report."
|
|
158
|
+
)
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
metrics = self.backtest.get_backtest_metrics(backtest_date_range)
|
|
162
|
+
run = self.backtest.get_backtest_run(backtest_date_range)
|
|
163
|
+
# Create plots
|
|
164
|
+
equity_with_drawdown_fig = get_equity_curve_with_drawdown_chart(
|
|
165
|
+
metrics.equity_curve, metrics.drawdown_series
|
|
166
|
+
)
|
|
167
|
+
equity_with_drawdown_plot_html = equity_with_drawdown_fig.to_html(
|
|
168
|
+
full_html=False, include_plotlyjs='cdn',
|
|
169
|
+
config={'responsive': True}, default_width="90%"
|
|
170
|
+
)
|
|
171
|
+
rolling_sharpe_ratio_fig = get_rolling_sharpe_ratio_chart(
|
|
172
|
+
metrics.rolling_sharpe_ratio
|
|
173
|
+
)
|
|
174
|
+
rolling_sharpe_ratio_plot_html = rolling_sharpe_ratio_fig.to_html(
|
|
175
|
+
full_html=False, include_plotlyjs='cdn',
|
|
176
|
+
config={'responsive': True}, default_width="90%"
|
|
177
|
+
)
|
|
178
|
+
monthly_returns_heatmap_fig = get_monthly_returns_heatmap_chart(
|
|
179
|
+
metrics.monthly_returns
|
|
180
|
+
)
|
|
181
|
+
monthly_returns_heatmap_html = monthly_returns_heatmap_fig.to_html(
|
|
182
|
+
full_html=False, include_plotlyjs='cdn',
|
|
183
|
+
config={'responsive': True}
|
|
184
|
+
)
|
|
185
|
+
yearly_returns_histogram_fig = get_yearly_returns_bar_chart(
|
|
186
|
+
metrics.yearly_returns
|
|
187
|
+
)
|
|
188
|
+
yearly_returns_histogram_html = yearly_returns_histogram_fig.to_html(
|
|
189
|
+
full_html=False, include_plotlyjs='cdn',
|
|
190
|
+
config={'responsive': True}
|
|
191
|
+
)
|
|
192
|
+
|
|
193
|
+
# Create OHLCV data completeness charts
|
|
194
|
+
data_files = []
|
|
195
|
+
ohlcv_data_completeness_charts_html = ""
|
|
196
|
+
|
|
197
|
+
for file in data_files:
|
|
198
|
+
try:
|
|
199
|
+
if file.endswith('.csv'):
|
|
200
|
+
df = pd.read_csv(file, parse_dates=['Datetime'])
|
|
201
|
+
file_name = os.path.basename(file)
|
|
202
|
+
symbol = get_symbol_from_file_name(file_name)
|
|
203
|
+
market = get_market_from_file_name(file_name)
|
|
204
|
+
time_frame = get_time_frame_from_file_name(file_name)
|
|
205
|
+
title = f"OHLCV Data Completeness for {market} - {symbol} - {time_frame.value}"
|
|
206
|
+
ohlcv_data_completeness_chart_html = \
|
|
207
|
+
get_ohlcv_data_completeness_chart(
|
|
208
|
+
df,
|
|
209
|
+
timeframe=time_frame.value,
|
|
210
|
+
windowsize=200,
|
|
211
|
+
title=title
|
|
212
|
+
)
|
|
213
|
+
|
|
214
|
+
ohlcv_data_completeness_charts_html += (
|
|
215
|
+
'<div class="ohlcv-data-completeness-chart">'
|
|
216
|
+
f'{ohlcv_data_completeness_chart_html}'
|
|
217
|
+
'</div>'
|
|
218
|
+
)
|
|
219
|
+
|
|
220
|
+
except Exception as e:
|
|
221
|
+
logger.warning(
|
|
222
|
+
"Error creating OHLCV data completeness " +
|
|
223
|
+
f"chart for {file}: {e}"
|
|
224
|
+
)
|
|
225
|
+
continue
|
|
226
|
+
|
|
227
|
+
# Create HTML tables
|
|
228
|
+
key_metrics_table_html = create_html_key_metrics_table(
|
|
229
|
+
metrics, run
|
|
230
|
+
)
|
|
231
|
+
trades_metrics_table_html = create_html_trade_metrics_table(
|
|
232
|
+
metrics, run
|
|
233
|
+
)
|
|
234
|
+
time_metrics_table_html = create_html_time_metrics_table(
|
|
235
|
+
metrics, run
|
|
236
|
+
)
|
|
237
|
+
trades_table_html = create_html_trades_table(run)
|
|
238
|
+
|
|
239
|
+
# Jinja2 environment setup
|
|
240
|
+
template_dir = os.path.join(os.path.dirname(__file__), 'templates')
|
|
241
|
+
env = Environment(loader=FileSystemLoader(template_dir))
|
|
242
|
+
template = env.get_template('report_template.html.j2')
|
|
243
|
+
|
|
244
|
+
# Render template with variables
|
|
245
|
+
html_rendered = template.render(
|
|
246
|
+
report=run,
|
|
247
|
+
equity_with_drawdown_plot_html=equity_with_drawdown_plot_html,
|
|
248
|
+
rolling_sharpe_ratio_plot_html=rolling_sharpe_ratio_plot_html,
|
|
249
|
+
monthly_returns_heatmap_html=monthly_returns_heatmap_html,
|
|
250
|
+
yearly_returns_histogram_html=yearly_returns_histogram_html,
|
|
251
|
+
key_metrics_table_html=key_metrics_table_html,
|
|
252
|
+
trades_metrics_table_html=trades_metrics_table_html,
|
|
253
|
+
time_metrics_table_html=time_metrics_table_html,
|
|
254
|
+
trades_table_html=trades_table_html,
|
|
255
|
+
data_completeness_charts_html=ohlcv_data_completeness_charts_html
|
|
256
|
+
)
|
|
257
|
+
self.html_report = html_rendered
|
|
258
|
+
|
|
259
|
+
@staticmethod
|
|
260
|
+
def _load_backtest(backtest_path: str) -> Backtest:
|
|
261
|
+
"""
|
|
262
|
+
Load the backtest from a give path
|
|
263
|
+
and return an instance of Backtest.
|
|
264
|
+
|
|
265
|
+
Args:
|
|
266
|
+
backtest_path (str): The path to the backtest directory.
|
|
267
|
+
|
|
268
|
+
Returns:
|
|
269
|
+
Backtest: An instance of Backtest loaded from the
|
|
270
|
+
backtest directory.
|
|
271
|
+
"""
|
|
272
|
+
return Backtest.open(backtest_path)
|
|
273
|
+
|
|
274
|
+
@staticmethod
|
|
275
|
+
def _is_backtest(backtest_path: Union[str, Path]) -> bool:
|
|
276
|
+
"""
|
|
277
|
+
Check if the given path is a valid backtest report directory.
|
|
278
|
+
|
|
279
|
+
Args:
|
|
280
|
+
backtest_path (Union[str, Path]): The path to check.
|
|
281
|
+
|
|
282
|
+
Returns:
|
|
283
|
+
bool: True if the path is a valid backtest report directory,
|
|
284
|
+
False otherwise.
|
|
285
|
+
"""
|
|
286
|
+
return (
|
|
287
|
+
os.path.exists(backtest_path) and
|
|
288
|
+
os.path.isdir(backtest_path) and
|
|
289
|
+
os.path.isfile(os.path.join(backtest_path, "results.json"))
|
|
290
|
+
and os.path.isfile(os.path.join(backtest_path, "metrics.json"))
|
|
291
|
+
)
|
|
292
|
+
|
|
293
|
+
@staticmethod
|
|
294
|
+
def open(
|
|
295
|
+
backtests: List[Backtest] = [],
|
|
296
|
+
directory_path=None
|
|
297
|
+
) -> "BacktestReport":
|
|
298
|
+
"""
|
|
299
|
+
Open the backtest report from a file.
|
|
300
|
+
|
|
301
|
+
Args:
|
|
302
|
+
backtests (List[Backtest], optional): A list of Backtest instances
|
|
303
|
+
to include in the report. If provided, it will use these
|
|
304
|
+
backtests as part of the report.
|
|
305
|
+
directory_path (str, optional): The directory path from
|
|
306
|
+
which the reports will be loaded.
|
|
307
|
+
|
|
308
|
+
Returns:
|
|
309
|
+
BacktestReport: An instance of BacktestReport loaded from the file.
|
|
310
|
+
|
|
311
|
+
Raises:
|
|
312
|
+
OperationalException: If the backtest path is not a valid backtest
|
|
313
|
+
report directory.
|
|
314
|
+
"""
|
|
315
|
+
loaded_backtests = []
|
|
316
|
+
|
|
317
|
+
if directory_path is not None:
|
|
318
|
+
# Check if the directory is a valid backtest report directory
|
|
319
|
+
if BacktestReport._is_backtest(directory_path):
|
|
320
|
+
backtests.append(
|
|
321
|
+
Backtest.open(directory_path)
|
|
322
|
+
)
|
|
323
|
+
else:
|
|
324
|
+
# Loop over all subdirectories
|
|
325
|
+
for root, dirs, _ in os.walk(directory_path):
|
|
326
|
+
for dir_name in dirs:
|
|
327
|
+
subdir_path = os.path.join(root, dir_name)
|
|
328
|
+
if BacktestReport._is_backtest(subdir_path):
|
|
329
|
+
loaded_backtests.append(
|
|
330
|
+
Backtest.open(directory_path=subdir_path)
|
|
331
|
+
)
|
|
332
|
+
|
|
333
|
+
if len(backtests) > 0:
|
|
334
|
+
|
|
335
|
+
for backtest in backtests:
|
|
336
|
+
if not isinstance(backtest, Backtest):
|
|
337
|
+
raise OperationalException(
|
|
338
|
+
"The provided backtest is not a valid Backtest instance."
|
|
339
|
+
)
|
|
340
|
+
|
|
341
|
+
# Add the backtests to the backtests list
|
|
342
|
+
loaded_backtests.extend(backtests)
|
|
343
|
+
|
|
344
|
+
if len(loaded_backtests) == 0:
|
|
345
|
+
raise OperationalException(
|
|
346
|
+
f"The directory {directory_path} is not a valid backtest report."
|
|
347
|
+
)
|
|
348
|
+
|
|
349
|
+
return BacktestReport(backtest=loaded_backtests)
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
from .equity_curve_drawdown import get_equity_curve_with_drawdown_chart
|
|
2
|
+
from .equity_curve import get_equity_curve_chart
|
|
3
|
+
from .rolling_sharp_ratio import get_rolling_sharpe_ratio_chart
|
|
4
|
+
from .monthly_returns_heatmap import get_monthly_returns_heatmap_chart
|
|
5
|
+
from .yearly_returns_barchart import get_yearly_returns_bar_chart
|
|
6
|
+
from .ohlcv_data_completeness import get_ohlcv_data_completeness_chart
|
|
7
|
+
from .entry_exist_signals import get_entry_and_exit_signals
|
|
8
|
+
from .line_chart import create_line_scatter
|
|
9
|
+
|
|
10
|
+
__all__ = [
|
|
11
|
+
"get_equity_curve_with_drawdown_chart",
|
|
12
|
+
"get_rolling_sharpe_ratio_chart",
|
|
13
|
+
"get_monthly_returns_heatmap_chart",
|
|
14
|
+
"get_yearly_returns_bar_chart",
|
|
15
|
+
"get_ohlcv_data_completeness_chart",
|
|
16
|
+
"get_entry_and_exit_signals",
|
|
17
|
+
"create_line_scatter",
|
|
18
|
+
"get_equity_curve_chart"
|
|
19
|
+
]
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import pandas as pd
|
|
2
|
+
import plotly.graph_objects as go
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def get_entry_and_exit_signals(
|
|
6
|
+
entry_signals: pd.Series,
|
|
7
|
+
exit_signals: pd.Series,
|
|
8
|
+
price_data: pd.DataFrame
|
|
9
|
+
):
|
|
10
|
+
"""
|
|
11
|
+
Plots the price chart with entry and exit signals.
|
|
12
|
+
|
|
13
|
+
Args:
|
|
14
|
+
entry_signals (pd.Series): Series containing buy
|
|
15
|
+
signals with datetime index. Entry signals should
|
|
16
|
+
be boolean values.
|
|
17
|
+
exit_signals (pd.Series): Series containing exit signals
|
|
18
|
+
with datetime index. Exit signals should be boolean values.
|
|
19
|
+
price_data (pd.DataFrame): DataFrame containing price
|
|
20
|
+
data with datetime index and 'Close'
|
|
21
|
+
|
|
22
|
+
Returns:
|
|
23
|
+
go.Figure: Plotly figure with price chart and signals.
|
|
24
|
+
"""
|
|
25
|
+
# Create the base candlestick or line chart
|
|
26
|
+
fig = go.Figure()
|
|
27
|
+
|
|
28
|
+
fig.add_trace(go.Scatter(
|
|
29
|
+
x=price_data.index,
|
|
30
|
+
y=price_data["Close"],
|
|
31
|
+
mode="lines",
|
|
32
|
+
name="Close Price",
|
|
33
|
+
line=dict(color="blue")
|
|
34
|
+
))
|
|
35
|
+
|
|
36
|
+
# Entry points
|
|
37
|
+
entry_points = price_data[entry_signals]
|
|
38
|
+
fig.add_trace(go.Scatter(
|
|
39
|
+
x=entry_points.index,
|
|
40
|
+
y=entry_points["Close"],
|
|
41
|
+
mode="markers",
|
|
42
|
+
name="Entry",
|
|
43
|
+
marker=dict(symbol="triangle-up", color="green", size=10)
|
|
44
|
+
))
|
|
45
|
+
|
|
46
|
+
# Exit points
|
|
47
|
+
exit_points = price_data[exit_signals]
|
|
48
|
+
fig.add_trace(go.Scatter(
|
|
49
|
+
x=exit_points.index,
|
|
50
|
+
y=exit_points["Close"],
|
|
51
|
+
mode="markers",
|
|
52
|
+
name="Exit",
|
|
53
|
+
marker=dict(symbol="triangle-down", color="red", size=10)
|
|
54
|
+
))
|
|
55
|
+
|
|
56
|
+
# Layout settings
|
|
57
|
+
fig.update_layout(
|
|
58
|
+
title="Entry and Exit Signals",
|
|
59
|
+
xaxis_title="Date",
|
|
60
|
+
yaxis_title="Price",
|
|
61
|
+
legend=dict(x=0, y=1),
|
|
62
|
+
height=600,
|
|
63
|
+
template="plotly_white"
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
return fig
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import pandas as pd
|
|
2
|
+
from plotly import graph_objects as go
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def get_equity_curve_chart(equity_curve_series):
|
|
6
|
+
equity_curve_df = pd.DataFrame(
|
|
7
|
+
equity_curve_series, columns=["value", "datetime"]
|
|
8
|
+
)
|
|
9
|
+
|
|
10
|
+
# Normalize equity to start at 1
|
|
11
|
+
equity_curve_df["value"] = (
|
|
12
|
+
equity_curve_df["value"] / equity_curve_df["value"].iloc[0]
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
fig = go.Figure()
|
|
16
|
+
|
|
17
|
+
# Draw equity curve
|
|
18
|
+
fig.add_trace(
|
|
19
|
+
go.Scatter(
|
|
20
|
+
x=equity_curve_df["datetime"],
|
|
21
|
+
y=equity_curve_df["value"],
|
|
22
|
+
mode="lines",
|
|
23
|
+
line=dict(color="rgba(0, 128, 0, 0.8)", width=1),
|
|
24
|
+
name="Equity Curve",
|
|
25
|
+
hovertemplate="<b>Equity</b><br>%{x}<br>Value: %{y:.2f}<extra></extra>"
|
|
26
|
+
)
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
fig.update_layout(
|
|
30
|
+
xaxis=dict(title=None),
|
|
31
|
+
yaxis=dict(title="Cumulative Equity (log)", type="log"),
|
|
32
|
+
title="Equity Curve with Drawdown",
|
|
33
|
+
hovermode="x unified",
|
|
34
|
+
legend=dict(orientation="h", yanchor="bottom", y=1.02, xanchor="right", x=1)
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
return fig
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import pandas as pd
|
|
2
|
+
from plotly.subplots import make_subplots
|
|
3
|
+
from plotly import graph_objects as go
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def get_equity_curve_with_drawdown_chart(equity_curve_series, drawdown_series):
|
|
7
|
+
equity_curve_df = pd.DataFrame(
|
|
8
|
+
equity_curve_series, columns=["value", "datetime"]
|
|
9
|
+
)
|
|
10
|
+
drawdown_df = pd.DataFrame(
|
|
11
|
+
drawdown_series, columns=["value", "datetime"]
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
# Normalize equity to start at 1
|
|
15
|
+
equity_curve_df["value"] = (
|
|
16
|
+
equity_curve_df["value"] / equity_curve_df["value"].iloc[0]
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
fig = make_subplots(
|
|
20
|
+
rows=2,
|
|
21
|
+
cols=1,
|
|
22
|
+
shared_xaxes=True,
|
|
23
|
+
vertical_spacing=0.05,
|
|
24
|
+
row_heights=[0.7, 0.3],
|
|
25
|
+
subplot_titles=["", ""]
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
# Draw equity curve
|
|
29
|
+
fig.add_trace(
|
|
30
|
+
go.Scatter(
|
|
31
|
+
x=equity_curve_df["datetime"],
|
|
32
|
+
y=equity_curve_df["value"],
|
|
33
|
+
mode="lines",
|
|
34
|
+
line=dict(color="rgba(0, 128, 0, 0.8)", width=1),
|
|
35
|
+
name="Equity Curve",
|
|
36
|
+
hovertemplate="<b>Equity</b><br>%{x}<br>Value: %{y:.2f}<extra></extra>"
|
|
37
|
+
),
|
|
38
|
+
row=1,
|
|
39
|
+
col=1
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
# Drawdown area
|
|
43
|
+
fig.add_trace(
|
|
44
|
+
go.Scatter(
|
|
45
|
+
x=drawdown_df["datetime"],
|
|
46
|
+
y=drawdown_df["value"],
|
|
47
|
+
mode="lines",
|
|
48
|
+
fill="tozeroy",
|
|
49
|
+
fillcolor="rgba(255, 99, 71, 0.3)",
|
|
50
|
+
line=dict(color="rgba(255, 99, 71, 0.8)", width=1),
|
|
51
|
+
name="Drawdown"
|
|
52
|
+
),
|
|
53
|
+
row=2,
|
|
54
|
+
col=1
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
# Final layout
|
|
58
|
+
fig.update_layout(
|
|
59
|
+
xaxis=dict(title=None),
|
|
60
|
+
yaxis=dict(title="Cumulative Equity (log)", type="log"),
|
|
61
|
+
xaxis2=dict(title=None),
|
|
62
|
+
yaxis2=dict(
|
|
63
|
+
title="Drawdown",
|
|
64
|
+
tickformat=".0%",
|
|
65
|
+
tickvals=[-0.2, -0.15, -0.1, -0.05, 0] # Clean % ticks
|
|
66
|
+
),
|
|
67
|
+
template="plotly_white",
|
|
68
|
+
height=600,
|
|
69
|
+
showlegend=False,
|
|
70
|
+
hovermode="x unified", # Enables vertical hover line
|
|
71
|
+
margin=dict(l=0, r=0, t=0, b=0),
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
return fig
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import pandas as pd
|
|
2
|
+
import plotly.graph_objects as go
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def get_monthly_returns_heatmap_chart(monthly_return_series):
|
|
6
|
+
df = pd.DataFrame(monthly_return_series, columns=["Return", "Timestamp"])
|
|
7
|
+
df["Timestamp"] = pd.to_datetime(df["Timestamp"], errors='coerce')
|
|
8
|
+
df["Year"] = df["Timestamp"].dt.year
|
|
9
|
+
df["Month"] = df["Timestamp"].dt.strftime("%b")
|
|
10
|
+
|
|
11
|
+
month_order = ["Jan", "Feb", "Mar", "Apr", "May", "Jun",
|
|
12
|
+
"Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]
|
|
13
|
+
df["Month"] = pd.Categorical(
|
|
14
|
+
df["Month"], categories=month_order, ordered=True
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
# Ensure all months are present for each year
|
|
18
|
+
all_years = df["Year"].unique()
|
|
19
|
+
all_months = pd.DataFrame(
|
|
20
|
+
[(year, month) for year in all_years for month in month_order],
|
|
21
|
+
columns=["Year", "Month"]
|
|
22
|
+
)
|
|
23
|
+
df = pd.merge(
|
|
24
|
+
all_months,
|
|
25
|
+
df,
|
|
26
|
+
on=["Year", "Month"],
|
|
27
|
+
how="left"
|
|
28
|
+
).fillna({"Return": 0.0})
|
|
29
|
+
|
|
30
|
+
# Pivot to matrix form
|
|
31
|
+
pivot_df = df.pivot(index="Year", columns="Month", values="Return")
|
|
32
|
+
pivot_df = pivot_df.reindex(columns=month_order)
|
|
33
|
+
pivot_df = pivot_df.sort_index(ascending=True) # Change to ascending order
|
|
34
|
+
|
|
35
|
+
z = pivot_df.values
|
|
36
|
+
text = [
|
|
37
|
+
[f"{v * 100:.2f}%" if pd.notna(v) else "" for v in row] for row in z
|
|
38
|
+
]
|
|
39
|
+
hover_template = "Year %{y}<br>Month %{x}<br>" + \
|
|
40
|
+
"Return: %{z:.2%}<extra></extra>"
|
|
41
|
+
fig = go.Figure(data=go.Heatmap(
|
|
42
|
+
z=z,
|
|
43
|
+
x=pivot_df.columns,
|
|
44
|
+
y=pivot_df.index,
|
|
45
|
+
text=text,
|
|
46
|
+
texttemplate="%{text}",
|
|
47
|
+
textfont={"size": 12},
|
|
48
|
+
colorscale="RdYlGn",
|
|
49
|
+
showscale=False,
|
|
50
|
+
hovertemplate=hover_template,
|
|
51
|
+
zmin=-0.1,
|
|
52
|
+
zmax=0.1
|
|
53
|
+
))
|
|
54
|
+
|
|
55
|
+
fig.update_layout(
|
|
56
|
+
title="Monthly Returns Heatmap (%)",
|
|
57
|
+
xaxis_title="Month",
|
|
58
|
+
yaxis=dict(
|
|
59
|
+
title=None,
|
|
60
|
+
tickmode='array',
|
|
61
|
+
tickvals=list(pivot_df.index),
|
|
62
|
+
ticktext=[str(year) for year in pivot_df.index],
|
|
63
|
+
autorange=True # Remove 'reversed' to match ascending order
|
|
64
|
+
),
|
|
65
|
+
template="plotly_white",
|
|
66
|
+
margin=dict(l=0, r=0, t=40, b=20),
|
|
67
|
+
height=350,
|
|
68
|
+
showlegend=False
|
|
69
|
+
)
|
|
70
|
+
return fig
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import plotly.graph_objects as go
|
|
2
|
+
import pandas as pd
|
|
3
|
+
import plotly.io as pio
|
|
4
|
+
|
|
5
|
+
def get_ohlcv_data_completeness_chart(
|
|
6
|
+
df,
|
|
7
|
+
timeframe='1min',
|
|
8
|
+
windowsize=100,
|
|
9
|
+
title="OHLCV Data completenes"
|
|
10
|
+
):
|
|
11
|
+
df = df.copy()
|
|
12
|
+
df['Datetime'] = pd.to_datetime(df['Datetime'])
|
|
13
|
+
df = df.sort_values('Datetime').tail(windowsize)
|
|
14
|
+
start = df['Datetime'].iloc[0]
|
|
15
|
+
end = df['Datetime'].iloc[-1]
|
|
16
|
+
freq = pd.to_timedelta(timeframe)
|
|
17
|
+
expected = pd.date_range(start, end, freq=freq)
|
|
18
|
+
actual = df['Datetime']
|
|
19
|
+
missing = expected.difference(actual)
|
|
20
|
+
|
|
21
|
+
# Calculte the percentage completeness
|
|
22
|
+
completeness = len(actual) / len(expected) * 100
|
|
23
|
+
title += f" ({completeness:.2f}% complete)"
|
|
24
|
+
fig = go.Figure()
|
|
25
|
+
fig.add_trace(
|
|
26
|
+
go.Scatter(
|
|
27
|
+
x=actual,
|
|
28
|
+
y=[1]*len(actual),
|
|
29
|
+
mode='markers',
|
|
30
|
+
name='Present',
|
|
31
|
+
marker=dict(color='green', size=6)
|
|
32
|
+
)
|
|
33
|
+
)
|
|
34
|
+
fig.add_trace(
|
|
35
|
+
go.Scatter(
|
|
36
|
+
x=missing,
|
|
37
|
+
y=[1]*len(missing),
|
|
38
|
+
mode='markers',
|
|
39
|
+
name='Missing',
|
|
40
|
+
marker=dict(color='red', size=6, symbol='x')
|
|
41
|
+
)
|
|
42
|
+
)
|
|
43
|
+
fig.update_layout(
|
|
44
|
+
title=title,
|
|
45
|
+
xaxis_title='Datetime',
|
|
46
|
+
yaxis=dict(showticklabels=False),
|
|
47
|
+
height=300,
|
|
48
|
+
showlegend=True
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
return pio.to_html(fig, full_html=False, include_plotlyjs='cdn')
|