@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/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,
|
|
@@ -588,6 +588,7 @@ export function interpretTree(
|
|
|
588
588
|
high: num(env.ctx.high) ?? undefined,
|
|
589
589
|
low: num(env.ctx.low) ?? undefined,
|
|
590
590
|
close: num(env.ctx.close) ?? undefined,
|
|
591
|
+
time: num(env.ctx.time) ?? undefined,
|
|
591
592
|
});
|
|
592
593
|
env.book.markOpenTrades(
|
|
593
594
|
num(env.ctx.high) ?? num(env.ctx.close) ?? 0,
|
|
@@ -937,6 +938,35 @@ function seriesGet(env: Env, id: string): PineSeries | undefined {
|
|
|
937
938
|
return env.series.get(id);
|
|
938
939
|
}
|
|
939
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
|
+
|
|
940
970
|
function bindName(env: Env, id: string, value: Value): void {
|
|
941
971
|
const stored: Value = typeof value === "number" && !Number.isFinite(value) ? NA : value;
|
|
942
972
|
const frame = activeFrame(env);
|
|
@@ -2107,6 +2137,7 @@ function evalCall(node: Call, env: Env): Value {
|
|
|
2107
2137
|
time: num(env.ctx.time) ?? undefined,
|
|
2108
2138
|
oca_name: evalAsString(callArg(node.args, -1, ["oca_name"]), env),
|
|
2109
2139
|
oca_type: evalAsString(callArg(node.args, -1, ["oca_type"]), env),
|
|
2140
|
+
deferMarket: true,
|
|
2110
2141
|
});
|
|
2111
2142
|
return NA;
|
|
2112
2143
|
}
|
|
@@ -2115,12 +2146,10 @@ function evalCall(node: Call, env: Env): Value {
|
|
|
2115
2146
|
const mark = num(env.ctx.close);
|
|
2116
2147
|
const qtyExpr = callArg(node.args, 1, ["qty"]);
|
|
2117
2148
|
const qtyRaw = qtyExpr == null ? undefined : unwrap(evalExpr(qtyExpr, env));
|
|
2118
|
-
|
|
2119
|
-
env.
|
|
2120
|
-
|
|
2121
|
-
|
|
2122
|
-
});
|
|
2123
|
-
} 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
|
+
});
|
|
2124
2153
|
return NA;
|
|
2125
2154
|
}
|
|
2126
2155
|
if (fname === "strategy.exit") {
|
|
@@ -2140,17 +2169,12 @@ function evalCall(node: Call, env: Env): Value {
|
|
|
2140
2169
|
trail_offset: unwrap(evalExpr(callArg(node.args, 10, ["trail_offset"]), env)),
|
|
2141
2170
|
price: num(env.ctx.close) ?? undefined,
|
|
2142
2171
|
time: num(env.ctx.time) ?? undefined,
|
|
2143
|
-
|
|
2144
|
-
open: num(env.ctx.open) ?? undefined,
|
|
2145
|
-
high: num(env.ctx.high) ?? undefined,
|
|
2146
|
-
low: num(env.ctx.low) ?? undefined,
|
|
2147
|
-
close: num(env.ctx.close) ?? undefined,
|
|
2148
|
-
},
|
|
2172
|
+
deferMarket: true,
|
|
2149
2173
|
});
|
|
2150
2174
|
return NA;
|
|
2151
2175
|
}
|
|
2152
2176
|
if (fname === "strategy.close_all") {
|
|
2153
|
-
env.book.
|
|
2177
|
+
env.book.placeCloseAll(env.barIndex, num(env.ctx.close) ?? undefined);
|
|
2154
2178
|
return NA;
|
|
2155
2179
|
}
|
|
2156
2180
|
if (fname === "strategy.cancel") {
|
|
@@ -2173,6 +2197,7 @@ function evalCall(node: Call, env: Env): Value {
|
|
|
2173
2197
|
time: num(env.ctx.time) ?? undefined,
|
|
2174
2198
|
oca_name: evalAsString(callArg(node.args, -1, ["oca_name"]), env),
|
|
2175
2199
|
oca_type: evalAsString(callArg(node.args, -1, ["oca_type"]), env),
|
|
2200
|
+
deferMarket: true,
|
|
2176
2201
|
});
|
|
2177
2202
|
return NA;
|
|
2178
2203
|
}
|
|
@@ -4126,11 +4151,11 @@ function evalExtraTa(fname: string | null, node: Call, env: Env, site: string):
|
|
|
4126
4151
|
}
|
|
4127
4152
|
if (fname === "ta.engulfing" || fname === "engulfing") {
|
|
4128
4153
|
const msg = "ta.engulfing() requires 4 arguments: open, high, low, close";
|
|
4129
|
-
unwrap(evalExpr(wave10aArg(0, ["open"], msg), env));
|
|
4154
|
+
const open = unwrap(evalExpr(wave10aArg(0, ["open"], msg), env));
|
|
4130
4155
|
unwrap(evalExpr(wave10aArg(1, ["high"], msg), env));
|
|
4131
4156
|
unwrap(evalExpr(wave10aArg(2, ["low"], msg), env));
|
|
4132
|
-
unwrap(evalExpr(wave10aArg(3, ["close"], msg), env));
|
|
4133
|
-
return env.ta.engulfing();
|
|
4157
|
+
const close = unwrap(evalExpr(wave10aArg(3, ["close"], msg), env));
|
|
4158
|
+
return env.ta.engulfing(site, open, close);
|
|
4134
4159
|
}
|
|
4135
4160
|
if (fname === "ta.gap_detector" || fname === "gap_detector") {
|
|
4136
4161
|
const msg = "ta.gap_detector() requires 3 arguments: high, low, prev_close";
|
|
@@ -4142,15 +4167,20 @@ function evalExtraTa(fname: string | null, node: Call, env: Env, site: string):
|
|
|
4142
4167
|
}
|
|
4143
4168
|
if (fname === "ta.double_top_bottom" || fname === "double_top_bottom") {
|
|
4144
4169
|
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(
|
|
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
|
+
);
|
|
4148
4178
|
}
|
|
4149
4179
|
if (fname === "ta.inside_bar_pattern" || fname === "inside_bar_pattern") {
|
|
4150
4180
|
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();
|
|
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);
|
|
4154
4184
|
}
|
|
4155
4185
|
if (fname === "ta.macd_signal" || fname === "macd_signal") {
|
|
4156
4186
|
const msg = "ta.macd_signal() requires 2 arguments: macd_line, signal_line";
|
|
@@ -4170,6 +4200,7 @@ function evalExtraTa(fname: string | null, node: Call, env: Env, site: string):
|
|
|
4170
4200
|
if (fname === "ta.stoch_smooth" || fname === "stoch_smooth") {
|
|
4171
4201
|
const msg = "ta.stoch_smooth() requires 6 arguments: high, low, close, period, smooth_k, smooth_d";
|
|
4172
4202
|
return env.ta.stochSmooth(
|
|
4203
|
+
site,
|
|
4173
4204
|
unwrap(evalExpr(wave10aArg(0, ["high"], msg), env)),
|
|
4174
4205
|
unwrap(evalExpr(wave10aArg(1, ["low"], msg), env)),
|
|
4175
4206
|
unwrap(evalExpr(wave10aArg(2, ["close"], msg), env)),
|
|
@@ -4213,7 +4244,11 @@ function evalExtraTa(fname: string | null, node: Call, env: Env, site: string):
|
|
|
4213
4244
|
}
|
|
4214
4245
|
if (fname === "ta.rsi_divergence" || fname === "rsi_divergence") {
|
|
4215
4246
|
const period = unwrap(evalExpr(callArg(node.args, 1, ["period", "length"]), env));
|
|
4216
|
-
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
|
+
);
|
|
4217
4252
|
}
|
|
4218
4253
|
if (fname === "ta.cumulative_delta" || fname === "cumulative_delta") {
|
|
4219
4254
|
const period = unwrap(evalExpr(callArg(node.args, 2, ["period", "length"]), env));
|
|
@@ -4260,6 +4295,366 @@ function evalExtraTa(fname: string | null, node: Call, env: Env, site: string):
|
|
|
4260
4295
|
);
|
|
4261
4296
|
}
|
|
4262
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
|
|
4263
4658
|
return undefined;
|
|
4264
4659
|
}
|
|
4265
4660
|
|
|
@@ -4271,6 +4666,16 @@ function hlc3(env: Env): Cell {
|
|
|
4271
4666
|
return (h + l + c) / 3;
|
|
4272
4667
|
}
|
|
4273
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
|
+
|
|
4274
4679
|
function asArray(value: Value): PineArray | null {
|
|
4275
4680
|
if (value instanceof PineArray) return value;
|
|
4276
4681
|
if (value && typeof value === "object" && "__array" in value) {
|
|
@@ -5149,11 +5554,24 @@ function evalDrawingCall(fname: string | null, node: Call, env: Env): Value | un
|
|
|
5149
5554
|
return NA;
|
|
5150
5555
|
}
|
|
5151
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).
|
|
5152
5560
|
const extra: Record<string, unknown> = {};
|
|
5153
5561
|
const position = drawRaw(node, env, 0, ["position"]);
|
|
5154
5562
|
if (position !== undefined) extra.position = position;
|
|
5155
|
-
putFinite(extra, "
|
|
5156
|
-
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;
|
|
5157
5575
|
return book.tableNew(env.barIndex, extra);
|
|
5158
5576
|
}
|
|
5159
5577
|
if (fname === "polyline.new") {
|
|
@@ -6194,9 +6612,10 @@ function syminfoPrefix(node: Call, env: Env): string {
|
|
|
6194
6612
|
}
|
|
6195
6613
|
|
|
6196
6614
|
function calendarMs(node: Call, env: Env): number | null {
|
|
6615
|
+
// Python `_timestamp_to_ms`: chart hosts store opens in seconds.
|
|
6197
6616
|
const arg = callArg(node.args, 0, ["time", "timestamp"]);
|
|
6198
|
-
if (arg == null) return num(env.ctx.time);
|
|
6199
|
-
return unwrap(evalExpr(arg, env));
|
|
6617
|
+
if (arg == null) return timestampToMs(num(env.ctx.time));
|
|
6618
|
+
return timestampToMs(unwrap(evalExpr(arg, env)));
|
|
6200
6619
|
}
|
|
6201
6620
|
|
|
6202
6621
|
function evalLibraryMemberCall(mod: LibraryModule, attr: string, node: Call, env: Env): Value {
|
package/src/runtime/str.ts
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
*
|
|
5
5
|
* Pine `str.*` helpers. `null` is na.
|
|
6
6
|
*/
|
|
7
|
+
import { timestampToMs } from "./time.ts";
|
|
7
8
|
|
|
8
9
|
function asStr(x: unknown): string | null {
|
|
9
10
|
if (x == null) return null;
|
|
@@ -303,8 +304,8 @@ function coerceFormatTimeMs(time: unknown): number | null {
|
|
|
303
304
|
if (raw == null || typeof raw === "boolean" || typeof raw === "string") return null;
|
|
304
305
|
if (typeof raw !== "number" || !Number.isFinite(raw)) return null;
|
|
305
306
|
let ts = Math.trunc(raw);
|
|
306
|
-
// Python
|
|
307
|
-
|
|
307
|
+
// Python `utility._timestamp_to_ms`: Unix seconds (< 1e11) become ms.
|
|
308
|
+
ts = timestampToMs(ts) ?? ts;
|
|
308
309
|
return ts;
|
|
309
310
|
}
|
|
310
311
|
|