@tradejs/node 3.1.29-beta.287 → 3.1.29-beta.289
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/dist/backtest.js +434 -224
- package/dist/backtest.mjs +10 -5
- package/dist/{chunk-KOV3U6ZZ.mjs → chunk-KYGJBMKE.mjs} +407 -202
- package/dist/diagnostics.js +408 -203
- package/dist/diagnostics.mjs +1 -1
- package/dist/mcp.d.mts +1 -1
- package/dist/mcp.d.ts +1 -1
- package/dist/mcp.js +423 -218
- package/dist/mcp.mjs +1 -1
- package/dist/registry.js +408 -203
- package/dist/registry.mjs +1 -1
- package/dist/runtimeDashboard.js +410 -205
- package/dist/runtimeDashboard.mjs +1 -1
- package/dist/runtimeStrategies.js +408 -203
- package/dist/runtimeStrategies.mjs +1 -1
- package/dist/strategies.d.mts +32 -22
- package/dist/strategies.d.ts +32 -22
- package/dist/strategies.js +416 -211
- package/dist/strategies.mjs +1 -1
- package/package.json +5 -5
|
@@ -16,6 +16,206 @@ import {
|
|
|
16
16
|
assessSignalWithJev
|
|
17
17
|
} from "./chunk-YR55Q355.mjs";
|
|
18
18
|
|
|
19
|
+
// src/strategy/entrySubmissionValidation.ts
|
|
20
|
+
import { logger } from "@tradejs/infra/logger";
|
|
21
|
+
|
|
22
|
+
// src/strategy/entryExecutionValidation.ts
|
|
23
|
+
var EntrySubmissionValidationError = class extends Error {
|
|
24
|
+
constructor() {
|
|
25
|
+
super(...arguments);
|
|
26
|
+
this.name = "EntrySubmissionValidationError";
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
var resolveEntryExecutionLimits = (config, direction) => {
|
|
30
|
+
const side = config[direction];
|
|
31
|
+
const minimum = side && typeof side === "object" && "minRiskRatio" in side ? side.minRiskRatio : void 0;
|
|
32
|
+
const maximum = config.MAX_LOSS_VALUE;
|
|
33
|
+
if (minimum == null && maximum == null) return void 0;
|
|
34
|
+
const minimumNetRiskRatio = minimum == null ? void 0 : Number(minimum);
|
|
35
|
+
const maxLossValue = maximum == null ? void 0 : Number(maximum);
|
|
36
|
+
const feeRate = Number(config.RISK_FEE_RATE ?? 0);
|
|
37
|
+
const slippage = Number(config.RISK_SLIPPAGE_BPS ?? 0);
|
|
38
|
+
const impact = Number(config.RISK_MARKET_IMPACT_BPS ?? 0);
|
|
39
|
+
const slippageBps = slippage + impact;
|
|
40
|
+
if (minimumNetRiskRatio != null && (!Number.isFinite(minimumNetRiskRatio) || minimumNetRiskRatio < 0) || maxLossValue != null && (!Number.isFinite(maxLossValue) || maxLossValue <= 0) || !Number.isFinite(feeRate) || feeRate < 0 || feeRate >= 1 || !Number.isFinite(slippage) || slippage < 0 || !Number.isFinite(impact) || impact < 0 || !Number.isFinite(slippageBps) || slippageBps < 0 || slippageBps >= 1e4)
|
|
41
|
+
throw new Error("INVALID_ENTRY_EXECUTION_LIMITS");
|
|
42
|
+
return { minimumNetRiskRatio, maxLossValue, feeRate, slippageBps };
|
|
43
|
+
};
|
|
44
|
+
var validateEntryExecution = ({
|
|
45
|
+
direction,
|
|
46
|
+
price,
|
|
47
|
+
qty,
|
|
48
|
+
stopLossPrice,
|
|
49
|
+
takeProfitPrice,
|
|
50
|
+
limits
|
|
51
|
+
}) => {
|
|
52
|
+
if (!Number.isFinite(price) || price <= 0 || !Number.isFinite(qty) || qty <= 0 || stopLossPrice == null || !Number.isFinite(stopLossPrice) || stopLossPrice <= 0 || !Number.isFinite(takeProfitPrice) || takeProfitPrice <= 0)
|
|
53
|
+
throw new EntrySubmissionValidationError(
|
|
54
|
+
"ENTRY_ECONOMICS_INPUTS_UNAVAILABLE"
|
|
55
|
+
);
|
|
56
|
+
const side = direction === "LONG" ? 1 : -1;
|
|
57
|
+
if (side * (price - stopLossPrice) <= 0 || side * (takeProfitPrice - price) <= 0)
|
|
58
|
+
throw new EntrySubmissionValidationError(
|
|
59
|
+
"INVALID_ENTRY_PROTECTION_AT_SUBMISSION"
|
|
60
|
+
);
|
|
61
|
+
const slippage = limits.slippageBps / 1e4;
|
|
62
|
+
const cost = (value, sign) => value * slippage + value * (1 + sign * slippage) * limits.feeRate;
|
|
63
|
+
const entryCost = cost(price, side);
|
|
64
|
+
const lossPerUnit = Math.abs(price - stopLossPrice) + entryCost + cost(stopLossPrice, -side);
|
|
65
|
+
const rewardPerUnit = Math.max(
|
|
66
|
+
0,
|
|
67
|
+
Math.abs(takeProfitPrice - price) - entryCost - cost(takeProfitPrice, -side)
|
|
68
|
+
);
|
|
69
|
+
const netRiskRatio = rewardPerUnit / lossPerUnit;
|
|
70
|
+
const plannedLossValue = qty * lossPerUnit;
|
|
71
|
+
if (!Number.isFinite(netRiskRatio) || !Number.isFinite(plannedLossValue))
|
|
72
|
+
throw new EntrySubmissionValidationError(
|
|
73
|
+
"ENTRY_ECONOMICS_INPUTS_UNAVAILABLE"
|
|
74
|
+
);
|
|
75
|
+
if (limits.minimumNetRiskRatio != null && netRiskRatio <= limits.minimumNetRiskRatio)
|
|
76
|
+
throw new EntrySubmissionValidationError(
|
|
77
|
+
"ENTRY_RR_BELOW_MIN_AT_SUBMISSION"
|
|
78
|
+
);
|
|
79
|
+
if (limits.maxLossValue != null && plannedLossValue > limits.maxLossValue * (1 + 1e-9))
|
|
80
|
+
throw new EntrySubmissionValidationError(
|
|
81
|
+
"ENTRY_RISK_EXCEEDS_MAX_LOSS_AT_SUBMISSION"
|
|
82
|
+
);
|
|
83
|
+
return { netRiskRatio, plannedLossValue };
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
// src/strategy/entrySubmissionValidation.ts
|
|
87
|
+
var MAX_ENTRY_QUOTE_AGE_MS = 5e3;
|
|
88
|
+
var toFiniteNumberOrNull = (value) => typeof value === "number" && Number.isFinite(value) ? value : null;
|
|
89
|
+
var getOrderArrivalSnapshot = async ({
|
|
90
|
+
connector,
|
|
91
|
+
symbol,
|
|
92
|
+
requireTimestamp = false
|
|
93
|
+
}) => {
|
|
94
|
+
if (typeof connector.getTopOfBookTicker !== "function") {
|
|
95
|
+
return {
|
|
96
|
+
arrivalSnapshotTime: Date.now(),
|
|
97
|
+
arrivalSource: "unavailable",
|
|
98
|
+
bid: null,
|
|
99
|
+
ask: null,
|
|
100
|
+
arrivalMid: null,
|
|
101
|
+
spreadBps: null
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
try {
|
|
105
|
+
const ticker = await connector.getTopOfBookTicker(symbol);
|
|
106
|
+
const arrivalSnapshotTime = toFiniteNumberOrNull(ticker?.timestamp);
|
|
107
|
+
const timestampUnavailable = requireTimestamp && arrivalSnapshotTime == null;
|
|
108
|
+
const bid = timestampUnavailable ? null : toFiniteNumberOrNull(ticker?.bidPrice);
|
|
109
|
+
const ask = timestampUnavailable ? null : toFiniteNumberOrNull(ticker?.askPrice);
|
|
110
|
+
const arrivalMid = bid != null && ask != null ? (bid + ask) / 2 : null;
|
|
111
|
+
const spreadBps = bid != null && ask != null && arrivalMid != null && arrivalMid > 0 ? (ask - bid) / arrivalMid * 1e4 : null;
|
|
112
|
+
return {
|
|
113
|
+
arrivalSnapshotTime: arrivalSnapshotTime ?? Date.now(),
|
|
114
|
+
arrivalSource: "top_of_book",
|
|
115
|
+
bid,
|
|
116
|
+
ask,
|
|
117
|
+
arrivalMid,
|
|
118
|
+
spreadBps
|
|
119
|
+
};
|
|
120
|
+
} catch (error) {
|
|
121
|
+
logger.warn(
|
|
122
|
+
"runtime order arrival snapshot failed: %s %s",
|
|
123
|
+
symbol,
|
|
124
|
+
error?.message || String(error)
|
|
125
|
+
);
|
|
126
|
+
return {
|
|
127
|
+
arrivalSnapshotTime: Date.now(),
|
|
128
|
+
arrivalSource: "top_of_book_error",
|
|
129
|
+
bid: null,
|
|
130
|
+
ask: null,
|
|
131
|
+
arrivalMid: null,
|
|
132
|
+
spreadBps: null
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
};
|
|
136
|
+
var validateEntryProtectionAtArrival = ({
|
|
137
|
+
direction,
|
|
138
|
+
signalPrice,
|
|
139
|
+
bid,
|
|
140
|
+
ask,
|
|
141
|
+
arrivalMid,
|
|
142
|
+
takeProfits,
|
|
143
|
+
stopLossPrice
|
|
144
|
+
}) => {
|
|
145
|
+
const entryReference = direction === "LONG" ? ask ?? arrivalMid ?? signalPrice : bid ?? arrivalMid ?? signalPrice;
|
|
146
|
+
const stopReference = direction === "LONG" ? bid ?? arrivalMid ?? signalPrice : ask ?? arrivalMid ?? signalPrice;
|
|
147
|
+
if (!Number.isFinite(entryReference) || entryReference <= 0 || !Number.isFinite(stopReference) || stopReference <= 0) {
|
|
148
|
+
throw new Error("INVALID_ENTRY_REFERENCE_AT_ARRIVAL");
|
|
149
|
+
}
|
|
150
|
+
for (const takeProfit of takeProfits) {
|
|
151
|
+
if (!Number.isFinite(takeProfit.price) || takeProfit.price <= 0 || (direction === "LONG" ? takeProfit.price <= entryReference : takeProfit.price >= entryReference)) {
|
|
152
|
+
throw new Error("TAKE_PROFIT_CROSSED_BEFORE_ENTRY");
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
if (stopLossPrice != null && (!Number.isFinite(stopLossPrice) || stopLossPrice <= 0 || (direction === "LONG" ? stopLossPrice >= stopReference : stopLossPrice <= stopReference))) {
|
|
156
|
+
throw new Error("STOP_LOSS_CROSSED_BEFORE_ENTRY");
|
|
157
|
+
}
|
|
158
|
+
};
|
|
159
|
+
var createEntrySubmissionValidation = ({
|
|
160
|
+
connector,
|
|
161
|
+
symbol,
|
|
162
|
+
direction,
|
|
163
|
+
takeProfits,
|
|
164
|
+
stopLossPrice,
|
|
165
|
+
limits,
|
|
166
|
+
onValidated
|
|
167
|
+
}) => {
|
|
168
|
+
if (!limits) return void 0;
|
|
169
|
+
if (!connector.capabilities?.orderSubmissionValidation)
|
|
170
|
+
throw new EntrySubmissionValidationError(
|
|
171
|
+
"ENTRY_SUBMISSION_VALIDATION_UNSUPPORTED"
|
|
172
|
+
);
|
|
173
|
+
return async ({ qty, executionPrice }) => {
|
|
174
|
+
const simulated = Reflect.get(connector, "__tradejsTestConnector") === true;
|
|
175
|
+
const snapshot2 = simulated && executionPrice != null ? {
|
|
176
|
+
arrivalSnapshotTime: Date.now(),
|
|
177
|
+
arrivalSource: "simulation",
|
|
178
|
+
bid: executionPrice,
|
|
179
|
+
ask: executionPrice,
|
|
180
|
+
arrivalMid: executionPrice,
|
|
181
|
+
spreadBps: 0
|
|
182
|
+
} : await getOrderArrivalSnapshot({
|
|
183
|
+
connector,
|
|
184
|
+
symbol,
|
|
185
|
+
requireTimestamp: true
|
|
186
|
+
});
|
|
187
|
+
const age = Date.now() - snapshot2.arrivalSnapshotTime;
|
|
188
|
+
if (snapshot2.bid == null || snapshot2.ask == null || !Number.isFinite(snapshot2.bid) || !Number.isFinite(snapshot2.ask) || snapshot2.bid <= 0 || snapshot2.ask < snapshot2.bid)
|
|
189
|
+
throw new EntrySubmissionValidationError(
|
|
190
|
+
"ENTRY_QUOTE_UNAVAILABLE_AT_SUBMISSION"
|
|
191
|
+
);
|
|
192
|
+
if (!Number.isFinite(age) || age < -MAX_ENTRY_QUOTE_AGE_MS || age > MAX_ENTRY_QUOTE_AGE_MS)
|
|
193
|
+
throw new EntrySubmissionValidationError(
|
|
194
|
+
"ENTRY_QUOTE_STALE_AT_SUBMISSION"
|
|
195
|
+
);
|
|
196
|
+
const price = direction === "LONG" ? snapshot2.ask : snapshot2.bid;
|
|
197
|
+
validateEntryProtectionAtArrival({
|
|
198
|
+
direction,
|
|
199
|
+
signalPrice: price,
|
|
200
|
+
bid: snapshot2.bid,
|
|
201
|
+
ask: snapshot2.ask,
|
|
202
|
+
arrivalMid: snapshot2.arrivalMid,
|
|
203
|
+
takeProfits,
|
|
204
|
+
stopLossPrice
|
|
205
|
+
});
|
|
206
|
+
const prices = takeProfits.map((tp) => tp.price);
|
|
207
|
+
const economics = validateEntryExecution({
|
|
208
|
+
direction,
|
|
209
|
+
price,
|
|
210
|
+
qty,
|
|
211
|
+
stopLossPrice,
|
|
212
|
+
takeProfitPrice: direction === "LONG" ? Math.max(...prices) : Math.min(...prices),
|
|
213
|
+
limits
|
|
214
|
+
});
|
|
215
|
+
onValidated?.(snapshot2, economics);
|
|
216
|
+
};
|
|
217
|
+
};
|
|
218
|
+
|
|
19
219
|
// src/binanceBreadthUniverses.ts
|
|
20
220
|
import { createHash } from "crypto";
|
|
21
221
|
|
|
@@ -328,7 +528,7 @@ import {
|
|
|
328
528
|
getLatestMarketBreadth,
|
|
329
529
|
getLatestMarketTradeFlow
|
|
330
530
|
} from "@tradejs/infra/timescale/marketContext";
|
|
331
|
-
import { logger } from "@tradejs/infra/logger";
|
|
531
|
+
import { logger as logger2 } from "@tradejs/infra/logger";
|
|
332
532
|
import { refreshSignalBaseContextGateFeatures } from "@tradejs/core/strategies";
|
|
333
533
|
|
|
334
534
|
// src/strategyHelpers/marketContextErrors.ts
|
|
@@ -399,7 +599,7 @@ var parseEnabledFlag = (value, env) => {
|
|
|
399
599
|
if (normalized === "live") return env !== "BACKTEST";
|
|
400
600
|
return false;
|
|
401
601
|
};
|
|
402
|
-
var
|
|
602
|
+
var toFiniteNumberOrNull2 = (value) => {
|
|
403
603
|
const numeric = typeof value === "number" ? value : Number(value);
|
|
404
604
|
return Number.isFinite(numeric) ? numeric : null;
|
|
405
605
|
};
|
|
@@ -664,14 +864,14 @@ var toTradeFlowContext = (row, interval) => row ? {
|
|
|
664
864
|
asOfTs: row.ts.getTime(),
|
|
665
865
|
ageMs: row.ageMs,
|
|
666
866
|
stale: row.stale,
|
|
667
|
-
trades:
|
|
668
|
-
buyPressurePct:
|
|
669
|
-
buyBaseVolume:
|
|
670
|
-
sellBaseVolume:
|
|
671
|
-
buyQuoteVolume:
|
|
672
|
-
sellQuoteVolume:
|
|
673
|
-
netBaseDelta:
|
|
674
|
-
netQuoteDelta:
|
|
867
|
+
trades: toFiniteNumberOrNull2(row.trades),
|
|
868
|
+
buyPressurePct: toFiniteNumberOrNull2(row.buyPressurePct),
|
|
869
|
+
buyBaseVolume: toFiniteNumberOrNull2(row.buyBaseVolume),
|
|
870
|
+
sellBaseVolume: toFiniteNumberOrNull2(row.sellBaseVolume),
|
|
871
|
+
buyQuoteVolume: toFiniteNumberOrNull2(row.buyQuoteVolume),
|
|
872
|
+
sellQuoteVolume: toFiniteNumberOrNull2(row.sellQuoteVolume),
|
|
873
|
+
netBaseDelta: toFiniteNumberOrNull2(row.netBaseDelta),
|
|
874
|
+
netQuoteDelta: toFiniteNumberOrNull2(row.netQuoteDelta)
|
|
675
875
|
} : null;
|
|
676
876
|
var getCachedReferenceRows = ({
|
|
677
877
|
referenceSymbols,
|
|
@@ -760,16 +960,16 @@ var toMarketBreadthContext = (breadth, interval) => ({
|
|
|
760
960
|
asOfTs: breadth.ts.getTime(),
|
|
761
961
|
ageMs: breadth.ageMs,
|
|
762
962
|
stale: breadth.stale,
|
|
763
|
-
symbolsCount:
|
|
764
|
-
advancers:
|
|
765
|
-
decliners:
|
|
766
|
-
unchanged:
|
|
767
|
-
advanceDeclineRatio:
|
|
768
|
-
pctAboveMa20:
|
|
769
|
-
pctAboveMa50:
|
|
770
|
-
equalWeightedReturn:
|
|
771
|
-
volumeWeightedReturn:
|
|
772
|
-
dispersion:
|
|
963
|
+
symbolsCount: toFiniteNumberOrNull2(breadth.symbolsCount),
|
|
964
|
+
advancers: toFiniteNumberOrNull2(breadth.advancers),
|
|
965
|
+
decliners: toFiniteNumberOrNull2(breadth.decliners),
|
|
966
|
+
unchanged: toFiniteNumberOrNull2(breadth.unchanged),
|
|
967
|
+
advanceDeclineRatio: toFiniteNumberOrNull2(breadth.advanceDeclineRatio),
|
|
968
|
+
pctAboveMa20: toFiniteNumberOrNull2(breadth.pctAboveMa20),
|
|
969
|
+
pctAboveMa50: toFiniteNumberOrNull2(breadth.pctAboveMa50),
|
|
970
|
+
equalWeightedReturn: toFiniteNumberOrNull2(breadth.equalWeightedReturn),
|
|
971
|
+
volumeWeightedReturn: toFiniteNumberOrNull2(breadth.volumeWeightedReturn),
|
|
972
|
+
dispersion: toFiniteNumberOrNull2(breadth.dispersion)
|
|
773
973
|
});
|
|
774
974
|
var enrichSignalWithBinanceMarketContext = async (params) => {
|
|
775
975
|
const {
|
|
@@ -870,46 +1070,46 @@ var enrichSignalWithBinanceMarketContext = async (params) => {
|
|
|
870
1070
|
asOfTs: primaryBreadth.ts.getTime(),
|
|
871
1071
|
ageMs: primaryBreadth.ageMs,
|
|
872
1072
|
stale: primaryBreadth.stale,
|
|
873
|
-
btcReturn1h:
|
|
1073
|
+
btcReturn1h: toFiniteNumberOrNull2(
|
|
874
1074
|
primaryBreadth.btcReturn1h
|
|
875
1075
|
),
|
|
876
|
-
btcReturn4h:
|
|
1076
|
+
btcReturn4h: toFiniteNumberOrNull2(
|
|
877
1077
|
primaryBreadth.btcReturn4h
|
|
878
1078
|
),
|
|
879
|
-
btcReturn24h:
|
|
1079
|
+
btcReturn24h: toFiniteNumberOrNull2(
|
|
880
1080
|
primaryBreadth.btcReturn24h
|
|
881
1081
|
),
|
|
882
|
-
altBasketReturn1h:
|
|
1082
|
+
altBasketReturn1h: toFiniteNumberOrNull2(
|
|
883
1083
|
primaryBreadth.altBasketReturn1h
|
|
884
1084
|
),
|
|
885
|
-
altBasketReturn4h:
|
|
1085
|
+
altBasketReturn4h: toFiniteNumberOrNull2(
|
|
886
1086
|
primaryBreadth.altBasketReturn4h
|
|
887
1087
|
),
|
|
888
|
-
altBasketReturn24h:
|
|
1088
|
+
altBasketReturn24h: toFiniteNumberOrNull2(
|
|
889
1089
|
primaryBreadth.altBasketReturn24h
|
|
890
1090
|
),
|
|
891
|
-
btcVsAltReturn1h:
|
|
1091
|
+
btcVsAltReturn1h: toFiniteNumberOrNull2(
|
|
892
1092
|
primaryBreadth.btcVsAltReturn1h
|
|
893
1093
|
),
|
|
894
|
-
btcVsAltReturn4h:
|
|
1094
|
+
btcVsAltReturn4h: toFiniteNumberOrNull2(
|
|
895
1095
|
primaryBreadth.btcVsAltReturn4h
|
|
896
1096
|
),
|
|
897
|
-
btcVsAltReturn24h:
|
|
1097
|
+
btcVsAltReturn24h: toFiniteNumberOrNull2(
|
|
898
1098
|
primaryBreadth.btcVsAltReturn24h
|
|
899
1099
|
),
|
|
900
|
-
btcTurnoverShare1h:
|
|
1100
|
+
btcTurnoverShare1h: toFiniteNumberOrNull2(
|
|
901
1101
|
primaryBreadth.btcTurnoverShare1h
|
|
902
1102
|
),
|
|
903
|
-
btcTurnoverShare24h:
|
|
1103
|
+
btcTurnoverShare24h: toFiniteNumberOrNull2(
|
|
904
1104
|
primaryBreadth.btcTurnoverShare24h
|
|
905
1105
|
),
|
|
906
|
-
btcTurnoverShareChange24h:
|
|
1106
|
+
btcTurnoverShareChange24h: toFiniteNumberOrNull2(
|
|
907
1107
|
primaryBreadth.btcTurnoverShareChange24h
|
|
908
1108
|
),
|
|
909
|
-
altVolToBtcVol24h:
|
|
1109
|
+
altVolToBtcVol24h: toFiniteNumberOrNull2(
|
|
910
1110
|
primaryBreadth.altVolToBtcVol24h
|
|
911
1111
|
),
|
|
912
|
-
altDispersion24h:
|
|
1112
|
+
altDispersion24h: toFiniteNumberOrNull2(
|
|
913
1113
|
primaryBreadth.altDispersion24h
|
|
914
1114
|
),
|
|
915
1115
|
regime: primaryBreadth.btcAltRegime ?? "unknown"
|
|
@@ -926,7 +1126,7 @@ var enrichSignalWithBinanceMarketContext = async (params) => {
|
|
|
926
1126
|
throw error;
|
|
927
1127
|
}
|
|
928
1128
|
binanceMarketContextUnavailable = true;
|
|
929
|
-
|
|
1129
|
+
logger2.warn(
|
|
930
1130
|
"Binance market context disabled after Timescale read failure: %s",
|
|
931
1131
|
String(error)
|
|
932
1132
|
);
|
|
@@ -936,7 +1136,7 @@ var enrichSignalWithBinanceMarketContext = async (params) => {
|
|
|
936
1136
|
|
|
937
1137
|
// src/strategyHelpers/coinMarketCapContext.ts
|
|
938
1138
|
import { refreshSignalBaseContextGateFeatures as refreshSignalBaseContextGateFeatures2 } from "@tradejs/core/strategies";
|
|
939
|
-
import { logger as
|
|
1139
|
+
import { logger as logger3 } from "@tradejs/infra/logger";
|
|
940
1140
|
import {
|
|
941
1141
|
getLatestMarketCmcExchangeLiquidityContext,
|
|
942
1142
|
getLatestMarketCmcFearGreedContext,
|
|
@@ -980,7 +1180,7 @@ var asInt = (value, fallback) => {
|
|
|
980
1180
|
const parsed = Number.parseInt(String(value ?? ""), 10);
|
|
981
1181
|
return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback;
|
|
982
1182
|
};
|
|
983
|
-
var
|
|
1183
|
+
var toFiniteNumberOrNull3 = (value) => {
|
|
984
1184
|
const numeric = typeof value === "number" ? value : typeof value === "string" && value.trim() ? Number(value) : Number.NaN;
|
|
985
1185
|
return Number.isFinite(numeric) ? numeric : null;
|
|
986
1186
|
};
|
|
@@ -1214,12 +1414,12 @@ var enrichSignalWithCoinMarketCapContext = async (params) => {
|
|
|
1214
1414
|
const ethRow = references.get("ETHUSDT") ?? null;
|
|
1215
1415
|
const previousBtcRow = previousReferences.get("BTCUSDT") ?? null;
|
|
1216
1416
|
const previousEthRow = previousReferences.get("ETHUSDT") ?? null;
|
|
1217
|
-
const btcMarketCapUsd =
|
|
1218
|
-
const ethMarketCapUsd =
|
|
1219
|
-
const previousBtcMarketCapUsd =
|
|
1417
|
+
const btcMarketCapUsd = toFiniteNumberOrNull3(btcRow?.marketCapUsd);
|
|
1418
|
+
const ethMarketCapUsd = toFiniteNumberOrNull3(ethRow?.marketCapUsd);
|
|
1419
|
+
const previousBtcMarketCapUsd = toFiniteNumberOrNull3(
|
|
1220
1420
|
previousBtcRow?.marketCapUsd
|
|
1221
1421
|
);
|
|
1222
|
-
const previousEthMarketCapUsd =
|
|
1422
|
+
const previousEthMarketCapUsd = toFiniteNumberOrNull3(
|
|
1223
1423
|
previousEthRow?.marketCapUsd
|
|
1224
1424
|
);
|
|
1225
1425
|
const ethBtcMarketCapRatio = safeDivide(ethMarketCapUsd, btcMarketCapUsd);
|
|
@@ -1228,16 +1428,16 @@ var enrichSignalWithCoinMarketCapContext = async (params) => {
|
|
|
1228
1428
|
previousBtcMarketCapUsd
|
|
1229
1429
|
);
|
|
1230
1430
|
const ethBtcMarketCapRatioChange24hPct = ethBtcMarketCapRatio != null && previousEthBtcMarketCapRatio != null && previousEthBtcMarketCapRatio > 0 ? (ethBtcMarketCapRatio - previousEthBtcMarketCapRatio) / previousEthBtcMarketCapRatio : null;
|
|
1231
|
-
const btcVolumeUsd =
|
|
1232
|
-
const ethVolumeUsd =
|
|
1431
|
+
const btcVolumeUsd = toFiniteNumberOrNull3(btcRow?.volumeUsd);
|
|
1432
|
+
const ethVolumeUsd = toFiniteNumberOrNull3(ethRow?.volumeUsd);
|
|
1233
1433
|
const referenceStale = btcRow?.stale === true || ethRow?.stale === true || !btcRow || !ethRow;
|
|
1234
|
-
const btcDominanceChange24hPct =
|
|
1434
|
+
const btcDominanceChange24hPct = toFiniteNumberOrNull3(
|
|
1235
1435
|
globalRow?.btcDominanceChange24hPct
|
|
1236
1436
|
);
|
|
1237
|
-
const altMarketCapChange24hPct =
|
|
1437
|
+
const altMarketCapChange24hPct = toFiniteNumberOrNull3(
|
|
1238
1438
|
globalRow?.altMarketCapChange24hPct
|
|
1239
1439
|
);
|
|
1240
|
-
const altVolumeChange24hPct =
|
|
1440
|
+
const altVolumeChange24hPct = toFiniteNumberOrNull3(
|
|
1241
1441
|
globalRow?.altVolumeChange24hPct
|
|
1242
1442
|
);
|
|
1243
1443
|
const altLiquidityRegime = globalRow ? toAltLiquidityRegime({
|
|
@@ -1248,19 +1448,19 @@ var enrichSignalWithCoinMarketCapContext = async (params) => {
|
|
|
1248
1448
|
}) : "unknown";
|
|
1249
1449
|
const exchangeLiquidityRegime = exchangeLiquidityRow ? toExchangeLiquidityRegime({
|
|
1250
1450
|
stale: exchangeLiquidityRow.stale,
|
|
1251
|
-
totalVolumeChange24hPct:
|
|
1451
|
+
totalVolumeChange24hPct: toFiniteNumberOrNull3(
|
|
1252
1452
|
exchangeLiquidityRow.totalVolumeChange24hPct
|
|
1253
1453
|
),
|
|
1254
1454
|
fallback: exchangeLiquidityRow.liquidityRegime ?? "unknown"
|
|
1255
1455
|
}) : "unknown";
|
|
1256
1456
|
const cmc100Row = indexRows.get("cmc100") ?? null;
|
|
1257
1457
|
const cmc20Row = indexRows.get("cmc20") ?? null;
|
|
1258
|
-
const cmc100Value =
|
|
1259
|
-
const cmc20Value =
|
|
1260
|
-
const cmc100Change24hPct =
|
|
1458
|
+
const cmc100Value = toFiniteNumberOrNull3(cmc100Row?.value);
|
|
1459
|
+
const cmc20Value = toFiniteNumberOrNull3(cmc20Row?.value);
|
|
1460
|
+
const cmc100Change24hPct = toFiniteNumberOrNull3(
|
|
1261
1461
|
cmc100Row?.valueChange24hPct
|
|
1262
1462
|
);
|
|
1263
|
-
const cmc20Change24hPct =
|
|
1463
|
+
const cmc20Change24hPct = toFiniteNumberOrNull3(cmc20Row?.valueChange24hPct);
|
|
1264
1464
|
const cmc20ToCmc100Ratio = safeDivide(cmc20Value, cmc100Value);
|
|
1265
1465
|
const cmc20ToCmc100RatioChange24hPct = cmc20Change24hPct != null && cmc100Change24hPct != null ? (1 + cmc20Change24hPct) / (1 + cmc100Change24hPct) - 1 : null;
|
|
1266
1466
|
const indexStale = cmc100Row?.stale === true || cmc20Row?.stale === true || !cmc100Row || !cmc20Row;
|
|
@@ -1284,41 +1484,41 @@ var enrichSignalWithCoinMarketCapContext = async (params) => {
|
|
|
1284
1484
|
asOfTs: globalRow.ts.getTime(),
|
|
1285
1485
|
ageMs: globalRow.ageMs,
|
|
1286
1486
|
stale: globalRow.stale,
|
|
1287
|
-
totalMarketCapUsd:
|
|
1487
|
+
totalMarketCapUsd: toFiniteNumberOrNull3(
|
|
1288
1488
|
globalRow.totalMarketCapUsd
|
|
1289
1489
|
),
|
|
1290
|
-
totalVolumeUsd:
|
|
1490
|
+
totalVolumeUsd: toFiniteNumberOrNull3(
|
|
1291
1491
|
globalRow.totalVolumeUsd
|
|
1292
1492
|
),
|
|
1293
|
-
totalVolumeReportedUsd:
|
|
1493
|
+
totalVolumeReportedUsd: toFiniteNumberOrNull3(
|
|
1294
1494
|
globalRow.totalVolumeReportedUsd
|
|
1295
1495
|
),
|
|
1296
|
-
altMarketCapUsd:
|
|
1496
|
+
altMarketCapUsd: toFiniteNumberOrNull3(
|
|
1297
1497
|
globalRow.altMarketCapUsd
|
|
1298
1498
|
),
|
|
1299
|
-
altVolumeUsd:
|
|
1300
|
-
altVolumeReportedUsd:
|
|
1499
|
+
altVolumeUsd: toFiniteNumberOrNull3(globalRow.altVolumeUsd),
|
|
1500
|
+
altVolumeReportedUsd: toFiniteNumberOrNull3(
|
|
1301
1501
|
globalRow.altVolumeReportedUsd
|
|
1302
1502
|
),
|
|
1303
|
-
btcDominancePct:
|
|
1503
|
+
btcDominancePct: toFiniteNumberOrNull3(
|
|
1304
1504
|
globalRow.btcDominancePct
|
|
1305
1505
|
),
|
|
1306
|
-
ethDominancePct:
|
|
1506
|
+
ethDominancePct: toFiniteNumberOrNull3(
|
|
1307
1507
|
globalRow.ethDominancePct
|
|
1308
1508
|
),
|
|
1309
1509
|
btcDominanceChange24hPct,
|
|
1310
|
-
ethDominanceChange24hPct:
|
|
1510
|
+
ethDominanceChange24hPct: toFiniteNumberOrNull3(
|
|
1311
1511
|
globalRow.ethDominanceChange24hPct
|
|
1312
1512
|
),
|
|
1313
1513
|
altMarketCapChange24hPct,
|
|
1314
1514
|
altVolumeChange24hPct,
|
|
1315
|
-
activeCryptocurrencies:
|
|
1515
|
+
activeCryptocurrencies: toFiniteNumberOrNull3(
|
|
1316
1516
|
globalRow.activeCryptocurrencies
|
|
1317
1517
|
),
|
|
1318
|
-
activeExchanges:
|
|
1518
|
+
activeExchanges: toFiniteNumberOrNull3(
|
|
1319
1519
|
globalRow.activeExchanges
|
|
1320
1520
|
),
|
|
1321
|
-
activeMarketPairs:
|
|
1521
|
+
activeMarketPairs: toFiniteNumberOrNull3(
|
|
1322
1522
|
globalRow.activeMarketPairs
|
|
1323
1523
|
),
|
|
1324
1524
|
altLiquidityRegime
|
|
@@ -1363,22 +1563,22 @@ var enrichSignalWithCoinMarketCapContext = async (params) => {
|
|
|
1363
1563
|
asOfTs: exchangeLiquidityRow.ts.getTime(),
|
|
1364
1564
|
ageMs: exchangeLiquidityRow.ageMs,
|
|
1365
1565
|
stale: exchangeLiquidityRow.stale,
|
|
1366
|
-
exchangesCount:
|
|
1566
|
+
exchangesCount: toFiniteNumberOrNull3(
|
|
1367
1567
|
exchangeLiquidityRow.exchangesCount
|
|
1368
1568
|
),
|
|
1369
|
-
totalVolumeUsd:
|
|
1569
|
+
totalVolumeUsd: toFiniteNumberOrNull3(
|
|
1370
1570
|
exchangeLiquidityRow.totalVolumeUsd
|
|
1371
1571
|
),
|
|
1372
|
-
totalVolumeChange24hPct:
|
|
1572
|
+
totalVolumeChange24hPct: toFiniteNumberOrNull3(
|
|
1373
1573
|
exchangeLiquidityRow.totalVolumeChange24hPct
|
|
1374
1574
|
),
|
|
1375
|
-
binanceVolumeUsd:
|
|
1575
|
+
binanceVolumeUsd: toFiniteNumberOrNull3(
|
|
1376
1576
|
exchangeLiquidityRow.binanceVolumeUsd
|
|
1377
1577
|
),
|
|
1378
|
-
binanceVolumeShare:
|
|
1578
|
+
binanceVolumeShare: toFiniteNumberOrNull3(
|
|
1379
1579
|
exchangeLiquidityRow.binanceVolumeShare
|
|
1380
1580
|
),
|
|
1381
|
-
topExchangeVolumeShare:
|
|
1581
|
+
topExchangeVolumeShare: toFiniteNumberOrNull3(
|
|
1382
1582
|
exchangeLiquidityRow.topExchangeVolumeShare
|
|
1383
1583
|
),
|
|
1384
1584
|
liquidityRegime: exchangeLiquidityRegime
|
|
@@ -1391,11 +1591,11 @@ var enrichSignalWithCoinMarketCapContext = async (params) => {
|
|
|
1391
1591
|
asOfTs: fearGreedRow.ts.getTime(),
|
|
1392
1592
|
ageMs: fearGreedRow.ageMs,
|
|
1393
1593
|
stale: fearGreedRow.stale,
|
|
1394
|
-
value:
|
|
1395
|
-
valueChange24h:
|
|
1594
|
+
value: toFiniteNumberOrNull3(fearGreedRow.value),
|
|
1595
|
+
valueChange24h: toFiniteNumberOrNull3(
|
|
1396
1596
|
fearGreedRow.valueChange24h
|
|
1397
1597
|
),
|
|
1398
|
-
valueChange7d:
|
|
1598
|
+
valueChange7d: toFiniteNumberOrNull3(
|
|
1399
1599
|
fearGreedRow.valueChange7d
|
|
1400
1600
|
),
|
|
1401
1601
|
classification: fearGreedRow.classification ?? "Unknown",
|
|
@@ -1415,13 +1615,13 @@ var enrichSignalWithCoinMarketCapContext = async (params) => {
|
|
|
1415
1615
|
cmc100Value,
|
|
1416
1616
|
cmc100Change24hPct,
|
|
1417
1617
|
cmc100TopConstituentSymbol: cmc100Row?.topConstituentSymbol ?? null,
|
|
1418
|
-
cmc100TopConstituentWeightPct:
|
|
1618
|
+
cmc100TopConstituentWeightPct: toFiniteNumberOrNull3(
|
|
1419
1619
|
cmc100Row?.topConstituentWeightPct
|
|
1420
1620
|
),
|
|
1421
1621
|
cmc20Value,
|
|
1422
1622
|
cmc20Change24hPct,
|
|
1423
1623
|
cmc20TopConstituentSymbol: cmc20Row?.topConstituentSymbol ?? null,
|
|
1424
|
-
cmc20TopConstituentWeightPct:
|
|
1624
|
+
cmc20TopConstituentWeightPct: toFiniteNumberOrNull3(
|
|
1425
1625
|
cmc20Row?.topConstituentWeightPct
|
|
1426
1626
|
),
|
|
1427
1627
|
cmc20ToCmc100Ratio,
|
|
@@ -1439,7 +1639,7 @@ var enrichSignalWithCoinMarketCapContext = async (params) => {
|
|
|
1439
1639
|
throw error;
|
|
1440
1640
|
}
|
|
1441
1641
|
coinMarketCapContextUnavailable = true;
|
|
1442
|
-
|
|
1642
|
+
logger3.warn(
|
|
1443
1643
|
"CoinMarketCap context disabled after Timescale read failure: %s",
|
|
1444
1644
|
String(error)
|
|
1445
1645
|
);
|
|
@@ -1673,7 +1873,7 @@ import {
|
|
|
1673
1873
|
getHyperliquidWhaleFlowAggregate,
|
|
1674
1874
|
getHyperliquidWhaleFlowSeriesRows
|
|
1675
1875
|
} from "@tradejs/infra/timescale/hyperliquidWhales";
|
|
1676
|
-
import { logger as
|
|
1876
|
+
import { logger as logger4 } from "@tradejs/infra/logger";
|
|
1677
1877
|
var MAX_CACHE_ENTRIES = 2048;
|
|
1678
1878
|
var MAX_COVERAGE_SERIES_CACHE_ENTRIES = 20;
|
|
1679
1879
|
var MAX_FLOW_SERIES_CACHE_ENTRIES = 64;
|
|
@@ -1977,7 +2177,7 @@ var loadHyperliquidWhaleFlowContext = async (params) => {
|
|
|
1977
2177
|
throw error;
|
|
1978
2178
|
}
|
|
1979
2179
|
hyperliquidWhaleContextUnavailable = true;
|
|
1980
|
-
|
|
2180
|
+
logger4.warn(
|
|
1981
2181
|
"Hyperliquid whale context disabled after Timescale read failure: %s",
|
|
1982
2182
|
String(error)
|
|
1983
2183
|
);
|
|
@@ -2016,7 +2216,7 @@ var enrichSignalWithHyperliquidWhaleContext = async (params) => {
|
|
|
2016
2216
|
};
|
|
2017
2217
|
|
|
2018
2218
|
// src/strategyHelpers/runtime.ts
|
|
2019
|
-
import { logger as
|
|
2219
|
+
import { logger as logger8 } from "@tradejs/infra/logger";
|
|
2020
2220
|
import { FEE_PERCENT } from "@tradejs/core/constants";
|
|
2021
2221
|
import {
|
|
2022
2222
|
buildMlFeatures,
|
|
@@ -2078,7 +2278,7 @@ import { randomUUID } from "crypto";
|
|
|
2078
2278
|
import { TTL_1M } from "@tradejs/core/constants";
|
|
2079
2279
|
import { getRuntimeStorageDayKey } from "@tradejs/core/time";
|
|
2080
2280
|
import { createRuntimeOrderLinkPrefix } from "@tradejs/core/trade";
|
|
2081
|
-
import { logger as
|
|
2281
|
+
import { logger as logger5 } from "@tradejs/infra/logger";
|
|
2082
2282
|
import {
|
|
2083
2283
|
delKey,
|
|
2084
2284
|
getData,
|
|
@@ -2143,7 +2343,7 @@ var recordRuntimeTradeOpen = async (params) => {
|
|
|
2143
2343
|
)
|
|
2144
2344
|
]);
|
|
2145
2345
|
} catch (error) {
|
|
2146
|
-
|
|
2346
|
+
logger5.error(
|
|
2147
2347
|
"runtime trade open journal failed: %s %s",
|
|
2148
2348
|
record.symbol,
|
|
2149
2349
|
error?.message || String(error)
|
|
@@ -2211,7 +2411,7 @@ var recordRuntimeTradeIncrease = async (params) => {
|
|
|
2211
2411
|
)
|
|
2212
2412
|
]);
|
|
2213
2413
|
} catch (error) {
|
|
2214
|
-
|
|
2414
|
+
logger5.error(
|
|
2215
2415
|
"runtime trade increase journal failed: %s %s",
|
|
2216
2416
|
symbol,
|
|
2217
2417
|
error?.message || String(error)
|
|
@@ -2318,7 +2518,7 @@ var markRuntimeTradeClosed = async (params) => {
|
|
|
2318
2518
|
)
|
|
2319
2519
|
]);
|
|
2320
2520
|
} catch (error) {
|
|
2321
|
-
|
|
2521
|
+
logger5.error(
|
|
2322
2522
|
"runtime trade close journal failed: %s %s",
|
|
2323
2523
|
symbol,
|
|
2324
2524
|
error?.message || String(error)
|
|
@@ -2328,7 +2528,7 @@ var markRuntimeTradeClosed = async (params) => {
|
|
|
2328
2528
|
};
|
|
2329
2529
|
|
|
2330
2530
|
// src/strategyHelpers/marketContextStages.ts
|
|
2331
|
-
import { logger as
|
|
2531
|
+
import { logger as logger7 } from "@tradejs/infra/logger";
|
|
2332
2532
|
|
|
2333
2533
|
// src/strategyHelpers/derivativesContext.ts
|
|
2334
2534
|
import {
|
|
@@ -2343,7 +2543,7 @@ import {
|
|
|
2343
2543
|
resolveDerivativesContextReferenceSymbols
|
|
2344
2544
|
} from "@tradejs/core/constants";
|
|
2345
2545
|
import { getDerivativesWindow } from "@tradejs/infra/timescale/derivatives";
|
|
2346
|
-
import { logger as
|
|
2546
|
+
import { logger as logger6 } from "@tradejs/infra/logger";
|
|
2347
2547
|
var STORED_INTERVALS = ["15m", "1h"];
|
|
2348
2548
|
var CONTEXT_INTERVALS = ["15m", "1h"];
|
|
2349
2549
|
var DEFAULT_LOOKBACK_HOURS = 48;
|
|
@@ -2398,7 +2598,7 @@ var resolvePrimaryReferenceSymbol2 = () => PRIMARY_DERIVATIVES_REFERENCE_SYMBOL;
|
|
|
2398
2598
|
var resolveSecondaryReferenceSymbol = () => SECONDARY_DERIVATIVES_REFERENCE_SYMBOL;
|
|
2399
2599
|
var getPrimaryIntervalContext = (context) => context?.intervals["15m"] ?? context?.intervals["1h"] ?? null;
|
|
2400
2600
|
var hasDerivativesSymbolData = (context) => Object.keys(context.intervals).length > 0 && !context.summary.riskFlags.includes("missing_derivatives");
|
|
2401
|
-
var
|
|
2601
|
+
var toFiniteNumberOrNull4 = (value) => {
|
|
2402
2602
|
if (typeof value === "number" && Number.isFinite(value)) return value;
|
|
2403
2603
|
if (typeof value === "string" && value.trim()) {
|
|
2404
2604
|
const parsed = Number(value);
|
|
@@ -2412,8 +2612,8 @@ var roundNullable = (value, digits = 4) => {
|
|
|
2412
2612
|
return Math.round(value * multiplier) / multiplier;
|
|
2413
2613
|
};
|
|
2414
2614
|
var deltaNullable = (targetValue, referenceValue) => {
|
|
2415
|
-
const target =
|
|
2416
|
-
const reference =
|
|
2615
|
+
const target = toFiniteNumberOrNull4(targetValue);
|
|
2616
|
+
const reference = toFiniteNumberOrNull4(referenceValue);
|
|
2417
2617
|
return target == null || reference == null ? null : roundNullable(target - reference);
|
|
2418
2618
|
};
|
|
2419
2619
|
var buildTargetDerivedContext = (params) => {
|
|
@@ -2575,7 +2775,7 @@ var enrichSignalWithDerivativesContext = async (params) => {
|
|
|
2575
2775
|
throw error;
|
|
2576
2776
|
}
|
|
2577
2777
|
derivativesContextUnavailable = true;
|
|
2578
|
-
|
|
2778
|
+
logger6.warn(
|
|
2579
2779
|
"Derivatives context disabled after Timescale read failure: %s",
|
|
2580
2780
|
String(error)
|
|
2581
2781
|
);
|
|
@@ -2628,7 +2828,7 @@ var runMarketContextStage = async ({
|
|
|
2628
2828
|
elapsedMs: Date.now() - startedAt
|
|
2629
2829
|
};
|
|
2630
2830
|
if (status === "timed_out") {
|
|
2631
|
-
|
|
2831
|
+
logger7.warn(
|
|
2632
2832
|
"Market context stage timed out: %s after %sms",
|
|
2633
2833
|
stage,
|
|
2634
2834
|
result.elapsedMs
|
|
@@ -2818,7 +3018,7 @@ var enrichSignalWithAi = async ({
|
|
|
2818
3018
|
signal.aiAnalysis = analysis;
|
|
2819
3019
|
return resolveAiQuality(analysis, direction);
|
|
2820
3020
|
} catch (err) {
|
|
2821
|
-
|
|
3021
|
+
logger8.error("AI analysis error: %s %s", symbol, formatAiError(err));
|
|
2822
3022
|
}
|
|
2823
3023
|
return void 0;
|
|
2824
3024
|
};
|
|
@@ -2835,75 +3035,6 @@ var enrichSignalWithMlAi = async ({
|
|
|
2835
3035
|
await enrichSignalWithMl({ signal, env, ml });
|
|
2836
3036
|
return enrichSignalWithAi({ signal, userName, symbol, direction, env, ai });
|
|
2837
3037
|
};
|
|
2838
|
-
var toFiniteNumberOrNull4 = (value) => typeof value === "number" && Number.isFinite(value) ? value : null;
|
|
2839
|
-
var getOrderArrivalSnapshot = async ({
|
|
2840
|
-
connector,
|
|
2841
|
-
symbol
|
|
2842
|
-
}) => {
|
|
2843
|
-
if (typeof connector.getTopOfBookTicker !== "function") {
|
|
2844
|
-
return {
|
|
2845
|
-
arrivalSnapshotTime: Date.now(),
|
|
2846
|
-
arrivalSource: "unavailable",
|
|
2847
|
-
bid: null,
|
|
2848
|
-
ask: null,
|
|
2849
|
-
arrivalMid: null,
|
|
2850
|
-
spreadBps: null
|
|
2851
|
-
};
|
|
2852
|
-
}
|
|
2853
|
-
try {
|
|
2854
|
-
const ticker = await connector.getTopOfBookTicker(symbol);
|
|
2855
|
-
const arrivalSnapshotTime = toFiniteNumberOrNull4(ticker?.timestamp);
|
|
2856
|
-
const bid = toFiniteNumberOrNull4(ticker?.bidPrice);
|
|
2857
|
-
const ask = toFiniteNumberOrNull4(ticker?.askPrice);
|
|
2858
|
-
const arrivalMid = bid != null && ask != null ? (bid + ask) / 2 : null;
|
|
2859
|
-
const spreadBps = bid != null && ask != null && arrivalMid != null && arrivalMid > 0 ? (ask - bid) / arrivalMid * 1e4 : null;
|
|
2860
|
-
return {
|
|
2861
|
-
arrivalSnapshotTime: arrivalSnapshotTime ?? Date.now(),
|
|
2862
|
-
arrivalSource: "top_of_book",
|
|
2863
|
-
bid,
|
|
2864
|
-
ask,
|
|
2865
|
-
arrivalMid,
|
|
2866
|
-
spreadBps
|
|
2867
|
-
};
|
|
2868
|
-
} catch (error) {
|
|
2869
|
-
logger7.warn(
|
|
2870
|
-
"runtime order arrival snapshot failed: %s %s",
|
|
2871
|
-
symbol,
|
|
2872
|
-
error?.message || String(error)
|
|
2873
|
-
);
|
|
2874
|
-
return {
|
|
2875
|
-
arrivalSnapshotTime: Date.now(),
|
|
2876
|
-
arrivalSource: "top_of_book_error",
|
|
2877
|
-
bid: null,
|
|
2878
|
-
ask: null,
|
|
2879
|
-
arrivalMid: null,
|
|
2880
|
-
spreadBps: null
|
|
2881
|
-
};
|
|
2882
|
-
}
|
|
2883
|
-
};
|
|
2884
|
-
var validateEntryProtectionAtArrival = ({
|
|
2885
|
-
direction,
|
|
2886
|
-
signalPrice,
|
|
2887
|
-
bid,
|
|
2888
|
-
ask,
|
|
2889
|
-
arrivalMid,
|
|
2890
|
-
takeProfits,
|
|
2891
|
-
stopLossPrice
|
|
2892
|
-
}) => {
|
|
2893
|
-
const entryReference = direction === "LONG" ? ask ?? arrivalMid ?? signalPrice : bid ?? arrivalMid ?? signalPrice;
|
|
2894
|
-
const stopReference = direction === "LONG" ? bid ?? arrivalMid ?? signalPrice : ask ?? arrivalMid ?? signalPrice;
|
|
2895
|
-
if (!Number.isFinite(entryReference) || entryReference <= 0 || !Number.isFinite(stopReference) || stopReference <= 0) {
|
|
2896
|
-
throw new Error("INVALID_ENTRY_REFERENCE_AT_ARRIVAL");
|
|
2897
|
-
}
|
|
2898
|
-
for (const takeProfit of takeProfits) {
|
|
2899
|
-
if (!Number.isFinite(takeProfit.price) || takeProfit.price <= 0 || (direction === "LONG" ? takeProfit.price <= entryReference : takeProfit.price >= entryReference)) {
|
|
2900
|
-
throw new Error("TAKE_PROFIT_CROSSED_BEFORE_ENTRY");
|
|
2901
|
-
}
|
|
2902
|
-
}
|
|
2903
|
-
if (stopLossPrice != null && (!Number.isFinite(stopLossPrice) || stopLossPrice <= 0 || (direction === "LONG" ? stopLossPrice >= stopReference : stopLossPrice <= stopReference))) {
|
|
2904
|
-
throw new Error("STOP_LOSS_CROSSED_BEFORE_ENTRY");
|
|
2905
|
-
}
|
|
2906
|
-
};
|
|
2907
3038
|
var resolveRuntimeTelemetryQuality = ({
|
|
2908
3039
|
signalClosePrice,
|
|
2909
3040
|
arrivalMid,
|
|
@@ -2980,7 +3111,8 @@ var executeEntryOrder = async ({
|
|
|
2980
3111
|
signal,
|
|
2981
3112
|
beforePlaceOrder,
|
|
2982
3113
|
recordRuntimeTrade = true,
|
|
2983
|
-
leverage
|
|
3114
|
+
leverage,
|
|
3115
|
+
executionLimits
|
|
2984
3116
|
}) => {
|
|
2985
3117
|
await beforePlaceOrder?.();
|
|
2986
3118
|
const previousPosition = positionIntent === "increase" ? await connector.getPosition(symbol) : null;
|
|
@@ -2991,23 +3123,57 @@ var executeEntryOrder = async ({
|
|
|
2991
3123
|
signal.orderQty = qty;
|
|
2992
3124
|
signal.orderValue = qty * currentPrice;
|
|
2993
3125
|
signal.orderFailureReason = void 0;
|
|
2994
|
-
|
|
2995
|
-
|
|
2996
|
-
|
|
2997
|
-
|
|
2998
|
-
|
|
2999
|
-
|
|
3000
|
-
|
|
3001
|
-
|
|
3002
|
-
|
|
3003
|
-
|
|
3004
|
-
|
|
3005
|
-
|
|
3006
|
-
|
|
3007
|
-
|
|
3008
|
-
|
|
3126
|
+
let arrivalSnapshot = {
|
|
3127
|
+
arrivalSnapshotTime: Date.now(),
|
|
3128
|
+
arrivalSource: "unavailable",
|
|
3129
|
+
bid: null,
|
|
3130
|
+
ask: null,
|
|
3131
|
+
arrivalMid: null,
|
|
3132
|
+
spreadBps: null
|
|
3133
|
+
};
|
|
3134
|
+
let orderSubmitTime = Date.now();
|
|
3135
|
+
const beforeSubmit = createEntrySubmissionValidation({
|
|
3136
|
+
connector,
|
|
3137
|
+
symbol,
|
|
3138
|
+
direction,
|
|
3139
|
+
takeProfits,
|
|
3140
|
+
stopLossPrice,
|
|
3141
|
+
limits: executionLimits,
|
|
3142
|
+
onValidated: (snapshot2, economics) => {
|
|
3143
|
+
arrivalSnapshot = snapshot2;
|
|
3144
|
+
orderSubmitTime = Date.now();
|
|
3145
|
+
if (executionLimits?.maxLossValue != null) {
|
|
3146
|
+
signal.riskDecision = {
|
|
3147
|
+
stage: "order_plan",
|
|
3148
|
+
status: "approved",
|
|
3149
|
+
reason: "PLANNED_LOSS_WITHIN_MAX_LOSS_VALUE",
|
|
3150
|
+
plannedLossValue: economics.plannedLossValue,
|
|
3151
|
+
maxLossValue: executionLimits.maxLossValue,
|
|
3152
|
+
limitUtilization: economics.plannedLossValue / executionLimits.maxLossValue,
|
|
3153
|
+
enforced: true
|
|
3154
|
+
};
|
|
3155
|
+
}
|
|
3156
|
+
}
|
|
3157
|
+
});
|
|
3158
|
+
if (!beforeSubmit) {
|
|
3159
|
+
arrivalSnapshot = await getOrderArrivalSnapshot({ connector, symbol });
|
|
3160
|
+
try {
|
|
3161
|
+
validateEntryProtectionAtArrival({
|
|
3162
|
+
direction,
|
|
3163
|
+
signalPrice: currentPrice,
|
|
3164
|
+
bid: arrivalSnapshot.bid,
|
|
3165
|
+
ask: arrivalSnapshot.ask,
|
|
3166
|
+
arrivalMid: arrivalSnapshot.arrivalMid,
|
|
3167
|
+
takeProfits,
|
|
3168
|
+
stopLossPrice
|
|
3169
|
+
});
|
|
3170
|
+
} catch (error) {
|
|
3171
|
+
signal.orderStatus = "failed";
|
|
3172
|
+
signal.orderFailureReason = error.message;
|
|
3173
|
+
throw error;
|
|
3174
|
+
}
|
|
3175
|
+
orderSubmitTime = Date.now();
|
|
3009
3176
|
}
|
|
3010
|
-
const orderSubmitTime = Date.now();
|
|
3011
3177
|
const orderPlaced = await connector.placeOrder({
|
|
3012
3178
|
symbol,
|
|
3013
3179
|
qty,
|
|
@@ -3018,7 +3184,8 @@ var executeEntryOrder = async ({
|
|
|
3018
3184
|
direction,
|
|
3019
3185
|
...typeof leverage === "number" && Number.isFinite(leverage) ? { leverage } : {},
|
|
3020
3186
|
orderId,
|
|
3021
|
-
signal
|
|
3187
|
+
signal,
|
|
3188
|
+
...beforeSubmit ? { beforeSubmit } : {}
|
|
3022
3189
|
});
|
|
3023
3190
|
const orderAckTime = Date.now();
|
|
3024
3191
|
const placedQty = typeof signal.orderQty === "number" && Number.isFinite(signal.orderQty) && signal.orderQty > 0 ? signal.orderQty : qty;
|
|
@@ -3305,7 +3472,7 @@ import {
|
|
|
3305
3472
|
createStrategyIndicatorsState,
|
|
3306
3473
|
getSharedStrategyReplayState
|
|
3307
3474
|
} from "@tradejs/core/strategies";
|
|
3308
|
-
import { logger as
|
|
3475
|
+
import { logger as logger10 } from "@tradejs/infra/logger";
|
|
3309
3476
|
|
|
3310
3477
|
// src/strategy/runtimeBacktestDelay.ts
|
|
3311
3478
|
import { BACKTEST_EXECUTION_INTERVAL } from "@tradejs/core/constants";
|
|
@@ -3867,7 +4034,7 @@ var canUseSharedReplayState = ({
|
|
|
3867
4034
|
}) => (env === "BACKTEST" || env === "PARITY") && Boolean(sharedReplayKey);
|
|
3868
4035
|
|
|
3869
4036
|
// src/strategy/runtimeExecution.ts
|
|
3870
|
-
import { logger as
|
|
4037
|
+
import { logger as logger9 } from "@tradejs/infra/logger";
|
|
3871
4038
|
import {
|
|
3872
4039
|
BACKTEST_WARNING_CODES
|
|
3873
4040
|
} from "@tradejs/types";
|
|
@@ -3921,7 +4088,7 @@ var handleExitDecision = async ({
|
|
|
3921
4088
|
deploymentId: connector.deploymentId
|
|
3922
4089
|
});
|
|
3923
4090
|
if (!activeTrade) {
|
|
3924
|
-
|
|
4091
|
+
logger9.warn(
|
|
3925
4092
|
"[%s] blocked closePosition for untracked runtime position: %s",
|
|
3926
4093
|
strategyName ?? "unknown",
|
|
3927
4094
|
symbol
|
|
@@ -3929,7 +4096,7 @@ var handleExitDecision = async ({
|
|
|
3929
4096
|
return "CLOSE_BLOCKED_BY_UNTRACKED_POSITION";
|
|
3930
4097
|
}
|
|
3931
4098
|
if (!strategyName || activeTrade.strategy !== strategyName) {
|
|
3932
|
-
|
|
4099
|
+
logger9.warn(
|
|
3933
4100
|
"[%s] blocked closePosition for foreign runtime position: %s ownedBy=%s",
|
|
3934
4101
|
strategyName ?? "unknown",
|
|
3935
4102
|
symbol,
|
|
@@ -3982,7 +4149,7 @@ var handleExitDecision = async ({
|
|
|
3982
4149
|
exitType: closedTrade?.exitType ?? "exit"
|
|
3983
4150
|
});
|
|
3984
4151
|
} catch (notificationError) {
|
|
3985
|
-
|
|
4152
|
+
logger9.error(
|
|
3986
4153
|
"runtime close notification error: %s %s",
|
|
3987
4154
|
symbol,
|
|
3988
4155
|
notificationError
|
|
@@ -3996,7 +4163,7 @@ var handleExitDecision = async ({
|
|
|
3996
4163
|
decision,
|
|
3997
4164
|
market
|
|
3998
4165
|
});
|
|
3999
|
-
|
|
4166
|
+
logger9.error("close order error: %s %s", symbol, err);
|
|
4000
4167
|
return "ORDER_ERROR";
|
|
4001
4168
|
}
|
|
4002
4169
|
return decision.code;
|
|
@@ -4023,7 +4190,7 @@ var handleProtectDecision = async ({
|
|
|
4023
4190
|
decision,
|
|
4024
4191
|
market
|
|
4025
4192
|
});
|
|
4026
|
-
|
|
4193
|
+
logger9.error("protect position error: %s %s", symbol, err);
|
|
4027
4194
|
return "ORDER_ERROR";
|
|
4028
4195
|
}
|
|
4029
4196
|
return decision.code;
|
|
@@ -4089,7 +4256,11 @@ var executeEntryDecision = async ({
|
|
|
4089
4256
|
...Number.isFinite(Number(hookCtx.strategyConfig.LEVERAGE)) ? { leverage: Number(hookCtx.strategyConfig.LEVERAGE) } : {},
|
|
4090
4257
|
signal,
|
|
4091
4258
|
beforePlaceOrder,
|
|
4092
|
-
recordRuntimeTrade: recordRuntimeJournal
|
|
4259
|
+
recordRuntimeTrade: recordRuntimeJournal,
|
|
4260
|
+
executionLimits: resolveEntryExecutionLimits(
|
|
4261
|
+
hookCtx.strategyConfig,
|
|
4262
|
+
decision.entryContext.direction
|
|
4263
|
+
)
|
|
4093
4264
|
});
|
|
4094
4265
|
await invokeStageHooks(
|
|
4095
4266
|
"afterPlaceOrder",
|
|
@@ -4111,19 +4282,32 @@ var executeEntryDecision = async ({
|
|
|
4111
4282
|
return signal;
|
|
4112
4283
|
}
|
|
4113
4284
|
await beforePlaceOrder();
|
|
4114
|
-
const
|
|
4285
|
+
const beforeSubmit = createEntrySubmissionValidation({
|
|
4115
4286
|
connector,
|
|
4116
|
-
symbol
|
|
4117
|
-
});
|
|
4118
|
-
validateEntryProtectionAtArrival({
|
|
4287
|
+
symbol,
|
|
4119
4288
|
direction: decision.entryContext.direction,
|
|
4120
|
-
signalPrice: decision.entryContext.prices.currentPrice,
|
|
4121
|
-
bid: arrivalSnapshot.bid,
|
|
4122
|
-
ask: arrivalSnapshot.ask,
|
|
4123
|
-
arrivalMid: arrivalSnapshot.arrivalMid,
|
|
4124
4289
|
takeProfits: decision.orderPlan.takeProfits,
|
|
4125
|
-
stopLossPrice: decision.orderPlan.stopLossPrice
|
|
4290
|
+
stopLossPrice: decision.orderPlan.stopLossPrice,
|
|
4291
|
+
limits: resolveEntryExecutionLimits(
|
|
4292
|
+
hookCtx.strategyConfig,
|
|
4293
|
+
decision.entryContext.direction
|
|
4294
|
+
)
|
|
4126
4295
|
});
|
|
4296
|
+
if (!beforeSubmit) {
|
|
4297
|
+
const arrivalSnapshot = await getOrderArrivalSnapshot({
|
|
4298
|
+
connector,
|
|
4299
|
+
symbol
|
|
4300
|
+
});
|
|
4301
|
+
validateEntryProtectionAtArrival({
|
|
4302
|
+
direction: decision.entryContext.direction,
|
|
4303
|
+
signalPrice: decision.entryContext.prices.currentPrice,
|
|
4304
|
+
bid: arrivalSnapshot.bid,
|
|
4305
|
+
ask: arrivalSnapshot.ask,
|
|
4306
|
+
arrivalMid: arrivalSnapshot.arrivalMid,
|
|
4307
|
+
takeProfits: decision.orderPlan.takeProfits,
|
|
4308
|
+
stopLossPrice: decision.orderPlan.stopLossPrice
|
|
4309
|
+
});
|
|
4310
|
+
}
|
|
4127
4311
|
const orderPlaced = await connector.placeOrder({
|
|
4128
4312
|
symbol,
|
|
4129
4313
|
qty: decision.orderPlan.qty,
|
|
@@ -4131,6 +4315,7 @@ var executeEntryDecision = async ({
|
|
|
4131
4315
|
timestamp: decision.entryContext.timestamp,
|
|
4132
4316
|
direction: decision.entryContext.direction,
|
|
4133
4317
|
positionIntent: decision.orderPlan.positionIntent,
|
|
4318
|
+
...beforeSubmit ? { beforeSubmit } : {},
|
|
4134
4319
|
...Number.isFinite(Number(hookCtx.strategyConfig.LEVERAGE)) ? { leverage: Number(hookCtx.strategyConfig.LEVERAGE) } : {}
|
|
4135
4320
|
});
|
|
4136
4321
|
if (!orderPlaced) {
|
|
@@ -4172,6 +4357,23 @@ var executeEntryDecision = async ({
|
|
|
4172
4357
|
{ decision, entry, market }
|
|
4173
4358
|
);
|
|
4174
4359
|
} catch (err) {
|
|
4360
|
+
if (err instanceof EntrySubmissionValidationError) {
|
|
4361
|
+
if (signal) {
|
|
4362
|
+
signal.orderStatus = "skipped";
|
|
4363
|
+
signal.orderSkipReason = err.message;
|
|
4364
|
+
signal.allocatorDecision = {
|
|
4365
|
+
stage: "runtime_admission",
|
|
4366
|
+
status: "rejected",
|
|
4367
|
+
reason: err.message
|
|
4368
|
+
};
|
|
4369
|
+
}
|
|
4370
|
+
logger9.warn(
|
|
4371
|
+
"entry rejected before submission: %s %s",
|
|
4372
|
+
symbol,
|
|
4373
|
+
err.message
|
|
4374
|
+
);
|
|
4375
|
+
return signal ?? err.message;
|
|
4376
|
+
}
|
|
4175
4377
|
if (signal) {
|
|
4176
4378
|
signal.orderStatus = "failed";
|
|
4177
4379
|
if (typeof signal.orderFailureReason !== "string" || !signal.orderFailureReason.trim()) {
|
|
@@ -4186,9 +4388,9 @@ var executeEntryDecision = async ({
|
|
|
4186
4388
|
market
|
|
4187
4389
|
});
|
|
4188
4390
|
if (err?.message === BACKTEST_WARNING_CODES.TAKE_PROFIT_CROSSED_BEFORE_ENTRY) {
|
|
4189
|
-
|
|
4391
|
+
logger9.warn("order warning: %s %s", symbol, err);
|
|
4190
4392
|
} else {
|
|
4191
|
-
|
|
4393
|
+
logger9.error("order error: %s %s", symbol, err);
|
|
4192
4394
|
}
|
|
4193
4395
|
return signal ?? "ORDER_ERROR";
|
|
4194
4396
|
}
|
|
@@ -4353,7 +4555,7 @@ var createStrategyRuntime = ({
|
|
|
4353
4555
|
try {
|
|
4354
4556
|
await projectHook(errorParams);
|
|
4355
4557
|
} catch (hookError) {
|
|
4356
|
-
|
|
4558
|
+
logger10.error(
|
|
4357
4559
|
"project hook onRuntimeError failed: %s %s",
|
|
4358
4560
|
strategyName,
|
|
4359
4561
|
hookError
|
|
@@ -4367,7 +4569,7 @@ var createStrategyRuntime = ({
|
|
|
4367
4569
|
try {
|
|
4368
4570
|
await onRuntimeError(errorParams);
|
|
4369
4571
|
} catch (hookError) {
|
|
4370
|
-
|
|
4572
|
+
logger10.error(
|
|
4371
4573
|
"runtime hook onRuntimeError failed: %s %s",
|
|
4372
4574
|
strategyName,
|
|
4373
4575
|
hookError
|
|
@@ -4381,7 +4583,7 @@ var createStrategyRuntime = ({
|
|
|
4381
4583
|
try {
|
|
4382
4584
|
return await hook(params);
|
|
4383
4585
|
} catch (error) {
|
|
4384
|
-
|
|
4586
|
+
logger10.error(
|
|
4385
4587
|
'strategy hook "%s" failed for %s: %s',
|
|
4386
4588
|
stage,
|
|
4387
4589
|
strategyName,
|
|
@@ -4580,8 +4782,11 @@ var createStrategyRuntime = ({
|
|
|
4580
4782
|
return enrichedBaseContext;
|
|
4581
4783
|
}
|
|
4582
4784
|
});
|
|
4785
|
+
const coreConfig = { ...config };
|
|
4786
|
+
delete coreConfig.RECORD_RUNTIME_TRADES;
|
|
4787
|
+
delete coreConfig.SIMULATE_ORDERS;
|
|
4583
4788
|
const core = await createCore({
|
|
4584
|
-
config,
|
|
4789
|
+
config: coreConfig,
|
|
4585
4790
|
data,
|
|
4586
4791
|
strategyApi,
|
|
4587
4792
|
indicatorsState
|
|
@@ -5200,6 +5405,8 @@ var createStrategyRuntime = ({
|
|
|
5200
5405
|
setStrategyRuntimeFactory(createStrategyRuntime);
|
|
5201
5406
|
|
|
5202
5407
|
export {
|
|
5408
|
+
getOrderArrivalSnapshot,
|
|
5409
|
+
validateEntryProtectionAtArrival,
|
|
5203
5410
|
buildMlPayload,
|
|
5204
5411
|
BINANCE_BREADTH_UNIVERSE_KEYS,
|
|
5205
5412
|
buildBinanceBreadthUniverseSnapshot,
|
|
@@ -5221,8 +5428,6 @@ export {
|
|
|
5221
5428
|
enrichSignalWithMl,
|
|
5222
5429
|
enrichSignalWithAi,
|
|
5223
5430
|
enrichSignalWithMlAi,
|
|
5224
|
-
getOrderArrivalSnapshot,
|
|
5225
|
-
validateEntryProtectionAtArrival,
|
|
5226
5431
|
executeEntryOrder,
|
|
5227
5432
|
resolveStrategyConfig,
|
|
5228
5433
|
RESEARCH_ENTRY_POLICY_CAPABILITIES,
|