@hoox-sh/pynets 0.7.0 → 0.8.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 +17 -0
- package/README.md +1 -1
- package/dist/browser.js +52 -52
- package/dist/index.js +194 -5
- 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 +36 -3
- 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
|
|
|
@@ -54200,7 +54369,11 @@ function evalExtraTa(fname, node, env, site) {
|
|
|
54200
54369
|
}
|
|
54201
54370
|
if (fname === "ta.breakout_detection" || fname === "breakout_detection") {
|
|
54202
54371
|
const msg = "ta.breakout_detection() requires 3 arguments";
|
|
54203
|
-
|
|
54372
|
+
const arg = (i, names) => {
|
|
54373
|
+
const e = wave10aArg(i, names, msg);
|
|
54374
|
+
return isSeriesExpr(e, env) ? NA : num2(evalExpr(e, env));
|
|
54375
|
+
};
|
|
54376
|
+
return env.ta.breakoutDetection(arg(0, ["close", "source"]), arg(1, ["resistance"]), arg(2, ["support"]));
|
|
54204
54377
|
}
|
|
54205
54378
|
if (fname === "ta.ema_cross_signal" || fname === "ema_cross_signal") {
|
|
54206
54379
|
const msg = "ta.ema_cross_signal() requires 3 arguments";
|
|
@@ -54294,6 +54467,22 @@ function evalExtraTa(fname, node, env, site) {
|
|
|
54294
54467
|
runtimeError("Returns and probabilities must have same length");
|
|
54295
54468
|
return env.ta.expectedValue(returns, probs);
|
|
54296
54469
|
}
|
|
54470
|
+
if (fname === "ta.gamma_levels" || fname === "gamma_levels") {
|
|
54471
|
+
const msg = "ta.gamma_levels() requires 3 arguments";
|
|
54472
|
+
const volArg = wave10aArg(0, ["volatility"], msg);
|
|
54473
|
+
const priceArg = wave10aArg(1, ["current_price", "price"], msg);
|
|
54474
|
+
const vol = isSeriesExpr(volArg, env) ? NA : num2(evalExpr(volArg, env));
|
|
54475
|
+
const price = isSeriesExpr(priceArg, env) ? NA : num2(evalExpr(priceArg, env));
|
|
54476
|
+
return env.ta.gammaLevels(vol, price, wave11n(2, ["period", "length"], msg));
|
|
54477
|
+
}
|
|
54478
|
+
if (fname === "ta.intelligent_strategy_synthesizer" || fname === "intelligent_strategy_synthesizer") {
|
|
54479
|
+
const msg = "ta.intelligent_strategy_synthesizer() requires 6 arguments";
|
|
54480
|
+
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));
|
|
54481
|
+
}
|
|
54482
|
+
if (fname === "ta.signal_confluence" || fname === "signal_confluence") {
|
|
54483
|
+
const msg = "ta.signal_confluence() requires 1 argument";
|
|
54484
|
+
return env.ta.signalConfluence(wave13v(0, ["signals"], msg));
|
|
54485
|
+
}
|
|
54297
54486
|
return;
|
|
54298
54487
|
}
|
|
54299
54488
|
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
|
@@ -4673,10 +4673,14 @@ function evalExtraTa(fname: string | null, node: Call, env: Env, site: string):
|
|
|
4673
4673
|
}
|
|
4674
4674
|
if (fname === "ta.breakout_detection" || fname === "breakout_detection") {
|
|
4675
4675
|
const msg = "ta.breakout_detection() requires 3 arguments";
|
|
4676
|
+
const arg = (i: number, names: string[]): Cell => {
|
|
4677
|
+
const e = wave10aArg(i, names, msg);
|
|
4678
|
+
return isSeriesExpr(e, env) ? NA : num(evalExpr(e, env));
|
|
4679
|
+
};
|
|
4676
4680
|
return env.ta.breakoutDetection(
|
|
4677
|
-
|
|
4678
|
-
|
|
4679
|
-
|
|
4681
|
+
arg(0, ["close", "source"]),
|
|
4682
|
+
arg(1, ["resistance"]),
|
|
4683
|
+
arg(2, ["support"]),
|
|
4680
4684
|
);
|
|
4681
4685
|
}
|
|
4682
4686
|
// wave12-end
|
|
@@ -4860,6 +4864,35 @@ function evalExtraTa(fname: string | null, node: Call, env: Env, site: string):
|
|
|
4860
4864
|
return env.ta.expectedValue(returns, probs);
|
|
4861
4865
|
}
|
|
4862
4866
|
// wave13-end
|
|
4867
|
+
// wave14-start — list-return + capstone synthesis
|
|
4868
|
+
if (fname === "ta.gamma_levels" || fname === "gamma_levels") {
|
|
4869
|
+
const msg = "ta.gamma_levels() requires 3 arguments";
|
|
4870
|
+
const volArg = wave10aArg(0, ["volatility"], msg);
|
|
4871
|
+
const priceArg = wave10aArg(1, ["current_price", "price"], msg);
|
|
4872
|
+
const vol = isSeriesExpr(volArg, env) ? NA : num(evalExpr(volArg, env));
|
|
4873
|
+
const price = isSeriesExpr(priceArg, env) ? NA : num(evalExpr(priceArg, env));
|
|
4874
|
+
return env.ta.gammaLevels(vol, price, wave11n(2, ["period", "length"], msg));
|
|
4875
|
+
}
|
|
4876
|
+
if (
|
|
4877
|
+
fname === "ta.intelligent_strategy_synthesizer" ||
|
|
4878
|
+
fname === "intelligent_strategy_synthesizer"
|
|
4879
|
+
) {
|
|
4880
|
+
const msg = "ta.intelligent_strategy_synthesizer() requires 6 arguments";
|
|
4881
|
+
return env.ta.intelligentStrategySynthesizer(
|
|
4882
|
+
site,
|
|
4883
|
+
wave13v(0, ["trend_indicators", "trend"], msg),
|
|
4884
|
+
wave13v(1, ["momentum_indicators", "momentum"], msg),
|
|
4885
|
+
wave13v(2, ["volatility_indicators", "volatility"], msg),
|
|
4886
|
+
wave13v(3, ["volume_indicators", "volume"], msg),
|
|
4887
|
+
wave13v(4, ["market_condition", "market_regime"], msg),
|
|
4888
|
+
wave13v(5, ["risk_profile"], msg),
|
|
4889
|
+
);
|
|
4890
|
+
}
|
|
4891
|
+
if (fname === "ta.signal_confluence" || fname === "signal_confluence") {
|
|
4892
|
+
const msg = "ta.signal_confluence() requires 1 argument";
|
|
4893
|
+
return env.ta.signalConfluence(wave13v(0, ["signals"], msg));
|
|
4894
|
+
}
|
|
4895
|
+
// wave14-end
|
|
4863
4896
|
return undefined;
|
|
4864
4897
|
}
|
|
4865
4898
|
|
package/src/runtime/ta.ts
CHANGED
|
@@ -8,6 +8,8 @@
|
|
|
8
8
|
|
|
9
9
|
export type Cell = number | null;
|
|
10
10
|
|
|
11
|
+
import { PineArray } from "./array.ts";
|
|
12
|
+
|
|
11
13
|
interface SmaState {
|
|
12
14
|
period: number;
|
|
13
15
|
window: Cell[];
|
|
@@ -4520,6 +4522,168 @@ export class TaEngine {
|
|
|
4520
4522
|
return finiteOut(ev / totalProb) ?? 0;
|
|
4521
4523
|
}
|
|
4522
4524
|
|
|
4525
|
+
/** `advanced.py` `_builtin_ta_gamma_levels` — returns `[high, low]`. */
|
|
4526
|
+
gammaLevels(volatility: Cell, currentPrice: Cell, period: Cell): PineArray {
|
|
4527
|
+
const v = finiteCell(volatility) ?? 0.02;
|
|
4528
|
+
const p = finiteCell(currentPrice) ?? 100;
|
|
4529
|
+
const n = this.coerceTaInt(period, "period must be integer");
|
|
4530
|
+
const volAdjusted = v * Math.sqrt(n);
|
|
4531
|
+
const distance = p * volAdjusted;
|
|
4532
|
+
const out = new PineArray();
|
|
4533
|
+
out.push(finiteOut(p + distance));
|
|
4534
|
+
out.push(finiteOut(p - distance));
|
|
4535
|
+
return out;
|
|
4536
|
+
}
|
|
4537
|
+
|
|
4538
|
+
/** `advanced.py` `_builtin_ta_intelligent_strategy_synthesizer` (wins MRO). */
|
|
4539
|
+
intelligentStrategySynthesizer(
|
|
4540
|
+
site: string,
|
|
4541
|
+
trend: unknown,
|
|
4542
|
+
momentum: unknown,
|
|
4543
|
+
volatility: unknown,
|
|
4544
|
+
volume: unknown,
|
|
4545
|
+
marketCondition: unknown,
|
|
4546
|
+
riskProfile: unknown,
|
|
4547
|
+
): TaRecord {
|
|
4548
|
+
const th = this.windowFor(`${site}:trend`, trend);
|
|
4549
|
+
const mh = this.windowFor(`${site}:momentum`, momentum);
|
|
4550
|
+
const vh = this.windowFor(`${site}:volatility`, volatility);
|
|
4551
|
+
const volh = this.windowFor(`${site}:volume`, volume);
|
|
4552
|
+
const regime = typeof marketCondition === "string" ? marketCondition : "ranging";
|
|
4553
|
+
const profile = typeof riskProfile === "string" ? riskProfile : "balanced";
|
|
4554
|
+
|
|
4555
|
+
const nums = (h: Cell[]): number[] => h.filter((v): v is number => v !== null);
|
|
4556
|
+
const trendVals = nums(th);
|
|
4557
|
+
const momentumVals = nums(mh);
|
|
4558
|
+
const volatilityVals = nums(vh);
|
|
4559
|
+
const volumeVals = nums(volh);
|
|
4560
|
+
const avg = (vals: number[], fallback: number): number =>
|
|
4561
|
+
vals.length === 0 ? fallback : vals.reduce((a, b) => a + b, 0) / vals.length;
|
|
4562
|
+
const trendAvg = avg(trendVals, 0);
|
|
4563
|
+
const momentumAvg = avg(momentumVals, 0);
|
|
4564
|
+
const volatilityAvg = avg(volatilityVals, 0.5);
|
|
4565
|
+
const volumeAvg = avg(volumeVals, 0);
|
|
4566
|
+
|
|
4567
|
+
const volatilityNormalized = Math.max(0, Math.min(1, volatilityAvg));
|
|
4568
|
+
const composite = trendAvg * 0.4 + momentumAvg * 0.35 + volumeAvg * 0.25;
|
|
4569
|
+
const compositeSignal = Math.max(-1, Math.min(1, composite));
|
|
4570
|
+
|
|
4571
|
+
let agreement = 0;
|
|
4572
|
+
if (Math.abs(trendAvg) > 0.3) agreement += 1;
|
|
4573
|
+
if (Math.abs(momentumAvg) > 0.3) agreement += 1;
|
|
4574
|
+
if (Math.abs(volumeAvg) > 0.3) agreement += 1;
|
|
4575
|
+
const baseConfidence = agreement / 3;
|
|
4576
|
+
const confidence = Math.max(0.1, Math.min(0.99, baseConfidence - volatilityNormalized * 0.3));
|
|
4577
|
+
|
|
4578
|
+
let recommendation: string;
|
|
4579
|
+
if (compositeSignal > 0.6) {
|
|
4580
|
+
recommendation = profile === "aggressive" ? "aggressive_long" : "conservative_long";
|
|
4581
|
+
} else if (compositeSignal > 0.2) {
|
|
4582
|
+
recommendation = "conservative_long";
|
|
4583
|
+
} else if (compositeSignal < -0.6) {
|
|
4584
|
+
recommendation = profile === "aggressive" ? "aggressive_short" : "conservative_short";
|
|
4585
|
+
} else if (compositeSignal < -0.2) {
|
|
4586
|
+
recommendation = "conservative_short";
|
|
4587
|
+
} else {
|
|
4588
|
+
recommendation = "hold";
|
|
4589
|
+
}
|
|
4590
|
+
|
|
4591
|
+
const riskMult = profile === "conservative" ? 0.5 : profile === "aggressive" ? 1.5 : 1;
|
|
4592
|
+
const riskLevel = volatilityNormalized * 50 * riskMult;
|
|
4593
|
+
const absSignal = Math.abs(compositeSignal);
|
|
4594
|
+
const expectedReturn = absSignal * 3;
|
|
4595
|
+
|
|
4596
|
+
let holdingPeriod: string;
|
|
4597
|
+
if (volatilityNormalized > 0.7) holdingPeriod = "scalp";
|
|
4598
|
+
else if (volatilityNormalized > 0.5) holdingPeriod = "day_trade";
|
|
4599
|
+
else if (absSignal > 0.5) holdingPeriod = "swing";
|
|
4600
|
+
else holdingPeriod = "position";
|
|
4601
|
+
|
|
4602
|
+
const shortRec = recommendation === "aggressive_short" || recommendation === "conservative_short";
|
|
4603
|
+
const longRec = recommendation === "aggressive_long" || recommendation === "conservative_long";
|
|
4604
|
+
const stopLoss = shortRec ? -0.7 : longRec ? -0.2 : -0.15;
|
|
4605
|
+
const takeProfit =
|
|
4606
|
+
recommendation === "aggressive_long" || recommendation === "aggressive_short"
|
|
4607
|
+
? 1.5
|
|
4608
|
+
: longRec || shortRec
|
|
4609
|
+
? 1.0
|
|
4610
|
+
: 0.5;
|
|
4611
|
+
|
|
4612
|
+
let alignment = 50;
|
|
4613
|
+
if (regime === "trending_up") {
|
|
4614
|
+
if (compositeSignal > 0) alignment = 90;
|
|
4615
|
+
else if (compositeSignal > -0.3) alignment = 60;
|
|
4616
|
+
else alignment = 30;
|
|
4617
|
+
} else if (regime === "trending_down") {
|
|
4618
|
+
if (compositeSignal < 0) alignment = 90;
|
|
4619
|
+
else if (compositeSignal < 0.3) alignment = 60;
|
|
4620
|
+
else alignment = 30;
|
|
4621
|
+
} else if (regime === "ranging") {
|
|
4622
|
+
alignment = 50 + (1 - absSignal) * 30;
|
|
4623
|
+
} else if (regime === "volatile") {
|
|
4624
|
+
alignment = 30 + absSignal * 40;
|
|
4625
|
+
} else {
|
|
4626
|
+
alignment = 40;
|
|
4627
|
+
}
|
|
4628
|
+
|
|
4629
|
+
return taRec({
|
|
4630
|
+
composite_signal: finiteOut(compositeSignal) ?? 0,
|
|
4631
|
+
confidence_level: finiteOut(confidence) ?? 0,
|
|
4632
|
+
strategy_recommendation: recommendation,
|
|
4633
|
+
risk_level: finiteOut(Math.max(0, Math.min(100, riskLevel))) ?? 0,
|
|
4634
|
+
expected_return: finiteOut(expectedReturn) ?? 0,
|
|
4635
|
+
holding_period: holdingPeriod,
|
|
4636
|
+
stop_loss_priority: stopLoss,
|
|
4637
|
+
take_profit_priority: takeProfit,
|
|
4638
|
+
regime_alignment: finiteOut(Math.max(0, Math.min(100, alignment))) ?? 0,
|
|
4639
|
+
});
|
|
4640
|
+
}
|
|
4641
|
+
|
|
4642
|
+
/** `advanced.py` `_builtin_ta_signal_confluence` (dict/map of signals). */
|
|
4643
|
+
signalConfluence(signals: unknown): TaRecord {
|
|
4644
|
+
let vals: unknown[] = [];
|
|
4645
|
+
if (signals != null && typeof signals === "object") {
|
|
4646
|
+
const obj = signals as { values?: () => unknown[] };
|
|
4647
|
+
if (typeof obj.values === "function") {
|
|
4648
|
+
const got = obj.values();
|
|
4649
|
+
vals = Array.isArray(got) ? got : [];
|
|
4650
|
+
} else if (!Array.isArray(signals)) {
|
|
4651
|
+
vals = Object.entries(signals)
|
|
4652
|
+
.filter(([k]) => k !== "__taRecord")
|
|
4653
|
+
.map(([, v]) => v);
|
|
4654
|
+
}
|
|
4655
|
+
}
|
|
4656
|
+
let signalCount = 0;
|
|
4657
|
+
let bullish = 0;
|
|
4658
|
+
let bearish = 0;
|
|
4659
|
+
for (const v of vals) {
|
|
4660
|
+
if (typeof v === "boolean") {
|
|
4661
|
+
if (v) {
|
|
4662
|
+
bullish += 1;
|
|
4663
|
+
signalCount += 1;
|
|
4664
|
+
}
|
|
4665
|
+
continue;
|
|
4666
|
+
}
|
|
4667
|
+
if (typeof v === "number" && Number.isFinite(v)) {
|
|
4668
|
+
if (v > 0) {
|
|
4669
|
+
bullish += 1;
|
|
4670
|
+
signalCount += 1;
|
|
4671
|
+
} else if (v < 0) {
|
|
4672
|
+
bearish += 1;
|
|
4673
|
+
signalCount += 1;
|
|
4674
|
+
}
|
|
4675
|
+
}
|
|
4676
|
+
}
|
|
4677
|
+
const total = vals.length > 0 ? vals.length : 1;
|
|
4678
|
+
const confluence = signalCount / total;
|
|
4679
|
+
const primary = bullish > bearish ? "buy" : bearish > bullish ? "sell" : "neutral";
|
|
4680
|
+
return taRec({
|
|
4681
|
+
signal_count: signalCount,
|
|
4682
|
+
confluence_level: finiteOut(confluence) ?? 0,
|
|
4683
|
+
primary_signal: primary,
|
|
4684
|
+
});
|
|
4685
|
+
}
|
|
4686
|
+
|
|
4523
4687
|
/**
|
|
4524
4688
|
* `ta.volume_profile_high` — numeric list only (`advanced.py`).
|
|
4525
4689
|
* A one-element pair is that price when its volume is the strict max.
|