@reefclaw/connect 0.1.8 → 0.1.10

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.
Files changed (134) hide show
  1. package/assets/plugin/ccxt/binance-private.d.ts +21 -0
  2. package/assets/plugin/ccxt/binance-private.js +132 -22
  3. package/assets/plugin/config/plugin-config-io.d.ts +6 -0
  4. package/assets/plugin/config/tool-gate.js +3 -0
  5. package/assets/plugin/exchange-adapter.d.ts +16 -0
  6. package/assets/plugin/index.js +567 -57
  7. package/assets/plugin/lifecycle/trading-operation-lock.d.ts +17 -0
  8. package/assets/plugin/lifecycle/trading-operation-lock.js +14 -0
  9. package/assets/plugin/live/bracket-id.d.ts +2 -3
  10. package/assets/plugin/live/bracket-id.js +22 -9
  11. package/assets/plugin/live/live-adapter.d.ts +24 -1
  12. package/assets/plugin/live/live-adapter.js +114 -2
  13. package/assets/plugin/live/local-signal-service.js +11 -6
  14. package/assets/plugin/live/local-strategy-evaluator.js +4 -0
  15. package/assets/plugin/live/proposal-decision-listener.d.ts +6 -0
  16. package/assets/plugin/live/proposal-decision-listener.js +4 -0
  17. package/assets/plugin/live/stop-watcher.d.ts +27 -1
  18. package/assets/plugin/live/stop-watcher.js +59 -2
  19. package/assets/plugin/onboarding/runtime.d.ts +13 -0
  20. package/assets/plugin/onboarding/runtime.js +22 -2
  21. package/assets/plugin/openclaw.plugin.json +1 -0
  22. package/assets/plugin/persistence/state-manager.d.ts +24 -0
  23. package/assets/plugin/persistence/state-manager.js +62 -4
  24. package/assets/plugin/portfolio/wave9-admission.d.ts +67 -0
  25. package/assets/plugin/portfolio/wave9-admission.js +262 -0
  26. package/assets/plugin/portfolio/wave9-policy.d.ts +36 -0
  27. package/assets/plugin/portfolio/wave9-policy.js +183 -0
  28. package/assets/plugin/signals/conditions/registry.js +50 -0
  29. package/assets/plugin/simulator/exchange-simulator.js +5 -0
  30. package/assets/plugin/simulator/fill-engine.js +5 -1
  31. package/assets/plugin/simulator/types.d.ts +6 -1
  32. package/assets/plugin/strategy/evaluator.d.ts +3 -0
  33. package/assets/plugin/strategy/evaluator.js +5 -0
  34. package/assets/plugin/tools/assessment-validation.d.ts +2 -0
  35. package/assets/plugin/tools/attach-brackets.d.ts +7 -2
  36. package/assets/plugin/tools/attach-brackets.js +36 -0
  37. package/assets/plugin/tools/cancel-all-orders.d.ts +2 -0
  38. package/assets/plugin/tools/cancel-all-orders.js +4 -1
  39. package/assets/plugin/tools/cancel-order.d.ts +4 -0
  40. package/assets/plugin/tools/cancel-order.js +49 -3
  41. package/assets/plugin/tools/close-position.d.ts +23 -0
  42. package/assets/plugin/tools/close-position.js +286 -13
  43. package/assets/plugin/tools/create-order.d.ts +28 -0
  44. package/assets/plugin/tools/create-order.js +1364 -192
  45. package/assets/plugin/tools/get-analytics.js +2 -2
  46. package/assets/plugin/tools/get-basis.js +2 -2
  47. package/assets/plugin/tools/get-cascade-risk.js +2 -2
  48. package/assets/plugin/tools/get-crypto-metrics.js +20 -3
  49. package/assets/plugin/tools/get-cvd.js +2 -2
  50. package/assets/plugin/tools/get-divergences.js +2 -2
  51. package/assets/plugin/tools/get-funding-context.js +2 -2
  52. package/assets/plugin/tools/get-liquidation-levels.js +2 -2
  53. package/assets/plugin/tools/get-liquidation-pulse.js +2 -2
  54. package/assets/plugin/tools/get-pattern-scan.js +2 -2
  55. package/assets/plugin/tools/get-regime.js +2 -2
  56. package/assets/plugin/tools/get-resting-liquidity.js +2 -2
  57. package/assets/plugin/tools/get-risk-scenario.js +2 -2
  58. package/assets/plugin/tools/get-session-review.js +2 -2
  59. package/assets/plugin/tools/get-setup-detail.js +9 -1
  60. package/assets/plugin/tools/get-signals.js +2 -2
  61. package/assets/plugin/tools/get-sizing.js +2 -2
  62. package/assets/plugin/tools/get-trade-feedback.js +2 -2
  63. package/assets/plugin/tools/get-trade-flow.js +2 -2
  64. package/assets/plugin/tools/get-volume-profile.js +2 -2
  65. package/assets/plugin/tools/get-wave9-status.d.ts +127 -0
  66. package/assets/plugin/tools/get-wave9-status.js +796 -0
  67. package/assets/plugin/tools/intel-api.d.ts +20 -0
  68. package/assets/plugin/tools/intel-api.js +67 -0
  69. package/assets/plugin/tools/intel-cache.d.ts +1 -1
  70. package/assets/plugin/tools/intel-cache.js +20 -5
  71. package/assets/plugin/tools/list-strategies.d.ts +11 -1
  72. package/assets/plugin/tools/list-strategies.js +17 -0
  73. package/assets/plugin/tools/modify-stop.d.ts +4 -0
  74. package/assets/plugin/tools/modify-stop.js +63 -24
  75. package/assets/plugin/tools/modify-target.d.ts +4 -0
  76. package/assets/plugin/tools/modify-target.js +62 -23
  77. package/assets/plugin/tools/scan-pairs.js +19 -8
  78. package/assets/plugin/tools/toggle-strategy.js +7 -0
  79. package/assets/plugin/types.d.ts +5 -0
  80. package/assets/plugin/venues/hyperliquid/hl-balance.d.ts +116 -0
  81. package/assets/plugin/venues/hyperliquid/hl-balance.js +145 -0
  82. package/assets/plugin/venues/hyperliquid/hl-brackets.d.ts +102 -0
  83. package/assets/plugin/venues/hyperliquid/hl-brackets.js +172 -0
  84. package/assets/plugin/venues/hyperliquid/hl-cloid.d.ts +22 -0
  85. package/assets/plugin/venues/hyperliquid/hl-cloid.js +82 -0
  86. package/assets/plugin/venues/hyperliquid/hl-info-cache.d.ts +46 -0
  87. package/assets/plugin/venues/hyperliquid/hl-info-cache.js +125 -0
  88. package/assets/plugin/venues/hyperliquid/hl-live-adapter.d.ts +88 -0
  89. package/assets/plugin/venues/hyperliquid/hl-live-adapter.js +353 -0
  90. package/assets/plugin/venues/hyperliquid/hl-precision.d.ts +61 -0
  91. package/assets/plugin/venues/hyperliquid/hl-precision.js +176 -0
  92. package/assets/plugin/venues/hyperliquid/hl-private.d.ts +88 -0
  93. package/assets/plugin/venues/hyperliquid/hl-private.js +357 -0
  94. package/assets/plugin/venues/hyperliquid/hl-public.d.ts +31 -4
  95. package/assets/plugin/venues/hyperliquid/hl-public.js +155 -11
  96. package/assets/plugin/venues/hyperliquid/hl-rate-gate.d.ts +57 -0
  97. package/assets/plugin/venues/hyperliquid/hl-rate-gate.js +220 -0
  98. package/assets/plugin/venues/hyperliquid/hl-user-stream.d.ts +90 -0
  99. package/assets/plugin/venues/hyperliquid/hl-user-stream.js +220 -0
  100. package/assets/plugin/venues/registry.d.ts +28 -9
  101. package/assets/plugin/venues/registry.js +19 -13
  102. package/assets/plugin/venues/symbols.d.ts +43 -0
  103. package/assets/plugin/venues/symbols.js +107 -0
  104. package/assets/plugin/wave9/live-account-capture.d.ts +67 -0
  105. package/assets/plugin/wave9/live-account-capture.js +435 -0
  106. package/assets/plugin/wave9/live-autonomous-protection.d.ts +39 -0
  107. package/assets/plugin/wave9/live-autonomous-protection.js +112 -0
  108. package/assets/plugin/wave9/live-durable-reconciliation-scheduler.d.ts +33 -0
  109. package/assets/plugin/wave9/live-durable-reconciliation-scheduler.js +115 -0
  110. package/assets/plugin/wave9/live-execution-ledger.d.ts +107 -0
  111. package/assets/plugin/wave9/live-execution-ledger.js +498 -0
  112. package/assets/plugin/wave9/live-position-confirmation.d.ts +18 -0
  113. package/assets/plugin/wave9/live-position-confirmation.js +111 -0
  114. package/assets/plugin/wave9/live-residual-protection.d.ts +18 -0
  115. package/assets/plugin/wave9/live-residual-protection.js +250 -0
  116. package/assets/plugin/wave9/live-startup-reconciliation.d.ts +38 -0
  117. package/assets/plugin/wave9/live-startup-reconciliation.js +454 -0
  118. package/assets/plugin/wave9/live-symbol-ownership.d.ts +20 -0
  119. package/assets/plugin/wave9/live-symbol-ownership.js +132 -0
  120. package/assets/plugin/wave9/paper-admission-guard.d.ts +199 -0
  121. package/assets/plugin/wave9/paper-admission-guard.js +650 -0
  122. package/assets/plugin/wave9/usdm-evidence-provider.d.ts +42 -0
  123. package/assets/plugin/wave9/usdm-evidence-provider.js +133 -0
  124. package/assets/shared/portfolio/wave9-admission.d.ts +67 -0
  125. package/assets/shared/portfolio/wave9-admission.js +260 -0
  126. package/assets/shared/portfolio/wave9-policy.d.ts +36 -0
  127. package/assets/shared/portfolio/wave9-policy.js +176 -0
  128. package/assets/shared/signals/conditions/registry.js +50 -0
  129. package/assets/shared/signals/indicators-extended.d.ts +52 -0
  130. package/assets/shared/signals/indicators-extended.js +284 -0
  131. package/assets/shared/signals/indicators.d.ts +15 -0
  132. package/assets/shared/signals/indicators.js +107 -0
  133. package/dist/daemon.js +104 -0
  134. package/package.json +1 -1
