@samsmith2121/synthetic-leverage-engine 0.1.0

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.
Files changed (44) hide show
  1. package/README.md +61 -0
  2. package/dist/src/analysis-cli.d.ts +1 -0
  3. package/dist/src/analysis-cli.js +59 -0
  4. package/dist/src/analysis.d.ts +214 -0
  5. package/dist/src/analysis.js +843 -0
  6. package/dist/src/backtest.d.ts +3 -0
  7. package/dist/src/backtest.js +172 -0
  8. package/dist/src/benchmark.d.ts +11 -0
  9. package/dist/src/benchmark.js +85 -0
  10. package/dist/src/contributions.d.ts +4 -0
  11. package/dist/src/contributions.js +11 -0
  12. package/dist/src/costs.d.ts +8 -0
  13. package/dist/src/costs.js +29 -0
  14. package/dist/src/index.d.ts +18 -0
  15. package/dist/src/index.js +34 -0
  16. package/dist/src/metrics.d.ts +15 -0
  17. package/dist/src/metrics.js +232 -0
  18. package/dist/src/public-api.d.ts +89 -0
  19. package/dist/src/public-api.js +103 -0
  20. package/dist/src/reporting.d.ts +112 -0
  21. package/dist/src/reporting.js +156 -0
  22. package/dist/src/signal.d.ts +34 -0
  23. package/dist/src/signal.js +80 -0
  24. package/dist/src/simulator/adapters.d.ts +9 -0
  25. package/dist/src/simulator/adapters.js +99 -0
  26. package/dist/src/simulator/index.d.ts +4 -0
  27. package/dist/src/simulator/index.js +31 -0
  28. package/dist/src/simulator/monte-carlo.d.ts +8 -0
  29. package/dist/src/simulator/monte-carlo.js +94 -0
  30. package/dist/src/simulator/series.d.ts +6 -0
  31. package/dist/src/simulator/series.js +152 -0
  32. package/dist/src/simulator/types.d.ts +111 -0
  33. package/dist/src/simulator/types.js +2 -0
  34. package/dist/src/step.d.ts +24 -0
  35. package/dist/src/step.js +75 -0
  36. package/dist/src/types.d.ts +145 -0
  37. package/dist/src/types.js +2 -0
  38. package/dist/src/validation-cases.d.ts +21 -0
  39. package/dist/src/validation-cases.js +930 -0
  40. package/dist/src/validation-harness.d.ts +1 -0
  41. package/dist/src/validation-harness.js +26 -0
  42. package/dist/src/validation.d.ts +8 -0
  43. package/dist/src/validation.js +244 -0
  44. package/package.json +36 -0
