@hoox-sh/pynets 0.5.0 → 0.6.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.
- package/CHANGELOG.md +21 -0
- package/README.md +6 -4
- package/dist/browser.js +68 -68
- package/dist/index.js +1719 -197
- package/package.json +1 -1
- package/src/cli/rich.ts +1 -1
- package/src/generated/PinescriptLexerBase.ts +22 -4
- package/src/parser/LexerBase.ts +22 -4
- package/src/runtime/compile/draw.ts +26 -2
- package/src/runtime/compile/emit_call.ts +324 -6
- package/src/runtime/compile/helpers.ts +2 -1
- package/src/runtime/compile/runtime.ts +20 -5
- package/src/runtime/compile/strategy.ts +5 -2
- package/src/runtime/interpret.ts +447 -28
- package/src/runtime/str.ts +3 -2
- package/src/runtime/strategy.ts +164 -12
- package/src/runtime/ta.ts +1090 -149
- package/src/runtime/time.ts +12 -0
package/package.json
CHANGED
package/src/cli/rich.ts
CHANGED
|
@@ -73,6 +73,10 @@ export default class PinescriptLexerBase extends Lexer {
|
|
|
73
73
|
private _lastPendingTokenType = 0;
|
|
74
74
|
private _lastPendingTokenTypeFromDefaultChannel = 0;
|
|
75
75
|
private _numOpens = 0;
|
|
76
|
+
// Open square brackets (history-refs / tuples). Tracked separately from
|
|
77
|
+
// parens so wrapped `[...]` allows any indentation, like `(...)`.
|
|
78
|
+
// Python `PinescriptLexerBase._numSqBrackets` SoT (Sept 2026 release notes).
|
|
79
|
+
private _numSqBrackets = 0;
|
|
76
80
|
private _indentLengthStack: number[] = [];
|
|
77
81
|
private _inputStarted = false;
|
|
78
82
|
|
|
@@ -129,6 +133,7 @@ export default class PinescriptLexerBase extends Lexer {
|
|
|
129
133
|
this._lastPendingTokenType = 0;
|
|
130
134
|
this._lastPendingTokenTypeFromDefaultChannel = 0;
|
|
131
135
|
this._numOpens = 0;
|
|
136
|
+
this._numSqBrackets = 0;
|
|
132
137
|
this._indentLengthStack = [];
|
|
133
138
|
this._inputStarted = false;
|
|
134
139
|
}
|
|
@@ -147,11 +152,17 @@ export default class PinescriptLexerBase extends Lexer {
|
|
|
147
152
|
const tok = this._currentToken!;
|
|
148
153
|
const tokType = tok.type;
|
|
149
154
|
const C = this.C;
|
|
150
|
-
if (tokType === C.LPAR
|
|
155
|
+
if (tokType === C.LPAR) {
|
|
151
156
|
this._numOpens += 1;
|
|
152
157
|
this._addPendingToken(tok);
|
|
153
|
-
} else if (tokType === C.
|
|
154
|
-
this.
|
|
158
|
+
} else if (tokType === C.LSQB) {
|
|
159
|
+
this._numSqBrackets += 1;
|
|
160
|
+
this._addPendingToken(tok);
|
|
161
|
+
} else if (tokType === C.RPAR) {
|
|
162
|
+
this._numOpens = Math.max(0, this._numOpens - 1);
|
|
163
|
+
this._addPendingToken(tok);
|
|
164
|
+
} else if (tokType === C.RSQB) {
|
|
165
|
+
this._numSqBrackets = Math.max(0, this._numSqBrackets - 1);
|
|
155
166
|
this._addPendingToken(tok);
|
|
156
167
|
} else if (tokType === C.NEWLINE) {
|
|
157
168
|
this._handleNEWLINEToken();
|
|
@@ -264,7 +275,14 @@ export default class PinescriptLexerBase extends Lexer {
|
|
|
264
275
|
}
|
|
265
276
|
|
|
266
277
|
private _handleNEWLINEToken(): void {
|
|
267
|
-
|
|
278
|
+
// Newlines inside (...) or [...] are hidden (Python parity): wrapped
|
|
279
|
+
// history-refs / tuples may use any indentation without emitting
|
|
280
|
+
// INDENT/DEDENT mid-expression.
|
|
281
|
+
if (
|
|
282
|
+
this._numOpens > 0 ||
|
|
283
|
+
this._numSqBrackets > 0 ||
|
|
284
|
+
this._operators.has(this._lastPendingTokenTypeFromDefaultChannel)
|
|
285
|
+
) {
|
|
268
286
|
this._hideAndAddPendingToken(this._currentToken!);
|
|
269
287
|
return;
|
|
270
288
|
}
|
package/src/parser/LexerBase.ts
CHANGED
|
@@ -73,6 +73,10 @@ export default class PinescriptLexerBase extends Lexer {
|
|
|
73
73
|
private _lastPendingTokenType = 0;
|
|
74
74
|
private _lastPendingTokenTypeFromDefaultChannel = 0;
|
|
75
75
|
private _numOpens = 0;
|
|
76
|
+
// Open square brackets (history-refs / tuples). Tracked separately from
|
|
77
|
+
// parens so wrapped `[...]` allows any indentation, like `(...)`.
|
|
78
|
+
// Python `PinescriptLexerBase._numSqBrackets` SoT (Sept 2026 release notes).
|
|
79
|
+
private _numSqBrackets = 0;
|
|
76
80
|
private _indentLengthStack: number[] = [];
|
|
77
81
|
private _inputStarted = false;
|
|
78
82
|
|
|
@@ -129,6 +133,7 @@ export default class PinescriptLexerBase extends Lexer {
|
|
|
129
133
|
this._lastPendingTokenType = 0;
|
|
130
134
|
this._lastPendingTokenTypeFromDefaultChannel = 0;
|
|
131
135
|
this._numOpens = 0;
|
|
136
|
+
this._numSqBrackets = 0;
|
|
132
137
|
this._indentLengthStack = [];
|
|
133
138
|
this._inputStarted = false;
|
|
134
139
|
}
|
|
@@ -147,11 +152,17 @@ export default class PinescriptLexerBase extends Lexer {
|
|
|
147
152
|
const tok = this._currentToken!;
|
|
148
153
|
const tokType = tok.type;
|
|
149
154
|
const C = this.C;
|
|
150
|
-
if (tokType === C.LPAR
|
|
155
|
+
if (tokType === C.LPAR) {
|
|
151
156
|
this._numOpens += 1;
|
|
152
157
|
this._addPendingToken(tok);
|
|
153
|
-
} else if (tokType === C.
|
|
154
|
-
this.
|
|
158
|
+
} else if (tokType === C.LSQB) {
|
|
159
|
+
this._numSqBrackets += 1;
|
|
160
|
+
this._addPendingToken(tok);
|
|
161
|
+
} else if (tokType === C.RPAR) {
|
|
162
|
+
this._numOpens = Math.max(0, this._numOpens - 1);
|
|
163
|
+
this._addPendingToken(tok);
|
|
164
|
+
} else if (tokType === C.RSQB) {
|
|
165
|
+
this._numSqBrackets = Math.max(0, this._numSqBrackets - 1);
|
|
155
166
|
this._addPendingToken(tok);
|
|
156
167
|
} else if (tokType === C.NEWLINE) {
|
|
157
168
|
this._handleNEWLINEToken();
|
|
@@ -264,7 +275,14 @@ export default class PinescriptLexerBase extends Lexer {
|
|
|
264
275
|
}
|
|
265
276
|
|
|
266
277
|
private _handleNEWLINEToken(): void {
|
|
267
|
-
|
|
278
|
+
// Newlines inside (...) or [...] are hidden (Python parity): wrapped
|
|
279
|
+
// history-refs / tuples may use any indentation without emitting
|
|
280
|
+
// INDENT/DEDENT mid-expression.
|
|
281
|
+
if (
|
|
282
|
+
this._numOpens > 0 ||
|
|
283
|
+
this._numSqBrackets > 0 ||
|
|
284
|
+
this._operators.has(this._lastPendingTokenTypeFromDefaultChannel)
|
|
285
|
+
) {
|
|
268
286
|
this._hideAndAddPendingToken(this._currentToken!);
|
|
269
287
|
return;
|
|
270
288
|
}
|
|
@@ -103,8 +103,32 @@ export class CompileDraw {
|
|
|
103
103
|
return this.book.boxNew(n(bar, 0), Object.keys(extra).length ? extra : undefined);
|
|
104
104
|
}
|
|
105
105
|
|
|
106
|
-
table_new(
|
|
107
|
-
|
|
106
|
+
table_new(
|
|
107
|
+
bar: unknown,
|
|
108
|
+
position?: unknown,
|
|
109
|
+
columns?: unknown,
|
|
110
|
+
rows?: unknown,
|
|
111
|
+
bgcolor?: unknown,
|
|
112
|
+
frameColor?: unknown,
|
|
113
|
+
frameWidth?: unknown,
|
|
114
|
+
borderColor?: unknown,
|
|
115
|
+
borderWidth?: unknown,
|
|
116
|
+
forceOverlay?: unknown,
|
|
117
|
+
): number {
|
|
118
|
+
// Pine positional order: (position, columns, rows, bgcolor, frame_color,
|
|
119
|
+
// frame_width, border_color, border_width, force_overlay) — Python
|
|
120
|
+
// `compiler.py` table emit SoT (columns then rows).
|
|
121
|
+
const extra: Record<string, unknown> = {};
|
|
122
|
+
if (position != null) extra.position = position;
|
|
123
|
+
putNum(extra, "columns", columns);
|
|
124
|
+
putNum(extra, "rows", rows);
|
|
125
|
+
if (bgcolor != null) extra.bgcolor = bgcolor;
|
|
126
|
+
if (frameColor != null) extra.frame_color = frameColor;
|
|
127
|
+
putNum(extra, "frame_width", frameWidth);
|
|
128
|
+
if (borderColor != null) extra.border_color = borderColor;
|
|
129
|
+
putNum(extra, "border_width", borderWidth);
|
|
130
|
+
if (forceOverlay != null) extra.force_overlay = forceOverlay;
|
|
131
|
+
return this.book.tableNew(n(bar, 0), Object.keys(extra).length ? extra : undefined);
|
|
108
132
|
}
|
|
109
133
|
|
|
110
134
|
polyline_new(bar: unknown, points?: unknown): number {
|
|
@@ -155,6 +155,44 @@ const TA_METHODS = new Set([
|
|
|
155
155
|
"volume_profile_high",
|
|
156
156
|
"volume_profile_low",
|
|
157
157
|
"volume_thrust",
|
|
158
|
+
"sma_weighted",
|
|
159
|
+
"apo",
|
|
160
|
+
"atr_normalized",
|
|
161
|
+
"beta",
|
|
162
|
+
"r_squared",
|
|
163
|
+
"comovement",
|
|
164
|
+
"skewness",
|
|
165
|
+
"kurtosis",
|
|
166
|
+
"volume_weighted_momentum",
|
|
167
|
+
"parkinson",
|
|
168
|
+
"garman_klass",
|
|
169
|
+
"mean_reversion_score",
|
|
170
|
+
"market_condition",
|
|
171
|
+
"order_flow_imbalance",
|
|
172
|
+
"liquidity_score",
|
|
173
|
+
"fear_greed_index",
|
|
174
|
+
"crowd_sentiment",
|
|
175
|
+
"smart_money_flow",
|
|
176
|
+
"trend_confirmation_score",
|
|
177
|
+
"max_loss_level",
|
|
178
|
+
"profit_lock_level",
|
|
179
|
+
"position_sizing",
|
|
180
|
+
"kelly_criterion",
|
|
181
|
+
"risk_reward_ratio",
|
|
182
|
+
"risk_reward_asymmetry",
|
|
183
|
+
"probability_of_movement",
|
|
184
|
+
"trend_strength",
|
|
185
|
+
"breakeven_level",
|
|
186
|
+
"market_structure_pivot",
|
|
187
|
+
"position_sizing_score",
|
|
188
|
+
"trailing_exit_level",
|
|
189
|
+
"optimal_entry_zone",
|
|
190
|
+
"mean_reversion_entry",
|
|
191
|
+
"multi_timeframe_signal",
|
|
192
|
+
"market_timing_index",
|
|
193
|
+
"regime_adaptive_signal",
|
|
194
|
+
"strategy_score",
|
|
195
|
+
"breakout_detection",
|
|
158
196
|
]);
|
|
159
197
|
|
|
160
198
|
/**
|
|
@@ -530,6 +568,26 @@ function hasArg(args: CallArgs, index: number, names: string[]): boolean {
|
|
|
530
568
|
return index >= 0 && index < args.pos.length;
|
|
531
569
|
}
|
|
532
570
|
|
|
571
|
+
/** Bare series names: Python sees a `PineSeries`, so list-only handlers return `na`. */
|
|
572
|
+
const BARE_SERIES_NAMES = new Set([
|
|
573
|
+
"open",
|
|
574
|
+
"high",
|
|
575
|
+
"low",
|
|
576
|
+
"close",
|
|
577
|
+
"volume",
|
|
578
|
+
"hl2",
|
|
579
|
+
"hlc3",
|
|
580
|
+
"ohlc4",
|
|
581
|
+
"hlcc4",
|
|
582
|
+
"time",
|
|
583
|
+
"time_close",
|
|
584
|
+
]);
|
|
585
|
+
|
|
586
|
+
function hasSeriesArg(args: CallArgs): boolean {
|
|
587
|
+
const isSeries = (n: expr): boolean => n.kind === "Name" && BARE_SERIES_NAMES.has(n.id);
|
|
588
|
+
return args.posNodes.some(isSeries) || Object.values(args.kwNodes).some(isSeries);
|
|
589
|
+
}
|
|
590
|
+
|
|
533
591
|
/** True when a keyword argument named one of *names* is present (interpret hasKwarg). */
|
|
534
592
|
function hasAnyKw(args: CallArgs, names: string[]): boolean {
|
|
535
593
|
return names.some((n) => Object.hasOwn(args.kw, n));
|
|
@@ -567,6 +625,11 @@ function cellList(expr: string): string {
|
|
|
567
625
|
return `(() => { const __v = ${expr}; const __cell = (e) => (e == null ? null : typeof e === "boolean" ? (e ? 1 : 0) : (typeof e === "number" && Number.isFinite(e) ? e : null)); if (__v != null && typeof __v.size === "function" && typeof __v.get === "function") { const __n = __v.size(); const __o = []; for (let __i = 0; __i < __n; __i++) __o.push(__cell(__v.get(__i))); return __o; } return [__cell(__v)]; })()`;
|
|
568
626
|
}
|
|
569
627
|
|
|
628
|
+
/** Interpret `seriesLastCell`: array → last cell; scalar → the value itself. */
|
|
629
|
+
function cellLast(expr: string): string {
|
|
630
|
+
return `(() => { const __v = ${expr}; const __cell = (e) => (e == null ? null : typeof e === "boolean" ? (e ? 1 : 0) : (typeof e === "number" && Number.isFinite(e) ? e : null)); if (__v != null && typeof __v.size === "function" && typeof __v.get === "function") { const __n = __v.size(); return __n === 0 ? null : __cell(__v.get(__n - 1)); } return __cell(__v); })()`;
|
|
631
|
+
}
|
|
632
|
+
|
|
570
633
|
const DRAW_KINDS = new Set(["label", "line", "box", "table", "polyline", "linefill"]);
|
|
571
634
|
|
|
572
635
|
const ARRAY_SIGS: Record<string, string[][]> = {
|
|
@@ -953,6 +1016,10 @@ function emitDraw(ctx: EmitCtx, name: string, methodSrc: expr | null, args: Call
|
|
|
953
1016
|
code = `__h.draw.box_new(__bar_idx, ${pick(args, 0, ["left"], "0")}, ${pick(args, 1, ["top"], "0")}, ${pick(args, 2, ["right"], "0")}, ${pick(args, 3, ["bottom"], "0")})`;
|
|
954
1017
|
} else if (kind === "polyline") {
|
|
955
1018
|
code = `__h.draw.polyline_new(__bar_idx, ${pick(args, 0, ["points"], "null")})`;
|
|
1019
|
+
} else if (kind === "table") {
|
|
1020
|
+
// Pine positional order: (position, columns, rows, bgcolor, frame_color,
|
|
1021
|
+
// frame_width, border_color, border_width, force_overlay).
|
|
1022
|
+
code = `__h.draw.table_new(__bar_idx, ${pick(args, 0, ["position"], "null")}, ${pick(args, 1, ["columns"], "null")}, ${pick(args, 2, ["rows"], "null")}, ${pick(args, 3, ["bgcolor"], "null")}, ${pick(args, 4, ["frame_color"], "null")}, ${pick(args, 5, ["frame_width"], "null")}, ${pick(args, 6, ["border_color"], "null")}, ${pick(args, 7, ["border_width"], "null")}, ${pick(args, 8, ["force_overlay"], "null")})`;
|
|
956
1023
|
} else if (kind === "linefill") {
|
|
957
1024
|
const color = hasArg(args, 2, ["color"]) ? pick(args, 2, ["color"], "null") : "null";
|
|
958
1025
|
code = `__h.draw.linefill_new(__bar_idx, ${pick(args, 0, ["id1", "line1"], "null")}, ${pick(args, 1, ["id2", "line2"], "null")}, ${color})`;
|
|
@@ -1619,6 +1686,11 @@ function taCall(method: string, site: string, parts: string[]): string {
|
|
|
1619
1686
|
return `__h.ta.${method}(${site}, ${parts.join(", ")})`;
|
|
1620
1687
|
}
|
|
1621
1688
|
|
|
1689
|
+
/** Call a stateless TaEngine method (no call-site argument). */
|
|
1690
|
+
function taPure(method: string, parts: string[]): string {
|
|
1691
|
+
return `__h.ta.${method}(${parts.join(", ")})`;
|
|
1692
|
+
}
|
|
1693
|
+
|
|
1622
1694
|
function emitTa(ctx: EmitCtx, method: string, args: CallArgs): string {
|
|
1623
1695
|
if (!TA_METHODS.has(method)) return "null";
|
|
1624
1696
|
if (method === "ad") method = "accdist";
|
|
@@ -2089,17 +2161,31 @@ function emitTa(ctx: EmitCtx, method: string, args: CallArgs): string {
|
|
|
2089
2161
|
]);
|
|
2090
2162
|
}
|
|
2091
2163
|
if (method === "stoch_smooth") {
|
|
2092
|
-
return
|
|
2164
|
+
return taCall("stochSmooth", site, [
|
|
2165
|
+
cellOf(pick(args, 0, ["high"], "null")),
|
|
2166
|
+
cellOf(pick(args, 1, ["low"], "null")),
|
|
2167
|
+
cellOf(pick(args, 2, ["close"], "null")),
|
|
2168
|
+
cellOf(pick(args, 3, ["period", "length"], "null")),
|
|
2169
|
+
cellOf(pick(args, 4, ["smooth_k", "k"], "null")),
|
|
2170
|
+
cellOf(pick(args, 5, ["smooth_d", "d"], "null")),
|
|
2171
|
+
]);
|
|
2093
2172
|
}
|
|
2094
2173
|
if (method === "engulfing") {
|
|
2095
|
-
//
|
|
2096
|
-
return `(() => { ${cellOf(pick(args, 0, ["open"], "null"))}; ${cellOf(pick(args, 1, ["high"], "null"))}; ${cellOf(pick(args, 2, ["low"], "null"))}; ${cellOf(pick(args, 3, ["close"], "null"))}; return __h.ta.engulfing(); })()`;
|
|
2174
|
+
// Args are still evaluated (interpret reads all four); high/low are unused.
|
|
2175
|
+
return `(() => { const __o = ${cellOf(pick(args, 0, ["open"], "null"))}; ${cellOf(pick(args, 1, ["high"], "null"))}; ${cellOf(pick(args, 2, ["low"], "null"))}; const __c = ${cellOf(pick(args, 3, ["close"], "null"))}; return __h.ta.engulfing(${site}, __o, __c); })()`;
|
|
2097
2176
|
}
|
|
2098
2177
|
if (method === "inside_bar_pattern") {
|
|
2099
|
-
return
|
|
2178
|
+
return taCall("insideBarPattern", site, [
|
|
2179
|
+
cellOf(pick(args, 0, ["high"], "null")),
|
|
2180
|
+
cellOf(pick(args, 1, ["low"], "null")),
|
|
2181
|
+
]);
|
|
2100
2182
|
}
|
|
2101
2183
|
if (method === "double_top_bottom") {
|
|
2102
|
-
return
|
|
2184
|
+
return taCall("doubleTopBottom", site, [
|
|
2185
|
+
cellOf(pick(args, 0, ["high"], "null")),
|
|
2186
|
+
cellOf(pick(args, 1, ["low"], "null")),
|
|
2187
|
+
cellOf(pick(args, 2, ["period", "length"], "null")),
|
|
2188
|
+
]);
|
|
2103
2189
|
}
|
|
2104
2190
|
if (method === "voi") {
|
|
2105
2191
|
return `__h.ta.voi(${cellOf(pick(args, 0, ["buy_volume", "buy"], "null"))}, ${cellOf(pick(args, 1, ["sell_volume", "sell"], "null"))})`;
|
|
@@ -2113,7 +2199,10 @@ function emitTa(ctx: EmitCtx, method: string, args: CallArgs): string {
|
|
|
2113
2199
|
if (method === "rsi_divergence") {
|
|
2114
2200
|
// Missing period is 0 (interpret `period ?? 0`), not the engine's null → na.
|
|
2115
2201
|
const period = `((__n) => (__n == null ? 0 : __n))(${cellOf(pick(args, 1, ["period", "length"], "null"))})`;
|
|
2116
|
-
return
|
|
2202
|
+
return taCall("rsiDivergence", site, [
|
|
2203
|
+
cellLast(pick(args, 0, ["rsi_series", "series", "source"], "null")),
|
|
2204
|
+
period,
|
|
2205
|
+
]);
|
|
2117
2206
|
}
|
|
2118
2207
|
if (method === "cumulative_delta") {
|
|
2119
2208
|
return taCall("cumulativeDelta", site, [
|
|
@@ -2148,5 +2237,234 @@ function emitTa(ctx: EmitCtx, method: string, args: CallArgs): string {
|
|
|
2148
2237
|
`((__n) => (__n == null ? 0.3 : __n))(${sens})`,
|
|
2149
2238
|
]);
|
|
2150
2239
|
}
|
|
2240
|
+
if (method === "sma_weighted") {
|
|
2241
|
+
return taCall("smaWeighted", site, [
|
|
2242
|
+
cellOf(pick(args, 0, ["series", "source"], "null")),
|
|
2243
|
+
cellOf(pick(args, 1, ["period", "length"], "null")),
|
|
2244
|
+
pick(args, 2, ["weight_type", "weight"], '"linear"'),
|
|
2245
|
+
]);
|
|
2246
|
+
}
|
|
2247
|
+
if (method === "apo") {
|
|
2248
|
+
return taCall("apo", site, [
|
|
2249
|
+
cellOf(pick(args, 0, ["series", "source"], "null")),
|
|
2250
|
+
cellOf(pick(args, 1, ["fast_period", "fast"], "null")),
|
|
2251
|
+
cellOf(pick(args, 2, ["slow_period", "slow"], "null")),
|
|
2252
|
+
]);
|
|
2253
|
+
}
|
|
2254
|
+
if (method === "atr_normalized") {
|
|
2255
|
+
return taCall("atrNormalized", site, [
|
|
2256
|
+
cellOf(pick(args, 0, ["high"], "null")),
|
|
2257
|
+
cellOf(pick(args, 1, ["low"], "null")),
|
|
2258
|
+
cellOf(pick(args, 2, ["close"], "null")),
|
|
2259
|
+
cellOf(pick(args, 3, ["period", "length"], "null")),
|
|
2260
|
+
]);
|
|
2261
|
+
}
|
|
2262
|
+
if (method === "beta" || method === "r_squared" || method === "comovement") {
|
|
2263
|
+
const fn = method === "beta" ? "beta" : method === "r_squared" ? "rSquared" : "comovement";
|
|
2264
|
+
return taCall(fn, site, [
|
|
2265
|
+
cellOf(pick(args, 0, ["series1", "series", "source"], "null")),
|
|
2266
|
+
cellOf(pick(args, 1, ["series2", "series_b", "other"], "null")),
|
|
2267
|
+
cellOf(pick(args, 2, ["length", "period"], "null")),
|
|
2268
|
+
]);
|
|
2269
|
+
}
|
|
2270
|
+
if (method === "skewness" || method === "kurtosis") {
|
|
2271
|
+
return taCall(method, site, [
|
|
2272
|
+
cellOf(pick(args, 0, ["series", "source"], "null")),
|
|
2273
|
+
cellOf(pick(args, 1, ["period", "length"], "null")),
|
|
2274
|
+
]);
|
|
2275
|
+
}
|
|
2276
|
+
if (method === "volume_weighted_momentum") {
|
|
2277
|
+
return taCall("volumeWeightedMomentum", site, [
|
|
2278
|
+
cellOf(pick(args, 0, ["series", "source"], "null")),
|
|
2279
|
+
cellOf(pick(args, 1, ["volume"], "null")),
|
|
2280
|
+
cellOf(pick(args, 2, ["length", "period"], "null")),
|
|
2281
|
+
]);
|
|
2282
|
+
}
|
|
2283
|
+
if (method === "parkinson") {
|
|
2284
|
+
if (hasSeriesArg(args)) return "null";
|
|
2285
|
+
return `__h.ta.parkinson(${cellOf(pick(args, 0, ["high"], "null"))}, ${cellOf(pick(args, 1, ["low"], "null"))})`;
|
|
2286
|
+
}
|
|
2287
|
+
if (method === "garman_klass") {
|
|
2288
|
+
if (hasSeriesArg(args)) return "null";
|
|
2289
|
+
return `__h.ta.garmanKlass(${cellOf(pick(args, 0, ["high"], "null"))}, ${cellOf(pick(args, 1, ["low"], "null"))}, ${cellOf(pick(args, 2, ["close"], "null"))}, ${cellOf(pick(args, 3, ["open"], "null"))})`;
|
|
2290
|
+
}
|
|
2291
|
+
if (method === "mean_reversion_score") {
|
|
2292
|
+
return taCall("meanReversionScore", site, [
|
|
2293
|
+
cellOf(pick(args, 0, ["close", "source"], "null")),
|
|
2294
|
+
cellOf(pick(args, 1, ["sma"], "null")),
|
|
2295
|
+
cellOf(pick(args, 2, ["stdev", "std_dev"], "null")),
|
|
2296
|
+
cellOf(pick(args, 3, ["period", "length"], "null")),
|
|
2297
|
+
]);
|
|
2298
|
+
}
|
|
2299
|
+
if (method === "market_condition") {
|
|
2300
|
+
return taCall("marketCondition", site, [
|
|
2301
|
+
cellOf(pick(args, 0, ["close", "source"], "null")),
|
|
2302
|
+
cellOf(pick(args, 1, ["atr"], "null")),
|
|
2303
|
+
cellOf(pick(args, 2, ["sma_period", "sma_length"], "null")),
|
|
2304
|
+
cellOf(pick(args, 3, ["stdev_period", "stdev_length"], "null")),
|
|
2305
|
+
]);
|
|
2306
|
+
}
|
|
2307
|
+
if (method === "order_flow_imbalance") {
|
|
2308
|
+
return taCall("orderFlowImbalance", site, [
|
|
2309
|
+
cellOf(pick(args, 0, ["high"], "null")),
|
|
2310
|
+
cellOf(pick(args, 1, ["low"], "null")),
|
|
2311
|
+
cellOf(pick(args, 2, ["close"], "null")),
|
|
2312
|
+
cellOf(pick(args, 3, ["volume"], "null")),
|
|
2313
|
+
cellOf(pick(args, 4, ["period", "length"], "null")),
|
|
2314
|
+
]);
|
|
2315
|
+
}
|
|
2316
|
+
if (method === "liquidity_score") {
|
|
2317
|
+
return taCall("liquidityScore", site, [
|
|
2318
|
+
cellOf(pick(args, 0, ["volume"], "null")),
|
|
2319
|
+
cellOf(pick(args, 1, ["volatility"], "null")),
|
|
2320
|
+
cellOf(pick(args, 2, ["spread"], "null")),
|
|
2321
|
+
cellOf(pick(args, 3, ["period", "length"], "null")),
|
|
2322
|
+
]);
|
|
2323
|
+
}
|
|
2324
|
+
if (method === "fear_greed_index") {
|
|
2325
|
+
return `__h.ta.fearGreedIndex(${cellOf(pick(args, 0, ["rsi"], "null"))}, ${cellOf(pick(args, 1, ["vix"], "null"))}, ${cellOf(pick(args, 2, ["put_call", "put_call_ratio"], "null"))}, ${cellOf(pick(args, 3, ["breadth"], "null"))})`;
|
|
2326
|
+
}
|
|
2327
|
+
if (method === "crowd_sentiment") {
|
|
2328
|
+
return `__h.ta.crowdSentiment(${cellOf(pick(args, 0, ["price_agreement", "price"], "null"))}, ${cellOf(pick(args, 1, ["volume_agreement", "volume"], "null"))}, ${cellOf(pick(args, 2, ["time_agreement", "time"], "null"))})`;
|
|
2329
|
+
}
|
|
2330
|
+
if (method === "smart_money_flow") {
|
|
2331
|
+
return `__h.ta.smartMoneyFlow(${cellOf(pick(args, 0, ["price_change"], "null"))}, ${cellOf(pick(args, 1, ["volume"], "null"))}, ${cellOf(pick(args, 2, ["time_since_high", "time_high"], "null"))}, ${cellOf(pick(args, 3, ["time_since_low", "time_low"], "null"))})`;
|
|
2332
|
+
}
|
|
2333
|
+
if (method === "trend_confirmation_score") {
|
|
2334
|
+
return `__h.ta.trendConfirmationScore(${cellOf(pick(args, 0, ["momentum"], "null"))}, ${cellOf(pick(args, 1, ["trend_alignment"], "null"))}, ${cellOf(pick(args, 2, ["strength"], "null"))}, ${cellOf(pick(args, 3, ["rsi"], "null"))}, ${cellOf(pick(args, 4, ["rsi_alignment"], "null"))})`;
|
|
2335
|
+
}
|
|
2336
|
+
if (method === "max_loss_level") {
|
|
2337
|
+
return `__h.ta.maxLossLevel(${cellOf(pick(args, 0, ["entry", "entry_price"], "null"))}, ${cellOf(pick(args, 1, ["account", "account_size"], "null"))}, ${cellOf(pick(args, 2, ["max_loss_pct", "max_loss_percent"], "null"))})`;
|
|
2338
|
+
}
|
|
2339
|
+
if (method === "profit_lock_level") {
|
|
2340
|
+
return `__h.ta.profitLockLevel(${cellOf(pick(args, 0, ["entry", "entry_price"], "null"))}, ${cellOf(pick(args, 1, ["current", "current_price"], "null"))}, ${cellOf(pick(args, 2, ["trail_pct", "trail_percent"], "null"))}, ${cellOf(pick(args, 3, ["direction"], "null"))})`;
|
|
2341
|
+
}
|
|
2342
|
+
if (method === "position_sizing") {
|
|
2343
|
+
return taPure("positionSizing", [
|
|
2344
|
+
cellOf(pick(args, 0, ["account", "account_size"], "null")),
|
|
2345
|
+
cellOf(pick(args, 1, ["risk_pct", "risk_percent"], "null")),
|
|
2346
|
+
cellOf(pick(args, 2, ["entry", "entry_price"], "null")),
|
|
2347
|
+
cellOf(pick(args, 3, ["stop", "stop_price"], "null")),
|
|
2348
|
+
]);
|
|
2349
|
+
}
|
|
2350
|
+
if (method === "kelly_criterion") {
|
|
2351
|
+
return taPure("kellyCriterion", [
|
|
2352
|
+
cellOf(pick(args, 0, ["win_rate"], "null")),
|
|
2353
|
+
cellOf(pick(args, 1, ["avg_win"], "null")),
|
|
2354
|
+
cellOf(pick(args, 2, ["avg_loss"], "null")),
|
|
2355
|
+
]);
|
|
2356
|
+
}
|
|
2357
|
+
if (method === "risk_reward_ratio") {
|
|
2358
|
+
return taPure("riskRewardRatio", [
|
|
2359
|
+
cellOf(pick(args, 0, ["entry", "entry_price"], "null")),
|
|
2360
|
+
cellOf(pick(args, 1, ["stop", "stop_price"], "null")),
|
|
2361
|
+
cellOf(pick(args, 2, ["target", "target_price"], "null")),
|
|
2362
|
+
]);
|
|
2363
|
+
}
|
|
2364
|
+
if (method === "risk_reward_asymmetry") {
|
|
2365
|
+
return taPure("riskRewardAsymmetry", [
|
|
2366
|
+
cellOf(pick(args, 0, ["entry", "entry_price"], "null")),
|
|
2367
|
+
cellOf(pick(args, 1, ["stop", "stop_price"], "null")),
|
|
2368
|
+
cellOf(pick(args, 2, ["target", "target_price"], "null")),
|
|
2369
|
+
cellOf(pick(args, 3, ["win_rate"], "null")),
|
|
2370
|
+
]);
|
|
2371
|
+
}
|
|
2372
|
+
if (method === "probability_of_movement") {
|
|
2373
|
+
return taPure("probabilityOfMovement", [
|
|
2374
|
+
cellOf(pick(args, 0, ["current", "current_price"], "null")),
|
|
2375
|
+
cellOf(pick(args, 1, ["target", "target_price"], "null")),
|
|
2376
|
+
cellOf(pick(args, 2, ["atr"], "null")),
|
|
2377
|
+
cellOf(pick(args, 3, ["period", "length"], "null")),
|
|
2378
|
+
]);
|
|
2379
|
+
}
|
|
2380
|
+
if (method === "trend_strength") {
|
|
2381
|
+
return taPure("trendStrength", [
|
|
2382
|
+
cellOf(pick(args, 0, ["price", "source"], "null")),
|
|
2383
|
+
cellOf(pick(args, 1, ["adx"], "null")),
|
|
2384
|
+
cellOf(pick(args, 2, ["rsi"], "null")),
|
|
2385
|
+
]);
|
|
2386
|
+
}
|
|
2387
|
+
if (method === "breakeven_level") {
|
|
2388
|
+
return taPure("breakevenLevel", [
|
|
2389
|
+
cellOf(pick(args, 0, ["entry", "entry_price"], "null")),
|
|
2390
|
+
cellOf(pick(args, 1, ["quantity", "qty"], "null")),
|
|
2391
|
+
cellOf(pick(args, 2, ["commission"], "null")),
|
|
2392
|
+
cellOf(pick(args, 3, ["slippage"], "null")),
|
|
2393
|
+
cellOf(pick(args, 4, ["direction"], "null")),
|
|
2394
|
+
]);
|
|
2395
|
+
}
|
|
2396
|
+
if (method === "market_structure_pivot") {
|
|
2397
|
+
return taPure("marketStructurePivot", [
|
|
2398
|
+
cellOf(pick(args, 2, ["close"], "null")),
|
|
2399
|
+
]);
|
|
2400
|
+
}
|
|
2401
|
+
if (method === "position_sizing_score") {
|
|
2402
|
+
return taPure("positionSizingScore", [
|
|
2403
|
+
cellOf(pick(args, 0, ["account_risk", "risk"], "null")),
|
|
2404
|
+
cellOf(pick(args, 1, ["volatility"], "null")),
|
|
2405
|
+
cellOf(pick(args, 2, ["conviction"], "null")),
|
|
2406
|
+
cellOf(pick(args, 3, ["correlation"], "null")),
|
|
2407
|
+
]);
|
|
2408
|
+
}
|
|
2409
|
+
if (method === "trailing_exit_level") {
|
|
2410
|
+
return taPure("trailingExitLevel", [
|
|
2411
|
+
cellOf(pick(args, 0, ["entry", "entry_price"], "null")),
|
|
2412
|
+
cellOf(pick(args, 1, ["current", "current_price"], "null")),
|
|
2413
|
+
cellOf(pick(args, 2, ["atr"], "null")),
|
|
2414
|
+
cellOf(pick(args, 3, ["multiplier", "mult"], "null")),
|
|
2415
|
+
cellOf(pick(args, 4, ["step"], "null")),
|
|
2416
|
+
]);
|
|
2417
|
+
}
|
|
2418
|
+
if (method === "optimal_entry_zone") {
|
|
2419
|
+
return taPure("optimalEntryZone", [cellOf(pick(args, 2, ["close"], "null"))]);
|
|
2420
|
+
}
|
|
2421
|
+
if (method === "mean_reversion_entry") {
|
|
2422
|
+
return taPure("meanReversionEntry", [
|
|
2423
|
+
cellOf(pick(args, 0, ["price", "source"], "null")),
|
|
2424
|
+
cellOf(pick(args, 1, ["sma"], "null")),
|
|
2425
|
+
cellOf(pick(args, 2, ["stdev", "std_dev"], "null")),
|
|
2426
|
+
cellOf(pick(args, 4, ["threshold"], "null")),
|
|
2427
|
+
]);
|
|
2428
|
+
}
|
|
2429
|
+
if (method === "multi_timeframe_signal") {
|
|
2430
|
+
return taPure("multiTimeframeSignal", [
|
|
2431
|
+
cellOf(pick(args, 0, ["s1", "signal1"], "null")),
|
|
2432
|
+
cellOf(pick(args, 1, ["s2", "signal2"], "null")),
|
|
2433
|
+
cellOf(pick(args, 2, ["s3", "signal3"], "null")),
|
|
2434
|
+
cellOf(pick(args, 3, ["w1", "weight1"], "null")),
|
|
2435
|
+
cellOf(pick(args, 4, ["w2", "weight2"], "null")),
|
|
2436
|
+
cellOf(pick(args, 5, ["w3", "weight3"], "null")),
|
|
2437
|
+
]);
|
|
2438
|
+
}
|
|
2439
|
+
if (method === "market_timing_index") {
|
|
2440
|
+
return taPure("marketTimingIndex", [
|
|
2441
|
+
cellOf(pick(args, 0, ["trend"], "null")),
|
|
2442
|
+
cellOf(pick(args, 1, ["momentum"], "null")),
|
|
2443
|
+
cellOf(pick(args, 2, ["volatility"], "null")),
|
|
2444
|
+
cellOf(pick(args, 3, ["sentiment"], "null")),
|
|
2445
|
+
]);
|
|
2446
|
+
}
|
|
2447
|
+
if (method === "regime_adaptive_signal") {
|
|
2448
|
+
return taPure("regimeAdaptiveSignal", [
|
|
2449
|
+
cellOf(pick(args, 0, ["signal_strength", "strength"], "null")),
|
|
2450
|
+
pick(args, 1, ["volatility_regime", "vol_regime"], '""'),
|
|
2451
|
+
pick(args, 2, ["trend_regime"], '""'),
|
|
2452
|
+
]);
|
|
2453
|
+
}
|
|
2454
|
+
if (method === "strategy_score") {
|
|
2455
|
+
return taPure("strategyScore", [
|
|
2456
|
+
cellOf(pick(args, 0, ["rsi"], "null")),
|
|
2457
|
+
cellOf(pick(args, 1, ["macd"], "null")),
|
|
2458
|
+
cellOf(pick(args, 2, ["ema_cross"], "null")),
|
|
2459
|
+
cellOf(pick(args, 3, ["trend"], "null")),
|
|
2460
|
+
]);
|
|
2461
|
+
}
|
|
2462
|
+
if (method === "breakout_detection") {
|
|
2463
|
+
return taPure("breakoutDetection", [
|
|
2464
|
+
cellOf(pick(args, 0, ["close", "source"], "null")),
|
|
2465
|
+
cellOf(pick(args, 1, ["resistance"], "null")),
|
|
2466
|
+
cellOf(pick(args, 2, ["support"], "null")),
|
|
2467
|
+
]);
|
|
2468
|
+
}
|
|
2151
2469
|
return "null";
|
|
2152
2470
|
}
|
|
@@ -20,9 +20,10 @@ export function naNum(v: unknown): number | null {
|
|
|
20
20
|
return NA;
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
-
/** Store cell or object handle. Objects (array/map/matrix/UDT) stay intact. */
|
|
23
|
+
/** Store cell or object handle. Objects (array/map/matrix/UDT) and strings stay intact. */
|
|
24
24
|
export function hold(v: unknown): unknown {
|
|
25
25
|
if (v != null && typeof v === "object") return v;
|
|
26
|
+
if (typeof v === "string") return v;
|
|
26
27
|
return naNum(v);
|
|
27
28
|
}
|
|
28
29
|
|
|
@@ -79,7 +79,7 @@ import {
|
|
|
79
79
|
} from "../footprint.ts";
|
|
80
80
|
import { resolveCurrencyRate, resolveSeed } from "../request.ts";
|
|
81
81
|
import { TaEngine } from "../ta.ts";
|
|
82
|
-
import { utcPartsFromMs, timestamp, weekOfYear, timeTradingDay, type UtcParts } from "../time.ts";
|
|
82
|
+
import { utcPartsFromMs, timestamp, timestampToMs, weekOfYear, timeTradingDay, type UtcParts } from "../time.ts";
|
|
83
83
|
import { TickerId } from "../ticker.ts";
|
|
84
84
|
import { timeframeInSeconds, timeframePeriodChanged } from "../timeframe.ts";
|
|
85
85
|
import { compileArray, compileMap, compileMatrix } from "./collections.ts";
|
|
@@ -203,8 +203,8 @@ export function createCompileHelpers(opts?: CompileHelperOpts) {
|
|
|
203
203
|
second: calendarSecond,
|
|
204
204
|
dayofweek: calendarDayofweek,
|
|
205
205
|
timestamp,
|
|
206
|
-
weekOfYear,
|
|
207
|
-
timeTradingDay,
|
|
206
|
+
weekOfYear: calendarWeekOfYear,
|
|
207
|
+
timeTradingDay: calendarTimeTradingDay,
|
|
208
208
|
timeframeInSeconds: timeframeInSecondsOrDaily,
|
|
209
209
|
timeframeChange,
|
|
210
210
|
timeClose,
|
|
@@ -339,10 +339,25 @@ function NA_FN(v?: unknown): number | null {
|
|
|
339
339
|
return isNaCell(v) ? 1 : 0;
|
|
340
340
|
}
|
|
341
341
|
|
|
342
|
-
/** Unix-ms → UTC calendar part. na / non-finite time → null. */
|
|
342
|
+
/** Unix-ms → UTC calendar part. na / non-finite time → null. Unix-second stamps become ms (Python `utility._timestamp_to_ms`). */
|
|
343
343
|
function utcPart(t: unknown, key: keyof UtcParts): number | null {
|
|
344
344
|
if (typeof t !== "number" || !Number.isFinite(t)) return null;
|
|
345
|
-
|
|
345
|
+
const ms = timestampToMs(t);
|
|
346
|
+
return ms == null ? null : utcPartsFromMs(ms)[key];
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
/** `weekofyear(t)` with unix-seconds coercion (Python `_resolve_timestamp_arg`). */
|
|
350
|
+
function calendarWeekOfYear(t: unknown): number | null {
|
|
351
|
+
if (typeof t !== "number" || !Number.isFinite(t)) return null;
|
|
352
|
+
const ms = timestampToMs(t);
|
|
353
|
+
return ms == null ? null : weekOfYear(ms);
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
/** `time_tradingday(t)` with unix-seconds coercion (Python `_resolve_timestamp_arg`). */
|
|
357
|
+
function calendarTimeTradingDay(t: unknown): number | null {
|
|
358
|
+
if (typeof t !== "number" || !Number.isFinite(t)) return null;
|
|
359
|
+
const ms = timestampToMs(t);
|
|
360
|
+
return ms == null ? null : timeTradingDay(ms);
|
|
346
361
|
}
|
|
347
362
|
|
|
348
363
|
function calendarYear(t: unknown): number | null {
|
|
@@ -124,6 +124,7 @@ export class CompileStrategy {
|
|
|
124
124
|
high: num(high, this.mark),
|
|
125
125
|
low: num(low, this.mark),
|
|
126
126
|
close: this.mark,
|
|
127
|
+
time: this.time,
|
|
127
128
|
};
|
|
128
129
|
this.ohlc = ohlc;
|
|
129
130
|
this.book.processPending(this.bar, ohlc);
|
|
@@ -141,18 +142,19 @@ export class CompileStrategy {
|
|
|
141
142
|
comment: opts?.comment,
|
|
142
143
|
oca_name: opts?.oca_name,
|
|
143
144
|
oca_type: opts?.oca_type,
|
|
145
|
+
deferMarket: true,
|
|
144
146
|
});
|
|
145
147
|
}
|
|
146
148
|
|
|
147
149
|
close(id?: unknown, qty?: unknown): void {
|
|
148
|
-
this.book.
|
|
150
|
+
this.book.placeClose(this.bar, str(id, ""), this.mark, {
|
|
149
151
|
time: this.time,
|
|
150
152
|
qty: optNum(qty),
|
|
151
153
|
});
|
|
152
154
|
}
|
|
153
155
|
|
|
154
156
|
close_all(_comment?: unknown): void {
|
|
155
|
-
this.book.
|
|
157
|
+
this.book.placeCloseAll(this.bar, this.mark);
|
|
156
158
|
}
|
|
157
159
|
|
|
158
160
|
order(id?: unknown, direction?: unknown, qty?: unknown, opts?: PlaceEntryOpts): void {
|
|
@@ -174,6 +176,7 @@ export class CompileStrategy {
|
|
|
174
176
|
price: this.mark,
|
|
175
177
|
time: this.time,
|
|
176
178
|
ohlc: this.ohlc,
|
|
179
|
+
deferMarket: true,
|
|
177
180
|
});
|
|
178
181
|
}
|
|
179
182
|
|