@@ -0,0 +1,176 @@
1
+ // Canonical frozen Wave 9 daily signal policy used by the default-off runtime parity
2
+ // observer. This deliberately calls the same generated condition registry and
3
+ // ATR implementation as the intelligence backtest instead of reimplementing
4
+ // momentum semantics in the plugin.
5
+ import { computeATR } from '../shared/indicators.js';
6
+ import { evaluateConditions } from '../signals/conditions/registry.js';
7
+ export const DAY_MS = 24 * 60 * 60 * 1_000;
8
+ export const WAVE9_LOOKBACK_DAYS = 28;
9
+ export const WAVE9_ATR_PERIOD = 14;
10
+ export const WAVE9_ATR_MULTIPLE = 3;
11
+ export const WAVE9_CONTEXT_BARS = 250;
12
+ export const WAVE9_LONG_STRATEGY = 'tsmom_28d_long_flat_reversal_1d';
13
+ export const WAVE9_SHORT_STRATEGY = 'tsmom_28d_short_flat_reversal_1d';
14
+ export const WAVE9_SYMBOL_PRIORITY = [
15
+ 'BTCUSDT',
16
+ 'ETHUSDT',
17
+ 'SOLUSDT',
18
+ 'BNBUSDT',
19
+ 'XRPUSDT',
20
+ 'DOGEUSDT',
21
+ 'ADAUSDT',
22
+ 'LINKUSDT',
23
+ ];
24
+ export const WAVE9_STRATEGY_PRIORITY = [
25
+ WAVE9_LONG_STRATEGY,
26
+ WAVE9_SHORT_STRATEGY,
27
+ ];
28
+ function requireFinitePositive(value, label) {
29
+ if (!Number.isFinite(value) || value <= 0) {
30
+ throw new Error(`${label} must be a positive finite number`);
31
+ }
32
+ }
33
+ /**
34
+ * Return the exact completed-daily context visible at a UTC daily boundary.
35
+ * The function is intentionally fail-closed: unordered, duplicated, gapped,
36
+ * non-UTC, or stale candles are rejected rather than silently repaired.
37
+ */
38
+ export function completedDailyContext(rawBars, eventTime) {
39
+ const eventMs = eventTime.getTime();
40
+ if (!Number.isFinite(eventMs) || eventMs % DAY_MS !== 0) {
41
+ throw new Error('Wave 9 evaluation time must be a finite UTC daily boundary');
42
+ }
43
+ let previous = Number.NEGATIVE_INFINITY;
44
+ for (const [index, bar] of rawBars.entries()) {
45
+ const time = bar.time.getTime();
46
+ if (!Number.isFinite(time) || time % DAY_MS !== 0) {
47
+ throw new Error(`daily bar ${index} is not aligned to UTC midnight`);
48
+ }
49
+ if (time <= previous) {
50
+ throw new Error('daily bars must be strictly chronological and unique');
51
+ }
52
+ previous = time;
53
+ requireFinitePositive(bar.open, `daily bar ${index} open`);
54
+ requireFinitePositive(bar.high, `daily bar ${index} high`);
55
+ requireFinitePositive(bar.low, `daily bar ${index} low`);
56
+ requireFinitePositive(bar.close, `daily bar ${index} close`);
57
+ if (!Number.isFinite(bar.volume) || bar.volume < 0) {
58
+ throw new Error(`daily bar ${index} volume must be finite and non-negative`);
59
+ }
60
+ if (bar.high < Math.max(bar.open, bar.close) || bar.low > Math.min(bar.open, bar.close)) {
61
+ throw new Error(`daily bar ${index} has invalid OHLC geometry`);
62
+ }
63
+ }
64
+ const completed = rawBars
65
+ .filter((bar) => bar.time.getTime() + DAY_MS <= eventMs)
66
+ .slice(-WAVE9_CONTEXT_BARS);
67
+ if (completed.length <= WAVE9_LOOKBACK_DAYS + 1) {
68
+ throw new Error(`Wave 9 requires at least ${WAVE9_LOOKBACK_DAYS + 2} completed daily bars`);
69
+ }
70
+ const expectedLastOpen = eventMs - DAY_MS;
71
+ const lastOpen = completed[completed.length - 1].time.getTime();
72
+ if (lastOpen !== expectedLastOpen) {
73
+ throw new Error(`latest completed daily bar must open at ${new Date(expectedLastOpen).toISOString()}`);
74
+ }
75
+ for (let index = 1; index < completed.length; index++) {
76
+ const prior = completed[index - 1].time.getTime();
77
+ const current = completed[index].time.getTime();
78
+ if (current - prior !== DAY_MS) {
79
+ throw new Error(`daily history has a gap between ${completed[index - 1].time.toISOString()} and ${completed[index].time.toISOString()}`);
80
+ }
81
+ }
82
+ return completed.map((bar) => ({ ...bar, time: new Date(bar.time) }));
83
+ }
84
+ function makeContext(bars, atr14) {
85
+ const latest = bars[bars.length - 1];
86
+ return {
87
+ symbol: 'WAVE9',
88
+ timestamp: new Date(latest.time.getTime() + DAY_MS),
89
+ ohlcv1h: bars,
90
+ ohlcv5m: [],
91
+ ohlcv4h: [],
92
+ ohlcv1d: bars,
93
+ currentPrice: latest.close,
94
+ atr14,
95
+ obImbalance: 0.5,
96
+ fundingRates: [],
97
+ fundingMean: 0,
98
+ fundingStd: 0.0001,
99
+ oiValues: [],
100
+ liqNearEntry: 0,
101
+ tradeFlow: [],
102
+ regime: 'UNKNOWN',
103
+ regimeConfidence: 0,
104
+ };
105
+ }
106
+ function conditionMet(context, type, params, direction) {
107
+ const result = evaluateConditions([{ type, params }], context, direction);
108
+ if (result.conditions.length !== 1 || result.conditions[0].name !== type) {
109
+ throw new Error(`canonical condition registry did not evaluate ${type}`);
110
+ }
111
+ return result.conditions[0].met;
112
+ }
113
+ /** Evaluate the exact Wave 9 entry-transition and signal-reversal semantics. */
114
+ export function evaluateWave9Daily(rawBars, eventTime) {
115
+ const bars = completedDailyContext(rawBars, eventTime);
116
+ const highs = bars.map((bar) => bar.high);
117
+ const lows = bars.map((bar) => bar.low);
118
+ const closes = bars.map((bar) => bar.close);
119
+ const atr14 = computeATR(highs, lows, closes, WAVE9_ATR_PERIOD);
120
+ requireFinitePositive(atr14, 'Wave 9 daily ATR(14)');
121
+ const context = makeContext(bars, atr14);
122
+ const currentClose = closes[closes.length - 1];
123
+ const currentPrior = closes[closes.length - 1 - WAVE9_LOOKBACK_DAYS];
124
+ const previousClose = closes[closes.length - 2];
125
+ const previousPrior = closes[closes.length - 2 - WAVE9_LOOKBACK_DAYS];
126
+ const currentReturn = (currentClose - currentPrior) / currentPrior;
127
+ const previousReturn = (previousClose - previousPrior) / previousPrior;
128
+ if (!Number.isFinite(currentReturn) || !Number.isFinite(previousReturn)) {
129
+ throw new Error('Wave 9 momentum inputs are not finite');
130
+ }
131
+ const longEntry = conditionMet(context, 'return_momentum_zero_cross', { tfHours: 24, lookback: WAVE9_LOOKBACK_DAYS, dirSign: 1 }, 'LONG');
132
+ const shortEntry = conditionMet(context, 'return_momentum_zero_cross', { tfHours: 24, lookback: WAVE9_LOOKBACK_DAYS, dirSign: -1 }, 'SHORT');
133
+ const exitLong = conditionMet(context, 'return_momentum', {
134
+ tfHours: 24,
135
+ lookback: WAVE9_LOOKBACK_DAYS,
136
+ minReturnPct: 0,
137
+ maxAbsReturnPct: 0,
138
+ dirSign: -1,
139
+ mode: 1,
140
+ }, 'SHORT');
141
+ const exitShort = conditionMet(context, 'return_momentum', {
142
+ tfHours: 24,
143
+ lookback: WAVE9_LOOKBACK_DAYS,
144
+ minReturnPct: 0,
145
+ maxAbsReturnPct: 0,
146
+ dirSign: 1,
147
+ mode: 1,
148
+ }, 'LONG');
149
+ const entries = [];
150
+ if (longEntry) {
151
+ entries.push({
152
+ strategy: WAVE9_LONG_STRATEGY,
153
+ direction: 'LONG',
154
+ stopPrice: currentClose - atr14 * WAVE9_ATR_MULTIPLE,
155
+ });
156
+ }
157
+ if (shortEntry) {
158
+ entries.push({
159
+ strategy: WAVE9_SHORT_STRATEGY,
160
+ direction: 'SHORT',
161
+ stopPrice: currentClose + atr14 * WAVE9_ATR_MULTIPLE,
162
+ });
163
+ }
164
+ return {
165
+ eventTime: eventTime.toISOString(),
166
+ completedDailyOpen: bars[bars.length - 1].time.toISOString(),
167
+ currentReturn,
168
+ previousReturn,
169
+ atr14,
170
+ referencePrice: currentClose,
171
+ entries,
172
+ exitLong,
173
+ exitShort,
174
+ sourceBarCount: bars.length,
175
+ };
176
+ }
@@ -770,6 +770,56 @@ register('return_momentum', (ctx, params, direction) => {
770
770
  : `${lookback}-bar ${tfHours}h return ${(ret * 100).toFixed(2)}% fails ${label} threshold ${(minReturnPct * 100).toFixed(1)}%${capText}`,
771
771
  };
772
772
  });