@@ -0,0 +1,89 @@
1
+ import { ParameterSweepConfig, ParameterSweepGrid, ParameterSweepResult, ScenarioAnalysisResult, ScenarioDefinition, StrategyConfigComparison, StrategyRunResult, StrategyVariant, SweepRankMetric } from './analysis';
2
+ import { toBacktestSummaryExportRows, toDiagnosticsExportRows, toParameterSweepExportRows, toScenarioExportRows, toStrategyComparisonRows } from './reporting';
3
+ import { DayInput, EngineConfig, MonteCarloConfig, MonteCarloSummary } from './types';
4
+ export interface SingleBacktestRequest {
5
+ days: DayInput[];
6
+ config: EngineConfig;
7
+ strategy: StrategyVariant;
8
+ }
9
+ export interface SingleBacktestResponse {
10
+ run: StrategyRunResult;
11
+ }
12
+ export interface StrategyComparisonRequest {
13
+ days: DayInput[];
14
+ config: EngineConfig;
15
+ strategies: StrategyVariant[];
16
+ rankBy?: SweepRankMetric;
17
+ }
18
+ export interface StrategyComparisonResponse {
19
+ comparison: StrategyConfigComparison;
20
+ }
21
+ export interface ParameterSweepRequest {
22
+ days: DayInput[];
23
+ config: EngineConfig;
24
+ grid: ParameterSweepGrid;
25
+ options?: ParameterSweepConfig;
26
+ }
27
+ export interface ParameterSweepResponse {
28
+ sweep: ParameterSweepResult;
29
+ }
30
+ export interface ScenarioAnalysisRequest {
31
+ days: DayInput[];
32
+ config: EngineConfig;
33
+ strategy: StrategyVariant;
34
+ scenarios: ScenarioDefinition[];
35
+ includeFullPeriod?: boolean;
36
+ }
37
+ export interface ScenarioAnalysisResponse {
38
+ scenarios: ScenarioAnalysisResult;
39
+ }
40
+ export interface DiagnosticsRequest {
41
+ days: DayInput[];
42
+ config: EngineConfig;
43
+ strategies: StrategyVariant[];
44
+ rankBy?: SweepRankMetric;
45
+ }
46
+ export interface DiagnosticsResponse {
47
+ comparison: StrategyConfigComparison;
48
+ diagnosticsRows: ReturnType<typeof toDiagnosticsExportRows>;
49
+ }
50
+ export interface MonteCarloRequest {
51
+ days: DayInput[];
52
+ config: EngineConfig;
53
+ monteCarlo: MonteCarloConfig;
54
+ }
55
+ export type MonteCarloWarningCode = 'MC_HAS_NON_FINITE_VALUES' | 'MC_SEED_NOT_REPRODUCIBLE' | 'MC_DIFFERENT_SEEDS_IDENTICAL' | 'MC_PATH_DATES_NOT_STRICTLY_INCREASING' | 'MC_PERCENTILES_NOT_ORDERED';
56
+ export interface MonteCarloWarning {
57
+ code: MonteCarloWarningCode;
58
+ message: string;
59
+ context?: Record<string, string | number | boolean | null>;
60
+ }
61
+ export interface MonteCarloResponse {
62
+ config: MonteCarloConfig;
63
+ summary: MonteCarloSummary;
64
+ pathCount: number;
65
+ warnings: MonteCarloWarning[];
66
+ }
67
+ export interface ExportRowsRequest {
68
+ comparison?: StrategyConfigComparison;
69
+ sweep?: ParameterSweepResult;
70
+ scenarios?: ScenarioAnalysisResult;
71
+ }
72
+ export interface ExportRowsResponse {
73
+ comparisonRows?: ReturnType<typeof toStrategyComparisonRows>;
74
+ backtestSummaryRows?: ReturnType<typeof toBacktestSummaryExportRows>;
75
+ sweepRows?: ReturnType<typeof toParameterSweepExportRows>;
76
+ scenarioRows?: ReturnType<typeof toScenarioExportRows>;
77
+ diagnosticsRows?: ReturnType<typeof toDiagnosticsExportRows>;
78
+ }
79
+ /**
80
+ * UI-focused facade for engine integration.
81
+ * Financial logic remains inside analysis/backtest modules.
82
+ */
83
+ export declare function runSingleBacktest(request: SingleBacktestRequest): SingleBacktestResponse;
84
+ export declare function runComparison(request: StrategyComparisonRequest): StrategyComparisonResponse;
85
+ export declare function runSweep(request: ParameterSweepRequest): ParameterSweepResponse;
86
+ export declare function runScenarios(request: ScenarioAnalysisRequest): ScenarioAnalysisResponse;
87
+ export declare function runDiagnostics(request: DiagnosticsRequest): DiagnosticsResponse;
88
+ export declare function runMonteCarlo(request: MonteCarloRequest): MonteCarloResponse;
89
+ export declare function buildExportRows(request: ExportRowsRequest): ExportRowsResponse;
@@ -0,0 +1,103 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.runSingleBacktest = runSingleBacktest;
4
+ exports.runComparison = runComparison;
5
+ exports.runSweep = runSweep;
6
+ exports.runScenarios = runScenarios;
7
+ exports.runDiagnostics = runDiagnostics;
8
+ exports.runMonteCarlo = runMonteCarlo;
9
+ exports.buildExportRows = buildExportRows;
10
+ const analysis_1 = require("./analysis");
11
+ const backtest_1 = require("./backtest");
12
+ const reporting_1 = require("./reporting");
13
+ /**
14
+ * UI-focused facade for engine integration.
15
+ * Financial logic remains inside analysis/backtest modules.
16
+ */
17
+ function runSingleBacktest(request) {
18
+ const comparison = (0, analysis_1.runStrategyComparison)(request.days, request.config, [request.strategy]);
19
+ return {
20
+ run: comparison.runs[0],
21
+ };
22
+ }
23
+ function runComparison(request) {
24
+ return {
25
+ comparison: (0, analysis_1.runStrategyComparison)(request.days, request.config, request.strategies, request.rankBy),
26
+ };
27
+ }
28
+ function runSweep(request) {
29
+ return {
30
+ sweep: (0, analysis_1.runParameterSweep)(request.days, request.config, request.grid, request.options),
31
+ };
32
+ }
33
+ function runScenarios(request) {
34
+ return {
35
+ scenarios: (0, analysis_1.runScenarioAnalysis)(request.days, request.config, request.strategy, request.scenarios, request.includeFullPeriod),
36
+ };
37
+ }
38
+ function runDiagnostics(request) {
39
+ const comparison = (0, analysis_1.runStrategyComparison)(request.days, request.config, request.strategies, request.rankBy);
40
+ return {
41
+ comparison,
42
+ diagnosticsRows: (0, reporting_1.toDiagnosticsExportRows)(comparison),
43
+ };
44
+ }
45
+ function runMonteCarlo(request) {
46
+ const result = (0, backtest_1.runMonteCarloBacktest)(request.days, request.config, request.monteCarlo);
47
+ return {
48
+ config: result.config,
49
+ summary: result.summary,
50
+ pathCount: result.paths.length,
51
+ warnings: mapMonteCarloWarnings(result.invariants),
52
+ };
53
+ }
54
+ function mapMonteCarloWarnings(invariants) {
55
+ if (!invariants) {
56
+ return [];
57
+ }
58
+ const warnings = [];
59
+ if (!invariants.hasFiniteValues) {
60
+ warnings.push({
61
+ code: 'MC_HAS_NON_FINITE_VALUES',
62
+ message: 'Monte Carlo invariants found non-finite values in path outputs.',
63
+ });
64
+ }
65
+ if (!invariants.sameSeedReproducible) {
66
+ warnings.push({
67
+ code: 'MC_SEED_NOT_REPRODUCIBLE',
68
+ message: 'Monte Carlo same-seed reproducibility invariant failed.',
69
+ });
70
+ }
71
+ if (!invariants.differentSeedsCanDiffer) {
72
+ warnings.push({
73
+ code: 'MC_DIFFERENT_SEEDS_IDENTICAL',
74
+ message: 'Monte Carlo different-seed divergence invariant failed.',
75
+ });
76
+ }
77
+ if (!invariants.pathDatesStrictlyIncreasing) {
78
+ warnings.push({
79
+ code: 'MC_PATH_DATES_NOT_STRICTLY_INCREASING',
80
+ message: 'Monte Carlo path date ordering invariant failed.',
81
+ });
82
+ }
83
+ if (!invariants.percentileSummariesOrdered) {
84
+ warnings.push({
85
+ code: 'MC_PERCENTILES_NOT_ORDERED',
86
+ message: 'Monte Carlo percentile ordering invariant failed.',
87
+ });
88
+ }
89
+ return warnings;
90
+ }
91
+ function buildExportRows(request) {
92
+ return {
93
+ ...(request.comparison
94
+ ? {
95
+ comparisonRows: (0, reporting_1.toStrategyComparisonRows)(request.comparison),
96
+ backtestSummaryRows: (0, reporting_1.toBacktestSummaryExportRows)(request.comparison),
97
+ diagnosticsRows: (0, reporting_1.toDiagnosticsExportRows)(request.comparison),
98
+ }
99
+ : {}),
100
+ ...(request.sweep ? { sweepRows: (0, reporting_1.toParameterSweepExportRows)(request.sweep) } : {}),
101
+ ...(request.scenarios ? { scenarioRows: (0, reporting_1.toScenarioExportRows)(request.scenarios) } : {}),
102
+ };
103
+ }
@@ -0,0 +1,112 @@
1
+ import { AnalysisRunSummary, OneWaySensitivityResult, ParameterSweepResult, ScenarioAnalysisResult, StrategyConfigComparison, SweepResultRow, TwoWaySensitivityResult } from './analysis';
2
+ export interface StrategyComparisonRow {
3
+ strategyName: string;
4
+ finalEquity: number;
5
+ totalInvested: number;
6
+ twrCagr: number | null;
7
+ xirr: number | null;
8
+ maxDrawdown: number;
9
+ volatility: number | null;
10
+ totalTradeCost: number;
11
+ totalBorrowCost: number;
12
+ totalErCost: number;
13
+ totalSwitches: number;
14
+ excessTotalReturn?: number | null;
15
+ excessTwrCagr?: number | null;
16
+ trackingError?: number | null;
17
+ }
18
+ export interface AnalysisReport {
19
+ generatedAt: string;
20
+ comparisonRows: StrategyComparisonRow[];
21
+ sweepRows: SweepResultRow[];
22
+ scenarioRows: Array<{
23
+ scenarioName: string;
24
+ startDate: string;
25
+ endDate: string;
26
+ summary?: AnalysisRunSummary;
27
+ error?: string;
28
+ warningCodes: string[];
29
+ }>;
30
+ }
31
+ export interface BacktestSummaryExportRow {
32
+ strategy_name: string;
33
+ final_equity: number;
34
+ total_invested: number;
35
+ twr_cagr: number | null;
36
+ xirr: number | null;
37
+ max_drawdown: number;
38
+ volatility: number | null;
39
+ total_trade_cost: number;
40
+ total_borrow_cost: number;
41
+ total_er_cost: number;
42
+ total_switches: number;
43
+ excess_twr_cagr: number | null;
44
+ }
45
+ export interface ParameterSweepExportRow {
46
+ strategy_name: string;
47
+ vix_threshold: number;
48
+ leverage_multiple: number;
49
+ trade_spread: number;
50
+ expense_ratio_annual: number;
51
+ cash_mode: string;
52
+ cash_yield_daily: number;
53
+ monte_carlo_block_length: number | null;
54
+ run_ok: boolean;
55
+ twr_cagr: number | null;
56
+ xirr: number | null;
57
+ max_drawdown: number | null;
58
+ excess_twr_cagr: number | null;
59
+ warning_codes: string;
60
+ error: string | null;
61
+ }
62
+ export interface ScenarioExportRow {
63
+ scenario_name: string;
64
+ start_date: string;
65
+ end_date: string;
66
+ strategy_name: string;
67
+ run_ok: boolean;
68
+ twr_cagr: number | null;
69
+ xirr: number | null;
70
+ max_drawdown: number | null;
71
+ warning_codes: string;
72
+ error: string | null;
73
+ }
74
+ export interface SensitivityExportRow {
75
+ analysis_type: 'one_way' | 'two_way';
76
+ parameter_x: string;
77
+ value_x: number | string;
78
+ parameter_y: string | null;
79
+ value_y: number | string | null;
80
+ strategy_name: string;
81
+ run_ok: boolean;
82
+ twr_cagr: number | null;
83
+ xirr: number | null;
84
+ max_drawdown: number | null;
85
+ excess_twr_cagr: number | null;
86
+ warning_codes: string;
87
+ error: string | null;
88
+ }
89
+ export interface DiagnosticsExportRow {
90
+ strategy_name: string;
91
+ pct_days_cash: number;
92
+ pct_days_one_x: number;
93
+ pct_days_leveraged: number;
94
+ average_applied_leverage: number;
95
+ total_switches: number;
96
+ total_cost: number;
97
+ trade_cost_share: number;
98
+ borrow_cost_share: number;
99
+ er_cost_share: number;
100
+ max_drawdown: number;
101
+ max_drawdown_duration_days: number;
102
+ outperform_pct: number | null;
103
+ excess_twr_cagr: number | null;
104
+ }
105
+ export declare function toStrategyComparisonRows(comparison: StrategyConfigComparison): StrategyComparisonRow[];
106
+ export declare function toBacktestSummaryExportRows(comparison: StrategyConfigComparison): BacktestSummaryExportRow[];
107
+ export declare function toParameterSweepExportRows(sweep: ParameterSweepResult): ParameterSweepExportRow[];
108
+ export declare function toScenarioExportRows(result: ScenarioAnalysisResult): ScenarioExportRow[];
109
+ export declare function toOneWaySensitivityExportRows(result: OneWaySensitivityResult): SensitivityExportRow[];
110
+ export declare function toTwoWaySensitivityExportRows(result: TwoWaySensitivityResult): SensitivityExportRow[];
111
+ export declare function toDiagnosticsExportRows(comparison: StrategyConfigComparison): DiagnosticsExportRow[];
112
+ export declare function buildAnalysisReport(comparison: StrategyConfigComparison, sweep: ParameterSweepResult, scenarios: ScenarioAnalysisResult): AnalysisReport;
@@ -0,0 +1,156 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.toStrategyComparisonRows = toStrategyComparisonRows;
4
+ exports.toBacktestSummaryExportRows = toBacktestSummaryExportRows;
5
+ exports.toParameterSweepExportRows = toParameterSweepExportRows;
6
+ exports.toScenarioExportRows = toScenarioExportRows;
7
+ exports.toOneWaySensitivityExportRows = toOneWaySensitivityExportRows;
8
+ exports.toTwoWaySensitivityExportRows = toTwoWaySensitivityExportRows;
9
+ exports.toDiagnosticsExportRows = toDiagnosticsExportRows;
10
+ exports.buildAnalysisReport = buildAnalysisReport;
11
+ function toStrategyComparisonRows(comparison) {
12
+ return comparison.runs
13
+ .filter((run) => run.ok)
14
+ .map((run) => ({
15
+ strategyName: run.strategy.name,
16
+ finalEquity: run.summary.finalEquity,
17
+ totalInvested: run.summary.totalInvested,
18
+ twrCagr: run.summary.twrCagr,
19
+ xirr: run.summary.xirr,
20
+ maxDrawdown: run.summary.maxDrawdown,
21
+ volatility: run.summary.volatility,
22
+ totalTradeCost: run.summary.totalTradeCost,
23
+ totalBorrowCost: run.summary.totalBorrowCost,
24
+ totalErCost: run.summary.totalErCost,
25
+ totalSwitches: run.summary.totalSwitches,
26
+ ...(run.summary.benchmark
27
+ ? {
28
+ excessTotalReturn: run.summary.benchmark.excessTotalReturn,
29
+ excessTwrCagr: run.summary.benchmark.excessTwrCagr,
30
+ trackingError: run.summary.benchmark.trackingError,
31
+ }
32
+ : {}),
33
+ }));
34
+ }
35
+ function toBacktestSummaryExportRows(comparison) {
36
+ return comparison.runs
37
+ .filter((run) => run.ok)
38
+ .map((run) => ({
39
+ strategy_name: run.strategy.name,
40
+ final_equity: run.summary.finalEquity,
41
+ total_invested: run.summary.totalInvested,
42
+ twr_cagr: run.summary.twrCagr,
43
+ xirr: run.summary.xirr,
44
+ max_drawdown: run.summary.maxDrawdown,
45
+ volatility: run.summary.volatility,
46
+ total_trade_cost: run.summary.totalTradeCost,
47
+ total_borrow_cost: run.summary.totalBorrowCost,
48
+ total_er_cost: run.summary.totalErCost,
49
+ total_switches: run.summary.totalSwitches,
50
+ excess_twr_cagr: run.summary.benchmark?.excessTwrCagr ?? null,
51
+ }));
52
+ }
53
+ function toParameterSweepExportRows(sweep) {
54
+ return sweep.rows.map((row) => ({
55
+ strategy_name: row.strategyName,
56
+ vix_threshold: row.vixThreshold,
57
+ leverage_multiple: row.leverageMultiple,
58
+ trade_spread: row.tradeSpread,
59
+ expense_ratio_annual: row.expenseRatioAnnual,
60
+ cash_mode: row.cashMode,
61
+ cash_yield_daily: row.cashYieldDaily,
62
+ monte_carlo_block_length: row.monteCarloBlockLength ?? null,
63
+ run_ok: row.run.ok,
64
+ twr_cagr: row.run.ok ? row.run.summary.twrCagr : null,
65
+ xirr: row.run.ok ? row.run.summary.xirr : null,
66
+ max_drawdown: row.run.ok ? row.run.summary.maxDrawdown : null,
67
+ excess_twr_cagr: row.run.ok ? row.run.summary.benchmark?.excessTwrCagr ?? null : null,
68
+ warning_codes: row.run.warnings.map((warning) => warning.code).join('|'),
69
+ error: row.run.ok ? null : row.run.error,
70
+ }));
71
+ }
72
+ function toScenarioExportRows(result) {
73
+ return result.rows.map((row) => ({
74
+ scenario_name: row.scenarioName,
75
+ start_date: row.startDate,
76
+ end_date: row.endDate,
77
+ strategy_name: row.run.strategy.name,
78
+ run_ok: row.run.ok,
79
+ twr_cagr: row.run.ok ? row.run.summary.twrCagr : null,
80
+ xirr: row.run.ok ? row.run.summary.xirr : null,
81
+ max_drawdown: row.run.ok ? row.run.summary.maxDrawdown : null,
82
+ warning_codes: row.warnings.map((warning) => warning.code).join('|'),
83
+ error: row.run.ok ? null : row.run.error,
84
+ }));
85
+ }
86
+ function toOneWaySensitivityExportRows(result) {
87
+ return result.rows.map((row) => ({
88
+ analysis_type: 'one_way',
89
+ parameter_x: row.parameter,
90
+ value_x: row.parameterValue,
91
+ parameter_y: null,
92
+ value_y: null,
93
+ strategy_name: row.run.strategy.name,
94
+ run_ok: row.run.ok,
95
+ twr_cagr: row.run.ok ? row.run.summary.twrCagr : null,
96
+ xirr: row.run.ok ? row.run.summary.xirr : null,
97
+ max_drawdown: row.run.ok ? row.run.summary.maxDrawdown : null,
98
+ excess_twr_cagr: row.run.ok ? row.run.summary.benchmark?.excessTwrCagr ?? null : null,
99
+ warning_codes: row.run.warnings.map((warning) => warning.code).join('|'),
100
+ error: row.run.ok ? null : row.run.error,
101
+ }));
102
+ }
103
+ function toTwoWaySensitivityExportRows(result) {
104
+ return result.cells.map((cell) => ({
105
+ analysis_type: 'two_way',
106
+ parameter_x: cell.xParameter,
107
+ value_x: cell.xValue,
108
+ parameter_y: cell.yParameter,
109
+ value_y: cell.yValue,
110
+ strategy_name: cell.run.strategy.name,
111
+ run_ok: cell.run.ok,
112
+ twr_cagr: cell.run.ok ? cell.run.summary.twrCagr : null,
113
+ xirr: cell.run.ok ? cell.run.summary.xirr : null,
114
+ max_drawdown: cell.run.ok ? cell.run.summary.maxDrawdown : null,
115
+ excess_twr_cagr: cell.run.ok ? cell.run.summary.benchmark?.excessTwrCagr ?? null : null,
116
+ warning_codes: cell.run.warnings.map((warning) => warning.code).join('|'),
117
+ error: cell.run.ok ? null : cell.run.error,
118
+ }));
119
+ }
120
+ function toDiagnosticsExportRows(comparison) {
121
+ return comparison.runs
122
+ .filter((run) => run.ok)
123
+ .map((run) => mapDiagnostics(run.strategy.name, run.summary.diagnostics));
124
+ }
125
+ function buildAnalysisReport(comparison, sweep, scenarios) {
126
+ return {
127
+ generatedAt: new Date().toISOString(),
128
+ comparisonRows: toStrategyComparisonRows(comparison),
129
+ sweepRows: sweep.rows,
130
+ scenarioRows: scenarios.rows.map((row) => ({
131
+ scenarioName: row.scenarioName,
132
+ startDate: row.startDate,
133
+ endDate: row.endDate,
134
+ ...(row.run.ok ? { summary: row.run.summary } : { error: row.run.error }),
135
+ warningCodes: row.warnings.map((warning) => warning.code),
136
+ })),
137
+ };
138
+ }
139
+ function mapDiagnostics(strategyName, diagnostics) {
140
+ return {
141
+ strategy_name: strategyName,
142
+ pct_days_cash: diagnostics.exposure.pctDaysCash,
143
+ pct_days_one_x: diagnostics.exposure.pctDaysOneX,
144
+ pct_days_leveraged: diagnostics.exposure.pctDaysLeveraged,
145
+ average_applied_leverage: diagnostics.exposure.averageAppliedLeverage,
146
+ total_switches: diagnostics.transitions.totalSwitches,
147
+ total_cost: diagnostics.costs.totalCost,
148
+ trade_cost_share: diagnostics.costs.tradeCostShare,
149
+ borrow_cost_share: diagnostics.costs.borrowCostShare,
150
+ er_cost_share: diagnostics.costs.erCostShare,
151
+ max_drawdown: diagnostics.drawdown.maxDrawdown,
152
+ max_drawdown_duration_days: diagnostics.drawdown.maxDrawdownDurationDays,
153
+ outperform_pct: diagnostics.benchmarkRelative?.outperformPct ?? null,
154
+ excess_twr_cagr: diagnostics.benchmarkRelative?.excessTwrCagr ?? null,
155
+ };
156
+ }
@@ -0,0 +1,34 @@
1
+ import { DayInput, EngineConfig, SignalContext } from './types';
2
+ export interface ThresholdSignalDay {
3
+ assetClose: number;
4
+ indicatorClose?: number;
5
+ }
6
+ export interface ThresholdSignalStrategy {
7
+ leverageMultiple: number;
8
+ vixDefensiveExposure: number;
9
+ signal: {
10
+ useSma: boolean;
11
+ smaLength: number;
12
+ exposureAboveSignal?: number;
13
+ exposureBelowSignal?: number;
14
+ useVixThreshold: boolean;
15
+ vixThreshold: number;
16
+ };
17
+ }
18
+ /**
19
+ * Pure signal function for day t inputs.
20
+ * The value returned here is not applied to day t return in fixed path.
21
+ */
22
+ export declare function computeSignal(context: SignalContext): number;
23
+ /**
24
+ * Maps signal value to bounded target position.
25
+ * Keeps leverage logic explicit and auditable.
26
+ */
27
+ export declare function deriveTargetPosition(signal: number, config: EngineConfig): number;
28
+ export declare function buildSignalContext(day: DayInput, previousDay?: DayInput): SignalContext;
29
+ export declare function computeSmaSeries(values: number[], length: number): Array<number | undefined>;
30
+ /**
31
+ * Shared threshold/switching helper for consumers that provide close-based inputs
32
+ * but still need engine-owned target exposure generation before the fixed path.
33
+ */
34
+ export declare function buildThresholdSignalTargets(days: ThresholdSignalDay[], strategy: ThresholdSignalStrategy): number[];
@@ -0,0 +1,80 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.computeSignal = computeSignal;
4
+ exports.deriveTargetPosition = deriveTargetPosition;
5
+ exports.buildSignalContext = buildSignalContext;
6
+ exports.computeSmaSeries = computeSmaSeries;
7
+ exports.buildThresholdSignalTargets = buildThresholdSignalTargets;
8
+ /**
9
+ * Pure signal function for day t inputs.
10
+ * The value returned here is not applied to day t return in fixed path.
11
+ */
12
+ function computeSignal(context) {
13
+ return context.day.signalInput;
14
+ }
15
+ /**
16
+ * Maps signal value to bounded target position.
17
+ * Keeps leverage logic explicit and auditable.
18
+ */
19
+ function deriveTargetPosition(signal, config) {
20
+ const capped = Math.max(-config.maxLeverage, Math.min(config.maxLeverage, signal));
21
+ if (Object.is(capped, -0)) {
22
+ return 0;
23
+ }
24
+ return capped;
25
+ }
26
+ function buildSignalContext(day, previousDay) {
27
+ return { day, previousDay };
28
+ }
29
+ function computeSmaSeries(values, length) {
30
+ const out = new Array(values.length).fill(undefined);
31
+ if (length <= 0) {
32
+ return out;
33
+ }
34
+ let sum = 0;
35
+ for (let i = 0; i < values.length; i += 1) {
36
+ sum += values[i];
37
+ if (i >= length) {
38
+ sum -= values[i - length];
39
+ }
40
+ if (i >= length - 1) {
41
+ out[i] = sum / length;
42
+ }
43
+ }
44
+ return out;
45
+ }
46
+ /**
47
+ * Shared threshold/switching helper for consumers that provide close-based inputs
48
+ * but still need engine-owned target exposure generation before the fixed path.
49
+ */
50
+ function buildThresholdSignalTargets(days, strategy) {
51
+ const smaSeries = computeSmaSeries(days.map((day) => day.assetClose), strategy.signal.smaLength);
52
+ const targets = [];
53
+ let vixDefensive = false;
54
+ for (let i = 0; i < days.length; i += 1) {
55
+ const day = days[i];
56
+ let target = strategy.leverageMultiple;
57
+ if (strategy.signal.useSma) {
58
+ const sma = smaSeries[i];
59
+ if (typeof sma === 'number') {
60
+ const above = strategy.signal.exposureAboveSignal ?? strategy.leverageMultiple;
61
+ const below = strategy.signal.exposureBelowSignal ?? strategy.leverageMultiple;
62
+ target = day.assetClose >= sma ? above : below;
63
+ }
64
+ }
65
+ if (strategy.signal.useVixThreshold && typeof day.indicatorClose === 'number') {
66
+ const threshold = strategy.signal.vixThreshold;
67
+ if (day.indicatorClose > threshold + 2) {
68
+ vixDefensive = true;
69
+ }
70
+ if (day.indicatorClose < threshold - 2) {
71
+ vixDefensive = false;
72
+ }
73
+ }
74
+ if (vixDefensive) {
75
+ target = strategy.vixDefensiveExposure;
76
+ }
77
+ targets.push(Math.max(0, target));
78
+ }
79
+ return targets;
80
+ }
@@ -0,0 +1,9 @@
1
+ import { DayInput, EngineConfig } from '../types';
2
+ import { SimulatorBacktestRequest, SimulatorDay, SimulatorMonteCarloRequest } from './types';
3
+ export interface PreparedSimulatorRun {
4
+ sourceDays: SimulatorDay[];
5
+ engineDays: DayInput[];
6
+ engineConfig: EngineConfig;
7
+ }
8
+ export declare function prepareSimulatorBacktest(request: SimulatorBacktestRequest): PreparedSimulatorRun;
9
+ export declare function prepareSimulatorMonteCarlo(request: SimulatorMonteCarloRequest): PreparedSimulatorRun;
@@ -0,0 +1,99 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.prepareSimulatorBacktest = prepareSimulatorBacktest;
4
+ exports.prepareSimulatorMonteCarlo = prepareSimulatorMonteCarlo;
5
+ const costs_1 = require("../costs");
6
+ const signal_1 = require("../signal");
7
+ const DEFAULT_BUILTIN_RATE_ANNUAL = 0.04;
8
+ function prepareSimulatorBacktest(request) {
9
+ return prepareSimulatorRun(request.days, request.config, request.strategy);
10
+ }
11
+ function prepareSimulatorMonteCarlo(request) {
12
+ return prepareSimulatorRun(request.days, request.config, request.strategy);
13
+ }
14
+ function prepareSimulatorRun(sourceDays, simulatorConfig, simulatorStrategy) {
15
+ const targetSignals = (0, signal_1.buildThresholdSignalTargets)(sourceDays, simulatorStrategy);
16
+ const engineDays = sourceDays.map((day, index) => buildEngineDay({
17
+ day,
18
+ index,
19
+ targetSignal: targetSignals[index],
20
+ previousDay: index > 0 ? sourceDays[index - 1] : undefined,
21
+ previousMonth: index > 0 ? monthOf(sourceDays[index - 1].date) : undefined,
22
+ simulatorConfig,
23
+ simulatorStrategy,
24
+ }));
25
+ return {
26
+ sourceDays,
27
+ engineDays,
28
+ engineConfig: {
29
+ initialEquity: simulatorConfig.initialEquity,
30
+ initialTargetPosition: 0,
31
+ initialAppliedPosition: 0,
32
+ tradeCostRate: 0,
33
+ maxLeverage: Math.max(1, ...targetSignals.map((signal) => Math.abs(signal))),
34
+ initialBorrowRateDaily: engineDays[0]?.borrowRateDaily,
35
+ cashYieldDaily: 0,
36
+ enableInvariantChecks: true,
37
+ },
38
+ };
39
+ }
40
+ function buildEngineDay(params) {
41
+ const { day, index, targetSignal, previousDay, previousMonth, simulatorConfig, simulatorStrategy, } = params;
42
+ return {
43
+ date: day.date,
44
+ assetReturn: computeAssetReturn(day.assetClose, previousDay?.assetClose),
45
+ benchmarkReturn: computeBenchmarkReturn(day.benchmarkClose, previousDay?.benchmarkClose),
46
+ signalInput: targetSignal,
47
+ contribution: contributionForDay(index, monthOf(day.date), previousMonth, simulatorConfig.monthlyContribution),
48
+ borrowRateDaily: resolveBorrowRateDaily(day, simulatorConfig, simulatorStrategy),
49
+ expenseRatioAnnual: simulatorConfig.expenseRatioPct / 100,
50
+ tradeSpread: simulatorConfig.tradeSpreadPct / 100,
51
+ };
52
+ }
53
+ function resolveBorrowRateDaily(day, simulatorConfig, simulatorStrategy) {
54
+ const annualFinancingRate = resolveAnnualFinancingRate(day, simulatorStrategy);
55
+ const annualBorrowRate = annualFinancingRate + simulatorConfig.borrowSpreadPct / 100;
56
+ return (0, costs_1.annualToDailyRate)(annualBorrowRate);
57
+ }
58
+ function resolveAnnualFinancingRate(day, strategy) {
59
+ if (strategy.rateSource.mode === 'fixed') {
60
+ return (strategy.rateSource.fixedRatePct ?? 0) / 100;
61
+ }
62
+ if (strategy.rateSource.mode === 'series') {
63
+ return day.financingRateAnnual ?? 0;
64
+ }
65
+ if (day.financingRateAnnual !== undefined) {
66
+ return day.financingRateAnnual;
67
+ }
68
+ return DEFAULT_BUILTIN_RATE_ANNUAL;
69
+ }
70
+ function contributionForDay(index, month, previousMonth, monthlyContribution) {
71
+ if (monthlyContribution <= 0) {
72
+ return 0;
73
+ }
74
+ if (index === 0) {
75
+ return 0;
76
+ }
77
+ return previousMonth !== month ? monthlyContribution : 0;
78
+ }
79
+ function computeCloseReturn(current, previous) {
80
+ if (previous === undefined) {
81
+ return undefined;
82
+ }
83
+ if (!Number.isFinite(previous) || previous === 0) {
84
+ return 0;
85
+ }
86
+ return (current - previous) / previous;
87
+ }
88
+ function computeAssetReturn(current, previous) {
89
+ return computeCloseReturn(current, previous) ?? 0;
90
+ }
91
+ function computeBenchmarkReturn(currentBenchmarkClose, previousBenchmarkClose) {
92
+ if (currentBenchmarkClose === undefined || previousBenchmarkClose === undefined) {
93
+ return undefined;
94
+ }
95
+ return computeCloseReturn(currentBenchmarkClose, previousBenchmarkClose);
96
+ }
97
+ function monthOf(date) {
98
+ return new Date(`${date}T00:00:00Z`).getUTCMonth();
99
+ }