@hoox-sh/pynets 0.3.0 → 0.5.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 +30 -0
- package/README.md +7 -5
- package/dist/browser.js +66 -66
- package/dist/index.js +2009 -47
- package/package.json +1 -1
- package/src/cli/rich.ts +1 -1
- package/src/runtime/compile/collections.ts +4 -0
- package/src/runtime/compile/draw.ts +609 -19
- package/src/runtime/compile/emit.ts +98 -1
- package/src/runtime/compile/emit_call.ts +366 -10
- package/src/runtime/compile/runtime.ts +71 -1
- package/src/runtime/compile/strategy.ts +253 -1
- package/src/runtime/footprint.ts +272 -0
- package/src/runtime/interpret.ts +325 -8
- package/src/runtime/request.ts +19 -4
- package/src/runtime/ta.ts +392 -0
package/src/runtime/interpret.ts
CHANGED
|
@@ -123,6 +123,27 @@ import {
|
|
|
123
123
|
type StubKwargs,
|
|
124
124
|
} from "./library.ts";
|
|
125
125
|
import { isRequestBuiltin, resolveRequest, resolveSecurity } from "./request.ts";
|
|
126
|
+
import {
|
|
127
|
+
Footprint,
|
|
128
|
+
footprintBuyVolume,
|
|
129
|
+
footprintDelta,
|
|
130
|
+
footprintGetRowByPrice,
|
|
131
|
+
footprintPoc,
|
|
132
|
+
footprintRows,
|
|
133
|
+
footprintSellVolume,
|
|
134
|
+
footprintTotalVolume,
|
|
135
|
+
footprintVah,
|
|
136
|
+
footprintVal,
|
|
137
|
+
volumeRowBuyVolume,
|
|
138
|
+
volumeRowDelta,
|
|
139
|
+
volumeRowDownPrice,
|
|
140
|
+
volumeRowHasBuyImbalance,
|
|
141
|
+
volumeRowHasSellImbalance,
|
|
142
|
+
volumeRowSellVolume,
|
|
143
|
+
volumeRowTotalVolume,
|
|
144
|
+
volumeRowUpPrice,
|
|
145
|
+
type VolumeRow,
|
|
146
|
+
} from "./footprint.ts";
|
|
126
147
|
import { NA, PineSeries, type Cell } from "./series.ts";
|
|
127
148
|
import {
|
|
128
149
|
strContains,
|
|
@@ -254,6 +275,8 @@ type Value =
|
|
|
254
275
|
| PineMap
|
|
255
276
|
| PineMatrix
|
|
256
277
|
| TickerId
|
|
278
|
+
| Footprint
|
|
279
|
+
| VolumeRow
|
|
257
280
|
| UdtType
|
|
258
281
|
| UdtInstance
|
|
259
282
|
| LibraryModule
|
|
@@ -337,7 +360,7 @@ interface Env {
|
|
|
337
360
|
onceFired: WeakSet<object>;
|
|
338
361
|
/** Python `_max_bars_back_decls` — recorded only; evaluation is unbounded. */
|
|
339
362
|
maxBarsBackDecls: Array<{ var: Value; num: Value }>;
|
|
340
|
-
/** Python `_alert_fire_bars` keys for `alertcondition`
|
|
363
|
+
/** Python `_alert_fire_bars` keys for `alert` / `alertcondition` freq gating. */
|
|
341
364
|
alertFireBars: Map<string, number>;
|
|
342
365
|
}
|
|
343
366
|
|
|
@@ -1510,6 +1533,22 @@ export const MATH_CONSTANTS: Record<string, Value> = {
|
|
|
1510
1533
|
"plot.linestyle_solid": "linestyle_solid",
|
|
1511
1534
|
"plot.linestyle_dashed": "linestyle_dashed",
|
|
1512
1535
|
"plot.linestyle_dotted": "linestyle_dotted",
|
|
1536
|
+
// Python plotting.py PlotStyle tokens — longer than hline's "solid".
|
|
1537
|
+
"plot.style_line": "style_line",
|
|
1538
|
+
"plot.style_linebr": "style_linebr",
|
|
1539
|
+
"plot.style_stepline": "style_stepline",
|
|
1540
|
+
"plot.style_steplinebr": "style_steplinebr",
|
|
1541
|
+
"plot.style_stepline_diamond": "style_stepline_diamond",
|
|
1542
|
+
"plot.style_histogram": "style_histogram",
|
|
1543
|
+
"plot.style_columns": "style_columns",
|
|
1544
|
+
"plot.style_cross": "style_cross",
|
|
1545
|
+
"plot.style_area": "style_area",
|
|
1546
|
+
"plot.style_areabr": "style_areabr",
|
|
1547
|
+
"plot.style_circles": "style_circles",
|
|
1548
|
+
// Python alerts.py `_alerts_builtin_map`.
|
|
1549
|
+
"alert.freq_once_per_bar": "once_per_bar",
|
|
1550
|
+
"alert.freq_once_per_bar_close": "once_per_bar_close",
|
|
1551
|
+
"alert.freq_all": "all",
|
|
1513
1552
|
"dayofweek.sunday": 1,
|
|
1514
1553
|
"dayofweek.monday": 2,
|
|
1515
1554
|
"dayofweek.tuesday": 3,
|
|
@@ -1889,6 +1928,8 @@ function evalCall(node: Call, env: Env): Value {
|
|
|
1889
1928
|
) {
|
|
1890
1929
|
return evalRequestCall(fname!, node, env);
|
|
1891
1930
|
}
|
|
1931
|
+
const fpCall = evalFootprintCall(fname, node, env);
|
|
1932
|
+
if (fpCall !== undefined) return fpCall;
|
|
1892
1933
|
const arrVal = evalArrayCall(fname, node, env);
|
|
1893
1934
|
if (arrVal !== undefined) return arrVal;
|
|
1894
1935
|
const colorVal = evalColorCall(fname, node, env);
|
|
@@ -2577,14 +2618,66 @@ function packDrawings(book: DrawingBook): DrawingEvent[] | undefined {
|
|
|
2577
2618
|
return book.items.length === 0 ? undefined : book.items;
|
|
2578
2619
|
}
|
|
2579
2620
|
|
|
2580
|
-
|
|
2581
|
-
|
|
2582
|
-
|
|
2583
|
-
|
|
2621
|
+
const FREQ_ONCE_PER_BAR = "once_per_bar";
|
|
2622
|
+
const FREQ_ONCE_PER_BAR_CLOSE = "once_per_bar_close";
|
|
2623
|
+
const FREQ_ALL = "all";
|
|
2624
|
+
|
|
2625
|
+
const ALERT_FREQ_ALIASES: Record<string, string> = {
|
|
2626
|
+
once_per_bar: FREQ_ONCE_PER_BAR,
|
|
2627
|
+
freq_once_per_bar: FREQ_ONCE_PER_BAR,
|
|
2628
|
+
"alert.freq_once_per_bar": FREQ_ONCE_PER_BAR,
|
|
2629
|
+
once_per_bar_close: FREQ_ONCE_PER_BAR_CLOSE,
|
|
2630
|
+
freq_once_per_bar_close: FREQ_ONCE_PER_BAR_CLOSE,
|
|
2631
|
+
"alert.freq_once_per_bar_close": FREQ_ONCE_PER_BAR_CLOSE,
|
|
2632
|
+
all: FREQ_ALL,
|
|
2633
|
+
freq_all: FREQ_ALL,
|
|
2634
|
+
"alert.freq_all": FREQ_ALL,
|
|
2635
|
+
};
|
|
2636
|
+
|
|
2637
|
+
/** Python `normalize_alert_freq`. Unknown non-empty tokens are kept (still once-per-bar gated). */
|
|
2638
|
+
function normalizeAlertFreq(freq: unknown): string {
|
|
2639
|
+
if (freq == null) return FREQ_ONCE_PER_BAR;
|
|
2640
|
+
if (typeof freq === "number" && !Number.isFinite(freq)) return FREQ_ONCE_PER_BAR;
|
|
2641
|
+
const s = String(freq).trim().toLowerCase().replace(/ /g, "_");
|
|
2642
|
+
return ALERT_FREQ_ALIASES[s] ?? (s ? s : FREQ_ONCE_PER_BAR);
|
|
2643
|
+
}
|
|
2644
|
+
|
|
2645
|
+
/** Python `_bar_is_confirmed`. This host sets `barstate.isconfirmed` to 1 on every bar. */
|
|
2646
|
+
function barIsConfirmed(_env: Env): boolean {
|
|
2647
|
+
return true;
|
|
2648
|
+
}
|
|
2649
|
+
|
|
2650
|
+
/** Python `_should_fire_alert`. */
|
|
2651
|
+
function shouldFireAlert(env: Env, source: string, title: string, message: string, freq: string): boolean {
|
|
2652
|
+
const freqN = normalizeAlertFreq(freq);
|
|
2653
|
+
if (freqN === FREQ_ALL) return true;
|
|
2654
|
+
if (freqN === FREQ_ONCE_PER_BAR_CLOSE && !barIsConfirmed(env)) return false;
|
|
2655
|
+
const key = `${source}|${title}|${message}|${freqN}`;
|
|
2656
|
+
if (env.alertFireBars.get(key) === env.barIndex) return false;
|
|
2584
2657
|
env.alertFireBars.set(key, env.barIndex);
|
|
2658
|
+
return true;
|
|
2659
|
+
}
|
|
2660
|
+
|
|
2661
|
+
/** Python `_emit_alert`. Records on the drawing book (no separate alerts array). */
|
|
2662
|
+
function emitAlert(
|
|
2663
|
+
env: Env,
|
|
2664
|
+
message: string,
|
|
2665
|
+
freq: unknown,
|
|
2666
|
+
opts?: { title?: string; source?: string },
|
|
2667
|
+
): void {
|
|
2668
|
+
const source = opts?.source ?? "alert";
|
|
2669
|
+
const title = opts?.title ?? "";
|
|
2670
|
+
const freqN = normalizeAlertFreq(freq);
|
|
2671
|
+
if (!shouldFireAlert(env, source, title, message, freqN)) return;
|
|
2585
2672
|
env.drawings.alert(env.barIndex, message);
|
|
2586
2673
|
const ev = env.drawings.items[env.drawings.items.length - 1];
|
|
2587
|
-
if (ev
|
|
2674
|
+
if (ev == null) return;
|
|
2675
|
+
ev.extra = title ? { source, title, freq: freqN } : { source, freq: freqN };
|
|
2676
|
+
}
|
|
2677
|
+
|
|
2678
|
+
/** Python `_emit_alert(..., source="alertcondition", freq=once_per_bar)`. */
|
|
2679
|
+
function fireAlertCondition(env: Env, title: string, message: string): void {
|
|
2680
|
+
emitAlert(env, message, FREQ_ONCE_PER_BAR, { title, source: "alertcondition" });
|
|
2588
2681
|
}
|
|
2589
2682
|
|
|
2590
2683
|
function inputTitle(node: Call): string | null {
|
|
@@ -2895,7 +2988,7 @@ function evalRequestSecurity(node: Call, env: Env): Cell {
|
|
|
2895
2988
|
);
|
|
2896
2989
|
}
|
|
2897
2990
|
|
|
2898
|
-
function evalRequestCall(fname: string, node: Call, env: Env):
|
|
2991
|
+
function evalRequestCall(fname: string, node: Call, env: Env): Value {
|
|
2899
2992
|
if (
|
|
2900
2993
|
fname === "request.security" ||
|
|
2901
2994
|
fname === "request.security_lower_tf" ||
|
|
@@ -2916,10 +3009,75 @@ function evalRequestCall(fname: string, node: Call, env: Env): Cell {
|
|
|
2916
3009
|
const v = resolveRequest(fname, host, { seed });
|
|
2917
3010
|
return typeof v === "number" || v === null ? v : NA;
|
|
2918
3011
|
}
|
|
3012
|
+
if (fname === "request.footprint") {
|
|
3013
|
+
const numTicks = unwrap(evalExpr(callArg(node.args, 0, ["num_ticks"]), env));
|
|
3014
|
+
const va = unwrap(evalExpr(callArg(node.args, 1, ["va_percentage"]), env));
|
|
3015
|
+
const v = resolveRequest(fname, host, {
|
|
3016
|
+
num_ticks: numTicks ?? 100,
|
|
3017
|
+
va_percentage: va ?? 70,
|
|
3018
|
+
});
|
|
3019
|
+
return v instanceof Footprint ? v : NA;
|
|
3020
|
+
}
|
|
2919
3021
|
const v = resolveRequest(fname, host, {});
|
|
2920
3022
|
return typeof v === "number" || v === null ? v : NA;
|
|
2921
3023
|
}
|
|
2922
3024
|
|
|
3025
|
+
function evalFootprintCall(fname: string | null, node: Call, env: Env): Value | undefined {
|
|
3026
|
+
if (fname == null) return undefined;
|
|
3027
|
+
if (fname.startsWith("footprint.")) {
|
|
3028
|
+
const fp = evalExpr(callArg(node.args, 0, ["id", "footprint"]), env);
|
|
3029
|
+
switch (fname) {
|
|
3030
|
+
case "footprint.buy_volume":
|
|
3031
|
+
return footprintBuyVolume(fp);
|
|
3032
|
+
case "footprint.sell_volume":
|
|
3033
|
+
return footprintSellVolume(fp);
|
|
3034
|
+
case "footprint.delta":
|
|
3035
|
+
return footprintDelta(fp);
|
|
3036
|
+
case "footprint.total_volume":
|
|
3037
|
+
return footprintTotalVolume(fp);
|
|
3038
|
+
case "footprint.vah":
|
|
3039
|
+
return footprintVah(fp);
|
|
3040
|
+
case "footprint.val":
|
|
3041
|
+
return footprintVal(fp);
|
|
3042
|
+
case "footprint.poc":
|
|
3043
|
+
return footprintPoc(fp);
|
|
3044
|
+
case "footprint.rows": {
|
|
3045
|
+
const arr = new PineArray();
|
|
3046
|
+
for (const row of footprintRows(fp)) arr.push(row);
|
|
3047
|
+
return arr;
|
|
3048
|
+
}
|
|
3049
|
+
case "footprint.get_row_by_price":
|
|
3050
|
+
return footprintGetRowByPrice(fp, unwrap(evalExpr(callArg(node.args, 1, ["price"]), env)));
|
|
3051
|
+
default:
|
|
3052
|
+
return undefined;
|
|
3053
|
+
}
|
|
3054
|
+
}
|
|
3055
|
+
if (fname.startsWith("volume_row.")) {
|
|
3056
|
+
const vr = evalExpr(callArg(node.args, 0, ["id", "volume_row"]), env);
|
|
3057
|
+
switch (fname) {
|
|
3058
|
+
case "volume_row.up_price":
|
|
3059
|
+
return volumeRowUpPrice(vr);
|
|
3060
|
+
case "volume_row.down_price":
|
|
3061
|
+
return volumeRowDownPrice(vr);
|
|
3062
|
+
case "volume_row.buy_volume":
|
|
3063
|
+
return volumeRowBuyVolume(vr);
|
|
3064
|
+
case "volume_row.sell_volume":
|
|
3065
|
+
return volumeRowSellVolume(vr);
|
|
3066
|
+
case "volume_row.delta":
|
|
3067
|
+
return volumeRowDelta(vr);
|
|
3068
|
+
case "volume_row.total_volume":
|
|
3069
|
+
return volumeRowTotalVolume(vr);
|
|
3070
|
+
case "volume_row.has_buy_imbalance":
|
|
3071
|
+
return volumeRowHasBuyImbalance(vr);
|
|
3072
|
+
case "volume_row.has_sell_imbalance":
|
|
3073
|
+
return volumeRowHasSellImbalance(vr);
|
|
3074
|
+
default:
|
|
3075
|
+
return undefined;
|
|
3076
|
+
}
|
|
3077
|
+
}
|
|
3078
|
+
return undefined;
|
|
3079
|
+
}
|
|
3080
|
+
|
|
2923
3081
|
function evalMathCall(fname: string | null, node: Call, env: Env): Cell | undefined {
|
|
2924
3082
|
if (fname === "math.abs" || fname === "abs") {
|
|
2925
3083
|
return mathAbs(cellArg(node, env, 0, ["number", "x"]));
|
|
@@ -3951,6 +4109,157 @@ function evalExtraTa(fname: string | null, node: Call, env: Env, site: string):
|
|
|
3951
4109
|
const z = env.ta.zigzag(site, srcArg(node, env), th);
|
|
3952
4110
|
return tupleOf([z.high, z.low, z.dir]);
|
|
3953
4111
|
}
|
|
4112
|
+
// wave10a-start
|
|
4113
|
+
const wave10aArg = (index: number, names: string[], message: string): expr => {
|
|
4114
|
+
const arg = callArg(node.args, index, names);
|
|
4115
|
+
if (arg == null) runtimeError(message);
|
|
4116
|
+
return arg;
|
|
4117
|
+
};
|
|
4118
|
+
if (fname === "ta.hammer" || fname === "hammer") {
|
|
4119
|
+
const msg = "ta.hammer() requires 4 arguments: open, high, low, close";
|
|
4120
|
+
return env.ta.hammer(
|
|
4121
|
+
unwrap(evalExpr(wave10aArg(0, ["open"], msg), env)),
|
|
4122
|
+
unwrap(evalExpr(wave10aArg(1, ["high"], msg), env)),
|
|
4123
|
+
unwrap(evalExpr(wave10aArg(2, ["low"], msg), env)),
|
|
4124
|
+
unwrap(evalExpr(wave10aArg(3, ["close"], msg), env)),
|
|
4125
|
+
);
|
|
4126
|
+
}
|
|
4127
|
+
if (fname === "ta.engulfing" || fname === "engulfing") {
|
|
4128
|
+
const msg = "ta.engulfing() requires 4 arguments: open, high, low, close";
|
|
4129
|
+
unwrap(evalExpr(wave10aArg(0, ["open"], msg), env));
|
|
4130
|
+
unwrap(evalExpr(wave10aArg(1, ["high"], msg), env));
|
|
4131
|
+
unwrap(evalExpr(wave10aArg(2, ["low"], msg), env));
|
|
4132
|
+
unwrap(evalExpr(wave10aArg(3, ["close"], msg), env));
|
|
4133
|
+
return env.ta.engulfing();
|
|
4134
|
+
}
|
|
4135
|
+
if (fname === "ta.gap_detector" || fname === "gap_detector") {
|
|
4136
|
+
const msg = "ta.gap_detector() requires 3 arguments: high, low, prev_close";
|
|
4137
|
+
return env.ta.gapDetector(
|
|
4138
|
+
unwrap(evalExpr(wave10aArg(0, ["high"], msg), env)),
|
|
4139
|
+
unwrap(evalExpr(wave10aArg(1, ["low"], msg), env)),
|
|
4140
|
+
unwrap(evalExpr(wave10aArg(2, ["prev_close", "prev"], msg), env)),
|
|
4141
|
+
);
|
|
4142
|
+
}
|
|
4143
|
+
if (fname === "ta.double_top_bottom" || fname === "double_top_bottom") {
|
|
4144
|
+
const msg = "ta.double_top_bottom() requires 3 arguments";
|
|
4145
|
+
unwrap(evalExpr(wave10aArg(0, ["high"], msg), env));
|
|
4146
|
+
unwrap(evalExpr(wave10aArg(1, ["low"], msg), env));
|
|
4147
|
+
return env.ta.doubleTopBottom(unwrap(evalExpr(wave10aArg(2, ["period", "length"], msg), env)));
|
|
4148
|
+
}
|
|
4149
|
+
if (fname === "ta.inside_bar_pattern" || fname === "inside_bar_pattern") {
|
|
4150
|
+
const msg = "ta.inside_bar_pattern() requires 2 arguments";
|
|
4151
|
+
unwrap(evalExpr(wave10aArg(0, ["high"], msg), env));
|
|
4152
|
+
unwrap(evalExpr(wave10aArg(1, ["low"], msg), env));
|
|
4153
|
+
return env.ta.insideBarPattern();
|
|
4154
|
+
}
|
|
4155
|
+
if (fname === "ta.macd_signal" || fname === "macd_signal") {
|
|
4156
|
+
const msg = "ta.macd_signal() requires 2 arguments: macd_line, signal_line";
|
|
4157
|
+
return env.ta.macdSignal(
|
|
4158
|
+
unwrap(evalExpr(wave10aArg(0, ["macd_line", "macd"], msg), env)),
|
|
4159
|
+
unwrap(evalExpr(wave10aArg(1, ["signal_line", "signal"], msg), env)),
|
|
4160
|
+
);
|
|
4161
|
+
}
|
|
4162
|
+
if (fname === "ta.acceleration_factor" || fname === "acceleration_factor") {
|
|
4163
|
+
const msg = "ta.acceleration_factor() requires 2 arguments";
|
|
4164
|
+
return env.ta.accelerationFactor(
|
|
4165
|
+
site,
|
|
4166
|
+
unwrap(evalExpr(wave10aArg(0, ["momentum_list", "momentum", "source", "series"], msg), env)),
|
|
4167
|
+
unwrap(evalExpr(wave10aArg(1, ["period", "length"], msg), env)),
|
|
4168
|
+
);
|
|
4169
|
+
}
|
|
4170
|
+
if (fname === "ta.stoch_smooth" || fname === "stoch_smooth") {
|
|
4171
|
+
const msg = "ta.stoch_smooth() requires 6 arguments: high, low, close, period, smooth_k, smooth_d";
|
|
4172
|
+
return env.ta.stochSmooth(
|
|
4173
|
+
unwrap(evalExpr(wave10aArg(0, ["high"], msg), env)),
|
|
4174
|
+
unwrap(evalExpr(wave10aArg(1, ["low"], msg), env)),
|
|
4175
|
+
unwrap(evalExpr(wave10aArg(2, ["close"], msg), env)),
|
|
4176
|
+
unwrap(evalExpr(wave10aArg(3, ["period", "length"], msg), env)),
|
|
4177
|
+
unwrap(evalExpr(wave10aArg(4, ["smooth_k", "k"], msg), env)),
|
|
4178
|
+
unwrap(evalExpr(wave10aArg(5, ["smooth_d", "d"], msg), env)),
|
|
4179
|
+
);
|
|
4180
|
+
}
|
|
4181
|
+
// wave10a-end
|
|
4182
|
+
// wave10b-start
|
|
4183
|
+
const wave10bList = (index: number, names: string[]): Array<number | null> => {
|
|
4184
|
+
const raw = evalExpr(callArg(node.args, index, names), env);
|
|
4185
|
+
const arr = asArray(raw);
|
|
4186
|
+
if (arr == null) return [unwrap(raw)];
|
|
4187
|
+
const out: Array<number | null> = [];
|
|
4188
|
+
for (let i = 0; i < arr.size(); i++) out.push(arr.get(i));
|
|
4189
|
+
return out;
|
|
4190
|
+
};
|
|
4191
|
+
if (fname === "ta.voi" || fname === "voi") {
|
|
4192
|
+
return env.ta.voi(
|
|
4193
|
+
unwrap(evalExpr(callArg(node.args, 0, ["buy_volume", "buy"]), env)),
|
|
4194
|
+
unwrap(evalExpr(callArg(node.args, 1, ["sell_volume", "sell"]), env)),
|
|
4195
|
+
);
|
|
4196
|
+
}
|
|
4197
|
+
if (fname === "ta.bid_ask_imbalance" || fname === "bid_ask_imbalance") {
|
|
4198
|
+
return env.ta.bidAskImbalance(
|
|
4199
|
+
unwrap(evalExpr(callArg(node.args, 0, ["bid_size", "bid"]), env)),
|
|
4200
|
+
unwrap(evalExpr(callArg(node.args, 1, ["ask_size", "ask"]), env)),
|
|
4201
|
+
unwrap(evalExpr(callArg(node.args, 2, ["bid_price"]), env)),
|
|
4202
|
+
unwrap(evalExpr(callArg(node.args, 3, ["ask_price"]), env)),
|
|
4203
|
+
);
|
|
4204
|
+
}
|
|
4205
|
+
if (fname === "ta.rsi_oversold_overbought" || fname === "rsi_oversold_overbought") {
|
|
4206
|
+
const os = unwrap(evalExpr(callArg(node.args, 1, ["oversold_level", "oversold"]), env));
|
|
4207
|
+
const ob = unwrap(evalExpr(callArg(node.args, 2, ["overbought_level", "overbought"]), env));
|
|
4208
|
+
return env.ta.rsiOversoldOverbought(
|
|
4209
|
+
unwrap(evalExpr(callArg(node.args, 0, ["rsi_series", "rsi", "source"]), env)),
|
|
4210
|
+
os ?? Number.NaN,
|
|
4211
|
+
ob ?? Number.NaN,
|
|
4212
|
+
);
|
|
4213
|
+
}
|
|
4214
|
+
if (fname === "ta.rsi_divergence" || fname === "rsi_divergence") {
|
|
4215
|
+
const period = unwrap(evalExpr(callArg(node.args, 1, ["period", "length"]), env));
|
|
4216
|
+
return env.ta.rsiDivergence(wave10bList(0, ["rsi_series", "series", "source"]), period ?? 0);
|
|
4217
|
+
}
|
|
4218
|
+
if (fname === "ta.cumulative_delta" || fname === "cumulative_delta") {
|
|
4219
|
+
const period = unwrap(evalExpr(callArg(node.args, 2, ["period", "length"]), env));
|
|
4220
|
+
return env.ta.cumulativeDelta(
|
|
4221
|
+
site,
|
|
4222
|
+
unwrap(evalExpr(callArg(node.args, 0, ["close", "source"]), env)),
|
|
4223
|
+
unwrap(evalExpr(callArg(node.args, 1, ["volume"]), env)),
|
|
4224
|
+
period ?? 0,
|
|
4225
|
+
);
|
|
4226
|
+
}
|
|
4227
|
+
if (fname === "ta.volume_momentum" || fname === "volume_momentum") {
|
|
4228
|
+
const period = unwrap(evalExpr(callArg(node.args, 1, ["period", "length"]), env));
|
|
4229
|
+
return env.ta.volumeMomentum(
|
|
4230
|
+
site,
|
|
4231
|
+
unwrap(evalExpr(callArg(node.args, 0, ["volume", "source"]), env)),
|
|
4232
|
+
period ?? 0,
|
|
4233
|
+
);
|
|
4234
|
+
}
|
|
4235
|
+
if (fname === "ta.volume_profile_high" || fname === "volume_profile_high") {
|
|
4236
|
+
return env.ta.volumeProfileHigh(
|
|
4237
|
+
wave10bList(0, ["close", "price", "source", "series"]),
|
|
4238
|
+
wave10bList(1, ["volume"]),
|
|
4239
|
+
);
|
|
4240
|
+
}
|
|
4241
|
+
if (fname === "ta.volume_profile_low" || fname === "volume_profile_low") {
|
|
4242
|
+
const period = unwrap(evalExpr(callArg(node.args, 2, ["period", "length"]), env));
|
|
4243
|
+
const levels = unwrap(evalExpr(callArg(node.args, 3, ["levels"]), env));
|
|
4244
|
+
return env.ta.volumeProfileLow(
|
|
4245
|
+
site,
|
|
4246
|
+
unwrap(evalExpr(callArg(node.args, 0, ["close", "source"]), env)),
|
|
4247
|
+
unwrap(evalExpr(callArg(node.args, 1, ["volume"]), env)),
|
|
4248
|
+
period ?? 0,
|
|
4249
|
+
levels ?? 0,
|
|
4250
|
+
);
|
|
4251
|
+
}
|
|
4252
|
+
if (fname === "ta.volume_thrust" || fname === "volume_thrust") {
|
|
4253
|
+
const sens = unwrap(evalExpr(callArg(node.args, 3, ["sensitivity"]), env));
|
|
4254
|
+
return env.ta.volumeThrust(
|
|
4255
|
+
site,
|
|
4256
|
+
unwrap(evalExpr(callArg(node.args, 0, ["close", "source"]), env)),
|
|
4257
|
+
unwrap(evalExpr(callArg(node.args, 1, ["volume"]), env)),
|
|
4258
|
+
unwrap(evalExpr(callArg(node.args, 2, ["volume_sma", "vol_sma", "sma"]), env)),
|
|
4259
|
+
sens ?? 0.3,
|
|
4260
|
+
);
|
|
4261
|
+
}
|
|
4262
|
+
// wave10b-end
|
|
3954
4263
|
return undefined;
|
|
3955
4264
|
}
|
|
3956
4265
|
|
|
@@ -4827,8 +5136,16 @@ function evalDrawingCall(fname: string | null, node: Call, env: Env): Value | un
|
|
|
4827
5136
|
return book.boxNew(env.barIndex, extra);
|
|
4828
5137
|
}
|
|
4829
5138
|
if (fname === "alert") {
|
|
5139
|
+
// Python `_builtin_alert`: zero-arg `alert()` is a soft no-op.
|
|
5140
|
+
if (asArgs(node.args).length === 0) return NA;
|
|
4830
5141
|
const msg = evalAlertMessage(callArg(node.args, 0, ["message"]), env);
|
|
4831
|
-
|
|
5142
|
+
const freqExpr = callArg(node.args, 1, ["freq"]);
|
|
5143
|
+
let freq: unknown = null;
|
|
5144
|
+
if (freqExpr != null) {
|
|
5145
|
+
const v = evalExpr(freqExpr, env);
|
|
5146
|
+
freq = typeof v === "string" ? v : unwrap(v);
|
|
5147
|
+
}
|
|
5148
|
+
emitAlert(env, msg, freq, { source: "alert" });
|
|
4832
5149
|
return NA;
|
|
4833
5150
|
}
|
|
4834
5151
|
if (fname === "table.new") {
|
package/src/runtime/request.ts
CHANGED
|
@@ -9,8 +9,11 @@
|
|
|
9
9
|
* is the host's job (`bucketIndex` / `shouldForwardFill`).
|
|
10
10
|
* Lower-TF request without a feed is na — no invented intra-bar array.
|
|
11
11
|
* Interpret should call this from `evalCall`; this module is host-agnostic.
|
|
12
|
+
* `request.footprint` is the Python mock (not foreign-na).
|
|
12
13
|
*/
|
|
13
14
|
|
|
15
|
+
import { createFootprint, seedFootprintRng } from "./footprint.ts";
|
|
16
|
+
|
|
14
17
|
export type Cell = number | null;
|
|
15
18
|
|
|
16
19
|
export interface SecurityArgs {
|
|
@@ -29,7 +32,7 @@ export interface RequestHost {
|
|
|
29
32
|
* Named or positional args for `resolveRequest`.
|
|
30
33
|
* Security uses `symbol` / `timeframe` / `sameSymbolValue`;
|
|
31
34
|
* currency uses `from`/`to` (or `from_currency`/`to_currency`);
|
|
32
|
-
* seed uses `seed`.
|
|
35
|
+
* seed uses `seed`; footprint uses `num_ticks` / `va_percentage`.
|
|
33
36
|
*/
|
|
34
37
|
export interface RequestNamedArgs {
|
|
35
38
|
symbol?: string | null;
|
|
@@ -41,6 +44,8 @@ export interface RequestNamedArgs {
|
|
|
41
44
|
from_currency?: string | null;
|
|
42
45
|
to_currency?: string | null;
|
|
43
46
|
seed?: unknown;
|
|
47
|
+
num_ticks?: unknown;
|
|
48
|
+
va_percentage?: unknown;
|
|
44
49
|
}
|
|
45
50
|
|
|
46
51
|
export type RequestArgs = readonly unknown[] | RequestNamedArgs;
|
|
@@ -231,7 +236,7 @@ function bareTicker(symbol: string): string {
|
|
|
231
236
|
return colon >= 0 ? s.slice(colon + 1) : s;
|
|
232
237
|
}
|
|
233
238
|
|
|
234
|
-
/** Missing / foreign fundamentals, economic, quandl
|
|
239
|
+
/** Missing / foreign fundamentals, economic, quandl → na. */
|
|
235
240
|
export function resolveForeignNa(): Cell {
|
|
236
241
|
return null;
|
|
237
242
|
}
|
|
@@ -249,9 +254,10 @@ export function resolveCurrencyRate(
|
|
|
249
254
|
return null;
|
|
250
255
|
}
|
|
251
256
|
|
|
252
|
-
/** `request.seed` — record the seed (Python returns None). */
|
|
257
|
+
/** `request.seed` — record the seed and seed the footprint RNG (Python returns None). */
|
|
253
258
|
export function resolveSeed(seed?: unknown): Cell {
|
|
254
259
|
lastSeed = coerceSeed(seed ?? 0);
|
|
260
|
+
seedFootprintRng(lastSeed);
|
|
255
261
|
return null;
|
|
256
262
|
}
|
|
257
263
|
|
|
@@ -262,7 +268,7 @@ export function lastRequestSeed(): number | null {
|
|
|
262
268
|
/**
|
|
263
269
|
* Dispatch a `request.*` name. Security keeps `resolveSecurity` semantics;
|
|
264
270
|
* lower-TF without a feed is na; currency is 1.0 only for identical codes;
|
|
265
|
-
* everything else is na.
|
|
271
|
+
* footprint is the Python mock; everything else is na.
|
|
266
272
|
*/
|
|
267
273
|
export function resolveRequest(
|
|
268
274
|
fname: string,
|
|
@@ -284,6 +290,10 @@ export function resolveRequest(
|
|
|
284
290
|
if (fname === "request.seed") {
|
|
285
291
|
return resolveSeed(seedFromArgs(args));
|
|
286
292
|
}
|
|
293
|
+
if (fname === "request.footprint") {
|
|
294
|
+
const [numTicks, va] = footprintFromArgs(args);
|
|
295
|
+
return createFootprint(numTicks, va);
|
|
296
|
+
}
|
|
287
297
|
return resolveForeignNa();
|
|
288
298
|
}
|
|
289
299
|
|
|
@@ -326,6 +336,11 @@ function seedFromArgs(args: RequestArgs): unknown {
|
|
|
326
336
|
return args.seed ?? 0;
|
|
327
337
|
}
|
|
328
338
|
|
|
339
|
+
function footprintFromArgs(args: RequestArgs): [unknown, unknown] {
|
|
340
|
+
if (!isNamedArgs(args)) return [args[0] ?? 100, args[1] ?? 70];
|
|
341
|
+
return [args.num_ticks ?? 100, args.va_percentage ?? 70];
|
|
342
|
+
}
|
|
343
|
+
|
|
329
344
|
function currencyPair(
|
|
330
345
|
from: string | null | undefined,
|
|
331
346
|
to: string | null | undefined,
|