dexbot 1.6.5 → 1.6.6
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 +36 -4
- package/README.md +104 -35
- package/analysis/README.md +11 -16
- package/analysis/account_resolver.ts +1 -11
- package/analysis/bot_usage/kibana_bot_queries.ts +1 -1
- package/analysis/chain_pool.ts +1 -1
- package/analysis/fills_source.ts +0 -6
- package/analysis/grid_correction_check.ts +20 -26
- package/analysis/resolve_source.ts +1 -1
- package/analysis/trade_profitability.ts +2 -5
- package/analysis/trend_detection/README.md +1 -3
- package/analysis/trend_detection/package.json +1 -1
- package/claw/package.json +1 -1
- package/claw/runtimes/openclaw-plugin/openclaw.plugin.json +1 -1
- package/claw/runtimes/openclaw-plugin/package.json +1 -1
- package/claw/tests/test_claw_mcp_transport.ts +2 -2
- package/dist/analysis/account_resolver.d.ts +1 -4
- package/dist/analysis/account_resolver.d.ts.map +1 -1
- package/dist/analysis/account_resolver.js +1 -1
- package/dist/analysis/account_resolver.js.map +1 -1
- package/dist/analysis/bot_usage/kibana_bot_queries.d.ts +1 -2
- package/dist/analysis/bot_usage/kibana_bot_queries.d.ts.map +1 -1
- package/dist/analysis/bot_usage/kibana_bot_queries.js +1 -1
- package/dist/analysis/bot_usage/kibana_bot_queries.js.map +1 -1
- package/dist/analysis/chain_pool.d.ts +1 -3
- package/dist/analysis/chain_pool.d.ts.map +1 -1
- package/dist/analysis/chain_pool.js +1 -1
- package/dist/analysis/chain_pool.js.map +1 -1
- package/dist/analysis/fills_source.d.ts +1 -51
- package/dist/analysis/fills_source.d.ts.map +1 -1
- package/dist/analysis/fills_source.js +1 -1
- package/dist/analysis/fills_source.js.map +1 -1
- package/dist/analysis/grid_correction_check.d.ts +2 -3
- package/dist/analysis/grid_correction_check.d.ts.map +1 -1
- package/dist/analysis/grid_correction_check.js +18 -26
- package/dist/analysis/grid_correction_check.js.map +1 -1
- package/dist/analysis/resolve_source.d.ts +1 -1
- package/dist/analysis/resolve_source.d.ts.map +1 -1
- package/dist/analysis/trade_profitability.d.ts.map +1 -1
- package/dist/analysis/trade_profitability.js +2 -7
- package/dist/analysis/trade_profitability.js.map +1 -1
- package/dist/dexbot.d.ts.map +1 -1
- package/dist/dexbot.js +60 -74
- package/dist/dexbot.js.map +1 -1
- package/dist/market_adapter/core/kibana_candles.d.ts +1 -15
- package/dist/market_adapter/core/kibana_candles.d.ts.map +1 -1
- package/dist/market_adapter/core/kibana_candles.js +1 -1
- package/dist/market_adapter/core/kibana_candles.js.map +1 -1
- package/dist/market_adapter/inputs/fetch_lp_data.d.ts +1 -35
- package/dist/market_adapter/inputs/fetch_lp_data.d.ts.map +1 -1
- package/dist/market_adapter/inputs/fetch_lp_data.js +10 -54
- package/dist/market_adapter/inputs/fetch_lp_data.js.map +1 -1
- package/dist/market_adapter/inputs/kibana_feed_source.d.ts +1 -8
- package/dist/market_adapter/inputs/kibana_feed_source.d.ts.map +1 -1
- package/dist/market_adapter/inputs/kibana_feed_source.js +1 -1
- package/dist/market_adapter/inputs/kibana_feed_source.js.map +1 -1
- package/dist/market_adapter/inputs/window_cache.d.ts +2 -63
- package/dist/market_adapter/inputs/window_cache.d.ts.map +1 -1
- package/dist/market_adapter/inputs/window_cache.js +22 -102
- package/dist/market_adapter/inputs/window_cache.js.map +1 -1
- package/dist/modules/account_bots.d.ts +16 -2
- package/dist/modules/account_bots.d.ts.map +1 -1
- package/dist/modules/account_bots.js +107 -44
- package/dist/modules/account_bots.js.map +1 -1
- package/dist/modules/bitshares-native/crypto/ecc.browser.d.ts.map +1 -1
- package/dist/modules/bitshares-native/crypto/ecc.browser.js +0 -31
- package/dist/modules/bitshares-native/crypto/ecc.browser.js.map +1 -1
- package/dist/modules/credit_runtime.d.ts +0 -2
- package/dist/modules/credit_runtime.d.ts.map +1 -1
- package/dist/modules/credit_runtime.js +1 -10
- package/dist/modules/credit_runtime.js.map +1 -1
- package/dist/modules/dexbot_class.d.ts +13 -10
- package/dist/modules/dexbot_class.d.ts.map +1 -1
- package/dist/modules/dexbot_class.js +0 -9
- package/dist/modules/dexbot_class.js.map +1 -1
- package/dist/modules/dexbot_cow_runtime.d.ts +13 -0
- package/dist/modules/dexbot_cow_runtime.d.ts.map +1 -1
- package/dist/modules/dexbot_cow_runtime.js +13 -1
- package/dist/modules/dexbot_cow_runtime.js.map +1 -1
- package/dist/modules/order/accounting.d.ts +4 -18
- package/dist/modules/order/accounting.d.ts.map +1 -1
- package/dist/modules/order/accounting.js +4 -34
- package/dist/modules/order/accounting.js.map +1 -1
- package/dist/modules/order/export.d.ts +9 -12
- package/dist/modules/order/export.d.ts.map +1 -1
- package/dist/modules/order/export.js +9 -12
- package/dist/modules/order/export.js.map +1 -1
- package/dist/modules/order/grid.d.ts +35 -40
- package/dist/modules/order/grid.d.ts.map +1 -1
- package/dist/modules/order/grid.js +43 -43
- package/dist/modules/order/grid.js.map +1 -1
- package/dist/modules/order/grid_reconcile.d.ts.map +1 -1
- package/dist/modules/order/grid_reconcile.js +111 -20
- package/dist/modules/order/grid_reconcile.js.map +1 -1
- package/dist/modules/order/grid_reconcile_internal.d.ts +5 -3
- package/dist/modules/order/grid_reconcile_internal.d.ts.map +1 -1
- package/dist/modules/order/grid_reconcile_internal.js +18 -3
- package/dist/modules/order/grid_reconcile_internal.js.map +1 -1
- package/dist/modules/order/manager.d.ts +0 -8
- package/dist/modules/order/manager.d.ts.map +1 -1
- package/dist/modules/order/manager.js +0 -10
- package/dist/modules/order/manager.js.map +1 -1
- package/dist/modules/order/utils/order.d.ts +22 -7
- package/dist/modules/order/utils/order.d.ts.map +1 -1
- package/dist/modules/order/utils/order.js +103 -21
- package/dist/modules/order/utils/order.js.map +1 -1
- package/dist/modules/order/utils/withPoolRef.d.ts +12 -0
- package/dist/modules/order/utils/withPoolRef.d.ts.map +1 -1
- package/dist/modules/order/utils/withPoolRef.js +17 -0
- package/dist/modules/order/utils/withPoolRef.js.map +1 -1
- package/dist/modules/order/working_grid.d.ts +8 -13
- package/dist/modules/order/working_grid.d.ts.map +1 -1
- package/dist/modules/order/working_grid.js +8 -29
- package/dist/modules/order/working_grid.js.map +1 -1
- package/dist/scripts/sync-version.js +0 -22
- package/dist/scripts/sync-version.js.map +1 -1
- package/dist/scripts/update_dist_freshness.d.ts +12 -0
- package/dist/scripts/update_dist_freshness.d.ts.map +1 -1
- package/dist/scripts/update_dist_freshness.js +30 -1
- package/dist/scripts/update_dist_freshness.js.map +1 -1
- package/docs/BITSHARES_ONBOARDING.md +9 -6
- package/docs/COW_INVARIANTS.md +1 -0
- package/docs/DEXBOT_COMPARISON.md +11 -11
- package/docs/EVOLUTION.md +8 -7
- package/docs/FUND_MOVEMENT_AND_ACCOUNTING.md +1 -1
- package/docs/GRID_PRICE_INVARIANT.md +518 -0
- package/docs/GRID_RECALCULATION.md +1 -1
- package/docs/GRID_RECONCILE.md +60 -39
- package/docs/ORDER_ENGINE_POST_1.0_RETROSPECTIVE.md +556 -0
- package/docs/README.md +3 -3
- package/docs/WORKFLOW.md +12 -6
- package/docs/architecture.md +40 -38
- package/docs/developer_guide.md +4 -8
- package/market_adapter/README.md +16 -12
- package/modules/README.md +1 -1
- package/package.json +7 -3
- package/scripts/README.md +0 -9
- package/scripts/reset-settings.sh +1 -1
- package/analysis/ama_fitting/package.json +0 -12
- package/analysis/analyze_derivatives.ts +0 -320
- package/analysis/derivative_chart_generator.ts +0 -874
- package/analysis/trend_detection/SIGNAL_DOCUMENTATION.md +0 -403
- package/analysis/trend_detection/derivative_analyzer.ts +0 -1059
- package/dist/analysis/analyze_derivatives.d.ts +0 -68
- package/dist/analysis/analyze_derivatives.d.ts.map +0 -1
- package/dist/analysis/analyze_derivatives.js +0 -287
- package/dist/analysis/analyze_derivatives.js.map +0 -1
- package/dist/analysis/derivative_chart_generator.d.ts +0 -12
- package/dist/analysis/derivative_chart_generator.d.ts.map +0 -1
- package/dist/analysis/derivative_chart_generator.js +0 -891
- package/dist/analysis/derivative_chart_generator.js.map +0 -1
- package/dist/analysis/trend_detection/derivative_analyzer.d.ts +0 -248
- package/dist/analysis/trend_detection/derivative_analyzer.d.ts.map +0 -1
- package/dist/analysis/trend_detection/derivative_analyzer.js +0 -910
- package/dist/analysis/trend_detection/derivative_analyzer.js.map +0 -1
|
@@ -1,910 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
import { roundTo } from '../../modules/order/utils/math.js';
|
|
3
|
-
/**
|
|
4
|
-
* Derivative Analyzer
|
|
5
|
-
*
|
|
6
|
-
* Trend detection from the sign (and magnitude) of the slow SMA derivative.
|
|
7
|
-
*
|
|
8
|
-
* The analyzer intentionally stays narrow:
|
|
9
|
-
* - SMA for direction / regime
|
|
10
|
-
* - MACD for momentum
|
|
11
|
-
* - RSI for exhaustion and counter-trend filtering
|
|
12
|
-
*/
|
|
13
|
-
// ─── Simple Moving Average ───────────────────────────────────────────────────
|
|
14
|
-
class SMA {
|
|
15
|
-
period;
|
|
16
|
-
values;
|
|
17
|
-
constructor(period) {
|
|
18
|
-
this.period = period;
|
|
19
|
-
this.values = [];
|
|
20
|
-
}
|
|
21
|
-
update(price) {
|
|
22
|
-
this.values.push(price);
|
|
23
|
-
if (this.values.length > this.period)
|
|
24
|
-
this.values.shift();
|
|
25
|
-
if (this.values.length < this.period)
|
|
26
|
-
return null;
|
|
27
|
-
return this.values.reduce((a, b) => a + b, 0) / this.period;
|
|
28
|
-
}
|
|
29
|
-
isReady() { return this.values.length >= this.period; }
|
|
30
|
-
reset(period) { this.period = period; this.values = []; }
|
|
31
|
-
}
|
|
32
|
-
// ─── Exponential Moving Average ──────────────────────────────────────────────
|
|
33
|
-
class EMA {
|
|
34
|
-
period;
|
|
35
|
-
multiplier;
|
|
36
|
-
value;
|
|
37
|
-
count;
|
|
38
|
-
_sum;
|
|
39
|
-
constructor(period) {
|
|
40
|
-
this.period = period;
|
|
41
|
-
this.multiplier = 2 / (period + 1);
|
|
42
|
-
this.value = null;
|
|
43
|
-
this.count = 0;
|
|
44
|
-
this._sum = 0;
|
|
45
|
-
}
|
|
46
|
-
update(price) {
|
|
47
|
-
this.count++;
|
|
48
|
-
if (this.count < this.period) {
|
|
49
|
-
this._sum += price;
|
|
50
|
-
return null;
|
|
51
|
-
}
|
|
52
|
-
if (this.count === this.period) {
|
|
53
|
-
this._sum += price;
|
|
54
|
-
this.value = this._sum / this.period;
|
|
55
|
-
return this.value;
|
|
56
|
-
}
|
|
57
|
-
this.value = price * this.multiplier + this.value * (1 - this.multiplier);
|
|
58
|
-
return this.value;
|
|
59
|
-
}
|
|
60
|
-
isReady() { return this.count >= this.period; }
|
|
61
|
-
reset(period) {
|
|
62
|
-
this.period = period ?? this.period;
|
|
63
|
-
this.multiplier = 2 / (this.period + 1);
|
|
64
|
-
this.value = null;
|
|
65
|
-
this.count = 0;
|
|
66
|
-
this._sum = 0;
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
// ─── MACD ─────────────────────────────────────────────────────────────────────
|
|
70
|
-
class MACD {
|
|
71
|
-
fastPeriod;
|
|
72
|
-
slowPeriod;
|
|
73
|
-
signalPeriod;
|
|
74
|
-
fastEma;
|
|
75
|
-
slowEma;
|
|
76
|
-
signalEma;
|
|
77
|
-
/**
|
|
78
|
-
* Moving Average Convergence Divergence
|
|
79
|
-
* macdLine = EMA(fast) - EMA(slow)
|
|
80
|
-
* signal = EMA(signalPeriod) of macdLine
|
|
81
|
-
* histogram = macdLine - signal
|
|
82
|
-
*/
|
|
83
|
-
constructor(fastPeriod = 12, slowPeriod = 26, signalPeriod = 9) {
|
|
84
|
-
this.fastPeriod = fastPeriod;
|
|
85
|
-
this.slowPeriod = slowPeriod;
|
|
86
|
-
this.signalPeriod = signalPeriod;
|
|
87
|
-
this.fastEma = new EMA(fastPeriod);
|
|
88
|
-
this.slowEma = new EMA(slowPeriod);
|
|
89
|
-
this.signalEma = new EMA(signalPeriod);
|
|
90
|
-
}
|
|
91
|
-
update(price) {
|
|
92
|
-
const fast = this.fastEma.update(price);
|
|
93
|
-
const slow = this.slowEma.update(price);
|
|
94
|
-
if (fast === null || slow === null)
|
|
95
|
-
return null;
|
|
96
|
-
const macdLine = fast - slow;
|
|
97
|
-
const signal = this.signalEma.update(macdLine);
|
|
98
|
-
if (signal === null)
|
|
99
|
-
return null;
|
|
100
|
-
return { macd: macdLine, signal, histogram: macdLine - signal };
|
|
101
|
-
}
|
|
102
|
-
isReady() { return this.slowEma.isReady() && this.signalEma.isReady(); }
|
|
103
|
-
reset(fastPeriod, slowPeriod, signalPeriod) {
|
|
104
|
-
this.fastPeriod = fastPeriod ?? this.fastPeriod;
|
|
105
|
-
this.slowPeriod = slowPeriod ?? this.slowPeriod;
|
|
106
|
-
this.signalPeriod = signalPeriod ?? this.signalPeriod;
|
|
107
|
-
this.fastEma.reset(this.fastPeriod);
|
|
108
|
-
this.slowEma.reset(this.slowPeriod);
|
|
109
|
-
this.signalEma.reset(this.signalPeriod);
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
// ─── RSI ──────────────────────────────────────────────────────────────────────
|
|
113
|
-
class RSI {
|
|
114
|
-
period;
|
|
115
|
-
count;
|
|
116
|
-
prevPrice;
|
|
117
|
-
avgGain;
|
|
118
|
-
avgLoss;
|
|
119
|
-
/**
|
|
120
|
-
* Relative Strength Index (Wilder smoothing)
|
|
121
|
-
* Overbought > 70, Oversold < 30
|
|
122
|
-
*/
|
|
123
|
-
constructor(period = 14) {
|
|
124
|
-
this.period = period;
|
|
125
|
-
this.count = 0;
|
|
126
|
-
this.prevPrice = null;
|
|
127
|
-
this.avgGain = 0;
|
|
128
|
-
this.avgLoss = 0;
|
|
129
|
-
}
|
|
130
|
-
update(price) {
|
|
131
|
-
if (this.prevPrice === null) {
|
|
132
|
-
this.prevPrice = price;
|
|
133
|
-
return null;
|
|
134
|
-
}
|
|
135
|
-
const change = price - this.prevPrice;
|
|
136
|
-
this.prevPrice = price;
|
|
137
|
-
const gain = change > 0 ? change : 0;
|
|
138
|
-
const loss = change < 0 ? -change : 0;
|
|
139
|
-
this.count++;
|
|
140
|
-
if (this.count < this.period) {
|
|
141
|
-
this.avgGain += gain;
|
|
142
|
-
this.avgLoss += loss;
|
|
143
|
-
return null;
|
|
144
|
-
}
|
|
145
|
-
if (this.count === this.period) {
|
|
146
|
-
this.avgGain = (this.avgGain + gain) / this.period;
|
|
147
|
-
this.avgLoss = (this.avgLoss + loss) / this.period;
|
|
148
|
-
}
|
|
149
|
-
else {
|
|
150
|
-
this.avgGain = (this.avgGain * (this.period - 1) + gain) / this.period;
|
|
151
|
-
this.avgLoss = (this.avgLoss * (this.period - 1) + loss) / this.period;
|
|
152
|
-
}
|
|
153
|
-
if (this.avgLoss === 0)
|
|
154
|
-
return 100;
|
|
155
|
-
return 100 - (100 / (1 + this.avgGain / this.avgLoss));
|
|
156
|
-
}
|
|
157
|
-
isReady() { return this.count >= this.period; }
|
|
158
|
-
reset(period) {
|
|
159
|
-
this.period = period ?? this.period;
|
|
160
|
-
this.count = 0;
|
|
161
|
-
this.prevPrice = null;
|
|
162
|
-
this.avgGain = 0;
|
|
163
|
-
this.avgLoss = 0;
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
class DerivativeAnalyzer {
|
|
167
|
-
minBarsForConfirmation;
|
|
168
|
-
slowSmaPeriod;
|
|
169
|
-
sma;
|
|
170
|
-
fastSmaPeriod;
|
|
171
|
-
fastSma;
|
|
172
|
-
macdConfig;
|
|
173
|
-
macd;
|
|
174
|
-
currMacd;
|
|
175
|
-
prevMacd;
|
|
176
|
-
rsiPeriod;
|
|
177
|
-
rsi;
|
|
178
|
-
currRsi;
|
|
179
|
-
prevRsi;
|
|
180
|
-
interpConfirmBars;
|
|
181
|
-
interpHoldBars;
|
|
182
|
-
rsiOverboughtLevel;
|
|
183
|
-
rsiOversoldLevel;
|
|
184
|
-
rsiBullThreshold;
|
|
185
|
-
rsiBearThreshold;
|
|
186
|
-
macdMinHist;
|
|
187
|
-
// Resolved threshold for the MACD-line gates: falls back to the historical
|
|
188
|
-
// 0.02 default only when unset/NaN, so an explicit 0 (strict sign test)
|
|
189
|
-
// stays expressible via the API.
|
|
190
|
-
macdLineGateThreshold;
|
|
191
|
-
fastSmaCommitmentBars;
|
|
192
|
-
trendFilterEnabled;
|
|
193
|
-
trendFilterMinBars;
|
|
194
|
-
momentumGateEnabled;
|
|
195
|
-
momentumGateMinBars;
|
|
196
|
-
momentumGateRsiZone;
|
|
197
|
-
barsInMacdDivergence;
|
|
198
|
-
barsInRsiDivergence;
|
|
199
|
-
priceRegimeGateEnabled;
|
|
200
|
-
priceRegimeMinDistancePct;
|
|
201
|
-
prevRawInterp;
|
|
202
|
-
barsInRawInterp;
|
|
203
|
-
currInterpretation;
|
|
204
|
-
currInterpretationRaw;
|
|
205
|
-
pendingInterp;
|
|
206
|
-
pendingInterpBars;
|
|
207
|
-
entryBias;
|
|
208
|
-
isBullWeakEntry;
|
|
209
|
-
isBullConfirmation;
|
|
210
|
-
isLateBullWithoutWeak;
|
|
211
|
-
isBearWeakEntry;
|
|
212
|
-
isBearConfirmation;
|
|
213
|
-
isLateBearWithoutWeak;
|
|
214
|
-
bullEntrySetupActive;
|
|
215
|
-
bullEntrySetupConfirmed;
|
|
216
|
-
bearEntrySetupActive;
|
|
217
|
-
bearEntrySetupConfirmed;
|
|
218
|
-
prevSma;
|
|
219
|
-
currSma;
|
|
220
|
-
prevFastSma;
|
|
221
|
-
currFastSma;
|
|
222
|
-
prevPrice;
|
|
223
|
-
currPrice;
|
|
224
|
-
prevRawSmaTrend;
|
|
225
|
-
barsInSmaTrend;
|
|
226
|
-
prevRawFastSmaTrend;
|
|
227
|
-
barsInFastSmaTrend;
|
|
228
|
-
barsAboveFastSma;
|
|
229
|
-
barsBelowFastSma;
|
|
230
|
-
updateCount;
|
|
231
|
-
constructor(config = {}) {
|
|
232
|
-
this.minBarsForConfirmation = config.minBarsForConfirmation || 3;
|
|
233
|
-
// SMA (slow)
|
|
234
|
-
this.slowSmaPeriod = config.slowSmaPeriod ?? 500;
|
|
235
|
-
this.sma = this.slowSmaPeriod ? new SMA(this.slowSmaPeriod) : null;
|
|
236
|
-
// SMA (fast)
|
|
237
|
-
this.fastSmaPeriod = config.fastSmaPeriod ?? 100;
|
|
238
|
-
this.fastSma = this.fastSmaPeriod ? new SMA(this.fastSmaPeriod) : null;
|
|
239
|
-
// MACD
|
|
240
|
-
this.macdConfig = {
|
|
241
|
-
fastPeriod: config.macdFastPeriod ?? 12,
|
|
242
|
-
slowPeriod: config.macdSlowPeriod ?? 26,
|
|
243
|
-
signalPeriod: config.macdSignalPeriod ?? 9,
|
|
244
|
-
};
|
|
245
|
-
this.macd = new MACD(this.macdConfig.fastPeriod, this.macdConfig.slowPeriod, this.macdConfig.signalPeriod);
|
|
246
|
-
this.currMacd = null;
|
|
247
|
-
this.prevMacd = null;
|
|
248
|
-
// RSI
|
|
249
|
-
this.rsiPeriod = config.rsiPeriod ?? 14;
|
|
250
|
-
this.rsi = new RSI(this.rsiPeriod);
|
|
251
|
-
this.currRsi = null;
|
|
252
|
-
this.prevRsi = null;
|
|
253
|
-
// Interpretation state
|
|
254
|
-
this.interpConfirmBars = config.interpConfirmBars ?? 3;
|
|
255
|
-
this.interpHoldBars = config.interpHoldBars ?? 0;
|
|
256
|
-
this.rsiOverboughtLevel = config.rsiOverboughtLevel ?? 70;
|
|
257
|
-
this.rsiOversoldLevel = config.rsiOversoldLevel ?? 30;
|
|
258
|
-
this.rsiBullThreshold = config.rsiBullThreshold ?? 55;
|
|
259
|
-
this.rsiBearThreshold = config.rsiBearThreshold ?? 45;
|
|
260
|
-
this.macdMinHist = config.macdMinHist ?? 0;
|
|
261
|
-
// Unset/NaN → historical 0.02 gate default; explicit 0 → strict sign test.
|
|
262
|
-
// (`this.macdMinHist || 0.02` at the gates made explicit 0 unreachable.)
|
|
263
|
-
this.macdLineGateThreshold = config.macdMinHist != null && Number.isFinite(config.macdMinHist)
|
|
264
|
-
? config.macdMinHist
|
|
265
|
-
: 0.02;
|
|
266
|
-
this.fastSmaCommitmentBars = config.fastSmaCommitmentBars ?? 2;
|
|
267
|
-
this.trendFilterEnabled = config.trendFilterEnabled ?? false;
|
|
268
|
-
this.trendFilterMinBars = config.trendFilterMinBars ?? 3;
|
|
269
|
-
this.momentumGateEnabled = config.momentumGateEnabled ?? false;
|
|
270
|
-
this.momentumGateMinBars = config.momentumGateMinBars ?? 3;
|
|
271
|
-
this.momentumGateRsiZone = config.momentumGateRsiZone ?? 35;
|
|
272
|
-
this.barsInMacdDivergence = 0;
|
|
273
|
-
this.barsInRsiDivergence = 0;
|
|
274
|
-
this.priceRegimeGateEnabled = config.priceRegimeGateEnabled ?? true;
|
|
275
|
-
this.priceRegimeMinDistancePct = config.priceRegimeMinDistancePct ?? 0.35;
|
|
276
|
-
this.prevRawInterp = null;
|
|
277
|
-
this.barsInRawInterp = 0;
|
|
278
|
-
this.currInterpretation = 'NEUTRAL';
|
|
279
|
-
this.currInterpretationRaw = 'NEUTRAL';
|
|
280
|
-
this.pendingInterp = null;
|
|
281
|
-
this.pendingInterpBars = 0;
|
|
282
|
-
this.entryBias = 'NONE';
|
|
283
|
-
this.isBullWeakEntry = false;
|
|
284
|
-
this.isBullConfirmation = false;
|
|
285
|
-
this.isLateBullWithoutWeak = false;
|
|
286
|
-
this.isBearWeakEntry = false;
|
|
287
|
-
this.isBearConfirmation = false;
|
|
288
|
-
this.isLateBearWithoutWeak = false;
|
|
289
|
-
this.bullEntrySetupActive = false;
|
|
290
|
-
this.bullEntrySetupConfirmed = false;
|
|
291
|
-
this.bearEntrySetupActive = false;
|
|
292
|
-
this.bearEntrySetupConfirmed = false;
|
|
293
|
-
// Previous / current SMA values (for derivative)
|
|
294
|
-
this.prevSma = null;
|
|
295
|
-
this.currSma = null;
|
|
296
|
-
this.prevFastSma = null;
|
|
297
|
-
this.currFastSma = null;
|
|
298
|
-
this.prevPrice = null;
|
|
299
|
-
this.currPrice = null;
|
|
300
|
-
// Independent trend states
|
|
301
|
-
this.prevRawSmaTrend = null;
|
|
302
|
-
this.barsInSmaTrend = 0;
|
|
303
|
-
this.prevRawFastSmaTrend = null;
|
|
304
|
-
this.barsInFastSmaTrend = 0;
|
|
305
|
-
this.barsAboveFastSma = 0;
|
|
306
|
-
this.barsBelowFastSma = 0;
|
|
307
|
-
this.updateCount = 0;
|
|
308
|
-
}
|
|
309
|
-
update(price, timestamp = null) {
|
|
310
|
-
if (!Number.isFinite(price) || price <= 0) {
|
|
311
|
-
throw new Error('price must be a positive finite number');
|
|
312
|
-
}
|
|
313
|
-
// Update current price at the start of the bar (before interpretation)
|
|
314
|
-
this.prevPrice = this.currPrice;
|
|
315
|
-
this.currPrice = price;
|
|
316
|
-
// SMA (slow)
|
|
317
|
-
if (this.sma) {
|
|
318
|
-
this.prevSma = this.currSma;
|
|
319
|
-
this.currSma = this.sma.update(price);
|
|
320
|
-
}
|
|
321
|
-
// SMA (fast)
|
|
322
|
-
if (this.fastSma) {
|
|
323
|
-
this.prevFastSma = this.currFastSma;
|
|
324
|
-
this.currFastSma = this.fastSma.update(price);
|
|
325
|
-
}
|
|
326
|
-
// MACD — normalized as % of price so scale is comparable across all price levels
|
|
327
|
-
if (this.macd) {
|
|
328
|
-
this.prevMacd = this.currMacd;
|
|
329
|
-
const raw = this.macd.update(price);
|
|
330
|
-
if (raw !== null && price > 0) {
|
|
331
|
-
this.currMacd = {
|
|
332
|
-
macd: (raw.macd / price) * 100,
|
|
333
|
-
signal: (raw.signal / price) * 100,
|
|
334
|
-
histogram: (raw.histogram / price) * 100,
|
|
335
|
-
};
|
|
336
|
-
}
|
|
337
|
-
else {
|
|
338
|
-
this.currMacd = null;
|
|
339
|
-
}
|
|
340
|
-
}
|
|
341
|
-
// RSI
|
|
342
|
-
if (this.rsi) {
|
|
343
|
-
this.prevRsi = this.currRsi;
|
|
344
|
-
this.currRsi = this.rsi.update(price);
|
|
345
|
-
}
|
|
346
|
-
// Advance momentum divergence counters (for Momentum Gate optimization)
|
|
347
|
-
this._advanceMomentumDivergence();
|
|
348
|
-
// Fast-SMA commitment tracking: price vs fast MA position
|
|
349
|
-
this._advancePriceFastSmaPosition();
|
|
350
|
-
// Interpretation (computed after MACD + RSI are updated)
|
|
351
|
-
if (this.macd || this.rsi) {
|
|
352
|
-
const prevInterpretation = this.currInterpretation;
|
|
353
|
-
this._resetEntryBias();
|
|
354
|
-
let rawInterp = this.trendFilterEnabled
|
|
355
|
-
? this._applyTrendFilter(this._computeInterpretation())
|
|
356
|
-
: this._computeInterpretation();
|
|
357
|
-
// Opt 13 — Post-filter MACD line gate (bull/bear trap suppression)
|
|
358
|
-
// The MACD line is the authoritative momentum regime indicator:
|
|
359
|
-
// line > threshold → bullish regime; line < -threshold → bearish regime.
|
|
360
|
-
// Any signal that contradicts the confirmed regime is suppressed to NEUTRAL.
|
|
361
|
-
// This ignores marginal crossings at ~0 and ensures signals only start when
|
|
362
|
-
// momentum is clearly confirmed.
|
|
363
|
-
if (this.macd && this.currMacd !== null) {
|
|
364
|
-
const threshold = this.macdLineGateThreshold;
|
|
365
|
-
if ((rawInterp === 'BEAR' || rawInterp === 'BEAR_WEAK') && this.currMacd.macd > -threshold)
|
|
366
|
-
rawInterp = 'NEUTRAL';
|
|
367
|
-
if ((rawInterp === 'BULL' || rawInterp === 'BULL_WEAK') && this.currMacd.macd < threshold)
|
|
368
|
-
rawInterp = 'NEUTRAL';
|
|
369
|
-
}
|
|
370
|
-
this.currInterpretationRaw = rawInterp;
|
|
371
|
-
// BULL/BEAR require confirmation bars; other states are immediate
|
|
372
|
-
if (rawInterp === 'BULL' || rawInterp === 'BEAR') {
|
|
373
|
-
// Check if fast-SMA commitment is met (required before counting confirmation)
|
|
374
|
-
let fastSmaPassesCommitment = true;
|
|
375
|
-
if (this.fastSma && this.currFastSma !== null && this.currPrice !== null) {
|
|
376
|
-
if (rawInterp === 'BULL') {
|
|
377
|
-
fastSmaPassesCommitment = this.barsAboveFastSma >= this.fastSmaCommitmentBars;
|
|
378
|
-
}
|
|
379
|
-
else if (rawInterp === 'BEAR') {
|
|
380
|
-
fastSmaPassesCommitment = this.barsBelowFastSma >= this.fastSmaCommitmentBars;
|
|
381
|
-
}
|
|
382
|
-
}
|
|
383
|
-
// Only count confirmation bars if the fast-SMA commitment passes
|
|
384
|
-
if (fastSmaPassesCommitment) {
|
|
385
|
-
if (rawInterp === this.prevRawInterp) {
|
|
386
|
-
this.barsInRawInterp++;
|
|
387
|
-
}
|
|
388
|
-
else {
|
|
389
|
-
this.prevRawInterp = rawInterp;
|
|
390
|
-
this.barsInRawInterp = 1;
|
|
391
|
-
}
|
|
392
|
-
}
|
|
393
|
-
else {
|
|
394
|
-
// Commitment not met — reset confirmation counter
|
|
395
|
-
this.prevRawInterp = rawInterp === 'BULL' ? 'BEAR' : 'BULL';
|
|
396
|
-
this.barsInRawInterp = 0;
|
|
397
|
-
}
|
|
398
|
-
const confirmed = this.barsInRawInterp >= this.interpConfirmBars
|
|
399
|
-
? rawInterp : 'NEUTRAL';
|
|
400
|
-
this._applyWithHysteresis(confirmed);
|
|
401
|
-
}
|
|
402
|
-
else {
|
|
403
|
-
this.prevRawInterp = rawInterp;
|
|
404
|
-
this.barsInRawInterp = 0;
|
|
405
|
-
this._applyWithHysteresis(rawInterp);
|
|
406
|
-
}
|
|
407
|
-
this._advanceEntryBias(prevInterpretation);
|
|
408
|
-
}
|
|
409
|
-
this.updateCount++;
|
|
410
|
-
// Update trend states
|
|
411
|
-
const smaRaw = this._rawSmaTrend();
|
|
412
|
-
const fastSmaRaw = this._rawFastSmaTrend();
|
|
413
|
-
this._advanceTrend(smaRaw, 'Sma');
|
|
414
|
-
this._advanceTrend(fastSmaRaw, 'FastSma');
|
|
415
|
-
const result = this.getAnalysis();
|
|
416
|
-
if (timestamp !== null)
|
|
417
|
-
result.timestamp = timestamp;
|
|
418
|
-
return result;
|
|
419
|
-
}
|
|
420
|
-
_advanceTrend(raw, key) {
|
|
421
|
-
const prevKey = `prevRaw${key}Trend`;
|
|
422
|
-
const barsKey = `barsIn${key}Trend`;
|
|
423
|
-
if (raw !== this[prevKey]) {
|
|
424
|
-
this[prevKey] = raw;
|
|
425
|
-
this[barsKey] = 1;
|
|
426
|
-
}
|
|
427
|
-
else {
|
|
428
|
-
this[barsKey]++;
|
|
429
|
-
}
|
|
430
|
-
}
|
|
431
|
-
// ── Derivative signs ───────────────────────────────────────
|
|
432
|
-
_rawSmaTrend() {
|
|
433
|
-
if (!this.sma || this.prevSma === null || this.currSma === null)
|
|
434
|
-
return 'NEUTRAL';
|
|
435
|
-
if (this.currSma > this.prevSma)
|
|
436
|
-
return 'UP';
|
|
437
|
-
if (this.currSma < this.prevSma)
|
|
438
|
-
return 'DOWN';
|
|
439
|
-
return 'NEUTRAL';
|
|
440
|
-
}
|
|
441
|
-
_rawFastSmaTrend() {
|
|
442
|
-
if (!this.fastSma || this.prevFastSma === null || this.currFastSma === null)
|
|
443
|
-
return 'NEUTRAL';
|
|
444
|
-
if (this.currFastSma > this.prevFastSma)
|
|
445
|
-
return 'UP';
|
|
446
|
-
if (this.currFastSma < this.prevFastSma)
|
|
447
|
-
return 'DOWN';
|
|
448
|
-
return 'NEUTRAL';
|
|
449
|
-
}
|
|
450
|
-
_advancePriceFastSmaPosition() {
|
|
451
|
-
if (!this.fastSma || this.currFastSma === null || this.currPrice === null) {
|
|
452
|
-
this.barsAboveFastSma = 0;
|
|
453
|
-
this.barsBelowFastSma = 0;
|
|
454
|
-
return;
|
|
455
|
-
}
|
|
456
|
-
if (this.currPrice > this.currFastSma) {
|
|
457
|
-
this.barsAboveFastSma++;
|
|
458
|
-
this.barsBelowFastSma = 0;
|
|
459
|
-
}
|
|
460
|
-
else {
|
|
461
|
-
this.barsBelowFastSma++;
|
|
462
|
-
this.barsAboveFastSma = 0;
|
|
463
|
-
}
|
|
464
|
-
}
|
|
465
|
-
_advanceMomentumDivergence() {
|
|
466
|
-
if (!this.momentumGateEnabled || !this.macd || !this.rsi || !this.sma)
|
|
467
|
-
return;
|
|
468
|
-
if (this.currMacd === null || this.currRsi === null)
|
|
469
|
-
return;
|
|
470
|
-
const slowDir = this._rawSmaTrend();
|
|
471
|
-
if (slowDir === 'NEUTRAL') {
|
|
472
|
-
this.barsInMacdDivergence = 0;
|
|
473
|
-
this.barsInRsiDivergence = 0;
|
|
474
|
-
return;
|
|
475
|
-
}
|
|
476
|
-
const macdOpposes = (slowDir === 'UP' && this.currMacd.histogram < -this.macdMinHist) ||
|
|
477
|
-
(slowDir === 'DOWN' && this.currMacd.histogram > this.macdMinHist);
|
|
478
|
-
const rsiOpposes = (slowDir === 'UP' && this.currRsi < this.momentumGateRsiZone) ||
|
|
479
|
-
(slowDir === 'DOWN' && this.currRsi > (100 - this.momentumGateRsiZone));
|
|
480
|
-
this.barsInMacdDivergence = macdOpposes ? this.barsInMacdDivergence + 1 : 0;
|
|
481
|
-
this.barsInRsiDivergence = rsiOpposes ? this.barsInRsiDivergence + 1 : 0;
|
|
482
|
-
}
|
|
483
|
-
// ── Trend filter ─────────────────────────────────────────
|
|
484
|
-
_applyTrendFilter(interp) {
|
|
485
|
-
// OB/OS are exit signals — not suppressed by trend direction
|
|
486
|
-
if (interp === 'OVERBOUGHT' || interp === 'OVERSOLD')
|
|
487
|
-
return interp;
|
|
488
|
-
// Compute fast MA direction and sustained bars count
|
|
489
|
-
let fastDir, fastBarsInDir;
|
|
490
|
-
if (this.fastSma) {
|
|
491
|
-
fastDir = this._rawFastSmaTrend();
|
|
492
|
-
fastBarsInDir = (fastDir === this.prevRawFastSmaTrend ? this.barsInFastSmaTrend + 1 : 1);
|
|
493
|
-
}
|
|
494
|
-
else if (this.sma) {
|
|
495
|
-
fastDir = this._rawSmaTrend();
|
|
496
|
-
fastBarsInDir = (fastDir === this.prevRawSmaTrend ? this.barsInSmaTrend + 1 : 1);
|
|
497
|
-
}
|
|
498
|
-
else {
|
|
499
|
-
return interp;
|
|
500
|
-
}
|
|
501
|
-
if (fastDir === 'NEUTRAL')
|
|
502
|
-
return interp;
|
|
503
|
-
// Primary gate: fast MA sustained in opposite direction → suppress to NEUTRAL
|
|
504
|
-
let suppressedByTrendFilter = false;
|
|
505
|
-
if (fastBarsInDir >= this.trendFilterMinBars) {
|
|
506
|
-
if (fastDir === 'UP' && (interp === 'BEAR' || interp === 'BEAR_WEAK')) {
|
|
507
|
-
interp = 'NEUTRAL';
|
|
508
|
-
suppressedByTrendFilter = true;
|
|
509
|
-
}
|
|
510
|
-
if (fastDir === 'DOWN' && (interp === 'BULL' || interp === 'BULL_WEAK')) {
|
|
511
|
-
interp = 'NEUTRAL';
|
|
512
|
-
suppressedByTrendFilter = true;
|
|
513
|
-
}
|
|
514
|
-
}
|
|
515
|
-
// Momentum Gate (Opt 11): if MACD + RSI both diverge from SMA(500) for
|
|
516
|
-
// >= momentumGateMinBars, allow signal through as WEAK instead of suppressing to NEUTRAL.
|
|
517
|
-
if (suppressedByTrendFilter && this.momentumGateEnabled && this.sma) {
|
|
518
|
-
const slowDir = this._rawSmaTrend();
|
|
519
|
-
const macdConf = this.barsInMacdDivergence >= this.momentumGateMinBars;
|
|
520
|
-
const rsiConf = this.barsInRsiDivergence >= this.momentumGateMinBars;
|
|
521
|
-
if (macdConf && rsiConf && slowDir !== 'NEUTRAL') {
|
|
522
|
-
if (slowDir === 'UP' && (this.currMacd?.histogram ?? 0) < -this.macdMinHist) {
|
|
523
|
-
interp = 'BEAR_WEAK';
|
|
524
|
-
}
|
|
525
|
-
if (slowDir === 'DOWN' && (this.currMacd?.histogram ?? 0) > this.macdMinHist) {
|
|
526
|
-
interp = 'BULL_WEAK';
|
|
527
|
-
}
|
|
528
|
-
}
|
|
529
|
-
}
|
|
530
|
-
// Macro regime gate: when fastSMA and SMA(slow) are both present and disagree,
|
|
531
|
-
// cap full BULL/BEAR to WEAK — a short-term dip/bounce against the macro trend
|
|
532
|
-
// does not warrant a confirmed directional signal.
|
|
533
|
-
if (this._violatesMacroDisagreeGate(interp, fastDir)) {
|
|
534
|
-
if (interp === 'BULL')
|
|
535
|
-
return 'BULL_WEAK';
|
|
536
|
-
if (interp === 'BEAR')
|
|
537
|
-
return 'BEAR_WEAK';
|
|
538
|
-
}
|
|
539
|
-
// Fast-SMA commitment gate — price vs fast MA cross-check (N-bar commitment required)
|
|
540
|
-
if (this.fastSma && this.currFastSma !== null && this.currPrice !== null) {
|
|
541
|
-
if (interp === 'BULL' && this.barsAboveFastSma < this.fastSmaCommitmentBars)
|
|
542
|
-
return 'BULL_WEAK';
|
|
543
|
-
if (interp === 'BEAR' && this.barsBelowFastSma < this.fastSmaCommitmentBars)
|
|
544
|
-
return 'BEAR_WEAK';
|
|
545
|
-
}
|
|
546
|
-
// Opt 12 — MACD Line & Histogram regime check
|
|
547
|
-
if (this.macd && this.currMacd !== null) {
|
|
548
|
-
if (interp === 'BULL' && this.currMacd.macd <= 0)
|
|
549
|
-
return 'BULL_WEAK';
|
|
550
|
-
if (interp === 'BEAR' && this.currMacd.macd >= 0)
|
|
551
|
-
return 'BEAR_WEAK';
|
|
552
|
-
if (interp === 'BEAR' && this.currMacd.histogram > 0)
|
|
553
|
-
return 'BEAR_WEAK';
|
|
554
|
-
}
|
|
555
|
-
// Macro price regime gate: require a minimum clearance beyond slow SMA.
|
|
556
|
-
if (this._violatesPriceRegimeGate(interp))
|
|
557
|
-
return 'NEUTRAL';
|
|
558
|
-
return interp;
|
|
559
|
-
}
|
|
560
|
-
// ── Hysteresis ────────────────────────────────────────────
|
|
561
|
-
_applyWithHysteresis(newInterp) {
|
|
562
|
-
// OB/OS are exit signals — always immediate, bypass hold
|
|
563
|
-
if (newInterp === 'OVERBOUGHT' || newInterp === 'OVERSOLD') {
|
|
564
|
-
this.currInterpretation = newInterp;
|
|
565
|
-
this.pendingInterp = null;
|
|
566
|
-
this.pendingInterpBars = 0;
|
|
567
|
-
return;
|
|
568
|
-
}
|
|
569
|
-
const curr = this.currInterpretation;
|
|
570
|
-
const isDowngrade = (curr === 'BULL' && newInterp !== 'BULL')
|
|
571
|
-
|| (curr === 'BEAR' && newInterp !== 'BEAR');
|
|
572
|
-
// Hard regime gates define when a confirmed signal is no longer valid at all.
|
|
573
|
-
// In those cases hysteresis must not keep a stale BULL/BEAR alive after invalidation.
|
|
574
|
-
const hardInvalidation = this._isHardInvalidation(curr);
|
|
575
|
-
if (!isDowngrade || this.interpHoldBars <= 0 || hardInvalidation) {
|
|
576
|
-
this.currInterpretation = newInterp;
|
|
577
|
-
this.pendingInterp = null;
|
|
578
|
-
this.pendingInterpBars = 0;
|
|
579
|
-
return;
|
|
580
|
-
}
|
|
581
|
-
// Debounce the downgrade — must hold for interpHoldBars bars
|
|
582
|
-
if (newInterp === this.pendingInterp) {
|
|
583
|
-
this.pendingInterpBars++;
|
|
584
|
-
if (this.pendingInterpBars >= this.interpHoldBars) {
|
|
585
|
-
this.currInterpretation = newInterp;
|
|
586
|
-
this.pendingInterp = null;
|
|
587
|
-
this.pendingInterpBars = 0;
|
|
588
|
-
}
|
|
589
|
-
}
|
|
590
|
-
else {
|
|
591
|
-
this.pendingInterp = newInterp;
|
|
592
|
-
this.pendingInterpBars = 1;
|
|
593
|
-
}
|
|
594
|
-
}
|
|
595
|
-
_resetEntryBias() {
|
|
596
|
-
this.entryBias = 'NONE';
|
|
597
|
-
this.isBullWeakEntry = false;
|
|
598
|
-
this.isBullConfirmation = false;
|
|
599
|
-
this.isLateBullWithoutWeak = false;
|
|
600
|
-
this.isBearWeakEntry = false;
|
|
601
|
-
this.isBearConfirmation = false;
|
|
602
|
-
this.isLateBearWithoutWeak = false;
|
|
603
|
-
}
|
|
604
|
-
_advanceEntryBias(prevInterpretation) {
|
|
605
|
-
const curr = this.currInterpretation;
|
|
606
|
-
const slowDir = this._rawSmaTrend();
|
|
607
|
-
const fastDir = this.fastSma ? this._rawFastSmaTrend() : slowDir;
|
|
608
|
-
const prevBullish = prevInterpretation === 'BULL' || prevInterpretation === 'BULL_WEAK';
|
|
609
|
-
const currBullish = curr === 'BULL' || curr === 'BULL_WEAK';
|
|
610
|
-
const prevBearish = prevInterpretation === 'BEAR' || prevInterpretation === 'BEAR_WEAK';
|
|
611
|
-
const currBearish = curr === 'BEAR' || curr === 'BEAR_WEAK';
|
|
612
|
-
const bullMacroAligned = slowDir === 'UP';
|
|
613
|
-
const bearMacroAligned = slowDir === 'DOWN';
|
|
614
|
-
const bullEntryAligned = bullMacroAligned && fastDir === 'UP';
|
|
615
|
-
const bearEntryAligned = bearMacroAligned && fastDir === 'DOWN';
|
|
616
|
-
if (!currBullish) {
|
|
617
|
-
this.bullEntrySetupActive = false;
|
|
618
|
-
this.bullEntrySetupConfirmed = false;
|
|
619
|
-
}
|
|
620
|
-
// Keep weak bullish states visible on the chart, but do not emit long
|
|
621
|
-
// entry events until both slow and fast SMA trends are aligned up.
|
|
622
|
-
if (!bullEntryAligned) {
|
|
623
|
-
this.bullEntrySetupActive = false;
|
|
624
|
-
this.bullEntrySetupConfirmed = false;
|
|
625
|
-
}
|
|
626
|
-
else if (curr === 'BULL_WEAK' && (!prevBullish || !this.bullEntrySetupActive)) {
|
|
627
|
-
this.bullEntrySetupActive = true;
|
|
628
|
-
this.bullEntrySetupConfirmed = false;
|
|
629
|
-
this.entryBias = 'EARLY_LONG';
|
|
630
|
-
this.isBullWeakEntry = true;
|
|
631
|
-
return;
|
|
632
|
-
}
|
|
633
|
-
if (curr === 'BULL' && prevInterpretation !== 'BULL') {
|
|
634
|
-
if (this.bullEntrySetupActive && !this.bullEntrySetupConfirmed) {
|
|
635
|
-
this.bullEntrySetupConfirmed = true;
|
|
636
|
-
this.entryBias = 'CONFIRM_LONG';
|
|
637
|
-
this.isBullConfirmation = true;
|
|
638
|
-
}
|
|
639
|
-
}
|
|
640
|
-
if (!currBearish) {
|
|
641
|
-
this.bearEntrySetupActive = false;
|
|
642
|
-
this.bearEntrySetupConfirmed = false;
|
|
643
|
-
}
|
|
644
|
-
// Keep weak bearish states visible on the chart, but do not emit short
|
|
645
|
-
// entry events until both slow and fast SMA trends are aligned down.
|
|
646
|
-
if (!bearEntryAligned) {
|
|
647
|
-
this.bearEntrySetupActive = false;
|
|
648
|
-
this.bearEntrySetupConfirmed = false;
|
|
649
|
-
return;
|
|
650
|
-
}
|
|
651
|
-
if (curr === 'BEAR_WEAK' && (!prevBearish || !this.bearEntrySetupActive)) {
|
|
652
|
-
this.bearEntrySetupActive = true;
|
|
653
|
-
this.bearEntrySetupConfirmed = false;
|
|
654
|
-
this.entryBias = 'EARLY_SHORT';
|
|
655
|
-
this.isBearWeakEntry = true;
|
|
656
|
-
return;
|
|
657
|
-
}
|
|
658
|
-
if (curr === 'BEAR' && prevInterpretation !== 'BEAR') {
|
|
659
|
-
if (this.bearEntrySetupActive && !this.bearEntrySetupConfirmed) {
|
|
660
|
-
this.bearEntrySetupConfirmed = true;
|
|
661
|
-
this.entryBias = 'CONFIRM_SHORT';
|
|
662
|
-
this.isBearConfirmation = true;
|
|
663
|
-
}
|
|
664
|
-
}
|
|
665
|
-
}
|
|
666
|
-
_isHardInvalidation(interp) {
|
|
667
|
-
if (interp !== 'BULL' && interp !== 'BEAR')
|
|
668
|
-
return false;
|
|
669
|
-
if (this.macd && this.currMacd !== null) {
|
|
670
|
-
const threshold = this.macdLineGateThreshold;
|
|
671
|
-
if (interp === 'BULL' && this.currMacd.macd < threshold)
|
|
672
|
-
return true;
|
|
673
|
-
if (interp === 'BEAR' && this.currMacd.macd > -threshold)
|
|
674
|
-
return true;
|
|
675
|
-
}
|
|
676
|
-
if (this._violatesMacroDisagreeGate(interp))
|
|
677
|
-
return true;
|
|
678
|
-
if (this._violatesPriceRegimeGate(interp))
|
|
679
|
-
return true;
|
|
680
|
-
return false;
|
|
681
|
-
}
|
|
682
|
-
_violatesMacroDisagreeGate(interp, fastDirOverride = null) {
|
|
683
|
-
if (!this.trendFilterEnabled || !this.fastSma || !this.sma)
|
|
684
|
-
return false;
|
|
685
|
-
if (interp !== 'BULL' && interp !== 'BEAR')
|
|
686
|
-
return false;
|
|
687
|
-
const fastDir = fastDirOverride ?? this._rawFastSmaTrend();
|
|
688
|
-
const slowDir = this._rawSmaTrend();
|
|
689
|
-
const slowBarsInDir = (slowDir === this.prevRawSmaTrend ? this.barsInSmaTrend + 1 : 1);
|
|
690
|
-
if (fastDir === 'NEUTRAL' || slowDir === 'NEUTRAL')
|
|
691
|
-
return false;
|
|
692
|
-
if (slowBarsInDir < this.trendFilterMinBars)
|
|
693
|
-
return false;
|
|
694
|
-
if (fastDir === slowDir)
|
|
695
|
-
return false;
|
|
696
|
-
return fastDir !== slowDir;
|
|
697
|
-
}
|
|
698
|
-
_violatesPriceRegimeGate(interp) {
|
|
699
|
-
if (!this.priceRegimeGateEnabled || !this.sma || this.currSma === null || this.currPrice === null) {
|
|
700
|
-
return false;
|
|
701
|
-
}
|
|
702
|
-
if (!['BULL', 'BULL_WEAK', 'BEAR', 'BEAR_WEAK'].includes(interp))
|
|
703
|
-
return false;
|
|
704
|
-
const distanceMultiplier = this.priceRegimeMinDistancePct / 100;
|
|
705
|
-
const bullFloor = this.currSma * (1 + distanceMultiplier);
|
|
706
|
-
const bearCeiling = this.currSma * (1 - distanceMultiplier);
|
|
707
|
-
if ((interp === 'BULL' || interp === 'BULL_WEAK') && this.currPrice < bullFloor)
|
|
708
|
-
return true;
|
|
709
|
-
if ((interp === 'BEAR' || interp === 'BEAR_WEAK') && this.currPrice > bearCeiling)
|
|
710
|
-
return true;
|
|
711
|
-
return false;
|
|
712
|
-
}
|
|
713
|
-
// ── Interpretation ────────────────────────────────────────
|
|
714
|
-
_computeInterpretation() {
|
|
715
|
-
const hist = this.currMacd?.histogram ?? null;
|
|
716
|
-
const macdLine = this.currMacd?.macd ?? null;
|
|
717
|
-
const prevHist = this.prevMacd?.histogram ?? null;
|
|
718
|
-
const prevMacdLine = this.prevMacd?.macd ?? null;
|
|
719
|
-
const rsi = this.currRsi;
|
|
720
|
-
if (hist === null && rsi === null)
|
|
721
|
-
return 'NEUTRAL';
|
|
722
|
-
// 1. MACD histogram sign with minimum magnitude filter
|
|
723
|
-
const histBull = hist !== null && hist > this.macdMinHist;
|
|
724
|
-
const histBear = hist !== null && hist < -this.macdMinHist;
|
|
725
|
-
// 2. MACD histogram momentum
|
|
726
|
-
const histMom = (hist !== null && prevHist !== null) ? hist - prevHist : null;
|
|
727
|
-
const histWeakeningBull = histBull && histMom !== null && histMom < 0;
|
|
728
|
-
const histWeakeningBear = histBear && histMom !== null && histMom > 0;
|
|
729
|
-
// 3. MACD zero-line position
|
|
730
|
-
const macdAboveZero = macdLine !== null && macdLine > 0;
|
|
731
|
-
const macdBelowZero = macdLine !== null && macdLine < 0;
|
|
732
|
-
// 5. MACD line slope — is the MACD line itself rising or falling?
|
|
733
|
-
const macdLineSlope = (macdLine !== null && prevMacdLine !== null) ? macdLine - prevMacdLine : null;
|
|
734
|
-
const macdLineRising = macdLineSlope !== null && macdLineSlope > 0;
|
|
735
|
-
const macdLineFalling = macdLineSlope !== null && macdLineSlope < 0;
|
|
736
|
-
// 6. RSI extremes (highest priority — exit signals)
|
|
737
|
-
const rsiOverbought = rsi !== null && rsi > this.rsiOverboughtLevel;
|
|
738
|
-
const rsiOversold = rsi !== null && rsi < this.rsiOversoldLevel;
|
|
739
|
-
// 7. RSI direction — rising or falling?
|
|
740
|
-
const rsiSlope = (rsi !== null && this.prevRsi !== null) ? rsi - this.prevRsi : null;
|
|
741
|
-
const rsiRising = rsiSlope !== null && rsiSlope > 0;
|
|
742
|
-
const rsiFalling = rsiSlope !== null && rsiSlope < 0;
|
|
743
|
-
// 8. RSI counter-trend: in opposing zone AND not recovering (direction confirms divergence)
|
|
744
|
-
const rsiCounterBull = rsi !== null && rsi < this.rsiBearThreshold && !rsiRising;
|
|
745
|
-
const rsiCounterBear = rsi !== null && rsi > this.rsiBullThreshold && !rsiFalling;
|
|
746
|
-
// Base signal driven by MACD histogram (RSI no longer hard-gates)
|
|
747
|
-
let raw = 'NEUTRAL';
|
|
748
|
-
if (histBull)
|
|
749
|
-
raw = 'BULL';
|
|
750
|
-
if (histBear)
|
|
751
|
-
raw = 'BEAR';
|
|
752
|
-
// RSI extreme exit — override immediately
|
|
753
|
-
if (raw === 'BULL' && rsiOverbought)
|
|
754
|
-
return 'OVERBOUGHT';
|
|
755
|
-
if (raw === 'BEAR' && rsiOversold)
|
|
756
|
-
return 'OVERSOLD';
|
|
757
|
-
// Weak conditions:
|
|
758
|
-
// histFading: histogram shrinking AND MACD line not supporting (any zero position)
|
|
759
|
-
// dualWeak: histogram positive but MACD line still on wrong side of zero AND not recovering
|
|
760
|
-
// rsiCounter: RSI in counter-trend territory AND direction confirms (standalone)
|
|
761
|
-
//
|
|
762
|
-
// Note: histogram = MACD line − Signal line, so macdBelowSignal is always false when
|
|
763
|
-
// histBull is true. Signal line crossover is not a valid structural check here.
|
|
764
|
-
const histFadingBull = histWeakeningBull && !macdLineRising;
|
|
765
|
-
const histFadingBear = histWeakeningBear && !macdLineFalling;
|
|
766
|
-
const dualWeakBull = histBull && macdBelowZero && !macdLineRising;
|
|
767
|
-
const dualWeakBear = histBear && macdAboveZero && !macdLineFalling;
|
|
768
|
-
const bullWeak = histFadingBull || dualWeakBull || rsiCounterBull;
|
|
769
|
-
const bearWeak = histFadingBear || dualWeakBear || rsiCounterBear;
|
|
770
|
-
if (raw === 'BULL' && bullWeak)
|
|
771
|
-
return 'BULL_WEAK';
|
|
772
|
-
if (raw === 'BEAR' && bearWeak)
|
|
773
|
-
return 'BEAR_WEAK';
|
|
774
|
-
return raw;
|
|
775
|
-
}
|
|
776
|
-
// ── Analysis ───────────────────────────────────────────────
|
|
777
|
-
getAnalysis() {
|
|
778
|
-
const warmupNeeded = Math.max(this.sma ? this.slowSmaPeriod : 0, this.fastSma ? this.fastSmaPeriod : 0, this.macd ? this.macdConfig.slowPeriod + this.macdConfig.signalPeriod : 0, this.rsi ? this.rsiPeriod + 1 : 0);
|
|
779
|
-
if (this.updateCount < warmupNeeded) {
|
|
780
|
-
return {
|
|
781
|
-
isReady: false,
|
|
782
|
-
reason: `Warming up: ${this.updateCount}/${warmupNeeded} candles`,
|
|
783
|
-
trend: 'NEUTRAL', confidence: 0, isConfirmed: false,
|
|
784
|
-
...this._emptySignals(),
|
|
785
|
-
price: this.currPrice, updateCount: this.updateCount,
|
|
786
|
-
};
|
|
787
|
-
}
|
|
788
|
-
const smaRaw = this._rawSmaTrend();
|
|
789
|
-
const fastSmaRaw = this._rawFastSmaTrend();
|
|
790
|
-
const smaConf = this._conf(smaRaw, this.barsInSmaTrend);
|
|
791
|
-
const fastSmaConf = this._conf(fastSmaRaw, this.barsInFastSmaTrend);
|
|
792
|
-
const smaConfirmed = smaRaw !== 'NEUTRAL' && this.barsInSmaTrend >= this.minBarsForConfirmation;
|
|
793
|
-
const fastSmaConfirmed = fastSmaRaw !== 'NEUTRAL' && this.barsInFastSmaTrend >= this.minBarsForConfirmation;
|
|
794
|
-
// Primary: fastSMA > SMA
|
|
795
|
-
const primary = this.fastSma ? fastSmaRaw : smaRaw;
|
|
796
|
-
const primConf = this.fastSma ? fastSmaConf : smaConf;
|
|
797
|
-
const primConfirm = this.fastSma ? fastSmaConfirmed : smaConfirmed;
|
|
798
|
-
return {
|
|
799
|
-
isReady: true,
|
|
800
|
-
trend: primConfirm ? primary : 'NEUTRAL',
|
|
801
|
-
confidence: primConf,
|
|
802
|
-
isConfirmed: primConfirm,
|
|
803
|
-
rawTrend: primary,
|
|
804
|
-
barsInTrend: this.fastSma ? this.barsInFastSmaTrend : this.barsInSmaTrend,
|
|
805
|
-
// SMA (slow)
|
|
806
|
-
smaRawTrend: smaRaw,
|
|
807
|
-
smaTrend: smaConfirmed ? smaRaw : 'NEUTRAL',
|
|
808
|
-
smaBarsInTrend: this.barsInSmaTrend,
|
|
809
|
-
smaConfidence: smaConf,
|
|
810
|
-
// SMA (fast)
|
|
811
|
-
fastSmaRawTrend: fastSmaRaw,
|
|
812
|
-
fastSmaTrend: fastSmaConfirmed ? fastSmaRaw : 'NEUTRAL',
|
|
813
|
-
fastSmaBarsInTrend: this.barsInFastSmaTrend,
|
|
814
|
-
fastSmaConfidence: fastSmaConf,
|
|
815
|
-
fastSmaValue: this.currFastSma !== null ? roundTo(this.currFastSma, 1e6) : null,
|
|
816
|
-
// MACD
|
|
817
|
-
macdLine: this.currMacd ? roundTo(this.currMacd.macd, 1e8) : null,
|
|
818
|
-
macdSignal: this.currMacd ? roundTo(this.currMacd.signal, 1e8) : null,
|
|
819
|
-
macdHistogram: this.currMacd ? roundTo(this.currMacd.histogram, 1e8) : null,
|
|
820
|
-
macdTrend: this.currMacd
|
|
821
|
-
? (this.currMacd.histogram > 0 ? 'BULL' : this.currMacd.histogram < 0 ? 'BEAR' : 'NEUTRAL')
|
|
822
|
-
: null,
|
|
823
|
-
// RSI
|
|
824
|
-
rsi: this.currRsi !== null ? roundTo(this.currRsi, 100) : null,
|
|
825
|
-
rsiZone: this.currRsi !== null
|
|
826
|
-
? (this.currRsi > this.rsiOverboughtLevel ? 'OVERBOUGHT' : this.currRsi < this.rsiOversoldLevel ? 'OVERSOLD' : 'NEUTRAL')
|
|
827
|
-
: null,
|
|
828
|
-
// Combined interpretation
|
|
829
|
-
interpretation: this.currInterpretation,
|
|
830
|
-
interpretationRaw: this.currInterpretationRaw,
|
|
831
|
-
interpretationBars: this.barsInRawInterp,
|
|
832
|
-
entryBias: this.entryBias,
|
|
833
|
-
isBullWeakEntry: this.isBullWeakEntry,
|
|
834
|
-
isBullConfirmation: this.isBullConfirmation,
|
|
835
|
-
isLateBullWithoutWeak: this.isLateBullWithoutWeak,
|
|
836
|
-
isBearWeakEntry: this.isBearWeakEntry,
|
|
837
|
-
isBearConfirmation: this.isBearConfirmation,
|
|
838
|
-
isLateBearWithoutWeak: this.isLateBearWithoutWeak,
|
|
839
|
-
// Values
|
|
840
|
-
price: this.currPrice,
|
|
841
|
-
slowSma: this.currSma !== null ? roundTo(this.currSma, 1e6) : null,
|
|
842
|
-
updateCount: this.updateCount,
|
|
843
|
-
};
|
|
844
|
-
}
|
|
845
|
-
_conf(raw, bars) {
|
|
846
|
-
return raw !== 'NEUTRAL' ? Math.min(100, Math.round((bars / 20) * 100)) : 0;
|
|
847
|
-
}
|
|
848
|
-
_emptySignals() {
|
|
849
|
-
return {
|
|
850
|
-
smaRawTrend: 'NEUTRAL', smaTrend: 'NEUTRAL', smaBarsInTrend: 0, smaConfidence: 0,
|
|
851
|
-
fastSmaRawTrend: 'NEUTRAL', fastSmaTrend: 'NEUTRAL', fastSmaBarsInTrend: 0, fastSmaConfidence: 0, fastSmaValue: null,
|
|
852
|
-
slowSma: null,
|
|
853
|
-
macdLine: null, macdSignal: null, macdHistogram: null, macdTrend: null,
|
|
854
|
-
rsi: null, rsiZone: null,
|
|
855
|
-
interpretation: 'NEUTRAL', interpretationRaw: 'NEUTRAL', interpretationBars: 0,
|
|
856
|
-
entryBias: 'NONE',
|
|
857
|
-
isBullWeakEntry: false,
|
|
858
|
-
isBullConfirmation: false,
|
|
859
|
-
isLateBullWithoutWeak: false,
|
|
860
|
-
isBearWeakEntry: false,
|
|
861
|
-
isBearConfirmation: false,
|
|
862
|
-
isLateBearWithoutWeak: false,
|
|
863
|
-
};
|
|
864
|
-
}
|
|
865
|
-
reset() {
|
|
866
|
-
if (this.sma)
|
|
867
|
-
this.sma.reset(this.slowSmaPeriod);
|
|
868
|
-
if (this.fastSma)
|
|
869
|
-
this.fastSma.reset(this.fastSmaPeriod);
|
|
870
|
-
if (this.macd)
|
|
871
|
-
this.macd.reset(this.macdConfig.fastPeriod, this.macdConfig.slowPeriod, this.macdConfig.signalPeriod);
|
|
872
|
-
if (this.rsi)
|
|
873
|
-
this.rsi.reset(this.rsiPeriod);
|
|
874
|
-
this.currMacd = null;
|
|
875
|
-
this.prevMacd = null;
|
|
876
|
-
this.currRsi = null;
|
|
877
|
-
this.prevRawInterp = null;
|
|
878
|
-
this.barsInRawInterp = 0;
|
|
879
|
-
this.currInterpretation = 'NEUTRAL';
|
|
880
|
-
this.currInterpretationRaw = 'NEUTRAL';
|
|
881
|
-
this.pendingInterp = null;
|
|
882
|
-
this.pendingInterpBars = 0;
|
|
883
|
-
this.entryBias = 'NONE';
|
|
884
|
-
this.isBullWeakEntry = false;
|
|
885
|
-
this.isBullConfirmation = false;
|
|
886
|
-
this.isLateBullWithoutWeak = false;
|
|
887
|
-
this.isBearWeakEntry = false;
|
|
888
|
-
this.isBearConfirmation = false;
|
|
889
|
-
this.isLateBearWithoutWeak = false;
|
|
890
|
-
this.bullEntrySetupActive = false;
|
|
891
|
-
this.bullEntrySetupConfirmed = false;
|
|
892
|
-
this.bearEntrySetupActive = false;
|
|
893
|
-
this.bearEntrySetupConfirmed = false;
|
|
894
|
-
this.prevSma = null;
|
|
895
|
-
this.currSma = null;
|
|
896
|
-
this.prevFastSma = null;
|
|
897
|
-
this.currFastSma = null;
|
|
898
|
-
this.currPrice = null;
|
|
899
|
-
this.prevRawSmaTrend = null;
|
|
900
|
-
this.barsInSmaTrend = 0;
|
|
901
|
-
this.prevRawFastSmaTrend = null;
|
|
902
|
-
this.barsInFastSmaTrend = 0;
|
|
903
|
-
this.barsAboveFastSma = 0;
|
|
904
|
-
this.barsBelowFastSma = 0;
|
|
905
|
-
this.updateCount = 0;
|
|
906
|
-
}
|
|
907
|
-
getUpdateCount() { return this.updateCount; }
|
|
908
|
-
}
|
|
909
|
-
export { DerivativeAnalyzer };
|
|
910
|
-
//# sourceMappingURL=derivative_analyzer.js.map
|