pinets 0.1.1 → 0.1.33
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 +8 -0
- package/dist/pinets.dev.browser.js +10700 -0
- package/dist/pinets.dev.cjs +818 -273
- package/dist/pinets.dev.cjs.map +1 -1
- package/dist/pinets.dev.es.js +872 -172
- 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 +24 -2
- package/dist/types/PineTS.class.d.ts +1 -1
- package/dist/types/marketData/Binance/BinanceProvider.class.d.ts +3 -0
- package/dist/types/namespaces/Core.d.ts +3 -0
- package/dist/types/namespaces/PineArray.d.ts +62 -0
- package/dist/types/namespaces/PineColor.d.ts +0 -0
- package/dist/types/namespaces/PineMath.d.ts +20 -19
- package/dist/types/namespaces/PineRequest.d.ts +2 -1
- package/dist/types/namespaces/TechnicalAnalysis.d.ts +5 -1
- package/dist/types/transpiler/ScopeManager.class.d.ts +2 -0
- package/package.json +2 -2
package/dist/pinets.dev.cjs
CHANGED
|
@@ -1,6 +1,3 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
Object.defineProperty(exports, '__esModule', { value: true });
|
|
4
1
|
|
|
5
2
|
/*
|
|
6
3
|
* Copyright (C) 2025 Alaa-eddine KADDOURI
|
|
@@ -17,7 +14,12 @@ Object.defineProperty(exports, '__esModule', { value: true });
|
|
|
17
14
|
*
|
|
18
15
|
* You should have received a copy of the GNU Affero General Public License
|
|
19
16
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
20
|
-
|
|
17
|
+
*/
|
|
18
|
+
'use strict';
|
|
19
|
+
|
|
20
|
+
Object.defineProperty(exports, '__esModule', { value: true });
|
|
21
|
+
|
|
22
|
+
// This file was generated. Do not modify manually!
|
|
21
23
|
var astralIdentifierCodes = [509, 0, 227, 0, 150, 4, 294, 9, 1368, 2, 2, 1, 6, 3, 41, 2, 5, 0, 166, 1, 574, 3, 9, 9, 7, 9, 32, 4, 318, 1, 80, 3, 71, 10, 50, 3, 123, 2, 54, 14, 32, 10, 3, 1, 11, 3, 46, 10, 8, 0, 46, 9, 7, 2, 37, 13, 2, 9, 6, 1, 45, 0, 13, 2, 49, 13, 9, 3, 2, 11, 83, 11, 7, 0, 3, 0, 158, 11, 6, 9, 7, 3, 56, 1, 2, 6, 3, 1, 3, 2, 10, 0, 11, 1, 3, 6, 4, 4, 68, 8, 2, 0, 3, 0, 2, 3, 2, 4, 2, 0, 15, 1, 83, 17, 10, 9, 5, 0, 82, 19, 13, 9, 214, 6, 3, 8, 28, 1, 83, 16, 16, 9, 82, 12, 9, 9, 7, 19, 58, 14, 5, 9, 243, 14, 166, 9, 71, 5, 2, 1, 3, 3, 2, 0, 2, 1, 13, 9, 120, 6, 3, 6, 4, 0, 29, 9, 41, 6, 2, 3, 9, 0, 10, 10, 47, 15, 343, 9, 54, 7, 2, 7, 17, 9, 57, 21, 2, 13, 123, 5, 4, 0, 2, 1, 2, 6, 2, 0, 9, 9, 49, 4, 2, 1, 2, 4, 9, 9, 330, 3, 10, 1, 2, 0, 49, 6, 4, 4, 14, 10, 5350, 0, 7, 14, 11465, 27, 2343, 9, 87, 9, 39, 4, 60, 6, 26, 9, 535, 9, 470, 0, 2, 54, 8, 3, 82, 0, 12, 1, 19628, 1, 4178, 9, 519, 45, 3, 22, 543, 4, 4, 5, 9, 7, 3, 6, 31, 3, 149, 2, 1418, 49, 513, 54, 5, 49, 9, 0, 15, 0, 23, 4, 2, 14, 1361, 6, 2, 16, 3, 6, 2, 1, 2, 4, 101, 0, 161, 6, 10, 9, 357, 0, 62, 13, 499, 13, 245, 1, 2, 9, 726, 6, 110, 6, 6, 9, 4759, 9, 787719, 239];
|
|
22
24
|
|
|
23
25
|
// This file was generated. Do not modify manually!
|
|
@@ -6146,22 +6148,7 @@ function parse(input, options) {
|
|
|
6146
6148
|
return Parser.parse(input, options)
|
|
6147
6149
|
}
|
|
6148
6150
|
|
|
6149
|
-
|
|
6150
|
-
* Copyright (C) 2025 Alaa-eddine KADDOURI
|
|
6151
|
-
*
|
|
6152
|
-
* This program is free software: you can redistribute it and/or modify
|
|
6153
|
-
* it under the terms of the GNU Affero General Public License as published by
|
|
6154
|
-
* the Free Software Foundation, either version 3 of the License, or
|
|
6155
|
-
* (at your option) any later version.
|
|
6156
|
-
*
|
|
6157
|
-
* This program is distributed in the hope that it will be useful,
|
|
6158
|
-
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
6159
|
-
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
6160
|
-
* GNU Affero General Public License for more details.
|
|
6161
|
-
*
|
|
6162
|
-
* You should have received a copy of the GNU Affero General Public License
|
|
6163
|
-
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
6164
|
-
*/// AST walker module for ESTree compatible trees
|
|
6151
|
+
// AST walker module for ESTree compatible trees
|
|
6165
6152
|
|
|
6166
6153
|
// A simple walk is one where you simply specify callbacks to be
|
|
6167
6154
|
// called on specific nodes. The last two arguments are optional. A
|
|
@@ -6465,22 +6452,7 @@ base.MethodDefinition = base.PropertyDefinition = base.Property = function (node
|
|
|
6465
6452
|
if (node.value) { c(node.value, st, "Expression"); }
|
|
6466
6453
|
};
|
|
6467
6454
|
|
|
6468
|
-
|
|
6469
|
-
* Copyright (C) 2025 Alaa-eddine KADDOURI
|
|
6470
|
-
*
|
|
6471
|
-
* This program is free software: you can redistribute it and/or modify
|
|
6472
|
-
* it under the terms of the GNU Affero General Public License as published by
|
|
6473
|
-
* the Free Software Foundation, either version 3 of the License, or
|
|
6474
|
-
* (at your option) any later version.
|
|
6475
|
-
*
|
|
6476
|
-
* This program is distributed in the hope that it will be useful,
|
|
6477
|
-
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
6478
|
-
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
6479
|
-
* GNU Affero General Public License for more details.
|
|
6480
|
-
*
|
|
6481
|
-
* You should have received a copy of the GNU Affero General Public License
|
|
6482
|
-
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
6483
|
-
*/// Astring is a tiny and fast JavaScript code generator from an ESTree-compliant AST.
|
|
6455
|
+
// Astring is a tiny and fast JavaScript code generator from an ESTree-compliant AST.
|
|
6484
6456
|
//
|
|
6485
6457
|
// Astring was written by David Bonnet and released under an MIT license.
|
|
6486
6458
|
//
|
|
@@ -7701,22 +7673,7 @@ function generate(node, options) {
|
|
|
7701
7673
|
return state.output
|
|
7702
7674
|
}
|
|
7703
7675
|
|
|
7704
|
-
|
|
7705
|
-
* Copyright (C) 2025 Alaa-eddine KADDOURI
|
|
7706
|
-
*
|
|
7707
|
-
* This program is free software: you can redistribute it and/or modify
|
|
7708
|
-
* it under the terms of the GNU Affero General Public License as published by
|
|
7709
|
-
* the Free Software Foundation, either version 3 of the License, or
|
|
7710
|
-
* (at your option) any later version.
|
|
7711
|
-
*
|
|
7712
|
-
* This program is distributed in the hope that it will be useful,
|
|
7713
|
-
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
7714
|
-
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
7715
|
-
* GNU Affero General Public License for more details.
|
|
7716
|
-
*
|
|
7717
|
-
* You should have received a copy of the GNU Affero General Public License
|
|
7718
|
-
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
7719
|
-
*/class ScopeManager {
|
|
7676
|
+
class ScopeManager {
|
|
7720
7677
|
scopes = [];
|
|
7721
7678
|
scopeTypes = [];
|
|
7722
7679
|
scopeCounts = /* @__PURE__ */ new Map();
|
|
@@ -7728,6 +7685,7 @@ function generate(node, options) {
|
|
|
7728
7685
|
loopVarNames = /* @__PURE__ */ new Map();
|
|
7729
7686
|
// Map original names to transformed names
|
|
7730
7687
|
paramIdCounter = 0;
|
|
7688
|
+
cacheIdCounter = 0;
|
|
7731
7689
|
tempVarCounter = 0;
|
|
7732
7690
|
get nextParamIdArg() {
|
|
7733
7691
|
return {
|
|
@@ -7735,6 +7693,12 @@ function generate(node, options) {
|
|
|
7735
7693
|
name: `'p${this.paramIdCounter++}'`
|
|
7736
7694
|
};
|
|
7737
7695
|
}
|
|
7696
|
+
get nextCacheIdArg() {
|
|
7697
|
+
return {
|
|
7698
|
+
type: "Identifier",
|
|
7699
|
+
name: `'cache_${this.cacheIdCounter++}'`
|
|
7700
|
+
};
|
|
7701
|
+
}
|
|
7738
7702
|
constructor() {
|
|
7739
7703
|
this.pushScope("glb");
|
|
7740
7704
|
}
|
|
@@ -7818,6 +7782,7 @@ function generate(node, options) {
|
|
|
7818
7782
|
}
|
|
7819
7783
|
}
|
|
7820
7784
|
|
|
7785
|
+
//!!!Warning!!! this code is not clean, it was initially written as a PoC then used as transpiler for PineTS
|
|
7821
7786
|
const CONTEXT_NAME = "$";
|
|
7822
7787
|
const UNDEFINED_ARG = {
|
|
7823
7788
|
type: "Identifier",
|
|
@@ -7914,6 +7879,9 @@ function transformMemberExpression(memberNode, originalParamName, scopeManager)
|
|
|
7914
7879
|
}
|
|
7915
7880
|
function transformVariableDeclaration(varNode, scopeManager) {
|
|
7916
7881
|
varNode.declarations.forEach((decl) => {
|
|
7882
|
+
if (decl.init.name == "na") {
|
|
7883
|
+
decl.init.name = "NaN";
|
|
7884
|
+
}
|
|
7917
7885
|
const isContextProperty = decl.init && decl.init.type === "MemberExpression" && decl.init.object && (decl.init.object.name === "context" || decl.init.object.name === CONTEXT_NAME || decl.init.object.name === "context2");
|
|
7918
7886
|
const isSubContextProperty = decl.init && decl.init.type === "MemberExpression" && decl.init.object?.object && (decl.init.object.object.name === "context" || decl.init.object.object.name === CONTEXT_NAME || decl.init.object.object.name === "context2");
|
|
7919
7887
|
const isArrowFunction = decl.init && decl.init.type === "ArrowFunctionExpression";
|
|
@@ -8076,7 +8044,28 @@ function transformVariableDeclaration(varNode, scopeManager) {
|
|
|
8076
8044
|
}
|
|
8077
8045
|
};
|
|
8078
8046
|
if (isArrayPatternVar) {
|
|
8079
|
-
assignmentExpr.expression.right.object.property.name +=
|
|
8047
|
+
assignmentExpr.expression.right.object.property.name += `?.[0][${decl.init.property.value}]`;
|
|
8048
|
+
const obj = assignmentExpr.expression.right.object;
|
|
8049
|
+
assignmentExpr.expression.right = {
|
|
8050
|
+
type: "CallExpression",
|
|
8051
|
+
callee: {
|
|
8052
|
+
type: "MemberExpression",
|
|
8053
|
+
object: {
|
|
8054
|
+
type: "Identifier",
|
|
8055
|
+
name: CONTEXT_NAME
|
|
8056
|
+
},
|
|
8057
|
+
property: {
|
|
8058
|
+
type: "Identifier",
|
|
8059
|
+
name: "init"
|
|
8060
|
+
},
|
|
8061
|
+
computed: false
|
|
8062
|
+
},
|
|
8063
|
+
arguments: [
|
|
8064
|
+
targetVarRef,
|
|
8065
|
+
obj
|
|
8066
|
+
/*, decl.init.property.value*/
|
|
8067
|
+
]
|
|
8068
|
+
};
|
|
8080
8069
|
}
|
|
8081
8070
|
if (isArrowFunction) {
|
|
8082
8071
|
scopeManager.pushScope("fn");
|
|
@@ -8229,14 +8218,22 @@ function transformAssignmentExpression(node, scopeManager) {
|
|
|
8229
8218
|
{ parent: node.right, inNamespaceCall: false },
|
|
8230
8219
|
{
|
|
8231
8220
|
Identifier(node2, state, c) {
|
|
8221
|
+
if (node2.name == "na") {
|
|
8222
|
+
node2.name = "NaN";
|
|
8223
|
+
}
|
|
8232
8224
|
node2.parent = state.parent;
|
|
8233
8225
|
transformIdentifier(node2, scopeManager);
|
|
8234
8226
|
const isBinaryOperation = node2.parent && node2.parent.type === "BinaryExpression";
|
|
8235
8227
|
const isConditional = node2.parent && node2.parent.type === "ConditionalExpression";
|
|
8236
|
-
|
|
8228
|
+
const isContextBound = scopeManager.isContextBound(node2.name) && !scopeManager.isRootParam(node2.name);
|
|
8229
|
+
const hasArrayAccess = node2.parent && node2.parent.type === "MemberExpression" && node2.parent.computed && node2.parent.object === node2;
|
|
8230
|
+
const isParamCall = node2.parent && node2.parent._isParamCall;
|
|
8231
|
+
const isMemberExpression = node2.parent && node2.parent.type === "MemberExpression";
|
|
8232
|
+
const isReserved = node2.name === "NaN";
|
|
8233
|
+
if (isContextBound || isConditional || isBinaryOperation) {
|
|
8237
8234
|
if (node2.type === "MemberExpression") {
|
|
8238
8235
|
transformArrayIndex(node2, scopeManager);
|
|
8239
|
-
} else if (node2.type === "Identifier") {
|
|
8236
|
+
} else if (node2.type === "Identifier" && !isMemberExpression && !hasArrayAccess && !isParamCall && !isReserved) {
|
|
8240
8237
|
addArrayAccess(node2);
|
|
8241
8238
|
}
|
|
8242
8239
|
}
|
|
@@ -8418,6 +8415,10 @@ function transformReturnStatement(node, scopeManager) {
|
|
|
8418
8415
|
}
|
|
8419
8416
|
function transformIdentifierForParam(node, scopeManager) {
|
|
8420
8417
|
if (node.type === "Identifier") {
|
|
8418
|
+
if (node.name === "na") {
|
|
8419
|
+
node.name = "NaN";
|
|
8420
|
+
return node;
|
|
8421
|
+
}
|
|
8421
8422
|
if (scopeManager.isLoopVariable(node.name)) {
|
|
8422
8423
|
return node;
|
|
8423
8424
|
}
|
|
@@ -8471,40 +8472,58 @@ function transformIdentifierForParam(node, scopeManager) {
|
|
|
8471
8472
|
}
|
|
8472
8473
|
return node;
|
|
8473
8474
|
}
|
|
8475
|
+
function getParamFromUnaryExpression(node, scopeManager, namespace) {
|
|
8476
|
+
const transformedArgument = transformOperand(node.argument, scopeManager, namespace);
|
|
8477
|
+
const unaryExpr = {
|
|
8478
|
+
type: "UnaryExpression",
|
|
8479
|
+
operator: node.operator,
|
|
8480
|
+
prefix: node.prefix,
|
|
8481
|
+
argument: transformedArgument,
|
|
8482
|
+
start: node.start,
|
|
8483
|
+
end: node.end
|
|
8484
|
+
};
|
|
8485
|
+
return unaryExpr;
|
|
8486
|
+
}
|
|
8474
8487
|
function transformOperand(node, scopeManager, namespace = "") {
|
|
8475
|
-
|
|
8476
|
-
|
|
8477
|
-
|
|
8478
|
-
if (node.type === "MemberExpression") {
|
|
8479
|
-
const transformedObject = node.object.type === "Identifier" ? transformIdentifierForParam(node.object, scopeManager) : node.object;
|
|
8480
|
-
return {
|
|
8481
|
-
type: "MemberExpression",
|
|
8482
|
-
object: transformedObject,
|
|
8483
|
-
property: node.property,
|
|
8484
|
-
computed: node.computed
|
|
8485
|
-
};
|
|
8486
|
-
} else if (node.type === "Identifier") {
|
|
8487
|
-
if (scopeManager.isLoopVariable(node.name)) {
|
|
8488
|
-
return node;
|
|
8488
|
+
switch (node.type) {
|
|
8489
|
+
case "BinaryExpression": {
|
|
8490
|
+
return getParamFromBinaryExpression(node, scopeManager, namespace);
|
|
8489
8491
|
}
|
|
8490
|
-
|
|
8491
|
-
|
|
8492
|
-
return
|
|
8492
|
+
case "MemberExpression": {
|
|
8493
|
+
const transformedObject = node.object.type === "Identifier" ? transformIdentifierForParam(node.object, scopeManager) : node.object;
|
|
8494
|
+
return {
|
|
8495
|
+
type: "MemberExpression",
|
|
8496
|
+
object: transformedObject,
|
|
8497
|
+
property: node.property,
|
|
8498
|
+
computed: node.computed
|
|
8499
|
+
};
|
|
8500
|
+
}
|
|
8501
|
+
case "Identifier": {
|
|
8502
|
+
if (scopeManager.isLoopVariable(node.name)) {
|
|
8503
|
+
return node;
|
|
8504
|
+
}
|
|
8505
|
+
const isMemberExprProperty = node.parent && node.parent.type === "MemberExpression" && node.parent.property === node;
|
|
8506
|
+
if (isMemberExprProperty) {
|
|
8507
|
+
return node;
|
|
8508
|
+
}
|
|
8509
|
+
const transformedObject = transformIdentifierForParam(node, scopeManager);
|
|
8510
|
+
return {
|
|
8511
|
+
type: "MemberExpression",
|
|
8512
|
+
object: transformedObject,
|
|
8513
|
+
property: {
|
|
8514
|
+
type: "Literal",
|
|
8515
|
+
value: 0
|
|
8516
|
+
},
|
|
8517
|
+
computed: true
|
|
8518
|
+
};
|
|
8519
|
+
}
|
|
8520
|
+
case "UnaryExpression": {
|
|
8521
|
+
return getParamFromUnaryExpression(node, scopeManager, namespace);
|
|
8493
8522
|
}
|
|
8494
|
-
const transformedObject = transformIdentifierForParam(node, scopeManager);
|
|
8495
|
-
return {
|
|
8496
|
-
type: "MemberExpression",
|
|
8497
|
-
object: transformedObject,
|
|
8498
|
-
property: {
|
|
8499
|
-
type: "Literal",
|
|
8500
|
-
value: 0
|
|
8501
|
-
},
|
|
8502
|
-
computed: true
|
|
8503
|
-
};
|
|
8504
8523
|
}
|
|
8505
8524
|
return node;
|
|
8506
8525
|
}
|
|
8507
|
-
function
|
|
8526
|
+
function getParamFromBinaryExpression(node, scopeManager, namespace) {
|
|
8508
8527
|
const transformedLeft = transformOperand(node.left, scopeManager, namespace);
|
|
8509
8528
|
const transformedRight = transformOperand(node.right, scopeManager, namespace);
|
|
8510
8529
|
const binaryExpr = {
|
|
@@ -8525,28 +8544,89 @@ function transformBinaryExpression(node, scopeManager, namespace) {
|
|
|
8525
8544
|
transformMemberExpression(node2, "", scopeManager);
|
|
8526
8545
|
}
|
|
8527
8546
|
});
|
|
8547
|
+
return binaryExpr;
|
|
8548
|
+
}
|
|
8549
|
+
function getParamFromLogicalExpression(node, scopeManager, namespace) {
|
|
8550
|
+
const transformedLeft = transformOperand(node.left, scopeManager, namespace);
|
|
8551
|
+
const transformedRight = transformOperand(node.right, scopeManager, namespace);
|
|
8552
|
+
const logicalExpr = {
|
|
8553
|
+
type: "LogicalExpression",
|
|
8554
|
+
operator: node.operator,
|
|
8555
|
+
left: transformedLeft,
|
|
8556
|
+
right: transformedRight,
|
|
8557
|
+
start: node.start,
|
|
8558
|
+
end: node.end
|
|
8559
|
+
};
|
|
8560
|
+
recursive(logicalExpr, scopeManager, {
|
|
8561
|
+
CallExpression(node2, scopeManager2) {
|
|
8562
|
+
if (!node2._transformed) {
|
|
8563
|
+
transformCallExpression(node2, scopeManager2);
|
|
8564
|
+
}
|
|
8565
|
+
}
|
|
8566
|
+
});
|
|
8567
|
+
return logicalExpr;
|
|
8568
|
+
}
|
|
8569
|
+
function getParamFromConditionalExpression(node, scopeManager, namespace) {
|
|
8570
|
+
recursive(
|
|
8571
|
+
node,
|
|
8572
|
+
{ parent: node, inNamespaceCall: false },
|
|
8573
|
+
{
|
|
8574
|
+
Identifier(node2, state, c) {
|
|
8575
|
+
if (node2.name == "NaN") return;
|
|
8576
|
+
if (node2.name == "na") {
|
|
8577
|
+
node2.name = "NaN";
|
|
8578
|
+
return;
|
|
8579
|
+
}
|
|
8580
|
+
node2.parent = state.parent;
|
|
8581
|
+
transformIdentifier(node2, scopeManager);
|
|
8582
|
+
const isBinaryOperation = node2.parent && node2.parent.type === "BinaryExpression";
|
|
8583
|
+
const isConditional = node2.parent && node2.parent.type === "ConditionalExpression";
|
|
8584
|
+
if (isConditional || isBinaryOperation) {
|
|
8585
|
+
if (node2.type === "MemberExpression") {
|
|
8586
|
+
transformArrayIndex(node2, scopeManager);
|
|
8587
|
+
} else if (node2.type === "Identifier") {
|
|
8588
|
+
addArrayAccess(node2);
|
|
8589
|
+
}
|
|
8590
|
+
}
|
|
8591
|
+
},
|
|
8592
|
+
MemberExpression(node2, state, c) {
|
|
8593
|
+
transformArrayIndex(node2, scopeManager);
|
|
8594
|
+
if (node2.object) {
|
|
8595
|
+
c(node2.object, { parent: node2, inNamespaceCall: state.inNamespaceCall });
|
|
8596
|
+
}
|
|
8597
|
+
},
|
|
8598
|
+
CallExpression(node2, state, c) {
|
|
8599
|
+
const isNamespaceCall = node2.callee && node2.callee.type === "MemberExpression" && node2.callee.object && node2.callee.object.type === "Identifier" && scopeManager.isContextBound(node2.callee.object.name);
|
|
8600
|
+
transformCallExpression(node2, scopeManager);
|
|
8601
|
+
node2.arguments.forEach((arg) => c(arg, { parent: node2, inNamespaceCall: isNamespaceCall || state.inNamespaceCall }));
|
|
8602
|
+
}
|
|
8603
|
+
}
|
|
8604
|
+
);
|
|
8528
8605
|
return {
|
|
8529
8606
|
type: "CallExpression",
|
|
8530
8607
|
callee: {
|
|
8531
8608
|
type: "MemberExpression",
|
|
8532
|
-
object: {
|
|
8533
|
-
|
|
8534
|
-
name: namespace
|
|
8535
|
-
},
|
|
8536
|
-
property: {
|
|
8537
|
-
type: "Identifier",
|
|
8538
|
-
name: "param"
|
|
8539
|
-
},
|
|
8540
|
-
computed: false
|
|
8609
|
+
object: { type: "Identifier", name: namespace },
|
|
8610
|
+
property: { type: "Identifier", name: "param" }
|
|
8541
8611
|
},
|
|
8542
|
-
arguments: [
|
|
8612
|
+
arguments: [node, UNDEFINED_ARG, scopeManager.nextParamIdArg],
|
|
8543
8613
|
_transformed: true,
|
|
8544
8614
|
_isParamCall: true
|
|
8545
8615
|
};
|
|
8546
8616
|
}
|
|
8547
8617
|
function transformFunctionArgument(arg, namespace, scopeManager) {
|
|
8548
|
-
|
|
8549
|
-
|
|
8618
|
+
switch (arg?.type) {
|
|
8619
|
+
case "BinaryExpression":
|
|
8620
|
+
arg = getParamFromBinaryExpression(arg, scopeManager, namespace);
|
|
8621
|
+
break;
|
|
8622
|
+
case "LogicalExpression":
|
|
8623
|
+
arg = getParamFromLogicalExpression(arg, scopeManager, namespace);
|
|
8624
|
+
break;
|
|
8625
|
+
case "ConditionalExpression":
|
|
8626
|
+
return getParamFromConditionalExpression(arg, scopeManager, namespace);
|
|
8627
|
+
case "UnaryExpression":
|
|
8628
|
+
arg = getParamFromUnaryExpression(arg, scopeManager, namespace);
|
|
8629
|
+
break;
|
|
8550
8630
|
}
|
|
8551
8631
|
const isArrayAccess = arg.type === "MemberExpression" && arg.computed && arg.property;
|
|
8552
8632
|
if (isArrayAccess) {
|
|
@@ -8611,6 +8691,10 @@ function transformFunctionArgument(arg, namespace, scopeManager) {
|
|
|
8611
8691
|
});
|
|
8612
8692
|
}
|
|
8613
8693
|
if (arg.type === "Identifier") {
|
|
8694
|
+
if (arg.name === "na") {
|
|
8695
|
+
arg.name = "NaN";
|
|
8696
|
+
return arg;
|
|
8697
|
+
}
|
|
8614
8698
|
if (scopeManager.isContextBound(arg.name) && !scopeManager.isRootParam(arg.name)) {
|
|
8615
8699
|
return {
|
|
8616
8700
|
type: "CallExpression",
|
|
@@ -8654,18 +8738,18 @@ function transformFunctionArgument(arg, namespace, scopeManager) {
|
|
|
8654
8738
|
_isParamCall: true
|
|
8655
8739
|
};
|
|
8656
8740
|
}
|
|
8657
|
-
function transformCallExpression(node, scopeManager) {
|
|
8741
|
+
function transformCallExpression(node, scopeManager, namespace) {
|
|
8658
8742
|
if (node._transformed) {
|
|
8659
8743
|
return;
|
|
8660
8744
|
}
|
|
8661
8745
|
const isNamespaceCall = node.callee && node.callee.type === "MemberExpression" && node.callee.object && node.callee.object.type === "Identifier" && (scopeManager.isContextBound(node.callee.object.name) || node.callee.object.name === "math" || node.callee.object.name === "ta");
|
|
8662
8746
|
if (isNamespaceCall) {
|
|
8663
|
-
const
|
|
8747
|
+
const namespace2 = node.callee.object.name;
|
|
8664
8748
|
node.arguments = node.arguments.map((arg) => {
|
|
8665
8749
|
if (arg._isParamCall) {
|
|
8666
8750
|
return arg;
|
|
8667
8751
|
}
|
|
8668
|
-
return transformFunctionArgument(arg,
|
|
8752
|
+
return transformFunctionArgument(arg, namespace2, scopeManager);
|
|
8669
8753
|
});
|
|
8670
8754
|
node._transformed = true;
|
|
8671
8755
|
} else if (node.callee && node.callee.type === "Identifier") {
|
|
@@ -8679,13 +8763,29 @@ function transformCallExpression(node, scopeManager) {
|
|
|
8679
8763
|
}
|
|
8680
8764
|
node.arguments.forEach((arg) => {
|
|
8681
8765
|
recursive(arg, scopeManager, {
|
|
8682
|
-
|
|
8766
|
+
Identifier(node2, state, c) {
|
|
8767
|
+
node2.parent = state.parent;
|
|
8768
|
+
transformIdentifier(node2, scopeManager);
|
|
8769
|
+
const isBinaryOperation = node2.parent && node2.parent.type === "BinaryExpression";
|
|
8770
|
+
const isConditional = node2.parent && node2.parent.type === "ConditionalExpression";
|
|
8771
|
+
if (isConditional || isBinaryOperation) {
|
|
8772
|
+
if (node2.type === "MemberExpression") {
|
|
8773
|
+
transformArrayIndex(node2, scopeManager);
|
|
8774
|
+
} else if (node2.type === "Identifier") {
|
|
8775
|
+
addArrayAccess(node2);
|
|
8776
|
+
}
|
|
8777
|
+
}
|
|
8778
|
+
},
|
|
8779
|
+
CallExpression(node2, state, c) {
|
|
8683
8780
|
if (!node2._transformed) {
|
|
8684
8781
|
transformCallExpression(node2, state);
|
|
8685
8782
|
}
|
|
8686
8783
|
},
|
|
8687
|
-
MemberExpression(node2) {
|
|
8784
|
+
MemberExpression(node2, state, c) {
|
|
8688
8785
|
transformMemberExpression(node2, "", scopeManager);
|
|
8786
|
+
if (node2.object) {
|
|
8787
|
+
c(node2.object, { parent: node2, inNamespaceCall: state.inNamespaceCall });
|
|
8788
|
+
}
|
|
8689
8789
|
}
|
|
8690
8790
|
});
|
|
8691
8791
|
});
|
|
@@ -9118,13 +9218,22 @@ class PineTS {
|
|
|
9118
9218
|
if (!this._readyPromise) throw new Error("PineTS is not ready");
|
|
9119
9219
|
return this._readyPromise;
|
|
9120
9220
|
}
|
|
9121
|
-
async run(
|
|
9221
|
+
async run(pineTSCode, n, useTACache) {
|
|
9122
9222
|
await this.ready();
|
|
9123
9223
|
if (!n) n = this._periods;
|
|
9124
|
-
const context = new Context(
|
|
9125
|
-
|
|
9224
|
+
const context = new Context({
|
|
9225
|
+
marketData: this.data,
|
|
9226
|
+
source: this.source,
|
|
9227
|
+
tickerId: this.tickerId,
|
|
9228
|
+
timeframe: this.timeframe,
|
|
9229
|
+
limit: this.limit,
|
|
9230
|
+
sDate: this.sDate,
|
|
9231
|
+
eDate: this.eDate
|
|
9232
|
+
});
|
|
9233
|
+
context.pineTSCode = pineTSCode;
|
|
9234
|
+
context.useTACache = useTACache;
|
|
9126
9235
|
const transformer = transpile.bind(this);
|
|
9127
|
-
let
|
|
9236
|
+
let transpiledFn = transformer(pineTSCode);
|
|
9128
9237
|
const contextVarNames = ["const", "var", "let", "params"];
|
|
9129
9238
|
for (let i = this._periods - n, idx = n - 1; i < this._periods; i++, idx--) {
|
|
9130
9239
|
context.idx = i;
|
|
@@ -9138,7 +9247,7 @@ class PineTS {
|
|
|
9138
9247
|
context.data.ohlc4 = this.ohlc4.slice(idx);
|
|
9139
9248
|
context.data.openTime = this.openTime.slice(idx);
|
|
9140
9249
|
context.data.closeTime = this.closeTime.slice(idx);
|
|
9141
|
-
const result = await
|
|
9250
|
+
const result = await transpiledFn(context);
|
|
9142
9251
|
if (typeof result === "object") {
|
|
9143
9252
|
if (typeof context.result !== "object") {
|
|
9144
9253
|
context.result = {};
|
|
@@ -9169,26 +9278,28 @@ class PineTS {
|
|
|
9169
9278
|
}
|
|
9170
9279
|
}
|
|
9171
9280
|
|
|
9172
|
-
|
|
9173
|
-
* Copyright (C) 2025 Alaa-eddine KADDOURI
|
|
9174
|
-
*
|
|
9175
|
-
* This program is free software: you can redistribute it and/or modify
|
|
9176
|
-
* it under the terms of the GNU Affero General Public License as published by
|
|
9177
|
-
* the Free Software Foundation, either version 3 of the License, or
|
|
9178
|
-
* (at your option) any later version.
|
|
9179
|
-
*
|
|
9180
|
-
* This program is distributed in the hope that it will be useful,
|
|
9181
|
-
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
9182
|
-
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
9183
|
-
* GNU Affero General Public License for more details.
|
|
9184
|
-
*
|
|
9185
|
-
* You should have received a copy of the GNU Affero General Public License
|
|
9186
|
-
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
9187
|
-
*/class Core {
|
|
9281
|
+
class Core {
|
|
9188
9282
|
constructor(context) {
|
|
9189
9283
|
this.context = context;
|
|
9190
9284
|
}
|
|
9191
9285
|
color = {
|
|
9286
|
+
param: (source, index = 0) => {
|
|
9287
|
+
if (Array.isArray(source)) {
|
|
9288
|
+
return source[index];
|
|
9289
|
+
}
|
|
9290
|
+
return source;
|
|
9291
|
+
},
|
|
9292
|
+
rgb: (r, g, b, a) => a ? `rgba(${r}, ${g}, ${b}, ${a})` : `rgb(${r}, ${g}, ${b})`,
|
|
9293
|
+
new: (color, a) => {
|
|
9294
|
+
if (color && color.startsWith("#")) {
|
|
9295
|
+
const hex = color.slice(1);
|
|
9296
|
+
const r = parseInt(hex.slice(0, 2), 16);
|
|
9297
|
+
const g = parseInt(hex.slice(2, 4), 16);
|
|
9298
|
+
const b = parseInt(hex.slice(4, 6), 16);
|
|
9299
|
+
return a ? `rgba(${r}, ${g}, ${b}, ${a})` : `rgb(${r}, ${g}, ${b})`;
|
|
9300
|
+
}
|
|
9301
|
+
return a ? `rgba(${color}, ${a})` : color;
|
|
9302
|
+
},
|
|
9192
9303
|
white: "white",
|
|
9193
9304
|
lime: "lime",
|
|
9194
9305
|
green: "green",
|
|
@@ -9219,7 +9330,7 @@ class PineTS {
|
|
|
9219
9330
|
this.context.plots[title].data.push({
|
|
9220
9331
|
time: this.context.marketData[this.context.marketData.length - this.context.idx - 1].openTime,
|
|
9221
9332
|
value: series[0],
|
|
9222
|
-
options: this.extractPlotOptions(options)
|
|
9333
|
+
options: { ...this.extractPlotOptions(options), style: "char" }
|
|
9223
9334
|
});
|
|
9224
9335
|
}
|
|
9225
9336
|
plot(series, title, options) {
|
|
@@ -9236,30 +9347,13 @@ class PineTS {
|
|
|
9236
9347
|
return Array.isArray(series) ? isNaN(series[0]) : isNaN(series);
|
|
9237
9348
|
}
|
|
9238
9349
|
nz(series, replacement = 0) {
|
|
9239
|
-
|
|
9240
|
-
|
|
9241
|
-
|
|
9242
|
-
return isNaN(series) ? replacement : series;
|
|
9243
|
-
}
|
|
9350
|
+
const val = Array.isArray(series) ? series[0] : series;
|
|
9351
|
+
const rep = Array.isArray(series) ? replacement[0] : replacement;
|
|
9352
|
+
return isNaN(val) ? rep : val;
|
|
9244
9353
|
}
|
|
9245
9354
|
}
|
|
9246
9355
|
|
|
9247
|
-
|
|
9248
|
-
* Copyright (C) 2025 Alaa-eddine KADDOURI
|
|
9249
|
-
*
|
|
9250
|
-
* This program is free software: you can redistribute it and/or modify
|
|
9251
|
-
* it under the terms of the GNU Affero General Public License as published by
|
|
9252
|
-
* the Free Software Foundation, either version 3 of the License, or
|
|
9253
|
-
* (at your option) any later version.
|
|
9254
|
-
*
|
|
9255
|
-
* This program is distributed in the hope that it will be useful,
|
|
9256
|
-
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
9257
|
-
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
9258
|
-
* GNU Affero General Public License for more details.
|
|
9259
|
-
*
|
|
9260
|
-
* You should have received a copy of the GNU Affero General Public License
|
|
9261
|
-
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
9262
|
-
*/class Input {
|
|
9356
|
+
class Input {
|
|
9263
9357
|
constructor(context) {
|
|
9264
9358
|
this.context = context;
|
|
9265
9359
|
}
|
|
@@ -9313,87 +9407,92 @@ class PineTS {
|
|
|
9313
9407
|
}
|
|
9314
9408
|
}
|
|
9315
9409
|
|
|
9316
|
-
|
|
9317
|
-
* Copyright (C) 2025 Alaa-eddine KADDOURI
|
|
9318
|
-
*
|
|
9319
|
-
* This program is free software: you can redistribute it and/or modify
|
|
9320
|
-
* it under the terms of the GNU Affero General Public License as published by
|
|
9321
|
-
* the Free Software Foundation, either version 3 of the License, or
|
|
9322
|
-
* (at your option) any later version.
|
|
9323
|
-
*
|
|
9324
|
-
* This program is distributed in the hope that it will be useful,
|
|
9325
|
-
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
9326
|
-
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
9327
|
-
* GNU Affero General Public License for more details.
|
|
9328
|
-
*
|
|
9329
|
-
* You should have received a copy of the GNU Affero General Public License
|
|
9330
|
-
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
9331
|
-
*/class PineMath {
|
|
9410
|
+
class PineMath {
|
|
9332
9411
|
constructor(context) {
|
|
9333
9412
|
this.context = context;
|
|
9334
9413
|
}
|
|
9335
9414
|
_cache = {};
|
|
9336
|
-
param(source, index
|
|
9415
|
+
param(source, index, name) {
|
|
9416
|
+
if (!this.context.params[name]) this.context.params[name] = [];
|
|
9337
9417
|
if (Array.isArray(source)) {
|
|
9338
|
-
|
|
9418
|
+
if (index) {
|
|
9419
|
+
this.context.params[name] = source.slice(index);
|
|
9420
|
+
this.context.params[name].length = source.length;
|
|
9421
|
+
return this.context.params[name];
|
|
9422
|
+
}
|
|
9423
|
+
this.context.params[name] = source.slice(0);
|
|
9424
|
+
return this.context.params[name];
|
|
9425
|
+
} else {
|
|
9426
|
+
this.context.params[name][0] = source;
|
|
9427
|
+
return this.context.params[name];
|
|
9339
9428
|
}
|
|
9340
|
-
return source;
|
|
9341
9429
|
}
|
|
9342
|
-
abs(
|
|
9343
|
-
return Math.abs(
|
|
9430
|
+
abs(source) {
|
|
9431
|
+
return Math.abs(source[0]);
|
|
9344
9432
|
}
|
|
9345
|
-
pow(
|
|
9346
|
-
return Math.pow(
|
|
9433
|
+
pow(source, power) {
|
|
9434
|
+
return Math.pow(source[0], power[0]);
|
|
9347
9435
|
}
|
|
9348
|
-
sqrt(
|
|
9349
|
-
return Math.sqrt(
|
|
9436
|
+
sqrt(source) {
|
|
9437
|
+
return Math.sqrt(source[0]);
|
|
9350
9438
|
}
|
|
9351
|
-
log(
|
|
9352
|
-
return Math.log(
|
|
9439
|
+
log(source) {
|
|
9440
|
+
return Math.log(source[0]);
|
|
9353
9441
|
}
|
|
9354
|
-
ln(
|
|
9355
|
-
return Math.log(
|
|
9442
|
+
ln(source) {
|
|
9443
|
+
return Math.log(source[0]);
|
|
9356
9444
|
}
|
|
9357
|
-
exp(
|
|
9358
|
-
return Math.exp(
|
|
9445
|
+
exp(source) {
|
|
9446
|
+
return Math.exp(source[0]);
|
|
9359
9447
|
}
|
|
9360
|
-
floor(
|
|
9361
|
-
return Math.floor(
|
|
9448
|
+
floor(source) {
|
|
9449
|
+
return Math.floor(source[0]);
|
|
9362
9450
|
}
|
|
9363
|
-
ceil(
|
|
9364
|
-
return Math.ceil(
|
|
9451
|
+
ceil(source) {
|
|
9452
|
+
return Math.ceil(source[0]);
|
|
9365
9453
|
}
|
|
9366
|
-
round(
|
|
9367
|
-
return Math.round(
|
|
9454
|
+
round(source) {
|
|
9455
|
+
return Math.round(source[0]);
|
|
9368
9456
|
}
|
|
9369
9457
|
random() {
|
|
9370
9458
|
return Math.random();
|
|
9371
9459
|
}
|
|
9372
|
-
max(...
|
|
9373
|
-
|
|
9460
|
+
max(...source) {
|
|
9461
|
+
const arg = source.map((e) => Array.isArray(e) ? e[0] : e);
|
|
9462
|
+
return Math.max(...arg);
|
|
9374
9463
|
}
|
|
9375
|
-
min(...
|
|
9376
|
-
|
|
9464
|
+
min(...source) {
|
|
9465
|
+
const arg = source.map((e) => Array.isArray(e) ? e[0] : e);
|
|
9466
|
+
return Math.min(...arg);
|
|
9377
9467
|
}
|
|
9378
|
-
|
|
9379
|
-
|
|
9468
|
+
//sum of last n values
|
|
9469
|
+
sum(source, length) {
|
|
9470
|
+
const len = Array.isArray(length) ? length[0] : length;
|
|
9471
|
+
if (Array.isArray(source)) {
|
|
9472
|
+
return source.slice(0, len).reduce((a, b) => a + b, 0);
|
|
9473
|
+
}
|
|
9474
|
+
return source;
|
|
9380
9475
|
}
|
|
9381
|
-
|
|
9382
|
-
return Math.
|
|
9476
|
+
sin(source) {
|
|
9477
|
+
return Math.sin(source[0]);
|
|
9383
9478
|
}
|
|
9384
|
-
|
|
9385
|
-
return Math.
|
|
9479
|
+
cos(source) {
|
|
9480
|
+
return Math.cos(source[0]);
|
|
9386
9481
|
}
|
|
9387
|
-
|
|
9388
|
-
return Math.
|
|
9482
|
+
tan(source) {
|
|
9483
|
+
return Math.tan(source[0]);
|
|
9389
9484
|
}
|
|
9390
|
-
acos(
|
|
9391
|
-
return Math.acos(
|
|
9485
|
+
acos(source) {
|
|
9486
|
+
return Math.acos(source[0]);
|
|
9392
9487
|
}
|
|
9393
|
-
|
|
9394
|
-
return Math.
|
|
9488
|
+
asin(source) {
|
|
9489
|
+
return Math.asin(source[0]);
|
|
9395
9490
|
}
|
|
9396
|
-
|
|
9491
|
+
atan(source) {
|
|
9492
|
+
return Math.atan(source[0]);
|
|
9493
|
+
}
|
|
9494
|
+
avg(...sources) {
|
|
9495
|
+
const args = sources.map((e) => Array.isArray(e) ? e[0] : e);
|
|
9397
9496
|
return args.reduce((a, b) => {
|
|
9398
9497
|
const aVal = Array.isArray(a) ? a[0] : a;
|
|
9399
9498
|
const bVal = Array.isArray(b) ? b[0] : b;
|
|
@@ -9402,22 +9501,8 @@ class PineTS {
|
|
|
9402
9501
|
}
|
|
9403
9502
|
}
|
|
9404
9503
|
|
|
9405
|
-
|
|
9406
|
-
|
|
9407
|
-
*
|
|
9408
|
-
* This program is free software: you can redistribute it and/or modify
|
|
9409
|
-
* it under the terms of the GNU Affero General Public License as published by
|
|
9410
|
-
* the Free Software Foundation, either version 3 of the License, or
|
|
9411
|
-
* (at your option) any later version.
|
|
9412
|
-
*
|
|
9413
|
-
* This program is distributed in the hope that it will be useful,
|
|
9414
|
-
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
9415
|
-
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
9416
|
-
* GNU Affero General Public License for more details.
|
|
9417
|
-
*
|
|
9418
|
-
* You should have received a copy of the GNU Affero General Public License
|
|
9419
|
-
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
9420
|
-
*/class PineRequest {
|
|
9504
|
+
const TIMEFRAMES = ["1", "3", "5", "15", "30", "45", "60", "120", "180", "240", "D", "W", "M"];
|
|
9505
|
+
class PineRequest {
|
|
9421
9506
|
constructor(context) {
|
|
9422
9507
|
this.context = context;
|
|
9423
9508
|
}
|
|
@@ -9436,27 +9521,46 @@ class PineTS {
|
|
|
9436
9521
|
return [source, name];
|
|
9437
9522
|
}
|
|
9438
9523
|
}
|
|
9439
|
-
async security(symbol, timeframe, expression) {
|
|
9440
|
-
|
|
9524
|
+
async security(symbol, timeframe, expression, gaps = false, lookahead = false, ignore_invalid_symbol = false, currency = null, calc_bars_count = null) {
|
|
9525
|
+
const _symbol = symbol[0];
|
|
9526
|
+
const _timeframe = timeframe[0];
|
|
9527
|
+
const _expression = expression[0];
|
|
9528
|
+
const _expression_name = expression[1];
|
|
9529
|
+
const ctxTimeframeIdx = TIMEFRAMES.indexOf(this.context.timeframe);
|
|
9530
|
+
const reqTimeframeIdx = TIMEFRAMES.indexOf(_timeframe);
|
|
9531
|
+
if (ctxTimeframeIdx == -1 || reqTimeframeIdx == -1) {
|
|
9532
|
+
throw new Error("Invalid timeframe");
|
|
9533
|
+
}
|
|
9534
|
+
if (ctxTimeframeIdx > reqTimeframeIdx) {
|
|
9535
|
+
throw new Error("Only higher timeframes are supported for now");
|
|
9536
|
+
}
|
|
9537
|
+
if (ctxTimeframeIdx === reqTimeframeIdx) {
|
|
9538
|
+
return _expression;
|
|
9539
|
+
}
|
|
9540
|
+
const myOpenTime = this.context.data.openTime[0];
|
|
9541
|
+
const myCloseTime = this.context.data.closeTime[0];
|
|
9542
|
+
if (this.context.cache[_expression_name]) {
|
|
9543
|
+
const secContext2 = this.context.cache[_expression_name];
|
|
9544
|
+
const secContextIdx2 = this._findSecContextIdx(myOpenTime, myCloseTime, secContext2.data.openTime, secContext2.data.closeTime, lookahead);
|
|
9545
|
+
return secContextIdx2 == -1 ? NaN : secContext2.params[_expression_name][secContextIdx2];
|
|
9546
|
+
}
|
|
9547
|
+
const pineTS = new PineTS(this.context.source, _symbol, _timeframe, this.context.limit || 1e3, this.context.sDate, this.context.eDate);
|
|
9548
|
+
const secContext = await pineTS.run(this.context.pineTSCode);
|
|
9549
|
+
this.context.cache[_expression_name] = secContext;
|
|
9550
|
+
const secContextIdx = this._findSecContextIdx(myOpenTime, myCloseTime, secContext.data.openTime, secContext.data.closeTime, lookahead);
|
|
9551
|
+
return secContextIdx == -1 ? NaN : secContext.params[_expression_name][secContextIdx];
|
|
9552
|
+
}
|
|
9553
|
+
_findSecContextIdx(myOpenTime, myCloseTime, openTime, closeTime, lookahead = false) {
|
|
9554
|
+
for (let i = 0; i < openTime.length; i++) {
|
|
9555
|
+
if (openTime[i] <= myOpenTime && myCloseTime <= closeTime[i]) {
|
|
9556
|
+
return i + (lookahead ? 1 : 2);
|
|
9557
|
+
}
|
|
9558
|
+
}
|
|
9559
|
+
return -1;
|
|
9441
9560
|
}
|
|
9442
9561
|
}
|
|
9443
9562
|
|
|
9444
|
-
|
|
9445
|
-
* Copyright (C) 2025 Alaa-eddine KADDOURI
|
|
9446
|
-
*
|
|
9447
|
-
* This program is free software: you can redistribute it and/or modify
|
|
9448
|
-
* it under the terms of the GNU Affero General Public License as published by
|
|
9449
|
-
* the Free Software Foundation, either version 3 of the License, or
|
|
9450
|
-
* (at your option) any later version.
|
|
9451
|
-
*
|
|
9452
|
-
* This program is distributed in the hope that it will be useful,
|
|
9453
|
-
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
9454
|
-
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
9455
|
-
* GNU Affero General Public License for more details.
|
|
9456
|
-
*
|
|
9457
|
-
* You should have received a copy of the GNU Affero General Public License
|
|
9458
|
-
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
9459
|
-
*/class TechnicalAnalysis {
|
|
9563
|
+
class TechnicalAnalysis {
|
|
9460
9564
|
constructor(context) {
|
|
9461
9565
|
this.context = context;
|
|
9462
9566
|
}
|
|
@@ -9473,6 +9577,7 @@ class PineTS {
|
|
|
9473
9577
|
if (Array.isArray(source)) {
|
|
9474
9578
|
if (index) {
|
|
9475
9579
|
this.context.params[name] = source.slice(index);
|
|
9580
|
+
this.context.params[name].length = source.length;
|
|
9476
9581
|
return this.context.params[name];
|
|
9477
9582
|
}
|
|
9478
9583
|
this.context.params[name] = source.slice(0);
|
|
@@ -9488,9 +9593,21 @@ class PineTS {
|
|
|
9488
9593
|
const idx = this.context.idx;
|
|
9489
9594
|
return this.context.precision(result[idx]);
|
|
9490
9595
|
}
|
|
9491
|
-
sma(source, _period) {
|
|
9596
|
+
sma(source, _period, _cacheId) {
|
|
9492
9597
|
const period = Array.isArray(_period) ? _period[0] : _period;
|
|
9493
|
-
const
|
|
9598
|
+
const reversedSource = source.slice(0).reverse();
|
|
9599
|
+
if (this.context.useTACache && _cacheId) {
|
|
9600
|
+
if (!this.context.cache[_cacheId]) {
|
|
9601
|
+
this.context.cache[_cacheId] = {};
|
|
9602
|
+
}
|
|
9603
|
+
const cacheObj = this.context.cache[_cacheId];
|
|
9604
|
+
if (cacheObj) {
|
|
9605
|
+
const result2 = sma_cache(reversedSource, period, cacheObj);
|
|
9606
|
+
const idx2 = this.context.idx;
|
|
9607
|
+
return this.context.precision(result2[idx2]);
|
|
9608
|
+
}
|
|
9609
|
+
}
|
|
9610
|
+
const result = sma(reversedSource, period);
|
|
9494
9611
|
const idx = this.context.idx;
|
|
9495
9612
|
return this.context.precision(result[idx]);
|
|
9496
9613
|
}
|
|
@@ -9606,6 +9723,44 @@ class PineTS {
|
|
|
9606
9723
|
const idx = this.context.idx;
|
|
9607
9724
|
return [[this.context.precision(supertrend[idx]), direction[idx]]];
|
|
9608
9725
|
}
|
|
9726
|
+
crossover(source1, source2) {
|
|
9727
|
+
const current1 = Array.isArray(source1) ? source1[0] : source1;
|
|
9728
|
+
const current2 = Array.isArray(source2) ? source2[0] : source2;
|
|
9729
|
+
const prev1 = Array.isArray(source1) ? source1[1] : this.context.data.series[source1][1];
|
|
9730
|
+
const prev2 = Array.isArray(source2) ? source2[1] : this.context.data.series[source2][1];
|
|
9731
|
+
return prev1 < prev2 && current1 > current2;
|
|
9732
|
+
}
|
|
9733
|
+
crossunder(source1, source2) {
|
|
9734
|
+
const current1 = Array.isArray(source1) ? source1[0] : source1;
|
|
9735
|
+
const current2 = Array.isArray(source2) ? source2[0] : source2;
|
|
9736
|
+
const prev1 = Array.isArray(source1) ? source1[1] : this.context.data.series[source1][1];
|
|
9737
|
+
const prev2 = Array.isArray(source2) ? source2[1] : this.context.data.series[source2][1];
|
|
9738
|
+
return prev1 > prev2 && current1 < current2;
|
|
9739
|
+
}
|
|
9740
|
+
pivothigh(source, _leftbars, _rightbars) {
|
|
9741
|
+
if (_rightbars == void 0) {
|
|
9742
|
+
_rightbars = _leftbars;
|
|
9743
|
+
_leftbars = source;
|
|
9744
|
+
source = this.context.data.high;
|
|
9745
|
+
}
|
|
9746
|
+
const leftbars = Array.isArray(_leftbars) ? _leftbars[0] : _leftbars;
|
|
9747
|
+
const rightbars = Array.isArray(_rightbars) ? _rightbars[0] : _rightbars;
|
|
9748
|
+
const result = pivothigh(source.slice(0).reverse(), leftbars, rightbars);
|
|
9749
|
+
const idx = this.context.idx;
|
|
9750
|
+
return this.context.precision(result[idx]);
|
|
9751
|
+
}
|
|
9752
|
+
pivotlow(source, _leftbars, _rightbars) {
|
|
9753
|
+
if (_rightbars == void 0) {
|
|
9754
|
+
_rightbars = _leftbars;
|
|
9755
|
+
_leftbars = source;
|
|
9756
|
+
source = this.context.data.low;
|
|
9757
|
+
}
|
|
9758
|
+
const leftbars = Array.isArray(_leftbars) ? _leftbars[0] : _leftbars;
|
|
9759
|
+
const rightbars = Array.isArray(_rightbars) ? _rightbars[0] : _rightbars;
|
|
9760
|
+
const result = pivotlow(source.slice(0).reverse(), leftbars, rightbars);
|
|
9761
|
+
const idx = this.context.idx;
|
|
9762
|
+
return this.context.precision(result[idx]);
|
|
9763
|
+
}
|
|
9609
9764
|
}
|
|
9610
9765
|
function atr(high, low, close, period) {
|
|
9611
9766
|
const tr = new Array(high.length);
|
|
@@ -9679,6 +9834,35 @@ function rma(source, period) {
|
|
|
9679
9834
|
}
|
|
9680
9835
|
return result;
|
|
9681
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
|
+
}
|
|
9682
9866
|
function sma(source, period) {
|
|
9683
9867
|
const result = new Array(source.length).fill(NaN);
|
|
9684
9868
|
for (let i = period - 1; i < source.length; i++) {
|
|
@@ -9797,7 +9981,7 @@ function lowest(source, length) {
|
|
|
9797
9981
|
let min = Infinity;
|
|
9798
9982
|
for (let j = 0; j < length; j++) {
|
|
9799
9983
|
const value = source[i - j];
|
|
9800
|
-
if (isNaN(value)) {
|
|
9984
|
+
if (isNaN(value) || value === void 0) {
|
|
9801
9985
|
min = min === Infinity ? NaN : min;
|
|
9802
9986
|
} else {
|
|
9803
9987
|
min = Math.min(min, value);
|
|
@@ -9916,23 +10100,247 @@ function calculateSupertrend(high, low, close, factor, atrPeriod) {
|
|
|
9916
10100
|
}
|
|
9917
10101
|
return [supertrend, direction];
|
|
9918
10102
|
}
|
|
10103
|
+
function pivothigh(source, leftbars, rightbars) {
|
|
10104
|
+
const result = new Array(source.length).fill(NaN);
|
|
10105
|
+
for (let i = leftbars + rightbars; i < source.length; i++) {
|
|
10106
|
+
const pivot = source[i - rightbars];
|
|
10107
|
+
let isPivot = true;
|
|
10108
|
+
for (let j = 1; j <= leftbars; j++) {
|
|
10109
|
+
if (source[i - rightbars - j] >= pivot) {
|
|
10110
|
+
isPivot = false;
|
|
10111
|
+
break;
|
|
10112
|
+
}
|
|
10113
|
+
}
|
|
10114
|
+
if (isPivot) {
|
|
10115
|
+
for (let j = 1; j <= rightbars; j++) {
|
|
10116
|
+
if (source[i - rightbars + j] >= pivot) {
|
|
10117
|
+
isPivot = false;
|
|
10118
|
+
break;
|
|
10119
|
+
}
|
|
10120
|
+
}
|
|
10121
|
+
}
|
|
10122
|
+
if (isPivot) {
|
|
10123
|
+
result[i] = pivot;
|
|
10124
|
+
}
|
|
10125
|
+
}
|
|
10126
|
+
return result;
|
|
10127
|
+
}
|
|
10128
|
+
function pivotlow(source, leftbars, rightbars) {
|
|
10129
|
+
const result = new Array(source.length).fill(NaN);
|
|
10130
|
+
for (let i = leftbars + rightbars; i < source.length; i++) {
|
|
10131
|
+
const pivot = source[i - rightbars];
|
|
10132
|
+
let isPivot = true;
|
|
10133
|
+
for (let j = 1; j <= leftbars; j++) {
|
|
10134
|
+
if (source[i - rightbars - j] <= pivot) {
|
|
10135
|
+
isPivot = false;
|
|
10136
|
+
break;
|
|
10137
|
+
}
|
|
10138
|
+
}
|
|
10139
|
+
if (isPivot) {
|
|
10140
|
+
for (let j = 1; j <= rightbars; j++) {
|
|
10141
|
+
if (source[i - rightbars + j] <= pivot) {
|
|
10142
|
+
isPivot = false;
|
|
10143
|
+
break;
|
|
10144
|
+
}
|
|
10145
|
+
}
|
|
10146
|
+
}
|
|
10147
|
+
if (isPivot) {
|
|
10148
|
+
result[i] = pivot;
|
|
10149
|
+
}
|
|
10150
|
+
}
|
|
10151
|
+
return result;
|
|
10152
|
+
}
|
|
9919
10153
|
|
|
9920
|
-
class
|
|
9921
|
-
constructor(
|
|
9922
|
-
this.
|
|
9923
|
-
|
|
9924
|
-
|
|
9925
|
-
|
|
9926
|
-
|
|
9927
|
-
|
|
9928
|
-
this.core = {
|
|
9929
|
-
plotchar: core.plotchar.bind(core),
|
|
9930
|
-
na: core.na.bind(core),
|
|
9931
|
-
color: core.color,
|
|
9932
|
-
plot: core.plot.bind(core),
|
|
9933
|
-
nz: core.nz.bind(core)
|
|
9934
|
-
};
|
|
10154
|
+
class PineArrayObject {
|
|
10155
|
+
constructor(array) {
|
|
10156
|
+
this.array = array;
|
|
10157
|
+
}
|
|
10158
|
+
}
|
|
10159
|
+
class PineArray {
|
|
10160
|
+
constructor(context) {
|
|
10161
|
+
this.context = context;
|
|
9935
10162
|
}
|
|
10163
|
+
_cache = {};
|
|
10164
|
+
param(source, index = 0) {
|
|
10165
|
+
if (Array.isArray(source)) {
|
|
10166
|
+
return source[index];
|
|
10167
|
+
}
|
|
10168
|
+
return source;
|
|
10169
|
+
}
|
|
10170
|
+
/**
|
|
10171
|
+
* This function simulates PineScript's array.get() function
|
|
10172
|
+
* @param id - the array object to get the value from
|
|
10173
|
+
* @param index - the index of the value to get
|
|
10174
|
+
* @returns the value at the given index
|
|
10175
|
+
*/
|
|
10176
|
+
get(id, index) {
|
|
10177
|
+
return id.array[index];
|
|
10178
|
+
}
|
|
10179
|
+
set(id, index, value) {
|
|
10180
|
+
id.array[index] = value;
|
|
10181
|
+
}
|
|
10182
|
+
push(id, value) {
|
|
10183
|
+
id.array.push(value);
|
|
10184
|
+
}
|
|
10185
|
+
// Basic statistics
|
|
10186
|
+
sum(id) {
|
|
10187
|
+
return id.array.reduce((a, b) => a + (isNaN(b) ? 0 : b), 0);
|
|
10188
|
+
}
|
|
10189
|
+
avg(id) {
|
|
10190
|
+
return this.sum(id) / id.array.length;
|
|
10191
|
+
}
|
|
10192
|
+
min(id, nth = 0) {
|
|
10193
|
+
const sorted = [...id.array].sort((a, b) => a - b);
|
|
10194
|
+
return sorted[nth] ?? this.context.NA;
|
|
10195
|
+
}
|
|
10196
|
+
max(id, nth = 0) {
|
|
10197
|
+
const sorted = [...id.array].sort((a, b) => b - a);
|
|
10198
|
+
return sorted[nth] ?? this.context.NA;
|
|
10199
|
+
}
|
|
10200
|
+
size(id) {
|
|
10201
|
+
return id.array.length;
|
|
10202
|
+
}
|
|
10203
|
+
// Array creation
|
|
10204
|
+
new_bool(size, initial_value = false) {
|
|
10205
|
+
return new PineArrayObject(Array(size).fill(initial_value));
|
|
10206
|
+
}
|
|
10207
|
+
new_float(size, initial_value = NaN) {
|
|
10208
|
+
return new PineArrayObject(Array(size).fill(initial_value));
|
|
10209
|
+
}
|
|
10210
|
+
new_int(size, initial_value = 0) {
|
|
10211
|
+
return new PineArrayObject(Array(size).fill(Math.round(initial_value)));
|
|
10212
|
+
}
|
|
10213
|
+
new_string(size, initial_value = "") {
|
|
10214
|
+
return new PineArrayObject(Array(size).fill(initial_value));
|
|
10215
|
+
}
|
|
10216
|
+
new(size, initial_value) {
|
|
10217
|
+
return new PineArrayObject(Array(size).fill(initial_value));
|
|
10218
|
+
}
|
|
10219
|
+
// Array operations
|
|
10220
|
+
slice(id, start, end) {
|
|
10221
|
+
const adjustedEnd = end !== void 0 ? end + 1 : void 0;
|
|
10222
|
+
return new PineArrayObject(id.array.slice(start, adjustedEnd));
|
|
10223
|
+
}
|
|
10224
|
+
reverse(id) {
|
|
10225
|
+
id.array.reverse();
|
|
10226
|
+
}
|
|
10227
|
+
includes(id, value) {
|
|
10228
|
+
return id.array.includes(value);
|
|
10229
|
+
}
|
|
10230
|
+
indexof(id, value) {
|
|
10231
|
+
return id.array.indexOf(value);
|
|
10232
|
+
}
|
|
10233
|
+
lastindexof(id, value) {
|
|
10234
|
+
return id.array.lastIndexOf(value);
|
|
10235
|
+
}
|
|
10236
|
+
// More complex functions
|
|
10237
|
+
stdev(id, biased = true) {
|
|
10238
|
+
const mean = this.avg(id);
|
|
10239
|
+
const deviations = id.array.map((x) => Math.pow(x - mean, 2));
|
|
10240
|
+
const divisor = biased ? id.array.length : id.array.length - 1;
|
|
10241
|
+
return Math.sqrt(this.sum(new PineArrayObject(deviations)) / divisor);
|
|
10242
|
+
}
|
|
10243
|
+
variance(id, biased = true) {
|
|
10244
|
+
const mean = this.avg(id);
|
|
10245
|
+
const deviations = id.array.map((x) => Math.pow(x - mean, 2));
|
|
10246
|
+
const divisor = biased ? id.array.length : id.array.length - 1;
|
|
10247
|
+
return this.sum(new PineArrayObject(deviations)) / divisor;
|
|
10248
|
+
}
|
|
10249
|
+
covariance(arr1, arr2, biased = true) {
|
|
10250
|
+
if (arr1.array.length !== arr2.array.length || arr1.array.length < 2) return NaN;
|
|
10251
|
+
const divisor = biased ? arr1.array.length : arr1.array.length - 1;
|
|
10252
|
+
const mean1 = this.avg(arr1);
|
|
10253
|
+
const mean2 = this.avg(arr2);
|
|
10254
|
+
let sum = 0;
|
|
10255
|
+
for (let i = 0; i < arr1.array.length; i++) {
|
|
10256
|
+
sum += (arr1.array[i] - mean1) * (arr2.array[i] - mean2);
|
|
10257
|
+
}
|
|
10258
|
+
return sum / divisor;
|
|
10259
|
+
}
|
|
10260
|
+
// Additional utility methods
|
|
10261
|
+
first(id) {
|
|
10262
|
+
return id.array.length > 0 ? id.array[0] : this.context.NA;
|
|
10263
|
+
}
|
|
10264
|
+
last(id) {
|
|
10265
|
+
return id.array.length > 0 ? id.array[id.array.length - 1] : this.context.NA;
|
|
10266
|
+
}
|
|
10267
|
+
clear(id) {
|
|
10268
|
+
id.array.length = 0;
|
|
10269
|
+
}
|
|
10270
|
+
join(id, separator = ",") {
|
|
10271
|
+
return id.array.join(separator);
|
|
10272
|
+
}
|
|
10273
|
+
/** Array Manipulation Functions */
|
|
10274
|
+
abs(id) {
|
|
10275
|
+
return new PineArrayObject(id.array.map((val) => Math.abs(val)));
|
|
10276
|
+
}
|
|
10277
|
+
concat(id, other) {
|
|
10278
|
+
id.array.push(...other.array);
|
|
10279
|
+
return id;
|
|
10280
|
+
}
|
|
10281
|
+
copy(id) {
|
|
10282
|
+
return new PineArrayObject([...id.array]);
|
|
10283
|
+
}
|
|
10284
|
+
every(id, callback) {
|
|
10285
|
+
return id.array.every(callback);
|
|
10286
|
+
}
|
|
10287
|
+
fill(id, value, start = 0, end) {
|
|
10288
|
+
const length = id.array.length;
|
|
10289
|
+
const adjustedEnd = end !== void 0 ? Math.min(end, length) : length;
|
|
10290
|
+
for (let i = start; i < adjustedEnd; i++) {
|
|
10291
|
+
id.array[i] = value;
|
|
10292
|
+
}
|
|
10293
|
+
}
|
|
10294
|
+
from(source) {
|
|
10295
|
+
return new PineArrayObject([...source]);
|
|
10296
|
+
}
|
|
10297
|
+
insert(id, index, value) {
|
|
10298
|
+
id.array.splice(index, 0, value);
|
|
10299
|
+
}
|
|
10300
|
+
pop(id) {
|
|
10301
|
+
return id.array.pop();
|
|
10302
|
+
}
|
|
10303
|
+
range(id) {
|
|
10304
|
+
return this.max(id) - this.min(id);
|
|
10305
|
+
}
|
|
10306
|
+
remove(id, index) {
|
|
10307
|
+
if (index >= 0 && index < id.array.length) {
|
|
10308
|
+
return id.array.splice(index, 1)[0];
|
|
10309
|
+
}
|
|
10310
|
+
return this.context.NA;
|
|
10311
|
+
}
|
|
10312
|
+
shift(id) {
|
|
10313
|
+
return id.array.shift();
|
|
10314
|
+
}
|
|
10315
|
+
sort(id, order = "asc") {
|
|
10316
|
+
id.array.sort((a, b) => order === "asc" ? a - b : b - a);
|
|
10317
|
+
}
|
|
10318
|
+
sort_indices(id, comparator) {
|
|
10319
|
+
const indices = id.array.map((_, index) => index);
|
|
10320
|
+
indices.sort((a, b) => {
|
|
10321
|
+
const valA = id.array[a];
|
|
10322
|
+
const valB = id.array[b];
|
|
10323
|
+
return comparator ? comparator(valA, valB) : valA - valB;
|
|
10324
|
+
});
|
|
10325
|
+
return new PineArrayObject(indices);
|
|
10326
|
+
}
|
|
10327
|
+
standardize(id) {
|
|
10328
|
+
const mean = this.avg(id);
|
|
10329
|
+
const stdev = this.stdev(id);
|
|
10330
|
+
if (stdev === 0) {
|
|
10331
|
+
return new PineArrayObject(id.array.map(() => 0));
|
|
10332
|
+
}
|
|
10333
|
+
return new PineArrayObject(id.array.map((x) => (x - mean) / stdev));
|
|
10334
|
+
}
|
|
10335
|
+
unshift(id, value) {
|
|
10336
|
+
id.array.unshift(value);
|
|
10337
|
+
}
|
|
10338
|
+
some(id, callback) {
|
|
10339
|
+
return id.array.some(callback);
|
|
10340
|
+
}
|
|
10341
|
+
}
|
|
10342
|
+
|
|
10343
|
+
class Context {
|
|
9936
10344
|
data = {
|
|
9937
10345
|
open: [],
|
|
9938
10346
|
high: [],
|
|
@@ -9943,11 +10351,16 @@ class Context {
|
|
|
9943
10351
|
hlc3: [],
|
|
9944
10352
|
ohlc4: []
|
|
9945
10353
|
};
|
|
10354
|
+
cache = {};
|
|
10355
|
+
useTACache = false;
|
|
10356
|
+
NA = NaN;
|
|
9946
10357
|
math;
|
|
9947
10358
|
ta;
|
|
9948
10359
|
input;
|
|
9949
10360
|
request;
|
|
10361
|
+
array;
|
|
9950
10362
|
core;
|
|
10363
|
+
lang;
|
|
9951
10364
|
idx = 0;
|
|
9952
10365
|
params = {};
|
|
9953
10366
|
const = {};
|
|
@@ -9955,7 +10368,44 @@ class Context {
|
|
|
9955
10368
|
let = {};
|
|
9956
10369
|
result = void 0;
|
|
9957
10370
|
plots = {};
|
|
10371
|
+
marketData;
|
|
10372
|
+
source;
|
|
10373
|
+
tickerId;
|
|
9958
10374
|
timeframe = "";
|
|
10375
|
+
limit;
|
|
10376
|
+
sDate;
|
|
10377
|
+
eDate;
|
|
10378
|
+
pineTSCode;
|
|
10379
|
+
constructor({
|
|
10380
|
+
marketData,
|
|
10381
|
+
source,
|
|
10382
|
+
tickerId,
|
|
10383
|
+
timeframe,
|
|
10384
|
+
limit,
|
|
10385
|
+
sDate,
|
|
10386
|
+
eDate
|
|
10387
|
+
}) {
|
|
10388
|
+
this.marketData = marketData;
|
|
10389
|
+
this.source = source;
|
|
10390
|
+
this.tickerId = tickerId;
|
|
10391
|
+
this.timeframe = timeframe;
|
|
10392
|
+
this.limit = limit;
|
|
10393
|
+
this.sDate = sDate;
|
|
10394
|
+
this.eDate = eDate;
|
|
10395
|
+
this.math = new PineMath(this);
|
|
10396
|
+
this.ta = new TechnicalAnalysis(this);
|
|
10397
|
+
this.input = new Input(this);
|
|
10398
|
+
this.request = new PineRequest(this);
|
|
10399
|
+
this.array = new PineArray(this);
|
|
10400
|
+
const core = new Core(this);
|
|
10401
|
+
this.core = {
|
|
10402
|
+
plotchar: core.plotchar.bind(core),
|
|
10403
|
+
na: core.na.bind(core),
|
|
10404
|
+
color: core.color,
|
|
10405
|
+
plot: core.plot.bind(core),
|
|
10406
|
+
nz: core.nz.bind(core)
|
|
10407
|
+
};
|
|
10408
|
+
}
|
|
9959
10409
|
//#region [Runtime functions] ===========================
|
|
9960
10410
|
/**
|
|
9961
10411
|
* this function is used to initialize the target variable with the source array
|
|
@@ -9982,12 +10432,13 @@ class Context {
|
|
|
9982
10432
|
return trg;
|
|
9983
10433
|
}
|
|
9984
10434
|
/**
|
|
9985
|
-
|
|
9986
|
-
|
|
9987
|
-
|
|
9988
|
-
|
|
9989
|
-
|
|
9990
|
-
|
|
10435
|
+
* this function is used to set the floating point precision of a number
|
|
10436
|
+
* by default it is set to 10 decimals which is the same as pine script
|
|
10437
|
+
* @param n - the number to be precision
|
|
10438
|
+
* @param decimals - the number of decimals to precision to
|
|
10439
|
+
|
|
10440
|
+
* @returns the precision number
|
|
10441
|
+
*/
|
|
9991
10442
|
precision(n, decimals = 10) {
|
|
9992
10443
|
if (typeof n !== "number" || isNaN(n)) return n;
|
|
9993
10444
|
return Number(n.toFixed(decimals));
|
|
@@ -10006,6 +10457,7 @@ class Context {
|
|
|
10006
10457
|
if (Array.isArray(source)) {
|
|
10007
10458
|
if (index) {
|
|
10008
10459
|
this.params[name] = source.slice(index);
|
|
10460
|
+
this.params[name].length = source.length;
|
|
10009
10461
|
return this.params[name];
|
|
10010
10462
|
}
|
|
10011
10463
|
this.params[name] = source.slice(0);
|
|
@@ -10018,22 +10470,7 @@ class Context {
|
|
|
10018
10470
|
//#endregion
|
|
10019
10471
|
}
|
|
10020
10472
|
|
|
10021
|
-
|
|
10022
|
-
* Copyright (C) 2025 Alaa-eddine KADDOURI
|
|
10023
|
-
*
|
|
10024
|
-
* This program is free software: you can redistribute it and/or modify
|
|
10025
|
-
* it under the terms of the GNU Affero General Public License as published by
|
|
10026
|
-
* the Free Software Foundation, either version 3 of the License, or
|
|
10027
|
-
* (at your option) any later version.
|
|
10028
|
-
*
|
|
10029
|
-
* This program is distributed in the hope that it will be useful,
|
|
10030
|
-
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
10031
|
-
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
10032
|
-
* GNU Affero General Public License for more details.
|
|
10033
|
-
*
|
|
10034
|
-
* You should have received a copy of the GNU Affero General Public License
|
|
10035
|
-
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
10036
|
-
*/const BINANCE_API_URL = "https://api.binance.com/api/v3";
|
|
10473
|
+
const BINANCE_API_URL = "https://api.binance.com/api/v3";
|
|
10037
10474
|
const timeframe_to_binance = {
|
|
10038
10475
|
"1": "1m",
|
|
10039
10476
|
// 1 minute
|
|
@@ -10055,6 +10492,8 @@ const timeframe_to_binance = {
|
|
|
10055
10492
|
// 3 hours (not directly supported by Binance, needs custom handling)
|
|
10056
10493
|
"240": "4h",
|
|
10057
10494
|
// 4 hours
|
|
10495
|
+
"4H": "4h",
|
|
10496
|
+
// 4 hours
|
|
10058
10497
|
"1D": "1d",
|
|
10059
10498
|
// 1 day
|
|
10060
10499
|
D: "1d",
|
|
@@ -10068,15 +10507,119 @@ const timeframe_to_binance = {
|
|
|
10068
10507
|
M: "1M"
|
|
10069
10508
|
// 1 month
|
|
10070
10509
|
};
|
|
10510
|
+
class CacheManager {
|
|
10511
|
+
cache;
|
|
10512
|
+
cacheDuration;
|
|
10513
|
+
constructor(cacheDuration = 5 * 60 * 1e3) {
|
|
10514
|
+
this.cache = /* @__PURE__ */ new Map();
|
|
10515
|
+
this.cacheDuration = cacheDuration;
|
|
10516
|
+
}
|
|
10517
|
+
generateKey(params) {
|
|
10518
|
+
return Object.entries(params).filter(([_, value]) => value !== void 0).map(([key, value]) => `${key}:${value}`).join("|");
|
|
10519
|
+
}
|
|
10520
|
+
get(params) {
|
|
10521
|
+
const key = this.generateKey(params);
|
|
10522
|
+
const cached = this.cache.get(key);
|
|
10523
|
+
if (!cached) return null;
|
|
10524
|
+
if (Date.now() - cached.timestamp > this.cacheDuration) {
|
|
10525
|
+
this.cache.delete(key);
|
|
10526
|
+
return null;
|
|
10527
|
+
}
|
|
10528
|
+
return cached.data;
|
|
10529
|
+
}
|
|
10530
|
+
set(params, data) {
|
|
10531
|
+
const key = this.generateKey(params);
|
|
10532
|
+
this.cache.set(key, {
|
|
10533
|
+
data,
|
|
10534
|
+
timestamp: Date.now()
|
|
10535
|
+
});
|
|
10536
|
+
}
|
|
10537
|
+
clear() {
|
|
10538
|
+
this.cache.clear();
|
|
10539
|
+
}
|
|
10540
|
+
// Optional: method to remove expired entries
|
|
10541
|
+
cleanup() {
|
|
10542
|
+
const now = Date.now();
|
|
10543
|
+
for (const [key, entry] of this.cache.entries()) {
|
|
10544
|
+
if (now - entry.timestamp > this.cacheDuration) {
|
|
10545
|
+
this.cache.delete(key);
|
|
10546
|
+
}
|
|
10547
|
+
}
|
|
10548
|
+
}
|
|
10549
|
+
}
|
|
10071
10550
|
class BinanceProvider {
|
|
10551
|
+
cacheManager;
|
|
10552
|
+
constructor() {
|
|
10553
|
+
this.cacheManager = new CacheManager(5 * 60 * 1e3);
|
|
10554
|
+
}
|
|
10555
|
+
async getMarketDataInterval(tickerId, timeframe, sDate, eDate) {
|
|
10556
|
+
try {
|
|
10557
|
+
const interval = timeframe_to_binance[timeframe.toUpperCase()];
|
|
10558
|
+
if (!interval) {
|
|
10559
|
+
console.error(`Unsupported timeframe: ${timeframe}`);
|
|
10560
|
+
return [];
|
|
10561
|
+
}
|
|
10562
|
+
const timeframeDurations = {
|
|
10563
|
+
"1m": 60 * 1e3,
|
|
10564
|
+
"3m": 3 * 60 * 1e3,
|
|
10565
|
+
"5m": 5 * 60 * 1e3,
|
|
10566
|
+
"15m": 15 * 60 * 1e3,
|
|
10567
|
+
"30m": 30 * 60 * 1e3,
|
|
10568
|
+
"1h": 60 * 60 * 1e3,
|
|
10569
|
+
"2h": 2 * 60 * 60 * 1e3,
|
|
10570
|
+
"4h": 4 * 60 * 60 * 1e3,
|
|
10571
|
+
"1d": 24 * 60 * 60 * 1e3,
|
|
10572
|
+
"1w": 7 * 24 * 60 * 60 * 1e3,
|
|
10573
|
+
"1M": 30 * 24 * 60 * 60 * 1e3
|
|
10574
|
+
};
|
|
10575
|
+
let allData = [];
|
|
10576
|
+
let currentStart = sDate;
|
|
10577
|
+
const endTime = eDate;
|
|
10578
|
+
const intervalDuration = timeframeDurations[interval];
|
|
10579
|
+
if (!intervalDuration) {
|
|
10580
|
+
console.error(`Duration not defined for interval: ${interval}`);
|
|
10581
|
+
return [];
|
|
10582
|
+
}
|
|
10583
|
+
while (currentStart < endTime) {
|
|
10584
|
+
const chunkEnd = Math.min(currentStart + 1e3 * intervalDuration, endTime);
|
|
10585
|
+
const data = await this.getMarketData(
|
|
10586
|
+
tickerId,
|
|
10587
|
+
timeframe,
|
|
10588
|
+
1e3,
|
|
10589
|
+
// Max allowed by Binance
|
|
10590
|
+
currentStart,
|
|
10591
|
+
chunkEnd
|
|
10592
|
+
);
|
|
10593
|
+
if (data.length === 0) break;
|
|
10594
|
+
allData = allData.concat(data);
|
|
10595
|
+
currentStart = data[data.length - 1].closeTime + 1;
|
|
10596
|
+
if (data.length < 1e3) break;
|
|
10597
|
+
}
|
|
10598
|
+
return allData;
|
|
10599
|
+
} catch (error) {
|
|
10600
|
+
console.error("Error in getMarketDataInterval:", error);
|
|
10601
|
+
return [];
|
|
10602
|
+
}
|
|
10603
|
+
}
|
|
10604
|
+
//TODO : allow querying more than 1000 klines
|
|
10605
|
+
//TODO : immplement cache
|
|
10072
10606
|
async getMarketData(tickerId, timeframe, limit, sDate, eDate) {
|
|
10073
10607
|
try {
|
|
10608
|
+
const cacheParams = { tickerId, timeframe, limit, sDate, eDate };
|
|
10609
|
+
const cachedData = this.cacheManager.get(cacheParams);
|
|
10610
|
+
if (cachedData) {
|
|
10611
|
+
console.log("cache hit", tickerId, timeframe, limit, sDate, eDate);
|
|
10612
|
+
return cachedData;
|
|
10613
|
+
}
|
|
10074
10614
|
const interval = timeframe_to_binance[timeframe.toUpperCase()];
|
|
10075
10615
|
if (!interval) {
|
|
10076
10616
|
console.error(`Unsupported timeframe: ${timeframe}`);
|
|
10077
10617
|
return [];
|
|
10078
10618
|
}
|
|
10079
10619
|
let url = `${BINANCE_API_URL}/klines?symbol=${tickerId}&interval=${interval}`;
|
|
10620
|
+
if (!limit && sDate && eDate) {
|
|
10621
|
+
return this.getMarketDataInterval(tickerId, timeframe, sDate, eDate);
|
|
10622
|
+
}
|
|
10080
10623
|
if (limit) {
|
|
10081
10624
|
url += `&limit=${limit}`;
|
|
10082
10625
|
}
|
|
@@ -10093,13 +10636,13 @@ class BinanceProvider {
|
|
|
10093
10636
|
const result = await response.json();
|
|
10094
10637
|
const data = result.map((item) => {
|
|
10095
10638
|
return {
|
|
10096
|
-
openTime: parseInt(item[0])
|
|
10639
|
+
openTime: parseInt(item[0]),
|
|
10097
10640
|
open: parseFloat(item[1]),
|
|
10098
10641
|
high: parseFloat(item[2]),
|
|
10099
10642
|
low: parseFloat(item[3]),
|
|
10100
10643
|
close: parseFloat(item[4]),
|
|
10101
10644
|
volume: parseFloat(item[5]),
|
|
10102
|
-
closeTime: parseInt(item[6])
|
|
10645
|
+
closeTime: parseInt(item[6]),
|
|
10103
10646
|
quoteAssetVolume: parseFloat(item[7]),
|
|
10104
10647
|
numberOfTrades: parseInt(item[8]),
|
|
10105
10648
|
takerBuyBaseAssetVolume: parseFloat(item[9]),
|
|
@@ -10107,6 +10650,7 @@ class BinanceProvider {
|
|
|
10107
10650
|
ignore: item[11]
|
|
10108
10651
|
};
|
|
10109
10652
|
});
|
|
10653
|
+
this.cacheManager.set(cacheParams, data);
|
|
10110
10654
|
return data;
|
|
10111
10655
|
} catch (error) {
|
|
10112
10656
|
console.error("Error in binance.klines:", error);
|
|
@@ -10117,6 +10661,7 @@ class BinanceProvider {
|
|
|
10117
10661
|
|
|
10118
10662
|
const Provider = {
|
|
10119
10663
|
Binance: new BinanceProvider()
|
|
10664
|
+
//TODO : add other providers (polygon, etc.)
|
|
10120
10665
|
};
|
|
10121
10666
|
|
|
10122
10667
|
exports.Context = Context;
|