773
+ // Strict return-momentum episode transition. Unlike return_momentum, this is
774
+ // an event rather than a level: it fires once when the lookback return crosses
775
+ // zero (or an explicit threshold), then remains false until the sign resets.
776
+ // That makes stop-outs and capacity-rejected entries naturally lock out for
777
+ // the rest of the same momentum episode.
778
+ register('return_momentum_zero_cross', (ctx, params, direction) => {
779
+ const lookback = Math.max(1, Math.floor(params.lookback ?? 28));
780
+ const thresholdPct = Math.max(0, params.thresholdPct ?? 0);
781
+ const dirSign = params.dirSign ?? 0;
782
+ const tfHours = params.tfHours ?? 24;
783
+ const bars = pickBars(ctx, tfHours);
784
+ if (bars.length <= lookback + 1) {
785
+ return {
786
+ met: false,
787
+ value: 0,
788
+ threshold: thresholdPct * 100,
789
+ description: `Insufficient ${tfHours}h bars for return_momentum_zero_cross`,
790
+ };
791
+ }
792
+ const currentClose = bars[bars.length - 1].close;
793
+ const currentPrior = bars[bars.length - 1 - lookback].close;
794
+ const previousClose = bars[bars.length - 2].close;
795
+ const previousPrior = bars[bars.length - 2 - lookback].close;
796
+ const currentReturn = currentPrior > 0
797
+ ? (currentClose - currentPrior) / currentPrior
798
+ : Number.NaN;
799
+ const previousReturn = previousPrior > 0
800
+ ? (previousClose - previousPrior) / previousPrior
801
+ : Number.NaN;
802
+ const sign = dirSign !== 0
803
+ ? Math.sign(dirSign)
804
+ : direction === 'LONG' ? 1 : direction === 'SHORT' ? -1 : 0;
805
+ const met = Number.isFinite(currentReturn)
806
+ && Number.isFinite(previousReturn)
807
+ && (sign > 0
808
+ ? previousReturn <= thresholdPct && currentReturn > thresholdPct
809
+ : sign < 0
810
+ ? previousReturn >= -thresholdPct && currentReturn < -thresholdPct
811
+ : false);
812
+ return {
813
+ met,
814
+ value: Number.isFinite(currentReturn)
815
+ ? Math.round(currentReturn * 10000) / 100
816
+ : 0,
817
+ threshold: thresholdPct * 100,
818
+ description: Number.isFinite(currentReturn) && Number.isFinite(previousReturn)
819
+ ? `${lookback}-bar ${tfHours}h return moved from ${(previousReturn * 100).toFixed(2)}% to ${(currentReturn * 100).toFixed(2)}%`
820
+ : `Invalid ${tfHours}h prices for return_momentum_zero_cross`,
821
+ };
822
+ });
773
823
  // ─── 32. rsi_multi_pivot_divergence ──────────────────────────────────
