pinets 0.1.34 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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.reverse().slice(0, MAX_PERIODS);
9160
9217
  this._periods = marketData.length;
9161
9218
  this.data = marketData;
9162
9219
  const _open = marketData.map((d) => d.open);
@@ -9427,6 +9484,9 @@ class PineMath {
9427
9484
  return this.context.params[name];
9428
9485
  }
9429
9486
  }
9487
+ __eq(a, b) {
9488
+ return Math.abs(a - b) < 1e-8;
9489
+ }
9430
9490
  abs(source) {
9431
9491
  return Math.abs(source[0]);
9432
9492
  }
@@ -9587,141 +9647,532 @@ class TechnicalAnalysis {
9587
9647
  return this.context.params[name];
9588
9648
  }
9589
9649
  }
9590
- ema(source, _period) {
9650
+ ema(source, _period, _callId) {
9591
9651
  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) {
9652
+ if (!this.context.taState) this.context.taState = {};
9653
+ const stateKey = _callId || `ema_${period}`;
9654
+ if (!this.context.taState[stateKey]) {
9655
+ this.context.taState[stateKey] = { prevEma: null, initSum: 0, initCount: 0 };
9656
+ }
9657
+ const state = this.context.taState[stateKey];
9658
+ const currentValue = source[0];
9659
+ if (state.initCount < period) {
9660
+ state.initSum += currentValue;
9661
+ state.initCount++;
9662
+ if (state.initCount === period) {
9663
+ state.prevEma = state.initSum / period;
9664
+ return this.context.precision(state.prevEma);
9665
+ }
9666
+ return NaN;
9667
+ }
9668
+ const alpha = 2 / (period + 1);
9669
+ const ema2 = currentValue * alpha + state.prevEma * (1 - alpha);
9670
+ state.prevEma = ema2;
9671
+ return this.context.precision(ema2);
9672
+ }
9673
+ sma(source, _period, _callId) {
9597
9674
  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) {
9675
+ if (!this.context.taState) this.context.taState = {};
9676
+ const stateKey = _callId || `sma_${period}`;
9677
+ if (!this.context.taState[stateKey]) {
9678
+ this.context.taState[stateKey] = { window: [], sum: 0 };
9679
+ }
9680
+ const state = this.context.taState[stateKey];
9681
+ const currentValue = source[0] || 0;
9682
+ state.window.unshift(currentValue);
9683
+ state.sum += currentValue;
9684
+ if (state.window.length < period) {
9685
+ return NaN;
9686
+ }
9687
+ if (state.window.length > period) {
9688
+ const oldValue = state.window.pop();
9689
+ state.sum -= oldValue;
9690
+ }
9691
+ const sma2 = state.sum / period;
9692
+ return this.context.precision(sma2);
9693
+ }
9694
+ vwma(source, _period, _callId) {
9615
9695
  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]);
9696
+ if (!this.context.taState) this.context.taState = {};
9697
+ const stateKey = _callId || `vwma_${period}`;
9698
+ if (!this.context.taState[stateKey]) {
9699
+ this.context.taState[stateKey] = { window: [], volumeWindow: [] };
9700
+ }
9701
+ const state = this.context.taState[stateKey];
9702
+ const currentValue = source[0];
9703
+ const currentVolume = this.context.data.volume[0];
9704
+ state.window.unshift(currentValue);
9705
+ state.volumeWindow.unshift(currentVolume);
9706
+ if (state.window.length < period) {
9707
+ return NaN;
9708
+ }
9709
+ if (state.window.length > period) {
9710
+ state.window.pop();
9711
+ state.volumeWindow.pop();
9712
+ }
9713
+ let sumVolPrice = 0;
9714
+ let sumVol = 0;
9715
+ for (let i = 0; i < period; i++) {
9716
+ sumVolPrice += state.window[i] * state.volumeWindow[i];
9717
+ sumVol += state.volumeWindow[i];
9718
+ }
9719
+ const vwma2 = sumVolPrice / sumVol;
9720
+ return this.context.precision(vwma2);
9620
9721
  }
9621
- wma(source, _period) {
9722
+ wma(source, _period, _callId) {
9622
9723
  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]);
