@hoox-sh/pynets 0.4.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 +34 -0
- package/README.md +9 -5
- package/dist/browser.js +68 -68
- package/dist/index.js +2196 -206
- 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.ts +17 -0
- package/src/runtime/compile/emit_call.ts +378 -7
- package/src/runtime/compile/helpers.ts +2 -1
- package/src/runtime/compile/runtime.ts +71 -6
- package/src/runtime/compile/strategy.ts +5 -2
- package/src/runtime/footprint.ts +272 -0
- package/src/runtime/interpret.ts +621 -36
- package/src/runtime/request.ts +19 -4
- 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/src/runtime/interpret.ts
CHANGED
|
@@ -114,7 +114,7 @@ import {
|
|
|
114
114
|
mathToDegrees,
|
|
115
115
|
mathToRadians,
|
|
116
116
|
} from "./math.ts";
|
|
117
|
-
import { utcPartsFromMs, timestamp, timeTradingDay, weekOfYear } from "./time.ts";
|
|
117
|
+
import { utcPartsFromMs, timestamp, timestampToMs, timeTradingDay, weekOfYear } from "./time.ts";
|
|
118
118
|
import {
|
|
119
119
|
LibraryModule,
|
|
120
120
|
LibraryRegistry,
|
|
@@ -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
|
|
|
@@ -565,6 +588,7 @@ export function interpretTree(
|
|
|
565
588
|
high: num(env.ctx.high) ?? undefined,
|
|
566
589
|
low: num(env.ctx.low) ?? undefined,
|
|
567
590
|
close: num(env.ctx.close) ?? undefined,
|
|
591
|
+
time: num(env.ctx.time) ?? undefined,
|
|
568
592
|
});
|
|
569
593
|
env.book.markOpenTrades(
|
|
570
594
|
num(env.ctx.high) ?? num(env.ctx.close) ?? 0,
|
|
@@ -914,6 +938,35 @@ function seriesGet(env: Env, id: string): PineSeries | undefined {
|
|
|
914
938
|
return env.series.get(id);
|
|
915
939
|
}
|
|
916
940
|
|
|
941
|
+
/** Bare series names Python's evaluator exposes as `PineSeries` objects. */
|
|
942
|
+
const BARE_SERIES_NAMES = new Set([
|
|
943
|
+
"open",
|
|
944
|
+
"high",
|
|
945
|
+
"low",
|
|
946
|
+
"close",
|
|
947
|
+
"volume",
|
|
948
|
+
"hl2",
|
|
949
|
+
"hlc3",
|
|
950
|
+
"ohlc4",
|
|
951
|
+
"hlcc4",
|
|
952
|
+
"time",
|
|
953
|
+
"time_close",
|
|
954
|
+
]);
|
|
955
|
+
|
|
956
|
+
/**
|
|
957
|
+
* True when an argument expression is a series reference rather than a scalar.
|
|
958
|
+
* Python handlers that do `args[0] if isinstance(args[0], list) else [args[0]]`
|
|
959
|
+
* (e.g. `ta.parkinson`, `ta.garman_klass`) see the `PineSeries` object in bar
|
|
960
|
+
* mode and therefore return `na`.
|
|
961
|
+
*/
|
|
962
|
+
function isSeriesExpr(value: expr, env: Env): boolean {
|
|
963
|
+
if (value.kind === "Name") {
|
|
964
|
+
if (BARE_SERIES_NAMES.has(value.id)) return true;
|
|
965
|
+
return seriesGet(env, value.id) != null;
|
|
966
|
+
}
|
|
967
|
+
return false;
|
|
968
|
+
}
|
|
969
|
+
|
|
917
970
|
function bindName(env: Env, id: string, value: Value): void {
|
|
918
971
|
const stored: Value = typeof value === "number" && !Number.isFinite(value) ? NA : value;
|
|
919
972
|
const frame = activeFrame(env);
|
|
@@ -1510,6 +1563,22 @@ export const MATH_CONSTANTS: Record<string, Value> = {
|
|
|
1510
1563
|
"plot.linestyle_solid": "linestyle_solid",
|
|
1511
1564
|
"plot.linestyle_dashed": "linestyle_dashed",
|
|
1512
1565
|
"plot.linestyle_dotted": "linestyle_dotted",
|
|
1566
|
+
// Python plotting.py PlotStyle tokens — longer than hline's "solid".
|
|
1567
|
+
"plot.style_line": "style_line",
|
|
1568
|
+
"plot.style_linebr": "style_linebr",
|
|
1569
|
+
"plot.style_stepline": "style_stepline",
|
|
1570
|
+
"plot.style_steplinebr": "style_steplinebr",
|
|
1571
|
+
"plot.style_stepline_diamond": "style_stepline_diamond",
|
|
1572
|
+
"plot.style_histogram": "style_histogram",
|
|
1573
|
+
"plot.style_columns": "style_columns",
|
|
1574
|
+
"plot.style_cross": "style_cross",
|
|
1575
|
+
"plot.style_area": "style_area",
|
|
1576
|
+
"plot.style_areabr": "style_areabr",
|
|
1577
|
+
"plot.style_circles": "style_circles",
|
|
1578
|
+
// Python alerts.py `_alerts_builtin_map`.
|
|
1579
|
+
"alert.freq_once_per_bar": "once_per_bar",
|
|
1580
|
+
"alert.freq_once_per_bar_close": "once_per_bar_close",
|
|
1581
|
+
"alert.freq_all": "all",
|
|
1513
1582
|
"dayofweek.sunday": 1,
|
|
1514
1583
|
"dayofweek.monday": 2,
|
|
1515
1584
|
"dayofweek.tuesday": 3,
|
|
@@ -1889,6 +1958,8 @@ function evalCall(node: Call, env: Env): Value {
|
|
|
1889
1958
|
) {
|
|
1890
1959
|
return evalRequestCall(fname!, node, env);
|
|
1891
1960
|
}
|
|
1961
|
+
const fpCall = evalFootprintCall(fname, node, env);
|
|
1962
|
+
if (fpCall !== undefined) return fpCall;
|
|
1892
1963
|
const arrVal = evalArrayCall(fname, node, env);
|
|
1893
1964
|
if (arrVal !== undefined) return arrVal;
|
|
1894
1965
|
const colorVal = evalColorCall(fname, node, env);
|
|
@@ -2066,6 +2137,7 @@ function evalCall(node: Call, env: Env): Value {
|
|
|
2066
2137
|
time: num(env.ctx.time) ?? undefined,
|
|
2067
2138
|
oca_name: evalAsString(callArg(node.args, -1, ["oca_name"]), env),
|
|
2068
2139
|
oca_type: evalAsString(callArg(node.args, -1, ["oca_type"]), env),
|
|
2140
|
+
deferMarket: true,
|
|
2069
2141
|
});
|
|
2070
2142
|
return NA;
|
|
2071
2143
|
}
|
|
@@ -2074,12 +2146,10 @@ function evalCall(node: Call, env: Env): Value {
|
|
|
2074
2146
|
const mark = num(env.ctx.close);
|
|
2075
2147
|
const qtyExpr = callArg(node.args, 1, ["qty"]);
|
|
2076
2148
|
const qtyRaw = qtyExpr == null ? undefined : unwrap(evalExpr(qtyExpr, env));
|
|
2077
|
-
|
|
2078
|
-
env.
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
});
|
|
2082
|
-
} else env.book.close(env.barIndex, id);
|
|
2149
|
+
env.book.placeClose(env.barIndex, id, mark ?? undefined, {
|
|
2150
|
+
time: num(env.ctx.time) ?? undefined,
|
|
2151
|
+
qty: qtyRaw ?? undefined,
|
|
2152
|
+
});
|
|
2083
2153
|
return NA;
|
|
2084
2154
|
}
|
|
2085
2155
|
if (fname === "strategy.exit") {
|
|
@@ -2099,17 +2169,12 @@ function evalCall(node: Call, env: Env): Value {
|
|
|
2099
2169
|
trail_offset: unwrap(evalExpr(callArg(node.args, 10, ["trail_offset"]), env)),
|
|
2100
2170
|
price: num(env.ctx.close) ?? undefined,
|
|
2101
2171
|
time: num(env.ctx.time) ?? undefined,
|
|
2102
|
-
|
|
2103
|
-
open: num(env.ctx.open) ?? undefined,
|
|
2104
|
-
high: num(env.ctx.high) ?? undefined,
|
|
2105
|
-
low: num(env.ctx.low) ?? undefined,
|
|
2106
|
-
close: num(env.ctx.close) ?? undefined,
|
|
2107
|
-
},
|
|
2172
|
+
deferMarket: true,
|
|
2108
2173
|
});
|
|
2109
2174
|
return NA;
|
|
2110
2175
|
}
|
|
2111
2176
|
if (fname === "strategy.close_all") {
|
|
2112
|
-
env.book.
|
|
2177
|
+
env.book.placeCloseAll(env.barIndex, num(env.ctx.close) ?? undefined);
|
|
2113
2178
|
return NA;
|
|
2114
2179
|
}
|
|
2115
2180
|
if (fname === "strategy.cancel") {
|
|
@@ -2132,6 +2197,7 @@ function evalCall(node: Call, env: Env): Value {
|
|
|
2132
2197
|
time: num(env.ctx.time) ?? undefined,
|
|
2133
2198
|
oca_name: evalAsString(callArg(node.args, -1, ["oca_name"]), env),
|
|
2134
2199
|
oca_type: evalAsString(callArg(node.args, -1, ["oca_type"]), env),
|
|
2200
|
+
deferMarket: true,
|
|
2135
2201
|
});
|
|
2136
2202
|
return NA;
|
|
2137
2203
|
}
|
|
@@ -2577,14 +2643,66 @@ function packDrawings(book: DrawingBook): DrawingEvent[] | undefined {
|
|
|
2577
2643
|
return book.items.length === 0 ? undefined : book.items;
|
|
2578
2644
|
}
|
|
2579
2645
|
|
|
2580
|
-
|
|
2581
|
-
|
|
2582
|
-
|
|
2583
|
-
|
|
2646
|
+
const FREQ_ONCE_PER_BAR = "once_per_bar";
|
|
2647
|
+
const FREQ_ONCE_PER_BAR_CLOSE = "once_per_bar_close";
|
|
2648
|
+
const FREQ_ALL = "all";
|
|
2649
|
+
|
|
2650
|
+
const ALERT_FREQ_ALIASES: Record<string, string> = {
|
|
2651
|
+
once_per_bar: FREQ_ONCE_PER_BAR,
|
|
2652
|
+
freq_once_per_bar: FREQ_ONCE_PER_BAR,
|
|
2653
|
+
"alert.freq_once_per_bar": FREQ_ONCE_PER_BAR,
|
|
2654
|
+
once_per_bar_close: FREQ_ONCE_PER_BAR_CLOSE,
|
|
2655
|
+
freq_once_per_bar_close: FREQ_ONCE_PER_BAR_CLOSE,
|
|
2656
|
+
"alert.freq_once_per_bar_close": FREQ_ONCE_PER_BAR_CLOSE,
|
|
2657
|
+
all: FREQ_ALL,
|
|
2658
|
+
freq_all: FREQ_ALL,
|
|
2659
|
+
"alert.freq_all": FREQ_ALL,
|
|
2660
|
+
};
|
|
2661
|
+
|
|
2662
|
+
/** Python `normalize_alert_freq`. Unknown non-empty tokens are kept (still once-per-bar gated). */
|
|
2663
|
+
function normalizeAlertFreq(freq: unknown): string {
|
|
2664
|
+
if (freq == null) return FREQ_ONCE_PER_BAR;
|
|
2665
|
+
if (typeof freq === "number" && !Number.isFinite(freq)) return FREQ_ONCE_PER_BAR;
|
|
2666
|
+
const s = String(freq).trim().toLowerCase().replace(/ /g, "_");
|
|
2667
|
+
return ALERT_FREQ_ALIASES[s] ?? (s ? s : FREQ_ONCE_PER_BAR);
|
|
2668
|
+
}
|
|
2669
|
+
|
|
2670
|
+
/** Python `_bar_is_confirmed`. This host sets `barstate.isconfirmed` to 1 on every bar. */
|
|
2671
|
+
function barIsConfirmed(_env: Env): boolean {
|
|
2672
|
+
return true;
|
|
2673
|
+
}
|
|
2674
|
+
|
|
2675
|
+
/** Python `_should_fire_alert`. */
|
|
2676
|
+
function shouldFireAlert(env: Env, source: string, title: string, message: string, freq: string): boolean {
|
|
2677
|
+
const freqN = normalizeAlertFreq(freq);
|
|
2678
|
+
if (freqN === FREQ_ALL) return true;
|
|
2679
|
+
if (freqN === FREQ_ONCE_PER_BAR_CLOSE && !barIsConfirmed(env)) return false;
|
|
2680
|
+
const key = `${source}|${title}|${message}|${freqN}`;
|
|
2681
|
+
if (env.alertFireBars.get(key) === env.barIndex) return false;
|
|
2584
2682
|
env.alertFireBars.set(key, env.barIndex);
|
|
2683
|
+
return true;
|
|
2684
|
+
}
|
|
2685
|
+
|
|
2686
|
+
/** Python `_emit_alert`. Records on the drawing book (no separate alerts array). */
|
|
2687
|
+
function emitAlert(
|
|
2688
|
+
env: Env,
|
|
2689
|
+
message: string,
|
|
2690
|
+
freq: unknown,
|
|
2691
|
+
opts?: { title?: string; source?: string },
|
|
2692
|
+
): void {
|
|
2693
|
+
const source = opts?.source ?? "alert";
|
|
2694
|
+
const title = opts?.title ?? "";
|
|
2695
|
+
const freqN = normalizeAlertFreq(freq);
|
|
2696
|
+
if (!shouldFireAlert(env, source, title, message, freqN)) return;
|
|
2585
2697
|
env.drawings.alert(env.barIndex, message);
|
|
2586
2698
|
const ev = env.drawings.items[env.drawings.items.length - 1];
|
|
2587
|
-
if (ev
|
|
2699
|
+
if (ev == null) return;
|
|
2700
|
+
ev.extra = title ? { source, title, freq: freqN } : { source, freq: freqN };
|
|
2701
|
+
}
|
|
2702
|
+
|
|
2703
|
+
/** Python `_emit_alert(..., source="alertcondition", freq=once_per_bar)`. */
|
|
2704
|
+
function fireAlertCondition(env: Env, title: string, message: string): void {
|
|
2705
|
+
emitAlert(env, message, FREQ_ONCE_PER_BAR, { title, source: "alertcondition" });
|
|
2588
2706
|
}
|
|
2589
2707
|
|
|
2590
2708
|
function inputTitle(node: Call): string | null {
|
|
@@ -2895,7 +3013,7 @@ function evalRequestSecurity(node: Call, env: Env): Cell {
|
|
|
2895
3013
|
);
|
|
2896
3014
|
}
|
|
2897
3015
|
|
|
2898
|
-
function evalRequestCall(fname: string, node: Call, env: Env):
|
|
3016
|
+
function evalRequestCall(fname: string, node: Call, env: Env): Value {
|
|
2899
3017
|
if (
|
|
2900
3018
|
fname === "request.security" ||
|
|
2901
3019
|
fname === "request.security_lower_tf" ||
|
|
@@ -2916,10 +3034,75 @@ function evalRequestCall(fname: string, node: Call, env: Env): Cell {
|
|
|
2916
3034
|
const v = resolveRequest(fname, host, { seed });
|
|
2917
3035
|
return typeof v === "number" || v === null ? v : NA;
|
|
2918
3036
|
}
|
|
3037
|
+
if (fname === "request.footprint") {
|
|
3038
|
+
const numTicks = unwrap(evalExpr(callArg(node.args, 0, ["num_ticks"]), env));
|
|
3039
|
+
const va = unwrap(evalExpr(callArg(node.args, 1, ["va_percentage"]), env));
|
|
3040
|
+
const v = resolveRequest(fname, host, {
|
|
3041
|
+
num_ticks: numTicks ?? 100,
|
|
3042
|
+
va_percentage: va ?? 70,
|
|
3043
|
+
});
|
|
3044
|
+
return v instanceof Footprint ? v : NA;
|
|
3045
|
+
}
|
|
2919
3046
|
const v = resolveRequest(fname, host, {});
|
|
2920
3047
|
return typeof v === "number" || v === null ? v : NA;
|
|
2921
3048
|
}
|
|
2922
3049
|
|
|
3050
|
+
function evalFootprintCall(fname: string | null, node: Call, env: Env): Value | undefined {
|
|
3051
|
+
if (fname == null) return undefined;
|
|
3052
|
+
if (fname.startsWith("footprint.")) {
|
|
3053
|
+
const fp = evalExpr(callArg(node.args, 0, ["id", "footprint"]), env);
|
|
3054
|
+
switch (fname) {
|
|
3055
|
+
case "footprint.buy_volume":
|
|
3056
|
+
return footprintBuyVolume(fp);
|
|
3057
|
+
case "footprint.sell_volume":
|
|
3058
|
+
return footprintSellVolume(fp);
|
|
3059
|
+
case "footprint.delta":
|
|
3060
|
+
return footprintDelta(fp);
|
|
3061
|
+
case "footprint.total_volume":
|
|
3062
|
+
return footprintTotalVolume(fp);
|
|
3063
|
+
case "footprint.vah":
|
|
3064
|
+
return footprintVah(fp);
|
|
3065
|
+
case "footprint.val":
|
|
3066
|
+
return footprintVal(fp);
|
|
3067
|
+
case "footprint.poc":
|
|
3068
|
+
return footprintPoc(fp);
|
|
3069
|
+
case "footprint.rows": {
|
|
3070
|
+
const arr = new PineArray();
|
|
3071
|
+
for (const row of footprintRows(fp)) arr.push(row);
|
|
3072
|
+
return arr;
|
|
3073
|
+
}
|
|
3074
|
+
case "footprint.get_row_by_price":
|
|
3075
|
+
return footprintGetRowByPrice(fp, unwrap(evalExpr(callArg(node.args, 1, ["price"]), env)));
|
|
3076
|
+
default:
|
|
3077
|
+
return undefined;
|
|
3078
|
+
}
|
|
3079
|
+
}
|
|
3080
|
+
if (fname.startsWith("volume_row.")) {
|
|
3081
|
+
const vr = evalExpr(callArg(node.args, 0, ["id", "volume_row"]), env);
|
|
3082
|
+
switch (fname) {
|
|
3083
|
+
case "volume_row.up_price":
|
|
3084
|
+
return volumeRowUpPrice(vr);
|
|
3085
|
+
case "volume_row.down_price":
|
|
3086
|
+
return volumeRowDownPrice(vr);
|
|
3087
|
+
case "volume_row.buy_volume":
|
|
3088
|
+
return volumeRowBuyVolume(vr);
|
|
3089
|
+
case "volume_row.sell_volume":
|
|
3090
|
+
return volumeRowSellVolume(vr);
|
|
3091
|
+
case "volume_row.delta":
|
|
3092
|
+
return volumeRowDelta(vr);
|
|
3093
|
+
case "volume_row.total_volume":
|
|
3094
|
+
return volumeRowTotalVolume(vr);
|
|
3095
|
+
case "volume_row.has_buy_imbalance":
|
|
3096
|
+
return volumeRowHasBuyImbalance(vr);
|
|
3097
|
+
case "volume_row.has_sell_imbalance":
|
|
3098
|
+
return volumeRowHasSellImbalance(vr);
|
|
3099
|
+
default:
|
|
3100
|
+
return undefined;
|
|
3101
|
+
}
|
|
3102
|
+
}
|
|
3103
|
+
return undefined;
|
|
3104
|
+
}
|
|
3105
|
+
|
|
2923
3106
|
function evalMathCall(fname: string | null, node: Call, env: Env): Cell | undefined {
|
|
2924
3107
|
if (fname === "math.abs" || fname === "abs") {
|
|
2925
3108
|
return mathAbs(cellArg(node, env, 0, ["number", "x"]));
|
|
@@ -3968,11 +4151,11 @@ function evalExtraTa(fname: string | null, node: Call, env: Env, site: string):
|
|
|
3968
4151
|
}
|
|
3969
4152
|
if (fname === "ta.engulfing" || fname === "engulfing") {
|
|
3970
4153
|
const msg = "ta.engulfing() requires 4 arguments: open, high, low, close";
|
|
3971
|
-
unwrap(evalExpr(wave10aArg(0, ["open"], msg), env));
|
|
4154
|
+
const open = unwrap(evalExpr(wave10aArg(0, ["open"], msg), env));
|
|
3972
4155
|
unwrap(evalExpr(wave10aArg(1, ["high"], msg), env));
|
|
3973
4156
|
unwrap(evalExpr(wave10aArg(2, ["low"], msg), env));
|
|
3974
|
-
unwrap(evalExpr(wave10aArg(3, ["close"], msg), env));
|
|
3975
|
-
return env.ta.engulfing();
|
|
4157
|
+
const close = unwrap(evalExpr(wave10aArg(3, ["close"], msg), env));
|
|
4158
|
+
return env.ta.engulfing(site, open, close);
|
|
3976
4159
|
}
|
|
3977
4160
|
if (fname === "ta.gap_detector" || fname === "gap_detector") {
|
|
3978
4161
|
const msg = "ta.gap_detector() requires 3 arguments: high, low, prev_close";
|
|
@@ -3984,15 +4167,20 @@ function evalExtraTa(fname: string | null, node: Call, env: Env, site: string):
|
|
|
3984
4167
|
}
|
|
3985
4168
|
if (fname === "ta.double_top_bottom" || fname === "double_top_bottom") {
|
|
3986
4169
|
const msg = "ta.double_top_bottom() requires 3 arguments";
|
|
3987
|
-
unwrap(evalExpr(wave10aArg(0, ["high"], msg), env));
|
|
3988
|
-
unwrap(evalExpr(wave10aArg(1, ["low"], msg), env));
|
|
3989
|
-
return env.ta.doubleTopBottom(
|
|
4170
|
+
const high = unwrap(evalExpr(wave10aArg(0, ["high"], msg), env));
|
|
4171
|
+
const low = unwrap(evalExpr(wave10aArg(1, ["low"], msg), env));
|
|
4172
|
+
return env.ta.doubleTopBottom(
|
|
4173
|
+
site,
|
|
4174
|
+
high,
|
|
4175
|
+
low,
|
|
4176
|
+
unwrap(evalExpr(wave10aArg(2, ["period", "length"], msg), env)),
|
|
4177
|
+
);
|
|
3990
4178
|
}
|
|
3991
4179
|
if (fname === "ta.inside_bar_pattern" || fname === "inside_bar_pattern") {
|
|
3992
4180
|
const msg = "ta.inside_bar_pattern() requires 2 arguments";
|
|
3993
|
-
unwrap(evalExpr(wave10aArg(0, ["high"], msg), env));
|
|
3994
|
-
unwrap(evalExpr(wave10aArg(1, ["low"], msg), env));
|
|
3995
|
-
return env.ta.insideBarPattern();
|
|
4181
|
+
const high = unwrap(evalExpr(wave10aArg(0, ["high"], msg), env));
|
|
4182
|
+
const low = unwrap(evalExpr(wave10aArg(1, ["low"], msg), env));
|
|
4183
|
+
return env.ta.insideBarPattern(site, high, low);
|
|
3996
4184
|
}
|
|
3997
4185
|
if (fname === "ta.macd_signal" || fname === "macd_signal") {
|
|
3998
4186
|
const msg = "ta.macd_signal() requires 2 arguments: macd_line, signal_line";
|
|
@@ -4012,6 +4200,7 @@ function evalExtraTa(fname: string | null, node: Call, env: Env, site: string):
|
|
|
4012
4200
|
if (fname === "ta.stoch_smooth" || fname === "stoch_smooth") {
|
|
4013
4201
|
const msg = "ta.stoch_smooth() requires 6 arguments: high, low, close, period, smooth_k, smooth_d";
|
|
4014
4202
|
return env.ta.stochSmooth(
|
|
4203
|
+
site,
|
|
4015
4204
|
unwrap(evalExpr(wave10aArg(0, ["high"], msg), env)),
|
|
4016
4205
|
unwrap(evalExpr(wave10aArg(1, ["low"], msg), env)),
|
|
4017
4206
|
unwrap(evalExpr(wave10aArg(2, ["close"], msg), env)),
|
|
@@ -4055,7 +4244,11 @@ function evalExtraTa(fname: string | null, node: Call, env: Env, site: string):
|
|
|
4055
4244
|
}
|
|
4056
4245
|
if (fname === "ta.rsi_divergence" || fname === "rsi_divergence") {
|
|
4057
4246
|
const period = unwrap(evalExpr(callArg(node.args, 1, ["period", "length"]), env));
|
|
4058
|
-
return env.ta.rsiDivergence(
|
|
4247
|
+
return env.ta.rsiDivergence(
|
|
4248
|
+
site,
|
|
4249
|
+
seriesLastCell(evalExpr(callArg(node.args, 0, ["rsi_series", "series", "source"]), env)),
|
|
4250
|
+
period ?? 0,
|
|
4251
|
+
);
|
|
4059
4252
|
}
|
|
4060
4253
|
if (fname === "ta.cumulative_delta" || fname === "cumulative_delta") {
|
|
4061
4254
|
const period = unwrap(evalExpr(callArg(node.args, 2, ["period", "length"]), env));
|
|
@@ -4102,6 +4295,366 @@ function evalExtraTa(fname: string | null, node: Call, env: Env, site: string):
|
|
|
4102
4295
|
);
|
|
4103
4296
|
}
|
|
4104
4297
|
// wave10b-end
|
|
4298
|
+
// wave11-start — advanced / statistical ta.* surface
|
|
4299
|
+
const wave11 = (index: number, names: string[], message: string): Value =>
|
|
4300
|
+
unwrap(evalExpr(wave10aArg(index, names, message), env));
|
|
4301
|
+
const wave11n = (index: number, names: string[], message: string): Cell =>
|
|
4302
|
+
num(wave11(index, names, message));
|
|
4303
|
+
if (fname === "ta.sma_weighted" || fname === "sma_weighted") {
|
|
4304
|
+
const msg = "ta.sma_weighted() requires at least 2 arguments: series, period";
|
|
4305
|
+
const weightArg = callArg(node.args, 2, ["weight_type", "weight"]);
|
|
4306
|
+
return env.ta.smaWeighted(
|
|
4307
|
+
site,
|
|
4308
|
+
wave11n(0, ["series", "source"], msg),
|
|
4309
|
+
wave11n(1, ["period", "length"], msg),
|
|
4310
|
+
weightArg == null ? "linear" : evalExpr(weightArg, env),
|
|
4311
|
+
);
|
|
4312
|
+
}
|
|
4313
|
+
if (fname === "ta.apo" || fname === "apo") {
|
|
4314
|
+
const msg = "ta.apo() requires 3 arguments: series, fast_period, slow_period";
|
|
4315
|
+
return env.ta.apo(
|
|
4316
|
+
site,
|
|
4317
|
+
wave11n(0, ["series", "source"], msg),
|
|
4318
|
+
wave11n(1, ["fast_period", "fast"], msg),
|
|
4319
|
+
wave11n(2, ["slow_period", "slow"], msg),
|
|
4320
|
+
);
|
|
4321
|
+
}
|
|
4322
|
+
if (fname === "ta.atr_normalized" || fname === "atr_normalized") {
|
|
4323
|
+
const msg = "ta.atr_normalized() requires 4 arguments: high, low, close, period";
|
|
4324
|
+
return env.ta.atrNormalized(
|
|
4325
|
+
site,
|
|
4326
|
+
wave11n(0, ["high"], msg),
|
|
4327
|
+
wave11n(1, ["low"], msg),
|
|
4328
|
+
wave11n(2, ["close"], msg),
|
|
4329
|
+
wave11n(3, ["period", "length"], msg),
|
|
4330
|
+
);
|
|
4331
|
+
}
|
|
4332
|
+
if (fname === "ta.beta" || fname === "beta") {
|
|
4333
|
+
const msg = "ta.beta() requires 3 arguments: series1, series2, length";
|
|
4334
|
+
return env.ta.beta(
|
|
4335
|
+
site,
|
|
4336
|
+
wave11n(0, ["series1", "series", "source"], msg),
|
|
4337
|
+
wave11n(1, ["series2", "series_b", "other"], msg),
|
|
4338
|
+
wave11n(2, ["length", "period"], msg),
|
|
4339
|
+
);
|
|
4340
|
+
}
|
|
4341
|
+
if (fname === "ta.r_squared" || fname === "r_squared") {
|
|
4342
|
+
const msg = "ta.r_squared() requires 3 arguments: series1, series2, length";
|
|
4343
|
+
return env.ta.rSquared(
|
|
4344
|
+
site,
|
|
4345
|
+
wave11n(0, ["series1", "series", "source"], msg),
|
|
4346
|
+
wave11n(1, ["series2", "series_b", "other"], msg),
|
|
4347
|
+
wave11n(2, ["length", "period"], msg),
|
|
4348
|
+
);
|
|
4349
|
+
}
|
|
4350
|
+
if (fname === "ta.comovement" || fname === "comovement") {
|
|
4351
|
+
const msg = "ta.comovement() requires 3 arguments: series1, series2, length";
|
|
4352
|
+
return env.ta.comovement(
|
|
4353
|
+
site,
|
|
4354
|
+
wave11n(0, ["series1", "series", "source"], msg),
|
|
4355
|
+
wave11n(1, ["series2", "series_b", "other"], msg),
|
|
4356
|
+
wave11n(2, ["length", "period"], msg),
|
|
4357
|
+
);
|
|
4358
|
+
}
|
|
4359
|
+
if (fname === "ta.skewness" || fname === "skewness") {
|
|
4360
|
+
const msg = "ta.skewness() requires 2 arguments: series, period";
|
|
4361
|
+
return env.ta.skewness(
|
|
4362
|
+
site,
|
|
4363
|
+
wave11n(0, ["series", "source"], msg),
|
|
4364
|
+
wave11n(1, ["period", "length"], msg),
|
|
4365
|
+
);
|
|
4366
|
+
}
|
|
4367
|
+
if (fname === "ta.kurtosis" || fname === "kurtosis") {
|
|
4368
|
+
const msg = "ta.kurtosis() requires 2 arguments: series, period";
|
|
4369
|
+
return env.ta.kurtosis(
|
|
4370
|
+
site,
|
|
4371
|
+
wave11n(0, ["series", "source"], msg),
|
|
4372
|
+
wave11n(1, ["period", "length"], msg),
|
|
4373
|
+
);
|
|
4374
|
+
}
|
|
4375
|
+
if (fname === "ta.volume_weighted_momentum" || fname === "volume_weighted_momentum") {
|
|
4376
|
+
const msg = "ta.volume_weighted_momentum() requires 3 arguments: series, volume, length";
|
|
4377
|
+
return env.ta.volumeWeightedMomentum(
|
|
4378
|
+
site,
|
|
4379
|
+
wave11n(0, ["series", "source"], msg),
|
|
4380
|
+
wave11n(1, ["volume"], msg),
|
|
4381
|
+
wave11n(2, ["length", "period"], msg),
|
|
4382
|
+
);
|
|
4383
|
+
}
|
|
4384
|
+
if (fname === "ta.parkinson" || fname === "parkinson") {
|
|
4385
|
+
const msg = "ta.parkinson() requires 2 arguments: high, low";
|
|
4386
|
+
const highArg = wave10aArg(0, ["high"], msg);
|
|
4387
|
+
const lowArg = wave10aArg(1, ["low"], msg);
|
|
4388
|
+
if (isSeriesExpr(highArg, env) || isSeriesExpr(lowArg, env)) return NA;
|
|
4389
|
+
return env.ta.parkinson(num(evalExpr(highArg, env)), num(evalExpr(lowArg, env)));
|
|
4390
|
+
}
|
|
4391
|
+
if (fname === "ta.garman_klass" || fname === "garman_klass") {
|
|
4392
|
+
const msg = "ta.garman_klass() requires 4 arguments: high, low, close, open";
|
|
4393
|
+
const argsExpr = [
|
|
4394
|
+
wave10aArg(0, ["high"], msg),
|
|
4395
|
+
wave10aArg(1, ["low"], msg),
|
|
4396
|
+
wave10aArg(2, ["close"], msg),
|
|
4397
|
+
wave10aArg(3, ["open"], msg),
|
|
4398
|
+
];
|
|
4399
|
+
if (argsExpr.some((a) => isSeriesExpr(a, env))) return NA;
|
|
4400
|
+
return env.ta.garmanKlass(
|
|
4401
|
+
num(evalExpr(argsExpr[0]!, env)),
|
|
4402
|
+
num(evalExpr(argsExpr[1]!, env)),
|
|
4403
|
+
num(evalExpr(argsExpr[2]!, env)),
|
|
4404
|
+
num(evalExpr(argsExpr[3]!, env)),
|
|
4405
|
+
);
|
|
4406
|
+
}
|
|
4407
|
+
if (fname === "ta.mean_reversion_score" || fname === "mean_reversion_score") {
|
|
4408
|
+
const msg = "ta.mean_reversion_score() requires 4 arguments";
|
|
4409
|
+
return env.ta.meanReversionScore(
|
|
4410
|
+
site,
|
|
4411
|
+
wave11n(0, ["close", "source"], msg),
|
|
4412
|
+
wave11n(1, ["sma"], msg),
|
|
4413
|
+
wave11n(2, ["stdev", "std_dev"], msg),
|
|
4414
|
+
wave11n(3, ["period", "length"], msg),
|
|
4415
|
+
);
|
|
4416
|
+
}
|
|
4417
|
+
if (fname === "ta.market_condition" || fname === "market_condition") {
|
|
4418
|
+
const msg = "ta.market_condition() requires 4 arguments";
|
|
4419
|
+
return env.ta.marketCondition(
|
|
4420
|
+
site,
|
|
4421
|
+
wave11n(0, ["close", "source"], msg),
|
|
4422
|
+
wave11n(1, ["atr"], msg),
|
|
4423
|
+
wave11n(2, ["sma_period", "sma_length"], msg),
|
|
4424
|
+
wave11n(3, ["stdev_period", "stdev_length"], msg),
|
|
4425
|
+
);
|
|
4426
|
+
}
|
|
4427
|
+
if (fname === "ta.order_flow_imbalance" || fname === "order_flow_imbalance") {
|
|
4428
|
+
const msg = "ta.order_flow_imbalance() requires 5 arguments";
|
|
4429
|
+
return env.ta.orderFlowImbalance(
|
|
4430
|
+
site,
|
|
4431
|
+
wave11n(0, ["high"], msg),
|
|
4432
|
+
wave11n(1, ["low"], msg),
|
|
4433
|
+
wave11n(2, ["close"], msg),
|
|
4434
|
+
wave11n(3, ["volume"], msg),
|
|
4435
|
+
wave11n(4, ["period", "length"], msg),
|
|
4436
|
+
);
|
|
4437
|
+
}
|
|
4438
|
+
if (fname === "ta.liquidity_score" || fname === "liquidity_score") {
|
|
4439
|
+
const msg = "ta.liquidity_score() requires 4 arguments";
|
|
4440
|
+
return env.ta.liquidityScore(
|
|
4441
|
+
site,
|
|
4442
|
+
wave11n(0, ["volume"], msg),
|
|
4443
|
+
wave11n(1, ["volatility"], msg),
|
|
4444
|
+
wave11n(2, ["spread"], msg),
|
|
4445
|
+
wave11n(3, ["period", "length"], msg),
|
|
4446
|
+
);
|
|
4447
|
+
}
|
|
4448
|
+
if (fname === "ta.fear_greed_index" || fname === "fear_greed_index") {
|
|
4449
|
+
const msg = "ta.fear_greed_index() requires 4 arguments";
|
|
4450
|
+
return env.ta.fearGreedIndex(
|
|
4451
|
+
wave11n(0, ["rsi"], msg),
|
|
4452
|
+
wave11n(1, ["vix"], msg),
|
|
4453
|
+
wave11n(2, ["put_call", "put_call_ratio"], msg),
|
|
4454
|
+
wave11n(3, ["breadth"], msg),
|
|
4455
|
+
);
|
|
4456
|
+
}
|
|
4457
|
+
if (fname === "ta.crowd_sentiment" || fname === "crowd_sentiment") {
|
|
4458
|
+
const msg = "ta.crowd_sentiment() requires 3 arguments";
|
|
4459
|
+
return env.ta.crowdSentiment(
|
|
4460
|
+
wave11n(0, ["price_agreement", "price"], msg),
|
|
4461
|
+
wave11n(1, ["volume_agreement", "volume"], msg),
|
|
4462
|
+
wave11n(2, ["time_agreement", "time"], msg),
|
|
4463
|
+
);
|
|
4464
|
+
}
|
|
4465
|
+
if (fname === "ta.smart_money_flow" || fname === "smart_money_flow") {
|
|
4466
|
+
const msg = "ta.smart_money_flow() requires 4 arguments";
|
|
4467
|
+
return env.ta.smartMoneyFlow(
|
|
4468
|
+
wave11n(0, ["price_change"], msg),
|
|
4469
|
+
wave11n(1, ["volume"], msg),
|
|
4470
|
+
wave11n(2, ["time_since_high", "time_high"], msg),
|
|
4471
|
+
wave11n(3, ["time_since_low", "time_low"], msg),
|
|
4472
|
+
);
|
|
4473
|
+
}
|
|
4474
|
+
if (fname === "ta.trend_confirmation_score" || fname === "trend_confirmation_score") {
|
|
4475
|
+
const msg = "ta.trend_confirmation_score() requires 6 arguments";
|
|
4476
|
+
wave11n(5, ["support_distance"], msg); // required, unused (Python parity)
|
|
4477
|
+
return env.ta.trendConfirmationScore(
|
|
4478
|
+
wave11n(0, ["momentum"], msg),
|
|
4479
|
+
wave11n(1, ["trend_alignment"], msg),
|
|
4480
|
+
wave11n(2, ["strength"], msg),
|
|
4481
|
+
wave11n(3, ["rsi"], msg),
|
|
4482
|
+
wave11n(4, ["rsi_alignment"], msg),
|
|
4483
|
+
);
|
|
4484
|
+
}
|
|
4485
|
+
if (fname === "ta.max_loss_level" || fname === "max_loss_level") {
|
|
4486
|
+
const msg = "ta.max_loss_level() requires 3 arguments";
|
|
4487
|
+
return env.ta.maxLossLevel(
|
|
4488
|
+
wave11n(0, ["entry", "entry_price"], msg),
|
|
4489
|
+
wave11n(1, ["account", "account_size"], msg),
|
|
4490
|
+
wave11n(2, ["max_loss_pct", "max_loss_percent"], msg),
|
|
4491
|
+
);
|
|
4492
|
+
}
|
|
4493
|
+
if (fname === "ta.profit_lock_level" || fname === "profit_lock_level") {
|
|
4494
|
+
const msg = "ta.profit_lock_level() requires 4 arguments";
|
|
4495
|
+
return env.ta.profitLockLevel(
|
|
4496
|
+
wave11n(0, ["entry", "entry_price"], msg),
|
|
4497
|
+
wave11n(1, ["current", "current_price"], msg),
|
|
4498
|
+
wave11n(2, ["trail_pct", "trail_percent"], msg),
|
|
4499
|
+
wave11n(3, ["direction"], msg),
|
|
4500
|
+
);
|
|
4501
|
+
}
|
|
4502
|
+
// wave11-end
|
|
4503
|
+
// wave12-start — risk / sizing / timing analytics
|
|
4504
|
+
const wave12s = (index: number, names: string[], message: string): Value =>
|
|
4505
|
+
evalExpr(wave10aArg(index, names, message), env);
|
|
4506
|
+
if (fname === "ta.position_sizing" || fname === "position_sizing") {
|
|
4507
|
+
const msg = "ta.position_sizing() requires 4 arguments";
|
|
4508
|
+
return env.ta.positionSizing(
|
|
4509
|
+
wave11n(0, ["account", "account_size"], msg),
|
|
4510
|
+
wave11n(1, ["risk_pct", "risk_percent"], msg),
|
|
4511
|
+
wave11n(2, ["entry", "entry_price"], msg),
|
|
4512
|
+
wave11n(3, ["stop", "stop_price"], msg),
|
|
4513
|
+
);
|
|
4514
|
+
}
|
|
4515
|
+
if (fname === "ta.kelly_criterion" || fname === "kelly_criterion") {
|
|
4516
|
+
const msg = "ta.kelly_criterion() requires 3 arguments";
|
|
4517
|
+
return env.ta.kellyCriterion(
|
|
4518
|
+
wave11n(0, ["win_rate"], msg),
|
|
4519
|
+
wave11n(1, ["avg_win"], msg),
|
|
4520
|
+
wave11n(2, ["avg_loss"], msg),
|
|
4521
|
+
);
|
|
4522
|
+
}
|
|
4523
|
+
if (fname === "ta.risk_reward_ratio" || fname === "risk_reward_ratio") {
|
|
4524
|
+
const msg = "ta.risk_reward_ratio() requires 3 arguments";
|
|
4525
|
+
return env.ta.riskRewardRatio(
|
|
4526
|
+
wave11n(0, ["entry", "entry_price"], msg),
|
|
4527
|
+
wave11n(1, ["stop", "stop_price"], msg),
|
|
4528
|
+
wave11n(2, ["target", "target_price"], msg),
|
|
4529
|
+
);
|
|
4530
|
+
}
|
|
4531
|
+
if (fname === "ta.risk_reward_asymmetry" || fname === "risk_reward_asymmetry") {
|
|
4532
|
+
const msg = "ta.risk_reward_asymmetry() requires 4 arguments";
|
|
4533
|
+
return env.ta.riskRewardAsymmetry(
|
|
4534
|
+
wave11n(0, ["entry", "entry_price"], msg),
|
|
4535
|
+
wave11n(1, ["stop", "stop_price"], msg),
|
|
4536
|
+
wave11n(2, ["target", "target_price"], msg),
|
|
4537
|
+
wave11n(3, ["win_rate"], msg),
|
|
4538
|
+
);
|
|
4539
|
+
}
|
|
4540
|
+
if (fname === "ta.probability_of_movement" || fname === "probability_of_movement") {
|
|
4541
|
+
const msg = "ta.probability_of_movement() requires 4 arguments";
|
|
4542
|
+
return env.ta.probabilityOfMovement(
|
|
4543
|
+
wave11n(0, ["current", "current_price"], msg),
|
|
4544
|
+
wave11n(1, ["target", "target_price"], msg),
|
|
4545
|
+
wave11n(2, ["atr"], msg),
|
|
4546
|
+
wave11n(3, ["period", "length"], msg),
|
|
4547
|
+
);
|
|
4548
|
+
}
|
|
4549
|
+
if (fname === "ta.trend_strength" || fname === "trend_strength") {
|
|
4550
|
+
const msg = "ta.trend_strength() requires 3 arguments";
|
|
4551
|
+
return env.ta.trendStrength(
|
|
4552
|
+
wave11n(0, ["price", "source"], msg),
|
|
4553
|
+
wave11n(1, ["adx"], msg),
|
|
4554
|
+
wave11n(2, ["rsi"], msg),
|
|
4555
|
+
);
|
|
4556
|
+
}
|
|
4557
|
+
if (fname === "ta.breakeven_level" || fname === "breakeven_level") {
|
|
4558
|
+
const msg = "ta.breakeven_level() requires 5 arguments";
|
|
4559
|
+
return env.ta.breakevenLevel(
|
|
4560
|
+
wave11n(0, ["entry", "entry_price"], msg),
|
|
4561
|
+
wave11n(1, ["quantity", "qty"], msg),
|
|
4562
|
+
wave11n(2, ["commission"], msg),
|
|
4563
|
+
wave11n(3, ["slippage"], msg),
|
|
4564
|
+
wave11n(4, ["direction"], msg),
|
|
4565
|
+
);
|
|
4566
|
+
}
|
|
4567
|
+
if (fname === "ta.market_structure_pivot" || fname === "market_structure_pivot") {
|
|
4568
|
+
const msg = "ta.market_structure_pivot() requires 5 arguments";
|
|
4569
|
+
wave11n(0, ["high"], msg);
|
|
4570
|
+
wave11n(1, ["low"], msg);
|
|
4571
|
+
const close = wave11n(2, ["close"], msg);
|
|
4572
|
+
wave11n(3, ["left", "left_bars"], msg);
|
|
4573
|
+
wave11n(4, ["right", "right_bars"], msg);
|
|
4574
|
+
return env.ta.marketStructurePivot(close);
|
|
4575
|
+
}
|
|
4576
|
+
if (fname === "ta.position_sizing_score" || fname === "position_sizing_score") {
|
|
4577
|
+
const msg = "ta.position_sizing_score() requires 4 arguments";
|
|
4578
|
+
return env.ta.positionSizingScore(
|
|
4579
|
+
wave11n(0, ["account_risk", "risk"], msg),
|
|
4580
|
+
wave11n(1, ["volatility"], msg),
|
|
4581
|
+
wave11n(2, ["conviction"], msg),
|
|
4582
|
+
wave11n(3, ["correlation"], msg),
|
|
4583
|
+
);
|
|
4584
|
+
}
|
|
4585
|
+
if (fname === "ta.trailing_exit_level" || fname === "trailing_exit_level") {
|
|
4586
|
+
const msg = "ta.trailing_exit_level() requires 5 arguments";
|
|
4587
|
+
return env.ta.trailingExitLevel(
|
|
4588
|
+
wave11n(0, ["entry", "entry_price"], msg),
|
|
4589
|
+
wave11n(1, ["current", "current_price"], msg),
|
|
4590
|
+
wave11n(2, ["atr"], msg),
|
|
4591
|
+
wave11n(3, ["multiplier", "mult"], msg),
|
|
4592
|
+
wave11n(4, ["step"], msg),
|
|
4593
|
+
);
|
|
4594
|
+
}
|
|
4595
|
+
if (fname === "ta.optimal_entry_zone" || fname === "optimal_entry_zone") {
|
|
4596
|
+
const msg = "ta.optimal_entry_zone() requires 4 arguments";
|
|
4597
|
+
wave11n(0, ["high"], msg);
|
|
4598
|
+
wave11n(1, ["low"], msg);
|
|
4599
|
+
const close = wave11n(2, ["close"], msg);
|
|
4600
|
+
wave11n(3, ["period", "length"], msg);
|
|
4601
|
+
return env.ta.optimalEntryZone(close);
|
|
4602
|
+
}
|
|
4603
|
+
if (fname === "ta.mean_reversion_entry" || fname === "mean_reversion_entry") {
|
|
4604
|
+
const msg = "ta.mean_reversion_entry() requires 5 arguments";
|
|
4605
|
+
return env.ta.meanReversionEntry(
|
|
4606
|
+
wave11n(0, ["price", "source"], msg),
|
|
4607
|
+
wave11n(1, ["sma"], msg),
|
|
4608
|
+
wave11n(2, ["stdev", "std_dev"], msg),
|
|
4609
|
+
wave11n(4, ["threshold"], msg),
|
|
4610
|
+
);
|
|
4611
|
+
}
|
|
4612
|
+
if (fname === "ta.multi_timeframe_signal" || fname === "multi_timeframe_signal") {
|
|
4613
|
+
const msg = "ta.multi_timeframe_signal() requires 6 arguments";
|
|
4614
|
+
return env.ta.multiTimeframeSignal(
|
|
4615
|
+
wave11n(0, ["s1", "signal1"], msg),
|
|
4616
|
+
wave11n(1, ["s2", "signal2"], msg),
|
|
4617
|
+
wave11n(2, ["s3", "signal3"], msg),
|
|
4618
|
+
wave11n(3, ["w1", "weight1"], msg),
|
|
4619
|
+
wave11n(4, ["w2", "weight2"], msg),
|
|
4620
|
+
wave11n(5, ["w3", "weight3"], msg),
|
|
4621
|
+
);
|
|
4622
|
+
}
|
|
4623
|
+
if (fname === "ta.market_timing_index" || fname === "market_timing_index") {
|
|
4624
|
+
const msg = "ta.market_timing_index() requires 4 arguments";
|
|
4625
|
+
return env.ta.marketTimingIndex(
|
|
4626
|
+
wave11n(0, ["trend"], msg),
|
|
4627
|
+
wave11n(1, ["momentum"], msg),
|
|
4628
|
+
wave11n(2, ["volatility"], msg),
|
|
4629
|
+
wave11n(3, ["sentiment"], msg),
|
|
4630
|
+
);
|
|
4631
|
+
}
|
|
4632
|
+
if (fname === "ta.regime_adaptive_signal" || fname === "regime_adaptive_signal") {
|
|
4633
|
+
const msg = "ta.regime_adaptive_signal() requires 4 arguments";
|
|
4634
|
+
const strength = wave11n(0, ["signal_strength", "strength"], msg);
|
|
4635
|
+
const volRegime = wave12s(1, ["volatility_regime", "vol_regime"], msg);
|
|
4636
|
+
const trendRegime = wave12s(2, ["trend_regime"], msg);
|
|
4637
|
+
wave11n(3, ["period", "length"], msg);
|
|
4638
|
+
return env.ta.regimeAdaptiveSignal(strength, volRegime, trendRegime);
|
|
4639
|
+
}
|
|
4640
|
+
if (fname === "ta.strategy_score" || fname === "strategy_score") {
|
|
4641
|
+
const msg = "ta.strategy_score() requires 4 arguments";
|
|
4642
|
+
return env.ta.strategyScore(
|
|
4643
|
+
wave11n(0, ["rsi"], msg),
|
|
4644
|
+
wave11n(1, ["macd"], msg),
|
|
4645
|
+
wave11n(2, ["ema_cross"], msg),
|
|
4646
|
+
wave11n(3, ["trend"], msg),
|
|
4647
|
+
);
|
|
4648
|
+
}
|
|
4649
|
+
if (fname === "ta.breakout_detection" || fname === "breakout_detection") {
|
|
4650
|
+
const msg = "ta.breakout_detection() requires 3 arguments";
|
|
4651
|
+
return env.ta.breakoutDetection(
|
|
4652
|
+
wave11n(0, ["close", "source"], msg),
|
|
4653
|
+
wave11n(1, ["resistance"], msg),
|
|
4654
|
+
wave11n(2, ["support"], msg),
|
|
4655
|
+
);
|
|
4656
|
+
}
|
|
4657
|
+
// wave12-end
|
|
4105
4658
|
return undefined;
|
|
4106
4659
|
}
|
|
4107
4660
|
|
|
@@ -4113,6 +4666,16 @@ function hlc3(env: Env): Cell {
|
|
|
4113
4666
|
return (h + l + c) / 3;
|
|
4114
4667
|
}
|
|
4115
4668
|
|
|
4669
|
+
/** Python `_series_last`: array → last cell; scalar → the value itself. */
|
|
4670
|
+
function seriesLastCell(value: Value): number | null {
|
|
4671
|
+
if (value instanceof PineArray) {
|
|
4672
|
+
const n = value.size();
|
|
4673
|
+
return n > 0 ? value.get(n - 1) : null;
|
|
4674
|
+
}
|
|
4675
|
+
const v = unwrap(value);
|
|
4676
|
+
return typeof v === "number" && Number.isFinite(v) ? v : null;
|
|
4677
|
+
}
|
|
4678
|
+
|
|
4116
4679
|
function asArray(value: Value): PineArray | null {
|
|
4117
4680
|
if (value instanceof PineArray) return value;
|
|
4118
4681
|
if (value && typeof value === "object" && "__array" in value) {
|
|
@@ -4978,16 +5541,37 @@ function evalDrawingCall(fname: string | null, node: Call, env: Env): Value | un
|
|
|
4978
5541
|
return book.boxNew(env.barIndex, extra);
|
|
4979
5542
|
}
|
|
4980
5543
|
if (fname === "alert") {
|
|
5544
|
+
// Python `_builtin_alert`: zero-arg `alert()` is a soft no-op.
|
|
5545
|
+
if (asArgs(node.args).length === 0) return NA;
|
|
4981
5546
|
const msg = evalAlertMessage(callArg(node.args, 0, ["message"]), env);
|
|
4982
|
-
|
|
5547
|
+
const freqExpr = callArg(node.args, 1, ["freq"]);
|
|
5548
|
+
let freq: unknown = null;
|
|
5549
|
+
if (freqExpr != null) {
|
|
5550
|
+
const v = evalExpr(freqExpr, env);
|
|
5551
|
+
freq = typeof v === "string" ? v : unwrap(v);
|
|
5552
|
+
}
|
|
5553
|
+
emitAlert(env, msg, freq, { source: "alert" });
|
|
4983
5554
|
return NA;
|
|
4984
5555
|
}
|
|
4985
5556
|
if (fname === "table.new") {
|
|
5557
|
+
// Pine positional order is (position, columns, rows, bgcolor,
|
|
5558
|
+
// frame_color, frame_width, border_color, border_width, force_overlay).
|
|
5559
|
+
// Python `_handle_table_new` SoT (columns then rows, not rows then columns).
|
|
4986
5560
|
const extra: Record<string, unknown> = {};
|
|
4987
5561
|
const position = drawRaw(node, env, 0, ["position"]);
|
|
4988
5562
|
if (position !== undefined) extra.position = position;
|
|
4989
|
-
putFinite(extra, "
|
|
4990
|
-
putFinite(extra, "
|
|
5563
|
+
putFinite(extra, "columns", drawNum(node, env, 1, ["columns"]));
|
|
5564
|
+
putFinite(extra, "rows", drawNum(node, env, 2, ["rows"]));
|
|
5565
|
+
const bgcolor = drawRaw(node, env, 3, ["bgcolor"]);
|
|
5566
|
+
if (bgcolor !== undefined) extra.bgcolor = bgcolor;
|
|
5567
|
+
const frameColor = drawRaw(node, env, 4, ["frame_color"]);
|
|
5568
|
+
if (frameColor !== undefined) extra.frame_color = frameColor;
|
|
5569
|
+
putFinite(extra, "frame_width", drawNum(node, env, 5, ["frame_width"]));
|
|
5570
|
+
const borderColor = drawRaw(node, env, 6, ["border_color"]);
|
|
5571
|
+
if (borderColor !== undefined) extra.border_color = borderColor;
|
|
5572
|
+
putFinite(extra, "border_width", drawNum(node, env, 7, ["border_width"]));
|
|
5573
|
+
const forceOverlay = drawRaw(node, env, 8, ["force_overlay"]);
|
|
5574
|
+
if (forceOverlay !== undefined) extra.force_overlay = forceOverlay;
|
|
4991
5575
|
return book.tableNew(env.barIndex, extra);
|
|
4992
5576
|
}
|
|
4993
5577
|
if (fname === "polyline.new") {
|
|
@@ -6028,9 +6612,10 @@ function syminfoPrefix(node: Call, env: Env): string {
|
|
|
6028
6612
|
}
|
|
6029
6613
|
|
|
6030
6614
|
function calendarMs(node: Call, env: Env): number | null {
|
|
6615
|
+
// Python `_timestamp_to_ms`: chart hosts store opens in seconds.
|
|
6031
6616
|
const arg = callArg(node.args, 0, ["time", "timestamp"]);
|
|
6032
|
-
if (arg == null) return num(env.ctx.time);
|
|
6033
|
-
return unwrap(evalExpr(arg, env));
|
|
6617
|
+
if (arg == null) return timestampToMs(num(env.ctx.time));
|
|
6618
|
+
return timestampToMs(unwrap(evalExpr(arg, env)));
|
|
6034
6619
|
}
|
|
6035
6620
|
|
|
6036
6621
|
function evalLibraryMemberCall(mod: LibraryModule, attr: string, node: Call, env: Env): Value {
|