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