9724
+ if (!this.context.taState) this.context.taState = {};
9725
+ const stateKey = _callId || `wma_${period}`;
9726
+ if (!this.context.taState[stateKey]) {
9727
+ this.context.taState[stateKey] = { window: [] };
9728
+ }
9729
+ const state = this.context.taState[stateKey];
9730
+ const currentValue = source[0];
9731
+ state.window.unshift(currentValue);
9732
+ if (state.window.length < period) {
9733
+ return NaN;
9734
+ }
9735
+ if (state.window.length > period) {
9736
+ state.window.pop();
9737
+ }
9738
+ let numerator = 0;
9739
+ let denominator = 0;
9740
+ for (let i = 0; i < period; i++) {
9741
+ const weight = period - i;
9742
+ numerator += state.window[i] * weight;
9743
+ denominator += weight;
9744
+ }
9745
+ const wma2 = numerator / denominator;
9746
+ return this.context.precision(wma2);
9626
9747
  }
9627
- hma(source, _period) {
9748
+ hma(source, _period, _callId) {
9628
9749
  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]);
9750
+ const halfPeriod = Math.floor(period / 2);
9751
+ const sqrtPeriod = Math.floor(Math.sqrt(period));
9752
+ const wma1 = this.wma(source, halfPeriod, _callId ? `${_callId}_wma1` : void 0);
9753
+ const wma2 = this.wma(source, period, _callId ? `${_callId}_wma2` : void 0);
9754
+ if (isNaN(wma1) || isNaN(wma2)) {
9755
+ return NaN;
9756
+ }
9757
+ if (!this.context.taState) this.context.taState = {};
9758
+ const stateKey = _callId || `hma_raw_${period}`;
9759
+ if (!this.context.taState[stateKey]) {
9760
+ this.context.taState[stateKey] = [];
9761
+ }
9762
+ const rawHma = 2 * wma1 - wma2;
9763
+ this.context.taState[stateKey].unshift(rawHma);
9764
+ const hmaStateKey = _callId ? `${_callId}_hma_final` : `hma_final_${period}`;
9765
+ if (!this.context.taState[hmaStateKey]) {
9766
+ this.context.taState[hmaStateKey] = { window: [] };
9767
+ }
9768
+ const state = this.context.taState[hmaStateKey];
9769
+ state.window.unshift(rawHma);
9770
+ if (state.window.length < sqrtPeriod) {
9771
+ return NaN;
9772
+ }
9773
+ if (state.window.length > sqrtPeriod) {
9774
+ state.window.pop();
9775
+ }
9776
+ let numerator = 0;
9777
+ let denominator = 0;
9778
+ for (let i = 0; i < sqrtPeriod; i++) {
9779
+ const weight = sqrtPeriod - i;
9780
+ numerator += state.window[i] * weight;
9781
+ denominator += weight;
9782
+ }
9783
+ const hma2 = numerator / denominator;
9784
+ return this.context.precision(hma2);
9632
9785
  }
9633
- rma(source, _period) {
9786
+ rma(source, _period, _callId) {
9634
9787
  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) {
9788
+ if (!this.context.taState) this.context.taState = {};
9789
+ const stateKey = _callId || `rma_${period}`;
9790
+ if (!this.context.taState[stateKey]) {
9791
+ this.context.taState[stateKey] = { prevRma: null, initSum: 0, initCount: 0 };
9792
+ }
9793
+ const state = this.context.taState[stateKey];
9794
+ const currentValue = source[0] || 0;
9795
+ if (state.initCount < period) {
9796
+ state.initSum += currentValue;
9797
+ state.initCount++;
9798
+ if (state.initCount === period) {
9799
+ state.prevRma = state.initSum / period;
9800
+ return this.context.precision(state.prevRma);
9801
+ }
9802
+ return NaN;
9803
+ }
9804
+ const alpha = 1 / period;
9805
+ const rma2 = currentValue * alpha + state.prevRma * (1 - alpha);
9806
+ state.prevRma = rma2;
9807
+ return this.context.precision(rma2);
9808
+ }
9809
+ change(source, _length = 1, _callId) {
9640
9810
  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]);
