@christtrade/depth 0.12.24 → 0.12.26

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.
@@ -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
  };
@@ -88,7 +95,7 @@ export interface ChartEvents {
88
95
  'timeframe:change': {
89
96
  tf: Timeframe;
90
97
  };
91
- /** Runtime entitlement change (see `chart.allowTimeframes`). The UI re-reads
98
+ /** Runtime entitlement change. The UI re-reads
92
99
  * `features` so locks and gates reflect what is allowed now. */
93
100
  'features:change': void;
94
101
  'timeframe:add-failed': {
@@ -542,6 +549,184 @@ 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
+ /** Bars this run actually covered - not always what was asked for. `clipped` is false for a run over everything loaded. */
569
+ range: {
570
+ fromNs: bigint | null;
571
+ toNs: bigint | null;
572
+ bars: number;
573
+ totalBars: number;
574
+ clipped: boolean;
575
+ /** Span the loaded data covers, so a picker can bound itself. */
576
+ dataFromNs: bigint | null;
577
+ dataToNs: bigint | null;
578
+ };
579
+ };
580
+ 'plugin:apply-params': {
581
+ id: string;
582
+ params: Record<string, unknown>;
583
+ };
584
+ 'plugin:strategy-range': {
585
+ id: string;
586
+ range: {
587
+ fromNs?: bigint;
588
+ toNs?: bigint;
589
+ } | null;
590
+ /**
591
+ * Fetch the span instead of clipping to what the chart holds. Opt-in -
592
+ * clipping is free and covers almost every range, fetching walks the
593
+ * network a chunk at a time. Requires `fromNs`; no streaming backwards
594
+ * from an open-ended start.
595
+ */
596
+ fetch?: boolean;
597
+ };
598
+ 'plugin:strategy-run': {
599
+ id: string;
600
+ /** `null` clears any bound and runs over everything loaded; omitted keeps the stored bound. */
601
+ range?: {
602
+ fromNs?: bigint;
603
+ toNs?: bigint;
604
+ } | null;
605
+ /** As on 'plugin:strategy-range' - go get the span, don't clip. */
606
+ fetch?: boolean;
607
+ };
608
+ 'plugin:strategy-mode': {
609
+ id: string;
610
+ manual: boolean;
611
+ };
612
+ 'plugin:strategy-stale': {
613
+ id: string;
614
+ name: string;
615
+ /** Bars arrived since the last run, or 0. */
616
+ newBars: number;
617
+ /** Params edited since the last run. Empty when none. */
618
+ params: Record<string, unknown>;
619
+ };
620
+ 'plugin:strategy-progress': {
621
+ id: string;
622
+ name: string;
623
+ phase: 'fetching' | 'running' | 'analysing' | 'done' | 'failed';
624
+ done: number;
625
+ total: number;
626
+ error?: string;
627
+ };
628
+ 'plugin:strategy-sweep': {
629
+ id: string;
630
+ grid: Array<Record<string, unknown>>;
631
+ /** Fraction held back from the end as out-of-sample. */
632
+ oosFraction?: number;
633
+ /**
634
+ * What each grid point patches over. Pass what the settings dialog
635
+ * currently holds, so parameters that are not being swept keep the value
636
+ * the user set. Omitted falls back to the script's declared defaults.
637
+ */
638
+ params?: Record<string, unknown>;
639
+ };
640
+ 'plugin:strategy-sweep-cancel': {
641
+ id: string;
642
+ };
643
+ 'plugin:strategy-sweep-progress': {
644
+ id: string;
645
+ done: number;
646
+ total: number;
647
+ };
648
+ 'plugin:strategy-sweep-done': {
649
+ id: string;
650
+ /** One entry per grid point, in grid order. `error` instead of `stats`
651
+ * for a combination that threw. */
652
+ results: Array<{
653
+ params: Record<string, unknown>;
654
+ stats?: StrategyStats;
655
+ inSample?: StrategyStats;
656
+ outOfSample?: StrategyStats;
657
+ error?: string;
658
+ }>;
659
+ };
660
+ 'plugin:strategy-sweep-cancelled': {
661
+ id: string;
662
+ done: number;
663
+ };
664
+ 'plugin:strategy-sweep-rejected': {
665
+ id: string;
666
+ name: string;
667
+ combos: number;
668
+ bars: number;
669
+ iterations: number;
670
+ reason: string;
671
+ };
672
+ 'plugin:strategy-walkforward': {
673
+ id: string;
674
+ grid: Array<Record<string, unknown>>;
675
+ params?: Record<string, unknown>;
676
+ /** Out-of-sample segments to test. Default 4. */
677
+ windows?: number;
678
+ /** In-sample length as a multiple of out-of-sample. Default 3. */
679
+ isMultiple?: number;
680
+ /** Fixed in-sample start rather than a sliding window. */
681
+ anchored?: boolean;
682
+ /** Which statistic the optimiser maximises. Default 'netPnl'. */
683
+ objective?: string;
684
+ /** False for drawdown-like objectives. */
685
+ higherIsBetter?: boolean;
686
+ };
687
+ 'plugin:strategy-walkforward-cancel': {
688
+ id: string;
689
+ };
690
+ 'plugin:strategy-walkforward-progress': {
691
+ id: string;
692
+ done: number;
693
+ total: number;
694
+ };
695
+ 'plugin:strategy-walkforward-done': {
696
+ id: string;
697
+ results: Array<{
698
+ window: {
699
+ index: number;
700
+ isFrom: number;
701
+ isTo: number;
702
+ oosFrom: number;
703
+ oosTo: number;
704
+ };
705
+ params?: Record<string, unknown>;
706
+ inSample?: StrategyStats;
707
+ outOfSample?: StrategyStats;
708
+ /** The test segment's own curve. Stitched across windows, this is the
709
+ * equity of parameters never fitted to the data they run over. */
710
+ outOfSampleEquity?: StrategyEquityPoint[];
711
+ error?: string;
712
+ }>;
713
+ };
714
+ 'plugin:strategy-walkforward-cancelled': {
715
+ id: string;
716
+ done: number;
717
+ };
718
+ 'plugin:strategy-walkforward-rejected': {
719
+ id: string;
720
+ name: string;
721
+ reason: string;
722
+ };
723
+ 'plugin:strategy-rejected': {
724
+ id: string;
725
+ name: string;
726
+ bars: number;
727
+ maxBars: number;
728
+ reason: string;
729
+ };
545
730
  'plugin:update-code': {
546
731
  id: string;
547
732
  code: string;
@@ -25,6 +25,14 @@ 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, reconcileIntrabar } 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 { clipRange, hasRange, emptyRangeReason } from './strategy-range';
32
+ export type { StrategyRange, ClippedRange } from './strategy-range';
33
+ export { planWalkForward, walkForwardEfficiency, parameterStability, pickBest, } from './strategy-walkforward';
34
+ export type { WalkForwardSpec, WalkForwardWindow, WalkForwardWindowResult, ParameterStability, } from './strategy-walkforward';
35
+ export type { BrokerApi, StrategyEquityPoint, ExitReason, OrderOpts, Side, StrategyBar, StrategyConfig, StrategyOrder, StrategyPosition, StrategyResult, StrategyStats, StrategyTrade, } from './strategy-runtime';
28
36
  export { isCompatible, DATA_LEVEL_LABELS, incompatibleReason } from './processing/data-level';
29
37
  export { processL3Chunk, mergeL3Chunks } from './processing/l3-processor';
30
38
  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,15 @@
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, reconcileIntrabar } from './strategy-runtime';
6
+ export { axisValues, checkSweepBudget, expandGrid, splitIndex } from './strategy-sweep';
7
+ export { clipRange, hasRange, emptyRangeReason } from './strategy-range';
8
+ export type { StrategyRange, ClippedRange } from './strategy-range';
9
+ export type { SweepAxis, SweepSpec, SweepResult, SweepBudget } from './strategy-sweep';
10
+ export { planWalkForward, walkForwardEfficiency, parameterStability, pickBest, } from './strategy-walkforward';
11
+ export type { WalkForwardSpec, WalkForwardWindow, WalkForwardWindowResult, ParameterStability, } from './strategy-walkforward';
12
+ export type { BrokerApi, StrategyEquityPoint, OrderOpts, Side, StrategyBar, StrategyConfig, StrategyOrder, StrategyPosition, StrategyResult, StrategyStats, StrategyTrade, } from './strategy-runtime';
5
13
  export interface ScriptScopeOptions {
6
14
  /** What the script's `plugin({...})` call resolves to. */
7
15
  plugin: (decl: unknown) => unknown;
@@ -38,6 +46,7 @@ export declare function buildScriptScope(opts: ScriptScopeOptions): Record<strin
38
46
  * script makes while evaluating.
39
47
  */
40
48
  export declare function evalScriptInScope(src: string, opts: ScriptScopeOptions): void;
49
+ export declare function defaultStrategyDraw(state: unknown, layout: 'overlay' | 'pane'): unknown[];
41
50
  /**
42
51
  * Identifies the stdlib surface this build exposes, for a client and a server
43
52
  * runner to agree they are talking about the same one.
@@ -0,0 +1,41 @@
1
+ import type { StrategyBar } from './strategy-runtime';
2
+ /** Inclusive both ends, nanoseconds. An omitted end means "as far as the data goes". */
3
+ export interface StrategyRange {
4
+ fromNs?: bigint;
5
+ toNs?: bigint;
6
+ }
7
+ /** Where a range lands in a bar array, plus what it cost. */
8
+ export interface ClippedRange {
9
+ /** First bar in range. */
10
+ from: number;
11
+ /** One past the last bar in range, so `bars.slice(from, to)` is the run. */
12
+ to: number;
13
+ /** How many bars the run will see. */
14
+ count: number;
15
+ /** How many bars were available before clipping. */
16
+ total: number;
17
+ /** Timestamps of the first and last bar actually in range. */
18
+ firstTs: bigint | null;
19
+ lastTs: bigint | null;
20
+ /** Span the whole data set covers, ignoring the range - lets a picker bound itself to what exists. */
21
+ dataFromNs: bigint | null;
22
+ dataToNs: bigint | null;
23
+ }
24
+ /**
25
+ * Resolve a range against a sorted bar array. `toNs` includes the bar that opens
26
+ * exactly on it. Binary search, not a filter - this runs once per sweep
27
+ * combination, and a linear scan over a million bars twenty thousand times
28
+ * turns a sweep into a hang.
29
+ *
30
+ * No warmup before `from`: feeding a run bars it isn't allowed to trade is how
31
+ * an out-of-sample window quietly stops being out of sample.
32
+ */
33
+ export declare function clipRange(bars: readonly StrategyBar[], range?: StrategyRange): ClippedRange;
34
+ /** True when the range asks for something, rather than being absent or empty. */
35
+ export declare function hasRange(range?: StrategyRange): boolean;
36
+ /**
37
+ * Why a clipped run has nothing to run over. Spelled out rather than an empty
38
+ * result, since a strategy with no trades and one given no bars look identical
39
+ * in a results panel, and only one of those is the user's fault.
40
+ */
41
+ export declare function emptyRangeReason(clipped: ClippedRange, range?: StrategyRange): string;
@@ -0,0 +1,365 @@
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/worst price while open, from the entry bar's full range. Exact for
32
+ * a market fill; an upper bound for a limit/stop filling mid-bar - don't
33
+ * read MAE as gospel on a one-bar trade.
34
+ */
35
+ highWatermark: number;
36
+ lowWatermark: number;
37
+ /** Bar index and timestamp of the best excursion, for efficiency analysis. */
38
+ mfeIndex: number;
39
+ mfeTs: bigint;
40
+ maeIndex: number;
41
+ maeTs: bigint;
42
+ /**
43
+ * |entry - stop| per unit, locked at entry. Undefined when the position
44
+ * opened without a stop, which makes every R-multiple on it meaningless
45
+ * rather than zero - the difference matters, so it stays undefined.
46
+ */
47
+ initialRiskPerUnit?: number;
48
+ /** Fills that have gone into this position. Checked against `pyramiding`. */
49
+ entries: number;
50
+ /**
51
+ * Commission already paid to open the quantity still held. Carried so the
52
+ * exit's trade record can report the round-trip fee - otherwise pnl
53
+ * wouldn't sum to the equity curve.
54
+ */
55
+ entryFees: number;
56
+ }
57
+ export interface StrategyTrade {
58
+ id: number;
59
+ side: Exclude<Side, 'flat'>;
60
+ qty: number;
61
+ entryTs: bigint;
62
+ entryPrice: number;
63
+ exitTs: bigint;
64
+ exitPrice: number;
65
+ /** Net of fees, in account currency. */
66
+ pnl: number;
67
+ /** Before fees. */
68
+ pnlGross: number;
69
+ /** Net return on the notional put at risk, as a fraction. */
70
+ pnlPct: number;
71
+ fees: number;
72
+ /** The fee split, because a strategy killed by commission and one killed by
73
+ * slippage need different fixes. */
74
+ commission: number;
75
+ slippage: number;
76
+ /** How many bars the position was held. */
77
+ bars: number;
78
+ /** Wall-clock time held, in nanoseconds. */
79
+ durationNs: bigint;
80
+ tag?: string;
81
+ reason: ExitReason;
82
+ /**
83
+ * Stop and target both sat inside the resolving bar, so which came first
84
+ * was assumed (stop wins) rather than observed. Intrabar data shrinks how
85
+ * often this happens but never zeroes it.
86
+ */
87
+ ambiguousExit?: boolean;
88
+ /** Worst the price went against this position before it closed. */
89
+ maeAbs: number;
90
+ /** Best the price went in favour of it. */
91
+ mfeAbs: number;
92
+ /** The same two, in account currency. */
93
+ maePnl: number;
94
+ mfePnl: number;
95
+ /** When the best and worst excursions happened. */
96
+ mfeTs: bigint;
97
+ maeTs: bigint;
98
+ /** Bars from entry to the best excursion. Tells you if you exit too late. */
99
+ barsToMfe: number;
100
+ /** |entry - stop| per unit at entry. */
101
+ initialRiskPerUnit?: number;
102
+ /** Total risk taken: initialRiskPerUnit x qty x contractSize. */
103
+ initialRiskTotal?: number;
104
+ /** Result in R. */
105
+ realizedR?: number;
106
+ /** Best and worst R reached while open. */
107
+ maxFavorableR?: number;
108
+ maxAdverseR?: number;
109
+ /**
110
+ * How much of the favourable excursion the exit actually captured, 0..1.
111
+ * A strategy averaging 0.2 here is right about direction and wrong about
112
+ * exits, which is a different problem from one that is simply wrong.
113
+ */
114
+ efficiency?: number;
115
+ /** Peak-to-exit give-back within this position, in account currency. */
116
+ maxDrawdownAbs: number;
117
+ maxDrawdownPct: number;
118
+ /** The protective levels this position opened with, if any. */
119
+ sl?: number;
120
+ tp?: number;
121
+ }
122
+ export interface StrategyEquityPoint {
123
+ ts: bigint;
124
+ /** Mark-to-market account value at this bar's close. */
125
+ equity: number;
126
+ /** Fraction below the running peak, 0 at a new high. */
127
+ drawdown: number;
128
+ }
129
+ export interface StrategyStats {
130
+ netPnl: number;
131
+ grossProfit: number;
132
+ grossLoss: number;
133
+ /** grossProfit / |grossLoss|. Infinity with no losers, 0 with no winners. */
134
+ profitFactor: number;
135
+ totalTrades: number;
136
+ wins: number;
137
+ losses: number;
138
+ /** Fraction, not percent. */
139
+ winRate: number;
140
+ avgWin: number;
141
+ avgLoss: number;
142
+ /** Expected P&L per trade. */
143
+ expectancy: number;
144
+ maxDrawdown: number;
145
+ maxDrawdownPct: number;
146
+ /** Annualised, from per-bar returns and the bar period. 0 without one. */
147
+ sharpe: number;
148
+ returnPct: number;
149
+ /** Fraction of bars holding a position. */
150
+ exposure: number;
151
+ finalEquity: number;
152
+ largestWin: number;
153
+ largestLoss: number;
154
+ /** Median trade P&L. The mean is what one outlier moves; this is not. */
155
+ medianPnl: number;
156
+ /** avgWin / |avgLoss|. How much bigger a winner is than a loser. */
157
+ payoffRatio: number;
158
+ /** Trades that closed exactly flat, counted separately from wins and losses. */
159
+ breakEvens: number;
160
+ maxWinStreak: number;
161
+ maxLossStreak: number;
162
+ /** Signed: positive means the run ended on N wins, negative on N losses. */
163
+ currentStreak: number;
164
+ avgBarsHeld: number;
165
+ avgWinBarsHeld: number;
166
+ avgLossBarsHeld: number;
167
+ /** Total bars in the run, for anything wanting to re-derive a rate. */
168
+ totalBars: number;
169
+ /** Like Sharpe but only downside deviation is punished. */
170
+ sortino: number;
171
+ /** Annualised return over max drawdown. */
172
+ calmar: number;
173
+ /** Net profit over max drawdown - how much the strategy makes per unit of pain. */
174
+ recoveryFactor: number;
175
+ /** RMS of the drawdown series. Punishes long shallow drawdowns Sharpe ignores. */
176
+ ulcerIndex: number;
177
+ /** System Quality Number: sqrt(n) x mean / stdev of trade P&L. */
178
+ sqn: number;
179
+ /** Kelly fraction from win rate and payoff. Negative means no edge. */
180
+ kelly: number;
181
+ /** Compound annual growth rate, from the run's wall-clock span. */
182
+ cagr: number;
183
+ /** How many trades carried a stop, and so have an R at all. */
184
+ tradesWithRisk: number;
185
+ avgR: number;
186
+ /** Sum of R. The expectancy curve most people actually track. */
187
+ totalR: number;
188
+ /** Mean of realizedR / maxFavorableR - how much of the move exits capture. */
189
+ avgEfficiency: number;
190
+ avgMae: number;
191
+ avgMfe: number;
192
+ longTrades: number;
193
+ shortTrades: number;
194
+ longWinRate: number;
195
+ shortWinRate: number;
196
+ longPnl: number;
197
+ shortPnl: number;
198
+ totalFees: number;
199
+ totalCommission: number;
200
+ /**
201
+ * What slippage cost, in account currency - already inside netPnl via the
202
+ * fill prices, reported here rather than deducted again.
203
+ */
204
+ totalSlippage: number;
205
+ /** What the run would have made with no commission at all. */
206
+ grossPnlBeforeCosts: number;
207
+ /** Chart bars whose fills were resolved against finer intrabar data. */
208
+ intrabarBars: number;
209
+ /**
210
+ * Bars given intrabar data that didn't reconcile with the aggregate and
211
+ * fell back to it. Non-zero means the finer feed has holes - the run is
212
+ * still valid, just not the run the intrabar toggle implies.
213
+ */
214
+ intrabarFallbacks: number;
215
+ /**
216
+ * Exits where stop and target shared a resolving bar and the stop was
217
+ * assumed first. Compare against `totalTrades` for how much of the result
218
+ * rests on that assumption.
219
+ */
220
+ ambiguousExits: number;
221
+ }
222
+ export interface StrategyConfig {
223
+ initialCapital: number;
224
+ /** Charged per contract per side. */
225
+ commission: number;
226
+ /** Applied against the trader, in ticks, on market and stop fills. */
227
+ slippageTicks: number;
228
+ tickSize: number;
229
+ /** Account-currency value of one point of price movement, per contract. */
230
+ contractSize: number;
231
+ /** How many entries may stack in the same direction. */
232
+ pyramiding: number;
233
+ /**
234
+ * Whether an opposite-side order flips a position or just closes it. True
235
+ * matches how most scripts read: sell() while long means "get out and go
236
+ * short".
237
+ */
238
+ allowReverse: boolean;
239
+ /**
240
+ * Smallest tradable quantity increment; order sizes floor to it. 1 for a
241
+ * listed future, fractional for spot - comes from the instrument, not
242
+ * assumed.
243
+ */
244
+ qtyStep: number;
245
+ }
246
+ export declare const DEFAULT_STRATEGY_CONFIG: StrategyConfig;
247
+ export interface StrategyResult {
248
+ trades: StrategyTrade[];
249
+ equity: StrategyEquityPoint[];
250
+ stats: StrategyStats;
251
+ position: StrategyPosition | null;
252
+ openOrders: StrategyOrder[];
253
+ }
254
+ /** One OHLCV bar, structurally what the stdlib and the data engine already use. */
255
+ export interface StrategyBar {
256
+ ts: bigint;
257
+ open: number;
258
+ high: number;
259
+ low: number;
260
+ close: number;
261
+ volume: number;
262
+ }
263
+ /**
264
+ * Do these finer bars actually subdivide this one?
265
+ *
266
+ * A feed with a hole in it is worse than none at all - it'd resolve a stop
267
+ * against a range missing the second price actually traded through. Rejected
268
+ * whole rather than repaired, since fixing it would mean inventing the
269
+ * ordering inside the gap.
270
+ *
271
+ * @param tolerance Half a tick - two aggregation paths over the same trades
272
+ * agree to the tick, not the float.
273
+ */
274
+ export declare function reconcileIntrabar(bar: StrategyBar, sub: readonly StrategyBar[] | undefined, tolerance: number): boolean;
275
+ export interface OrderOpts {
276
+ /** Place a limit instead of a market order. */
277
+ limit?: number;
278
+ /** Place a stop instead of a market order. */
279
+ stop?: number;
280
+ /** Protective stop for the resulting position, as an absolute price. */
281
+ sl?: number;
282
+ /** Profit target for the resulting position, as an absolute price. */
283
+ tp?: number;
284
+ /** Free label, carried onto the trade and shown on the chart marker. */
285
+ tag?: string;
286
+ }
287
+ /** What a strategy script sees as `broker`. */
288
+ export interface BrokerApi {
289
+ buy(qty?: number, opts?: OrderOpts): number;
290
+ sell(qty?: number, opts?: OrderOpts): number;
291
+ /** Flatten at the next bar's open. No-op when already flat. */
292
+ close(): void;
293
+ cancelAll(): void;
294
+ readonly position: Readonly<StrategyPosition> | null;
295
+ readonly equity: number;
296
+ readonly cash: number;
297
+ readonly openOrders: readonly StrategyOrder[];
298
+ }
299
+ export declare class StrategyEngine {
300
+ private cfg;
301
+ private pending;
302
+ private pos;
303
+ private trades;
304
+ private equityCurve;
305
+ private realized;
306
+ private peak;
307
+ private nextOrderId;
308
+ private index;
309
+ private barsInPosition;
310
+ private barCount;
311
+ private markPrice;
312
+ private closeRequested;
313
+ private barNs;
314
+ private slippagePaid;
315
+ private usedIntrabar;
316
+ private intrabarBars;
317
+ private intrabarFallbacks;
318
+ private ambiguousExits;
319
+ private pendingEquity;
320
+ private equityCount;
321
+ private firstTs;
322
+ private lastTs;
323
+ private lastEquity;
324
+ private prevEquity;
325
+ private maxDdAbs;
326
+ private maxDdPct;
327
+ private ulcerSum;
328
+ private retN;
329
+ private retMean;
330
+ private retM2;
331
+ private downSum;
332
+ private downCount;
333
+ private curvePhase;
334
+ private curveStride;
335
+ private lastFolded;
336
+ constructor(cfg?: Partial<StrategyConfig>);
337
+ setBarNs(ns: bigint): void;
338
+ beginBar(bar: StrategyBar, index: number, sub?: readonly StrategyBar[]): void;
339
+ endBar(bar: StrategyBar): void;
340
+ private foldPending;
341
+ private keepInCurve;
342
+ finish(lastBar: StrategyBar | undefined): void;
343
+ get equity(): number;
344
+ get result(): StrategyResult;
345
+ api(): BrokerApi;
346
+ private place;
347
+ private processPending;
348
+ private fillPrice;
349
+ private execute;
350
+ private maxEntries;
351
+ private roundQty;
352
+ private enter;
353
+ private slippageCost;
354
+ private exit;
355
+ private checkBrackets;
356
+ private trackExcursion;
357
+ private commissionFor;
358
+ private unrealized;
359
+ private computeStats;
360
+ private runYears;
361
+ private cagr;
362
+ private sortino;
363
+ private barsInPositionTotal;
364
+ private sharpe;
365
+ }