@tradejs/core 3.0.1 → 3.1.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 +2 -0
- package/dist/backtest.mjs +4 -4
- package/dist/chunk-NYJU3J7Y.mjs +265 -0
- package/dist/http.d.mts +8 -0
- package/dist/http.d.ts +8 -0
- package/dist/http.js +79 -0
- package/dist/http.mjs +54 -0
- package/dist/indicators.mjs +2 -2
- package/dist/runtimeTrades.d.mts +82 -0
- package/dist/runtimeTrades.d.ts +82 -0
- package/dist/runtimeTrades.js +577 -0
- package/dist/runtimeTrades.mjs +478 -0
- package/dist/strategies.mjs +2 -2
- package/dist/trade.mjs +14 -250
- package/package.json +12 -2
- package/dist/{chunk-QRRQA4TU.mjs → chunk-OLHUGX7X.mjs} +3 -3
package/dist/trade.mjs
CHANGED
|
@@ -1,254 +1,18 @@
|
|
|
1
1
|
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
var toFiniteNumberOrNull = (value) => typeof value === "number" && Number.isFinite(value) ? value : null;
|
|
17
|
-
var toRecord = (value) => value && typeof value === "object" ? value : null;
|
|
18
|
-
var getMedian = (values) => {
|
|
19
|
-
if (!values.length) {
|
|
20
|
-
return null;
|
|
21
|
-
}
|
|
22
|
-
const sorted = [...values].sort((left, right) => left - right);
|
|
23
|
-
const middle = Math.floor(sorted.length / 2);
|
|
24
|
-
if (sorted.length % 2 === 0) {
|
|
25
|
-
const left = sorted[middle - 1];
|
|
26
|
-
const right = sorted[middle];
|
|
27
|
-
return left == null || right == null ? null : (left + right) / 2;
|
|
28
|
-
}
|
|
29
|
-
return sorted[middle] ?? null;
|
|
30
|
-
};
|
|
31
|
-
var extractCandleClose = (value) => {
|
|
32
|
-
if (typeof value === "number" && Number.isFinite(value)) {
|
|
33
|
-
return value;
|
|
34
|
-
}
|
|
35
|
-
const record = toRecord(value);
|
|
36
|
-
return toFiniteNumberOrNull(record?.close);
|
|
37
|
-
};
|
|
38
|
-
var extractCloseSeries = ({
|
|
39
|
-
closes,
|
|
40
|
-
candles
|
|
41
|
-
}) => {
|
|
42
|
-
const source = Array.isArray(closes) && closes.length ? closes : candles;
|
|
43
|
-
if (!Array.isArray(source)) {
|
|
44
|
-
return [];
|
|
45
|
-
}
|
|
46
|
-
return source.map(extractCandleClose).filter((value) => value != null && value > 0);
|
|
47
|
-
};
|
|
48
|
-
var getSignalIntervalMs = (interval) => {
|
|
49
|
-
if (typeof interval !== "string") {
|
|
50
|
-
return null;
|
|
51
|
-
}
|
|
52
|
-
try {
|
|
53
|
-
return intervalToMs(interval);
|
|
54
|
-
} catch {
|
|
55
|
-
return null;
|
|
56
|
-
}
|
|
57
|
-
};
|
|
58
|
-
var getSignalCandleSeries = (signal) => {
|
|
59
|
-
const indicators = toRecord(signal?.indicators);
|
|
60
|
-
if (!indicators) {
|
|
61
|
-
return null;
|
|
62
|
-
}
|
|
63
|
-
const intervalKey = (() => {
|
|
64
|
-
switch (signal?.interval) {
|
|
65
|
-
case "15":
|
|
66
|
-
return "candles15m";
|
|
67
|
-
case "60":
|
|
68
|
-
return "candles1h";
|
|
69
|
-
case "240":
|
|
70
|
-
return "candles4h";
|
|
71
|
-
case "D":
|
|
72
|
-
return "candles1d";
|
|
73
|
-
default:
|
|
74
|
-
return null;
|
|
75
|
-
}
|
|
76
|
-
})();
|
|
77
|
-
const keys = [
|
|
78
|
-
intervalKey,
|
|
79
|
-
"candles15m",
|
|
80
|
-
"candles1h",
|
|
81
|
-
"candles4h",
|
|
82
|
-
"candles1d"
|
|
83
|
-
].filter((key) => Boolean(key));
|
|
84
|
-
for (const key of keys) {
|
|
85
|
-
const value = indicators[key];
|
|
86
|
-
if (Array.isArray(value) && value.length > 1) {
|
|
87
|
-
return value;
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
const candle = indicators.candle;
|
|
91
|
-
const prevCandle = indicators.prevCandle;
|
|
92
|
-
return prevCandle && candle ? [prevCandle, candle] : null;
|
|
93
|
-
};
|
|
94
|
-
var calculateEffectiveSlippageBps = ({
|
|
95
|
-
baseSlippageBps = BACKTEST_BASE_SLIPPAGE_BPS,
|
|
96
|
-
spreadBps,
|
|
97
|
-
spreadMultiplier = BACKTEST_SPREAD_SLIPPAGE_MULTIPLIER,
|
|
98
|
-
marketImpactBps = BACKTEST_MARKET_IMPACT_BPS,
|
|
99
|
-
delayRiskBps
|
|
100
|
-
} = {}) => {
|
|
101
|
-
const base = toNonNegativeFiniteNumber(baseSlippageBps);
|
|
102
|
-
const spread = toNonNegativeFiniteNumber(spreadBps);
|
|
103
|
-
const multiplier = toNonNegativeFiniteNumber(spreadMultiplier);
|
|
104
|
-
const marketImpact = toNonNegativeFiniteNumber(marketImpactBps);
|
|
105
|
-
const delayRisk = toNonNegativeFiniteNumber(delayRiskBps);
|
|
106
|
-
return base + spread * multiplier + marketImpact + delayRisk;
|
|
107
|
-
};
|
|
108
|
-
var calculateExecutionSlippageBreakdown = ({
|
|
109
|
-
baseSlippageBps = BACKTEST_BASE_SLIPPAGE_BPS,
|
|
110
|
-
spreadBps,
|
|
111
|
-
spreadMultiplier = BACKTEST_SPREAD_SLIPPAGE_MULTIPLIER,
|
|
112
|
-
marketImpactBps = BACKTEST_MARKET_IMPACT_BPS,
|
|
113
|
-
delayRiskBps
|
|
114
|
-
} = {}) => {
|
|
115
|
-
const base = toNonNegativeFiniteNumber(baseSlippageBps);
|
|
116
|
-
const spread = toNonNegativeFiniteNumber(spreadBps);
|
|
117
|
-
const multiplier = toNonNegativeFiniteNumber(spreadMultiplier);
|
|
118
|
-
const spreadSlippage = spread * multiplier;
|
|
119
|
-
const marketImpact = toNonNegativeFiniteNumber(marketImpactBps);
|
|
120
|
-
const delayRisk = toNonNegativeFiniteNumber(delayRiskBps);
|
|
121
|
-
return {
|
|
122
|
-
baseSlippageBps: base,
|
|
123
|
-
spreadBps: spread,
|
|
124
|
-
spreadMultiplier: multiplier,
|
|
125
|
-
spreadSlippageBps: spreadSlippage,
|
|
126
|
-
marketImpactBps: marketImpact,
|
|
127
|
-
delayRiskBps: delayRisk,
|
|
128
|
-
effectiveSlippageBps: base + spreadSlippage + marketImpact + delayRisk
|
|
129
|
-
};
|
|
130
|
-
};
|
|
131
|
-
var calculateDelayRiskBps = ({
|
|
132
|
-
closes,
|
|
133
|
-
candles,
|
|
134
|
-
intervalMs,
|
|
135
|
-
expectedDelayMs = BACKTEST_EXPECTED_DELAY_MS,
|
|
136
|
-
lookbackCandles = BACKTEST_DELAY_RISK_LOOKBACK_CANDLES,
|
|
137
|
-
multiplier = BACKTEST_DELAY_RISK_MULTIPLIER,
|
|
138
|
-
maxBps = BACKTEST_DELAY_RISK_MAX_BPS
|
|
139
|
-
} = {}) => {
|
|
140
|
-
const normalizedLookback = Math.max(
|
|
141
|
-
1,
|
|
142
|
-
Math.trunc(toNonNegativeFiniteNumber(lookbackCandles, 1))
|
|
143
|
-
);
|
|
144
|
-
const closeSeries = extractCloseSeries({ closes, candles }).slice(
|
|
145
|
-
-(normalizedLookback + 1)
|
|
146
|
-
);
|
|
147
|
-
if (closeSeries.length < 2) {
|
|
148
|
-
return null;
|
|
149
|
-
}
|
|
150
|
-
const moveBps = [];
|
|
151
|
-
for (let index = 1; index < closeSeries.length; index += 1) {
|
|
152
|
-
const previous = closeSeries[index - 1];
|
|
153
|
-
const current = closeSeries[index];
|
|
154
|
-
if (previous != null && current != null && previous > 0 && current > 0) {
|
|
155
|
-
moveBps.push(Math.abs(current / previous - 1) * 1e4);
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
const medianMoveBps = getMedian(moveBps);
|
|
159
|
-
if (medianMoveBps == null) {
|
|
160
|
-
return null;
|
|
161
|
-
}
|
|
162
|
-
const delayScale = typeof intervalMs === "number" && Number.isFinite(intervalMs) && intervalMs > 0 && typeof expectedDelayMs === "number" && Number.isFinite(expectedDelayMs) && expectedDelayMs > 0 ? Math.sqrt(expectedDelayMs / intervalMs) : 1;
|
|
163
|
-
const rawDelayRisk = medianMoveBps * delayScale * toNonNegativeFiniteNumber(multiplier);
|
|
164
|
-
const cappedDelayRisk = Math.min(
|
|
165
|
-
rawDelayRisk,
|
|
166
|
-
toNonNegativeFiniteNumber(maxBps, Number.POSITIVE_INFINITY)
|
|
167
|
-
);
|
|
168
|
-
return Number.isFinite(cappedDelayRisk) ? cappedDelayRisk : null;
|
|
169
|
-
};
|
|
170
|
-
var slippageBpsToRate = (slippageBps) => toNonNegativeFiniteNumber(slippageBps) / 1e4;
|
|
171
|
-
var applyExecutionSlippage = ({
|
|
172
|
-
price,
|
|
173
|
-
direction,
|
|
174
|
-
stage,
|
|
175
|
-
...modelParams
|
|
176
|
-
}) => {
|
|
177
|
-
const slippageRate = slippageBpsToRate(
|
|
178
|
-
calculateExecutionSlippageBreakdown(modelParams).effectiveSlippageBps
|
|
179
|
-
);
|
|
180
|
-
if (!slippageRate) {
|
|
181
|
-
return price;
|
|
182
|
-
}
|
|
183
|
-
const sign = direction === "LONG" ? stage === "entry" ? 1 : -1 : stage === "entry" ? -1 : 1;
|
|
184
|
-
return price * (1 + sign * slippageRate);
|
|
185
|
-
};
|
|
186
|
-
var extractExecutionSpreadBps = (signal) => {
|
|
187
|
-
const additionalIndicators = toRecord(signal?.additionalIndicators);
|
|
188
|
-
const explicitSlippage = toRecord(additionalIndicators?.executionSlippage);
|
|
189
|
-
return toFiniteNumberOrNull(explicitSlippage?.spreadBps);
|
|
190
|
-
};
|
|
191
|
-
var extractExecutionMarketImpactBps = (signal) => {
|
|
192
|
-
const additionalIndicators = toRecord(signal?.additionalIndicators);
|
|
193
|
-
const explicitSlippage = toRecord(additionalIndicators?.executionSlippage);
|
|
194
|
-
return toFiniteNumberOrNull(explicitSlippage?.marketImpactBps);
|
|
195
|
-
};
|
|
196
|
-
var extractExecutionDelayRiskBps = (signal) => {
|
|
197
|
-
const additionalIndicators = toRecord(signal?.additionalIndicators);
|
|
198
|
-
const explicitSlippage = toRecord(additionalIndicators?.executionSlippage);
|
|
199
|
-
const explicitDelayRisk = toFiniteNumberOrNull(
|
|
200
|
-
explicitSlippage?.delayRiskBps
|
|
201
|
-
);
|
|
202
|
-
if (explicitDelayRisk != null) {
|
|
203
|
-
return explicitDelayRisk;
|
|
204
|
-
}
|
|
205
|
-
return calculateDelayRiskBps({
|
|
206
|
-
candles: getSignalCandleSeries(signal),
|
|
207
|
-
intervalMs: getSignalIntervalMs(signal?.interval)
|
|
208
|
-
});
|
|
209
|
-
};
|
|
210
|
-
|
|
211
|
-
// src/trade.ts
|
|
212
|
-
var ORDER_LINK_PREFIX = "tjs-";
|
|
213
|
-
var ORDER_LINK_SEPARATOR = "--";
|
|
214
|
-
var STRATEGY_SLUG_LENGTH = 10;
|
|
215
|
-
var STRATEGY_HASH_LENGTH = 5;
|
|
216
|
-
var toBase36Hash = (value) => {
|
|
217
|
-
let hash = 0;
|
|
218
|
-
for (let index = 0; index < value.length; index += 1) {
|
|
219
|
-
hash = hash * 31 + value.charCodeAt(index) >>> 0;
|
|
220
|
-
}
|
|
221
|
-
return hash.toString(36).padStart(STRATEGY_HASH_LENGTH, "0");
|
|
222
|
-
};
|
|
223
|
-
var normalizeStrategyOrderLinkKey = (strategyName) => {
|
|
224
|
-
const normalized = String(strategyName ?? "").trim().toLowerCase();
|
|
225
|
-
if (!normalized) {
|
|
226
|
-
return null;
|
|
227
|
-
}
|
|
228
|
-
const slug = normalized.replace(/[^a-z0-9]+/g, "").slice(0, STRATEGY_SLUG_LENGTH) || "strategy";
|
|
229
|
-
const hash = toBase36Hash(normalized).slice(0, STRATEGY_HASH_LENGTH);
|
|
230
|
-
return `${slug}-${hash}`;
|
|
231
|
-
};
|
|
232
|
-
var createRuntimeOrderLinkPrefix = (strategyName) => {
|
|
233
|
-
const strategyKey = normalizeStrategyOrderLinkKey(strategyName);
|
|
234
|
-
return strategyKey ? `${ORDER_LINK_PREFIX}${strategyKey}${ORDER_LINK_SEPARATOR}` : ORDER_LINK_PREFIX;
|
|
235
|
-
};
|
|
236
|
-
var parseStrategyOrderLinkKey = (orderLinkId) => {
|
|
237
|
-
const normalized = String(orderLinkId ?? "").trim().toLowerCase();
|
|
238
|
-
if (!normalized.startsWith(ORDER_LINK_PREFIX)) {
|
|
239
|
-
return null;
|
|
240
|
-
}
|
|
241
|
-
const remainder = normalized.slice(ORDER_LINK_PREFIX.length);
|
|
242
|
-
const separatorIndex = remainder.indexOf(ORDER_LINK_SEPARATOR);
|
|
243
|
-
if (separatorIndex <= 0) {
|
|
244
|
-
return null;
|
|
245
|
-
}
|
|
246
|
-
const strategyPart = remainder.slice(0, separatorIndex).trim();
|
|
247
|
-
if (!strategyPart) {
|
|
248
|
-
return null;
|
|
249
|
-
}
|
|
250
|
-
return strategyPart;
|
|
251
|
-
};
|
|
2
|
+
applyExecutionSlippage,
|
|
3
|
+
calculateDelayRiskBps,
|
|
4
|
+
calculateEffectiveSlippageBps,
|
|
5
|
+
calculateExecutionSlippageBreakdown,
|
|
6
|
+
createRuntimeOrderLinkPrefix,
|
|
7
|
+
extractExecutionDelayRiskBps,
|
|
8
|
+
extractExecutionMarketImpactBps,
|
|
9
|
+
extractExecutionSpreadBps,
|
|
10
|
+
normalizeStrategyOrderLinkKey,
|
|
11
|
+
parseStrategyOrderLinkKey,
|
|
12
|
+
slippageBpsToRate
|
|
13
|
+
} from "./chunk-NYJU3J7Y.mjs";
|
|
14
|
+
import "./chunk-M7QGVZ3J.mjs";
|
|
15
|
+
import "./chunk-MKCQSB4H.mjs";
|
|
252
16
|
export {
|
|
253
17
|
applyExecutionSlippage,
|
|
254
18
|
calculateDelayRiskBps,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tradejs/core",
|
|
3
|
-
"version": "3.0
|
|
3
|
+
"version": "3.1.0",
|
|
4
4
|
"description": "MIT-licensed browser-safe API for TradeJS config, strategy authoring, figures, and shared helpers.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"tradejs",
|
|
@@ -83,6 +83,11 @@
|
|
|
83
83
|
"import": "./dist/grid.mjs",
|
|
84
84
|
"require": "./dist/grid.js"
|
|
85
85
|
},
|
|
86
|
+
"./http": {
|
|
87
|
+
"types": "./dist/http.d.ts",
|
|
88
|
+
"import": "./dist/http.mjs",
|
|
89
|
+
"require": "./dist/http.js"
|
|
90
|
+
},
|
|
86
91
|
"./indicators": {
|
|
87
92
|
"types": "./dist/indicators.d.ts",
|
|
88
93
|
"import": "./dist/indicators.mjs",
|
|
@@ -93,6 +98,11 @@
|
|
|
93
98
|
"import": "./dist/math.mjs",
|
|
94
99
|
"require": "./dist/math.js"
|
|
95
100
|
},
|
|
101
|
+
"./runtimeTrades": {
|
|
102
|
+
"types": "./dist/runtimeTrades.d.ts",
|
|
103
|
+
"import": "./dist/runtimeTrades.mjs",
|
|
104
|
+
"require": "./dist/runtimeTrades.js"
|
|
105
|
+
},
|
|
96
106
|
"./strategies": {
|
|
97
107
|
"types": "./dist/strategies.d.ts",
|
|
98
108
|
"import": "./dist/strategies.mjs",
|
|
@@ -115,7 +125,7 @@
|
|
|
115
125
|
}
|
|
116
126
|
},
|
|
117
127
|
"dependencies": {
|
|
118
|
-
"@tradejs/types": "^3.0
|
|
128
|
+
"@tradejs/types": "^3.1.0",
|
|
119
129
|
"date-fns": "^3.6.0",
|
|
120
130
|
"fast-technical-indicators": "^1.1.4",
|
|
121
131
|
"klinecharts": "10.0.0-alpha9",
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import {
|
|
2
2
|
cloneArrayValues
|
|
3
3
|
} from "./chunk-M7QGVZ3J.mjs";
|
|
4
|
-
import {
|
|
5
|
-
round
|
|
6
|
-
} from "./chunk-AYC2QVKI.mjs";
|
|
7
4
|
import {
|
|
8
5
|
toMs
|
|
9
6
|
} from "./chunk-S4KHOAXM.mjs";
|
|
7
|
+
import {
|
|
8
|
+
round
|
|
9
|
+
} from "./chunk-AYC2QVKI.mjs";
|
|
10
10
|
import {
|
|
11
11
|
CORRELATION_WINDOW,
|
|
12
12
|
ML_BASE_CANDLES_WINDOW,
|