pinets 0.1.34 → 0.2.1

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.
@@ -7687,6 +7687,7 @@ class ScopeManager {
7687
7687
  paramIdCounter = 0;
7688
7688
  cacheIdCounter = 0;
7689
7689
  tempVarCounter = 0;
7690
+ taCallIdCounter = 0;
7690
7691
  get nextParamIdArg() {
7691
7692
  return {
7692
7693
  type: "Identifier",
@@ -7699,6 +7700,12 @@ class ScopeManager {
7699
7700
  name: `'cache_${this.cacheIdCounter++}'`
7700
7701
  };
7701
7702
  }
7703
+ getNextTACallId() {
7704
+ return {
7705
+ type: "Literal",
7706
+ value: `_ta${this.taCallIdCounter++}`
7707
+ };
7708
+ }
7702
7709
  constructor() {
7703
7710
  this.pushScope("glb");
7704
7711
  }
@@ -7723,6 +7730,14 @@ class ScopeManager {
7723
7730
  this.rootParams.add(name);
7724
7731
  }
7725
7732
  }
7733
+ removeContextBoundVar(name) {
7734
+ if (this.contextBoundVars.has(name)) {
7735
+ this.contextBoundVars.delete(name);
7736
+ if (this.rootParams.has(name)) {
7737
+ this.rootParams.delete(name);
7738
+ }
7739
+ }
7740
+ }
7726
7741
  addArrayPatternElement(name) {
7727
7742
  this.arrayPatternElements.add(name);
7728
7743
  }
@@ -8369,38 +8384,46 @@ function transformReturnStatement(node, scopeManager) {
8369
8384
  return prop;
8370
8385
  });
8371
8386
  } else if (node.argument.type === "Identifier") {
8372
- const [scopedName, kind] = scopeManager.getVariable(node.argument.name);
8373
- node.argument = {
8374
- type: "MemberExpression",
8375
- object: {
8387
+ transformIdentifier(node.argument, scopeManager);
8388
+ if (node.argument.type === "Identifier") {
8389
+ addArrayAccess(node.argument);
8390
+ }
8391
+ }
8392
+ if (curScope === "fn") {
8393
+ if (node.argument.type === "Identifier" && scopeManager.isContextBound(node.argument.name) && !scopeManager.isRootParam(node.argument.name)) {
8394
+ node.argument = {
8376
8395
  type: "MemberExpression",
8377
- object: {
8378
- type: "Identifier",
8379
- name: CONTEXT_NAME
8380
- },
8396
+ object: node.argument,
8381
8397
  property: {
8382
- type: "Identifier",
8383
- name: kind
8398
+ type: "Literal",
8399
+ value: 0
8384
8400
  },
8385
- computed: false
8386
- },
8387
- property: {
8388
- type: "Identifier",
8389
- name: scopedName
8390
- },
8391
- computed: false
8392
- };
8393
- node.argument = {
8394
- type: "MemberExpression",
8395
- object: node.argument,
8396
- property: {
8397
- type: "Literal",
8398
- value: 0
8399
- },
8400
- computed: true
8401
- };
8402
- }
8403
- if (curScope === "fn") {
8401
+ computed: true
8402
+ };
8403
+ } else if (node.argument.type === "MemberExpression") {
8404
+ if (node.argument.object.type === "Identifier" && scopeManager.isContextBound(node.argument.object.name) && !scopeManager.isRootParam(node.argument.object.name)) {
8405
+ if (!node.argument._indexTransformed) {
8406
+ transformArrayIndex(node.argument, scopeManager);
8407
+ node.argument._indexTransformed = true;
8408
+ }
8409
+ }
8410
+ } else if (node.argument.type === "BinaryExpression" || node.argument.type === "LogicalExpression" || node.argument.type === "ConditionalExpression" || node.argument.type === "CallExpression") {
8411
+ recursive(node.argument, scopeManager, {
8412
+ Identifier(node2, state) {
8413
+ transformIdentifier(node2, state);
8414
+ if (node2.type === "Identifier" && !node2._arrayAccessed) {
8415
+ addArrayAccess(node2);
8416
+ node2._arrayAccessed = true;
8417
+ }
8418
+ },
8419
+ MemberExpression(node2) {
8420
+ transformMemberExpression(node2, "", scopeManager);
8421
+ },
8422
+ CallExpression(node2, state) {
8423
+ transformCallExpression(node2, state);
8424
+ }
8425
+ });
8426
+ }
8404
8427
  node.argument = {
8405
8428
  type: "CallExpression",
8406
8429
  callee: {
@@ -8751,6 +8774,9 @@ function transformCallExpression(node, scopeManager, namespace) {
8751
8774
  }
8752
8775
  return transformFunctionArgument(arg, namespace2, scopeManager);
8753
8776
  });
8777
+ if (namespace2 === "ta") {
8778
+ node.arguments.push(scopeManager.getNextTACallId());
8779
+ }
8754
8780
  node._transformed = true;
8755
8781
  } else if (node.callee && node.callee.type === "Identifier") {
8756
8782
  node.arguments = node.arguments.map((arg) => {
@@ -8791,9 +8817,11 @@ function transformCallExpression(node, scopeManager, namespace) {
8791
8817
  });
8792
8818
  }
8793
8819
  function transformFunctionDeclaration(node, scopeManager) {
8820
+ const boundParamNames = [];
8794
8821
  node.params.forEach((param) => {
8795
8822
  if (param.type === "Identifier") {
8796
8823
  scopeManager.addContextBoundVar(param.name, false);
8824
+ boundParamNames.push(param.name);
8797
8825
  }
8798
8826
  });
8799
8827
  if (node.body && node.body.type === "BlockStatement") {
@@ -9141,11 +9169,40 @@ function transpile(fn) {
9141
9169
  transformIfStatement(node, state, c);
9142
9170
  }
9143
9171
  });
9172
+ transformEqualityChecks(ast);
9144
9173
  const transformedCode = generate(ast);
9145
9174
  const _wraperFunction = new Function("", `return ${transformedCode}`);
9146
9175
  return _wraperFunction(this);
9147
9176
  }
9177
+ function transformEqualityChecks(ast) {
9178
+ simple(ast, {
9179
+ BinaryExpression(node) {
9180
+ if (node.operator === "==" || node.operator === "===") {
9181
+ const leftOperand = node.left;
9182
+ const rightOperand = node.right;
9183
+ Object.assign(node, {
9184
+ type: "CallExpression",
9185
+ callee: {
9186
+ type: "MemberExpression",
9187
+ object: {
9188
+ type: "Identifier",
9189
+ name: "$.math"
9190
+ },
9191
+ property: {
9192
+ type: "Identifier",
9193
+ name: "__eq"
9194
+ },
9195
+ computed: false
9196
+ },
9197
+ arguments: [leftOperand, rightOperand],
9198
+ _transformed: true
9199
+ });
9200
+ }
9201
+ }
9202
+ });
9203
+ }
9148
9204
 
