@christtrade/depth 0.12.24 → 0.12.25
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.
- package/depth.cjs +38 -38
- package/depth.mjs +38 -38
- package/package.json +1 -1
- package/script-runtime.cjs +2 -2
- package/script-runtime.mjs +2 -2
- package/types/core/TypedEventBus.d.ts +130 -0
- package/types/core/index.d.ts +6 -0
- package/types/core/script-dsl.d.ts +9 -0
- package/types/core/script-runtime.d.ts +9 -2
- package/types/core/strategy-runtime.d.ts +325 -0
- package/types/core/strategy-sweep.d.ts +75 -0
- package/types/core/strategy-walkforward.d.ts +91 -0
- package/types/hooks/useChartData.d.ts +2 -0
- package/types/hooks/useChartSubscriptions.d.ts +1 -0
- package/types/interfaces/plugins/IChartPlugin.d.ts +1 -1
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { LiveTransformer } from '../interfaces/ICoordinateTransformer';
|
|
2
2
|
import { ChartSettings } from '../lib/types/chart-settings';
|
|
3
3
|
import { type SessionStatus } from './SessionUtils';
|
|
4
|
+
import type { StrategyEquityPoint, StrategyPosition, StrategyStats, StrategyTrade } from './strategy-runtime';
|
|
4
5
|
import { BarPreviewResponse, OhlcvBar, SymbolInfo, SymbolSearchMode, SymbolSearchRequest, TradingSession } from '../interfaces/IDataAdapter';
|
|
5
6
|
import { DataLevel } from '../interfaces/IDataAdapter';
|
|
6
7
|
import { IExecutionAdapter } from '../interfaces/IExecutionAdapter';
|
|
@@ -26,6 +27,12 @@ export interface ChartEvents {
|
|
|
26
27
|
} | null;
|
|
27
28
|
'chart:ready': void;
|
|
28
29
|
'chart:reset-view': void;
|
|
30
|
+
'chart:goto-range': {
|
|
31
|
+
fromNs: bigint;
|
|
32
|
+
toNs?: bigint;
|
|
33
|
+
/** Fraction of the span to leave either side. Default 0.25. */
|
|
34
|
+
padding?: number;
|
|
35
|
+
};
|
|
29
36
|
'chart:set-tool': {
|
|
30
37
|
tool: ActiveDrawingTool;
|
|
31
38
|
};
|
|
@@ -542,6 +549,129 @@ export interface ChartEvents {
|
|
|
542
549
|
'plugin:indicator-updated': {
|
|
543
550
|
id: string;
|
|
544
551
|
};
|
|
552
|
+
'plugin:strategy-updated': {
|
|
553
|
+
id: string;
|
|
554
|
+
name: string;
|
|
555
|
+
stats: StrategyStats;
|
|
556
|
+
trades: StrategyTrade[];
|
|
557
|
+
equity: StrategyEquityPoint[];
|
|
558
|
+
/** Still open at the last bar, if the run ended holding something. */
|
|
559
|
+
position: StrategyPosition | null;
|
|
560
|
+
/** The parameter values that produced these numbers. */
|
|
561
|
+
params: Record<string, unknown>;
|
|
562
|
+
/**
|
|
563
|
+
* The script's parameter declarations, so a sweep UI can offer a range
|
|
564
|
+
* for each without the author declaring anything extra - a numeric
|
|
565
|
+
* ParamDef already carries min, max and step.
|
|
566
|
+
*/
|
|
567
|
+
paramDefs: Record<string, unknown>;
|
|
568
|
+
};
|
|
569
|
+
'plugin:apply-params': {
|
|
570
|
+
id: string;
|
|
571
|
+
params: Record<string, unknown>;
|
|
572
|
+
};
|
|
573
|
+
'plugin:strategy-sweep': {
|
|
574
|
+
id: string;
|
|
575
|
+
grid: Array<Record<string, unknown>>;
|
|
576
|
+
/** Fraction held back from the end as out-of-sample. */
|
|
577
|
+
oosFraction?: number;
|
|
578
|
+
/**
|
|
579
|
+
* What each grid point patches over. Pass what the settings dialog
|
|
580
|
+
* currently holds, so parameters that are not being swept keep the value
|
|
581
|
+
* the user set. Omitted falls back to the script's declared defaults.
|
|
582
|
+
*/
|
|
583
|
+
params?: Record<string, unknown>;
|
|
584
|
+
};
|
|
585
|
+
'plugin:strategy-sweep-cancel': {
|
|
586
|
+
id: string;
|
|
587
|
+
};
|
|
588
|
+
'plugin:strategy-sweep-progress': {
|
|
589
|
+
id: string;
|
|
590
|
+
done: number;
|
|
591
|
+
total: number;
|
|
592
|
+
};
|
|
593
|
+
'plugin:strategy-sweep-done': {
|
|
594
|
+
id: string;
|
|
595
|
+
/** One entry per grid point, in grid order. `error` instead of `stats`
|
|
596
|
+
* for a combination that threw. */
|
|
597
|
+
results: Array<{
|
|
598
|
+
params: Record<string, unknown>;
|
|
599
|
+
stats?: StrategyStats;
|
|
600
|
+
inSample?: StrategyStats;
|
|
601
|
+
outOfSample?: StrategyStats;
|
|
602
|
+
error?: string;
|
|
603
|
+
}>;
|
|
604
|
+
};
|
|
605
|
+
'plugin:strategy-sweep-cancelled': {
|
|
606
|
+
id: string;
|
|
607
|
+
done: number;
|
|
608
|
+
};
|
|
609
|
+
'plugin:strategy-sweep-rejected': {
|
|
610
|
+
id: string;
|
|
611
|
+
name: string;
|
|
612
|
+
combos: number;
|
|
613
|
+
bars: number;
|
|
614
|
+
iterations: number;
|
|
615
|
+
reason: string;
|
|
616
|
+
};
|
|
617
|
+
'plugin:strategy-walkforward': {
|
|
618
|
+
id: string;
|
|
619
|
+
grid: Array<Record<string, unknown>>;
|
|
620
|
+
params?: Record<string, unknown>;
|
|
621
|
+
/** Out-of-sample segments to test. Default 4. */
|
|
622
|
+
windows?: number;
|
|
623
|
+
/** In-sample length as a multiple of out-of-sample. Default 3. */
|
|
624
|
+
isMultiple?: number;
|
|
625
|
+
/** Fixed in-sample start rather than a sliding window. */
|
|
626
|
+
anchored?: boolean;
|
|
627
|
+
/** Which statistic the optimiser maximises. Default 'netPnl'. */
|
|
628
|
+
objective?: string;
|
|
629
|
+
/** False for drawdown-like objectives. */
|
|
630
|
+
higherIsBetter?: boolean;
|
|
631
|
+
};
|
|
632
|
+
'plugin:strategy-walkforward-cancel': {
|
|
633
|
+
id: string;
|
|
634
|
+
};
|
|
635
|
+
'plugin:strategy-walkforward-progress': {
|
|
636
|
+
id: string;
|
|
637
|
+
done: number;
|
|
638
|
+
total: number;
|
|
639
|
+
};
|
|
640
|
+
'plugin:strategy-walkforward-done': {
|
|
641
|
+
id: string;
|
|
642
|
+
results: Array<{
|
|
643
|
+
window: {
|
|
644
|
+
index: number;
|
|
645
|
+
isFrom: number;
|
|
646
|
+
isTo: number;
|
|
647
|
+
oosFrom: number;
|
|
648
|
+
oosTo: number;
|
|
649
|
+
};
|
|
650
|
+
params?: Record<string, unknown>;
|
|
651
|
+
inSample?: StrategyStats;
|
|
652
|
+
outOfSample?: StrategyStats;
|
|
653
|
+
/** The test segment's own curve. Stitched across windows, this is the
|
|
654
|
+
* equity of parameters never fitted to the data they run over. */
|
|
655
|
+
outOfSampleEquity?: StrategyEquityPoint[];
|
|
656
|
+
error?: string;
|
|
657
|
+
}>;
|
|
658
|
+
};
|
|
659
|
+
'plugin:strategy-walkforward-cancelled': {
|
|
660
|
+
id: string;
|
|
661
|
+
done: number;
|
|
662
|
+
};
|
|
663
|
+
'plugin:strategy-walkforward-rejected': {
|
|
664
|
+
id: string;
|
|
665
|
+
name: string;
|
|
666
|
+
reason: string;
|
|
667
|
+
};
|
|
668
|
+
'plugin:strategy-rejected': {
|
|
669
|
+
id: string;
|
|
670
|
+
name: string;
|
|
671
|
+
bars: number;
|
|
672
|
+
maxBars: number;
|
|
673
|
+
reason: string;
|
|
674
|
+
};
|
|
545
675
|
'plugin:update-code': {
|
|
546
676
|
id: string;
|
|
547
677
|
code: string;
|
package/types/core/index.d.ts
CHANGED
|
@@ -25,6 +25,12 @@ export type { ChartModelInit, ChartPluginRef, ChartPaneState } from './ChartMode
|
|
|
25
25
|
export { DrawingStore } from './DrawingStore';
|
|
26
26
|
export type { ChartPlugin, PluginType, Permission, PluginManifest, PluginContext, PluginDataSnapshot, DrawingPlugin, ChartTypePlugin, ChartTypeRenderContext, DataSourcePlugin, IndicatorPlugin, IndicatorRenderContext, } from '../interfaces/plugins';
|
|
27
27
|
export { createScriptedPlugin } from './ScriptedPlugin';
|
|
28
|
+
export { StrategyEngine, DEFAULT_STRATEGY_CONFIG } from './strategy-runtime';
|
|
29
|
+
export { axisValues, checkSweepBudget, expandGrid, splitIndex, MAX_SWEEP_COMBOS, MAX_SWEEP_BAR_ITERATIONS, } from './strategy-sweep';
|
|
30
|
+
export type { SweepAxis, SweepSpec, SweepResult, SweepBudget } from './strategy-sweep';
|
|
31
|
+
export { planWalkForward, walkForwardEfficiency, parameterStability, pickBest, } from './strategy-walkforward';
|
|
32
|
+
export type { WalkForwardSpec, WalkForwardWindow, WalkForwardWindowResult, ParameterStability, } from './strategy-walkforward';
|
|
33
|
+
export type { BrokerApi, StrategyEquityPoint, ExitReason, OrderOpts, Side, StrategyBar, StrategyConfig, StrategyOrder, StrategyPosition, StrategyResult, StrategyStats, StrategyTrade, } from './strategy-runtime';
|
|
28
34
|
export { isCompatible, DATA_LEVEL_LABELS, incompatibleReason } from './processing/data-level';
|
|
29
35
|
export { processL3Chunk, mergeL3Chunks } from './processing/l3-processor';
|
|
30
36
|
export type { ProcessedL3Chunk, L3DataChunk } from './processing/l3-processor';
|
|
@@ -4,6 +4,7 @@ export declare const PluginType: Readonly<{
|
|
|
4
4
|
readonly chartType: "chart-type";
|
|
5
5
|
readonly dataSource: "data-source";
|
|
6
6
|
readonly extension: "extension";
|
|
7
|
+
readonly strategy: "strategy";
|
|
7
8
|
}>;
|
|
8
9
|
export declare const DataLevel: Readonly<{
|
|
9
10
|
readonly mbo: "l3";
|
|
@@ -16,5 +17,13 @@ export declare const Layout: Readonly<{
|
|
|
16
17
|
readonly overlay: "overlay";
|
|
17
18
|
readonly pane: "pane";
|
|
18
19
|
}>;
|
|
20
|
+
/** Why a strategy position closed. Mirrors ExitReason in strategy-runtime.ts. */
|
|
21
|
+
export declare const ExitReason: Readonly<{
|
|
22
|
+
readonly signal: "signal";
|
|
23
|
+
readonly stop: "stop";
|
|
24
|
+
readonly target: "target";
|
|
25
|
+
readonly reverse: "reverse";
|
|
26
|
+
readonly endOfData: "end-of-data";
|
|
27
|
+
}>;
|
|
19
28
|
/** The DSL globals as one bag, for splatting into an eval scope. */
|
|
20
29
|
export declare const SCRIPT_DSL: Record<string, unknown>;
|
|
@@ -1,7 +1,13 @@
|
|
|
1
1
|
import { STDLIB } from '../lib/indicator-stdlib';
|
|
2
|
-
import { PluginType, DataLevel, Layout, SCRIPT_DSL } from './script-dsl';
|
|
3
|
-
export { STDLIB, SCRIPT_DSL, PluginType, DataLevel, Layout };
|
|
2
|
+
import { PluginType, DataLevel, Layout, ExitReason, SCRIPT_DSL } from './script-dsl';
|
|
3
|
+
export { STDLIB, SCRIPT_DSL, PluginType, DataLevel, Layout, ExitReason };
|
|
4
4
|
export type { OhlcvBar, DrawCommand } from '../lib/indicator-stdlib';
|
|
5
|
+
export { StrategyEngine, DEFAULT_STRATEGY_CONFIG } from './strategy-runtime';
|
|
6
|
+
export { axisValues, checkSweepBudget, expandGrid, splitIndex } from './strategy-sweep';
|
|
7
|
+
export type { SweepAxis, SweepSpec, SweepResult, SweepBudget } from './strategy-sweep';
|
|
8
|
+
export { planWalkForward, walkForwardEfficiency, parameterStability, pickBest, } from './strategy-walkforward';
|
|
9
|
+
export type { WalkForwardSpec, WalkForwardWindow, WalkForwardWindowResult, ParameterStability, } from './strategy-walkforward';
|
|
10
|
+
export type { BrokerApi, StrategyEquityPoint, OrderOpts, Side, StrategyBar, StrategyConfig, StrategyOrder, StrategyPosition, StrategyResult, StrategyStats, StrategyTrade, } from './strategy-runtime';
|
|
5
11
|
export interface ScriptScopeOptions {
|
|
6
12
|
/** What the script's `plugin({...})` call resolves to. */
|
|
7
13
|
plugin: (decl: unknown) => unknown;
|
|
@@ -38,6 +44,7 @@ export declare function buildScriptScope(opts: ScriptScopeOptions): Record<strin
|
|
|
38
44
|
* script makes while evaluating.
|
|
39
45
|
*/
|
|
40
46
|
export declare function evalScriptInScope(src: string, opts: ScriptScopeOptions): void;
|
|
47
|
+
export declare function defaultStrategyDraw(state: unknown, layout: 'overlay' | 'pane'): unknown[];
|
|
41
48
|
/**
|
|
42
49
|
* Identifies the stdlib surface this build exposes, for a client and a server
|
|
43
50
|
* runner to agree they are talking about the same one.
|
|
@@ -0,0 +1,325 @@
|
|
|
1
|
+
/** Which way a position or fill points. */
|
|
2
|
+
export type Side = 'long' | 'short' | 'flat';
|
|
3
|
+
export type OrderKind = 'market' | 'limit' | 'stop';
|
|
4
|
+
/** Why a position closed, for the trade log and the markers on the chart. */
|
|
5
|
+
export type ExitReason = 'signal' | 'stop' | 'target' | 'reverse' | 'end-of-data';
|
|
6
|
+
export interface StrategyOrder {
|
|
7
|
+
id: number;
|
|
8
|
+
side: 'buy' | 'sell';
|
|
9
|
+
qty: number;
|
|
10
|
+
kind: OrderKind;
|
|
11
|
+
/** Trigger price for limit/stop. Ignored for market. */
|
|
12
|
+
price?: number;
|
|
13
|
+
/** Protective stop attached to the position this order opens. */
|
|
14
|
+
sl?: number;
|
|
15
|
+
/** Profit target attached to the position this order opens. */
|
|
16
|
+
tp?: number;
|
|
17
|
+
tag?: string;
|
|
18
|
+
/** Bar index the order was placed on. It becomes eligible on the next one. */
|
|
19
|
+
placedIndex: number;
|
|
20
|
+
}
|
|
21
|
+
export interface StrategyPosition {
|
|
22
|
+
side: Side;
|
|
23
|
+
qty: number;
|
|
24
|
+
avgPrice: number;
|
|
25
|
+
entryTs: bigint;
|
|
26
|
+
entryIndex: number;
|
|
27
|
+
sl?: number;
|
|
28
|
+
tp?: number;
|
|
29
|
+
tag?: string;
|
|
30
|
+
/**
|
|
31
|
+
* Best and worst price reached while this position has been open.
|
|
32
|
+
*
|
|
33
|
+
* Tracked from the bar the position opened, over that bar's full range. A
|
|
34
|
+
* market order fills at the open so the whole range is fair; a limit or stop
|
|
35
|
+
* filling mid-bar makes the entry bar's contribution an upper bound. Every
|
|
36
|
+
* later bar is exact. Worth knowing before reading MAE as gospel on
|
|
37
|
+
* one-bar trades.
|
|
38
|
+
*/
|
|
39
|
+
highWatermark: number;
|
|
40
|
+
lowWatermark: number;
|
|
41
|
+
/** Bar index and timestamp of the best excursion, for efficiency analysis. */
|
|
42
|
+
mfeIndex: number;
|
|
43
|
+
mfeTs: bigint;
|
|
44
|
+
maeIndex: number;
|
|
45
|
+
maeTs: bigint;
|
|
46
|
+
/**
|
|
47
|
+
* |entry - stop| per unit, locked at entry. Undefined when the position
|
|
48
|
+
* opened without a stop, which makes every R-multiple on it meaningless
|
|
49
|
+
* rather than zero - the difference matters, so it stays undefined.
|
|
50
|
+
*/
|
|
51
|
+
initialRiskPerUnit?: number;
|
|
52
|
+
/** Fills that have gone into this position. Checked against `pyramiding`. */
|
|
53
|
+
entries: number;
|
|
54
|
+
/**
|
|
55
|
+
* Commission already paid to open the quantity still held. Carried on the
|
|
56
|
+
* position so the trade log can report a round-trip fee: equity is charged
|
|
57
|
+
* at entry, but nothing is logged until the exit, and a trade whose pnl
|
|
58
|
+
* omitted the entry side would not sum to the equity curve.
|
|
59
|
+
*/
|
|
60
|
+
entryFees: number;
|
|
61
|
+
}
|
|
62
|
+
export interface StrategyTrade {
|
|
63
|
+
id: number;
|
|
64
|
+
side: Exclude<Side, 'flat'>;
|
|
65
|
+
qty: number;
|
|
66
|
+
entryTs: bigint;
|
|
67
|
+
entryPrice: number;
|
|
68
|
+
exitTs: bigint;
|
|
69
|
+
exitPrice: number;
|
|
70
|
+
/** Net of fees, in account currency. */
|
|
71
|
+
pnl: number;
|
|
72
|
+
/** Before fees. */
|
|
73
|
+
pnlGross: number;
|
|
74
|
+
/** Net return on the notional put at risk, as a fraction. */
|
|
75
|
+
pnlPct: number;
|
|
76
|
+
fees: number;
|
|
77
|
+
/** The fee split, because a strategy killed by commission and one killed by
|
|
78
|
+
* slippage need different fixes. */
|
|
79
|
+
commission: number;
|
|
80
|
+
slippage: number;
|
|
81
|
+
/** How many bars the position was held. */
|
|
82
|
+
bars: number;
|
|
83
|
+
/** Wall-clock time held, in nanoseconds. */
|
|
84
|
+
durationNs: bigint;
|
|
85
|
+
tag?: string;
|
|
86
|
+
reason: ExitReason;
|
|
87
|
+
/** Worst the price went against this position before it closed. */
|
|
88
|
+
maeAbs: number;
|
|
89
|
+
/** Best the price went in favour of it. */
|
|
90
|
+
mfeAbs: number;
|
|
91
|
+
/** The same two, in account currency. */
|
|
92
|
+
maePnl: number;
|
|
93
|
+
mfePnl: number;
|
|
94
|
+
/** When the best and worst excursions happened. */
|
|
95
|
+
mfeTs: bigint;
|
|
96
|
+
maeTs: bigint;
|
|
97
|
+
/** Bars from entry to the best excursion. Tells you if you exit too late. */
|
|
98
|
+
barsToMfe: number;
|
|
99
|
+
/** |entry - stop| per unit at entry. */
|
|
100
|
+
initialRiskPerUnit?: number;
|
|
101
|
+
/** Total risk taken: initialRiskPerUnit x qty x contractSize. */
|
|
102
|
+
initialRiskTotal?: number;
|
|
103
|
+
/** Result in R. */
|
|
104
|
+
realizedR?: number;
|
|
105
|
+
/** Best and worst R reached while open. */
|
|
106
|
+
maxFavorableR?: number;
|
|
107
|
+
maxAdverseR?: number;
|
|
108
|
+
/**
|
|
109
|
+
* How much of the favourable excursion the exit actually captured, 0..1.
|
|
110
|
+
* A strategy averaging 0.2 here is right about direction and wrong about
|
|
111
|
+
* exits, which is a different problem from one that is simply wrong.
|
|
112
|
+
*/
|
|
113
|
+
efficiency?: number;
|
|
114
|
+
/** Peak-to-exit give-back within this position, in account currency. */
|
|
115
|
+
maxDrawdownAbs: number;
|
|
116
|
+
maxDrawdownPct: number;
|
|
117
|
+
/** The protective levels this position opened with, if any. */
|
|
118
|
+
sl?: number;
|
|
119
|
+
tp?: number;
|
|
120
|
+
}
|
|
121
|
+
export interface StrategyEquityPoint {
|
|
122
|
+
ts: bigint;
|
|
123
|
+
/** Mark-to-market account value at this bar's close. */
|
|
124
|
+
equity: number;
|
|
125
|
+
/** Fraction below the running peak, 0 at a new high. */
|
|
126
|
+
drawdown: number;
|
|
127
|
+
}
|
|
128
|
+
export interface StrategyStats {
|
|
129
|
+
netPnl: number;
|
|
130
|
+
grossProfit: number;
|
|
131
|
+
grossLoss: number;
|
|
132
|
+
/** grossProfit / |grossLoss|. Infinity with no losers, 0 with no winners. */
|
|
133
|
+
profitFactor: number;
|
|
134
|
+
totalTrades: number;
|
|
135
|
+
wins: number;
|
|
136
|
+
losses: number;
|
|
137
|
+
/** Fraction, not percent. */
|
|
138
|
+
winRate: number;
|
|
139
|
+
avgWin: number;
|
|
140
|
+
avgLoss: number;
|
|
141
|
+
/** Expected P&L per trade. */
|
|
142
|
+
expectancy: number;
|
|
143
|
+
maxDrawdown: number;
|
|
144
|
+
maxDrawdownPct: number;
|
|
145
|
+
/** Annualised, from per-bar returns and the bar period. 0 without one. */
|
|
146
|
+
sharpe: number;
|
|
147
|
+
returnPct: number;
|
|
148
|
+
/** Fraction of bars holding a position. */
|
|
149
|
+
exposure: number;
|
|
150
|
+
finalEquity: number;
|
|
151
|
+
largestWin: number;
|
|
152
|
+
largestLoss: number;
|
|
153
|
+
/** Median trade P&L. The mean is what one outlier moves; this is not. */
|
|
154
|
+
medianPnl: number;
|
|
155
|
+
/** avgWin / |avgLoss|. How much bigger a winner is than a loser. */
|
|
156
|
+
payoffRatio: number;
|
|
157
|
+
/** Trades that closed exactly flat, counted separately from wins and losses. */
|
|
158
|
+
breakEvens: number;
|
|
159
|
+
maxWinStreak: number;
|
|
160
|
+
maxLossStreak: number;
|
|
161
|
+
/** Signed: positive means the run ended on N wins, negative on N losses. */
|
|
162
|
+
currentStreak: number;
|
|
163
|
+
avgBarsHeld: number;
|
|
164
|
+
avgWinBarsHeld: number;
|
|
165
|
+
avgLossBarsHeld: number;
|
|
166
|
+
/** Total bars in the run, for anything wanting to re-derive a rate. */
|
|
167
|
+
totalBars: number;
|
|
168
|
+
/** Like Sharpe but only downside deviation is punished. */
|
|
169
|
+
sortino: number;
|
|
170
|
+
/** Annualised return over max drawdown. */
|
|
171
|
+
calmar: number;
|
|
172
|
+
/** Net profit over max drawdown - how much the strategy makes per unit of pain. */
|
|
173
|
+
recoveryFactor: number;
|
|
174
|
+
/** RMS of the drawdown series. Punishes long shallow drawdowns Sharpe ignores. */
|
|
175
|
+
ulcerIndex: number;
|
|
176
|
+
/** System Quality Number: sqrt(n) x mean / stdev of trade P&L. */
|
|
177
|
+
sqn: number;
|
|
178
|
+
/** Kelly fraction from win rate and payoff. Negative means no edge. */
|
|
179
|
+
kelly: number;
|
|
180
|
+
/** Compound annual growth rate, from the run's wall-clock span. */
|
|
181
|
+
cagr: number;
|
|
182
|
+
/** How many trades carried a stop, and so have an R at all. */
|
|
183
|
+
tradesWithRisk: number;
|
|
184
|
+
avgR: number;
|
|
185
|
+
/** Sum of R. The expectancy curve most people actually track. */
|
|
186
|
+
totalR: number;
|
|
187
|
+
/** Mean of realizedR / maxFavorableR - how much of the move exits capture. */
|
|
188
|
+
avgEfficiency: number;
|
|
189
|
+
avgMae: number;
|
|
190
|
+
avgMfe: number;
|
|
191
|
+
longTrades: number;
|
|
192
|
+
shortTrades: number;
|
|
193
|
+
longWinRate: number;
|
|
194
|
+
shortWinRate: number;
|
|
195
|
+
longPnl: number;
|
|
196
|
+
shortPnl: number;
|
|
197
|
+
totalFees: number;
|
|
198
|
+
totalCommission: number;
|
|
199
|
+
/**
|
|
200
|
+
* What slippage cost, in account currency.
|
|
201
|
+
*
|
|
202
|
+
* Not a separate deduction - slippage is already inside every fill price and
|
|
203
|
+
* so already inside netPnl. This reports what that was worth, so a strategy
|
|
204
|
+
* that only loses to slippage is distinguishable from one that is simply
|
|
205
|
+
* wrong. Subtracting it again would double-count it.
|
|
206
|
+
*/
|
|
207
|
+
totalSlippage: number;
|
|
208
|
+
/** What the run would have made with no commission at all. */
|
|
209
|
+
grossPnlBeforeCosts: number;
|
|
210
|
+
}
|
|
211
|
+
export interface StrategyConfig {
|
|
212
|
+
initialCapital: number;
|
|
213
|
+
/** Charged per contract per side. */
|
|
214
|
+
commission: number;
|
|
215
|
+
/** Applied against the trader, in ticks, on market and stop fills. */
|
|
216
|
+
slippageTicks: number;
|
|
217
|
+
tickSize: number;
|
|
218
|
+
/** Account-currency value of one point of price movement, per contract. */
|
|
219
|
+
contractSize: number;
|
|
220
|
+
/** How many entries may stack in the same direction. */
|
|
221
|
+
pyramiding: number;
|
|
222
|
+
/**
|
|
223
|
+
* Whether an opposite-side order flips an open position or merely closes it.
|
|
224
|
+
*
|
|
225
|
+
* True matches how most scripts read: sell() while long means "get out and go
|
|
226
|
+
* short". False means sell() can only ever flatten, and opening the other way
|
|
227
|
+
* takes a second, separate order.
|
|
228
|
+
*/
|
|
229
|
+
allowReverse: boolean;
|
|
230
|
+
/**
|
|
231
|
+
* Smallest tradable quantity increment. Order sizes floor to it.
|
|
232
|
+
*
|
|
233
|
+
* 1 for a listed future, where a third of a contract does not exist; spot
|
|
234
|
+
* venues are the other case entirely, so this comes from the instrument
|
|
235
|
+
* rather than being assumed.
|
|
236
|
+
*/
|
|
237
|
+
qtyStep: number;
|
|
238
|
+
}
|
|
239
|
+
export declare const DEFAULT_STRATEGY_CONFIG: StrategyConfig;
|
|
240
|
+
export interface StrategyResult {
|
|
241
|
+
trades: StrategyTrade[];
|
|
242
|
+
equity: StrategyEquityPoint[];
|
|
243
|
+
stats: StrategyStats;
|
|
244
|
+
position: StrategyPosition | null;
|
|
245
|
+
openOrders: StrategyOrder[];
|
|
246
|
+
}
|
|
247
|
+
/** One OHLCV bar, structurally what the stdlib and the data engine already use. */
|
|
248
|
+
export interface StrategyBar {
|
|
249
|
+
ts: bigint;
|
|
250
|
+
open: number;
|
|
251
|
+
high: number;
|
|
252
|
+
low: number;
|
|
253
|
+
close: number;
|
|
254
|
+
volume: number;
|
|
255
|
+
}
|
|
256
|
+
export interface OrderOpts {
|
|
257
|
+
/** Place a limit instead of a market order. */
|
|
258
|
+
limit?: number;
|
|
259
|
+
/** Place a stop instead of a market order. */
|
|
260
|
+
stop?: number;
|
|
261
|
+
/** Protective stop for the resulting position, as an absolute price. */
|
|
262
|
+
sl?: number;
|
|
263
|
+
/** Profit target for the resulting position, as an absolute price. */
|
|
264
|
+
tp?: number;
|
|
265
|
+
/** Free label, carried onto the trade and shown on the chart marker. */
|
|
266
|
+
tag?: string;
|
|
267
|
+
}
|
|
268
|
+
/** What a strategy script sees as `broker`. */
|
|
269
|
+
export interface BrokerApi {
|
|
270
|
+
buy(qty?: number, opts?: OrderOpts): number;
|
|
271
|
+
sell(qty?: number, opts?: OrderOpts): number;
|
|
272
|
+
/** Flatten at the next bar's open. No-op when already flat. */
|
|
273
|
+
close(): void;
|
|
274
|
+
cancelAll(): void;
|
|
275
|
+
readonly position: Readonly<StrategyPosition> | null;
|
|
276
|
+
readonly equity: number;
|
|
277
|
+
readonly cash: number;
|
|
278
|
+
readonly openOrders: readonly StrategyOrder[];
|
|
279
|
+
}
|
|
280
|
+
export declare class StrategyEngine {
|
|
281
|
+
private cfg;
|
|
282
|
+
private pending;
|
|
283
|
+
private pos;
|
|
284
|
+
private trades;
|
|
285
|
+
private equityCurve;
|
|
286
|
+
private realized;
|
|
287
|
+
private peak;
|
|
288
|
+
private nextOrderId;
|
|
289
|
+
private index;
|
|
290
|
+
private barsInPosition;
|
|
291
|
+
private barCount;
|
|
292
|
+
private markPrice;
|
|
293
|
+
private closeRequested;
|
|
294
|
+
private barNs;
|
|
295
|
+
private slippagePaid;
|
|
296
|
+
constructor(cfg?: Partial<StrategyConfig>);
|
|
297
|
+
setBarNs(ns: bigint): void;
|
|
298
|
+
beginBar(bar: StrategyBar, index: number): void;
|
|
299
|
+
endBar(bar: StrategyBar): void;
|
|
300
|
+
finish(lastBar: StrategyBar | undefined): void;
|
|
301
|
+
get equity(): number;
|
|
302
|
+
get result(): StrategyResult;
|
|
303
|
+
api(): BrokerApi;
|
|
304
|
+
private place;
|
|
305
|
+
private processPending;
|
|
306
|
+
private fillPrice;
|
|
307
|
+
private execute;
|
|
308
|
+
private maxEntries;
|
|
309
|
+
private roundQty;
|
|
310
|
+
private enter;
|
|
311
|
+
private slippageCost;
|
|
312
|
+
private exit;
|
|
313
|
+
private checkBrackets;
|
|
314
|
+
private trackExcursion;
|
|
315
|
+
private commissionFor;
|
|
316
|
+
private unrealized;
|
|
317
|
+
private computeStats;
|
|
318
|
+
private runYears;
|
|
319
|
+
private cagr;
|
|
320
|
+
private sortino;
|
|
321
|
+
private barReturns;
|
|
322
|
+
private peakAt;
|
|
323
|
+
private barsInPositionTotal;
|
|
324
|
+
private sharpe;
|
|
325
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import type { StrategyStats } from './strategy-runtime';
|
|
2
|
+
/**
|
|
3
|
+
* One axis of a sweep: a parameter and the values to try for it.
|
|
4
|
+
*
|
|
5
|
+
* Explicit `values` for anything discrete (a boolean, a select, a handful of
|
|
6
|
+
* lengths worth testing). `from`/`to`/`step` for a numeric range, which is what
|
|
7
|
+
* a ParamDef with min/max/step already describes - so the UI can offer a sweep
|
|
8
|
+
* over a parameter without the author declaring anything extra.
|
|
9
|
+
*/
|
|
10
|
+
export type SweepAxis = {
|
|
11
|
+
param: string;
|
|
12
|
+
values: unknown[];
|
|
13
|
+
} | {
|
|
14
|
+
param: string;
|
|
15
|
+
from: number;
|
|
16
|
+
to: number;
|
|
17
|
+
step: number;
|
|
18
|
+
};
|
|
19
|
+
export interface SweepSpec {
|
|
20
|
+
axes: SweepAxis[];
|
|
21
|
+
/**
|
|
22
|
+
* Fraction of the run held back from the end as out-of-sample, 0 to 0.9.
|
|
23
|
+
*
|
|
24
|
+
* Optional but strongly encouraged, and the reason it is here rather than in
|
|
25
|
+
* some later "advanced" feature: a sweep is a machine for overfitting. Report
|
|
26
|
+
* only the best in-sample result and you have found the parameters that best
|
|
27
|
+
* describe noise you already have. The out-of-sample column is what tells you
|
|
28
|
+
* whether you found anything at all.
|
|
29
|
+
*/
|
|
30
|
+
oosFraction?: number;
|
|
31
|
+
}
|
|
32
|
+
/** What one point of the grid produced. */
|
|
33
|
+
export interface SweepResult {
|
|
34
|
+
/** The parameter values this run used - only the swept ones. */
|
|
35
|
+
params: Record<string, unknown>;
|
|
36
|
+
/** Over the whole window. */
|
|
37
|
+
stats: StrategyStats;
|
|
38
|
+
/** The leading portion, when a split was requested. */
|
|
39
|
+
inSample?: StrategyStats;
|
|
40
|
+
/** The held-back tail. The number that actually means something. */
|
|
41
|
+
outOfSample?: StrategyStats;
|
|
42
|
+
}
|
|
43
|
+
export declare const MAX_SWEEP_BAR_ITERATIONS = 200000000;
|
|
44
|
+
export declare const MAX_SWEEP_COMBOS = 20000;
|
|
45
|
+
export declare function axisValues(axis: SweepAxis): unknown[];
|
|
46
|
+
/**
|
|
47
|
+
* The cartesian product of every axis, as parameter patches.
|
|
48
|
+
*
|
|
49
|
+
* Ordered so the first axis varies slowest. That makes the result array read as
|
|
50
|
+
* rows of the first parameter when laid out as a grid, which is what a heatmap
|
|
51
|
+
* wants without having to sort anything.
|
|
52
|
+
*/
|
|
53
|
+
export declare function expandGrid(axes: SweepAxis[]): Array<Record<string, unknown>>;
|
|
54
|
+
export interface SweepBudget {
|
|
55
|
+
combos: number;
|
|
56
|
+
bars: number;
|
|
57
|
+
iterations: number;
|
|
58
|
+
ok: boolean;
|
|
59
|
+
/** Populated when ok is false. Written for a user, not a log. */
|
|
60
|
+
reason?: string;
|
|
61
|
+
}
|
|
62
|
+
/** Whether a sweep is worth attempting here, and what to say when it is not. */
|
|
63
|
+
export declare function checkSweepBudget(combos: number, bars: number): SweepBudget;
|
|
64
|
+
/**
|
|
65
|
+
* Where to cut a run into in-sample and out-of-sample halves.
|
|
66
|
+
*
|
|
67
|
+
* The tail is held back, never the head: the point is to test on data that comes
|
|
68
|
+
* *after* what the parameters were chosen on, because that is the only ordering
|
|
69
|
+
* that resembles trading them.
|
|
70
|
+
*
|
|
71
|
+
* Returns null when there is no meaningful split - too few bars, or a fraction
|
|
72
|
+
* that would leave one side empty. A missing OOS column is honest; a two-bar one
|
|
73
|
+
* is worse than none.
|
|
74
|
+
*/
|
|
75
|
+
export declare function splitIndex(barCount: number, oosFraction: number | undefined): number | null;
|