@hoox-sh/pynets 0.7.0 → 0.9.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 +10 -2
- package/dist/browser.js +52 -52
- package/dist/index.js +227 -14
- package/package.json +1 -1
- package/src/cli/rich.ts +1 -1
- package/src/runtime/compile/emit_call.ts +34 -3
- package/src/runtime/interpret.ts +82 -11
- package/src/runtime/ta.ts +164 -0
package/dist/index.js
CHANGED
|
@@ -44189,6 +44189,150 @@ class TaEngine {
|
|
|
44189
44189
|
ev += returns[i] * probabilities[i];
|
|
44190
44190
|
return finiteOut2(ev / totalProb) ?? 0;
|
|
44191
44191
|
}
|
|
44192
|
+
gammaLevels(volatility, currentPrice, period) {
|
|
44193
|
+
const v = finiteCell2(volatility) ?? 0.02;
|
|
44194
|
+
const p = finiteCell2(currentPrice) ?? 100;
|
|
44195
|
+
const n = this.coerceTaInt(period, "period must be integer");
|
|
44196
|
+
const volAdjusted = v * Math.sqrt(n);
|
|
44197
|
+
const distance = p * volAdjusted;
|
|
44198
|
+
const out = new PineArray;
|
|
44199
|
+
out.push(finiteOut2(p + distance));
|
|
44200
|
+
out.push(finiteOut2(p - distance));
|
|
44201
|
+
return out;
|
|
44202
|
+
}
|
|
44203
|
+
intelligentStrategySynthesizer(site, trend, momentum, volatility, volume, marketCondition, riskProfile) {
|
|
44204
|
+
const th = this.windowFor(`${site}:trend`, trend);
|
|
44205
|
+
const mh = this.windowFor(`${site}:momentum`, momentum);
|
|
44206
|
+
const vh = this.windowFor(`${site}:volatility`, volatility);
|
|
44207
|
+
const volh = this.windowFor(`${site}:volume`, volume);
|
|
44208
|
+
const regime = typeof marketCondition === "string" ? marketCondition : "ranging";
|
|
44209
|
+
const profile = typeof riskProfile === "string" ? riskProfile : "balanced";
|
|
44210
|
+
const nums = (h) => h.filter((v) => v !== null);
|
|
44211
|
+
const trendVals = nums(th);
|
|
44212
|
+
const momentumVals = nums(mh);
|
|
44213
|
+
const volatilityVals = nums(vh);
|
|
44214
|
+
const volumeVals = nums(volh);
|
|
44215
|
+
const avg = (vals, fallback) => vals.length === 0 ? fallback : vals.reduce((a, b) => a + b, 0) / vals.length;
|
|
44216
|
+
const trendAvg = avg(trendVals, 0);
|
|
44217
|
+
const momentumAvg = avg(momentumVals, 0);
|
|
44218
|
+
const volatilityAvg = avg(volatilityVals, 0.5);
|
|
44219
|
+
const volumeAvg = avg(volumeVals, 0);
|
|
44220
|
+
const volatilityNormalized = Math.max(0, Math.min(1, volatilityAvg));
|
|
44221
|
+
const composite = trendAvg * 0.4 + momentumAvg * 0.35 + volumeAvg * 0.25;
|
|
44222
|
+
const compositeSignal = Math.max(-1, Math.min(1, composite));
|
|
44223
|
+
let agreement = 0;
|
|
44224
|
+
if (Math.abs(trendAvg) > 0.3)
|
|
44225
|
+
agreement += 1;
|
|
44226
|
+
if (Math.abs(momentumAvg) > 0.3)
|
|
44227
|
+
agreement += 1;
|
|
44228
|
+
if (Math.abs(volumeAvg) > 0.3)
|
|
44229
|
+
agreement += 1;
|
|
44230
|
+
const baseConfidence = agreement / 3;
|
|
44231
|
+
const confidence = Math.max(0.1, Math.min(0.99, baseConfidence - volatilityNormalized * 0.3));
|
|
44232
|
+
let recommendation;
|
|
44233
|
+
if (compositeSignal > 0.6) {
|
|
44234
|
+
recommendation = profile === "aggressive" ? "aggressive_long" : "conservative_long";
|
|
44235
|
+
} else if (compositeSignal > 0.2) {
|
|
44236
|
+
recommendation = "conservative_long";
|
|
44237
|
+
} else if (compositeSignal < -0.6) {
|
|
44238
|
+
recommendation = profile === "aggressive" ? "aggressive_short" : "conservative_short";
|
|
44239
|
+
} else if (compositeSignal < -0.2) {
|
|
44240
|
+
recommendation = "conservative_short";
|
|
44241
|
+
} else {
|
|
44242
|
+
recommendation = "hold";
|
|
44243
|
+
}
|
|
44244
|
+
const riskMult = profile === "conservative" ? 0.5 : profile === "aggressive" ? 1.5 : 1;
|
|
44245
|
+
const riskLevel = volatilityNormalized * 50 * riskMult;
|
|
44246
|
+
const absSignal = Math.abs(compositeSignal);
|
|
44247
|
+
const expectedReturn = absSignal * 3;
|
|
44248
|
+
let holdingPeriod;
|
|
44249
|
+
if (volatilityNormalized > 0.7)
|
|
44250
|
+
holdingPeriod = "scalp";
|
|
44251
|
+
else if (volatilityNormalized > 0.5)
|
|
44252
|
+
holdingPeriod = "day_trade";
|
|
44253
|
+
else if (absSignal > 0.5)
|
|
44254
|
+
holdingPeriod = "swing";
|
|
44255
|
+
else
|
|
44256
|
+
holdingPeriod = "position";
|
|
44257
|
+
const shortRec = recommendation === "aggressive_short" || recommendation === "conservative_short";
|
|
44258
|
+
const longRec = recommendation === "aggressive_long" || recommendation === "conservative_long";
|
|
44259
|
+
const stopLoss = shortRec ? -0.7 : longRec ? -0.2 : -0.15;
|
|
44260
|
+
const takeProfit = recommendation === "aggressive_long" || recommendation === "aggressive_short" ? 1.5 : longRec || shortRec ? 1 : 0.5;
|
|
44261
|
+
let alignment = 50;
|
|
44262
|
+
if (regime === "trending_up") {
|
|
44263
|
+
if (compositeSignal > 0)
|
|
44264
|
+
alignment = 90;
|
|
44265
|
+
else if (compositeSignal > -0.3)
|
|
44266
|
+
alignment = 60;
|
|
44267
|
+
else
|
|
44268
|
+
alignment = 30;
|
|
44269
|
+
} else if (regime === "trending_down") {
|
|
44270
|
+
if (compositeSignal < 0)
|
|
44271
|
+
alignment = 90;
|
|
44272
|
+
else if (compositeSignal < 0.3)
|
|
44273
|
+
alignment = 60;
|
|
44274
|
+
else
|
|
44275
|
+
alignment = 30;
|
|
44276
|
+
} else if (regime === "ranging") {
|
|
44277
|
+
alignment = 50 + (1 - absSignal) * 30;
|
|
44278
|
+
} else if (regime === "volatile") {
|
|
44279
|
+
alignment = 30 + absSignal * 40;
|
|
44280
|
+
} else {
|
|
44281
|
+
alignment = 40;
|
|
44282
|
+
}
|
|
44283
|
+
return taRec({
|
|
44284
|
+
composite_signal: finiteOut2(compositeSignal) ?? 0,
|
|
44285
|
+
confidence_level: finiteOut2(confidence) ?? 0,
|
|
44286
|
+
strategy_recommendation: recommendation,
|
|
44287
|
+
risk_level: finiteOut2(Math.max(0, Math.min(100, riskLevel))) ?? 0,
|
|
44288
|
+
expected_return: finiteOut2(expectedReturn) ?? 0,
|
|
44289
|
+
holding_period: holdingPeriod,
|
|
44290
|
+
stop_loss_priority: stopLoss,
|
|
44291
|
+
take_profit_priority: takeProfit,
|
|
44292
|
+
regime_alignment: finiteOut2(Math.max(0, Math.min(100, alignment))) ?? 0
|
|
44293
|
+
});
|
|
44294
|
+
}
|
|
44295
|
+
signalConfluence(signals) {
|
|
44296
|
+
let vals = [];
|
|
44297
|
+
if (signals != null && typeof signals === "object") {
|
|
44298
|
+
const obj = signals;
|
|
44299
|
+
if (typeof obj.values === "function") {
|
|
44300
|
+
const got = obj.values();
|
|
44301
|
+
vals = Array.isArray(got) ? got : [];
|
|
44302
|
+
} else if (!Array.isArray(signals)) {
|
|
44303
|
+
vals = Object.entries(signals).filter(([k]) => k !== "__taRecord").map(([, v]) => v);
|
|
44304
|
+
}
|
|
44305
|
+
}
|
|
44306
|
+
let signalCount = 0;
|
|
44307
|
+
let bullish = 0;
|
|
44308
|
+
let bearish = 0;
|
|
44309
|
+
for (const v of vals) {
|
|
44310
|
+
if (typeof v === "boolean") {
|
|
44311
|
+
if (v) {
|
|
44312
|
+
bullish += 1;
|
|
44313
|
+
signalCount += 1;
|
|
44314
|
+
}
|
|
44315
|
+
continue;
|
|
44316
|
+
}
|
|
44317
|
+
if (typeof v === "number" && Number.isFinite(v)) {
|
|
44318
|
+
if (v > 0) {
|
|
44319
|
+
bullish += 1;
|
|
44320
|
+
signalCount += 1;
|
|
44321
|
+
} else if (v < 0) {
|
|
44322
|
+
bearish += 1;
|
|
44323
|
+
signalCount += 1;
|
|
44324
|
+
}
|
|
44325
|
+
}
|
|
44326
|
+
}
|
|
44327
|
+
const total = vals.length > 0 ? vals.length : 1;
|
|
44328
|
+
const confluence = signalCount / total;
|
|
44329
|
+
const primary = bullish > bearish ? "buy" : bearish > bullish ? "sell" : "neutral";
|
|
44330
|
+
return taRec({
|
|
44331
|
+
signal_count: signalCount,
|
|
44332
|
+
confluence_level: finiteOut2(confluence) ?? 0,
|
|
44333
|
+
primary_signal: primary
|
|
44334
|
+
});
|
|
44335
|
+
}
|
|
44192
44336
|
volumeProfileHigh(prices, volumes) {
|
|
44193
44337
|
if (prices.length === 0 || volumes.length === 0 || prices.length !== volumes.length)
|
|
44194
44338
|
return 0;
|
|
@@ -44830,7 +44974,10 @@ var TA_METHODS = new Set([
|
|
|
44830
44974
|
"employment_cycle_indicator",
|
|
44831
44975
|
"gdp_growth_proxy",
|
|
44832
44976
|
"inflation_proxy_indicator",
|
|
44833
|
-
"expected_value"
|
|
44977
|
+
"expected_value",
|
|
44978
|
+
"gamma_levels",
|
|
44979
|
+
"intelligent_strategy_synthesizer",
|
|
44980
|
+
"signal_confluence"
|
|
44834
44981
|
]);
|
|
44835
44982
|
var TA_BARE_ATTRS = new Set([
|
|
44836
44983
|
"accdist",
|
|
@@ -45199,6 +45346,9 @@ function hasSeriesArg(args) {
|
|
|
45199
45346
|
const isSeries = (n) => n.kind === "Name" && BARE_SERIES_NAMES.has(n.id);
|
|
45200
45347
|
return args.posNodes.some(isSeries) || Object.values(args.kwNodes).some(isSeries);
|
|
45201
45348
|
}
|
|
45349
|
+
function isSeriesNode(n) {
|
|
45350
|
+
return n != null && n.kind === "Name" && BARE_SERIES_NAMES.has(n.id);
|
|
45351
|
+
}
|
|
45202
45352
|
function hasAnyKw(args, names) {
|
|
45203
45353
|
return names.some((n) => Object.hasOwn(args.kw, n));
|
|
45204
45354
|
}
|
|
@@ -46951,10 +47101,11 @@ function emitTa(ctx, method, args) {
|
|
|
46951
47101
|
]);
|
|
46952
47102
|
}
|
|
46953
47103
|
if (method === "breakout_detection") {
|
|
47104
|
+
const arg = (i, names) => isSeriesNode(args.posNodes[i]) ? "null" : cellOf(pick(args, i, names, "null"));
|
|
46954
47105
|
return taPure("breakoutDetection", [
|
|
46955
|
-
|
|
46956
|
-
|
|
46957
|
-
|
|
47106
|
+
arg(0, ["close", "source"]),
|
|
47107
|
+
arg(1, ["resistance"]),
|
|
47108
|
+
arg(2, ["support"])
|
|
46958
47109
|
]);
|
|
46959
47110
|
}
|
|
46960
47111
|
if (method === "ema_cross_signal") {
|
|
@@ -47085,6 +47236,24 @@ function emitTa(ctx, method, args) {
|
|
|
47085
47236
|
if (method === "expected_value") {
|
|
47086
47237
|
return `__h.ta.expectedValue(${cellList(pick(args, 0, ["returns"], "null"))}, ${cellList(pick(args, 1, ["probabilities", "probs"], "null"))})`;
|
|
47087
47238
|
}
|
|
47239
|
+
if (method === "gamma_levels") {
|
|
47240
|
+
const vol = isSeriesNode(args.posNodes[0]) ? "null" : cellOf(pick(args, 0, ["volatility"], "null"));
|
|
47241
|
+
const price = isSeriesNode(args.posNodes[1]) ? "null" : cellOf(pick(args, 1, ["current_price", "price"], "null"));
|
|
47242
|
+
return taPure("gammaLevels", [vol, price, cellOf(pick(args, 2, ["period", "length"], "null"))]);
|
|
47243
|
+
}
|
|
47244
|
+
if (method === "intelligent_strategy_synthesizer") {
|
|
47245
|
+
return taCall("intelligentStrategySynthesizer", site, [
|
|
47246
|
+
arrOrCell(pick(args, 0, ["trend_indicators", "trend"], "null")),
|
|
47247
|
+
arrOrCell(pick(args, 1, ["momentum_indicators", "momentum"], "null")),
|
|
47248
|
+
arrOrCell(pick(args, 2, ["volatility_indicators", "volatility"], "null")),
|
|
47249
|
+
arrOrCell(pick(args, 3, ["volume_indicators", "volume"], "null")),
|
|
47250
|
+
pick(args, 4, ["market_condition", "market_regime"], '"ranging"'),
|
|
47251
|
+
pick(args, 5, ["risk_profile"], '"balanced"')
|
|
47252
|
+
]);
|
|
47253
|
+
}
|
|
47254
|
+
if (method === "signal_confluence") {
|
|
47255
|
+
return taPure("signalConfluence", [pick(args, 0, ["signals"], "null")]);
|
|
47256
|
+
}
|
|
47088
47257
|
return "null";
|
|
47089
47258
|
}
|
|
47090
47259
|
|
|
@@ -50740,7 +50909,8 @@ class Runtime {
|
|
|
50740
50909
|
broker: extraOpts?.broker ?? this.broker,
|
|
50741
50910
|
timeframe: extraOpts?.timeframe ?? this.timeframe,
|
|
50742
50911
|
libraries: extraOpts?.libraries != null ? resolveLibraries(extraOpts.libraries) : this.libraries,
|
|
50743
|
-
timeout_seconds: extraOpts?.timeout_seconds ?? this.timeout_seconds
|
|
50912
|
+
timeout_seconds: extraOpts?.timeout_seconds ?? this.timeout_seconds,
|
|
50913
|
+
realtime_last_bar: extraOpts?.realtimeLastBar ?? false
|
|
50744
50914
|
};
|
|
50745
50915
|
if (mode === "compile") {
|
|
50746
50916
|
return runCompiled(source, ohlcv, host);
|
|
@@ -50803,7 +50973,7 @@ class RuntimeStream {
|
|
|
50803
50973
|
return;
|
|
50804
50974
|
this.bars.push(bar);
|
|
50805
50975
|
try {
|
|
50806
|
-
const out = this.runtime.run(this.source, this.bars);
|
|
50976
|
+
const out = this.runtime.run(this.source, this.bars, { realtimeLastBar: true });
|
|
50807
50977
|
for (const h of this.barHandlers)
|
|
50808
50978
|
h(out);
|
|
50809
50979
|
return out;
|
|
@@ -50814,9 +50984,29 @@ class RuntimeStream {
|
|
|
50814
50984
|
return;
|
|
50815
50985
|
}
|
|
50816
50986
|
}
|
|
50987
|
+
get barCount() {
|
|
50988
|
+
return this.bars.length;
|
|
50989
|
+
}
|
|
50990
|
+
updateLastBar(bar) {
|
|
50991
|
+
if (this.closed)
|
|
50992
|
+
throw new Error("stream is closed");
|
|
50993
|
+
if (this.bars.length === 0)
|
|
50994
|
+
throw new Error("updateLastBar requires at least one bar");
|
|
50995
|
+
this.bars[this.bars.length - 1] = bar;
|
|
50996
|
+
const out = this.runtime.run(this.source, this.bars, { realtimeLastBar: true });
|
|
50997
|
+
for (const h of this.barHandlers)
|
|
50998
|
+
h(out);
|
|
50999
|
+
return out;
|
|
51000
|
+
}
|
|
51001
|
+
appendBars(bars) {
|
|
51002
|
+
let out;
|
|
51003
|
+
for (const bar of bars)
|
|
51004
|
+
out = this.push(bar);
|
|
51005
|
+
return out;
|
|
51006
|
+
}
|
|
50817
51007
|
close() {
|
|
50818
51008
|
this.closed = true;
|
|
50819
|
-
const out = this.runtime.run(this.source, this.bars);
|
|
51009
|
+
const out = this.runtime.run(this.source, this.bars, { realtimeLastBar: false });
|
|
50820
51010
|
for (const h of this.endHandlers)
|
|
50821
51011
|
h(out);
|
|
50822
51012
|
return out;
|
|
@@ -50863,7 +51053,8 @@ function interpretTree(tree, ohlcv, host) {
|
|
|
50863
51053
|
pendingExports: new Map,
|
|
50864
51054
|
onceFired: new WeakSet,
|
|
50865
51055
|
maxBarsBackDecls: [],
|
|
50866
|
-
alertFireBars: new Map
|
|
51056
|
+
alertFireBars: new Map,
|
|
51057
|
+
realtimeLastBar: host?.realtime_last_bar ?? false
|
|
50867
51058
|
};
|
|
50868
51059
|
const n = ohlcv.length;
|
|
50869
51060
|
let runError;
|
|
@@ -51612,7 +51803,8 @@ function evalExpr(node, env) {
|
|
|
51612
51803
|
return NA;
|
|
51613
51804
|
for (const s of onceNode.body)
|
|
51614
51805
|
execStmt(s, env);
|
|
51615
|
-
env
|
|
51806
|
+
if (barIsConfirmed(env))
|
|
51807
|
+
env.onceFired.add(onceNode);
|
|
51616
51808
|
return NA;
|
|
51617
51809
|
}
|
|
51618
51810
|
case "BoolOp":
|
|
@@ -51959,20 +52151,21 @@ function evalAttribute(node, env) {
|
|
|
51959
52151
|
}
|
|
51960
52152
|
if (node.value.id === "barstate") {
|
|
51961
52153
|
const last = env.barIndex === env.barCount - 1;
|
|
52154
|
+
const realtime = env.realtimeLastBar && last;
|
|
51962
52155
|
if (node.attr === "isfirst")
|
|
51963
52156
|
return env.barIndex === 0 ? 1 : 0;
|
|
51964
52157
|
if (node.attr === "islast")
|
|
51965
52158
|
return last ? 1 : 0;
|
|
51966
52159
|
if (node.attr === "ishistory")
|
|
51967
|
-
return
|
|
52160
|
+
return realtime ? 0 : 1;
|
|
51968
52161
|
if (node.attr === "isconfirmed")
|
|
51969
52162
|
return 1;
|
|
51970
52163
|
if (node.attr === "isnew")
|
|
51971
52164
|
return 1;
|
|
51972
52165
|
if (node.attr === "isrealtime")
|
|
51973
|
-
return 0;
|
|
52166
|
+
return realtime ? 1 : 0;
|
|
51974
52167
|
if (node.attr === "islastconfirmedhistory")
|
|
51975
|
-
return last ? 1 : 0;
|
|
52168
|
+
return realtime ? 0 : last ? 1 : 0;
|
|
51976
52169
|
}
|
|
51977
52170
|
if (node.value.id === "session") {
|
|
51978
52171
|
if (node.attr === "regular")
|
|
@@ -52741,7 +52934,7 @@ function formatRunError(err) {
|
|
|
52741
52934
|
return "runtime error";
|
|
52742
52935
|
}
|
|
52743
52936
|
function isPlainInputs(extra) {
|
|
52744
|
-
return !("inputs" in extra) && !("broker" in extra) && !("timeframe" in extra) && !("libraries" in extra) && !("mode" in extra) && !("timeout_seconds" in extra);
|
|
52937
|
+
return !("inputs" in extra) && !("broker" in extra) && !("timeframe" in extra) && !("libraries" in extra) && !("mode" in extra) && !("timeout_seconds" in extra) && !("realtimeLastBar" in extra);
|
|
52745
52938
|
}
|
|
52746
52939
|
function resolveLibraries(libs) {
|
|
52747
52940
|
if (libs instanceof LibraryRegistry)
|
|
@@ -54200,7 +54393,11 @@ function evalExtraTa(fname, node, env, site) {
|
|
|
54200
54393
|
}
|
|
54201
54394
|
if (fname === "ta.breakout_detection" || fname === "breakout_detection") {
|
|
54202
54395
|
const msg = "ta.breakout_detection() requires 3 arguments";
|
|
54203
|
-
|
|
54396
|
+
const arg = (i, names) => {
|
|
54397
|
+
const e = wave10aArg(i, names, msg);
|
|
54398
|
+
return isSeriesExpr(e, env) ? NA : num2(evalExpr(e, env));
|
|
54399
|
+
};
|
|
54400
|
+
return env.ta.breakoutDetection(arg(0, ["close", "source"]), arg(1, ["resistance"]), arg(2, ["support"]));
|
|
54204
54401
|
}
|
|
54205
54402
|
if (fname === "ta.ema_cross_signal" || fname === "ema_cross_signal") {
|
|
54206
54403
|
const msg = "ta.ema_cross_signal() requires 3 arguments";
|
|
@@ -54294,6 +54491,22 @@ function evalExtraTa(fname, node, env, site) {
|
|
|
54294
54491
|
runtimeError("Returns and probabilities must have same length");
|
|
54295
54492
|
return env.ta.expectedValue(returns, probs);
|
|
54296
54493
|
}
|
|
54494
|
+
if (fname === "ta.gamma_levels" || fname === "gamma_levels") {
|
|
54495
|
+
const msg = "ta.gamma_levels() requires 3 arguments";
|
|
54496
|
+
const volArg = wave10aArg(0, ["volatility"], msg);
|
|
54497
|
+
const priceArg = wave10aArg(1, ["current_price", "price"], msg);
|
|
54498
|
+
const vol = isSeriesExpr(volArg, env) ? NA : num2(evalExpr(volArg, env));
|
|
54499
|
+
const price = isSeriesExpr(priceArg, env) ? NA : num2(evalExpr(priceArg, env));
|
|
54500
|
+
return env.ta.gammaLevels(vol, price, wave11n(2, ["period", "length"], msg));
|
|
54501
|
+
}
|
|
54502
|
+
if (fname === "ta.intelligent_strategy_synthesizer" || fname === "intelligent_strategy_synthesizer") {
|
|
54503
|
+
const msg = "ta.intelligent_strategy_synthesizer() requires 6 arguments";
|
|
54504
|
+
return env.ta.intelligentStrategySynthesizer(site, wave13v(0, ["trend_indicators", "trend"], msg), wave13v(1, ["momentum_indicators", "momentum"], msg), wave13v(2, ["volatility_indicators", "volatility"], msg), wave13v(3, ["volume_indicators", "volume"], msg), wave13v(4, ["market_condition", "market_regime"], msg), wave13v(5, ["risk_profile"], msg));
|
|
54505
|
+
}
|
|
54506
|
+
if (fname === "ta.signal_confluence" || fname === "signal_confluence") {
|
|
54507
|
+
const msg = "ta.signal_confluence() requires 1 argument";
|
|
54508
|
+
return env.ta.signalConfluence(wave13v(0, ["signals"], msg));
|
|
54509
|
+
}
|
|
54297
54510
|
return;
|
|
54298
54511
|
}
|
|
54299
54512
|
function hlc3(env) {
|
package/package.json
CHANGED
package/src/cli/rich.ts
CHANGED
|
@@ -211,6 +211,9 @@ const TA_METHODS = new Set([
|
|
|
211
211
|
"gdp_growth_proxy",
|
|
212
212
|
"inflation_proxy_indicator",
|
|
213
213
|
"expected_value",
|
|
214
|
+
"gamma_levels",
|
|
215
|
+
"intelligent_strategy_synthesizer",
|
|
216
|
+
"signal_confluence",
|
|
214
217
|
]);
|
|
215
218
|
|
|
216
219
|
/**
|
|
@@ -613,6 +616,10 @@ function hasSeriesArg(args: CallArgs): boolean {
|
|
|
613
616
|
return args.posNodes.some(isSeries) || Object.values(args.kwNodes).some(isSeries);
|
|
614
617
|
}
|
|
615
618
|
|
|
619
|
+
function isSeriesNode(n: expr | undefined): boolean {
|
|
620
|
+
return n != null && n.kind === "Name" && BARE_SERIES_NAMES.has(n.id);
|
|
621
|
+
}
|
|
622
|
+
|
|
616
623
|
/** True when a keyword argument named one of *names* is present (interpret hasKwarg). */
|
|
617
624
|
function hasAnyKw(args: CallArgs, names: string[]): boolean {
|
|
618
625
|
return names.some((n) => Object.hasOwn(args.kw, n));
|
|
@@ -2493,10 +2500,12 @@ function emitTa(ctx: EmitCtx, method: string, args: CallArgs): string {
|
|
|
2493
2500
|
]);
|
|
2494
2501
|
}
|
|
2495
2502
|
if (method === "breakout_detection") {
|
|
2503
|
+
const arg = (i: number, names: string[]): string =>
|
|
2504
|
+
isSeriesNode(args.posNodes[i]) ? "null" : cellOf(pick(args, i, names, "null"));
|
|
2496
2505
|
return taPure("breakoutDetection", [
|
|
2497
|
-
|
|
2498
|
-
|
|
2499
|
-
|
|
2506
|
+
arg(0, ["close", "source"]),
|
|
2507
|
+
arg(1, ["resistance"]),
|
|
2508
|
+
arg(2, ["support"]),
|
|
2500
2509
|
]);
|
|
2501
2510
|
}
|
|
2502
2511
|
if (method === "ema_cross_signal") {
|
|
@@ -2627,5 +2636,27 @@ function emitTa(ctx: EmitCtx, method: string, args: CallArgs): string {
|
|
|
2627
2636
|
if (method === "expected_value") {
|
|
2628
2637
|
return `__h.ta.expectedValue(${cellList(pick(args, 0, ["returns"], "null"))}, ${cellList(pick(args, 1, ["probabilities", "probs"], "null"))})`;
|
|
2629
2638
|
}
|
|
2639
|
+
if (method === "gamma_levels") {
|
|
2640
|
+
const vol = isSeriesNode(args.posNodes[0])
|
|
2641
|
+
? "null"
|
|
2642
|
+
: cellOf(pick(args, 0, ["volatility"], "null"));
|
|
2643
|
+
const price = isSeriesNode(args.posNodes[1])
|
|
2644
|
+
? "null"
|
|
2645
|
+
: cellOf(pick(args, 1, ["current_price", "price"], "null"));
|
|
2646
|
+
return taPure("gammaLevels", [vol, price, cellOf(pick(args, 2, ["period", "length"], "null"))]);
|
|
2647
|
+
}
|
|
2648
|
+
if (method === "intelligent_strategy_synthesizer") {
|
|
2649
|
+
return taCall("intelligentStrategySynthesizer", site, [
|
|
2650
|
+
arrOrCell(pick(args, 0, ["trend_indicators", "trend"], "null")),
|
|
2651
|
+
arrOrCell(pick(args, 1, ["momentum_indicators", "momentum"], "null")),
|
|
2652
|
+
arrOrCell(pick(args, 2, ["volatility_indicators", "volatility"], "null")),
|
|
2653
|
+
arrOrCell(pick(args, 3, ["volume_indicators", "volume"], "null")),
|
|
2654
|
+
pick(args, 4, ["market_condition", "market_regime"], '"ranging"'),
|
|
2655
|
+
pick(args, 5, ["risk_profile"], '"balanced"'),
|
|
2656
|
+
]);
|
|
2657
|
+
}
|
|
2658
|
+
if (method === "signal_confluence") {
|
|
2659
|
+
return taPure("signalConfluence", [pick(args, 0, ["signals"], "null")]);
|
|
2660
|
+
}
|
|
2630
2661
|
return "null";
|
|
2631
2662
|
}
|
package/src/runtime/interpret.ts
CHANGED
|
@@ -218,6 +218,8 @@ export interface RuntimeOptions {
|
|
|
218
218
|
mode?: RuntimeMode;
|
|
219
219
|
/** Wall-clock budget in seconds. Interpret only; checked every 32 bars. */
|
|
220
220
|
timeout_seconds?: number | null;
|
|
221
|
+
/** Visit the last bar as a forming realtime bar (`barstate.isrealtime`). */
|
|
222
|
+
realtimeLastBar?: boolean;
|
|
221
223
|
}
|
|
222
224
|
|
|
223
225
|
export interface RuntimeFill {
|
|
@@ -362,6 +364,8 @@ interface Env {
|
|
|
362
364
|
maxBarsBackDecls: Array<{ var: Value; num: Value }>;
|
|
363
365
|
/** Python `_alert_fire_bars` keys for `alert` / `alertcondition` freq gating. */
|
|
364
366
|
alertFireBars: Map<string, number>;
|
|
367
|
+
/** Visit the last bar as a forming realtime bar (`barstate.isrealtime`). */
|
|
368
|
+
realtimeLastBar: boolean;
|
|
365
369
|
}
|
|
366
370
|
|
|
367
371
|
export class Runtime {
|
|
@@ -420,6 +424,7 @@ export class Runtime {
|
|
|
420
424
|
timeframe: extraOpts?.timeframe ?? this.timeframe,
|
|
421
425
|
libraries: extraOpts?.libraries != null ? resolveLibraries(extraOpts.libraries) : this.libraries,
|
|
422
426
|
timeout_seconds: extraOpts?.timeout_seconds ?? this.timeout_seconds,
|
|
427
|
+
realtime_last_bar: extraOpts?.realtimeLastBar ?? false,
|
|
423
428
|
};
|
|
424
429
|
if (mode === "compile") {
|
|
425
430
|
return runCompiled(source, ohlcv, host);
|
|
@@ -494,7 +499,7 @@ export class RuntimeStream {
|
|
|
494
499
|
if (this.closed) return undefined;
|
|
495
500
|
this.bars.push(bar);
|
|
496
501
|
try {
|
|
497
|
-
const out = this.runtime.run(this.source, this.bars);
|
|
502
|
+
const out = this.runtime.run(this.source, this.bars, { realtimeLastBar: true });
|
|
498
503
|
for (const h of this.barHandlers) h(out);
|
|
499
504
|
return out;
|
|
500
505
|
} catch (err) {
|
|
@@ -504,9 +509,35 @@ export class RuntimeStream {
|
|
|
504
509
|
}
|
|
505
510
|
}
|
|
506
511
|
|
|
512
|
+
/** Number of committed bars in the stream. */
|
|
513
|
+
get barCount(): number {
|
|
514
|
+
return this.bars.length;
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
/**
|
|
518
|
+
* Re-tick the forming last bar with revised OHLCV. The bar count does not
|
|
519
|
+
* advance; the script is re-evaluated from bar 0, so `once`, TA state, and
|
|
520
|
+
* broker fills naturally roll back to the revised history.
|
|
521
|
+
*/
|
|
522
|
+
updateLastBar(bar: OHLCVBar): RuntimeResult {
|
|
523
|
+
if (this.closed) throw new Error("stream is closed");
|
|
524
|
+
if (this.bars.length === 0) throw new Error("updateLastBar requires at least one bar");
|
|
525
|
+
this.bars[this.bars.length - 1] = bar;
|
|
526
|
+
const out = this.runtime.run(this.source, this.bars, { realtimeLastBar: true });
|
|
527
|
+
for (const h of this.barHandlers) h(out);
|
|
528
|
+
return out;
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
/** Append several bars, returning the final result. */
|
|
532
|
+
appendBars(bars: OHLCVBar[]): RuntimeResult | undefined {
|
|
533
|
+
let out: RuntimeResult | undefined;
|
|
534
|
+
for (const bar of bars) out = this.push(bar);
|
|
535
|
+
return out;
|
|
536
|
+
}
|
|
537
|
+
|
|
507
538
|
close(): RuntimeResult {
|
|
508
539
|
this.closed = true;
|
|
509
|
-
const out = this.runtime.run(this.source, this.bars);
|
|
540
|
+
const out = this.runtime.run(this.source, this.bars, { realtimeLastBar: false });
|
|
510
541
|
for (const h of this.endHandlers) h(out);
|
|
511
542
|
return out;
|
|
512
543
|
}
|
|
@@ -528,6 +559,7 @@ export function interpretTree(
|
|
|
528
559
|
timeframe?: string | null;
|
|
529
560
|
libraries?: LibraryRegistry;
|
|
530
561
|
timeout_seconds?: number | null;
|
|
562
|
+
realtime_last_bar?: boolean;
|
|
531
563
|
},
|
|
532
564
|
): RuntimeResult {
|
|
533
565
|
const scriptNode = tree.kind === "Script" ? (tree as Script) : null;
|
|
@@ -566,6 +598,7 @@ export function interpretTree(
|
|
|
566
598
|
onceFired: new WeakSet(),
|
|
567
599
|
maxBarsBackDecls: [],
|
|
568
600
|
alertFireBars: new Map(),
|
|
601
|
+
realtimeLastBar: host?.realtime_last_bar ?? false,
|
|
569
602
|
};
|
|
570
603
|
|
|
571
604
|
const n = ohlcv.length;
|
|
@@ -1319,7 +1352,9 @@ function evalExpr(node: expr | undefined, env: Env): Value {
|
|
|
1319
1352
|
const t = onceNode.test == null ? 1 : unwrap(evalExpr(onceNode.test, env));
|
|
1320
1353
|
if (!isTruthy(t)) return NA;
|
|
1321
1354
|
for (const s of onceNode.body) execStmt(s, env);
|
|
1322
|
-
|
|
1355
|
+
// Python commits the fired flag only on a confirmed bar; unconfirmed
|
|
1356
|
+
// realtime ticks may re-run the body (rollback semantics).
|
|
1357
|
+
if (barIsConfirmed(env)) env.onceFired.add(onceNode);
|
|
1323
1358
|
return NA;
|
|
1324
1359
|
}
|
|
1325
1360
|
case "BoolOp":
|
|
@@ -1652,13 +1687,14 @@ function evalAttribute(node: Attribute, env: Env): Value {
|
|
|
1652
1687
|
}
|
|
1653
1688
|
if (node.value.id === "barstate") {
|
|
1654
1689
|
const last = env.barIndex === env.barCount - 1;
|
|
1690
|
+
const realtime = env.realtimeLastBar && last;
|
|
1655
1691
|
if (node.attr === "isfirst") return env.barIndex === 0 ? 1 : 0;
|
|
1656
1692
|
if (node.attr === "islast") return last ? 1 : 0;
|
|
1657
|
-
if (node.attr === "ishistory") return
|
|
1693
|
+
if (node.attr === "ishistory") return realtime ? 0 : 1;
|
|
1658
1694
|
if (node.attr === "isconfirmed") return 1;
|
|
1659
1695
|
if (node.attr === "isnew") return 1;
|
|
1660
|
-
if (node.attr === "isrealtime") return 0;
|
|
1661
|
-
if (node.attr === "islastconfirmedhistory") return last ? 1 : 0;
|
|
1696
|
+
if (node.attr === "isrealtime") return realtime ? 1 : 0;
|
|
1697
|
+
if (node.attr === "islastconfirmedhistory") return realtime ? 0 : last ? 1 : 0;
|
|
1662
1698
|
}
|
|
1663
1699
|
if (node.value.id === "session") {
|
|
1664
1700
|
if (node.attr === "regular") return "regular";
|
|
@@ -2470,7 +2506,8 @@ function isPlainInputs(extra: RuntimeOptions | InputOverrides): extra is InputOv
|
|
|
2470
2506
|
!("timeframe" in extra) &&
|
|
2471
2507
|
!("libraries" in extra) &&
|
|
2472
2508
|
!("mode" in extra) &&
|
|
2473
|
-
!("timeout_seconds" in extra)
|
|
2509
|
+
!("timeout_seconds" in extra) &&
|
|
2510
|
+
!("realtimeLastBar" in extra)
|
|
2474
2511
|
);
|
|
2475
2512
|
}
|
|
2476
2513
|
|
|
@@ -2623,6 +2660,7 @@ function runAuto(
|
|
|
2623
2660
|
timeframe?: string | null;
|
|
2624
2661
|
libraries?: LibraryRegistry;
|
|
2625
2662
|
timeout_seconds?: number | null;
|
|
2663
|
+
realtime_last_bar?: boolean;
|
|
2626
2664
|
},
|
|
2627
2665
|
): RuntimeResult {
|
|
2628
2666
|
const elig = compileEligible(source);
|
|
@@ -2700,7 +2738,7 @@ function normalizeAlertFreq(freq: unknown): string {
|
|
|
2700
2738
|
return ALERT_FREQ_ALIASES[s] ?? (s ? s : FREQ_ONCE_PER_BAR);
|
|
2701
2739
|
}
|
|
2702
2740
|
|
|
2703
|
-
/** Python `_bar_is_confirmed
|
|
2741
|
+
/** Python `_bar_is_confirmed`: single-tick runs are confirmed (ticks>1 not modeled). */
|
|
2704
2742
|
function barIsConfirmed(_env: Env): boolean {
|
|
2705
2743
|
return true;
|
|
2706
2744
|
}
|
|
@@ -4673,10 +4711,14 @@ function evalExtraTa(fname: string | null, node: Call, env: Env, site: string):
|
|
|
4673
4711
|
}
|
|
4674
4712
|
if (fname === "ta.breakout_detection" || fname === "breakout_detection") {
|
|
4675
4713
|
const msg = "ta.breakout_detection() requires 3 arguments";
|
|
4714
|
+
const arg = (i: number, names: string[]): Cell => {
|
|
4715
|
+
const e = wave10aArg(i, names, msg);
|
|
4716
|
+
return isSeriesExpr(e, env) ? NA : num(evalExpr(e, env));
|
|
4717
|
+
};
|
|
4676
4718
|
return env.ta.breakoutDetection(
|
|
4677
|
-
|
|
4678
|
-
|
|
4679
|
-
|
|
4719
|
+
arg(0, ["close", "source"]),
|
|
4720
|
+
arg(1, ["resistance"]),
|
|
4721
|
+
arg(2, ["support"]),
|
|
4680
4722
|
);
|
|
4681
4723
|
}
|
|
4682
4724
|
// wave12-end
|
|
@@ -4860,6 +4902,35 @@ function evalExtraTa(fname: string | null, node: Call, env: Env, site: string):
|
|
|
4860
4902
|
return env.ta.expectedValue(returns, probs);
|
|
4861
4903
|
}
|
|
4862
4904
|
// wave13-end
|
|
4905
|
+
// wave14-start — list-return + capstone synthesis
|
|
4906
|
+
if (fname === "ta.gamma_levels" || fname === "gamma_levels") {
|
|
4907
|
+
const msg = "ta.gamma_levels() requires 3 arguments";
|
|
4908
|
+
const volArg = wave10aArg(0, ["volatility"], msg);
|
|
4909
|
+
const priceArg = wave10aArg(1, ["current_price", "price"], msg);
|
|
4910
|
+
const vol = isSeriesExpr(volArg, env) ? NA : num(evalExpr(volArg, env));
|
|
4911
|
+
const price = isSeriesExpr(priceArg, env) ? NA : num(evalExpr(priceArg, env));
|
|
4912
|
+
return env.ta.gammaLevels(vol, price, wave11n(2, ["period", "length"], msg));
|
|
4913
|
+
}
|
|
4914
|
+
if (
|
|
4915
|
+
fname === "ta.intelligent_strategy_synthesizer" ||
|
|
4916
|
+
fname === "intelligent_strategy_synthesizer"
|
|
4917
|
+
) {
|
|
4918
|
+
const msg = "ta.intelligent_strategy_synthesizer() requires 6 arguments";
|
|
4919
|
+
return env.ta.intelligentStrategySynthesizer(
|
|
4920
|
+
site,
|
|
4921
|
+
wave13v(0, ["trend_indicators", "trend"], msg),
|
|
4922
|
+
wave13v(1, ["momentum_indicators", "momentum"], msg),
|
|
4923
|
+
wave13v(2, ["volatility_indicators", "volatility"], msg),
|
|
4924
|
+
wave13v(3, ["volume_indicators", "volume"], msg),
|
|
4925
|
+
wave13v(4, ["market_condition", "market_regime"], msg),
|
|
4926
|
+
wave13v(5, ["risk_profile"], msg),
|
|
4927
|
+
);
|
|
4928
|
+
}
|
|
4929
|
+
if (fname === "ta.signal_confluence" || fname === "signal_confluence") {
|
|
4930
|
+
const msg = "ta.signal_confluence() requires 1 argument";
|
|
4931
|
+
return env.ta.signalConfluence(wave13v(0, ["signals"], msg));
|
|
4932
|
+
}
|
|
4933
|
+
// wave14-end
|
|
4863
4934
|
return undefined;
|
|
4864
4935
|
}
|
|
4865
4936
|
|