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.
- package/README.md +12 -5
- package/dist/pinets.dev.browser.js +639 -484
- package/dist/pinets.dev.cjs +639 -484
- package/dist/pinets.dev.cjs.map +1 -1
- package/dist/pinets.dev.es.js +637 -482
- package/dist/pinets.dev.es.js.map +1 -1
- package/dist/pinets.min.browser.js +12 -12
- package/dist/pinets.min.cjs +12 -12
- 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.
|
|
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
|
|
1657
|
+
for (let i = this._periods - n; i < this._periods; i++) {
|
|
1591
1658
|
context.idx = i;
|
|
1592
|
-
context.data.close
|
|
1593
|
-
context.data.open
|
|
1594
|
-
context.data.high
|
|
1595
|
-
context.data.low
|
|
1596
|
-
context.data.volume
|
|
1597
|
-
context.data.hl2
|
|
1598
|
-
context.data.hlc3
|
|
1599
|
-
context.data.ohlc4
|
|
1600
|
-
context.data.openTime
|
|
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
|
-
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
|
|
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
|
|
1972
|
-
const
|
|
1973
|
-
|
|
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
|
-
|
|
2043
|
+
sma(source, _period, _callId) {
|
|
1976
2044
|
const period = Array.isArray(_period) ? _period[0] : _period;
|
|
1977
|
-
|
|
1978
|
-
const
|
|
1979
|
-
|
|
1980
|
-
|
|
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
|
-
|
|
1985
|
-
const
|
|
1986
|
-
|
|
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
|
|
1991
|
-
const
|
|
1992
|
-
|
|
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
|
-
|
|
1997
|
-
const
|
|
1998
|
-
|
|
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
|
-
|
|
2003
|
-
const
|
|
2004
|
-
|
|
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
|
-
|
|
2009
|
-
const
|
|
2010
|
-
|
|
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
|
-
|
|
2015
|
-
const
|
|
2016
|
-
|
|
2017
|
-
|
|
2018
|
-
|
|
2019
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2030
|
-
const
|
|
2031
|
-
|
|
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
|
-
|
|
2036
|
-
const
|
|
2037
|
-
|
|
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
|
-
|
|
2042
|
-
const
|
|
2043
|
-
|
|
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
|
-
|
|
2048
|
-
const
|
|
2049
|
-
|
|
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
|
-
|
|
2054
|
-
const
|
|
2055
|
-
|
|
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
|
-
|
|
2060
|
-
const
|
|
2061
|
-
|
|
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
|
-
|
|
2067
|
-
const
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
|
|
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
|
-
|
|
2074
|
-
const
|
|
2075
|
-
|
|
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
|
-
|
|
2081
|
-
const
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
|
|
2085
|
-
|
|
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
|
-
|
|
2990
|
-
if (
|
|
2991
|
-
|
|
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 = {
|