9205
+ const MAX_PERIODS = 5e3;
9149
9206
  class PineTS {
9150
9207
  constructor(source, tickerId, timeframe, limit, sDate, eDate) {
9151
9208
  this.source = source;
@@ -9156,7 +9213,7 @@ class PineTS {
9156
9213
  this.eDate = eDate;
9157
9214
  this._readyPromise = new Promise((resolve) => {
9158
9215
  this.loadMarketData(source, tickerId, timeframe, limit, sDate, eDate).then((data) => {
9159
- const marketData = data.reverse();
9216
+ const marketData = data.slice(0, MAX_PERIODS);
9160
9217
  this._periods = marketData.length;
9161
9218
  this.data = marketData;
9162
9219
  const _open = marketData.map((d) => d.open);
@@ -9234,19 +9291,28 @@ class PineTS {
9234
9291
  context.useTACache = useTACache;
9235
9292
  const transformer = transpile.bind(this);
9236
9293
  let transpiledFn = transformer(pineTSCode);
9294
+ context.data.close = [];
9295
+ context.data.open = [];
9296
+ context.data.high = [];
9297
+ context.data.low = [];
9298
+ context.data.volume = [];
9299
+ context.data.hl2 = [];
9300
+ context.data.hlc3 = [];
9301
+ context.data.ohlc4 = [];
9302
+ context.data.openTime = [];
9303
+ context.data.closeTime = [];
9237
9304
  const contextVarNames = ["const", "var", "let", "params"];
9238
- for (let i = this._periods - n, idx = n - 1; i < this._periods; i++, idx--) {
9305
+ for (let i = this._periods - n; i < this._periods; i++) {
9239
9306
  context.idx = i;
9240
- context.data.close = this.close.slice(idx);
9241
- context.data.open = this.open.slice(idx);
9242
- context.data.high = this.high.slice(idx);
9243
- context.data.low = this.low.slice(idx);
9244
- context.data.volume = this.volume.slice(idx);
9245
- context.data.hl2 = this.hl2.slice(idx);
9246
- context.data.hlc3 = this.hlc3.slice(idx);
9247
- context.data.ohlc4 = this.ohlc4.slice(idx);
9248
- context.data.openTime = this.openTime.slice(idx);
9249
- context.data.closeTime = this.closeTime.slice(idx);
9307
+ context.data.close.unshift(this.close[i]);
9308
+ context.data.open.unshift(this.open[i]);
9309
+ context.data.high.unshift(this.high[i]);
9310
+ context.data.low.unshift(this.low[i]);
9311
+ context.data.volume.unshift(this.volume[i]);
9312
+ context.data.hl2.unshift(this.hl2[i]);
9313
+ context.data.hlc3.unshift(this.hlc3[i]);
9314
+ context.data.ohlc4.unshift(this.ohlc4[i]);
9315
+ context.data.openTime.unshift(this.openTime[i]);
9250
9316
  const result = await transpiledFn(context);
9251
9317
  if (typeof result === "object") {
9252
9318
  if (typeof context.result !== "object") {
@@ -9427,6 +9493,9 @@ class PineMath {
9427
9493
  return this.context.params[name];
9428
9494
  }
9429
9495
  }
9496
+ __eq(a, b) {
9497
+ return Math.abs(a - b) < 1e-8;
9498
+ }
9430
9499
  abs(source) {
9431
9500
  return Math.abs(source[0]);
9432
9501
  }
@@ -9587,141 +9656,532 @@ class TechnicalAnalysis {
9587
9656
  return this.context.params[name];
9588
9657
  }
9589
9658
  }
9590
- ema(source, _period) {
9659
+ ema(source, _period, _callId) {
9591
9660
  const period = Array.isArray(_period) ? _period[0] : _period;
9592
- const result = ema(source.slice(0).reverse(), period);
9593
- const idx = this.context.idx;
9594
- return this.context.precision(result[idx]);
9595
- }
9596
- sma(source, _period, _cacheId) {
9661
+ if (!this.context.taState) this.context.taState = {};
9662
+ const stateKey = _callId || `ema_${period}`;
9663
+ if (!this.context.taState[stateKey]) {
9664
+ this.context.taState[stateKey] = { prevEma: null, initSum: 0, initCount: 0 };
9665
+ }
9666
+ const state = this.context.taState[stateKey];
9667
+ const currentValue = source[0];
9668
+ if (state.initCount < period) {
9669
+ state.initSum += currentValue;
9670
+ state.initCount++;
9671
+ if (state.initCount === period) {
9672
+ state.prevEma = state.initSum / period;
9673
+ return this.context.precision(state.prevEma);
9674
+ }
9675
+ return NaN;
9676
+ }
9677
+ const alpha = 2 / (period + 1);
9678
+ const ema2 = currentValue * alpha + state.prevEma * (1 - alpha);
9679
+ state.prevEma = ema2;
9680
+ return this.context.precision(ema2);
9681
+ }
9682
+ sma(source, _period, _callId) {
9597
9683
  const period = Array.isArray(_period) ? _period[0] : _period;
9598
- const reversedSource = source.slice(0).reverse();
9599
- if (this.context.useTACache && _cacheId) {
9600
- if (!this.context.cache[_cacheId]) {
9601
- this.context.cache[_cacheId] = {};
9602
- }
9603
- const cacheObj = this.context.cache[_cacheId];
9604
- if (cacheObj) {
9605
- const result2 = sma_cache(reversedSource, period, cacheObj);
9606
- const idx2 = this.context.idx;
9607
- return this.context.precision(result2[idx2]);
9608
- }
9609
- }
9610
- const result = sma(reversedSource, period);
9611
- const idx = this.context.idx;
9612
- return this.context.precision(result[idx]);
9613
- }
9614
- vwma(source, _period) {
9684
+ if (!this.context.taState) this.context.taState = {};
9685
+ const stateKey = _callId || `sma_${period}`;
9686
+ if (!this.context.taState[stateKey]) {
9687
+ this.context.taState[stateKey] = { window: [], sum: 0 };
9688
+ }
9689
+ const state = this.context.taState[stateKey];
9690
+ const currentValue = source[0] || 0;
9691
+ state.window.unshift(currentValue);
9692
+ state.sum += currentValue;
9693
+ if (state.window.length < period) {
9694
+ return NaN;
9695
+ }
9696
+ if (state.window.length > period) {
9697
+ const oldValue = state.window.pop();
9698
+ state.sum -= oldValue;
9699
+ }
9700
+ const sma2 = state.sum / period;
9701
+ return this.context.precision(sma2);
9702
+ }
9703
+ vwma(source, _period, _callId) {
9615
9704
  const period = Array.isArray(_period) ? _period[0] : _period;
9616
- const volume = this.context.data.volume;
9617
- const result = vwma(source.slice(0).reverse(), volume.slice(0).reverse(), period);
9618
- const idx = this.context.idx;
9619
- return this.context.precision(result[idx]);
9705
+ if (!this.context.taState) this.context.taState = {};
9706
+ const stateKey = _callId || `vwma_${period}`;
9707
+ if (!this.context.taState[stateKey]) {
9708
+ this.context.taState[stateKey] = { window: [], volumeWindow: [] };
9709
+ }
9710
+ const state = this.context.taState[stateKey];
9711
+ const currentValue = source[0];
9712
+ const currentVolume = this.context.data.volume[0];
9713
+ state.window.unshift(currentValue);
9714
+ state.volumeWindow.unshift(currentVolume);
9715
+ if (state.window.length < period) {
9716
+ return NaN;
9717
+ }
9718
+ if (state.window.length > period) {
9719
+ state.window.pop();
9720
+ state.volumeWindow.pop();
9721
+ }
9722
+ let sumVolPrice = 0;
9723
+ let sumVol = 0;
9724
+ for (let i = 0; i < period; i++) {
9725
+ sumVolPrice += state.window[i] * state.volumeWindow[i];
9726
+ sumVol += state.volumeWindow[i];
9727
+ }
9728
+ const vwma2 = sumVolPrice / sumVol;
9729
+ return this.context.precision(vwma2);
9620
9730
  }
9621
- wma(source, _period) {
9731
+ wma(source, _period, _callId) {
9622
9732
  const period = Array.isArray(_period) ? _period[0] : _period;
9623
- const result = wma(source.slice(0).reverse(), period);
9624
- const idx = this.context.idx;
9625
- return this.context.precision(result[idx]);
9733
+ if (!this.context.taState) this.context.taState = {};
9734
+ const stateKey = _callId || `wma_${period}`;
9735
+ if (!this.context.taState[stateKey]) {
9736
+ this.context.taState[stateKey] = { window: [] };
9737
+ }
9738
+ const state = this.context.taState[stateKey];
9739
+ const currentValue = source[0];
9740
+ state.window.unshift(currentValue);
9741
+ if (state.window.length < period) {
9742
+ return NaN;
9743
+ }
9744
+ if (state.window.length > period) {
9745
+ state.window.pop();
9746
+ }
9747
+ let numerator = 0;
9748
+ let denominator = 0;
9749
+ for (let i = 0; i < period; i++) {
9750
+ const weight = period - i;
9751
+ numerator += state.window[i] * weight;
9752
+ denominator += weight;
9753
+ }
9754
+ const wma2 = numerator / denominator;
9755
+ return this.context.precision(wma2);
9626
9756
  }
9627
- hma(source, _period) {
9757
+ hma(source, _period, _callId) {
9628
9758
  const period = Array.isArray(_period) ? _period[0] : _period;
9629
- const result = hma(source.slice(0).reverse(), period);
9630
- const idx = this.context.idx;
9631
- return this.context.precision(result[idx]);
9759
+ const halfPeriod = Math.floor(period / 2);
9760
+ const sqrtPeriod = Math.floor(Math.sqrt(period));
9761
+ const wma1 = this.wma(source, halfPeriod, _callId ? `${_callId}_wma1` : void 0);
9762
+ const wma2 = this.wma(source, period, _callId ? `${_callId}_wma2` : void 0);
9763
+ if (isNaN(wma1) || isNaN(wma2)) {
9764
+ return NaN;
9765
+ }
9766
+ if (!this.context.taState) this.context.taState = {};
9767
+ const stateKey = _callId || `hma_raw_${period}`;
9768
+ if (!this.context.taState[stateKey]) {
9769
+ this.context.taState[stateKey] = [];
9770
+ }
9771
+ const rawHma = 2 * wma1 - wma2;
9772
+ this.context.taState[stateKey].unshift(rawHma);
9773
+ const hmaStateKey = _callId ? `${_callId}_hma_final` : `hma_final_${period}`;
9774
+ if (!this.context.taState[hmaStateKey]) {
9775
+ this.context.taState[hmaStateKey] = { window: [] };
9776
+ }
9777
+ const state = this.context.taState[hmaStateKey];
9778
+ state.window.unshift(rawHma);
9779
+ if (state.window.length < sqrtPeriod) {
9780
+ return NaN;
9781
+ }
9782
+ if (state.window.length > sqrtPeriod) {
9783
+ state.window.pop();
9784
+ }
9785
+ let numerator = 0;
9786
+ let denominator = 0;
9787
+ for (let i = 0; i < sqrtPeriod; i++) {
9788
+ const weight = sqrtPeriod - i;
9789
+ numerator += state.window[i] * weight;
9790
+ denominator += weight;
9791
+ }
9792
+ const hma2 = numerator / denominator;
9793
+ return this.context.precision(hma2);
9632
9794
  }
9633
- rma(source, _period) {
9795
+ rma(source, _period, _callId) {
9634
9796
  const period = Array.isArray(_period) ? _period[0] : _period;
9635
- const result = rma(source.slice(0).reverse(), period);
9636
- const idx = this.context.idx;
9637
- return this.context.precision(result[idx]);
9638
- }
9639
- change(source, _length = 1) {
9797
+ if (!this.context.taState) this.context.taState = {};
9798
+ const stateKey = _callId || `rma_${period}`;
9799
+ if (!this.context.taState[stateKey]) {
9800
+ this.context.taState[stateKey] = { prevRma: null, initSum: 0, initCount: 0 };
9801
+ }
9802
+ const state = this.context.taState[stateKey];
9803
+ const currentValue = source[0] || 0;
9804
+ if (state.initCount < period) {
9805
+ state.initSum += currentValue;
9806
+ state.initCount++;
9807
+ if (state.initCount === period) {
9808
+ state.prevRma = state.initSum / period;
9809
+ return this.context.precision(state.prevRma);
9810
+ }
9811
+ return NaN;
9812
+ }
9813
+ const alpha = 1 / period;
9814
+ const rma2 = currentValue * alpha + state.prevRma * (1 - alpha);
9815
+ state.prevRma = rma2;
9816
+ return this.context.precision(rma2);
9817
+ }
9818
+ change(source, _length = 1, _callId) {
9640
9819
  const length = Array.isArray(_length) ? _length[0] : _length;
9641
- const result = change(source.slice(0).reverse(), length);
9642
- const idx = this.context.idx;
9643
- return this.context.precision(result[idx]);
9820
+ if (!this.context.taState) this.context.taState = {};
9821
+ const stateKey = _callId || `change_${length}`;
9822
+ if (!this.context.taState[stateKey]) {
9823
+ this.context.taState[stateKey] = { window: [] };
9824
+ }
9825
+ const state = this.context.taState[stateKey];
9826
+ const currentValue = source[0];
9827
+ state.window.unshift(currentValue);
9828
+ if (state.window.length <= length) {
9829
+ return NaN;
9830
+ }
9831
+ if (state.window.length > length + 1) {
9832
+ state.window.pop();
9833
+ }
9834
+ const change2 = currentValue - state.window[length];
9835
+ return this.context.precision(change2);
9644
9836
  }
9645
- rsi(source, _period) {
9837
+ rsi(source, _period, _callId) {
9646
9838
  const period = Array.isArray(_period) ? _period[0] : _period;
9647
- const result = rsi(source.slice(0).reverse(), period);
9648
- const idx = this.context.idx;
9649
- return this.context.precision(result[idx]);
9839
+ if (!this.context.taState) this.context.taState = {};
9840
+ const stateKey = _callId || `rsi_${period}`;
9841
+ if (!this.context.taState[stateKey]) {
9842
+ this.context.taState[stateKey] = {
9843
+ prevValue: null,
9844
+ avgGain: 0,
9845
+ avgLoss: 0,
9846
+ initGains: [],
9847
+ initLosses: []
9848
+ };
9849
+ }
9850
+ const state = this.context.taState[stateKey];
9851
+ const currentValue = source[0];
9852
+ if (state.prevValue !== null) {
9853
+ const diff = currentValue - state.prevValue;
9854
+ const gain = diff > 0 ? diff : 0;
9855
+ const loss = diff < 0 ? -diff : 0;
9856
+ if (state.initGains.length < period) {
9857
+ state.initGains.push(gain);
9858
+ state.initLosses.push(loss);
9859
+ if (state.initGains.length === period) {
9860
+ state.avgGain = state.initGains.reduce((a, b) => a + b, 0) / period;
9861
+ state.avgLoss = state.initLosses.reduce((a, b) => a + b, 0) / period;
9862
+ state.prevValue = currentValue;
9863
+ const rsi3 = state.avgLoss === 0 ? 100 : 100 - 100 / (1 + state.avgGain / state.avgLoss);
9864
+ return this.context.precision(rsi3);
9865
+ }
9866
+ state.prevValue = currentValue;
9867
+ return NaN;
9868
+ }
9869
+ state.avgGain = (state.avgGain * (period - 1) + gain) / period;
9870
+ state.avgLoss = (state.avgLoss * (period - 1) + loss) / period;
9871
+ const rsi2 = state.avgLoss === 0 ? 100 : 100 - 100 / (1 + state.avgGain / state.avgLoss);
9872
+ state.prevValue = currentValue;
9873
+ return this.context.precision(rsi2);
9874
+ }
9875
+ state.prevValue = currentValue;
9876
+ return NaN;
9650
9877
  }
9651
- atr(_period) {
9878
+ atr(_period, _callId) {
9652
9879
  const period = Array.isArray(_period) ? _period[0] : _period;
9653
- const high = this.context.data.high.slice().reverse();
9654
- const low = this.context.data.low.slice().reverse();
9655
- const close = this.context.data.close.slice().reverse();
9656
- const result = atr(high, low, close, period);
9657
- const idx = this.context.idx;
9658
- return this.context.precision(result[idx]);
9880
+ if (!this.context.taState) this.context.taState = {};
9881
+ const stateKey = _callId || `atr_${period}`;
9882
+ if (!this.context.taState[stateKey]) {
9883
+ this.context.taState[stateKey] = {
9884
+ prevAtr: null,
9885
+ initSum: 0,
9886
+ initCount: 0,
9887
+ prevClose: null
9888
+ };
9889
+ }
9890
+ const state = this.context.taState[stateKey];
9891
+ const high = this.context.data.high[0];
9892
+ const low = this.context.data.low[0];
9893
+ const close = this.context.data.close[0];
9894
+ let tr;
9895
+ if (state.prevClose !== null) {
9896
+ const hl = high - low;
9897
+ const hc = Math.abs(high - state.prevClose);
9898
+ const lc = Math.abs(low - state.prevClose);
9899
+ tr = Math.max(hl, hc, lc);
9900
+ } else {
9901
+ tr = high - low;
9902
+ }
9903
+ state.prevClose = close;
9904
+ if (state.initCount < period) {
9905
+ state.initSum += tr;
9906
+ state.initCount++;
9907
+ if (state.initCount === period) {
9908
+ state.prevAtr = state.initSum / period;
9909
+ return this.context.precision(state.prevAtr);
9910
+ }
9911
+ return NaN;
9912
+ }
9913
+ const atr2 = (state.prevAtr * (period - 1) + tr) / period;
9914
+ state.prevAtr = atr2;
9915
+ return this.context.precision(atr2);
9659
9916
  }
9660
- mom(source, _length) {
9917
+ mom(source, _length, _callId) {
9661
9918
  const length = Array.isArray(_length) ? _length[0] : _length;
9662
- const result = mom(source.slice(0).reverse(), length);
9663
- const idx = this.context.idx;
9664
- return this.context.precision(result[idx]);
9919
+ return this.change(source, length);
9665
9920
  }
9666
- roc(source, _length) {
9921
+ roc(source, _length, _callId) {
9667
9922
  const length = Array.isArray(_length) ? _length[0] : _length;
9668
- const result = roc(source.slice(0).reverse(), length);
9669
- const idx = this.context.idx;
9670
- return this.context.precision(result[idx]);
9923
+ if (!this.context.taState) this.context.taState = {};
9924
+ const stateKey = _callId || `roc_${length}`;
9925
+ if (!this.context.taState[stateKey]) {
9926
+ this.context.taState[stateKey] = { window: [] };
9927
+ }
9928
+ const state = this.context.taState[stateKey];
9929
+ const currentValue = source[0];
9930
+ state.window.unshift(currentValue);
9931
+ if (state.window.length <= length) {
9932
+ return NaN;
9933
+ }
9934
+ if (state.window.length > length + 1) {
9935
+ state.window.pop();
9936
+ }
9937
+ const prevValue = state.window[length];
9938
+ const roc2 = (currentValue - prevValue) / prevValue * 100;
9939
+ return this.context.precision(roc2);
9671
9940
  }
9672
- dev(source, _length) {
9941
+ dev(source, _length, _callId) {
9673
9942
  const length = Array.isArray(_length) ? _length[0] : _length;
9674
- const result = dev(source.slice(0).reverse(), length);
9675
- const idx = this.context.idx;
9676
- return this.context.precision(result[idx]);
9943
+ if (!this.context.taState) this.context.taState = {};
9944
+ const stateKey = _callId || `dev_${length}`;
9945
+ if (!this.context.taState[stateKey]) {
9946
+ this.context.taState[stateKey] = { window: [], sum: 0 };
9947
+ }
9948
+ const state = this.context.taState[stateKey];
9949
+ const currentValue = source[0] || 0;
9950
+ state.window.unshift(currentValue);
9951
+ state.sum += currentValue;
9952
+ if (state.window.length < length) {
9953
+ return NaN;
9954
+ }
9955
+ if (state.window.length > length) {
9956
+ const oldValue = state.window.pop();
9957
+ state.sum -= oldValue;
9958
+ }
9959
+ const mean = state.sum / length;
9960
+ let sumDeviation = 0;
9961
+ for (let i = 0; i < length; i++) {
9962
+ sumDeviation += Math.abs(state.window[i] - mean);
9963
+ }
9964
+ const dev2 = sumDeviation / length;
9965
+ return this.context.precision(dev2);
9677
9966
  }
9678
- variance(source, _length) {
9967
+ variance(source, _length, _callId) {
9679
9968
  const length = Array.isArray(_length) ? _length[0] : _length;
9680
- const result = variance(source.slice(0).reverse(), length);
9681
- const idx = this.context.idx;
9682
- return this.context.precision(result[idx]);
9969
+ if (!this.context.taState) this.context.taState = {};
9970
+ const stateKey = _callId || `variance_${length}`;
9971
+ if (!this.context.taState[stateKey]) {
9972
+ this.context.taState[stateKey] = { window: [] };
9973
+ }
9974
+ const state = this.context.taState[stateKey];
9975
+ const currentValue = source[0];
9976
+ state.window.unshift(currentValue);
9977
+ if (state.window.length < length) {
9978
+ return NaN;
9979
+ }
9980
+ if (state.window.length > length) {
9981
+ state.window.pop();
9982
+ }
9983
+ let sum = 0;
9984
+ let sumSquares = 0;
9985
+ for (let i = 0; i < length; i++) {
9986
+ sum += state.window[i];
9987
+ sumSquares += state.window[i] * state.window[i];
9988
+ }
9989
+ const mean = sum / length;
9990
+ const variance2 = sumSquares / length - mean * mean;
9991
+ return this.context.precision(variance2);
9683
9992
  }
9684
- highest(source, _length) {
9993
+ highest(source, _length, _callId) {
9685
9994
  const length = Array.isArray(_length) ? _length[0] : _length;
9686
- const result = highest(source.slice(0).reverse(), length);
9687
- const idx = this.context.idx;
9688
- return this.context.precision(result[idx]);
9995
+ if (!this.context.taState) this.context.taState = {};
9996
+ const stateKey = _callId || `highest_${length}`;
9997
+ if (!this.context.taState[stateKey]) {
9998
+ this.context.taState[stateKey] = { window: [] };
9999
+ }
10000
+ const state = this.context.taState[stateKey];
10001
+ const currentValue = source[0];
10002
+ state.window.unshift(currentValue);
10003
+ if (state.window.length < length) {
10004
+ return NaN;
10005
+ }
10006
+ if (state.window.length > length) {
10007
+ state.window.pop();
10008
+ }
10009
+ const max = Math.max(...state.window.filter((v) => !isNaN(v)));
10010
+ return this.context.precision(max);
9689
10011
  }
9690
- lowest(source, _length) {
10012
+ lowest(source, _length, _callId) {
9691
10013
  const length = Array.isArray(_length) ? _length[0] : _length;
9692
- const result = lowest(source.slice(0).reverse(), length);
9693
- const idx = this.context.idx;
9694
- return this.context.precision(result[idx]);
10014
+ if (!this.context.taState) this.context.taState = {};
10015
+ const stateKey = _callId || `lowest_${length}`;
10016
+ if (!this.context.taState[stateKey]) {
10017
+ this.context.taState[stateKey] = { window: [] };
10018
+ }
10019
+ const state = this.context.taState[stateKey];
10020
+ const currentValue = source[0];
10021
+ state.window.unshift(currentValue);
10022
+ if (state.window.length < length) {
10023
+ return NaN;
10024
+ }
10025
+ if (state.window.length > length) {
10026
+ state.window.pop();
10027
+ }
10028
+ const validValues = state.window.filter((v) => !isNaN(v) && v !== void 0);
10029
+ const min = validValues.length > 0 ? Math.min(...validValues) : NaN;
10030
+ return this.context.precision(min);
9695
10031
  }
9696
- median(source, _length) {
10032
+ median(source, _length, _callId) {
9697
10033
  const length = Array.isArray(_length) ? _length[0] : _length;
9698
- const result = median(source.slice(0).reverse(), length);
9699
- const idx = this.context.idx;
9700
- return this.context.precision(result[idx]);
10034
+ if (!this.context.taState) this.context.taState = {};
10035
+ const stateKey = _callId || `median_${length}`;
10036
+ if (!this.context.taState[stateKey]) {
10037
+ this.context.taState[stateKey] = { window: [] };
10038
+ }
10039
+ const state = this.context.taState[stateKey];
10040
+ const currentValue = source[0];
10041
+ state.window.unshift(currentValue);
10042
+ if (state.window.length < length) {
10043
+ return NaN;
10044
+ }
10045
+ if (state.window.length > length) {
10046
+ state.window.pop();
10047
+ }
10048
+ const sorted = state.window.slice().sort((a, b) => a - b);
10049
+ const mid = Math.floor(length / 2);
10050
+ const median2 = length % 2 === 0 ? (sorted[mid - 1] + sorted[mid]) / 2 : sorted[mid];
10051
+ return this.context.precision(median2);
9701
10052
  }
9702
- stdev(source, _length, _bias = true) {
10053
+ stdev(source, _length, _bias = true, _callId) {
9703
10054
  const length = Array.isArray(_length) ? _length[0] : _length;
9704
10055
  const bias = Array.isArray(_bias) ? _bias[0] : _bias;
9705
- const result = stdev(source.slice(0).reverse(), length, bias);
9706
- const idx = this.context.idx;
9707
- return this.context.precision(result[idx]);
10056
+ if (!this.context.taState) this.context.taState = {};
10057
+ const stateKey = _callId || `stdev_${length}_${bias}`;
10058
+ if (!this.context.taState[stateKey]) {
10059
+ this.context.taState[stateKey] = { window: [], sum: 0 };
10060
+ }
10061
+ const state = this.context.taState[stateKey];
10062
+ const currentValue = source[0];
10063
+ state.window.unshift(currentValue);
10064
+ state.sum += currentValue;
10065
+ if (state.window.length < length) {
10066
+ return NaN;
10067
+ }
10068
+ if (state.window.length > length) {
10069
+ const oldValue = state.window.pop();
10070
+ state.sum -= oldValue;
10071
+ }
10072
+ const mean = state.sum / length;
10073
+ let sumSquaredDiff = 0;
10074
+ for (let i = 0; i < length; i++) {
10075
+ sumSquaredDiff += Math.pow(state.window[i] - mean, 2);
10076
+ }
10077
+ const divisor = bias ? length : length - 1;
10078
+ const stdev2 = Math.sqrt(sumSquaredDiff / divisor);
10079
+ return this.context.precision(stdev2);
9708
10080
  }
9709
- linreg(source, _length, _offset) {
10081
+ linreg(source, _length, _offset, _callId) {
9710
10082
  const length = Array.isArray(_length) ? _length[0] : _length;
9711
10083
  const offset = Array.isArray(_offset) ? _offset[0] : _offset;
9712
- const result = linreg(source.slice(0).reverse(), length, offset);
9713
- const idx = this.context.idx;
9714
- return this.context.precision(result[idx]);
10084
+ if (!this.context.taState) this.context.taState = {};
10085
+ const stateKey = _callId || `linreg_${length}_${offset}`;
10086
+ if (!this.context.taState[stateKey]) {
10087
+ this.context.taState[stateKey] = { window: [] };
10088
+ }
10089
+ const state = this.context.taState[stateKey];
10090
+ const currentValue = source[0];
10091
+ state.window.unshift(currentValue);
10092
+ if (state.window.length < length) {
10093
+ return NaN;
10094
+ }
10095
+ if (state.window.length > length) {
10096
+ state.window.pop();
10097
+ }
10098
+ let sumX = 0;
10099
+ let sumY = 0;
10100
+ let sumXY = 0;
10101
+ let sumXX = 0;
10102
+ const n = length;
10103
+ for (let j = 0; j < length; j++) {
10104
+ const x = length - 1 - j;
10105
+ const y = state.window[j];
10106
+ sumX += x;
10107
+ sumY += y;
10108
+ sumXY += x * y;
10109
+ sumXX += x * x;
10110
+ }
10111
+ const denominator = n * sumXX - sumX * sumX;
10112
+ if (denominator === 0) {
10113
+ return NaN;
10114
+ }
10115
+ const slope = (n * sumXY - sumX * sumY) / denominator;
10116
+ const intercept = (sumY - slope * sumX) / n;
10117
+ const linRegValue = intercept + slope * (length - 1 - offset);
10118
+ return this.context.precision(linRegValue);
9715
10119
  }
9716
- supertrend(_factor, _atrPeriod) {
10120
+ supertrend(_factor, _atrPeriod, _callId) {
9717
10121
  const factor = Array.isArray(_factor) ? _factor[0] : _factor;
9718
10122
  const atrPeriod = Array.isArray(_atrPeriod) ? _atrPeriod[0] : _atrPeriod;
9719
- const high = this.context.data.high.slice().reverse();
9720
- const low = this.context.data.low.slice().reverse();
9721
- const close = this.context.data.close.slice().reverse();
9722
- const [supertrend, direction] = calculateSupertrend(high, low, close, factor, atrPeriod);
9723
- const idx = this.context.idx;
9724
- return [[this.context.precision(supertrend[idx]), direction[idx]]];
10123
+ if (!this.context.taState) this.context.taState = {};
10124
+ const stateKey = `supertrend_${factor}_${atrPeriod}`;
10125
+ if (!this.context.taState[stateKey]) {
10126
+ this.context.taState[stateKey] = {
10127
+ prevUpperBand: null,
10128
+ prevLowerBand: null,
10129
+ prevSupertrend: null,
10130
+ prevDirection: null
10131
+ };
10132
+ }
10133
+ const state = this.context.taState[stateKey];
10134
+ const high = this.context.data.high[0];
10135
+ const low = this.context.data.low[0];
10136
+ const close = this.context.data.close[0];
10137
+ const atrValue = this.atr(atrPeriod, _callId ? `${_callId}_atr` : void 0);
10138
+ if (isNaN(atrValue)) {
10139
+ return [[NaN, 0]];
10140
+ }
10141
+ const hl2 = (high + low) / 2;
10142
+ let upperBand = hl2 + factor * atrValue;
10143
+ let lowerBand = hl2 - factor * atrValue;
10144
+ if (state.prevUpperBand !== null) {
10145
+ if (upperBand < state.prevUpperBand || this.context.data.close[1] > state.prevUpperBand) {
10146
+ upperBand = upperBand;
10147
+ } else {
10148
+ upperBand = state.prevUpperBand;
10149
+ }
10150
+ if (lowerBand > state.prevLowerBand || this.context.data.close[1] < state.prevLowerBand) {
10151
+ lowerBand = lowerBand;
10152
+ } else {
10153
+ lowerBand = state.prevLowerBand;
10154
+ }
10155
+ }
10156
+ let direction;
10157
+ let supertrend;
10158
+ if (state.prevSupertrend === null) {
10159
+ direction = close <= upperBand ? -1 : 1;
10160
+ supertrend = direction === -1 ? upperBand : lowerBand;
10161
+ } else {
10162
+ if (state.prevSupertrend === state.prevUpperBand) {
10163
+ if (close > upperBand) {
10164
+ direction = 1;
10165
+ supertrend = lowerBand;
10166
+ } else {
10167
+ direction = -1;
10168
+ supertrend = upperBand;
10169
+ }
10170
+ } else {
10171
+ if (close < lowerBand) {
10172
+ direction = -1;
10173
+ supertrend = upperBand;
10174
+ } else {
10175
+ direction = 1;
10176
+ supertrend = lowerBand;
10177
+ }
10178
+ }
10179
+ }
10180
+ state.prevUpperBand = upperBand;
10181
+ state.prevLowerBand = lowerBand;
10182
+ state.prevSupertrend = supertrend;
10183
+ state.prevDirection = direction;
10184
+ return [[this.context.precision(supertrend), direction]];
9725
10185
  }
9726
10186
  crossover(source1, source2) {
9727
10187
  const current1 = Array.isArray(source1) ? source1[0] : source1;
@@ -9762,344 +10222,6 @@ class TechnicalAnalysis {
9762
10222
  return this.context.precision(result[idx]);
9763
10223
  }
9764
10224
  }
9765
- function atr(high, low, close, period) {
9766
- const tr = new Array(high.length);
9767
- tr[0] = high[0] - low[0];
9768
- for (let i = 1; i < high.length; i++) {
9769
- const hl = high[i] - low[i];
9770
- const hc = Math.abs(high[i] - close[i - 1]);
9771
- const lc = Math.abs(low[i] - close[i - 1]);
9772
- tr[i] = Math.max(hl, hc, lc);
9773
- }
9774
- const atr2 = new Array(high.length).fill(NaN);
9775
- let sum = 0;
9776
- for (let i = 0; i < period; i++) {
9777
- sum += tr[i];
9778
- }
9779
- atr2[period - 1] = sum / period;
9780
- for (let i = period; i < tr.length; i++) {
9781
- atr2[i] = (atr2[i - 1] * (period - 1) + tr[i]) / period;
9782
- }
9783
- return atr2;
9784
- }
9785
- function ema(source, period) {
9786
- const result = new Array(source.length).fill(NaN);
9787
- const alpha = 2 / (period + 1);
9788
- let sum = 0;
9789
- for (let i = 0; i < period; i++) {
9790
- sum += source[i] || 0;
9791
- }
9792
- result[period - 1] = sum / period;
9793
- for (let i = period; i < source.length; i++) {
9794
- result[i] = source[i] * alpha + result[i - 1] * (1 - alpha);
9795
- }
9796
- return result;
9797
- }
9798
- function rsi(source, period) {
9799
- const result = new Array(source.length).fill(NaN);
9800
- const gains = new Array(source.length).fill(0);
9801
- const losses = new Array(source.length).fill(0);
9802
- for (let i = 1; i < source.length; i++) {
9803
- const diff = source[i] - source[i - 1];
9804
- gains[i] = diff > 0 ? diff : 0;
9805
- losses[i] = diff < 0 ? -diff : 0;
9806
- }
9807
- let avgGain = 0;
9808
- let avgLoss = 0;
9809
- for (let i = 1; i <= period; i++) {
9810
- avgGain += gains[i];
9811
- avgLoss += losses[i];
9812
- }
9813
- avgGain /= period;
9814
- avgLoss /= period;
9815
- result[period] = avgLoss === 0 ? 100 : 100 - 100 / (1 + avgGain / avgLoss);
9816
- for (let i = period + 1; i < source.length; i++) {
9817
- avgGain = (avgGain * (period - 1) + gains[i]) / period;
9818
- avgLoss = (avgLoss * (period - 1) + losses[i]) / period;
9819
- result[i] = avgLoss === 0 ? 100 : 100 - 100 / (1 + avgGain / avgLoss);
9820
- }
9821
- return result;
9822
- }
9823
- function rma(source, period) {
9824
- const result = new Array(source.length).fill(NaN);
9825
- const alpha = 1 / period;
9826
- let sum = 0;
9827
- for (let i = 0; i < period; i++) {
9828
- sum += source[i] || 0;
9829
- }
9830
- result[period - 1] = sum / period;
9831
- for (let i = period; i < source.length; i++) {
9832
- const currentValue = source[i] || 0;
9833
- result[i] = currentValue * alpha + result[i - 1] * (1 - alpha);
9834
- }
9835
- return result;
9836
- }
9837
- function sma_cache(source, period, cacheObj) {
9838
- const result = cacheObj.previousResult || new Array(source.length).fill(NaN);
9839
- const lastProcessedIndex = cacheObj.lastProcessedIndex || -1;
9840
- let previousSum = cacheObj.previousSum || 0;
9841
- if (lastProcessedIndex === -1 || source.length !== lastProcessedIndex + 1) {
9842
- previousSum = 0;
9843
- for (let i = 0; i < period; i++) {
9844
- previousSum += source[i] || 0;
9845
- }
9846
- result[period - 1] = previousSum / period;
9847
- for (let i = 0; i < period - 1; i++) {
9848
- result[i] = NaN;
9849
- }
9850
- for (let i = period; i < source.length; i++) {
9851
- previousSum = previousSum - (source[i - period] || 0) + (source[i] || 0);
9852
- result[i] = previousSum / period;
9853
- }
9854
- } else if (source.length === lastProcessedIndex + 2) {
9855
- const newIndex = source.length - 1;
9856
- previousSum = previousSum - (source[newIndex - period] || 0) + (source[newIndex] || 0);
9857
- result[newIndex] = previousSum / period;
9858
- } else {
9859
- return sma(source, period);
9860
- }
9861
- cacheObj.previousSum = previousSum;
9862
- cacheObj.lastProcessedIndex = source.length - 1;
9863
- cacheObj.previousResult = result;
9864
- return result;
9865
- }
9866
- function sma(source, period) {
9867
- const result = new Array(source.length).fill(NaN);
9868
- for (let i = period - 1; i < source.length; i++) {
9869
- let sum = 0;
9870
- for (let j = 0; j < period; j++) {
9871
- sum += source[i - j] || 0;
9872
- }
9873
- result[i] = sum / period;
9874
- }
9875
- return result;
9876
- }
9877
- function vwma(source, volume, period) {
9878
- const result = new Array(source.length).fill(NaN);
9879
- for (let i = period - 1; i < source.length; i++) {
9880
- let sumVol = 0;
9881
- let sumVolPrice = 0;
9882
- for (let j = 0; j < period; j++) {
9883
- sumVol += volume[i - j];
9884
- sumVolPrice += source[i - j] * volume[i - j];
9885
- }
9886
- result[i] = sumVolPrice / sumVol;
9887
- }
9888
- return result;
9889
- }
9890
- function hma(source, period) {
9891
- const halfPeriod = Math.floor(period / 2);
9892
- const wma1 = wma(source, halfPeriod);
9893
- const wma2 = wma(source, period);
9894
- const rawHma = wma1.map((value, index) => 2 * value - wma2[index]);
9895
- const sqrtPeriod = Math.floor(Math.sqrt(period));
9896
- const result = wma(rawHma, sqrtPeriod);
9897
- return result;
9898
- }
9899
- function wma(source, period) {
9900
- const result = new Array(source.length);
9901
- for (let i = period - 1; i < source.length; i++) {
9902
- let numerator = 0;
9903
- let denominator = 0;
9904
- for (let j = 0; j < period; j++) {
9905
- numerator += source[i - j] * (period - j);
9906
- denominator += period - j;
9907
- }
9908
- result[i] = numerator / denominator;
9909
- }
9910
- for (let i = 0; i < period - 1; i++) {
9911
- result[i] = NaN;
9912
- }
9913
- return result;
9914
- }
9915
- function change(source, length = 1) {
9916
- const result = new Array(source.length).fill(NaN);
9917
- for (let i = length; i < source.length; i++) {
9918
- result[i] = source[i] - source[i - length];
9919
- }
9920
- return result;
9921
- }
9922
- function mom(source, length) {
9923
- const result = new Array(source.length).fill(NaN);
9924
- for (let i = length; i < source.length; i++) {
9925
- result[i] = source[i] - source[i - length];
9926
- }
9927
- return result;
9928
- }
9929
- function roc(source, length) {
9930
- const result = new Array(source.length).fill(NaN);
9931
- for (let i = length; i < source.length; i++) {
9932
- result[i] = (source[i] - source[i - length]) / source[i - length] * 100;
9933
- }
9934
- return result;
9935
- }
9936
- function dev(source, length) {
9937
- const result = new Array(source.length).fill(NaN);
9938
- const smaValues = sma(source, length);
9939
- for (let i = length - 1; i < source.length; i++) {
9940
- let sumDeviation = 0;
9941
- for (let j = 0; j < length; j++) {
9942
- sumDeviation += Math.abs(source[i - j] - smaValues[i]);
9943
- }
9944
- result[i] = sumDeviation / length;
9945
- }
9946
- return result;
9947
- }
9948
- function variance(source, length) {
9949
- const result = new Array(source.length).fill(NaN);
9950
- for (let i = length - 1; i < source.length; i++) {
9951
- let sum = 0;
9952
- let sumSquares = 0;
9953
- for (let j = 0; j < length; j++) {
9954
- sum += source[i - j];
9955
- sumSquares += source[i - j] * source[i - j];
9956
- }
9957
- const mean = sum / length;
9958
- result[i] = sumSquares / length - mean * mean;
9959
- }
9960
- return result;
9961
- }
9962
- function highest(source, length) {
9963
- const result = new Array(source.length).fill(NaN);
9964
- for (let i = length - 1; i < source.length; i++) {
9965
- let max = -Infinity;
9966
- for (let j = 0; j < length; j++) {
9967
- const value = source[i - j];
9968
- if (isNaN(value)) {
9969
- max = max === -Infinity ? NaN : max;
9970
- } else {
9971
- max = Math.max(max, value);
9972
- }
9973
- }
9974
- result[i] = max;
9975
- }
9976
- return result;
9977
- }
9978
- function lowest(source, length) {
9979
- const result = new Array(source.length).fill(NaN);
9980
- for (let i = length - 1; i < source.length; i++) {
9981
- let min = Infinity;
9982
- for (let j = 0; j < length; j++) {
9983
- const value = source[i - j];
9984
- if (isNaN(value) || value === void 0) {
9985
- min = min === Infinity ? NaN : min;
9986
- } else {
9987
- min = Math.min(min, value);
9988
- }
9989
- }
9990
- result[i] = min;
9991
- }
9992
- return result;
9993
- }
9994
- function median(source, length) {
9995
- const result = new Array(source.length).fill(NaN);
9996
- for (let i = length - 1; i < source.length; i++) {
9997
- const window = source.slice(i - length + 1, i + 1);
9998
- const sorted = window.slice().sort((a, b) => a - b);
9999
- const mid = Math.floor(length / 2);
10000
- result[i] = length % 2 === 0 ? (sorted[mid - 1] + sorted[mid]) / 2 : sorted[mid];
10001
- }
10002
- return result;
10003
- }
10004
- function stdev(source, length, biased = true) {
10005
- const result = new Array(source.length).fill(NaN);
10006
- const smaValues = sma(source, length);
10007
- for (let i = length - 1; i < source.length; i++) {
10008
- let sum = 0;
10009
- for (let j = 0; j < length; j++) {
10010
- sum += Math.pow(source[i - j] - smaValues[i], 2);
10011
- }
10012
- const divisor = biased ? length : length - 1;
10013
- result[i] = Math.sqrt(sum / divisor);
10014
- }
10015
- return result;
10016
- }
10017
- function linreg(source, length, offset) {
10018
- const size = source.length;
10019
- const output = new Array(size).fill(NaN);
10020
- for (let i = length - 1; i < size; i++) {
10021
- let sumX = 0;
10022
- let sumY = 0;
10023
- let sumXY = 0;
10024
- let sumXX = 0;
10025
- const n = length;
10026
- for (let j = 0; j < length; j++) {
10027
- const x = j;
10028
- const y = source[i - length + 1 + j];
10029
- sumX += x;
10030
- sumY += y;
10031
- sumXY += x * y;
10032
- sumXX += x * x;
10033
- }
10034
- const denominator = n * sumXX - sumX * sumX;
10035
- if (denominator === 0) {
10036
- output[i] = NaN;
10037
- continue;
10038
- }
10039
- const slope = (n * sumXY - sumX * sumY) / denominator;
10040
- const intercept = (sumY - slope * sumX) / n;
10041
- const linRegValue = intercept + slope * (length - 1 - offset);
10042
- output[i] = linRegValue;
10043
- }
10044
- return output;
10045
- }
10046
- function calculateSupertrend(high, low, close, factor, atrPeriod) {
10047
- const length = high.length;
10048
- const supertrend = new Array(length).fill(NaN);
10049
- const direction = new Array(length).fill(0);
10050
- const atrValues = atr(high, low, close, atrPeriod);
10051
- const upperBand = new Array(length).fill(NaN);
10052
- const lowerBand = new Array(length).fill(NaN);
10053
- for (let i = 0; i < length; i++) {
10054
- const hl2 = (high[i] + low[i]) / 2;
10055
- const atrValue = atrValues[i];
10056
- if (!isNaN(atrValue)) {
10057
- upperBand[i] = hl2 + factor * atrValue;
10058
- lowerBand[i] = hl2 - factor * atrValue;
10059
- }
10060
- }
10061
- let prevUpperBand = upperBand[atrPeriod];
10062
- let prevLowerBand = lowerBand[atrPeriod];
10063
- let prevSupertrend = close[atrPeriod] <= prevUpperBand ? prevUpperBand : prevLowerBand;
10064
- let prevDirection = close[atrPeriod] <= prevUpperBand ? -1 : 1;
10065
- supertrend[atrPeriod] = prevSupertrend;
10066
- direction[atrPeriod] = prevDirection;
10067
- for (let i = atrPeriod + 1; i < length; i++) {
10068
- let currentUpperBand = upperBand[i];
10069
- if (currentUpperBand < prevUpperBand || close[i - 1] > prevUpperBand) {
10070
- upperBand[i] = currentUpperBand;
10071
- } else {
10072
- upperBand[i] = prevUpperBand;
10073
- }
10074
- let currentLowerBand = lowerBand[i];
10075
- if (currentLowerBand > prevLowerBand || close[i - 1] < prevLowerBand) {
10076
- lowerBand[i] = currentLowerBand;
10077
- } else {
10078
- lowerBand[i] = prevLowerBand;
10079
- }
10080
- if (prevSupertrend === prevUpperBand) {
10081
- if (close[i] > upperBand[i]) {
10082
- direction[i] = 1;
10083
- supertrend[i] = lowerBand[i];
10084
- } else {
10085
- direction[i] = -1;
10086
- supertrend[i] = upperBand[i];
10087
- }
10088
- } else {
10089
- if (close[i] < lowerBand[i]) {
10090
- direction[i] = -1;
10091
- supertrend[i] = upperBand[i];
10092
- } else {
10093
- direction[i] = 1;
10094
- supertrend[i] = lowerBand[i];
10095
- }
10096
- }
10097
- prevUpperBand = upperBand[i];
10098
- prevLowerBand = lowerBand[i];
10099
- prevSupertrend = supertrend[i];
10100
- }
10101
- return [supertrend, direction];
10102
- }
10103
10225
  function pivothigh(source, leftbars, rightbars) {
10104
10226
  const result = new Array(source.length).fill(NaN);
10105
10227
  for (let i = leftbars + rightbars; i < source.length; i++) {
@@ -10352,6 +10474,8 @@ class Context {
10352
10474
  ohlc4: []
10353
10475
  };
10354
10476
  cache = {};
10477
+ taState = {};
10478
+ // State for incremental TA calculations
10355
10479
  useTACache = false;
10356
10480
  NA = NaN;
10357
10481
  math;
@@ -10593,7 +10717,6 @@ class BinanceProvider {
10593
10717
  if (data.length === 0) break;
10594
10718
  allData = allData.concat(data);
10595
10719
  currentStart = data[data.length - 1].closeTime + 1;
10596
- if (data.length < 1e3) break;
10597
10720
  }
10598
10721
  return allData;
10599
10722
  } catch (error) {
@@ -10601,8 +10724,6 @@ class BinanceProvider {
10601
10724
  return [];
10602
10725
  }
10603
10726
  }
10604
- //TODO : allow querying more than 1000 klines
10605
- //TODO : immplement cache
10606
10727
  async getMarketData(tickerId, timeframe, limit, sDate, eDate) {
10607
10728
  try {
10608
10729
  const cacheParams = { tickerId, timeframe, limit, sDate, eDate };
@@ -10616,12 +10737,16 @@ class BinanceProvider {
10616
10737
  console.error(`Unsupported timeframe: ${timeframe}`);
10617
10738
  return [];
10618
10739
  }
10619
- let url = `${BINANCE_API_URL}/klines?symbol=${tickerId}&interval=${interval}`;
10620
- if (!limit && sDate && eDate) {
10621
- return this.getMarketDataInterval(tickerId, timeframe, sDate, eDate);
10740
+ const needsPagination = this.shouldPaginate(timeframe, limit, sDate, eDate);
10741
+ if (needsPagination && sDate && eDate) {
10742
+ const allData = await this.getMarketDataInterval(tickerId, timeframe, sDate, eDate);
10743
+ const result2 = limit ? allData.slice(0, limit) : allData;
10744
+ this.cacheManager.set(cacheParams, result2);
10745
+ return result2;
10622
10746
  }
10747
+ let url = `${BINANCE_API_URL}/klines?symbol=${tickerId}&interval=${interval}`;
10623
10748
  if (limit) {
10624
- url += `&limit=${limit}`;
10749
+ url += `&limit=${Math.min(limit, 1e3)}`;
10625
10750
  }
10626
10751
  if (sDate) {
10627
10752
  url += `&startTime=${sDate}`;
@@ -10657,6 +10782,36 @@ class BinanceProvider {
10657
10782
  return [];
10658
10783
  }
10659
10784
  }
10785
+ /**
10786
+ * Determines if pagination is needed based on the parameters
10787
+ */
10788
+ shouldPaginate(timeframe, limit, sDate, eDate) {
10789
+ if (limit && limit > 1e3) {
10790
+ return true;
10791
+ }
10792
+ if (sDate && eDate) {
10793
+ const interval = timeframe_to_binance[timeframe.toUpperCase()];
10794
+ const timeframeDurations = {
10795
+ "1m": 60 * 1e3,
10796
+ "3m": 3 * 60 * 1e3,
10797
+ "5m": 5 * 60 * 1e3,
10798
+ "15m": 15 * 60 * 1e3,
10799
+ "30m": 30 * 60 * 1e3,
10800
+ "1h": 60 * 60 * 1e3,
10801
+ "2h": 2 * 60 * 60 * 1e3,
10802
+ "4h": 4 * 60 * 60 * 1e3,
10803
+ "1d": 24 * 60 * 60 * 1e3,
10804
+ "1w": 7 * 24 * 60 * 60 * 1e3,
10805
+ "1M": 30 * 24 * 60 * 60 * 1e3
10806
+ };
10807
+ const intervalDuration = timeframeDurations[interval];
10808
+ if (intervalDuration) {
10809
+ const requiredCandles = Math.ceil((eDate - sDate) / intervalDuration);
10810
+ return requiredCandles > 1e3;
10811
+ }
10812
+ }
10813
+ return false;
10814
+ }
10660
10815
  }
10661
10816
 
10662
10817
  const Provider = {