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.
@@ -37,6 +37,7 @@ class ScopeManager {
37
37
  __publicField$7(this, "paramIdCounter", 0);
38
38
  __publicField$7(this, "cacheIdCounter", 0);
39
39
  __publicField$7(this, "tempVarCounter", 0);
40
+ __publicField$7(this, "taCallIdCounter", 0);
40
41
  this.pushScope("glb");
41
42
  }
42
43
  get nextParamIdArg() {
@@ -51,6 +52,12 @@ class ScopeManager {
51
52
  name: `'cache_${this.cacheIdCounter++}'`
52
53
  };
53
54
  }
55
+ getNextTACallId() {
56
+ return {
57
+ type: "Literal",
58
+ value: `_ta${this.taCallIdCounter++}`
59
+ };
60
+ }
54
61
  pushScope(type) {
55
62
  this.scopes.push(/* @__PURE__ */ new Map());
56
63
  this.scopeTypes.push(type);
@@ -72,6 +79,14 @@ class ScopeManager {
72
79
  this.rootParams.add(name);
73
80
  }
74
81
  }
82
+ removeContextBoundVar(name) {
83
+ if (this.contextBoundVars.has(name)) {
84
+ this.contextBoundVars.delete(name);
85
+ if (this.rootParams.has(name)) {
86
+ this.rootParams.delete(name);
87
+ }
88
+ }
89
+ }
75
90
  addArrayPatternElement(name) {
76
91
  this.arrayPatternElements.add(name);
77
92
  }
@@ -718,38 +733,46 @@ function transformReturnStatement(node, scopeManager) {
718
733
  return prop;
719
734
  });
720
735
  } else if (node.argument.type === "Identifier") {
721
- const [scopedName, kind] = scopeManager.getVariable(node.argument.name);
722
- node.argument = {
723
- type: "MemberExpression",
724
- object: {
736
+ transformIdentifier(node.argument, scopeManager);
737
+ if (node.argument.type === "Identifier") {
738
+ addArrayAccess(node.argument);
739
+ }
740
+ }
741
+ if (curScope === "fn") {
742
+ if (node.argument.type === "Identifier" && scopeManager.isContextBound(node.argument.name) && !scopeManager.isRootParam(node.argument.name)) {
743
+ node.argument = {
725
744
  type: "MemberExpression",
726
- object: {
727
- type: "Identifier",
728
- name: CONTEXT_NAME
729
- },
745
+ object: node.argument,
730
746
  property: {
731
- type: "Identifier",
732
- name: kind
747
+ type: "Literal",
748
+ value: 0
733
749
  },
734
- computed: false
735
- },
736
- property: {
737
- type: "Identifier",
738
- name: scopedName
739
- },
740
- computed: false
741
- };
742
- node.argument = {
743
- type: "MemberExpression",
744
- object: node.argument,
745
- property: {
746
- type: "Literal",
747
- value: 0
748
- },
749
- computed: true
750
- };
751
- }
752
- if (curScope === "fn") {
750
+ computed: true
751
+ };
752
+ } else if (node.argument.type === "MemberExpression") {
753
+ if (node.argument.object.type === "Identifier" && scopeManager.isContextBound(node.argument.object.name) && !scopeManager.isRootParam(node.argument.object.name)) {
754
+ if (!node.argument._indexTransformed) {
755
+ transformArrayIndex(node.argument, scopeManager);
756
+ node.argument._indexTransformed = true;
757
+ }
758
+ }
759
+ } else if (node.argument.type === "BinaryExpression" || node.argument.type === "LogicalExpression" || node.argument.type === "ConditionalExpression" || node.argument.type === "CallExpression") {
760
+ walk.recursive(node.argument, scopeManager, {
761
+ Identifier(node2, state) {
762
+ transformIdentifier(node2, state);
763
+ if (node2.type === "Identifier" && !node2._arrayAccessed) {
764
+ addArrayAccess(node2);
765
+ node2._arrayAccessed = true;
766
+ }
767
+ },
768
+ MemberExpression(node2) {
769
+ transformMemberExpression(node2, "", scopeManager);
770
+ },
771
+ CallExpression(node2, state) {
772
+ transformCallExpression(node2, state);
773
+ }
774
+ });
775
+ }
753
776
  node.argument = {
754
777
  type: "CallExpression",
755
778
  callee: {
@@ -1100,6 +1123,9 @@ function transformCallExpression(node, scopeManager, namespace) {
1100
1123
  }
1101
1124
  return transformFunctionArgument(arg, namespace2, scopeManager);
1102
1125
  });
1126
+ if (namespace2 === "ta") {
1127
+ node.arguments.push(scopeManager.getNextTACallId());
1128
+ }
1103
1129
  node._transformed = true;
1104
1130
  } else if (node.callee && node.callee.type === "Identifier") {
1105
1131
  node.arguments = node.arguments.map((arg) => {
@@ -1140,9 +1166,11 @@ function transformCallExpression(node, scopeManager, namespace) {
1140
1166
  });
1141
1167
  }
1142
1168
  function transformFunctionDeclaration(node, scopeManager) {
1169
+ const boundParamNames = [];
1143
1170
  node.params.forEach((param) => {
1144
1171
  if (param.type === "Identifier") {
1145
1172
  scopeManager.addContextBoundVar(param.name, false);
1173
+ boundParamNames.push(param.name);
1146
1174
  }
1147
1175
  });
1148
1176
  if (node.body && node.body.type === "BlockStatement") {
@@ -1490,14 +1518,43 @@ function transpile(fn) {
1490
1518
  transformIfStatement(node, state, c);
1491
1519
  }
1492
1520
  });
1521
+ transformEqualityChecks(ast);
1493
1522
  const transformedCode = astring.generate(ast);
1494
1523
  const _wraperFunction = new Function("", `return ${transformedCode}`);
1495
1524
  return _wraperFunction(this);
1496
1525
  }
1526
+ function transformEqualityChecks(ast) {
1527
+ walk.simple(ast, {
1528
+ BinaryExpression(node) {
1529
+ if (node.operator === "==" || node.operator === "===") {
1530
+ const leftOperand = node.left;
1531
+ const rightOperand = node.right;
1532
+ Object.assign(node, {
1533
+ type: "CallExpression",
1534
+ callee: {
1535
+ type: "MemberExpression",
1536
+ object: {
1537
+ type: "Identifier",
1538
+ name: "$.math"
1539
+ },
1540
+ property: {
1541
+ type: "Identifier",
1542
+ name: "__eq"
1543
+ },
1544
+ computed: false
1545
+ },
1546
+ arguments: [leftOperand, rightOperand],
1547
+ _transformed: true
1548
+ });
1549
+ }
1550
+ }
1551
+ });
1552
+ }
1497
1553
 
1498
1554
  var __defProp$6 = Object.defineProperty;