774
824
  // Strict N-pivot RSI divergence: price makes successively lower lows (or
775
825
  // higher highs) while RSI makes the opposite, with optional volume dry-up
@@ -0,0 +1,52 @@
1
+ export interface OhlcvInput {
2
+ open: number;
3
+ high: number;
4
+ low: number;
5
+ close: number;
6
+ volume: number;
7
+ }
8
+ export interface MACDResult {
9
+ line: number;
10
+ signal: number;
11
+ histogram: number;
12
+ crossover: 'bullish' | 'bearish' | 'none';
13
+ }
14
+ export interface BollingerResult {
15
+ upper: number;
16
+ middle: number;
17
+ lower: number;
18
+ bandwidth: number;
19
+ percentB: number;
20
+ }
21
+ export interface StochRSIResult {
22
+ k: number;
23
+ d: number;
24
+ }
25
+ export interface IchimokuResult {
26
+ tenkan: number;
27
+ kijun: number;
28
+ senkouA: number;
29
+ senkouB: number;
30
+ chikou: number;
31
+ cloudPosition: 'above' | 'below' | 'inside';
32
+ }
33
+ export interface SupertrendResult {
34
+ value: number;
35
+ direction: 'bullish' | 'bearish';
36
+ }
37
+ export declare function computeMACD(closes: number[], fast?: number, slow?: number, signal?: number): MACDResult;
38
+ export declare function computeBollingerBands(closes: number[], period?: number, stdDev?: number): BollingerResult;
39
+ export declare function computeVWAP(bars: OhlcvInput[]): number;
40
+ export declare function computeStochRSI(closes: number[], rsiPeriod?: number, stochPeriod?: number, kSmooth?: number, dSmooth?: number): StochRSIResult;
41
+ export declare function computeADX(highs: number[], lows: number[], closes: number[], period?: number): {
42
+ adx: number;
43
+ plusDI: number;
44
+ minusDI: number;
45
+ };
46
+ export declare function computeIchimoku(highs: number[], lows: number[], closes: number[], tenkanPeriod?: number, kijunPeriod?: number, senkouBPeriod?: number): IchimokuResult;
47
+ export declare function computeOBV(closes: number[], volumes: number[]): {
48
+ obv: number;
49
+ slope: 'rising' | 'falling' | 'flat';
50
+ };
51
+ export declare function computeSupertrend(highs: number[], lows: number[], closes: number[], period?: number, multiplier?: number): SupertrendResult;
52
+ export declare function computeWilliamsR(highs: number[], lows: number[], closes: number[], period?: number): number;
@@ -0,0 +1,284 @@
1
+ // Extended indicator computations for Phase 13 — Expanded Indicators.
2
+ // Wraps technicalindicators library + custom implementations.
3
+ // All functions take OHLCV arrays (oldest first) and return latest values.
4
+ import { computeATRSeries, computeRSI, mean, computeStd } from './indicators.js';
5
+ // ─── MACD (12, 26, 9) ──────────────────────────────────────────────────
6
+ export function computeMACD(closes, fast = 12, slow = 26, signal = 9) {
7
+ if (closes.length < slow + signal) {
8
+ return { line: 0, signal: 0, histogram: 0, crossover: 'none' };
9
+ }
10
+ // Compute full EMA series (both aligned to start at index `slow - 1`)
11
+ const emaFastSeries = emaSeries(closes, fast);
12
+ const emaSlowSeries = emaSeries(closes, slow);
13
+ // Align: fast series starts earlier, so take the tail matching slow series length
14
+ const offset = emaFastSeries.length - emaSlowSeries.length;
15
+ const macdLine = [];
16
+ for (let i = 0; i < emaSlowSeries.length; i++) {
17
+ macdLine.push(emaFastSeries[i + offset] - emaSlowSeries[i]);
18
+ }
19
+ // Signal line = EMA of MACD line
20
+ const signalSeries = emaSeries(macdLine, signal);
21
+ const sigOffset = macdLine.length - signalSeries.length;
22
+ const currentLine = macdLine[macdLine.length - 1];
23
+ const currentSignal = signalSeries[signalSeries.length - 1];
24
+ const prevLine = macdLine.length >= 2 ? macdLine[macdLine.length - 2] : currentLine;
25
+ const prevSignalIdx = signalSeries.length >= 2 ? signalSeries.length - 2 : signalSeries.length - 1;
26
+ const prevSignal = signalSeries[prevSignalIdx];
27
+ let crossover = 'none';
28
+ if (prevLine <= prevSignal && currentLine > currentSignal)
29
+ crossover = 'bullish';
30
+ else if (prevLine >= prevSignal && currentLine < currentSignal)
31
+ crossover = 'bearish';
32
+ return {
33
+ line: currentLine,
34
+ signal: currentSignal,
35
+ histogram: currentLine - currentSignal,
36
+ crossover,
37
+ };
38
+ }
39
+ // ─── Bollinger Bands (20, 2σ) ───────────────────────────────────────────
40
+ export function computeBollingerBands(closes, period = 20, stdDev = 2) {
41
+ if (closes.length < period) {
42
+ const p = closes[closes.length - 1] ?? 0;
43
+ return { upper: p, middle: p, lower: p, bandwidth: 0, percentB: 0.5 };
44
+ }
45
+ const slice = closes.slice(-period);
46
+ const middle = mean(slice);
47
+ const std = computeStd(slice);
48
+ const upper = middle + stdDev * std;
49
+ const lower = middle - stdDev * std;
50
+ const bandwidth = middle > 0 ? ((upper - lower) / middle) * 100 : 0;
51
+ const price = closes[closes.length - 1];
52
+ const percentB = upper !== lower ? (price - lower) / (upper - lower) : 0.5;
53
+ return { upper, middle, lower, bandwidth, percentB };
54
+ }
55
+ // ─── VWAP ───────────────────────────────────────────────────────────────
56
+ export function computeVWAP(bars) {
57
+ if (bars.length === 0)
58
+ return 0;
59
+ let cumVolume = 0;
60
+ let cumTPxVol = 0;
61
+ for (const bar of bars) {
62
+ const tp = (bar.high + bar.low + bar.close) / 3;
63
+ cumVolume += bar.volume;
64
+ cumTPxVol += tp * bar.volume;
65
+ }
66
+ return cumVolume > 0 ? cumTPxVol / cumVolume : bars[bars.length - 1].close;
67
+ }
68
+ // ─── Stochastic RSI (14, 14, 3, 3) ─────────────────────────────────────
69
+ export function computeStochRSI(closes, rsiPeriod = 14, stochPeriod = 14, kSmooth = 3, dSmooth = 3) {
70
+ if (closes.length < rsiPeriod + stochPeriod + dSmooth) {
71
+ return { k: 50, d: 50 };
72
+ }
73
+ // Compute RSI series
74
+ const rsiValues = [];
75
+ for (let i = rsiPeriod + 1; i <= closes.length; i++) {
76
+ rsiValues.push(computeRSI(closes.slice(0, i), rsiPeriod));
77
+ }
78
+ if (rsiValues.length < stochPeriod)
79
+ return { k: 50, d: 50 };
80
+ // Stochastic of RSI
81
+ const rawK = [];
82
+ for (let i = stochPeriod - 1; i < rsiValues.length; i++) {
83
+ const window = rsiValues.slice(i - stochPeriod + 1, i + 1);
84
+ const min = Math.min(...window);
85
+ const max = Math.max(...window);
86
+ rawK.push(max !== min ? ((rsiValues[i] - min) / (max - min)) * 100 : 50);
87
+ }
88
+ // %K = SMA of raw stochastic
89
+ const kValues = sma(rawK, kSmooth);
90
+ // %D = SMA of %K
91
+ const dValues = sma(kValues, dSmooth);
92
+ return {
93
+ k: Math.round(kValues[kValues.length - 1] ?? 50),
94
+ d: Math.round(dValues[dValues.length - 1] ?? 50),
95
+ };
96
+ }
97
+ // ─── ADX (14) ───────────────────────────────────────────────────────────
98
+ export function computeADX(highs, lows, closes, period = 14) {
99
+ if (highs.length < period * 2 + 1) {
100
+ return { adx: 0, plusDI: 0, minusDI: 0 };
101
+ }
102
+ const plusDM = [];
103
+ const minusDM = [];
104
+ const tr = [];
105
+ for (let i = 1; i < highs.length; i++) {
106
+ const upMove = highs[i] - highs[i - 1];
107
+ const downMove = lows[i - 1] - lows[i];
108
+ plusDM.push(upMove > downMove && upMove > 0 ? upMove : 0);
109
+ minusDM.push(downMove > upMove && downMove > 0 ? downMove : 0);
110
+ tr.push(Math.max(highs[i] - lows[i], Math.abs(highs[i] - closes[i - 1]), Math.abs(lows[i] - closes[i - 1])));
111
+ }
112
+ // Smooth with Wilder's smoothing (equivalent to EMA with alpha=1/period)
113
+ const smoothPlusDM = wilderSmooth(plusDM, period);
114
+ const smoothMinusDM = wilderSmooth(minusDM, period);
115
+ const smoothTR = wilderSmooth(tr, period);
116
+ // +DI and -DI series
117
+ const plusDISeries = [];
118
+ const minusDISeries = [];
119
+ for (let i = 0; i < smoothTR.length; i++) {
120
+ plusDISeries.push(smoothTR[i] > 0 ? (smoothPlusDM[i] / smoothTR[i]) * 100 : 0);
121
+ minusDISeries.push(smoothTR[i] > 0 ? (smoothMinusDM[i] / smoothTR[i]) * 100 : 0);
122
+ }
123
+ // DX series
124
+ const dxSeries = [];
125
+ for (let i = 0; i < plusDISeries.length; i++) {
126
+ const sum = plusDISeries[i] + minusDISeries[i];
127
+ dxSeries.push(sum > 0 ? (Math.abs(plusDISeries[i] - minusDISeries[i]) / sum) * 100 : 0);
128
+ }
129
+ // ADX = Wilder smooth of DX
130
+ const adxSeries = wilderSmooth(dxSeries, period);
131
+ return {
132
+ adx: Math.round(adxSeries[adxSeries.length - 1] ?? 0),
133
+ plusDI: Math.round(plusDISeries[plusDISeries.length - 1] ?? 0),
134
+ minusDI: Math.round(minusDISeries[minusDISeries.length - 1] ?? 0),
135
+ };
136
+ }
137
+ // ─── Ichimoku Cloud ─────────────────────────────────────────────────────
138
+ export function computeIchimoku(highs, lows, closes, tenkanPeriod = 9, kijunPeriod = 26, senkouBPeriod = 52) {
139
+ const n = highs.length;
140
+ if (n < senkouBPeriod) {
141
+ const p = closes[n - 1] ?? 0;
142
+ return { tenkan: p, kijun: p, senkouA: p, senkouB: p, chikou: p, cloudPosition: 'inside' };
143
+ }
144
+ const midpoint = (arr, period, end) => {
145
+ const slice = arr.slice(Math.max(0, end - period + 1), end + 1);
146
+ return (Math.max(...slice) + Math.min(...slice)) / 2;
147
+ };
148
+ const tenkan = midpoint(highs.concat().map((h, i) => Math.max(h, lows[i])), tenkanPeriod, n - 1);
149
+ const kijun = midpoint(highs.concat().map((h, i) => Math.max(h, lows[i])), kijunPeriod, n - 1);
150
+ // Recalculate properly using highs and lows separately
151
+ const tenkanH = Math.max(...highs.slice(-tenkanPeriod));
152
+ const tenkanL = Math.min(...lows.slice(-tenkanPeriod));
153
+ const tenkanVal = (tenkanH + tenkanL) / 2;
154
+ const kijunH = Math.max(...highs.slice(-kijunPeriod));
155
+ const kijunL = Math.min(...lows.slice(-kijunPeriod));
156
+ const kijunVal = (kijunH + kijunL) / 2;
157
+ const senkouA = (tenkanVal + kijunVal) / 2;
158
+ const senkouBH = Math.max(...highs.slice(-senkouBPeriod));
159
+ const senkouBL = Math.min(...lows.slice(-senkouBPeriod));
160
+ const senkouB = (senkouBH + senkouBL) / 2;
161
+ const chikou = closes[n - 1]; // Current close (projected back 26 periods)
162
+ const price = closes[n - 1];
163
+ const cloudTop = Math.max(senkouA, senkouB);
164
+ const cloudBottom = Math.min(senkouA, senkouB);
165
+ const cloudPosition = price > cloudTop ? 'above' : price < cloudBottom ? 'below' : 'inside';
166
+ return { tenkan: tenkanVal, kijun: kijunVal, senkouA, senkouB, chikou, cloudPosition };
167
+ }
168
+ // ─── OBV (On-Balance Volume) ────────────────────────────────────────────
169
+ export function computeOBV(closes, volumes) {
170
+ if (closes.length < 2)
171
+ return { obv: 0, slope: 'flat' };
172
+ let obv = 0;
173
+ const obvSeries = [0];
174
+ for (let i = 1; i < closes.length; i++) {
175
+ if (closes[i] > closes[i - 1])
176
+ obv += volumes[i];
177
+ else if (closes[i] < closes[i - 1])
178
+ obv -= volumes[i];
179
+ obvSeries.push(obv);
180
+ }
181
+ // Slope over last 10 bars
182
+ const lookback = Math.min(10, obvSeries.length);
183
+ const recent = obvSeries.slice(-lookback);
184
+ const first = recent[0];
185
+ const last = recent[recent.length - 1];
186
+ const threshold = Math.abs(first) * 0.01; // 1% threshold
187
+ const slope = last - first > threshold ? 'rising' : last - first < -threshold ? 'falling' : 'flat';
188
+ return { obv, slope };
189
+ }
190
+ // ─── Supertrend (10, 3) ─────────────────────────────────────────────────
191
+ export function computeSupertrend(highs, lows, closes, period = 10, multiplier = 3) {
192
+ const atrSeries = computeATRSeries(highs, lows, closes, period);
193
+ if (atrSeries.length === 0) {
194
+ return { value: closes[closes.length - 1] ?? 0, direction: 'bullish' };
195
+ }
196
+ // ATR series starts at index 1 (needs previous close for TR)
197
+ // Align: atrSeries[i] corresponds to bar index i+1
198
+ let upperBand = 0;
199
+ let lowerBand = 0;
200
+ let supertrend = 0;
201
+ let direction = 'bullish';
202
+ for (let i = 0; i < atrSeries.length; i++) {
203
+ const barIdx = i + 1; // offset for TR calculation
204
+ const hl2 = (highs[barIdx] + lows[barIdx]) / 2;
205
+ const atr = atrSeries[i];
206
+ const basicUpper = hl2 + multiplier * atr;
207
+ const basicLower = hl2 - multiplier * atr;
208
+ upperBand = i > 0 && basicUpper < upperBand && closes[barIdx - 1] > upperBand ? upperBand : basicUpper;
209
+ lowerBand = i > 0 && basicLower > lowerBand && closes[barIdx - 1] < lowerBand ? lowerBand : basicLower;
210
+ if (i === 0) {
211
+ supertrend = closes[barIdx] > upperBand ? lowerBand : upperBand;
212
+ direction = closes[barIdx] > upperBand ? 'bullish' : 'bearish';
213
+ }
214
+ else {
215
+ if (direction === 'bullish') {
216
+ if (closes[barIdx] < lowerBand) {
217
+ direction = 'bearish';
218
+ supertrend = upperBand;
219
+ }
220
+ else {
221
+ supertrend = lowerBand;
222
+ }
223
+ }
224
+ else {
225
+ if (closes[barIdx] > upperBand) {
226
+ direction = 'bullish';
227
+ supertrend = lowerBand;
228
+ }
229
+ else {
230
+ supertrend = upperBand;
231
+ }
232
+ }
233
+ }
234
+ }
235
+ return { value: supertrend, direction };
236
+ }
237
+ // ─── Williams %R (14) ───────────────────────────────────────────────────
238
+ export function computeWilliamsR(highs, lows, closes, period = 14) {
239
+ if (highs.length < period)
240
+ return -50;
241
+ const recentHighs = highs.slice(-period);
242
+ const recentLows = lows.slice(-period);
243
+ const hh = Math.max(...recentHighs);
244
+ const ll = Math.min(...recentLows);
245
+ const close = closes[closes.length - 1];
246
+ return hh !== ll ? ((hh - close) / (hh - ll)) * -100 : -50;
247
+ }
248
+ // ─── Helper: EMA series ─────────────────────────────────────────────────
249
+ function emaSeries(data, period) {
250
+ if (data.length === 0)
251
+ return [];
252
+ if (data.length < period)
253
+ return [data[data.length - 1]];
254
+ const k = 2 / (period + 1);
255
+ const result = [];
256
+ let ema = mean(data.slice(0, period));
257
+ result.push(ema);
258
+ for (let i = period; i < data.length; i++) {
259
+ ema = data[i] * k + ema * (1 - k);
260
+ result.push(ema);
261
+ }
262
+ return result;
263
+ }
264
+ // ─── Helper: SMA series ─────────────────────────────────────────────────
265
+ function sma(data, period) {
266
+ if (data.length < period)
267
+ return data.length > 0 ? [mean(data)] : [];
268
+ const result = [];
269
+ for (let i = period - 1; i < data.length; i++) {
270
+ result.push(mean(data.slice(i - period + 1, i + 1)));
271
+ }
272
+ return result;
273
+ }
274
+ // ─── Helper: Wilder's smoothing ─────────────────────────────────────────
275
+ function wilderSmooth(data, period) {
276
+ if (data.length < period)
277
+ return [];
278
+ const result = [];
279
+ result.push(mean(data.slice(0, period)));
280
+ for (let i = period; i < data.length; i++) {
281
+ result.push((result[result.length - 1] * (period - 1) + data[i]) / period);
282
+ }
283
+ return result;
284
+ }
@@ -0,0 +1,15 @@
1
+ export declare function computeEMA(data: number[], period: number): number;
2
+ export declare function computeATR(highs: number[], lows: number[], closes: number[], period: number): number;
3
+ export declare function computeATRSeries(highs: number[], lows: number[], closes: number[], period: number): number[];
4
+ export declare function computeRSI(closes: number[], period: number): number;
5
+ export declare function linearSlope(y: number[], x?: number[]): number;
6
+ export declare function computeStd(data: number[]): number;
7
+ export declare function mean(data: number[]): number;
8
+ /** Find swing highs and lows from OHLCV bars (simple pivot-point method). */
9
+ export declare function findSwingPoints(bars: {
10
+ high: number;
11
+ low: number;
12
+ }[], lookback?: number): {
13
+ highs: number[];
14
+ lows: number[];
15
+ };