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,334 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Strategy registry for managing backtest strategy metadata and catalog persistence.
|
|
3
|
+
|
|
4
|
+
Follows the same governance pattern as SignalRegistry for consistency.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import json
|
|
8
|
+
import logging
|
|
9
|
+
from dataclasses import dataclass, asdict
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
|
|
12
|
+
from .config import BacktestConfig
|
|
13
|
+
|
|
14
|
+
logger = logging.getLogger(__name__)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@dataclass(frozen=True)
|
|
18
|
+
class StrategyMetadata:
|
|
19
|
+
"""
|
|
20
|
+
Metadata for a registered backtest strategy.
|
|
21
|
+
|
|
22
|
+
Attributes
|
|
23
|
+
----------
|
|
24
|
+
name : str
|
|
25
|
+
Unique strategy identifier (e.g., "conservative", "balanced").
|
|
26
|
+
description : str
|
|
27
|
+
Human-readable description of strategy characteristics.
|
|
28
|
+
position_size_mm : float
|
|
29
|
+
Baseline notional position size in millions.
|
|
30
|
+
sizing_mode : str
|
|
31
|
+
Position sizing mode: 'binary' (full position for any non-zero signal)
|
|
32
|
+
or 'proportional' (scaled by signal magnitude).
|
|
33
|
+
stop_loss_pct : float | None
|
|
34
|
+
Stop loss as percentage of initial position value. None to disable.
|
|
35
|
+
take_profit_pct : float | None
|
|
36
|
+
Take profit as percentage of initial position value. None to disable.
|
|
37
|
+
max_holding_days : int | None
|
|
38
|
+
Maximum days to hold a position before forced exit. None for no limit.
|
|
39
|
+
transaction_cost_bps : float
|
|
40
|
+
Round-trip transaction cost in basis points.
|
|
41
|
+
dv01_per_million : float
|
|
42
|
+
DV01 per $1MM notional for risk calculations.
|
|
43
|
+
enabled : bool
|
|
44
|
+
Whether strategy should be included in evaluation.
|
|
45
|
+
"""
|
|
46
|
+
|
|
47
|
+
name: str
|
|
48
|
+
description: str
|
|
49
|
+
position_size_mm: float
|
|
50
|
+
sizing_mode: str
|
|
51
|
+
stop_loss_pct: float | None
|
|
52
|
+
take_profit_pct: float | None
|
|
53
|
+
max_holding_days: int | None
|
|
54
|
+
transaction_cost_bps: float
|
|
55
|
+
dv01_per_million: float
|
|
56
|
+
enabled: bool = True
|
|
57
|
+
|
|
58
|
+
def __post_init__(self) -> None:
|
|
59
|
+
"""Validate strategy metadata."""
|
|
60
|
+
if not self.name:
|
|
61
|
+
raise ValueError("Strategy name cannot be empty")
|
|
62
|
+
if self.position_size_mm <= 0:
|
|
63
|
+
raise ValueError(
|
|
64
|
+
f"Strategy '{self.name}': position_size_mm must be positive, "
|
|
65
|
+
f"got {self.position_size_mm}"
|
|
66
|
+
)
|
|
67
|
+
if self.sizing_mode not in {"binary", "proportional"}:
|
|
68
|
+
raise ValueError(
|
|
69
|
+
f"Strategy '{self.name}': sizing_mode must be 'binary' or 'proportional', "
|
|
70
|
+
f"got '{self.sizing_mode}'"
|
|
71
|
+
)
|
|
72
|
+
if self.stop_loss_pct is not None and not (0 < self.stop_loss_pct <= 100):
|
|
73
|
+
raise ValueError(
|
|
74
|
+
f"Strategy '{self.name}': stop_loss_pct must be in (0, 100], "
|
|
75
|
+
f"got {self.stop_loss_pct}"
|
|
76
|
+
)
|
|
77
|
+
if self.take_profit_pct is not None and not (0 < self.take_profit_pct <= 100):
|
|
78
|
+
raise ValueError(
|
|
79
|
+
f"Strategy '{self.name}': take_profit_pct must be in (0, 100], "
|
|
80
|
+
f"got {self.take_profit_pct}"
|
|
81
|
+
)
|
|
82
|
+
if self.max_holding_days is not None and self.max_holding_days <= 0:
|
|
83
|
+
raise ValueError(
|
|
84
|
+
f"Strategy '{self.name}': max_holding_days must be positive, "
|
|
85
|
+
f"got {self.max_holding_days}"
|
|
86
|
+
)
|
|
87
|
+
if self.transaction_cost_bps < 0:
|
|
88
|
+
raise ValueError(
|
|
89
|
+
f"Strategy '{self.name}': transaction_cost_bps must be non-negative, "
|
|
90
|
+
f"got {self.transaction_cost_bps}"
|
|
91
|
+
)
|
|
92
|
+
if self.dv01_per_million <= 0:
|
|
93
|
+
raise ValueError(
|
|
94
|
+
f"Strategy '{self.name}': dv01_per_million must be positive, "
|
|
95
|
+
f"got {self.dv01_per_million}"
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
def to_config(
|
|
99
|
+
self,
|
|
100
|
+
position_size_mm_override: float | None = None,
|
|
101
|
+
sizing_mode_override: str | None = None,
|
|
102
|
+
stop_loss_pct_override: float | None = None,
|
|
103
|
+
take_profit_pct_override: float | None = None,
|
|
104
|
+
max_holding_days_override: int | None = None,
|
|
105
|
+
) -> BacktestConfig:
|
|
106
|
+
"""
|
|
107
|
+
Convert strategy metadata to BacktestConfig.
|
|
108
|
+
|
|
109
|
+
Supports runtime parameter overrides for rapid experimentation.
|
|
110
|
+
|
|
111
|
+
Parameters
|
|
112
|
+
----------
|
|
113
|
+
position_size_mm_override : float | None, default None
|
|
114
|
+
Override catalog position_size_mm value.
|
|
115
|
+
sizing_mode_override : str | None, default None
|
|
116
|
+
Override catalog sizing_mode value.
|
|
117
|
+
stop_loss_pct_override : float | None, default None
|
|
118
|
+
Override catalog stop_loss_pct value (use False to explicitly disable).
|
|
119
|
+
take_profit_pct_override : float | None, default None
|
|
120
|
+
Override catalog take_profit_pct value (use False to explicitly disable).
|
|
121
|
+
max_holding_days_override : int | None, default None
|
|
122
|
+
Override catalog max_holding_days value (use False to explicitly disable).
|
|
123
|
+
|
|
124
|
+
Returns
|
|
125
|
+
-------
|
|
126
|
+
BacktestConfig
|
|
127
|
+
Full backtest configuration with strategy parameters.
|
|
128
|
+
|
|
129
|
+
Examples
|
|
130
|
+
--------
|
|
131
|
+
>>> metadata = StrategyMetadata(
|
|
132
|
+
... name="aggressive",
|
|
133
|
+
... description="High risk tolerance",
|
|
134
|
+
... position_size_mm=15.0,
|
|
135
|
+
... sizing_mode="binary"
|
|
136
|
+
... )
|
|
137
|
+
>>> config = metadata.to_config(position_size_mm_override=20.0)
|
|
138
|
+
>>> config.position_size_mm
|
|
139
|
+
20.0
|
|
140
|
+
"""
|
|
141
|
+
return BacktestConfig(
|
|
142
|
+
position_size_mm=(
|
|
143
|
+
position_size_mm_override
|
|
144
|
+
if position_size_mm_override is not None
|
|
145
|
+
else self.position_size_mm
|
|
146
|
+
),
|
|
147
|
+
sizing_mode=(
|
|
148
|
+
sizing_mode_override
|
|
149
|
+
if sizing_mode_override is not None
|
|
150
|
+
else self.sizing_mode
|
|
151
|
+
),
|
|
152
|
+
stop_loss_pct=(
|
|
153
|
+
stop_loss_pct_override
|
|
154
|
+
if stop_loss_pct_override is not None
|
|
155
|
+
else self.stop_loss_pct
|
|
156
|
+
),
|
|
157
|
+
take_profit_pct=(
|
|
158
|
+
take_profit_pct_override
|
|
159
|
+
if take_profit_pct_override is not None
|
|
160
|
+
else self.take_profit_pct
|
|
161
|
+
),
|
|
162
|
+
max_holding_days=(
|
|
163
|
+
max_holding_days_override
|
|
164
|
+
if max_holding_days_override is not None
|
|
165
|
+
else self.max_holding_days
|
|
166
|
+
),
|
|
167
|
+
transaction_cost_bps=self.transaction_cost_bps,
|
|
168
|
+
dv01_per_million=self.dv01_per_million,
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
class StrategyRegistry:
|
|
173
|
+
"""
|
|
174
|
+
Registry for strategy metadata with JSON catalog persistence.
|
|
175
|
+
|
|
176
|
+
Manages strategy definitions, enabling/disabling strategies, and catalog I/O.
|
|
177
|
+
Follows pattern from models.registry.SignalRegistry.
|
|
178
|
+
|
|
179
|
+
Parameters
|
|
180
|
+
----------
|
|
181
|
+
catalog_path : str | Path
|
|
182
|
+
Path to JSON catalog file containing strategy metadata.
|
|
183
|
+
|
|
184
|
+
Examples
|
|
185
|
+
--------
|
|
186
|
+
>>> from aponyx.config import STRATEGY_CATALOG_PATH
|
|
187
|
+
>>> registry = StrategyRegistry(STRATEGY_CATALOG_PATH)
|
|
188
|
+
>>> enabled = registry.get_enabled()
|
|
189
|
+
>>> metadata = registry.get_metadata("balanced")
|
|
190
|
+
>>> config = metadata.to_config()
|
|
191
|
+
"""
|
|
192
|
+
|
|
193
|
+
def __init__(self, catalog_path: str | Path) -> None:
|
|
194
|
+
"""
|
|
195
|
+
Initialize registry and load catalog from JSON file.
|
|
196
|
+
|
|
197
|
+
Parameters
|
|
198
|
+
----------
|
|
199
|
+
catalog_path : str | Path
|
|
200
|
+
Path to JSON catalog file.
|
|
201
|
+
|
|
202
|
+
Raises
|
|
203
|
+
------
|
|
204
|
+
FileNotFoundError
|
|
205
|
+
If catalog file does not exist.
|
|
206
|
+
ValueError
|
|
207
|
+
If catalog JSON is invalid or contains duplicate strategy names.
|
|
208
|
+
"""
|
|
209
|
+
self._catalog_path = Path(catalog_path)
|
|
210
|
+
self._strategies: dict[str, StrategyMetadata] = {}
|
|
211
|
+
self._load_catalog()
|
|
212
|
+
|
|
213
|
+
logger.info(
|
|
214
|
+
"Loaded strategy registry: catalog=%s, strategies=%d, enabled=%d",
|
|
215
|
+
self._catalog_path,
|
|
216
|
+
len(self._strategies),
|
|
217
|
+
len(self.get_enabled()),
|
|
218
|
+
)
|
|
219
|
+
|
|
220
|
+
def _load_catalog(self) -> None:
|
|
221
|
+
"""Load strategy metadata from JSON catalog file."""
|
|
222
|
+
if not self._catalog_path.exists():
|
|
223
|
+
raise FileNotFoundError(f"Strategy catalog not found: {self._catalog_path}")
|
|
224
|
+
|
|
225
|
+
with open(self._catalog_path, "r", encoding="utf-8") as f:
|
|
226
|
+
catalog_data = json.load(f)
|
|
227
|
+
|
|
228
|
+
if not isinstance(catalog_data, list):
|
|
229
|
+
raise ValueError("Strategy catalog must be a JSON array")
|
|
230
|
+
|
|
231
|
+
for entry in catalog_data:
|
|
232
|
+
try:
|
|
233
|
+
metadata = StrategyMetadata(**entry)
|
|
234
|
+
if metadata.name in self._strategies:
|
|
235
|
+
raise ValueError(
|
|
236
|
+
f"Duplicate strategy name in catalog: {metadata.name}"
|
|
237
|
+
)
|
|
238
|
+
self._strategies[metadata.name] = metadata
|
|
239
|
+
except TypeError as e:
|
|
240
|
+
raise ValueError(
|
|
241
|
+
f"Invalid strategy metadata in catalog: {entry}. Error: {e}"
|
|
242
|
+
) from e
|
|
243
|
+
|
|
244
|
+
logger.debug("Loaded %d strategies from catalog", len(self._strategies))
|
|
245
|
+
|
|
246
|
+
# Fail-fast validation: thresholds already validated in __post_init__
|
|
247
|
+
# No additional validation needed beyond dataclass constraints
|
|
248
|
+
|
|
249
|
+
def get_metadata(self, name: str) -> StrategyMetadata:
|
|
250
|
+
"""
|
|
251
|
+
Retrieve metadata for a specific strategy.
|
|
252
|
+
|
|
253
|
+
Parameters
|
|
254
|
+
----------
|
|
255
|
+
name : str
|
|
256
|
+
Strategy name.
|
|
257
|
+
|
|
258
|
+
Returns
|
|
259
|
+
-------
|
|
260
|
+
StrategyMetadata
|
|
261
|
+
Strategy metadata.
|
|
262
|
+
|
|
263
|
+
Raises
|
|
264
|
+
------
|
|
265
|
+
KeyError
|
|
266
|
+
If strategy name is not registered.
|
|
267
|
+
"""
|
|
268
|
+
if name not in self._strategies:
|
|
269
|
+
raise KeyError(
|
|
270
|
+
f"Strategy '{name}' not found in registry. "
|
|
271
|
+
f"Available strategies: {sorted(self._strategies.keys())}"
|
|
272
|
+
)
|
|
273
|
+
return self._strategies[name]
|
|
274
|
+
|
|
275
|
+
def get_enabled(self) -> dict[str, StrategyMetadata]:
|
|
276
|
+
"""
|
|
277
|
+
Get all enabled strategies.
|
|
278
|
+
|
|
279
|
+
Returns
|
|
280
|
+
-------
|
|
281
|
+
dict[str, StrategyMetadata]
|
|
282
|
+
Mapping from strategy name to metadata for enabled strategies only.
|
|
283
|
+
"""
|
|
284
|
+
return {name: meta for name, meta in self._strategies.items() if meta.enabled}
|
|
285
|
+
|
|
286
|
+
def list_all(self) -> dict[str, StrategyMetadata]:
|
|
287
|
+
"""
|
|
288
|
+
Get all registered strategies (enabled and disabled).
|
|
289
|
+
|
|
290
|
+
Returns
|
|
291
|
+
-------
|
|
292
|
+
dict[str, StrategyMetadata]
|
|
293
|
+
Mapping from strategy name to metadata for all strategies.
|
|
294
|
+
"""
|
|
295
|
+
return self._strategies.copy()
|
|
296
|
+
|
|
297
|
+
def strategy_exists(self, name: str) -> bool:
|
|
298
|
+
"""
|
|
299
|
+
Check if strategy is registered.
|
|
300
|
+
|
|
301
|
+
Parameters
|
|
302
|
+
----------
|
|
303
|
+
name : str
|
|
304
|
+
Strategy name.
|
|
305
|
+
|
|
306
|
+
Returns
|
|
307
|
+
-------
|
|
308
|
+
bool
|
|
309
|
+
True if strategy exists in registry.
|
|
310
|
+
"""
|
|
311
|
+
return name in self._strategies
|
|
312
|
+
|
|
313
|
+
def save_catalog(self, path: str | Path | None = None) -> None:
|
|
314
|
+
"""
|
|
315
|
+
Save strategy metadata to JSON catalog file.
|
|
316
|
+
|
|
317
|
+
Parameters
|
|
318
|
+
----------
|
|
319
|
+
path : str | Path | None
|
|
320
|
+
Output path. If None, overwrites original catalog file.
|
|
321
|
+
"""
|
|
322
|
+
output_path = Path(path) if path else self._catalog_path
|
|
323
|
+
|
|
324
|
+
catalog_data = [asdict(meta) for meta in self._strategies.values()]
|
|
325
|
+
|
|
326
|
+
output_path.parent.mkdir(parents=True, exist_ok=True)
|
|
327
|
+
with open(output_path, "w", encoding="utf-8") as f:
|
|
328
|
+
json.dump(catalog_data, f, indent=2)
|
|
329
|
+
|
|
330
|
+
logger.info(
|
|
331
|
+
"Saved strategy catalog: path=%s, strategies=%d",
|
|
332
|
+
output_path,
|
|
333
|
+
len(catalog_data),
|
|
334
|
+
)
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
[
|
|
2
|
+
{
|
|
3
|
+
"name": "conservative",
|
|
4
|
+
"description": "Conservative sizing with tight risk management for low-turnover trades",
|
|
5
|
+
"position_size_mm": 5.0,
|
|
6
|
+
"sizing_mode": "proportional",
|
|
7
|
+
"stop_loss_pct": 3.0,
|
|
8
|
+
"take_profit_pct": 8.0,
|
|
9
|
+
"max_holding_days": 20,
|
|
10
|
+
"transaction_cost_bps": 1.0,
|
|
11
|
+
"dv01_per_million": 475.0,
|
|
12
|
+
"enabled": true
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
"name": "balanced",
|
|
16
|
+
"description": "Balanced position sizing with moderate risk management",
|
|
17
|
+
"position_size_mm": 10.0,
|
|
18
|
+
"sizing_mode": "proportional",
|
|
19
|
+
"stop_loss_pct": 5.0,
|
|
20
|
+
"take_profit_pct": 10.0,
|
|
21
|
+
"max_holding_days": null,
|
|
22
|
+
"transaction_cost_bps": 1.0,
|
|
23
|
+
"dv01_per_million": 475.0,
|
|
24
|
+
"enabled": true
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
"name": "aggressive",
|
|
28
|
+
"description": "Aggressive sizing with wide risk bands for high-turnover trading",
|
|
29
|
+
"position_size_mm": 15.0,
|
|
30
|
+
"sizing_mode": "proportional",
|
|
31
|
+
"stop_loss_pct": 10.0,
|
|
32
|
+
"take_profit_pct": null,
|
|
33
|
+
"max_holding_days": null,
|
|
34
|
+
"transaction_cost_bps": 1.0,
|
|
35
|
+
"dv01_per_million": 475.0,
|
|
36
|
+
"enabled": true
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
"name": "experimental",
|
|
40
|
+
"description": "Experimental configuration for research and testing",
|
|
41
|
+
"position_size_mm": 10.0,
|
|
42
|
+
"sizing_mode": "proportional",
|
|
43
|
+
"stop_loss_pct": null,
|
|
44
|
+
"take_profit_pct": null,
|
|
45
|
+
"max_holding_days": null,
|
|
46
|
+
"transaction_cost_bps": 1.0,
|
|
47
|
+
"dv01_per_million": 475.0,
|
|
48
|
+
"enabled": false
|
|
49
|
+
}
|
|
50
|
+
]
|
aponyx/cli/__init__.py
ADDED