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