9811
+ if (!this.context.taState) this.context.taState = {};
9812
+ const stateKey = _callId || `change_${length}`;
9813
+ if (!this.context.taState[stateKey]) {
9814
+ this.context.taState[stateKey] = { window: [] };
9815
+ }
9816
+ const state = this.context.taState[stateKey];
9817
+ const currentValue = source[0];
9818
+ state.window.unshift(currentValue);
9819
+ if (state.window.length <= length) {
9820
+ return NaN;
9821
+ }
9822
+ if (state.window.length > length + 1) {
9823
+ state.window.pop();
9824
+ }
9825
+ const change2 = currentValue - state.window[length];
9826
+ return this.context.precision(change2);
9644
9827
  }
9645
- rsi(source, _period) {
9828
+ rsi(source, _period, _callId) {
9646
9829
  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]);
9830
+ if (!this.context.taState) this.context.taState = {};
9831
+ const stateKey = _callId || `rsi_${period}`;
9832
+ if (!this.context.taState[stateKey]) {
9833
+ this.context.taState[stateKey] = {
9834
+ prevValue: null,
9835
+ avgGain: 0,
9836
+ avgLoss: 0,
9837
+ initGains: [],
9838
+ initLosses: []
9839
+ };
9840
+ }
9841
+ const state = this.context.taState[stateKey];
9842
+ const currentValue = source[0];
9843
+ if (state.prevValue !== null) {
9844
+ const diff = currentValue - state.prevValue;
9845
+ const gain = diff > 0 ? diff : 0;
9846
+ const loss = diff < 0 ? -diff : 0;
9847
+ if (state.initGains.length < period) {
9848
+ state.initGains.push(gain);
9849
+ state.initLosses.push(loss);
9850
+ if (state.initGains.length === period) {
9851
+ state.avgGain = state.initGains.reduce((a, b) => a + b, 0) / period;
9852
+ state.avgLoss = state.initLosses.reduce((a, b) => a + b, 0) / period;
9853
+ state.prevValue = currentValue;
9854
+ const rsi3 = state.avgLoss === 0 ? 100 : 100 - 100 / (1 + state.avgGain / state.avgLoss);
9855
+ return this.context.precision(rsi3);
9856
+ }
9857
+ state.prevValue = currentValue;
9858
+ return NaN;
9859
+ }
9860
+ state.avgGain = (state.avgGain * (period - 1) + gain) / period;
9861
+ state.avgLoss = (state.avgLoss * (period - 1) + loss) / period;
9862
+ const rsi2 = state.avgLoss === 0 ? 100 : 100 - 100 / (1 + state.avgGain / state.avgLoss);
9863
+ state.prevValue = currentValue;
9864
+ return this.context.precision(rsi2);
9865
+ }
9866
+ state.prevValue = currentValue;
9867
+ return NaN;
9650
9868
  }
9651
- atr(_period) {
9869
+ atr(_period, _callId) {
9652
9870
  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]);
9871
+ if (!this.context.taState) this.context.taState = {};
9872
+ const stateKey = _callId || `atr_${period}`;
9873
+ if (!this.context.taState[stateKey]) {
9874
+ this.context.taState[stateKey] = {
9875
+ prevAtr: null,
9876
+ initSum: 0,
9877
+ initCount: 0,
9878
+ prevClose: null
9879
+ };
9880
+ }
9881
+ const state = this.context.taState[stateKey];
9882
+ const high = this.context.data.high[0];
9883
+ const low = this.context.data.low[0];
9884
+ const close = this.context.data.close[0];
9885
+ let tr;
9886
+ if (state.prevClose !== null) {
9887
+ const hl = high - low;
9888
+ const hc = Math.abs(high - state.prevClose);
9889
+ const lc = Math.abs(low - state.prevClose);
9890
+ tr = Math.max(hl, hc, lc);
9891
+ } else {
9892
+ tr = high - low;
9893
+ }
9894
+ state.prevClose = close;
9895
+ if (state.initCount < period) {
9896
+ state.initSum += tr;
9897
+ state.initCount++;
9898
+ if (state.initCount === period) {
9899
+ state.prevAtr = state.initSum / period;
9900
+ return this.context.precision(state.prevAtr);
9901
+ }
9902
+ return NaN;
9903
+ }
9904
+ const atr2 = (state.prevAtr * (period - 1) + tr) / period;
9905
+ state.prevAtr = atr2;
9906
+ return this.context.precision(atr2);
9659
9907
  }
