aponyx 0.1.18__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.
- aponyx/__init__.py +14 -0
- aponyx/backtest/__init__.py +31 -0
- aponyx/backtest/adapters.py +77 -0
- aponyx/backtest/config.py +84 -0
- aponyx/backtest/engine.py +560 -0
- aponyx/backtest/protocols.py +101 -0
- aponyx/backtest/registry.py +334 -0
- aponyx/backtest/strategy_catalog.json +50 -0
- aponyx/cli/__init__.py +5 -0
- aponyx/cli/commands/__init__.py +8 -0
- aponyx/cli/commands/clean.py +349 -0
- aponyx/cli/commands/list.py +302 -0
- aponyx/cli/commands/report.py +167 -0
- aponyx/cli/commands/run.py +377 -0
- aponyx/cli/main.py +125 -0
- aponyx/config/__init__.py +82 -0
- aponyx/data/__init__.py +99 -0
- aponyx/data/bloomberg_config.py +306 -0
- aponyx/data/bloomberg_instruments.json +26 -0
- aponyx/data/bloomberg_securities.json +42 -0
- aponyx/data/cache.py +294 -0
- aponyx/data/fetch.py +659 -0
- aponyx/data/fetch_registry.py +135 -0
- aponyx/data/loaders.py +205 -0
- aponyx/data/providers/__init__.py +13 -0
- aponyx/data/providers/bloomberg.py +383 -0
- aponyx/data/providers/file.py +111 -0
- aponyx/data/registry.py +500 -0
- aponyx/data/requirements.py +96 -0
- aponyx/data/sample_data.py +415 -0
- aponyx/data/schemas.py +60 -0
- aponyx/data/sources.py +171 -0
- aponyx/data/synthetic_params.json +46 -0
- aponyx/data/transforms.py +336 -0
- aponyx/data/validation.py +308 -0
- aponyx/docs/__init__.py +24 -0
- aponyx/docs/adding_data_providers.md +682 -0
- aponyx/docs/cdx_knowledge_base.md +455 -0
- aponyx/docs/cdx_overlay_strategy.md +135 -0
- aponyx/docs/cli_guide.md +607 -0
- aponyx/docs/governance_design.md +551 -0
- aponyx/docs/logging_design.md +251 -0
- aponyx/docs/performance_evaluation_design.md +265 -0
- aponyx/docs/python_guidelines.md +786 -0
- aponyx/docs/signal_registry_usage.md +369 -0
- aponyx/docs/signal_suitability_design.md +558 -0
- aponyx/docs/visualization_design.md +277 -0
- aponyx/evaluation/__init__.py +11 -0
- aponyx/evaluation/performance/__init__.py +24 -0
- aponyx/evaluation/performance/adapters.py +109 -0
- aponyx/evaluation/performance/analyzer.py +384 -0
- aponyx/evaluation/performance/config.py +320 -0
- aponyx/evaluation/performance/decomposition.py +304 -0
- aponyx/evaluation/performance/metrics.py +761 -0
- aponyx/evaluation/performance/registry.py +327 -0
- aponyx/evaluation/performance/report.py +541 -0
- aponyx/evaluation/suitability/__init__.py +67 -0
- aponyx/evaluation/suitability/config.py +143 -0
- aponyx/evaluation/suitability/evaluator.py +389 -0
- aponyx/evaluation/suitability/registry.py +328 -0
- aponyx/evaluation/suitability/report.py +398 -0
- aponyx/evaluation/suitability/scoring.py +367 -0
- aponyx/evaluation/suitability/tests.py +303 -0
- aponyx/examples/01_generate_synthetic_data.py +53 -0
- aponyx/examples/02_fetch_data_file.py +82 -0
- aponyx/examples/03_fetch_data_bloomberg.py +104 -0
- aponyx/examples/04_compute_signal.py +164 -0
- aponyx/examples/05_evaluate_suitability.py +224 -0
- aponyx/examples/06_run_backtest.py +242 -0
- aponyx/examples/07_analyze_performance.py +214 -0
- aponyx/examples/08_visualize_results.py +272 -0
- aponyx/main.py +7 -0
- aponyx/models/__init__.py +45 -0
- aponyx/models/config.py +83 -0
- aponyx/models/indicator_transformation.json +52 -0
- aponyx/models/indicators.py +292 -0
- aponyx/models/metadata.py +447 -0
- aponyx/models/orchestrator.py +213 -0
- aponyx/models/registry.py +860 -0
- aponyx/models/score_transformation.json +42 -0
- aponyx/models/signal_catalog.json +29 -0
- aponyx/models/signal_composer.py +513 -0
- aponyx/models/signal_transformation.json +29 -0
- aponyx/persistence/__init__.py +16 -0
- aponyx/persistence/json_io.py +132 -0
- aponyx/persistence/parquet_io.py +378 -0
- aponyx/py.typed +0 -0
- aponyx/reporting/__init__.py +10 -0
- aponyx/reporting/generator.py +517 -0
- aponyx/visualization/__init__.py +20 -0
- aponyx/visualization/app.py +37 -0
- aponyx/visualization/plots.py +309 -0
- aponyx/visualization/visualizer.py +242 -0
- aponyx/workflows/__init__.py +18 -0
- aponyx/workflows/concrete_steps.py +720 -0
- aponyx/workflows/config.py +122 -0
- aponyx/workflows/engine.py +279 -0
- aponyx/workflows/registry.py +116 -0
- aponyx/workflows/steps.py +180 -0
- aponyx-0.1.18.dist-info/METADATA +552 -0
- aponyx-0.1.18.dist-info/RECORD +104 -0
- aponyx-0.1.18.dist-info/WHEEL +4 -0
- aponyx-0.1.18.dist-info/entry_points.txt +2 -0
- aponyx-0.1.18.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Execute backtest for signal using strategy from catalog.
|
|
3
|
+
|
|
4
|
+
Prerequisites
|
|
5
|
+
-------------
|
|
6
|
+
Signals saved from signal computation (04_compute_signal.py):
|
|
7
|
+
- Signal files exist in data/workflows/signals/{signal_name}.parquet
|
|
8
|
+
CDX spread data available from registry.
|
|
9
|
+
Strategy catalog configured in backtest/strategy_catalog.json.
|
|
10
|
+
|
|
11
|
+
Outputs
|
|
12
|
+
-------
|
|
13
|
+
BacktestResult with positions and P&L:
|
|
14
|
+
- positions: DataFrame with signal, position, days_held, spread
|
|
15
|
+
- pnl: DataFrame with spread_pnl, cost, net_pnl, cumulative_pnl
|
|
16
|
+
- metadata: Config and execution details
|
|
17
|
+
Backtest results saved to data/workflows/backtests/{signal_name}_{strategy}_*.parquet.
|
|
18
|
+
|
|
19
|
+
Examples
|
|
20
|
+
--------
|
|
21
|
+
Run from project root:
|
|
22
|
+
python -m aponyx.examples.06_run_backtest
|
|
23
|
+
|
|
24
|
+
Expected output: BacktestResult with positions and P&L history.
|
|
25
|
+
Results saved to data/workflows/backtests/spread_momentum_balanced_*.parquet.
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
import pandas as pd
|
|
29
|
+
|
|
30
|
+
from aponyx.config import (
|
|
31
|
+
REGISTRY_PATH,
|
|
32
|
+
DATA_DIR,
|
|
33
|
+
DATA_WORKFLOWS_DIR,
|
|
34
|
+
STRATEGY_CATALOG_PATH,
|
|
35
|
+
)
|
|
36
|
+
from aponyx.data.registry import DataRegistry
|
|
37
|
+
from aponyx.backtest import BacktestResult, run_backtest, StrategyRegistry
|
|
38
|
+
from aponyx.persistence import load_parquet, save_parquet
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def main() -> BacktestResult:
|
|
42
|
+
"""
|
|
43
|
+
Execute backtest workflow.
|
|
44
|
+
|
|
45
|
+
Loads signal and spread data, selects strategy from catalog,
|
|
46
|
+
runs backtest, and saves results.
|
|
47
|
+
|
|
48
|
+
Returns
|
|
49
|
+
-------
|
|
50
|
+
BacktestResult
|
|
51
|
+
Backtest result with positions and P&L.
|
|
52
|
+
"""
|
|
53
|
+
signal_name, product, strategy_name = define_backtest_parameters()
|
|
54
|
+
signal, spread = load_backtest_data(signal_name, product)
|
|
55
|
+
config = load_strategy_config(strategy_name)
|
|
56
|
+
result = execute_backtest(signal, spread, config)
|
|
57
|
+
save_backtest_result(result, signal_name, strategy_name)
|
|
58
|
+
return result
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def define_backtest_parameters() -> tuple[str, str, str]:
|
|
62
|
+
"""
|
|
63
|
+
Define backtest parameters.
|
|
64
|
+
|
|
65
|
+
Returns
|
|
66
|
+
-------
|
|
67
|
+
tuple[str, str, str]
|
|
68
|
+
Signal name, product identifier, and strategy name.
|
|
69
|
+
|
|
70
|
+
Notes
|
|
71
|
+
-----
|
|
72
|
+
Choose one signal-product-strategy combination for demonstration.
|
|
73
|
+
In practice, evaluate multiple strategies per signal.
|
|
74
|
+
"""
|
|
75
|
+
signal_name = "spread_momentum"
|
|
76
|
+
product = "cdx_ig_5y"
|
|
77
|
+
strategy_name = "balanced"
|
|
78
|
+
return signal_name, product, strategy_name
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def load_backtest_data(
|
|
82
|
+
signal_name: str,
|
|
83
|
+
product: str,
|
|
84
|
+
) -> tuple[pd.Series, pd.Series]:
|
|
85
|
+
"""
|
|
86
|
+
Load signal and spread data for backtest.
|
|
87
|
+
|
|
88
|
+
Parameters
|
|
89
|
+
----------
|
|
90
|
+
signal_name : str
|
|
91
|
+
Name of signal to load from processed directory.
|
|
92
|
+
product : str
|
|
93
|
+
Product identifier for spread data.
|
|
94
|
+
|
|
95
|
+
Returns
|
|
96
|
+
-------
|
|
97
|
+
tuple[pd.Series, pd.Series]
|
|
98
|
+
Signal series and spread series (aligned).
|
|
99
|
+
|
|
100
|
+
Notes
|
|
101
|
+
-----
|
|
102
|
+
Loads signal saved by previous step (04_compute_signal.py).
|
|
103
|
+
Spread data loaded from registry using product identifier.
|
|
104
|
+
"""
|
|
105
|
+
signal = load_signal(signal_name)
|
|
106
|
+
spread = load_spread_data(product)
|
|
107
|
+
|
|
108
|
+
# Align signal and spread to common dates
|
|
109
|
+
common_idx = signal.index.intersection(spread.index)
|
|
110
|
+
signal = signal.loc[common_idx]
|
|
111
|
+
spread = spread.loc[common_idx]
|
|
112
|
+
|
|
113
|
+
return signal, spread
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def load_signal(signal_name: str) -> pd.Series:
|
|
117
|
+
"""
|
|
118
|
+
Load signal from processed directory.
|
|
119
|
+
|
|
120
|
+
Parameters
|
|
121
|
+
----------
|
|
122
|
+
signal_name : str
|
|
123
|
+
Name of signal file (without .parquet extension).
|
|
124
|
+
|
|
125
|
+
Returns
|
|
126
|
+
-------
|
|
127
|
+
pd.Series
|
|
128
|
+
Signal series with DatetimeIndex.
|
|
129
|
+
"""
|
|
130
|
+
signal_path = DATA_WORKFLOWS_DIR / "signals" / f"{signal_name}.parquet"
|
|
131
|
+
signal_df = load_parquet(signal_path)
|
|
132
|
+
return signal_df["value"]
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def load_spread_data(product: str) -> pd.Series:
|
|
136
|
+
"""
|
|
137
|
+
Load spread data for target product.
|
|
138
|
+
|
|
139
|
+
Parameters
|
|
140
|
+
----------
|
|
141
|
+
product : str
|
|
142
|
+
Product identifier (e.g., "cdx_ig_5y").
|
|
143
|
+
|
|
144
|
+
Returns
|
|
145
|
+
-------
|
|
146
|
+
pd.Series
|
|
147
|
+
Spread series with DatetimeIndex.
|
|
148
|
+
|
|
149
|
+
Notes
|
|
150
|
+
-----
|
|
151
|
+
Uses DataRegistry.load_dataset_by_security() for efficient lookup.
|
|
152
|
+
"""
|
|
153
|
+
data_registry = DataRegistry(REGISTRY_PATH, DATA_DIR)
|
|
154
|
+
spread_df = data_registry.load_dataset_by_security(product)
|
|
155
|
+
return spread_df["spread"]
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
def load_strategy_config(strategy_name: str):
|
|
159
|
+
"""
|
|
160
|
+
Load strategy configuration from catalog.
|
|
161
|
+
|
|
162
|
+
Parameters
|
|
163
|
+
----------
|
|
164
|
+
strategy_name : str
|
|
165
|
+
Name of strategy in catalog (e.g., "balanced", "conservative").
|
|
166
|
+
|
|
167
|
+
Returns
|
|
168
|
+
-------
|
|
169
|
+
BacktestConfig
|
|
170
|
+
Backtest configuration with strategy parameters.
|
|
171
|
+
|
|
172
|
+
Notes
|
|
173
|
+
-----
|
|
174
|
+
Reads strategy metadata from catalog and converts to BacktestConfig.
|
|
175
|
+
"""
|
|
176
|
+
registry = StrategyRegistry(STRATEGY_CATALOG_PATH)
|
|
177
|
+
metadata = registry.get_metadata(strategy_name)
|
|
178
|
+
return metadata.to_config()
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
def execute_backtest(
|
|
182
|
+
signal: pd.Series,
|
|
183
|
+
spread: pd.Series,
|
|
184
|
+
config,
|
|
185
|
+
) -> BacktestResult:
|
|
186
|
+
"""
|
|
187
|
+
Run backtest with signal and spread data.
|
|
188
|
+
|
|
189
|
+
Parameters
|
|
190
|
+
----------
|
|
191
|
+
signal : pd.Series
|
|
192
|
+
Signal series with DatetimeIndex.
|
|
193
|
+
spread : pd.Series
|
|
194
|
+
Spread series with DatetimeIndex.
|
|
195
|
+
config : BacktestConfig
|
|
196
|
+
Backtest configuration.
|
|
197
|
+
|
|
198
|
+
Returns
|
|
199
|
+
-------
|
|
200
|
+
BacktestResult
|
|
201
|
+
Backtest result with positions and P&L.
|
|
202
|
+
"""
|
|
203
|
+
return run_backtest(signal, spread, config)
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
def save_backtest_result(
|
|
207
|
+
result: BacktestResult,
|
|
208
|
+
signal_name: str,
|
|
209
|
+
strategy_name: str,
|
|
210
|
+
) -> None:
|
|
211
|
+
"""
|
|
212
|
+
Save backtest results to processed directory.
|
|
213
|
+
|
|
214
|
+
Parameters
|
|
215
|
+
----------
|
|
216
|
+
result : BacktestResult
|
|
217
|
+
Backtest result to save.
|
|
218
|
+
signal_name : str
|
|
219
|
+
Name of signal.
|
|
220
|
+
strategy_name : str
|
|
221
|
+
Name of strategy.
|
|
222
|
+
|
|
223
|
+
Notes
|
|
224
|
+
-----
|
|
225
|
+
Saves positions and P&L DataFrames to separate parquet files.
|
|
226
|
+
File naming: {signal_name}_{strategy_name}_positions.parquet
|
|
227
|
+
{signal_name}_{strategy_name}_pnl.parquet
|
|
228
|
+
"""
|
|
229
|
+
backtests_dir = DATA_WORKFLOWS_DIR / "backtests"
|
|
230
|
+
backtests_dir.mkdir(parents=True, exist_ok=True)
|
|
231
|
+
|
|
232
|
+
# Save positions
|
|
233
|
+
positions_path = backtests_dir / f"{signal_name}_{strategy_name}_positions.parquet"
|
|
234
|
+
save_parquet(result.positions, positions_path)
|
|
235
|
+
|
|
236
|
+
# Save P&L
|
|
237
|
+
pnl_path = backtests_dir / f"{signal_name}_{strategy_name}_pnl.parquet"
|
|
238
|
+
save_parquet(result.pnl, pnl_path)
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
if __name__ == "__main__":
|
|
242
|
+
main()
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Compute comprehensive performance metrics for backtest results.
|
|
3
|
+
|
|
4
|
+
Prerequisites
|
|
5
|
+
-------------
|
|
6
|
+
Backtest results saved from backtest execution (06_run_backtest.py):
|
|
7
|
+
- Positions file: data/workflows/backtests/{signal}_{strategy}_positions.parquet
|
|
8
|
+
- P&L file: data/workflows/backtests/{signal}_{strategy}_pnl.parquet
|
|
9
|
+
|
|
10
|
+
Outputs
|
|
11
|
+
-------
|
|
12
|
+
PerformanceResult with comprehensive metrics:
|
|
13
|
+
- All 21+ performance metrics (Sharpe, drawdown, profit factor, tail ratio, etc.)
|
|
14
|
+
- Subperiod stability analysis (quarterly breakdown with full metrics per period)
|
|
15
|
+
- Return attribution (directional, signal strength, win/loss decomposition)
|
|
16
|
+
- Overall stability score and interpretive summary
|
|
17
|
+
Performance report saved to reports/performance/{signal}_{strategy}_{timestamp}.md.
|
|
18
|
+
|
|
19
|
+
Examples
|
|
20
|
+
--------
|
|
21
|
+
Run from project root:
|
|
22
|
+
python -m aponyx.examples.07_analyze_performance
|
|
23
|
+
|
|
24
|
+
Expected output: PerformanceResult with stability score ~0.7, profit factor ~1.5.
|
|
25
|
+
Markdown report saved to reports/performance/spread_momentum_balanced_{timestamp}.md.
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
from aponyx.config import DATA_WORKFLOWS_DIR
|
|
29
|
+
from aponyx.backtest import BacktestResult
|
|
30
|
+
from aponyx.evaluation.performance import (
|
|
31
|
+
analyze_backtest_performance,
|
|
32
|
+
PerformanceConfig,
|
|
33
|
+
PerformanceResult,
|
|
34
|
+
generate_performance_report,
|
|
35
|
+
save_report,
|
|
36
|
+
)
|
|
37
|
+
from aponyx.persistence import load_parquet
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def main() -> PerformanceResult:
|
|
41
|
+
"""
|
|
42
|
+
Execute performance analysis workflow.
|
|
43
|
+
|
|
44
|
+
Loads backtest results, computes comprehensive metrics,
|
|
45
|
+
generates interpretive report, and saves outputs.
|
|
46
|
+
|
|
47
|
+
Returns
|
|
48
|
+
-------
|
|
49
|
+
PerformanceResult
|
|
50
|
+
Performance evaluation with metrics, attribution, and summary.
|
|
51
|
+
"""
|
|
52
|
+
signal_name, strategy_name = define_analysis_parameters()
|
|
53
|
+
backtest_result = load_backtest_result(signal_name, strategy_name)
|
|
54
|
+
config = define_performance_config()
|
|
55
|
+
performance = compute_performance_metrics(backtest_result, config)
|
|
56
|
+
save_performance_report(performance, signal_name, strategy_name)
|
|
57
|
+
return performance
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def define_analysis_parameters() -> tuple[str, str]:
|
|
61
|
+
"""
|
|
62
|
+
Define analysis parameters.
|
|
63
|
+
|
|
64
|
+
Returns
|
|
65
|
+
-------
|
|
66
|
+
tuple[str, str]
|
|
67
|
+
Signal name and strategy name.
|
|
68
|
+
|
|
69
|
+
Notes
|
|
70
|
+
-----
|
|
71
|
+
Must match the signal-strategy combination from backtest step.
|
|
72
|
+
"""
|
|
73
|
+
signal_name = "spread_momentum"
|
|
74
|
+
strategy_name = "balanced"
|
|
75
|
+
return signal_name, strategy_name
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def load_backtest_result(
|
|
79
|
+
signal_name: str,
|
|
80
|
+
strategy_name: str,
|
|
81
|
+
) -> BacktestResult:
|
|
82
|
+
"""
|
|
83
|
+
Load backtest results from processed directory.
|
|
84
|
+
|
|
85
|
+
Parameters
|
|
86
|
+
----------
|
|
87
|
+
signal_name : str
|
|
88
|
+
Name of signal.
|
|
89
|
+
strategy_name : str
|
|
90
|
+
Name of strategy.
|
|
91
|
+
|
|
92
|
+
Returns
|
|
93
|
+
-------
|
|
94
|
+
BacktestResult
|
|
95
|
+
Backtest result with positions and P&L DataFrames.
|
|
96
|
+
|
|
97
|
+
Notes
|
|
98
|
+
-----
|
|
99
|
+
Loads positions and P&L from separate parquet files saved
|
|
100
|
+
by previous step (06_run_backtest.py). Reconstructs BacktestResult
|
|
101
|
+
with minimal metadata for analysis.
|
|
102
|
+
"""
|
|
103
|
+
backtests_dir = DATA_WORKFLOWS_DIR / "backtests"
|
|
104
|
+
|
|
105
|
+
positions_path = backtests_dir / f"{signal_name}_{strategy_name}_positions.parquet"
|
|
106
|
+
pnl_path = backtests_dir / f"{signal_name}_{strategy_name}_pnl.parquet"
|
|
107
|
+
|
|
108
|
+
positions = load_parquet(positions_path)
|
|
109
|
+
pnl = load_parquet(pnl_path)
|
|
110
|
+
|
|
111
|
+
metadata = {
|
|
112
|
+
"signal_id": signal_name,
|
|
113
|
+
"strategy_id": strategy_name,
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
return BacktestResult(
|
|
117
|
+
positions=positions,
|
|
118
|
+
pnl=pnl,
|
|
119
|
+
metadata=metadata,
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def define_performance_config() -> PerformanceConfig:
|
|
124
|
+
"""
|
|
125
|
+
Define performance evaluation configuration.
|
|
126
|
+
|
|
127
|
+
Returns
|
|
128
|
+
-------
|
|
129
|
+
PerformanceConfig
|
|
130
|
+
Configuration with analysis parameters.
|
|
131
|
+
|
|
132
|
+
Notes
|
|
133
|
+
-----
|
|
134
|
+
Uses quarterly subperiod analysis (n_subperiods=4) and
|
|
135
|
+
3-month rolling window (rolling_window=63) for stability metrics.
|
|
136
|
+
Attribution uses terciles (attribution_quantiles=3) for
|
|
137
|
+
low/medium/high signal strength decomposition.
|
|
138
|
+
"""
|
|
139
|
+
return PerformanceConfig(
|
|
140
|
+
min_obs=252,
|
|
141
|
+
n_subperiods=4,
|
|
142
|
+
risk_free_rate=0.0,
|
|
143
|
+
rolling_window=63,
|
|
144
|
+
report_format="markdown",
|
|
145
|
+
attribution_quantiles=3,
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
def compute_performance_metrics(
|
|
150
|
+
backtest_result: BacktestResult,
|
|
151
|
+
config: PerformanceConfig,
|
|
152
|
+
) -> PerformanceResult:
|
|
153
|
+
"""
|
|
154
|
+
Compute comprehensive performance metrics.
|
|
155
|
+
|
|
156
|
+
Parameters
|
|
157
|
+
----------
|
|
158
|
+
backtest_result : BacktestResult
|
|
159
|
+
Backtest output with positions and P&L.
|
|
160
|
+
config : PerformanceConfig
|
|
161
|
+
Performance evaluation configuration.
|
|
162
|
+
|
|
163
|
+
Returns
|
|
164
|
+
-------
|
|
165
|
+
PerformanceResult
|
|
166
|
+
Complete performance analysis with metrics, attribution, and summary.
|
|
167
|
+
|
|
168
|
+
Notes
|
|
169
|
+
-----
|
|
170
|
+
Orchestrates all performance computations:
|
|
171
|
+
- Basic metrics: Sharpe, max drawdown, hit rate, trades
|
|
172
|
+
- Extended metrics: rolling Sharpe stability, recovery time, tail ratios
|
|
173
|
+
- Subperiod analysis: quarterly breakdown with full metrics per period
|
|
174
|
+
- Attribution: directional, signal strength, win/loss decomposition
|
|
175
|
+
- Stability score: consistency across subperiods
|
|
176
|
+
- Summary: interpretive text with key findings
|
|
177
|
+
"""
|
|
178
|
+
return analyze_backtest_performance(backtest_result, config)
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
def save_performance_report(
|
|
182
|
+
performance: PerformanceResult,
|
|
183
|
+
signal_name: str,
|
|
184
|
+
strategy_name: str,
|
|
185
|
+
) -> None:
|
|
186
|
+
"""
|
|
187
|
+
Generate and save performance report.
|
|
188
|
+
|
|
189
|
+
Parameters
|
|
190
|
+
----------
|
|
191
|
+
performance : PerformanceResult
|
|
192
|
+
Performance analysis results.
|
|
193
|
+
signal_name : str
|
|
194
|
+
Name of signal.
|
|
195
|
+
strategy_name : str
|
|
196
|
+
Name of strategy.
|
|
197
|
+
|
|
198
|
+
Notes
|
|
199
|
+
-----
|
|
200
|
+
Generates markdown report with formatted tables and saves to
|
|
201
|
+
reports/performance/{signal}_{strategy}_{timestamp}.md.
|
|
202
|
+
Report includes all metrics, attribution breakdown, and summary.
|
|
203
|
+
"""
|
|
204
|
+
report = generate_performance_report(
|
|
205
|
+
performance,
|
|
206
|
+
signal_id=signal_name,
|
|
207
|
+
strategy_id=strategy_name,
|
|
208
|
+
generate_tearsheet=False,
|
|
209
|
+
)
|
|
210
|
+
save_report(report, signal_name, strategy_name, DATA_WORKFLOWS_DIR / "reports")
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
if __name__ == "__main__":
|
|
214
|
+
main()
|