1499
1555
  var __defNormalProp$6 = (obj, key, value) => key in obj ? __defProp$6(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
1500
1556
  var __publicField$6 = (obj, key, value) => __defNormalProp$6(obj, typeof key !== "symbol" ? key + "" : key, value);
1557
+ const MAX_PERIODS = 5e3;
1501
1558
  class PineTS {
1502
1559
  constructor(source, tickerId, timeframe, limit, sDate, eDate) {
1503
1560
  this.source = source;
@@ -1527,7 +1584,7 @@ class PineTS {
1527
1584
  __publicField$6(this, "_ready", false);
1528
1585
  this._readyPromise = new Promise((resolve) => {
1529
1586
  this.loadMarketData(source, tickerId, timeframe, limit, sDate, eDate).then((data) => {
1530
- const marketData = data.reverse();
1587
+ const marketData = data.slice(0, MAX_PERIODS);
1531
1588
  this._periods = marketData.length;
1532
1589
  this.data = marketData;
1533
1590
  const _open = marketData.map((d) => d.open);
@@ -1586,19 +1643,28 @@ class PineTS {
1586
1643
  context.useTACache = useTACache;
1587
1644
  const transformer = transpile.bind(this);
1588
1645
  let transpiledFn = transformer(pineTSCode);
1646
+ context.data.close = [];
1647
+ context.data.open = [];
1648
+ context.data.high = [];
1649
+ context.data.low = [];
1650
+ context.data.volume = [];
1651
+ context.data.hl2 = [];
1652
+ context.data.hlc3 = [];
1653
+ context.data.ohlc4 = [];
1654
+ context.data.openTime = [];
1655
+ context.data.closeTime = [];
1589
1656
  const contextVarNames = ["const", "var", "let", "params"];
1590
- for (let i = this._periods - n, idx = n - 1; i < this._periods; i++, idx--) {
1657
+ for (let i = this._periods - n; i < this._periods; i++) {
1591
1658
  context.idx = i;
1592
- context.data.close = this.close.slice(idx);
1593
- context.data.open = this.open.slice(idx);
1594
- context.data.high = this.high.slice(idx);
1595
- context.data.low = this.low.slice(idx);
1596
- context.data.volume = this.volume.slice(idx);
1597
- context.data.hl2 = this.hl2.slice(idx);
1598
- context.data.hlc3 = this.hlc3.slice(idx);
1599
- context.data.ohlc4 = this.ohlc4.slice(idx);
1600
- context.data.openTime = this.openTime.slice(idx);
1601
- context.data.closeTime = this.closeTime.slice(idx);
1659
+ context.data.close.unshift(this.close[i]);
1660
+ context.data.open.unshift(this.open[i]);
1661
+ context.data.high.unshift(this.high[i]);
1662
+ context.data.low.unshift(this.low[i]);
1663
+ context.data.volume.unshift(this.volume[i]);
1664
+ context.data.hl2.unshift(this.hl2[i]);
1665
+ context.data.hlc3.unshift(this.hlc3[i]);
1666
+ context.data.ohlc4.unshift(this.ohlc4[i]);
1667
+ context.data.openTime.unshift(this.openTime[i]);
1602
1668
  const result = await transpiledFn(context);
1603
1669
  if (typeof result === "object") {
1604
1670
  if (typeof context.result !== "object") {
@@ -1785,6 +1851,9 @@ class PineMath {
1785
1851
  return this.context.params[name];
1786
1852
  }
1787
1853
  }
1854
+ __eq(a, b) {
1855
+ return Math.abs(a - b) < 1e-8;
1856
+ }
1788
1857
  abs(source) {
1789
1858
  return Math.abs(source[0]);
1790
1859
  }
@@ -1948,141 +2017,532 @@ class TechnicalAnalysis {
1948
2017
  return this.context.params[name];
1949
2018
  }
1950
2019
  }
1951
- ema(source, _period) {
1952
- const period = Array.isArray(_period) ? _period[0] : _period;
1953
- const result = ema(source.slice(0).reverse(), period);
1954
- const idx = this.context.idx;
1955
- return this.context.precision(result[idx]);
1956
- }
1957
- sma(source, _period, _cacheId) {
2020
+ ema(source, _period, _callId) {
1958
2021
  const period = Array.isArray(_period) ? _period[0] : _period;
1959
- const reversedSource = source.slice(0).reverse();
1960
- if (this.context.useTACache && _cacheId) {
1961
- if (!this.context.cache[_cacheId]) {
1962
- this.context.cache[_cacheId] = {};
1963
- }
1964
- const cacheObj = this.context.cache[_cacheId];
1965
- if (cacheObj) {
1966
- const result2 = sma_cache(reversedSource, period, cacheObj);
1967
- const idx2 = this.context.idx;
1968
- return this.context.precision(result2[idx2]);
2022
+ if (!this.context.taState) this.context.taState = {};
2023
+ const stateKey = _callId || `ema_${period}`;
2024
+ if (!this.context.taState[stateKey]) {
2025
+ this.context.taState[stateKey] = { prevEma: null, initSum: 0, initCount: 0 };
2026
+ }
2027
+ const state = this.context.taState[stateKey];
2028
+ const currentValue = source[0];
2029
+ if (state.initCount < period) {
2030
+ state.initSum += currentValue;
2031
+ state.initCount++;
2032
+ if (state.initCount === period) {
2033
+ state.prevEma = state.initSum / period;
2034
+ return this.context.precision(state.prevEma);
1969
2035
  }
2036
+ return NaN;
1970
2037
  }
1971
- const result = sma(reversedSource, period);
1972
- const idx = this.context.idx;
1973
- return this.context.precision(result[idx]);
2038
+ const alpha = 2 / (period + 1);
2039
+ const ema2 = currentValue * alpha + state.prevEma * (1 - alpha);
2040
+ state.prevEma = ema2;
2041
+ return this.context.precision(ema2);
1974
2042
  }
1975
- vwma(source, _period) {
2043
+ sma(source, _period, _callId) {
1976
2044
  const period = Array.isArray(_period) ? _period[0] : _period;
1977
- const volume = this.context.data.volume;
1978
- const result = vwma(source.slice(0).reverse(), volume.slice(0).reverse(), period);
1979
- const idx = this.context.idx;
1980
- return this.context.precision(result[idx]);
2045
+ if (!this.context.taState) this.context.taState = {};
2046
+ const stateKey = _callId || `sma_${period}`;
2047
+ if (!this.context.taState[stateKey]) {
2048
+ this.context.taState[stateKey] = { window: [], sum: 0 };
2049
+ }
2050
+ const state = this.context.taState[stateKey];
2051
+ const currentValue = source[0] || 0;
2052
+ state.window.unshift(currentValue);
2053
+ state.sum += currentValue;
2054
+ if (state.window.length < period) {
2055
+ return NaN;
2056
+ }
2057
+ if (state.window.length > period) {
2058
+ const oldValue = state.window.pop();
2059
+ state.sum -= oldValue;
2060
+ }
2061
+ const sma2 = state.sum / period;
2062
+ return this.context.precision(sma2);
2063
+ }
2064
+ vwma(source, _period, _callId) {
2065
+ const period = Array.isArray(_period) ? _period[0] : _period;
2066
+ if (!this.context.taState) this.context.taState = {};
2067
+ const stateKey = _callId || `vwma_${period}`;
2068
+ if (!this.context.taState[stateKey]) {
2069
+ this.context.taState[stateKey] = { window: [], volumeWindow: [] };
2070
+ }
2071
+ const state = this.context.taState[stateKey];
2072
+ const currentValue = source[0];
2073
+ const currentVolume = this.context.data.volume[0];
2074
+ state.window.unshift(currentValue);
2075
+ state.volumeWindow.unshift(currentVolume);
2076
+ if (state.window.length < period) {
2077
+ return NaN;
2078
+ }
2079
+ if (state.window.length > period) {
2080
+ state.window.pop();
2081
+ state.volumeWindow.pop();
2082
+ }
2083
+ let sumVolPrice = 0;
2084
+ let sumVol = 0;
2085
+ for (let i = 0; i < period; i++) {
2086
+ sumVolPrice += state.window[i] * state.volumeWindow[i];
2087
+ sumVol += state.volumeWindow[i];
2088
+ }
2089
+ const vwma2 = sumVolPrice / sumVol;
2090
+ return this.context.precision(vwma2);
1981
2091
  }
1982
- wma(source, _period) {
2092
+ wma(source, _period, _callId) {
1983
2093
  const period = Array.isArray(_period) ? _period[0] : _period;
1984
- const result = wma(source.slice(0).reverse(), period);
1985
- const idx = this.context.idx;
1986
- return this.context.precision(result[idx]);
2094
+ if (!this.context.taState) this.context.taState = {};
2095
+ const stateKey = _callId || `wma_${period}`;
2096
+ if (!this.context.taState[stateKey]) {
2097
+ this.context.taState[stateKey] = { window: [] };
2098
+ }
2099
+ const state = this.context.taState[stateKey];
2100
+ const currentValue = source[0];
2101
+ state.window.unshift(currentValue);
2102
+ if (state.window.length < period) {
2103
+ return NaN;
2104
+ }
2105
+ if (state.window.length > period) {
2106
+ state.window.pop();
2107
+ }
2108
+ let numerator = 0;
2109
+ let denominator = 0;
2110
+ for (let i = 0; i < period; i++) {
2111
+ const weight = period - i;
2112
+ numerator += state.window[i] * weight;
2113
+ denominator += weight;
2114
+ }
2115
+ const wma2 = numerator / denominator;
2116
+ return this.context.precision(wma2);
1987
2117
  }
1988
- hma(source, _period) {
2118
+ hma(source, _period, _callId) {
1989
2119
  const period = Array.isArray(_period) ? _period[0] : _period;
1990
- const result = hma(source.slice(0).reverse(), period);
1991
- const idx = this.context.idx;
1992
- return this.context.precision(result[idx]);
2120
+ const halfPeriod = Math.floor(period / 2);
2121
+ const sqrtPeriod = Math.floor(Math.sqrt(period));
2122
+ const wma1 = this.wma(source, halfPeriod, _callId ? `${_callId}_wma1` : void 0);
2123
+ const wma2 = this.wma(source, period, _callId ? `${_callId}_wma2` : void 0);
2124
+ if (isNaN(wma1) || isNaN(wma2)) {
2125
+ return NaN;
2126
+ }
2127
+ if (!this.context.taState) this.context.taState = {};
2128
+ const stateKey = _callId || `hma_raw_${period}`;
2129
+ if (!this.context.taState[stateKey]) {
2130
+ this.context.taState[stateKey] = [];
2131
+ }
2132
+ const rawHma = 2 * wma1 - wma2;
2133
+ this.context.taState[stateKey].unshift(rawHma);
2134
+ const hmaStateKey = _callId ? `${_callId}_hma_final` : `hma_final_${period}`;
2135
+ if (!this.context.taState[hmaStateKey]) {
2136
+ this.context.taState[hmaStateKey] = { window: [] };
2137
+ }
2138
+ const state = this.context.taState[hmaStateKey];
2139
+ state.window.unshift(rawHma);
2140
+ if (state.window.length < sqrtPeriod) {
2141
+ return NaN;
2142
+ }
2143
+ if (state.window.length > sqrtPeriod) {
2144
+ state.window.pop();
2145
+ }
2146
+ let numerator = 0;
2147
+ let denominator = 0;
2148
+ for (let i = 0; i < sqrtPeriod; i++) {
2149
+ const weight = sqrtPeriod - i;
2150
+ numerator += state.window[i] * weight;
2151
+ denominator += weight;
2152
+ }
2153
+ const hma2 = numerator / denominator;
2154
+ return this.context.precision(hma2);
1993
2155
  }
1994
- rma(source, _period) {
2156
+ rma(source, _period, _callId) {
1995
2157
  const period = Array.isArray(_period) ? _period[0] : _period;
1996
- const result = rma(source.slice(0).reverse(), period);
1997
- const idx = this.context.idx;
1998
- return this.context.precision(result[idx]);
2158
+ if (!this.context.taState) this.context.taState = {};
2159
+ const stateKey = _callId || `rma_${period}`;
2160
+ if (!this.context.taState[stateKey]) {
2161
+ this.context.taState[stateKey] = { prevRma: null, initSum: 0, initCount: 0 };
2162
+ }
2163
+ const state = this.context.taState[stateKey];
2164
+ const currentValue = source[0] || 0;
2165
+ if (state.initCount < period) {
2166
+ state.initSum += currentValue;
2167
+ state.initCount++;
2168
+ if (state.initCount === period) {
2169
+ state.prevRma = state.initSum / period;
2170
+ return this.context.precision(state.prevRma);
2171
+ }
2172
+ return NaN;
2173
+ }
2174
+ const alpha = 1 / period;
2175
+ const rma2 = currentValue * alpha + state.prevRma * (1 - alpha);
2176
+ state.prevRma = rma2;
2177
+ return this.context.precision(rma2);
1999
2178
  }
2000
- change(source, _length = 1) {
2179
+ change(source, _length = 1, _callId) {
2001
2180
  const length = Array.isArray(_length) ? _length[0] : _length;
2002
- const result = change(source.slice(0).reverse(), length);
2003
- const idx = this.context.idx;
2004
- return this.context.precision(result[idx]);
2181
+ if (!this.context.taState) this.context.taState = {};
2182
+ const stateKey = _callId || `change_${length}`;
2183
+ if (!this.context.taState[stateKey]) {
2184
+ this.context.taState[stateKey] = { window: [] };
2185
+ }
2186
+ const state = this.context.taState[stateKey];
2187
+ const currentValue = source[0];
2188
+ state.window.unshift(currentValue);
2189
+ if (state.window.length <= length) {
2190
+ return NaN;
2191
+ }
2192
+ if (state.window.length > length + 1) {
2193
+ state.window.pop();
2194
+ }
2195
+ const change2 = currentValue - state.window[length];
2196
+ return this.context.precision(change2);
2005
2197
  }
2006
- rsi(source, _period) {
2198
+ rsi(source, _period, _callId) {
2007
2199
  const period = Array.isArray(_period) ? _period[0] : _period;
2008
- const result = rsi(source.slice(0).reverse(), period);
2009
- const idx = this.context.idx;
2010
- return this.context.precision(result[idx]);
2200
+ if (!this.context.taState) this.context.taState = {};
2201
+ const stateKey = _callId || `rsi_${period}`;
2202
+ if (!this.context.taState[stateKey]) {
2203
+ this.context.taState[stateKey] = {
2204
+ prevValue: null,
2205
+ avgGain: 0,
2206
+ avgLoss: 0,
2207
+ initGains: [],
2208
+ initLosses: []
2209
+ };
2210
+ }
2211
+ const state = this.context.taState[stateKey];
2212
+ const currentValue = source[0];
2213
+ if (state.prevValue !== null) {
2214
+ const diff = currentValue - state.prevValue;
2215
+ const gain = diff > 0 ? diff : 0;
2216
+ const loss = diff < 0 ? -diff : 0;
2217
+ if (state.initGains.length < period) {
2218
+ state.initGains.push(gain);
2219
+ state.initLosses.push(loss);
2220
+ if (state.initGains.length === period) {
2221
+ state.avgGain = state.initGains.reduce((a, b) => a + b, 0) / period;
2222
+ state.avgLoss = state.initLosses.reduce((a, b) => a + b, 0) / period;
2223
+ state.prevValue = currentValue;
2224
+ const rsi3 = state.avgLoss === 0 ? 100 : 100 - 100 / (1 + state.avgGain / state.avgLoss);
2225
+ return this.context.precision(rsi3);
2226
+ }
2227
+ state.prevValue = currentValue;
2228
+ return NaN;
2229
+ }
2230
+ state.avgGain = (state.avgGain * (period - 1) + gain) / period;
2231
+ state.avgLoss = (state.avgLoss * (period - 1) + loss) / period;
2232
+ const rsi2 = state.avgLoss === 0 ? 100 : 100 - 100 / (1 + state.avgGain / state.avgLoss);
2233
+ state.prevValue = currentValue;
2234
+ return this.context.precision(rsi2);
2235
+ }
2236
+ state.prevValue = currentValue;
2237
+ return NaN;
2011
2238
  }
2012
- atr(_period) {
2239
+ atr(_period, _callId) {
2013
2240
  const period = Array.isArray(_period) ? _period[0] : _period;
2014
- const high = this.context.data.high.slice().reverse();
2015
- const low = this.context.data.low.slice().reverse();
2016
- const close = this.context.data.close.slice().reverse();
2017
- const result = atr(high, low, close, period);
2018
- const idx = this.context.idx;
2019
- return this.context.precision(result[idx]);
2241
+ if (!this.context.taState) this.context.taState = {};
2242
+ const stateKey = _callId || `atr_${period}`;
2243
+ if (!this.context.taState[stateKey]) {
2244
+ this.context.taState[stateKey] = {
2245
+ prevAtr: null,
2246
+ initSum: 0,
2247
+ initCount: 0,
2248
+ prevClose: null
2249
+ };
2250
+ }
2251
+ const state = this.context.taState[stateKey];
2252
+ const high = this.context.data.high[0];
2253
+ const low = this.context.data.low[0];
2254
+ const close = this.context.data.close[0];
2255
+ let tr;
2256
+ if (state.prevClose !== null) {
2257
+ const hl = high - low;
2258
+ const hc = Math.abs(high - state.prevClose);
2259
+ const lc = Math.abs(low - state.prevClose);
2260
+ tr = Math.max(hl, hc, lc);
2261
+ } else {
2262
+ tr = high - low;
2263
+ }
2264
+ state.prevClose = close;
2265
+ if (state.initCount < period) {
2266
+ state.initSum += tr;
2267
+ state.initCount++;
2268
+ if (state.initCount === period) {
2269
+ state.prevAtr = state.initSum / period;
2270
+ return this.context.precision(state.prevAtr);
2271
+ }
2272
+ return NaN;
2273
+ }
2274
+ const atr2 = (state.prevAtr * (period - 1) + tr) / period;
2275
+ state.prevAtr = atr2;
2276
+ return this.context.precision(atr2);
2020
2277
  }
2021
- mom(source, _length) {
2278
+ mom(source, _length, _callId) {
2022
2279
  const length = Array.isArray(_length) ? _length[0] : _length;
2023
- const result = mom(source.slice(0).reverse(), length);
2024
- const idx = this.context.idx;
2025
- return this.context.precision(result[idx]);
2280
+ return this.change(source, length);
2026
2281
  }
2027
- roc(source, _length) {
2282
+ roc(source, _length, _callId) {
2028
2283
  const length = Array.isArray(_length) ? _length[0] : _length;
2029
- const result = roc(source.slice(0).reverse(), length);
2030
- const idx = this.context.idx;
2031
- return this.context.precision(result[idx]);
2284
+ if (!this.context.taState) this.context.taState = {};
2285
+ const stateKey = _callId || `roc_${length}`;
2286
+ if (!this.context.taState[stateKey]) {
2287
+ this.context.taState[stateKey] = { window: [] };
2288
+ }
2289
+ const state = this.context.taState[stateKey];
2290
+ const currentValue = source[0];
2291
+ state.window.unshift(currentValue);
2292
+ if (state.window.length <= length) {
2293
+ return NaN;
2294
+ }
2295
+ if (state.window.length > length + 1) {
2296
+ state.window.pop();
2297
+ }
2298
+ const prevValue = state.window[length];
2299
+ const roc2 = (currentValue - prevValue) / prevValue * 100;
2300
+ return this.context.precision(roc2);
2032
2301
  }
2033
- dev(source, _length) {
2302
+ dev(source, _length, _callId) {
2034
2303
  const length = Array.isArray(_length) ? _length[0] : _length;
2035
- const result = dev(source.slice(0).reverse(), length);
2036
- const idx = this.context.idx;
2037
- return this.context.precision(result[idx]);
2304
+ if (!this.context.taState) this.context.taState = {};
2305
+ const stateKey = _callId || `dev_${length}`;
2306
+ if (!this.context.taState[stateKey]) {
2307
+ this.context.taState[stateKey] = { window: [], sum: 0 };
2308
+ }
2309
+ const state = this.context.taState[stateKey];
2310
+ const currentValue = source[0] || 0;
2311
+ state.window.unshift(currentValue);
2312
+ state.sum += currentValue;
2313
+ if (state.window.length < length) {
2314
+ return NaN;
2315
+ }
2316
+ if (state.window.length > length) {
2317
+ const oldValue = state.window.pop();
2318
+ state.sum -= oldValue;
2319
+ }
2320
+ const mean = state.sum / length;
2321
+ let sumDeviation = 0;
2322
+ for (let i = 0; i < length; i++) {
2323
+ sumDeviation += Math.abs(state.window[i] - mean);
2324
+ }
2325
+ const dev2 = sumDeviation / length;
2326
+ return this.context.precision(dev2);
2038
2327
  }
2039
- variance(source, _length) {
2328
+ variance(source, _length, _callId) {
2040
2329
  const length = Array.isArray(_length) ? _length[0] : _length;
2041
- const result = variance(source.slice(0).reverse(), length);
2042
- const idx = this.context.idx;
2043
- return this.context.precision(result[idx]);
2330
+ if (!this.context.taState) this.context.taState = {};
2331
+ const stateKey = _callId || `variance_${length}`;
2332
+ if (!this.context.taState[stateKey]) {
2333
+ this.context.taState[stateKey] = { window: [] };
2334
+ }
2335
+ const state = this.context.taState[stateKey];
2336
+ const currentValue = source[0];
2337
+ state.window.unshift(currentValue);
2338
+ if (state.window.length < length) {
2339
+ return NaN;
2340
+ }
2341
+ if (state.window.length > length) {
2342
+ state.window.pop();
2343
+ }
2344
+ let sum = 0;
2345
+ let sumSquares = 0;
2346
+ for (let i = 0; i < length; i++) {
2347
+ sum += state.window[i];
2348
+ sumSquares += state.window[i] * state.window[i];
2349
+ }
2350
+ const mean = sum / length;
2351
+ const variance2 = sumSquares / length - mean * mean;
2352
+ return this.context.precision(variance2);
2044
2353
  }
2045
- highest(source, _length) {
2354
+ highest(source, _length, _callId) {
2046
2355
  const length = Array.isArray(_length) ? _length[0] : _length;
2047
- const result = highest(source.slice(0).reverse(), length);
2048
- const idx = this.context.idx;
2049
- return this.context.precision(result[idx]);
2356
+ if (!this.context.taState) this.context.taState = {};
2357
+ const stateKey = _callId || `highest_${length}`;
2358
+ if (!this.context.taState[stateKey]) {
2359
+ this.context.taState[stateKey] = { window: [] };
2360
+ }
2361
+ const state = this.context.taState[stateKey];
2362
+ const currentValue = source[0];
2363
+ state.window.unshift(currentValue);
2364
+ if (state.window.length < length) {
2365
+ return NaN;
2366
+ }
2367
+ if (state.window.length > length) {
2368
+ state.window.pop();
2369
+ }
2370
+ const max = Math.max(...state.window.filter((v) => !isNaN(v)));
2371
+ return this.context.precision(max);
2050
2372
  }
2051
- lowest(source, _length) {
2373
+ lowest(source, _length, _callId) {
2052
2374
  const length = Array.isArray(_length) ? _length[0] : _length;
2053
- const result = lowest(source.slice(0).reverse(), length);
2054
- const idx = this.context.idx;
2055
- return this.context.precision(result[idx]);
2375
+ if (!this.context.taState) this.context.taState = {};
2376
+ const stateKey = _callId || `lowest_${length}`;
2377
+ if (!this.context.taState[stateKey]) {
2378
+ this.context.taState[stateKey] = { window: [] };
2379
+ }
2380
+ const state = this.context.taState[stateKey];
2381
+ const currentValue = source[0];
2382
+ state.window.unshift(currentValue);
2383
+ if (state.window.length < length) {
2384
+ return NaN;
2385
+ }
2386
+ if (state.window.length > length) {
2387
+ state.window.pop();
2388
+ }
2389
+ const validValues = state.window.filter((v) => !isNaN(v) && v !== void 0);
2390
+ const min = validValues.length > 0 ? Math.min(...validValues) : NaN;
2391
+ return this.context.precision(min);
2056
2392
  }
2057
- median(source, _length) {
2393
+ median(source, _length, _callId) {
2058
2394
  const length = Array.isArray(_length) ? _length[0] : _length;
2059
- const result = median(source.slice(0).reverse(), length);
2060
- const idx = this.context.idx;
2061
- return this.context.precision(result[idx]);
2395
+ if (!this.context.taState) this.context.taState = {};
2396
+ const stateKey = _callId || `median_${length}`;
2397
+ if (!this.context.taState[stateKey]) {
2398
+ this.context.taState[stateKey] = { window: [] };
2399
+ }
2400
+ const state = this.context.taState[stateKey];
2401
+ const currentValue = source[0];
2402
+ state.window.unshift(currentValue);
2403
+ if (state.window.length < length) {
2404
+ return NaN;
2405
+ }
2406
+ if (state.window.length > length) {
2407
+ state.window.pop();
2408
+ }
2409
+ const sorted = state.window.slice().sort((a, b) => a - b);
2410
+ const mid = Math.floor(length / 2);
2411
+ const median2 = length % 2 === 0 ? (sorted[mid - 1] + sorted[mid]) / 2 : sorted[mid];
2412
+ return this.context.precision(median2);
2062
2413
  }
2063
- stdev(source, _length, _bias = true) {
2414
+ stdev(source, _length, _bias = true, _callId) {
2064
2415
  const length = Array.isArray(_length) ? _length[0] : _length;
2065
2416
  const bias = Array.isArray(_bias) ? _bias[0] : _bias;
2066
- const result = stdev(source.slice(0).reverse(), length, bias);
2067
- const idx = this.context.idx;
2068
- return this.context.precision(result[idx]);
2069
- }
2070
- linreg(source, _length, _offset) {
2417
+ if (!this.context.taState) this.context.taState = {};
2418
+ const stateKey = _callId || `stdev_${length}_${bias}`;
2419
+ if (!this.context.taState[stateKey]) {
2420
+ this.context.taState[stateKey] = { window: [], sum: 0 };
2421
+ }
2422
+ const state = this.context.taState[stateKey];
2423
+ const currentValue = source[0];
2424
+ state.window.unshift(currentValue);
2425
+ state.sum += currentValue;
2426
+ if (state.window.length < length) {
2427
+ return NaN;
2428
+ }
2429
+ if (state.window.length > length) {
2430
+ const oldValue = state.window.pop();
2431
+ state.sum -= oldValue;
2432
+ }
2433
+ const mean = state.sum / length;
2434
+ let sumSquaredDiff = 0;
2435
+ for (let i = 0; i < length; i++) {
2436
+ sumSquaredDiff += Math.pow(state.window[i] - mean, 2);
2437
+ }
2438
+ const divisor = bias ? length : length - 1;
2439
+ const stdev2 = Math.sqrt(sumSquaredDiff / divisor);
2440
+ return this.context.precision(stdev2);
2441
+ }
2442
+ linreg(source, _length, _offset, _callId) {
2071
2443
  const length = Array.isArray(_length) ? _length[0] : _length;
2072
2444
  const offset = Array.isArray(_offset) ? _offset[0] : _offset;
2073
- const result = linreg(source.slice(0).reverse(), length, offset);
2074
- const idx = this.context.idx;
2075
- return this.context.precision(result[idx]);
2445
+ if (!this.context.taState) this.context.taState = {};
2446
+ const stateKey = _callId || `linreg_${length}_${offset}`;
2447
+ if (!this.context.taState[stateKey]) {
2448
+ this.context.taState[stateKey] = { window: [] };
2449
+ }
2450
+ const state = this.context.taState[stateKey];
2451
+ const currentValue = source[0];
2452
+ state.window.unshift(currentValue);
2453
+ if (state.window.length < length) {
2454
+ return NaN;
2455
+ }
2456
+ if (state.window.length > length) {
2457
+ state.window.pop();
2458
+ }
2459
+ let sumX = 0;
2460
+ let sumY = 0;
2461
+ let sumXY = 0;
2462
+ let sumXX = 0;
2463
+ const n = length;
2464
+ for (let j = 0; j < length; j++) {
2465
+ const x = length - 1 - j;
2466
+ const y = state.window[j];
2467
+ sumX += x;
2468
+ sumY += y;
2469
+ sumXY += x * y;
2470
+ sumXX += x * x;
2471
+ }
2472
+ const denominator = n * sumXX - sumX * sumX;
2473
+ if (denominator === 0) {
2474
+ return NaN;
2475
+ }
2476
+ const slope = (n * sumXY - sumX * sumY) / denominator;
2477
+ const intercept = (sumY - slope * sumX) / n;
2478
+ const linRegValue = intercept + slope * (length - 1 - offset);
2479
+ return this.context.precision(linRegValue);
2076
2480
  }
2077
- supertrend(_factor, _atrPeriod) {
2481
+ supertrend(_factor, _atrPeriod, _callId) {
2078
2482
  const factor = Array.isArray(_factor) ? _factor[0] : _factor;
2079
2483
  const atrPeriod = Array.isArray(_atrPeriod) ? _atrPeriod[0] : _atrPeriod;
2080
- const high = this.context.data.high.slice().reverse();
2081
- const low = this.context.data.low.slice().reverse();
2082
- const close = this.context.data.close.slice().reverse();
2083
- const [supertrend, direction] = calculateSupertrend(high, low, close, factor, atrPeriod);
2084
- const idx = this.context.idx;
2085
- return [[this.context.precision(supertrend[idx]), direction[idx]]];
2484
+ if (!this.context.taState) this.context.taState = {};
2485
+ const stateKey = `supertrend_${factor}_${atrPeriod}`;
2486
+ if (!this.context.taState[stateKey]) {
2487
+ this.context.taState[stateKey] = {
2488
+ prevUpperBand: null,
2489
+ prevLowerBand: null,
2490
+ prevSupertrend: null,
2491
+ prevDirection: null
2492
+ };
2493
+ }
2494
+ const state = this.context.taState[stateKey];
2495
+ const high = this.context.data.high[0];
2496
+ const low = this.context.data.low[0];
2497
+ const close = this.context.data.close[0];
2498
+ const atrValue = this.atr(atrPeriod, _callId ? `${_callId}_atr` : void 0);
2499
+ if (isNaN(atrValue)) {
2500
+ return [[NaN, 0]];
2501
+ }
2502
+ const hl2 = (high + low) / 2;
2503
+ let upperBand = hl2 + factor * atrValue;
2504
+ let lowerBand = hl2 - factor * atrValue;
2505
+ if (state.prevUpperBand !== null) {
2506
+ if (upperBand < state.prevUpperBand || this.context.data.close[1] > state.prevUpperBand) {
2507
+ upperBand = upperBand;
2508
+ } else {
2509
+ upperBand = state.prevUpperBand;
2510
+ }
2511
+ if (lowerBand > state.prevLowerBand || this.context.data.close[1] < state.prevLowerBand) {
2512
+ lowerBand = lowerBand;
2513
+ } else {
2514
+ lowerBand = state.prevLowerBand;
2515
+ }
2516
+ }
2517
+ let direction;
2518
+ let supertrend;
2519
+ if (state.prevSupertrend === null) {
2520
+ direction = close <= upperBand ? -1 : 1;
2521
+ supertrend = direction === -1 ? upperBand : lowerBand;
2522
+ } else {
2523
+ if (state.prevSupertrend === state.prevUpperBand) {
2524
+ if (close > upperBand) {
2525
+ direction = 1;
2526
+ supertrend = lowerBand;
2527
+ } else {
2528
+ direction = -1;
2529
+ supertrend = upperBand;
2530
+ }
2531
+ } else {
2532
+ if (close < lowerBand) {
2533
+ direction = -1;
2534
+ supertrend = upperBand;
2535
+ } else {
2536
+ direction = 1;
2537
+ supertrend = lowerBand;
2538
+ }
2539
+ }
2540
+ }
2541
+ state.prevUpperBand = upperBand;
2542
+ state.prevLowerBand = lowerBand;
2543
+ state.prevSupertrend = supertrend;
2544
+ state.prevDirection = direction;
2545
+ return [[this.context.precision(supertrend), direction]];
2086
2546
  }
2087
2547
  crossover(source1, source2) {
2088
2548
  const current1 = Array.isArray(source1) ? source1[0] : source1;
@@ -2123,344 +2583,6 @@ class TechnicalAnalysis {
2123
2583
  return this.context.precision(result[idx]);
2124
2584
  }
2125
2585
  }
2126
- function atr(high, low, close, period) {
2127
- const tr = new Array(high.length);
2128
- tr[0] = high[0] - low[0];
2129
- for (let i = 1; i < high.length; i++) {
2130
- const hl = high[i] - low[i];
2131
- const hc = Math.abs(high[i] - close[i - 1]);
2132
- const lc = Math.abs(low[i] - close[i - 1]);
2133
- tr[i] = Math.max(hl, hc, lc);
2134
- }
2135
- const atr2 = new Array(high.length).fill(NaN);
2136
- let sum = 0;
2137
- for (let i = 0; i < period; i++) {
2138
- sum += tr[i];
2139
- }
2140
- atr2[period - 1] = sum / period;
2141
- for (let i = period; i < tr.length; i++) {
2142
- atr2[i] = (atr2[i - 1] * (period - 1) + tr[i]) / period;
2143
- }
2144
- return atr2;
2145
- }
2146
- function ema(source, period) {
2147
- const result = new Array(source.length).fill(NaN);
2148
- const alpha = 2 / (period + 1);
2149
- let sum = 0;
2150
- for (let i = 0; i < period; i++) {
2151
- sum += source[i] || 0;
2152
- }
2153
- result[period - 1] = sum / period;
2154
- for (let i = period; i < source.length; i++) {
2155
- result[i] = source[i] * alpha + result[i - 1] * (1 - alpha);
2156
- }
2157
- return result;
2158
- }
2159
- function rsi(source, period) {
2160
- const result = new Array(source.length).fill(NaN);
2161
- const gains = new Array(source.length).fill(0);
2162
- const losses = new Array(source.length).fill(0);
2163
- for (let i = 1; i < source.length; i++) {
2164
- const diff = source[i] - source[i - 1];
2165
- gains[i] = diff > 0 ? diff : 0;
2166
- losses[i] = diff < 0 ? -diff : 0;
2167
- }
2168
- let avgGain = 0;
2169
- let avgLoss = 0;
2170
- for (let i = 1; i <= period; i++) {
2171
- avgGain += gains[i];
2172
- avgLoss += losses[i];
2173
- }
2174
- avgGain /= period;
2175
- avgLoss /= period;
2176
- result[period] = avgLoss === 0 ? 100 : 100 - 100 / (1 + avgGain / avgLoss);
2177
- for (let i = period + 1; i < source.length; i++) {
2178
- avgGain = (avgGain * (period - 1) + gains[i]) / period;
2179
- avgLoss = (avgLoss * (period - 1) + losses[i]) / period;
2180
- result[i] = avgLoss === 0 ? 100 : 100 - 100 / (1 + avgGain / avgLoss);
2181
- }
2182
- return result;
2183
- }
2184
- function rma(source, period) {
2185
- const result = new Array(source.length).fill(NaN);
2186
- const alpha = 1 / period;
2187
- let sum = 0;
2188
- for (let i = 0; i < period; i++) {
2189
- sum += source[i] || 0;
2190
- }
2191
- result[period - 1] = sum / period;
2192
- for (let i = period; i < source.length; i++) {
2193
- const currentValue = source[i] || 0;
2194
- result[i] = currentValue * alpha + result[i - 1] * (1 - alpha);
2195
- }
2196
- return result;
2197
- }
2198
- function sma_cache(source, period, cacheObj) {
2199
- const result = cacheObj.previousResult || new Array(source.length).fill(NaN);
2200
- const lastProcessedIndex = cacheObj.lastProcessedIndex || -1;
2201
- let previousSum = cacheObj.previousSum || 0;
2202
- if (lastProcessedIndex === -1 || source.length !== lastProcessedIndex + 1) {
2203
- previousSum = 0;
2204
- for (let i = 0; i < period; i++) {
2205
- previousSum += source[i] || 0;
2206
- }
2207
- result[period - 1] = previousSum / period;
2208
- for (let i = 0; i < period - 1; i++) {
2209
- result[i] = NaN;
2210
- }
2211
- for (let i = period; i < source.length; i++) {
2212
- previousSum = previousSum - (source[i - period] || 0) + (source[i] || 0);
2213
- result[i] = previousSum / period;
2214
- }
2215
- } else if (source.length === lastProcessedIndex + 2) {
2216
- const newIndex = source.length - 1;
2217
- previousSum = previousSum - (source[newIndex - period] || 0) + (source[newIndex] || 0);
2218
- result[newIndex] = previousSum / period;
2219
- } else {
2220
- return sma(source, period);
2221
- }
2222
- cacheObj.previousSum = previousSum;
2223
- cacheObj.lastProcessedIndex = source.length - 1;
2224
- cacheObj.previousResult = result;
2225
- return result;
2226
- }
2227
- function sma(source, period) {
2228
- const result = new Array(source.length).fill(NaN);
2229
- for (let i = period - 1; i < source.length; i++) {
2230
- let sum = 0;
2231
- for (let j = 0; j < period; j++) {
2232
- sum += source[i - j] || 0;
2233
- }
2234
- result[i] = sum / period;
2235
- }
2236
- return result;
2237
- }
2238
- function vwma(source, volume, period) {
2239
- const result = new Array(source.length).fill(NaN);
2240
- for (let i = period - 1; i < source.length; i++) {
2241
- let sumVol = 0;
2242
- let sumVolPrice = 0;
2243
- for (let j = 0; j < period; j++) {
2244
- sumVol += volume[i - j];
2245
- sumVolPrice += source[i - j] * volume[i - j];
2246
- }
2247
- result[i] = sumVolPrice / sumVol;
2248
- }
2249
- return result;
2250
- }
2251
- function hma(source, period) {
2252
- const halfPeriod = Math.floor(period / 2);
2253
- const wma1 = wma(source, halfPeriod);
2254
- const wma2 = wma(source, period);
2255
- const rawHma = wma1.map((value, index) => 2 * value - wma2[index]);
2256
- const sqrtPeriod = Math.floor(Math.sqrt(period));
2257
- const result = wma(rawHma, sqrtPeriod);
2258
- return result;
2259
- }
2260
- function wma(source, period) {
2261
- const result = new Array(source.length);
2262
- for (let i = period - 1; i < source.length; i++) {
2263
- let numerator = 0;
2264
- let denominator = 0;
2265
- for (let j = 0; j < period; j++) {
2266
- numerator += source[i - j] * (period - j);
2267
- denominator += period - j;
2268
- }
2269
- result[i] = numerator / denominator;
2270
- }
2271
- for (let i = 0; i < period - 1; i++) {
2272
- result[i] = NaN;
2273
- }
2274
- return result;
2275
- }
2276
- function change(source, length = 1) {
2277
- const result = new Array(source.length).fill(NaN);
2278
- for (let i = length; i < source.length; i++) {
2279
- result[i] = source[i] - source[i - length];
2280
- }
2281
- return result;
2282
- }
2283
- function mom(source, length) {
2284
- const result = new Array(source.length).fill(NaN);
2285
- for (let i = length; i < source.length; i++) {
2286
- result[i] = source[i] - source[i - length];
2287
- }
2288
- return result;
2289
- }
2290
- function roc(source, length) {
2291
- const result = new Array(source.length).fill(NaN);
2292
- for (let i = length; i < source.length; i++) {
2293
- result[i] = (source[i] - source[i - length]) / source[i - length] * 100;
2294
- }
2295
- return result;
2296
- }
2297
- function dev(source, length) {
2298
- const result = new Array(source.length).fill(NaN);
2299
- const smaValues = sma(source, length);
2300
- for (let i = length - 1; i < source.length; i++) {
2301
- let sumDeviation = 0;
2302
- for (let j = 0; j < length; j++) {
2303
- sumDeviation += Math.abs(source[i - j] - smaValues[i]);
2304
- }
2305
- result[i] = sumDeviation / length;
2306
- }
2307
- return result;
2308
- }
2309
- function variance(source, length) {
2310
- const result = new Array(source.length).fill(NaN);
2311
- for (let i = length - 1; i < source.length; i++) {
2312
- let sum = 0;
2313
- let sumSquares = 0;
2314
- for (let j = 0; j < length; j++) {
2315
- sum += source[i - j];
2316
- sumSquares += source[i - j] * source[i - j];
2317
- }
2318
- const mean = sum / length;
2319
- result[i] = sumSquares / length - mean * mean;
2320
- }
2321
- return result;
2322
- }
2323
- function highest(source, length) {
2324
- const result = new Array(source.length).fill(NaN);
2325
- for (let i = length - 1; i < source.length; i++) {
2326
- let max = -Infinity;
2327
- for (let j = 0; j < length; j++) {
2328
- const value = source[i - j];
2329
- if (isNaN(value)) {
2330
- max = max === -Infinity ? NaN : max;
2331
- } else {
2332
- max = Math.max(max, value);
2333
- }
2334
- }
2335
- result[i] = max;
2336
- }
2337
- return result;
2338
- }
2339
- function lowest(source, length) {
2340
- const result = new Array(source.length).fill(NaN);
2341
- for (let i = length - 1; i < source.length; i++) {
2342
- let min = Infinity;
2343
- for (let j = 0; j < length; j++) {
2344
- const value = source[i - j];
2345
- if (isNaN(value) || value === void 0) {
2346
- min = min === Infinity ? NaN : min;
2347
- } else {
2348
- min = Math.min(min, value);
2349
- }
2350
- }
2351
- result[i] = min;
2352
- }
2353
- return result;
2354
- }
2355
- function median(source, length) {
2356
- const result = new Array(source.length).fill(NaN);
2357
- for (let i = length - 1; i < source.length; i++) {
2358
- const window = source.slice(i - length + 1, i + 1);
2359
- const sorted = window.slice().sort((a, b) => a - b);
2360
- const mid = Math.floor(length / 2);
2361
- result[i] = length % 2 === 0 ? (sorted[mid - 1] + sorted[mid]) / 2 : sorted[mid];
2362
- }
2363
- return result;
2364
- }
2365
- function stdev(source, length, biased = true) {
2366
- const result = new Array(source.length).fill(NaN);
2367
- const smaValues = sma(source, length);
2368
- for (let i = length - 1; i < source.length; i++) {
2369
- let sum = 0;
2370
- for (let j = 0; j < length; j++) {
2371
- sum += Math.pow(source[i - j] - smaValues[i], 2);
2372
- }
2373
- const divisor = biased ? length : length - 1;
2374
- result[i] = Math.sqrt(sum / divisor);
2375
- }
2376
- return result;
2377
- }
2378
- function linreg(source, length, offset) {
2379
- const size = source.length;
2380
- const output = new Array(size).fill(NaN);
2381
- for (let i = length - 1; i < size; i++) {
2382
- let sumX = 0;
2383
- let sumY = 0;
2384
- let sumXY = 0;
2385
- let sumXX = 0;
2386
- const n = length;
2387
- for (let j = 0; j < length; j++) {
2388
- const x = j;
2389
- const y = source[i - length + 1 + j];
2390
- sumX += x;
2391
- sumY += y;
2392
- sumXY += x * y;
2393
- sumXX += x * x;
2394
- }
2395
- const denominator = n * sumXX - sumX * sumX;
2396
- if (denominator === 0) {
2397
- output[i] = NaN;
2398
- continue;
2399
- }
2400
- const slope = (n * sumXY - sumX * sumY) / denominator;
2401
- const intercept = (sumY - slope * sumX) / n;
2402
- const linRegValue = intercept + slope * (length - 1 - offset);
2403
- output[i] = linRegValue;
2404
- }
2405
- return output;
2406
- }
2407
- function calculateSupertrend(high, low, close, factor, atrPeriod) {
2408
- const length = high.length;
2409
- const supertrend = new Array(length).fill(NaN);
2410
- const direction = new Array(length).fill(0);
2411
- const atrValues = atr(high, low, close, atrPeriod);
2412
- const upperBand = new Array(length).fill(NaN);
2413
- const lowerBand = new Array(length).fill(NaN);
2414
- for (let i = 0; i < length; i++) {
2415
- const hl2 = (high[i] + low[i]) / 2;
2416
- const atrValue = atrValues[i];
2417
- if (!isNaN(atrValue)) {
2418
- upperBand[i] = hl2 + factor * atrValue;
2419
- lowerBand[i] = hl2 - factor * atrValue;
2420
- }
2421
- }
2422
- let prevUpperBand = upperBand[atrPeriod];
2423
- let prevLowerBand = lowerBand[atrPeriod];
2424
- let prevSupertrend = close[atrPeriod] <= prevUpperBand ? prevUpperBand : prevLowerBand;
2425
- let prevDirection = close[atrPeriod] <= prevUpperBand ? -1 : 1;
2426
- supertrend[atrPeriod] = prevSupertrend;
2427
- direction[atrPeriod] = prevDirection;
2428
- for (let i = atrPeriod + 1; i < length; i++) {
2429
- let currentUpperBand = upperBand[i];
2430
- if (currentUpperBand < prevUpperBand || close[i - 1] > prevUpperBand) {
2431
- upperBand[i] = currentUpperBand;
2432
- } else {
2433
- upperBand[i] = prevUpperBand;
2434
- }
2435
- let currentLowerBand = lowerBand[i];
2436
- if (currentLowerBand > prevLowerBand || close[i - 1] < prevLowerBand) {
2437
- lowerBand[i] = currentLowerBand;
2438
- } else {
2439
- lowerBand[i] = prevLowerBand;
2440
- }
2441
- if (prevSupertrend === prevUpperBand) {
2442
- if (close[i] > upperBand[i]) {
2443
- direction[i] = 1;
2444
- supertrend[i] = lowerBand[i];
2445
- } else {
2446
- direction[i] = -1;
2447
- supertrend[i] = upperBand[i];
2448
- }
2449
- } else {
2450
- if (close[i] < lowerBand[i]) {
2451
- direction[i] = -1;
2452
- supertrend[i] = upperBand[i];
2453
- } else {
2454
- direction[i] = 1;
2455
- supertrend[i] = lowerBand[i];
2456
- }
2457
- }
2458
- prevUpperBand = upperBand[i];
2459
- prevLowerBand = lowerBand[i];
2460
- prevSupertrend = supertrend[i];
2461
- }
2462
- return [supertrend, direction];
2463
- }
2464
2586
  function pivothigh(source, leftbars, rightbars) {
2465
2587
  const result = new Array(source.length).fill(NaN);
2466
2588
  for (let i = leftbars + rightbars; i < source.length; i++) {
@@ -2728,6 +2850,8 @@ class Context {
2728
2850
  ohlc4: []
2729
2851
  });
2730
2852
  __publicField$1(this, "cache", {});
2853
+ __publicField$1(this, "taState", {});
2854
+ // State for incremental TA calculations
2731
2855
  __publicField$1(this, "useTACache", false);
2732
2856
  __publicField$1(this, "NA", NaN);
2733
2857
  __publicField$1(this, "math");
@@ -2963,7 +3087,6 @@ class BinanceProvider {
2963
3087
  if (data.length === 0) break;
2964
3088
  allData = allData.concat(data);
2965
3089
  currentStart = data[data.length - 1].closeTime + 1;
2966
- if (data.length < 1e3) break;
2967
3090
  }
2968
3091
  return allData;
2969
3092
  } catch (error) {
@@ -2971,8 +3094,6 @@ class BinanceProvider {
2971
3094
  return [];
2972
3095
  }
2973
3096
  }
2974
- //TODO : allow querying more than 1000 klines
2975
- //TODO : immplement cache
2976
3097
  async getMarketData(tickerId, timeframe, limit, sDate, eDate) {
2977
3098
  try {
2978
3099
  const cacheParams = { tickerId, timeframe, limit, sDate, eDate };
@@ -2986,12 +3107,16 @@ class BinanceProvider {
2986
3107
  console.error(`Unsupported timeframe: ${timeframe}`);
2987
3108
  return [];
2988
3109
  }
2989
- let url = `${BINANCE_API_URL}/klines?symbol=${tickerId}&interval=${interval}`;
2990
- if (!limit && sDate && eDate) {
2991
- return this.getMarketDataInterval(tickerId, timeframe, sDate, eDate);
3110
+ const needsPagination = this.shouldPaginate(timeframe, limit, sDate, eDate);
3111
+ if (needsPagination && sDate && eDate) {
3112
+ const allData = await this.getMarketDataInterval(tickerId, timeframe, sDate, eDate);
3113
+ const result2 = limit ? allData.slice(0, limit) : allData;
3114
+ this.cacheManager.set(cacheParams, result2);
3115
+ return result2;
2992
3116
  }
3117
+ let url = `${BINANCE_API_URL}/klines?symbol=${tickerId}&interval=${interval}`;
2993
3118
  if (limit) {
2994
- url += `&limit=${limit}`;
3119
+ url += `&limit=${Math.min(limit, 1e3)}`;
2995
3120
  }
2996
3121
  if (sDate) {
2997
3122
  url += `&startTime=${sDate}`;
@@ -3027,6 +3152,36 @@ class BinanceProvider {
3027
3152
  return [];
3028
3153
  }
3029
3154
  }
3155
+ /**
3156
+ * Determines if pagination is needed based on the parameters
3157
+ */
3158
+ shouldPaginate(timeframe, limit, sDate, eDate) {
3159
+ if (limit && limit > 1e3) {
3160
+ return true;
3161
+ }
3162
+ if (sDate && eDate) {
3163
+ const interval = timeframe_to_binance[timeframe.toUpperCase()];
3164
+ const timeframeDurations = {
3165
+ "1m": 60 * 1e3,
3166
+ "3m": 3 * 60 * 1e3,
3167
+ "5m": 5 * 60 * 1e3,
3168
+ "15m": 15 * 60 * 1e3,
3169
+ "30m": 30 * 60 * 1e3,
3170
+ "1h": 60 * 60 * 1e3,
3171
+ "2h": 2 * 60 * 60 * 1e3,
3172
+ "4h": 4 * 60 * 60 * 1e3,
3173
+ "1d": 24 * 60 * 60 * 1e3,
3174
+ "1w": 7 * 24 * 60 * 60 * 1e3,
3175
+ "1M": 30 * 24 * 60 * 60 * 1e3
3176
+ };
3177
+ const intervalDuration = timeframeDurations[interval];
3178
+ if (intervalDuration) {
3179
+ const requiredCandles = Math.ceil((eDate - sDate) / intervalDuration);
3180
+ return requiredCandles > 1e3;
3181
+ }
3182
+ }
3183
+ return false;
3184
+ }
3030
3185
  }
3031
3186
 
3032
3187
  const Provider = {