9660
- mom(source, _length) {
9908
+ mom(source, _length, _callId) {
9661
9909
  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]);
9910
+ return this.change(source, length);
9665
9911
  }
9666
- roc(source, _length) {
9912
+ roc(source, _length, _callId) {
9667
9913
  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]);
9914
+ if (!this.context.taState) this.context.taState = {};
9915
+ const stateKey = _callId || `roc_${length}`;
9916
+ if (!this.context.taState[stateKey]) {
9917
+ this.context.taState[stateKey] = { window: [] };
9918
+ }
9919
+ const state = this.context.taState[stateKey];
9920
+ const currentValue = source[0];
9921
+ state.window.unshift(currentValue);
9922
+ if (state.window.length <= length) {
9923
+ return NaN;
9924
+ }
9925
+ if (state.window.length > length + 1) {
9926
+ state.window.pop();
9927
+ }
9928
+ const prevValue = state.window[length];
9929
+ const roc2 = (currentValue - prevValue) / prevValue * 100;
9930
+ return this.context.precision(roc2);
9671
9931
  }
9672
- dev(source, _length) {
9932
+ dev(source, _length, _callId) {
9673
9933
  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]);
9934
+ if (!this.context.taState) this.context.taState = {};
9935
+ const stateKey = _callId || `dev_${length}`;
9936
+ if (!this.context.taState[stateKey]) {
9937
+ this.context.taState[stateKey] = { window: [], sum: 0 };
9938
+ }
9939
+ const state = this.context.taState[stateKey];
9940
+ const currentValue = source[0] || 0;
9941
+ state.window.unshift(currentValue);
9942
+ state.sum += currentValue;
9943
+ if (state.window.length < length) {
9944
+ return NaN;
9945
+ }
9946
+ if (state.window.length > length) {
9947
+ const oldValue = state.window.pop();
9948
+ state.sum -= oldValue;
9949
+ }
9950
+ const mean = state.sum / length;
9951
+ let sumDeviation = 0;
9952
+ for (let i = 0; i < length; i++) {
9953
+ sumDeviation += Math.abs(state.window[i] - mean);
9954
+ }
9955
+ const dev2 = sumDeviation / length;
9956
+ return this.context.precision(dev2);
9677
9957
  }
9678
- variance(source, _length) {
9958
+ variance(source, _length, _callId) {
9679
9959
  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]);
9960
+ if (!this.context.taState) this.context.taState = {};
9961
+ const stateKey = _callId || `variance_${length}`;
9962
+ if (!this.context.taState[stateKey]) {
9963
+ this.context.taState[stateKey] = { window: [] };
9964
+ }
9965
+ const state = this.context.taState[stateKey];
9966
+ const currentValue = source[0];
9967
+ state.window.unshift(currentValue);
9968
+ if (state.window.length < length) {
9969
+ return NaN;
9970
+ }
9971
+ if (state.window.length > length) {
9972
+ state.window.pop();
9973
+ }
9974
+ let sum = 0;
9975
+ let sumSquares = 0;
9976
+ for (let i = 0; i < length; i++) {
9977
+ sum += state.window[i];
9978
+ sumSquares += state.window[i] * state.window[i];
9979
+ }
9980
+ const mean = sum / length;
9981
+ const variance2 = sumSquares / length - mean * mean;
9982
+ return this.context.precision(variance2);
9683
9983
  }
9684
- highest(source, _length) {
9984
+ highest(source, _length, _callId) {
9685
9985
  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]);
9986
+ if (!this.context.taState) this.context.taState = {};
9987
+ const stateKey = _callId || `highest_${length}`;
9988
+ if (!this.context.taState[stateKey]) {
9989
+ this.context.taState[stateKey] = { window: [] };
9990
+ }
9991
+ const state = this.context.taState[stateKey];
9992
+ const currentValue = source[0];
9993
+ state.window.unshift(currentValue);
9994
+ if (state.window.length < length) {
9995
+ return NaN;
9996
+ }
9997
+ if (state.window.length > length) {
9998
+ state.window.pop();
9999
+ }
10000
+ const max = Math.max(...state.window.filter((v) => !isNaN(v)));
10001
+ return this.context.precision(max);
9689
10002
  }
