@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/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.
|