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.
- package/README.md +7 -5
- package/dist/pinets.dev.browser.js +619 -473
- package/dist/pinets.dev.cjs +619 -473
- package/dist/pinets.dev.cjs.map +1 -1
- package/dist/pinets.dev.es.js +617 -471
- package/dist/pinets.dev.es.js.map +1 -1
- package/dist/pinets.min.browser.js +12 -12
- package/dist/pinets.min.cjs +10 -10
- package/dist/pinets.min.es.js +2 -2
- package/dist/types/Context.class.d.ts +1 -0
- package/dist/types/PineTS.class.d.ts +0 -3
- package/dist/types/marketData/Binance/BinanceProvider.class.d.ts +4 -0
- package/dist/types/namespaces/PineMath.d.ts +1 -0
- package/dist/types/namespaces/TechnicalAnalysis.d.ts +19 -19
- package/dist/types/transpiler/ScopeManager.class.d.ts +3 -0
- package/package.json +1 -1
package/dist/pinets.dev.es.js
CHANGED
|
@@ -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
|
-
|
|
722
|
-
node.argument
|
|
723
|
-
|
|
724
|
-
|
|
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: "
|
|
732
|
-
|
|
747
|
+
type: "Literal",
|
|
748
|
+
value: 0
|
|
733
749
|
},
|
|
734
|
-
computed:
|
|
735
|
-
}
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
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.reverse().slice(0, MAX_PERIODS);
|
|
1531
1588
|
this._periods = marketData.length;
|
|
1532
1589
|
this.data = marketData;
|
|
1533
1590
|
const _open = marketData.map((d) => d.open);
|
|
@@ -1785,6 +1842,9 @@ class PineMath {
|
|
|
1785
1842
|
return this.context.params[name];
|
|
1786
1843
|
}
|
|
1787
1844
|
}
|
|
1845
|
+
__eq(a, b) {
|
|
1846
|
+
return Math.abs(a - b) < 1e-8;
|
|
1847
|
+
}
|
|
1788
1848
|
abs(source) {
|
|
1789
1849
|
return Math.abs(source[0]);
|
|
1790
1850
|
}
|
|
@@ -1948,141 +2008,532 @@ class TechnicalAnalysis {
|
|
|
1948
2008
|
return this.context.params[name];
|
|
1949
2009
|
}
|
|
1950
2010
|
}
|
|
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) {
|
|
2011
|
+
ema(source, _period, _callId) {
|
|
1958
2012
|
const period = Array.isArray(_period) ? _period[0] : _period;
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
|
|
2013
|
+
if (!this.context.taState) this.context.taState = {};
|
|
2014
|
+
const stateKey = _callId || `ema_${period}`;
|
|
2015
|
+
if (!this.context.taState[stateKey]) {
|
|
2016
|
+
this.context.taState[stateKey] = { prevEma: null, initSum: 0, initCount: 0 };
|
|
2017
|
+
}
|
|
2018
|
+
const state = this.context.taState[stateKey];
|
|
2019
|
+
const currentValue = source[0];
|
|
2020
|
+
if (state.initCount < period) {
|
|
2021
|
+
state.initSum += currentValue;
|
|
2022
|
+
state.initCount++;
|
|
2023
|
+
if (state.initCount === period) {
|
|
2024
|
+
state.prevEma = state.initSum / period;
|
|
2025
|
+
return this.context.precision(state.prevEma);
|
|
1969
2026
|
}
|
|
2027
|
+
return NaN;
|
|
1970
2028
|
}
|
|
1971
|
-
const
|
|
1972
|
-
const
|
|
1973
|
-
|
|
2029
|
+
const alpha = 2 / (period + 1);
|
|
2030
|
+
const ema2 = currentValue * alpha + state.prevEma * (1 - alpha);
|
|
2031
|
+
state.prevEma = ema2;
|
|
2032
|
+
return this.context.precision(ema2);
|
|
1974
2033
|
}
|
|
1975
|
-
|
|
2034
|
+
sma(source, _period, _callId) {
|
|
1976
2035
|
const period = Array.isArray(_period) ? _period[0] : _period;
|
|
1977
|
-
|
|
1978
|
-
const
|
|
1979
|
-
|
|
1980
|
-
|
|
2036
|
+
if (!this.context.taState) this.context.taState = {};
|
|
2037
|
+
const stateKey = _callId || `sma_${period}`;
|
|
2038
|
+
if (!this.context.taState[stateKey]) {
|
|
2039
|
+
this.context.taState[stateKey] = { window: [], sum: 0 };
|
|
2040
|
+
}
|
|
2041
|
+
const state = this.context.taState[stateKey];
|
|
2042
|
+
const currentValue = source[0] || 0;
|
|
2043
|
+
state.window.unshift(currentValue);
|
|
2044
|
+
state.sum += currentValue;
|
|
2045
|
+
if (state.window.length < period) {
|
|
2046
|
+
return NaN;
|
|
2047
|
+
}
|
|
2048
|
+
if (state.window.length > period) {
|
|
2049
|
+
const oldValue = state.window.pop();
|
|
2050
|
+
state.sum -= oldValue;
|
|
2051
|
+
}
|
|
2052
|
+
const sma2 = state.sum / period;
|
|
2053
|
+
return this.context.precision(sma2);
|
|
2054
|
+
}
|
|
2055
|
+
vwma(source, _period, _callId) {
|
|
2056
|
+
const period = Array.isArray(_period) ? _period[0] : _period;
|
|
2057
|
+
if (!this.context.taState) this.context.taState = {};
|
|
2058
|
+
const stateKey = _callId || `vwma_${period}`;
|
|
2059
|
+
if (!this.context.taState[stateKey]) {
|
|
2060
|
+
this.context.taState[stateKey] = { window: [], volumeWindow: [] };
|
|
2061
|
+
}
|
|
2062
|
+
const state = this.context.taState[stateKey];
|
|
2063
|
+
const currentValue = source[0];
|
|
2064
|
+
const currentVolume = this.context.data.volume[0];
|
|
2065
|
+
state.window.unshift(currentValue);
|
|
2066
|
+
state.volumeWindow.unshift(currentVolume);
|
|
2067
|
+
if (state.window.length < period) {
|
|
2068
|
+
return NaN;
|
|
2069
|
+
}
|
|
2070
|
+
if (state.window.length > period) {
|
|
2071
|
+
state.window.pop();
|
|
2072
|
+
state.volumeWindow.pop();
|
|
2073
|
+
}
|
|
2074
|
+
let sumVolPrice = 0;
|
|
2075
|
+
let sumVol = 0;
|
|
2076
|
+
for (let i = 0; i < period; i++) {
|
|
2077
|
+
sumVolPrice += state.window[i] * state.volumeWindow[i];
|
|
2078
|
+
sumVol += state.volumeWindow[i];
|
|
2079
|
+
}
|
|
2080
|
+
const vwma2 = sumVolPrice / sumVol;
|
|
2081
|
+
return this.context.precision(vwma2);
|
|
1981
2082
|
}
|
|
1982
|
-
wma(source, _period) {
|
|
2083
|
+
wma(source, _period, _callId) {
|
|
1983
2084
|
const period = Array.isArray(_period) ? _period[0] : _period;
|
|
1984
|
-
|
|
1985
|
-
const
|
|
1986
|
-
|
|
2085
|
+
if (!this.context.taState) this.context.taState = {};
|
|
2086
|
+
const stateKey = _callId || `wma_${period}`;
|
|
2087
|
+
if (!this.context.taState[stateKey]) {
|
|
2088
|
+
this.context.taState[stateKey] = { window: [] };
|
|
2089
|
+
}
|
|
2090
|
+
const state = this.context.taState[stateKey];
|
|
2091
|
+
const currentValue = source[0];
|
|
2092
|
+
state.window.unshift(currentValue);
|
|
2093
|
+
if (state.window.length < period) {
|
|
2094
|
+
return NaN;
|
|
2095
|
+
}
|
|
2096
|
+
if (state.window.length > period) {
|
|
2097
|
+
state.window.pop();
|
|
2098
|
+
}
|
|
2099
|
+
let numerator = 0;
|
|
2100
|
+
let denominator = 0;
|
|
2101
|
+
for (let i = 0; i < period; i++) {
|
|
2102
|
+
const weight = period - i;
|
|
2103
|
+
numerator += state.window[i] * weight;
|
|
2104
|
+
denominator += weight;
|
|
2105
|
+
}
|
|
2106
|
+
const wma2 = numerator / denominator;
|
|
2107
|
+
return this.context.precision(wma2);
|
|
1987
2108
|
}
|
|
1988
|
-
hma(source, _period) {
|
|
2109
|
+
hma(source, _period, _callId) {
|
|
1989
2110
|
const period = Array.isArray(_period) ? _period[0] : _period;
|
|
1990
|
-
const
|
|
1991
|
-
const
|
|
1992
|
-
|
|
2111
|
+
const halfPeriod = Math.floor(period / 2);
|
|
2112
|
+
const sqrtPeriod = Math.floor(Math.sqrt(period));
|
|
2113
|
+
const wma1 = this.wma(source, halfPeriod, _callId ? `${_callId}_wma1` : void 0);
|
|
2114
|
+
const wma2 = this.wma(source, period, _callId ? `${_callId}_wma2` : void 0);
|
|
2115
|
+
if (isNaN(wma1) || isNaN(wma2)) {
|
|
2116
|
+
return NaN;
|
|
2117
|
+
}
|
|
2118
|
+
if (!this.context.taState) this.context.taState = {};
|
|
2119
|
+
const stateKey = _callId || `hma_raw_${period}`;
|
|
2120
|
+
if (!this.context.taState[stateKey]) {
|
|
2121
|
+
this.context.taState[stateKey] = [];
|
|
2122
|
+
}
|
|
2123
|
+
const rawHma = 2 * wma1 - wma2;
|
|
2124
|
+
this.context.taState[stateKey].unshift(rawHma);
|
|
2125
|
+
const hmaStateKey = _callId ? `${_callId}_hma_final` : `hma_final_${period}`;
|
|
2126
|
+
if (!this.context.taState[hmaStateKey]) {
|
|
2127
|
+
this.context.taState[hmaStateKey] = { window: [] };
|
|
2128
|
+
}
|
|
2129
|
+
const state = this.context.taState[hmaStateKey];
|
|
2130
|
+
state.window.unshift(rawHma);
|
|
2131
|
+
if (state.window.length < sqrtPeriod) {
|
|
2132
|
+
return NaN;
|
|
2133
|
+
}
|
|
2134
|
+
if (state.window.length > sqrtPeriod) {
|
|
2135
|
+
state.window.pop();
|
|
2136
|
+
}
|
|
2137
|
+
let numerator = 0;
|
|
2138
|
+
let denominator = 0;
|
|
2139
|
+
for (let i = 0; i < sqrtPeriod; i++) {
|
|
2140
|
+
const weight = sqrtPeriod - i;
|
|
2141
|
+
numerator += state.window[i] * weight;
|
|
2142
|
+
denominator += weight;
|
|
2143
|
+
}
|
|
2144
|
+
const hma2 = numerator / denominator;
|
|
2145
|
+
return this.context.precision(hma2);
|
|
1993
2146
|
}
|
|
1994
|
-
rma(source, _period) {
|
|
2147
|
+
rma(source, _period, _callId) {
|
|
1995
2148
|
const period = Array.isArray(_period) ? _period[0] : _period;
|
|
1996
|
-
|
|
1997
|
-
const
|
|
1998
|
-
|
|
2149
|
+
if (!this.context.taState) this.context.taState = {};
|
|
2150
|
+
const stateKey = _callId || `rma_${period}`;
|
|
2151
|
+
if (!this.context.taState[stateKey]) {
|
|
2152
|
+
this.context.taState[stateKey] = { prevRma: null, initSum: 0, initCount: 0 };
|
|
2153
|
+
}
|
|
2154
|
+
const state = this.context.taState[stateKey];
|
|
2155
|
+
const currentValue = source[0] || 0;
|
|
2156
|
+
if (state.initCount < period) {
|
|
2157
|
+
state.initSum += currentValue;
|
|
2158
|
+
state.initCount++;
|
|
2159
|
+
if (state.initCount === period) {
|
|
2160
|
+
state.prevRma = state.initSum / period;
|
|
2161
|
+
return this.context.precision(state.prevRma);
|
|
2162
|
+
}
|
|
2163
|
+
return NaN;
|
|
2164
|
+
}
|
|
2165
|
+
const alpha = 1 / period;
|
|
2166
|
+
const rma2 = currentValue * alpha + state.prevRma * (1 - alpha);
|
|
2167
|
+
state.prevRma = rma2;
|
|
2168
|
+
return this.context.precision(rma2);
|
|
1999
2169
|
}
|
|
2000
|
-
change(source, _length = 1) {
|
|
2170
|
+
change(source, _length = 1, _callId) {
|
|
2001
2171
|
const length = Array.isArray(_length) ? _length[0] : _length;
|
|
2002
|
-
|
|
2003
|
-
const
|
|
2004
|
-
|
|
2172
|
+
if (!this.context.taState) this.context.taState = {};
|
|
2173
|
+
const stateKey = _callId || `change_${length}`;
|
|
2174
|
+
if (!this.context.taState[stateKey]) {
|
|
2175
|
+
this.context.taState[stateKey] = { window: [] };
|
|
2176
|
+
}
|
|
2177
|
+
const state = this.context.taState[stateKey];
|
|
2178
|
+
const currentValue = source[0];
|
|
2179
|
+
state.window.unshift(currentValue);
|
|
2180
|
+
if (state.window.length <= length) {
|
|
2181
|
+
return NaN;
|
|
2182
|
+
}
|
|
2183
|
+
if (state.window.length > length + 1) {
|
|
2184
|
+
state.window.pop();
|
|
2185
|
+
}
|
|
2186
|
+
const change2 = currentValue - state.window[length];
|
|
2187
|
+
return this.context.precision(change2);
|
|
2005
2188
|
}
|
|
2006
|
-
rsi(source, _period) {
|
|
2189
|
+
rsi(source, _period, _callId) {
|
|
2007
2190
|
const period = Array.isArray(_period) ? _period[0] : _period;
|
|
2008
|
-
|
|
2009
|
-
const
|
|
2010
|
-
|
|
2191
|
+
if (!this.context.taState) this.context.taState = {};
|
|
2192
|
+
const stateKey = _callId || `rsi_${period}`;
|
|
2193
|
+
if (!this.context.taState[stateKey]) {
|
|
2194
|
+
this.context.taState[stateKey] = {
|
|
2195
|
+
prevValue: null,
|
|
2196
|
+
avgGain: 0,
|
|
2197
|
+
avgLoss: 0,
|
|
2198
|
+
initGains: [],
|
|
2199
|
+
initLosses: []
|
|
2200
|
+
};
|
|
2201
|
+
}
|
|
2202
|
+
const state = this.context.taState[stateKey];
|
|
2203
|
+
const currentValue = source[0];
|
|
2204
|
+
if (state.prevValue !== null) {
|
|
2205
|
+
const diff = currentValue - state.prevValue;
|
|
2206
|
+
const gain = diff > 0 ? diff : 0;
|
|
2207
|
+
const loss = diff < 0 ? -diff : 0;
|
|
2208
|
+
if (state.initGains.length < period) {
|
|
2209
|
+
state.initGains.push(gain);
|
|
2210
|
+
state.initLosses.push(loss);
|
|
2211
|
+
if (state.initGains.length === period) {
|
|
2212
|
+
state.avgGain = state.initGains.reduce((a, b) => a + b, 0) / period;
|
|
2213
|
+
state.avgLoss = state.initLosses.reduce((a, b) => a + b, 0) / period;
|
|
2214
|
+
state.prevValue = currentValue;
|
|
2215
|
+
const rsi3 = state.avgLoss === 0 ? 100 : 100 - 100 / (1 + state.avgGain / state.avgLoss);
|
|
2216
|
+
return this.context.precision(rsi3);
|
|
2217
|
+
}
|
|
2218
|
+
state.prevValue = currentValue;
|
|
2219
|
+
return NaN;
|
|
2220
|
+
}
|
|
2221
|
+
state.avgGain = (state.avgGain * (period - 1) + gain) / period;
|
|
2222
|
+
state.avgLoss = (state.avgLoss * (period - 1) + loss) / period;
|
|
2223
|
+
const rsi2 = state.avgLoss === 0 ? 100 : 100 - 100 / (1 + state.avgGain / state.avgLoss);
|
|
2224
|
+
state.prevValue = currentValue;
|
|
2225
|
+
return this.context.precision(rsi2);
|
|
2226
|
+
}
|
|
2227
|
+
state.prevValue = currentValue;
|
|
2228
|
+
return NaN;
|
|
2011
2229
|
}
|
|
2012
|
-
atr(_period) {
|
|
2230
|
+
atr(_period, _callId) {
|
|
2013
2231
|
const period = Array.isArray(_period) ? _period[0] : _period;
|
|
2014
|
-
|
|
2015
|
-
const
|
|
2016
|
-
|
|
2017
|
-
|
|
2018
|
-
|
|
2019
|
-
|
|
2232
|
+
if (!this.context.taState) this.context.taState = {};
|
|
2233
|
+
const stateKey = _callId || `atr_${period}`;
|
|
2234
|
+
if (!this.context.taState[stateKey]) {
|
|
2235
|
+
this.context.taState[stateKey] = {
|
|
2236
|
+
prevAtr: null,
|
|
2237
|
+
initSum: 0,
|
|
2238
|
+
initCount: 0,
|
|
2239
|
+
prevClose: null
|
|
2240
|
+
};
|
|
2241
|
+
}
|
|
2242
|
+
const state = this.context.taState[stateKey];
|
|
2243
|
+
const high = this.context.data.high[0];
|
|
2244
|
+
const low = this.context.data.low[0];
|
|
2245
|
+
const close = this.context.data.close[0];
|
|
2246
|
+
let tr;
|
|
2247
|
+
if (state.prevClose !== null) {
|
|
2248
|
+
const hl = high - low;
|
|
2249
|
+
const hc = Math.abs(high - state.prevClose);
|
|
2250
|
+
const lc = Math.abs(low - state.prevClose);
|
|
2251
|
+
tr = Math.max(hl, hc, lc);
|
|
2252
|
+
} else {
|
|
2253
|
+
tr = high - low;
|
|
2254
|
+
}
|
|
2255
|
+
state.prevClose = close;
|
|
2256
|
+
if (state.initCount < period) {
|
|
2257
|
+
state.initSum += tr;
|
|
2258
|
+
state.initCount++;
|
|
2259
|
+
if (state.initCount === period) {
|
|
2260
|
+
state.prevAtr = state.initSum / period;
|
|
2261
|
+
return this.context.precision(state.prevAtr);
|
|
2262
|
+
}
|
|
2263
|
+
return NaN;
|
|
2264
|
+
}
|
|
2265
|
+
const atr2 = (state.prevAtr * (period - 1) + tr) / period;
|
|
2266
|
+
state.prevAtr = atr2;
|
|
2267
|
+
return this.context.precision(atr2);
|
|
2020
2268
|
}
|
|
2021
|
-
mom(source, _length) {
|
|
2269
|
+
mom(source, _length, _callId) {
|
|
2022
2270
|
const length = Array.isArray(_length) ? _length[0] : _length;
|
|
2023
|
-
|
|
2024
|
-
const idx = this.context.idx;
|
|
2025
|
-
return this.context.precision(result[idx]);
|
|
2271
|
+
return this.change(source, length);
|
|
2026
2272
|
}
|
|
2027
|
-
roc(source, _length) {
|
|
2273
|
+
roc(source, _length, _callId) {
|
|
2028
2274
|
const length = Array.isArray(_length) ? _length[0] : _length;
|
|
2029
|
-
|
|
2030
|
-
const
|
|
2031
|
-
|
|
2275
|
+
if (!this.context.taState) this.context.taState = {};
|
|
2276
|
+
const stateKey = _callId || `roc_${length}`;
|
|
2277
|
+
if (!this.context.taState[stateKey]) {
|
|
2278
|
+
this.context.taState[stateKey] = { window: [] };
|
|
2279
|
+
}
|
|
2280
|
+
const state = this.context.taState[stateKey];
|
|
2281
|
+
const currentValue = source[0];
|
|
2282
|
+
state.window.unshift(currentValue);
|
|
2283
|
+
if (state.window.length <= length) {
|
|
2284
|
+
return NaN;
|
|
2285
|
+
}
|
|
2286
|
+
if (state.window.length > length + 1) {
|
|
2287
|
+
state.window.pop();
|
|
2288
|
+
}
|
|
2289
|
+
const prevValue = state.window[length];
|
|
2290
|
+
const roc2 = (currentValue - prevValue) / prevValue * 100;
|
|
2291
|
+
return this.context.precision(roc2);
|
|
2032
2292
|
}
|
|
2033
|
-
dev(source, _length) {
|
|
2293
|
+
dev(source, _length, _callId) {
|
|
2034
2294
|
const length = Array.isArray(_length) ? _length[0] : _length;
|
|
2035
|
-
|
|
2036
|
-
const
|
|
2037
|
-
|
|
2295
|
+
if (!this.context.taState) this.context.taState = {};
|
|
2296
|
+
const stateKey = _callId || `dev_${length}`;
|
|
2297
|
+
if (!this.context.taState[stateKey]) {
|
|
2298
|
+
this.context.taState[stateKey] = { window: [], sum: 0 };
|
|
2299
|
+
}
|
|
2300
|
+
const state = this.context.taState[stateKey];
|
|
2301
|
+
const currentValue = source[0] || 0;
|
|
2302
|
+
state.window.unshift(currentValue);
|
|
2303
|
+
state.sum += currentValue;
|
|
2304
|
+
if (state.window.length < length) {
|
|
2305
|
+
return NaN;
|
|
2306
|
+
}
|
|
2307
|
+
if (state.window.length > length) {
|
|
2308
|
+
const oldValue = state.window.pop();
|
|
2309
|
+
state.sum -= oldValue;
|
|
2310
|
+
}
|
|
2311
|
+
const mean = state.sum / length;
|
|
2312
|
+
let sumDeviation = 0;
|
|
2313
|
+
for (let i = 0; i < length; i++) {
|
|
2314
|
+
sumDeviation += Math.abs(state.window[i] - mean);
|
|
2315
|
+
}
|
|
2316
|
+
const dev2 = sumDeviation / length;
|
|
2317
|
+
return this.context.precision(dev2);
|
|
2038
2318
|
}
|
|
2039
|
-
variance(source, _length) {
|
|
2319
|
+
variance(source, _length, _callId) {
|
|
2040
2320
|
const length = Array.isArray(_length) ? _length[0] : _length;
|
|
2041
|
-
|
|
2042
|
-
const
|
|
2043
|
-
|
|
2321
|
+
if (!this.context.taState) this.context.taState = {};
|
|
2322
|
+
const stateKey = _callId || `variance_${length}`;
|
|
2323
|
+
if (!this.context.taState[stateKey]) {
|
|
2324
|
+
this.context.taState[stateKey] = { window: [] };
|
|
2325
|
+
}
|
|
2326
|
+
const state = this.context.taState[stateKey];
|
|
2327
|
+
const currentValue = source[0];
|
|
2328
|
+
state.window.unshift(currentValue);
|
|
2329
|
+
if (state.window.length < length) {
|
|
2330
|
+
return NaN;
|
|
2331
|
+
}
|
|
2332
|
+
if (state.window.length > length) {
|
|
2333
|
+
state.window.pop();
|
|
2334
|
+
}
|
|
2335
|
+
let sum = 0;
|
|
2336
|
+
let sumSquares = 0;
|
|
2337
|
+
for (let i = 0; i < length; i++) {
|
|
2338
|
+
sum += state.window[i];
|
|
2339
|
+
sumSquares += state.window[i] * state.window[i];
|
|
2340
|
+
}
|
|
2341
|
+
const mean = sum / length;
|
|
2342
|
+
const variance2 = sumSquares / length - mean * mean;
|
|
2343
|
+
return this.context.precision(variance2);
|
|
2044
2344
|
}
|
|
2045
|
-
highest(source, _length) {
|
|
2345
|
+
highest(source, _length, _callId) {
|
|
2046
2346
|
const length = Array.isArray(_length) ? _length[0] : _length;
|
|
2047
|
-
|
|
2048
|
-
const
|
|
2049
|
-
|
|
2347
|
+
if (!this.context.taState) this.context.taState = {};
|
|
2348
|
+
const stateKey = _callId || `highest_${length}`;
|
|
2349
|
+
if (!this.context.taState[stateKey]) {
|
|
2350
|
+
this.context.taState[stateKey] = { window: [] };
|
|
2351
|
+
}
|
|
2352
|
+
const state = this.context.taState[stateKey];
|
|
2353
|
+
const currentValue = source[0];
|
|
2354
|
+
state.window.unshift(currentValue);
|
|
2355
|
+
if (state.window.length < length) {
|
|
2356
|
+
return NaN;
|
|
2357
|
+
}
|
|
2358
|
+
if (state.window.length > length) {
|
|
2359
|
+
state.window.pop();
|
|
2360
|
+
}
|
|
2361
|
+
const max = Math.max(...state.window.filter((v) => !isNaN(v)));
|
|
2362
|
+
return this.context.precision(max);
|
|
2050
2363
|
}
|
|
2051
|
-
lowest(source, _length) {
|
|
2364
|
+
lowest(source, _length, _callId) {
|
|
2052
2365
|
const length = Array.isArray(_length) ? _length[0] : _length;
|
|
2053
|
-
|
|
2054
|
-
const
|
|
2055
|
-
|
|
2366
|
+
if (!this.context.taState) this.context.taState = {};
|
|
2367
|
+
const stateKey = _callId || `lowest_${length}`;
|
|
2368
|
+
if (!this.context.taState[stateKey]) {
|
|
2369
|
+
this.context.taState[stateKey] = { window: [] };
|
|
2370
|
+
}
|
|
2371
|
+
const state = this.context.taState[stateKey];
|
|
2372
|
+
const currentValue = source[0];
|
|
2373
|
+
state.window.unshift(currentValue);
|
|
2374
|
+
if (state.window.length < length) {
|
|
2375
|
+
return NaN;
|
|
2376
|
+
}
|
|
2377
|
+
if (state.window.length > length) {
|
|
2378
|
+
state.window.pop();
|
|
2379
|
+
}
|
|
2380
|
+
const validValues = state.window.filter((v) => !isNaN(v) && v !== void 0);
|
|
2381
|
+
const min = validValues.length > 0 ? Math.min(...validValues) : NaN;
|
|
2382
|
+
return this.context.precision(min);
|
|
2056
2383
|
}
|
|
2057
|
-
median(source, _length) {
|
|
2384
|
+
median(source, _length, _callId) {
|
|
2058
2385
|
const length = Array.isArray(_length) ? _length[0] : _length;
|
|
2059
|
-
|
|
2060
|
-
const
|
|
2061
|
-
|
|
2386
|
+
if (!this.context.taState) this.context.taState = {};
|
|
2387
|
+
const stateKey = _callId || `median_${length}`;
|
|
2388
|
+
if (!this.context.taState[stateKey]) {
|
|
2389
|
+
this.context.taState[stateKey] = { window: [] };
|
|
2390
|
+
}
|
|
2391
|
+
const state = this.context.taState[stateKey];
|
|
2392
|
+
const currentValue = source[0];
|
|
2393
|
+
state.window.unshift(currentValue);
|
|
2394
|
+
if (state.window.length < length) {
|
|
2395
|
+
return NaN;
|
|
2396
|
+
}
|
|
2397
|
+
if (state.window.length > length) {
|
|
2398
|
+
state.window.pop();
|
|
2399
|
+
}
|
|
2400
|
+
const sorted = state.window.slice().sort((a, b) => a - b);
|
|
2401
|
+
const mid = Math.floor(length / 2);
|
|
2402
|
+
const median2 = length % 2 === 0 ? (sorted[mid - 1] + sorted[mid]) / 2 : sorted[mid];
|
|
2403
|
+
return this.context.precision(median2);
|
|
2062
2404
|
}
|
|
2063
|
-
stdev(source, _length, _bias = true) {
|
|
2405
|
+
stdev(source, _length, _bias = true, _callId) {
|
|
2064
2406
|
const length = Array.isArray(_length) ? _length[0] : _length;
|
|
2065
2407
|
const bias = Array.isArray(_bias) ? _bias[0] : _bias;
|
|
2066
|
-
|
|
2067
|
-
const
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
|
|
2408
|
+
if (!this.context.taState) this.context.taState = {};
|
|
2409
|
+
const stateKey = _callId || `stdev_${length}_${bias}`;
|
|
2410
|
+
if (!this.context.taState[stateKey]) {
|
|
2411
|
+
this.context.taState[stateKey] = { window: [], sum: 0 };
|
|
2412
|
+
}
|
|
2413
|
+
const state = this.context.taState[stateKey];
|
|
2414
|
+
const currentValue = source[0];
|
|
2415
|
+
state.window.unshift(currentValue);
|
|
2416
|
+
state.sum += currentValue;
|
|
2417
|
+
if (state.window.length < length) {
|
|
2418
|
+
return NaN;
|
|
2419
|
+
}
|
|
2420
|
+
if (state.window.length > length) {
|
|
2421
|
+
const oldValue = state.window.pop();
|
|
2422
|
+
state.sum -= oldValue;
|
|
2423
|
+
}
|
|
2424
|
+
const mean = state.sum / length;
|
|
2425
|
+
let sumSquaredDiff = 0;
|
|
2426
|
+
for (let i = 0; i < length; i++) {
|
|
2427
|
+
sumSquaredDiff += Math.pow(state.window[i] - mean, 2);
|
|
2428
|
+
}
|
|
2429
|
+
const divisor = bias ? length : length - 1;
|
|
2430
|
+
const stdev2 = Math.sqrt(sumSquaredDiff / divisor);
|
|
2431
|
+
return this.context.precision(stdev2);
|
|
2432
|
+
}
|
|
2433
|
+
linreg(source, _length, _offset, _callId) {
|
|
2071
2434
|
const length = Array.isArray(_length) ? _length[0] : _length;
|
|
2072
2435
|
const offset = Array.isArray(_offset) ? _offset[0] : _offset;
|
|
2073
|
-
|
|
2074
|
-
const
|
|
2075
|
-
|
|
2436
|
+
if (!this.context.taState) this.context.taState = {};
|
|
2437
|
+
const stateKey = _callId || `linreg_${length}_${offset}`;
|
|
2438
|
+
if (!this.context.taState[stateKey]) {
|
|
2439
|
+
this.context.taState[stateKey] = { window: [] };
|
|
2440
|
+
}
|
|
2441
|
+
const state = this.context.taState[stateKey];
|
|
2442
|
+
const currentValue = source[0];
|
|
2443
|
+
state.window.unshift(currentValue);
|
|
2444
|
+
if (state.window.length < length) {
|
|
2445
|
+
return NaN;
|
|
2446
|
+
}
|
|
2447
|
+
if (state.window.length > length) {
|
|
2448
|
+
state.window.pop();
|
|
2449
|
+
}
|
|
2450
|
+
let sumX = 0;
|
|
2451
|
+
let sumY = 0;
|
|
2452
|
+
let sumXY = 0;
|
|
2453
|
+
let sumXX = 0;
|
|
2454
|
+
const n = length;
|
|
2455
|
+
for (let j = 0; j < length; j++) {
|
|
2456
|
+
const x = length - 1 - j;
|
|
2457
|
+
const y = state.window[j];
|
|
2458
|
+
sumX += x;
|
|
2459
|
+
sumY += y;
|
|
2460
|
+
sumXY += x * y;
|
|
2461
|
+
sumXX += x * x;
|
|
2462
|
+
}
|
|
2463
|
+
const denominator = n * sumXX - sumX * sumX;
|
|
2464
|
+
if (denominator === 0) {
|
|
2465
|
+
return NaN;
|
|
2466
|
+
}
|
|
2467
|
+
const slope = (n * sumXY - sumX * sumY) / denominator;
|
|
2468
|
+
const intercept = (sumY - slope * sumX) / n;
|
|
2469
|
+
const linRegValue = intercept + slope * (length - 1 - offset);
|
|
2470
|
+
return this.context.precision(linRegValue);
|
|
2076
2471
|
}
|
|
2077
|
-
supertrend(_factor, _atrPeriod) {
|
|
2472
|
+
supertrend(_factor, _atrPeriod, _callId) {
|
|
2078
2473
|
const factor = Array.isArray(_factor) ? _factor[0] : _factor;
|
|
2079
2474
|
const atrPeriod = Array.isArray(_atrPeriod) ? _atrPeriod[0] : _atrPeriod;
|
|
2080
|
-
|
|
2081
|
-
const
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
|
|
2085
|
-
|
|
2475
|
+
if (!this.context.taState) this.context.taState = {};
|
|
2476
|
+
const stateKey = `supertrend_${factor}_${atrPeriod}`;
|
|
2477
|
+
if (!this.context.taState[stateKey]) {
|
|
2478
|
+
this.context.taState[stateKey] = {
|
|
2479
|
+
prevUpperBand: null,
|
|
2480
|
+
prevLowerBand: null,
|
|
2481
|
+
prevSupertrend: null,
|
|
2482
|
+
prevDirection: null
|
|
2483
|
+
};
|
|
2484
|
+
}
|
|
2485
|
+
const state = this.context.taState[stateKey];
|
|
2486
|
+
const high = this.context.data.high[0];
|
|
2487
|
+
const low = this.context.data.low[0];
|
|
2488
|
+
const close = this.context.data.close[0];
|
|
2489
|
+
const atrValue = this.atr(atrPeriod, _callId ? `${_callId}_atr` : void 0);
|
|
2490
|
+
if (isNaN(atrValue)) {
|
|
2491
|
+
return [[NaN, 0]];
|
|
2492
|
+
}
|
|
2493
|
+
const hl2 = (high + low) / 2;
|
|
2494
|
+
let upperBand = hl2 + factor * atrValue;
|
|
2495
|
+
let lowerBand = hl2 - factor * atrValue;
|
|
2496
|
+
if (state.prevUpperBand !== null) {
|
|
2497
|
+
if (upperBand < state.prevUpperBand || this.context.data.close[1] > state.prevUpperBand) {
|
|
2498
|
+
upperBand = upperBand;
|
|
2499
|
+
} else {
|
|
2500
|
+
upperBand = state.prevUpperBand;
|
|
2501
|
+
}
|
|
2502
|
+
if (lowerBand > state.prevLowerBand || this.context.data.close[1] < state.prevLowerBand) {
|
|
2503
|
+
lowerBand = lowerBand;
|
|
2504
|
+
} else {
|
|
2505
|
+
lowerBand = state.prevLowerBand;
|
|
2506
|
+
}
|
|
2507
|
+
}
|
|
2508
|
+
let direction;
|
|
2509
|
+
let supertrend;
|
|
2510
|
+
if (state.prevSupertrend === null) {
|
|
2511
|
+
direction = close <= upperBand ? -1 : 1;
|
|
2512
|
+
supertrend = direction === -1 ? upperBand : lowerBand;
|
|
2513
|
+
} else {
|
|
2514
|
+
if (state.prevSupertrend === state.prevUpperBand) {
|
|
2515
|
+
if (close > upperBand) {
|
|
2516
|
+
direction = 1;
|
|
2517
|
+
supertrend = lowerBand;
|
|
2518
|
+
} else {
|
|
2519
|
+
direction = -1;
|
|
2520
|
+
supertrend = upperBand;
|
|
2521
|
+
}
|
|
2522
|
+
} else {
|
|
2523
|
+
if (close < lowerBand) {
|
|
2524
|
+
direction = -1;
|
|
2525
|
+
supertrend = upperBand;
|
|
2526
|
+
} else {
|
|
2527
|
+
direction = 1;
|
|
2528
|
+
supertrend = lowerBand;
|
|
2529
|
+
}
|
|
2530
|
+
}
|
|
2531
|
+
}
|
|
2532
|
+
state.prevUpperBand = upperBand;
|
|
2533
|
+
state.prevLowerBand = lowerBand;
|
|
2534
|
+
state.prevSupertrend = supertrend;
|
|
2535
|
+
state.prevDirection = direction;
|
|
2536
|
+
return [[this.context.precision(supertrend), direction]];
|
|
2086
2537
|
}
|
|
2087
2538
|
crossover(source1, source2) {
|
|
2088
2539
|
const current1 = Array.isArray(source1) ? source1[0] : source1;
|
|
@@ -2123,344 +2574,6 @@ class TechnicalAnalysis {
|
|
|
2123
2574
|
return this.context.precision(result[idx]);
|
|
2124
2575
|
}
|
|
2125
2576
|
}
|
|
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
2577
|
function pivothigh(source, leftbars, rightbars) {
|
|
2465
2578
|
const result = new Array(source.length).fill(NaN);
|
|
2466
2579
|
for (let i = leftbars + rightbars; i < source.length; i++) {
|
|
@@ -2728,6 +2841,8 @@ class Context {
|
|
|
2728
2841
|
ohlc4: []
|
|
2729
2842
|
});
|
|
2730
2843
|
__publicField$1(this, "cache", {});
|
|
2844
|
+
__publicField$1(this, "taState", {});
|
|
2845
|
+
// State for incremental TA calculations
|
|
2731
2846
|
__publicField$1(this, "useTACache", false);
|
|
2732
2847
|
__publicField$1(this, "NA", NaN);
|
|
2733
2848
|
__publicField$1(this, "math");
|
|
@@ -2963,7 +3078,6 @@ class BinanceProvider {
|
|
|
2963
3078
|
if (data.length === 0) break;
|
|
2964
3079
|
allData = allData.concat(data);
|
|
2965
3080
|
currentStart = data[data.length - 1].closeTime + 1;
|
|
2966
|
-
if (data.length < 1e3) break;
|
|
2967
3081
|
}
|
|
2968
3082
|
return allData;
|
|
2969
3083
|
} catch (error) {
|
|
@@ -2971,8 +3085,6 @@ class BinanceProvider {
|
|
|
2971
3085
|
return [];
|
|
2972
3086
|
}
|
|
2973
3087
|
}
|
|
2974
|
-
//TODO : allow querying more than 1000 klines
|
|
2975
|
-
//TODO : immplement cache
|
|
2976
3088
|
async getMarketData(tickerId, timeframe, limit, sDate, eDate) {
|
|
2977
3089
|
try {
|
|
2978
3090
|
const cacheParams = { tickerId, timeframe, limit, sDate, eDate };
|
|
@@ -2986,12 +3098,16 @@ class BinanceProvider {
|
|
|
2986
3098
|
console.error(`Unsupported timeframe: ${timeframe}`);
|
|
2987
3099
|
return [];
|
|
2988
3100
|
}
|
|
2989
|
-
|
|
2990
|
-
if (
|
|
2991
|
-
|
|
3101
|
+
const needsPagination = this.shouldPaginate(timeframe, limit, sDate, eDate);
|
|
3102
|
+
if (needsPagination && sDate && eDate) {
|
|
3103
|
+
const allData = await this.getMarketDataInterval(tickerId, timeframe, sDate, eDate);
|
|
3104
|
+
const result2 = limit ? allData.slice(0, limit) : allData;
|
|
3105
|
+
this.cacheManager.set(cacheParams, result2);
|
|
3106
|
+
return result2;
|
|
2992
3107
|
}
|
|
3108
|
+
let url = `${BINANCE_API_URL}/klines?symbol=${tickerId}&interval=${interval}`;
|
|
2993
3109
|
if (limit) {
|
|
2994
|
-
url += `&limit=${limit}`;
|
|
3110
|
+
url += `&limit=${Math.min(limit, 1e3)}`;
|
|
2995
3111
|
}
|
|
2996
3112
|
if (sDate) {
|
|
2997
3113
|
url += `&startTime=${sDate}`;
|
|
@@ -3027,6 +3143,36 @@ class BinanceProvider {
|
|
|
3027
3143
|
return [];
|
|
3028
3144
|
}
|
|
3029
3145
|
}
|
|
3146
|
+
/**
|
|
3147
|
+
* Determines if pagination is needed based on the parameters
|
|
3148
|
+
*/
|
|
3149
|
+
shouldPaginate(timeframe, limit, sDate, eDate) {
|
|
3150
|
+
if (limit && limit > 1e3) {
|
|
3151
|
+
return true;
|
|
3152
|
+
}
|
|
3153
|
+
if (sDate && eDate) {
|
|
3154
|
+
const interval = timeframe_to_binance[timeframe.toUpperCase()];
|
|
3155
|
+
const timeframeDurations = {
|
|
3156
|
+
"1m": 60 * 1e3,
|
|
3157
|
+
"3m": 3 * 60 * 1e3,
|
|
3158
|
+
"5m": 5 * 60 * 1e3,
|
|
3159
|
+
"15m": 15 * 60 * 1e3,
|
|
3160
|
+
"30m": 30 * 60 * 1e3,
|
|
3161
|
+
"1h": 60 * 60 * 1e3,
|
|
3162
|
+
"2h": 2 * 60 * 60 * 1e3,
|
|
3163
|
+
"4h": 4 * 60 * 60 * 1e3,
|
|
3164
|
+
"1d": 24 * 60 * 60 * 1e3,
|
|
3165
|
+
"1w": 7 * 24 * 60 * 60 * 1e3,
|
|
3166
|
+
"1M": 30 * 24 * 60 * 60 * 1e3
|
|
3167
|
+
};
|
|
3168
|
+
const intervalDuration = timeframeDurations[interval];
|
|
3169
|
+
if (intervalDuration) {
|
|
3170
|
+
const requiredCandles = Math.ceil((eDate - sDate) / intervalDuration);
|
|
3171
|
+
return requiredCandles > 1e3;
|
|
3172
|
+
}
|
|
3173
|
+
}
|
|
3174
|
+
return false;
|
|
3175
|
+
}
|
|
3030
3176
|
}
|
|
3031
3177
|
|
|
3032
3178
|
const Provider = {
|