9690
- lowest(source, _length) {
10003
+ lowest(source, _length, _callId) {
9691
10004
  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]);
10005
+ if (!this.context.taState) this.context.taState = {};
10006
+ const stateKey = _callId || `lowest_${length}`;
10007
+ if (!this.context.taState[stateKey]) {
10008
+ this.context.taState[stateKey] = { window: [] };
10009
+ }
10010
+ const state = this.context.taState[stateKey];
10011
+ const currentValue = source[0];
10012
+ state.window.unshift(currentValue);
10013
+ if (state.window.length < length) {
10014
+ return NaN;
10015
+ }
10016
+ if (state.window.length > length) {
10017
+ state.window.pop();
10018
+ }
10019
+ const validValues = state.window.filter((v) => !isNaN(v) && v !== void 0);
10020
+ const min = validValues.length > 0 ? Math.min(...validValues) : NaN;
10021
+ return this.context.precision(min);
9695
10022
  }
9696
- median(source, _length) {
10023
+ median(source, _length, _callId) {
9697
10024
  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]);
10025
+ if (!this.context.taState) this.context.taState = {};
10026
+ const stateKey = _callId || `median_${length}`;
10027
+ if (!this.context.taState[stateKey]) {
10028
+ this.context.taState[stateKey] = { window: [] };
10029
+ }
10030
+ const state = this.context.taState[stateKey];
10031
+ const currentValue = source[0];
10032
+ state.window.unshift(currentValue);
10033
+ if (state.window.length < length) {
10034
+ return NaN;
10035
+ }
10036
+ if (state.window.length > length) {
10037
+ state.window.pop();
10038
+ }
10039
+ const sorted = state.window.slice().sort((a, b) => a - b);
10040
+ const mid = Math.floor(length / 2);
10041
+ const median2 = length % 2 === 0 ? (sorted[mid - 1] + sorted[mid]) / 2 : sorted[mid];
10042
+ return this.context.precision(median2);
9701
10043
  }
9702
- stdev(source, _length, _bias = true) {
10044
+ stdev(source, _length, _bias = true, _callId) {
9703
10045
  const length = Array.isArray(_length) ? _length[0] : _length;
9704
10046
  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]);
10047
+ if (!this.context.taState) this.context.taState = {};
10048
+ const stateKey = _callId || `stdev_${length}_${bias}`;
10049
+ if (!this.context.taState[stateKey]) {
10050
+ this.context.taState[stateKey] = { window: [], sum: 0 };
10051
+ }
10052
+ const state = this.context.taState[stateKey];
10053
+ const currentValue = source[0];
10054
+ state.window.unshift(currentValue);
10055
+ state.sum += currentValue;
10056
+ if (state.window.length < length) {
10057
+ return NaN;
10058
+ }
10059
+ if (state.window.length > length) {
10060
+ const oldValue = state.window.pop();
10061
+ state.sum -= oldValue;
10062
+ }
10063
+ const mean = state.sum / length;
10064
+ let sumSquaredDiff = 0;
10065
+ for (let i = 0; i < length; i++) {
10066
+ sumSquaredDiff += Math.pow(state.window[i] - mean, 2);
10067
+ }
10068
+ const divisor = bias ? length : length - 1;
10069
+ const stdev2 = Math.sqrt(sumSquaredDiff / divisor);
10070
+ return this.context.precision(stdev2);
9708
10071
  }
9709
- linreg(source, _length, _offset) {
10072
+ linreg(source, _length, _offset, _callId) {
9710
10073
  const length = Array.isArray(_length) ? _length[0] : _length;
9711
10074
  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]);
