@christtrade/depth 0.12.25 → 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.
- package/depth.cjs +44 -44
- package/depth.css +1 -1
- package/depth.mjs +44 -44
- package/depth.nopreflight.css +1 -1
- package/package.json +1 -1
- package/script-runtime.cjs +2 -2
- package/script-runtime.mjs +2 -2
- package/types/components/indicators/indicators-settings-dialog.d.ts +7 -0
- package/types/core/DataEngine.d.ts +23 -0
- package/types/core/DepthChart.d.ts +2 -2
- package/types/core/ScriptedPlugin.d.ts +7 -0
- package/types/core/TypedEventBus.d.ts +56 -1
- package/types/core/index.d.ts +3 -1
- package/types/core/script-runtime.d.ts +3 -1
- package/types/core/strategy-range.d.ts +41 -0
- package/types/core/strategy-runtime.d.ts +70 -30
- package/types/core/strategy-stream.d.ts +40 -0
- package/types/interfaces/plugins/IChartPlugin.d.ts +12 -0
|
@@ -95,7 +95,7 @@ export interface ChartEvents {
|
|
|
95
95
|
'timeframe:change': {
|
|
96
96
|
tf: Timeframe;
|
|
97
97
|
};
|
|
98
|
-
/** Runtime entitlement change
|
|
98
|
+
/** Runtime entitlement change. The UI re-reads
|
|
99
99
|
* `features` so locks and gates reflect what is allowed now. */
|
|
100
100
|
'features:change': void;
|
|
101
101
|
'timeframe:add-failed': {
|
|
@@ -565,11 +565,66 @@ export interface ChartEvents {
|
|
|
565
565
|
* ParamDef already carries min, max and step.
|
|
566
566
|
*/
|
|
567
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
|
+
};
|
|
568
579
|
};
|
|
569
580
|
'plugin:apply-params': {
|
|
570
581
|
id: string;
|
|
571
582
|
params: Record<string, unknown>;
|
|
572
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
|
+
};
|
|
573
628
|
'plugin:strategy-sweep': {
|
|
574
629
|
id: string;
|
|
575
630
|
grid: Array<Record<string, unknown>>;
|
package/types/core/index.d.ts
CHANGED
|
@@ -25,9 +25,11 @@ 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';
|
|
28
|
+
export { StrategyEngine, DEFAULT_STRATEGY_CONFIG, reconcileIntrabar } from './strategy-runtime';
|
|
29
29
|
export { axisValues, checkSweepBudget, expandGrid, splitIndex, MAX_SWEEP_COMBOS, MAX_SWEEP_BAR_ITERATIONS, } from './strategy-sweep';
|
|
30
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';
|
|
31
33
|
export { planWalkForward, walkForwardEfficiency, parameterStability, pickBest, } from './strategy-walkforward';
|
|
32
34
|
export type { WalkForwardSpec, WalkForwardWindow, WalkForwardWindowResult, ParameterStability, } from './strategy-walkforward';
|
|
33
35
|
export type { BrokerApi, StrategyEquityPoint, ExitReason, OrderOpts, Side, StrategyBar, StrategyConfig, StrategyOrder, StrategyPosition, StrategyResult, StrategyStats, StrategyTrade, } from './strategy-runtime';
|
|
@@ -2,8 +2,10 @@ import { STDLIB } from '../lib/indicator-stdlib';
|
|
|
2
2
|
import { PluginType, DataLevel, Layout, ExitReason, SCRIPT_DSL } from './script-dsl';
|
|
3
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';
|
|
5
|
+
export { StrategyEngine, DEFAULT_STRATEGY_CONFIG, reconcileIntrabar } from './strategy-runtime';
|
|
6
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';
|
|
7
9
|
export type { SweepAxis, SweepSpec, SweepResult, SweepBudget } from './strategy-sweep';
|
|
8
10
|
export { planWalkForward, walkForwardEfficiency, parameterStability, pickBest, } from './strategy-walkforward';
|
|
9
11
|
export type { WalkForwardSpec, WalkForwardWindow, WalkForwardWindowResult, ParameterStability, } from './strategy-walkforward';
|
|
@@ -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;
|
|
@@ -28,13 +28,9 @@ export interface StrategyPosition {
|
|
|
28
28
|
tp?: number;
|
|
29
29
|
tag?: string;
|
|
30
30
|
/**
|
|
31
|
-
* Best
|
|
32
|
-
*
|
|
33
|
-
*
|
|
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.
|
|
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.
|
|
38
34
|
*/
|
|
39
35
|
highWatermark: number;
|
|
40
36
|
lowWatermark: number;
|
|
@@ -52,10 +48,9 @@ export interface StrategyPosition {
|
|
|
52
48
|
/** Fills that have gone into this position. Checked against `pyramiding`. */
|
|
53
49
|
entries: number;
|
|
54
50
|
/**
|
|
55
|
-
* Commission already paid to open the quantity still held. Carried
|
|
56
|
-
*
|
|
57
|
-
*
|
|
58
|
-
* omitted the entry side would not sum to the equity curve.
|
|
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.
|
|
59
54
|
*/
|
|
60
55
|
entryFees: number;
|
|
61
56
|
}
|
|
@@ -84,6 +79,12 @@ export interface StrategyTrade {
|
|
|
84
79
|
durationNs: bigint;
|
|
85
80
|
tag?: string;
|
|
86
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;
|
|
87
88
|
/** Worst the price went against this position before it closed. */
|
|
88
89
|
maeAbs: number;
|
|
89
90
|
/** Best the price went in favour of it. */
|
|
@@ -197,16 +198,26 @@ export interface StrategyStats {
|
|
|
197
198
|
totalFees: number;
|
|
198
199
|
totalCommission: number;
|
|
199
200
|
/**
|
|
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.
|
|
201
|
+
* What slippage cost, in account currency - already inside netPnl via the
|
|
202
|
+
* fill prices, reported here rather than deducted again.
|
|
206
203
|
*/
|
|
207
204
|
totalSlippage: number;
|
|
208
205
|
/** What the run would have made with no commission at all. */
|
|
209
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;
|
|
210
221
|
}
|
|
211
222
|
export interface StrategyConfig {
|
|
212
223
|
initialCapital: number;
|
|
@@ -220,19 +231,15 @@ export interface StrategyConfig {
|
|
|
220
231
|
/** How many entries may stack in the same direction. */
|
|
221
232
|
pyramiding: number;
|
|
222
233
|
/**
|
|
223
|
-
* Whether an opposite-side order flips
|
|
224
|
-
*
|
|
225
|
-
*
|
|
226
|
-
* short". False means sell() can only ever flatten, and opening the other way
|
|
227
|
-
* takes a second, separate order.
|
|
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".
|
|
228
237
|
*/
|
|
229
238
|
allowReverse: boolean;
|
|
230
239
|
/**
|
|
231
|
-
* Smallest tradable quantity increment
|
|
232
|
-
*
|
|
233
|
-
*
|
|
234
|
-
* venues are the other case entirely, so this comes from the instrument
|
|
235
|
-
* rather than being assumed.
|
|
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.
|
|
236
243
|
*/
|
|
237
244
|
qtyStep: number;
|
|
238
245
|
}
|
|
@@ -253,6 +260,18 @@ export interface StrategyBar {
|
|
|
253
260
|
close: number;
|
|
254
261
|
volume: number;
|
|
255
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;
|
|
256
275
|
export interface OrderOpts {
|
|
257
276
|
/** Place a limit instead of a market order. */
|
|
258
277
|
limit?: number;
|
|
@@ -293,10 +312,33 @@ export declare class StrategyEngine {
|
|
|
293
312
|
private closeRequested;
|
|
294
313
|
private barNs;
|
|
295
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;
|
|
296
336
|
constructor(cfg?: Partial<StrategyConfig>);
|
|
297
337
|
setBarNs(ns: bigint): void;
|
|
298
|
-
beginBar(bar: StrategyBar, index: number): void;
|
|
338
|
+
beginBar(bar: StrategyBar, index: number, sub?: readonly StrategyBar[]): void;
|
|
299
339
|
endBar(bar: StrategyBar): void;
|
|
340
|
+
private foldPending;
|
|
341
|
+
private keepInCurve;
|
|
300
342
|
finish(lastBar: StrategyBar | undefined): void;
|
|
301
343
|
get equity(): number;
|
|
302
344
|
get result(): StrategyResult;
|
|
@@ -318,8 +360,6 @@ export declare class StrategyEngine {
|
|
|
318
360
|
private runYears;
|
|
319
361
|
private cagr;
|
|
320
362
|
private sortino;
|
|
321
|
-
private barReturns;
|
|
322
|
-
private peakAt;
|
|
323
363
|
private barsInPositionTotal;
|
|
324
364
|
private sharpe;
|
|
325
365
|
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
export type ChunkPlan = {
|
|
2
|
+
fromNs: bigint;
|
|
3
|
+
toNs: bigint;
|
|
4
|
+
};
|
|
5
|
+
/** `coveredTo`: how far the source actually got - adapters cap responses and report the cap here, not as an error. */
|
|
6
|
+
export type RangeFetchResult<B> = {
|
|
7
|
+
bars: B[];
|
|
8
|
+
coveredTo: bigint | null;
|
|
9
|
+
};
|
|
10
|
+
export type RangeFetch<B> = (opts: {
|
|
11
|
+
fromNs: bigint;
|
|
12
|
+
toNs: bigint;
|
|
13
|
+
barNs: bigint;
|
|
14
|
+
}) => Promise<RangeFetchResult<B>>;
|
|
15
|
+
export declare const DEFAULT_STREAM_DEPTH = 4;
|
|
16
|
+
export declare const DEFAULT_MAX_CONTINUATIONS = 16;
|
|
17
|
+
/**
|
|
18
|
+
* Chunk boundaries decided up front, not lazily - a request has to fire before
|
|
19
|
+
* its predecessor answers. `chunkBars` is bars in a chunk, not the width: a
|
|
20
|
+
* hundred-bar chunk spans ninety-nine periods, ends inclusive.
|
|
21
|
+
*/
|
|
22
|
+
export declare function planChunks(fromNs: bigint, toNs: bigint, barNs: bigint, chunkBars: bigint, maxChunks: number): ChunkPlan[];
|
|
23
|
+
export type StreamOptions<B> = {
|
|
24
|
+
plan: readonly ChunkPlan[];
|
|
25
|
+
barNs: bigint;
|
|
26
|
+
fetch: RangeFetch<B>;
|
|
27
|
+
/** Pieces, not whole chunks - a two-request chunk arrives as two calls rather than paying to join them. */
|
|
28
|
+
deliver: (bars: B[], chunkIndex: number) => void;
|
|
29
|
+
/** False once superseded - checked after every await so a stale walk's late chunks don't reach the new run. */
|
|
30
|
+
isAlive?: () => boolean;
|
|
31
|
+
onProgress?: (delivered: number, total: number) => void;
|
|
32
|
+
depth?: number;
|
|
33
|
+
maxContinuations?: number;
|
|
34
|
+
};
|
|
35
|
+
/**
|
|
36
|
+
* Rejects with the first failure *in plan order*, not time order - otherwise a
|
|
37
|
+
* later failure could skip the bars an earlier, still-outstanding chunk owns,
|
|
38
|
+
* and a run that quietly drops a year still produces a plausible equity curve.
|
|
39
|
+
*/
|
|
40
|
+
export declare function streamRangeChunks<B>(opts: StreamOptions<B>): Promise<number>;
|
|
@@ -163,6 +163,18 @@ export interface PluginContext {
|
|
|
163
163
|
dataLevel: DataLevel;
|
|
164
164
|
horizon: () => number;
|
|
165
165
|
getData(): Readonly<PluginDataSnapshot>;
|
|
166
|
+
fetchRange(opts: {
|
|
167
|
+
/** Defaults to the focused symbol. */
|
|
168
|
+
symbol?: string;
|
|
169
|
+
fromNs: bigint;
|
|
170
|
+
toNs: bigint;
|
|
171
|
+
/** Bar period to fetch at. Defaults to the chart's current timeframe. */
|
|
172
|
+
barNs?: bigint;
|
|
173
|
+
}): Promise<{
|
|
174
|
+
bars: OhlcvBar[];
|
|
175
|
+
hasMore: boolean;
|
|
176
|
+
coveredTo: bigint | null;
|
|
177
|
+
}>;
|
|
166
178
|
/**
|
|
167
179
|
* The bar still being built at the playback horizon, or `null` before any
|
|
168
180
|
* data has arrived.
|