10075
+ if (!this.context.taState) this.context.taState = {};
10076
+ const stateKey = _callId || `linreg_${length}_${offset}`;
10077
+ if (!this.context.taState[stateKey]) {
10078
+ this.context.taState[stateKey] = { window: [] };
10079
+ }
10080
+ const state = this.context.taState[stateKey];
10081
+ const currentValue = source[0];
10082
+ state.window.unshift(currentValue);
10083
+ if (state.window.length < length) {
10084
+ return NaN;
10085
+ }
10086
+ if (state.window.length > length) {
10087
+ state.window.pop();
10088
+ }
10089
+ let sumX = 0;
10090
+ let sumY = 0;
10091
+ let sumXY = 0;
10092
+ let sumXX = 0;
10093
+ const n = length;
10094
+ for (let j = 0; j < length; j++) {
10095
+ const x = length - 1 - j;
10096
+ const y = state.window[j];
10097
+ sumX += x;
10098
+ sumY += y;
10099
+ sumXY += x * y;
10100
+ sumXX += x * x;
10101
+ }
10102
+ const denominator = n * sumXX - sumX * sumX;
10103
+ if (denominator === 0) {
10104
+ return NaN;
10105
+ }
10106
+ const slope = (n * sumXY - sumX * sumY) / denominator;
10107
+ const intercept = (sumY - slope * sumX) / n;
10108
+ const linRegValue = intercept + slope * (length - 1 - offset);
10109
+ return this.context.precision(linRegValue);
9715
10110
  }
9716
- supertrend(_factor, _atrPeriod) {
10111
+ supertrend(_factor, _atrPeriod, _callId) {
9717
10112
  const factor = Array.isArray(_factor) ? _factor[0] : _factor;
9718
10113
  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]]];
10114
+ if (!this.context.taState) this.context.taState = {};
10115
+ const stateKey = `supertrend_${factor}_${atrPeriod}`;
10116
+ if (!this.context.taState[stateKey]) {
10117
+ this.context.taState[stateKey] = {
10118
+ prevUpperBand: null,
10119
+ prevLowerBand: null,
10120
+ prevSupertrend: null,
10121
+ prevDirection: null
10122
+ };
10123
+ }
10124
+ const state = this.context.taState[stateKey];
10125
+ const high = this.context.data.high[0];
10126
+ const low = this.context.data.low[0];
10127
+ const close = this.context.data.close[0];
10128
+ const atrValue = this.atr(atrPeriod, _callId ? `${_callId}_atr` : void 0);
10129
+ if (isNaN(atrValue)) {
10130
+ return [[NaN, 0]];
10131
+ }
10132
+ const hl2 = (high + low) / 2;
10133
+ let upperBand = hl2 + factor * atrValue;
10134
+ let lowerBand = hl2 - factor * atrValue;
10135
+ if (state.prevUpperBand !== null) {
10136
+ if (upperBand < state.prevUpperBand || this.context.data.close[1] > state.prevUpperBand) {
10137
+ upperBand = upperBand;
10138
+ } else {
10139
+ upperBand = state.prevUpperBand;
10140
+ }
10141
+ if (lowerBand > state.prevLowerBand || this.context.data.close[1] < state.prevLowerBand) {
10142
+ lowerBand = lowerBand;
10143
+ } else {
10144
+ lowerBand = state.prevLowerBand;
10145
+ }
10146
+ }
10147
+ let direction;
10148
+ let supertrend;
10149
+ if (state.prevSupertrend === null) {
10150
+ direction = close <= upperBand ? -1 : 1;
10151
+ supertrend = direction === -1 ? upperBand : lowerBand;
10152
+ } else {
10153
+ if (state.prevSupertrend === state.prevUpperBand) {
10154
+ if (close > upperBand) {
10155
+ direction = 1;
10156
+ supertrend = lowerBand;
10157
+ } else {
10158
+ direction = -1;
10159
+ supertrend = upperBand;
10160
+ }
10161
+ } else {
10162
+ if (close < lowerBand) {
10163
+ direction = -1;
10164
+ supertrend = upperBand;
10165
+ } else {
10166
+ direction = 1;
10167
+ supertrend = lowerBand;
10168
+ }
10169
+ }
10170
+ }
10171
+ state.prevUpperBand = upperBand;
10172
+ state.prevLowerBand = lowerBand;
10173
+ state.prevSupertrend = supertrend;
10174
+ state.prevDirection = direction;
10175
+ return [[this.context.precision(supertrend), direction]];
9725
10176
  }
9726
10177
  crossover(source1, source2) {
9727
10178
  const current1 = Array.isArray(source1) ? source1[0] : source1;
@@ -9762,344 +10213,6 @@ class TechnicalAnalysis {
9762
10213
  return this.context.precision(result[idx]);
9763
10214
  }
9764
10215
  }
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
10216
  function pivothigh(source, leftbars, rightbars) {
10104
10217
  const result = new Array(source.length).fill(NaN);
10105
10218
  for (let i = leftbars + rightbars; i < source.length; i++) {
@@ -10352,6 +10465,8 @@ class Context {
10352
10465
  ohlc4: []
10353
10466
  };
10354
10467
  cache = {};
10468
+ taState = {};
10469
+ // State for incremental TA calculations
10355
10470
  useTACache = false;
10356
10471
  NA = NaN;
10357
10472
  math;
@@ -10593,7 +10708,6 @@ class BinanceProvider {
10593
10708
  if (data.length === 0) break;
10594
10709
  allData = allData.concat(data);
10595
10710
  currentStart = data[data.length - 1].closeTime + 1;
10596
- if (data.length < 1e3) break;
10597
10711
  }
10598
10712
  return allData;
10599
10713
  } catch (error) {
@@ -10601,8 +10715,6 @@ class BinanceProvider {
10601
10715
  return [];
10602
10716
  }
10603
10717
  }
10604
- //TODO : allow querying more than 1000 klines
10605
- //TODO : immplement cache
10606
10718
  async getMarketData(tickerId, timeframe, limit, sDate, eDate) {
10607
10719
  try {
10608
10720
  const cacheParams = { tickerId, timeframe, limit, sDate, eDate };
@@ -10616,12 +10728,16 @@ class BinanceProvider {
10616
10728
  console.error(`Unsupported timeframe: ${timeframe}`);
10617
10729
  return [];
10618
10730
  }
10619
- let url = `${BINANCE_API_URL}/klines?symbol=${tickerId}&interval=${interval}`;
10620
- if (!limit && sDate && eDate) {
10621
- return this.getMarketDataInterval(tickerId, timeframe, sDate, eDate);
10731
+ const needsPagination = this.shouldPaginate(timeframe, limit, sDate, eDate);
10732
+ if (needsPagination && sDate && eDate) {
10733
+ const allData = await this.getMarketDataInterval(tickerId, timeframe, sDate, eDate);
10734
+ const result2 = limit ? allData.slice(0, limit) : allData;
10735
+ this.cacheManager.set(cacheParams, result2);
10736
+ return result2;
10622
10737
  }
10738
+ let url = `${BINANCE_API_URL}/klines?symbol=${tickerId}&interval=${interval}`;
10623
10739
  if (limit) {
10624
- url += `&limit=${limit}`;
10740
+ url += `&limit=${Math.min(limit, 1e3)}`;
10625
10741
  }
10626
10742
  if (sDate) {
10627
10743
  url += `&startTime=${sDate}`;
@@ -10657,6 +10773,36 @@ class BinanceProvider {
10657
10773
  return [];
10658
10774
  }
10659
10775
  }
10776
+ /**
10777
+ * Determines if pagination is needed based on the parameters
10778
+ */
10779
+ shouldPaginate(timeframe, limit, sDate, eDate) {
10780
+ if (limit && limit > 1e3) {
10781
+ return true;
10782
+ }
10783
+ if (sDate && eDate) {
10784
+ const interval = timeframe_to_binance[timeframe.toUpperCase()];
10785
+ const timeframeDurations = {
10786
+ "1m": 60 * 1e3,
10787
+ "3m": 3 * 60 * 1e3,
10788
+ "5m": 5 * 60 * 1e3,
10789
+ "15m": 15 * 60 * 1e3,
10790
+ "30m": 30 * 60 * 1e3,
10791
+ "1h": 60 * 60 * 1e3,
10792
+ "2h": 2 * 60 * 60 * 1e3,
10793
+ "4h": 4 * 60 * 60 * 1e3,
10794
+ "1d": 24 * 60 * 60 * 1e3,
10795
+ "1w": 7 * 24 * 60 * 60 * 1e3,
10796
+ "1M": 30 * 24 * 60 * 60 * 1e3
10797
+ };
10798
+ const intervalDuration = timeframeDurations[interval];
10799
+ if (intervalDuration) {
10800
+ const requiredCandles = Math.ceil((eDate - sDate) / intervalDuration);
10801
+ return requiredCandles > 1e3;
10802
+ }
10803
+ }
10804
+ return false;
10805
+ }
10660
10806
  }
10661
10807
 
10662
10808
  const Provider = {