@tradejs/node 3.1.29-beta.290 → 3.1.29-beta.291
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/ai.js +109 -48
- package/dist/ai.mjs +1 -1
- package/dist/backtest.js +2491 -1871
- package/dist/backtest.mjs +3 -2
- package/dist/{chunk-PB2KHBGH.mjs → chunk-HRON2EAI.mjs} +413 -22
- package/dist/{chunk-UL5Z46JL.mjs → chunk-PYJWXAYR.mjs} +97 -30
- package/dist/chunk-UUNEWAT4.mjs +193 -0
- package/dist/{chunk-BO42QWAB.mjs → chunk-W6PPAADK.mjs} +1 -1
- package/dist/cli.js +834 -773
- package/dist/cli.mjs +1 -1
- package/dist/diagnostics.js +2511 -1891
- package/dist/diagnostics.mjs +4 -3
- package/dist/mcp.d.mts +1 -1
- package/dist/mcp.d.ts +1 -1
- package/dist/mcp.js +2537 -1917
- package/dist/mcp.mjs +4 -3
- package/dist/portfolio.d.mts +28 -0
- package/dist/portfolio.d.ts +28 -0
- package/dist/portfolio.js +210 -0
- package/dist/portfolio.mjs +9 -0
- package/dist/registry.js +2429 -1809
- package/dist/registry.mjs +3 -2
- package/dist/runtimeDashboard.js +2236 -1614
- package/dist/runtimeDashboard.mjs +4 -3
- package/dist/runtimeStrategies.js +2052 -1432
- package/dist/runtimeStrategies.mjs +4 -3
- package/dist/strategies.d.mts +3 -1
- package/dist/strategies.d.ts +3 -1
- package/dist/strategies.js +2085 -1465
- package/dist/strategies.mjs +3 -2
- package/package.json +10 -5
package/dist/runtimeDashboard.js
CHANGED
|
@@ -784,7 +784,7 @@ var import_time4 = require("@tradejs/core/time");
|
|
|
784
784
|
var import_logger15 = require("@tradejs/infra/logger");
|
|
785
785
|
var import_runtimeDeploymentEvents = require("@tradejs/infra/runtimeDeploymentEvents");
|
|
786
786
|
var import_tradingAccounts3 = require("@tradejs/infra/tradingAccounts");
|
|
787
|
-
var
|
|
787
|
+
var import_redis5 = require("@tradejs/infra/redis");
|
|
788
788
|
|
|
789
789
|
// src/connectorsRegistry.ts
|
|
790
790
|
var import_logger4 = require("@tradejs/infra/logger");
|
|
@@ -2083,1376 +2083,2112 @@ var syncRuntimeTrades = async ({
|
|
|
2083
2083
|
|
|
2084
2084
|
// src/runtimeStrategies.ts
|
|
2085
2085
|
var import_jev8 = require("@tradejs/core/jev");
|
|
2086
|
-
var
|
|
2086
|
+
var import_node_crypto5 = require("crypto");
|
|
2087
2087
|
var import_promises2 = require("fs/promises");
|
|
2088
2088
|
var import_node_path2 = __toESM(require("path"));
|
|
2089
2089
|
var import_runtimeControls = require("@tradejs/infra/runtimeControls");
|
|
2090
2090
|
var import_tradingAccounts2 = require("@tradejs/infra/tradingAccounts");
|
|
2091
2091
|
|
|
2092
|
-
// src/
|
|
2093
|
-
var
|
|
2092
|
+
// src/strategy/portfolioRuntime.ts
|
|
2093
|
+
var import_node_crypto2 = require("crypto");
|
|
2094
|
+
var import_portfolio2 = require("@tradejs/core/portfolio");
|
|
2095
|
+
var import_redis3 = require("@tradejs/infra/redis");
|
|
2094
2096
|
|
|
2095
|
-
// src/
|
|
2096
|
-
var
|
|
2097
|
-
|
|
2098
|
-
|
|
2099
|
-
|
|
2100
|
-
|
|
2101
|
-
|
|
2102
|
-
|
|
2103
|
-
|
|
2104
|
-
|
|
2105
|
-
|
|
2106
|
-
|
|
2107
|
-
|
|
2108
|
-
|
|
2109
|
-
|
|
2110
|
-
|
|
2111
|
-
|
|
2112
|
-
|
|
2113
|
-
|
|
2114
|
-
|
|
2115
|
-
|
|
2116
|
-
|
|
2117
|
-
|
|
2118
|
-
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
const questions = questionsForJevInput(input);
|
|
2127
|
-
const questionsHash = (0, import_jev5.jevHash)(questions);
|
|
2128
|
-
const base = {
|
|
2129
|
-
questionSet: import_jev4.JEV_QUESTION_SET,
|
|
2130
|
-
schema: "tradejs-signal-assessment/v5",
|
|
2131
|
-
source: config.source,
|
|
2132
|
-
mode: config.mode,
|
|
2133
|
-
inputHash,
|
|
2134
|
-
questionsHash,
|
|
2135
|
-
geometryStatus: input.geometryStatus,
|
|
2136
|
-
levelsValid: typeof input.features["signal.validLevels"] === "boolean" ? input.features["signal.validLevels"] : null
|
|
2137
|
-
};
|
|
2138
|
-
if (!input.questions.length)
|
|
2139
|
-
return {
|
|
2140
|
-
assessment: {
|
|
2141
|
-
...base,
|
|
2142
|
-
status: "unavailable",
|
|
2143
|
-
model: "",
|
|
2144
|
-
scores: emptyScores(),
|
|
2145
|
-
confidence: emptyScores()
|
|
2146
|
-
}
|
|
2147
|
-
};
|
|
2148
|
-
if (config.source === "local") {
|
|
2149
|
-
const contents = await import_promises.default.readFile(
|
|
2150
|
-
import_node_path.default.resolve(projectRoot, config.modelFile),
|
|
2151
|
-
"utf8"
|
|
2152
|
-
);
|
|
2153
|
-
if ((0, import_jev5.jevFileHash)(contents) !== config.modelSha256)
|
|
2154
|
-
throw new Error("Local Jev gate checksum mismatch");
|
|
2155
|
-
const { validateJevGateModel: validateJevGateModel2 } = await Promise.resolve().then(() => (init_jevTraining(), jevTraining_exports));
|
|
2156
|
-
const model = validateJevGateModel2(JSON.parse(contents));
|
|
2157
|
-
if (model.strategy !== input.strategy || model.questionsHash !== (0, import_jev5.jevHash)(JEV_QUESTIONS))
|
|
2158
|
-
throw new Error("Local Jev gate strategy or question version mismatch");
|
|
2159
|
-
const scores = Object.fromEntries(
|
|
2160
|
-
import_jev4.JEV_DIMENSIONS.map((key) => [
|
|
2161
|
-
key,
|
|
2162
|
-
input.questions.includes(key) && model.trees[key] ? (0, import_jev4.predictJevTree)(model.trees[key], input.features) : null
|
|
2163
|
-
])
|
|
2164
|
-
);
|
|
2165
|
-
return {
|
|
2166
|
-
assessment: {
|
|
2167
|
-
...base,
|
|
2168
|
-
status: "available",
|
|
2169
|
-
model: config.modelSha256,
|
|
2170
|
-
scores,
|
|
2171
|
-
confidence: emptyScores()
|
|
2097
|
+
// src/portfolio.ts
|
|
2098
|
+
var import_portfolio = require("@tradejs/core/portfolio");
|
|
2099
|
+
function createPortfolioCoordinator({
|
|
2100
|
+
policy,
|
|
2101
|
+
strategy,
|
|
2102
|
+
store,
|
|
2103
|
+
execution
|
|
2104
|
+
}) {
|
|
2105
|
+
const fingerprint3 = (0, import_portfolio.portfolioFingerprint)(policy, strategy);
|
|
2106
|
+
let cache;
|
|
2107
|
+
async function transact(now2, change, force = false) {
|
|
2108
|
+
for (let attempt = 0; attempt < 16; attempt++) {
|
|
2109
|
+
const previous = await store.read();
|
|
2110
|
+
if (previous && previous.fingerprint !== fingerprint3)
|
|
2111
|
+
throw new Error(
|
|
2112
|
+
"Portfolio policy changed under existing state; reconcile the persisted scope before changing configuration"
|
|
2113
|
+
);
|
|
2114
|
+
const base = previous ?? (0, import_portfolio.createPortfolioState)(fingerprint3);
|
|
2115
|
+
const bucket = Math.floor(now2 / policy.observationIntervalMs);
|
|
2116
|
+
const observation = !force && cache?.revision === base.revision && cache.bucket === bucket ? cache.observation : await execution.observe(now2, base.reservations);
|
|
2117
|
+
const reconciled = (0, import_portfolio.reconcilePortfolio)(
|
|
2118
|
+
policy,
|
|
2119
|
+
strategy,
|
|
2120
|
+
base,
|
|
2121
|
+
observation
|
|
2122
|
+
);
|
|
2123
|
+
for (const intent of base.closing) {
|
|
2124
|
+
if (!observation.positions.some(
|
|
2125
|
+
(p) => p.symbol === intent.position.symbol
|
|
2126
|
+
))
|
|
2127
|
+
await execution.confirmClosed?.(intent, observation.asOf);
|
|
2172
2128
|
}
|
|
2173
|
-
|
|
2174
|
-
|
|
2175
|
-
|
|
2176
|
-
|
|
2177
|
-
|
|
2178
|
-
const file = import_node_path.default.join(dir, "records", `${id}.json`);
|
|
2179
|
-
const getRecord = async () => {
|
|
2180
|
-
const existing = await (0, import_jev5.readJevArtifact)(file);
|
|
2181
|
-
if (existing) {
|
|
2182
|
-
if (existing.schema !== "tradejs-jev-record/v4" || existing.id !== id || existing.inputHash !== inputHash || existing.questionsHash !== questionsHash || (0, import_jev5.jevHash)(existing.input) !== inputHash || (0, import_jev5.jevHash)(existing.provider) !== (0, import_jev5.jevHash)(provider))
|
|
2183
|
-
throw new Error("Jev recording identity mismatch");
|
|
2184
|
-
(0, import_jev5.validateJevResponse)(existing.response, input.questions);
|
|
2185
|
-
for (const key of import_jev4.JEV_DIMENSIONS) {
|
|
2186
|
-
const expected = input.questions.includes(key) ? existing.response.answers[key].score / (0, import_jev5.jevScoreMaximum)(key) : null;
|
|
2187
|
-
if (existing.scores[key] !== expected)
|
|
2188
|
-
throw new Error("Jev recording scores do not match the response");
|
|
2129
|
+
const changed = change(reconciled, observation);
|
|
2130
|
+
const next = { ...changed.state, revision: base.revision + 1 };
|
|
2131
|
+
if (await store.compare(previous, next)) {
|
|
2132
|
+
cache = { revision: next.revision, bucket, observation };
|
|
2133
|
+
return { state: next, observation, result: changed.result };
|
|
2189
2134
|
}
|
|
2190
|
-
|
|
2135
|
+
cache = void 0;
|
|
2191
2136
|
}
|
|
2192
|
-
|
|
2193
|
-
|
|
2194
|
-
|
|
2137
|
+
throw new Error("Portfolio state contention; entry was not submitted");
|
|
2138
|
+
}
|
|
2139
|
+
async function tick(now2, force = false) {
|
|
2140
|
+
const claimed = await transact(
|
|
2141
|
+
now2,
|
|
2142
|
+
(state, observation) => {
|
|
2143
|
+
const commands = (0, import_portfolio.portfolioCloseCommands)(policy, state, observation);
|
|
2144
|
+
return {
|
|
2145
|
+
state: {
|
|
2146
|
+
...state,
|
|
2147
|
+
closing: state.closing.map(
|
|
2148
|
+
(c) => commands.some((x) => x.intent.positionId === c.positionId) ? { ...c, lastAttemptAt: observation.asOf } : c
|
|
2149
|
+
)
|
|
2150
|
+
},
|
|
2151
|
+
result: commands
|
|
2152
|
+
};
|
|
2153
|
+
},
|
|
2154
|
+
force
|
|
2155
|
+
);
|
|
2156
|
+
for (const command of claimed.result)
|
|
2157
|
+
await execution.close(
|
|
2158
|
+
command.position,
|
|
2159
|
+
command.intent,
|
|
2160
|
+
claimed.observation.asOf
|
|
2195
2161
|
);
|
|
2196
|
-
|
|
2197
|
-
|
|
2198
|
-
|
|
2199
|
-
|
|
2200
|
-
|
|
2201
|
-
|
|
2202
|
-
|
|
2203
|
-
|
|
2162
|
+
if (claimed.result.length) {
|
|
2163
|
+
cache = void 0;
|
|
2164
|
+
const refreshed = await transact(
|
|
2165
|
+
now2,
|
|
2166
|
+
(state) => ({ state, result: void 0 }),
|
|
2167
|
+
true
|
|
2168
|
+
);
|
|
2169
|
+
return (0, import_portfolio.buildPortfolioContext)(
|
|
2170
|
+
policy,
|
|
2171
|
+
strategy,
|
|
2172
|
+
refreshed.state,
|
|
2173
|
+
refreshed.observation
|
|
2204
2174
|
);
|
|
2205
|
-
const started = Date.now();
|
|
2206
|
-
const state = stateForJevInput(input);
|
|
2207
|
-
const responseId = (0, import_jev5.jevHash)({
|
|
2208
|
-
state,
|
|
2209
|
-
questionsHash,
|
|
2210
|
-
provider,
|
|
2211
|
-
user: (0, import_jev5.jevHash)(userName)
|
|
2212
|
-
});
|
|
2213
|
-
const responseFile = import_node_path.default.join(dir, "responses", `${responseId}.json`);
|
|
2214
|
-
let responsePromise = pendingResponses.get(responseFile);
|
|
2215
|
-
if (!responsePromise) {
|
|
2216
|
-
responsePromise = (async () => {
|
|
2217
|
-
const saved = await (0, import_jev5.readJevArtifact)(responseFile);
|
|
2218
|
-
if (saved) return (0, import_jev5.validateJevResponse)(saved, input.questions);
|
|
2219
|
-
const fresh = await (0, import_jev5.requestJev)(provider, apiKey, state, questions);
|
|
2220
|
-
await (0, import_jev5.writeJevArtifact)(responseFile, fresh);
|
|
2221
|
-
return fresh;
|
|
2222
|
-
})();
|
|
2223
|
-
pendingResponses.set(responseFile, responsePromise);
|
|
2224
|
-
}
|
|
2225
|
-
let response;
|
|
2226
|
-
try {
|
|
2227
|
-
response = await responsePromise;
|
|
2228
|
-
} finally {
|
|
2229
|
-
pendingResponses.delete(responseFile);
|
|
2230
2175
|
}
|
|
2231
|
-
|
|
2232
|
-
|
|
2233
|
-
|
|
2234
|
-
|
|
2235
|
-
|
|
2176
|
+
return (0, import_portfolio.buildPortfolioContext)(
|
|
2177
|
+
policy,
|
|
2178
|
+
strategy,
|
|
2179
|
+
claimed.state,
|
|
2180
|
+
claimed.observation
|
|
2236
2181
|
);
|
|
2237
|
-
const value = {
|
|
2238
|
-
schema: "tradejs-jev-record/v4",
|
|
2239
|
-
id,
|
|
2240
|
-
inputHash,
|
|
2241
|
-
questionsHash,
|
|
2242
|
-
provider,
|
|
2243
|
-
input,
|
|
2244
|
-
response,
|
|
2245
|
-
scores,
|
|
2246
|
-
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2247
|
-
elapsedMs: Date.now() - started
|
|
2248
|
-
};
|
|
2249
|
-
await (0, import_jev5.writeJevArtifact)(file, value);
|
|
2250
|
-
return await (0, import_jev5.readJevArtifact)(file);
|
|
2251
|
-
};
|
|
2252
|
-
const pendingKey = `${file}:${(0, import_jev5.jevHash)(userName)}`;
|
|
2253
|
-
let promise = pending.get(pendingKey);
|
|
2254
|
-
if (!promise) {
|
|
2255
|
-
promise = getRecord();
|
|
2256
|
-
pending.set(pendingKey, promise);
|
|
2257
2182
|
}
|
|
2258
|
-
|
|
2259
|
-
|
|
2260
|
-
|
|
2261
|
-
|
|
2262
|
-
|
|
2183
|
+
async function updateReservation(now2, id, submitted, qty) {
|
|
2184
|
+
return transact(
|
|
2185
|
+
now2,
|
|
2186
|
+
(s, o) => {
|
|
2187
|
+
const exists = s.reservations.some((r) => r.id === id);
|
|
2188
|
+
const blocked = submitted && (!exists || s.closing.length > 0 || o.asOf <= s.blockedThrough);
|
|
2189
|
+
return {
|
|
2190
|
+
state: {
|
|
2191
|
+
...s,
|
|
2192
|
+
reservations: s.reservations.flatMap(
|
|
2193
|
+
(r) => r.id !== id ? [r] : submitted && !blocked ? [{ ...r, submitted: true, qty: qty ?? r.qty }] : []
|
|
2194
|
+
)
|
|
2195
|
+
},
|
|
2196
|
+
result: !blocked
|
|
2197
|
+
};
|
|
2198
|
+
},
|
|
2199
|
+
true
|
|
2200
|
+
);
|
|
2263
2201
|
}
|
|
2264
2202
|
return {
|
|
2265
|
-
|
|
2266
|
-
|
|
2267
|
-
|
|
2268
|
-
|
|
2269
|
-
|
|
2270
|
-
|
|
2271
|
-
|
|
2272
|
-
|
|
2273
|
-
|
|
2274
|
-
|
|
2275
|
-
|
|
2276
|
-
|
|
2277
|
-
|
|
2203
|
+
async context(now2) {
|
|
2204
|
+
const state = await store.read() ?? (0, import_portfolio.createPortfolioState)(fingerprint3);
|
|
2205
|
+
if (state.fingerprint !== fingerprint3)
|
|
2206
|
+
throw new Error("Portfolio policy changed under existing state");
|
|
2207
|
+
const observation = await execution.observe(now2, state.reservations);
|
|
2208
|
+
(0, import_portfolio.validatePortfolioObservation)(observation);
|
|
2209
|
+
return (0, import_portfolio.buildPortfolioContext)(policy, strategy, state, observation);
|
|
2210
|
+
},
|
|
2211
|
+
tick,
|
|
2212
|
+
async submit(now2, entry, send) {
|
|
2213
|
+
await tick(now2);
|
|
2214
|
+
const admitted = await transact(
|
|
2215
|
+
now2,
|
|
2216
|
+
(s, o) => {
|
|
2217
|
+
const r = (0, import_portfolio.reservePortfolioEntry)(policy, strategy, s, o, {
|
|
2218
|
+
...entry,
|
|
2219
|
+
submitted: false,
|
|
2220
|
+
oppositeHandled: false
|
|
2221
|
+
});
|
|
2222
|
+
return { state: r.state, result: r.reason };
|
|
2223
|
+
},
|
|
2224
|
+
true
|
|
2225
|
+
);
|
|
2226
|
+
if (admitted.result) return { reason: admitted.result };
|
|
2227
|
+
let submitted = false;
|
|
2228
|
+
try {
|
|
2229
|
+
const value = await send(
|
|
2230
|
+
async (qty) => {
|
|
2231
|
+
if (!Number.isFinite(qty) || qty <= 0)
|
|
2232
|
+
throw new Error("Invalid submitted portfolio quantity");
|
|
2233
|
+
submitted = true;
|
|
2234
|
+
const update = await updateReservation(now2, entry.id, true, qty);
|
|
2235
|
+
if (!update.result)
|
|
2236
|
+
throw new Error("PORTFOLIO_SUBMISSION_NO_LONGER_ADMITTED");
|
|
2237
|
+
},
|
|
2238
|
+
async () => {
|
|
2239
|
+
await transact(
|
|
2240
|
+
now2,
|
|
2241
|
+
(s) => ({
|
|
2242
|
+
state: {
|
|
2243
|
+
...s,
|
|
2244
|
+
reservations: s.reservations.filter((r) => r.id !== entry.id),
|
|
2245
|
+
completedSignals: {
|
|
2246
|
+
...s.completedSignals,
|
|
2247
|
+
[entry.signalId]: now2
|
|
2248
|
+
}
|
|
2249
|
+
},
|
|
2250
|
+
result: void 0
|
|
2251
|
+
}),
|
|
2252
|
+
true
|
|
2253
|
+
);
|
|
2254
|
+
}
|
|
2255
|
+
);
|
|
2256
|
+
if (!submitted) await updateReservation(now2, entry.id, false);
|
|
2257
|
+
else await tick(now2, true);
|
|
2258
|
+
return { value };
|
|
2259
|
+
} catch (error) {
|
|
2260
|
+
if (!submitted) await updateReservation(now2, entry.id, false);
|
|
2261
|
+
throw error;
|
|
2262
|
+
}
|
|
2278
2263
|
}
|
|
2279
2264
|
};
|
|
2280
|
-
}
|
|
2281
|
-
|
|
2282
|
-
|
|
2283
|
-
|
|
2284
|
-
|
|
2285
|
-
|
|
2286
|
-
|
|
2287
|
-
|
|
2288
|
-
|
|
2289
|
-
|
|
2290
|
-
try {
|
|
2291
|
-
const input = buildJevInput(signal);
|
|
2292
|
-
({ assessment } = await evaluateJevInput({
|
|
2293
|
-
input,
|
|
2294
|
-
config,
|
|
2295
|
-
userName,
|
|
2296
|
-
projectRoot
|
|
2297
|
-
}));
|
|
2298
|
-
} catch (error) {
|
|
2299
|
-
if (strict) throw error;
|
|
2300
|
-
assessment = {
|
|
2301
|
-
questionSet: import_jev4.JEV_QUESTION_SET,
|
|
2302
|
-
schema: "tradejs-signal-assessment/v5",
|
|
2303
|
-
source: config.source,
|
|
2304
|
-
mode: config.mode,
|
|
2305
|
-
status: "unavailable",
|
|
2306
|
-
inputHash: "",
|
|
2307
|
-
questionsHash: "",
|
|
2308
|
-
geometryStatus: "absent",
|
|
2309
|
-
levelsValid: null,
|
|
2310
|
-
model: config.provider?.model ?? config.modelSha256 ?? "",
|
|
2311
|
-
scores: emptyScores(),
|
|
2312
|
-
confidence: emptyScores()
|
|
2313
|
-
};
|
|
2314
|
-
}
|
|
2315
|
-
signal.assessment = assessment;
|
|
2316
|
-
signal.additionalIndicators = {
|
|
2317
|
-
...signal.additionalIndicators,
|
|
2318
|
-
jev: (0, import_jev4.jevAssessmentFeatures)(assessment)
|
|
2265
|
+
}
|
|
2266
|
+
function createMemoryPortfolioStore() {
|
|
2267
|
+
let state = null;
|
|
2268
|
+
return {
|
|
2269
|
+
read: async () => state === null ? null : structuredClone(state),
|
|
2270
|
+
compare: async (expected, next) => {
|
|
2271
|
+
if ((state?.revision ?? -1) !== (expected?.revision ?? -1)) return false;
|
|
2272
|
+
state = structuredClone(next);
|
|
2273
|
+
return true;
|
|
2274
|
+
}
|
|
2319
2275
|
};
|
|
2320
|
-
|
|
2321
|
-
signalId: signal.signalId,
|
|
2322
|
-
strategy: signal.strategy,
|
|
2323
|
-
symbol: signal.symbol,
|
|
2324
|
-
timestamp: signal.timestamp,
|
|
2325
|
-
scope,
|
|
2326
|
-
user: (0, import_jev5.jevHash)(userName),
|
|
2327
|
-
config,
|
|
2328
|
-
assessment
|
|
2329
|
-
};
|
|
2330
|
-
await (0, import_jev5.writeJevArtifact)(
|
|
2331
|
-
import_node_path.default.resolve(
|
|
2332
|
-
projectRoot,
|
|
2333
|
-
config.recordsDir ?? "data/ai/jev",
|
|
2334
|
-
"decisions",
|
|
2335
|
-
`${(0, import_jev5.jevHash)(identity)}.json`
|
|
2336
|
-
),
|
|
2337
|
-
identity
|
|
2338
|
-
);
|
|
2339
|
-
return assessment;
|
|
2340
|
-
};
|
|
2341
|
-
|
|
2342
|
-
// src/strategyRuntime.ts
|
|
2343
|
-
var import_constants7 = require("@tradejs/core/constants");
|
|
2344
|
-
var import_strategies6 = require("@tradejs/core/strategies");
|
|
2345
|
-
var import_logger14 = require("@tradejs/infra/logger");
|
|
2346
|
-
|
|
2347
|
-
// src/strategyHelpers/runtime.ts
|
|
2348
|
-
var import_logger12 = require("@tradejs/infra/logger");
|
|
2349
|
-
var import_constants5 = require("@tradejs/core/constants");
|
|
2350
|
-
var import_ml2 = require("@tradejs/infra/ml");
|
|
2276
|
+
}
|
|
2351
2277
|
|
|
2352
|
-
// src/
|
|
2278
|
+
// src/runtimeJournal.ts
|
|
2279
|
+
var import_node_crypto = require("crypto");
|
|
2280
|
+
var import_constants3 = require("@tradejs/core/constants");
|
|
2281
|
+
var import_time3 = require("@tradejs/core/time");
|
|
2282
|
+
var import_trade = require("@tradejs/core/trade");
|
|
2353
2283
|
var import_logger5 = require("@tradejs/infra/logger");
|
|
2354
|
-
|
|
2355
|
-
|
|
2356
|
-
var
|
|
2357
|
-
|
|
2358
|
-
super(...arguments);
|
|
2359
|
-
this.name = "EntrySubmissionValidationError";
|
|
2360
|
-
}
|
|
2361
|
-
};
|
|
2362
|
-
var resolveEntryExecutionLimits = (config, direction) => {
|
|
2363
|
-
const side = config[direction];
|
|
2364
|
-
const minimum = side && typeof side === "object" && "minRiskRatio" in side ? side.minRiskRatio : void 0;
|
|
2365
|
-
const maximum = config.MAX_LOSS_VALUE;
|
|
2366
|
-
if (minimum == null && maximum == null) return void 0;
|
|
2367
|
-
const minimumNetRiskRatio = minimum == null ? void 0 : Number(minimum);
|
|
2368
|
-
const maxLossValue = maximum == null ? void 0 : Number(maximum);
|
|
2369
|
-
const feeRate = Number(config.RISK_FEE_RATE ?? 0);
|
|
2370
|
-
const slippage = Number(config.RISK_SLIPPAGE_BPS ?? 0);
|
|
2371
|
-
const impact = Number(config.RISK_MARKET_IMPACT_BPS ?? 0);
|
|
2372
|
-
const slippageBps = slippage + impact;
|
|
2373
|
-
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)
|
|
2374
|
-
throw new Error("INVALID_ENTRY_EXECUTION_LIMITS");
|
|
2375
|
-
return { minimumNetRiskRatio, maxLossValue, feeRate, slippageBps };
|
|
2376
|
-
};
|
|
2377
|
-
var validateEntryExecution = ({
|
|
2284
|
+
var import_redis2 = require("@tradejs/infra/redis");
|
|
2285
|
+
var now = () => Date.now();
|
|
2286
|
+
var toRandomOrderSuffix = () => (0, import_node_crypto.randomUUID)().replace(/-/g, "").slice(0, 12).toLowerCase();
|
|
2287
|
+
var calculateClosedPnl = ({
|
|
2378
2288
|
direction,
|
|
2379
|
-
|
|
2380
|
-
|
|
2381
|
-
|
|
2382
|
-
takeProfitPrice,
|
|
2383
|
-
limits
|
|
2289
|
+
entryPrice,
|
|
2290
|
+
exitPrice,
|
|
2291
|
+
qty
|
|
2384
2292
|
}) => {
|
|
2385
|
-
|
|
2386
|
-
|
|
2387
|
-
"ENTRY_ECONOMICS_INPUTS_UNAVAILABLE"
|
|
2388
|
-
);
|
|
2389
|
-
const side = direction === "LONG" ? 1 : -1;
|
|
2390
|
-
if (side * (price - stopLossPrice) <= 0 || side * (takeProfitPrice - price) <= 0)
|
|
2391
|
-
throw new EntrySubmissionValidationError(
|
|
2392
|
-
"INVALID_ENTRY_PROTECTION_AT_SUBMISSION"
|
|
2393
|
-
);
|
|
2394
|
-
const slippage = limits.slippageBps / 1e4;
|
|
2395
|
-
const cost = (value, sign) => value * slippage + value * (1 + sign * slippage) * limits.feeRate;
|
|
2396
|
-
const entryCost = cost(price, side);
|
|
2397
|
-
const lossPerUnit = Math.abs(price - stopLossPrice) + entryCost + cost(stopLossPrice, -side);
|
|
2398
|
-
const rewardPerUnit = Math.max(
|
|
2399
|
-
0,
|
|
2400
|
-
Math.abs(takeProfitPrice - price) - entryCost - cost(takeProfitPrice, -side)
|
|
2401
|
-
);
|
|
2402
|
-
const netRiskRatio = rewardPerUnit / lossPerUnit;
|
|
2403
|
-
const plannedLossValue = qty * lossPerUnit;
|
|
2404
|
-
if (!Number.isFinite(netRiskRatio) || !Number.isFinite(plannedLossValue))
|
|
2405
|
-
throw new EntrySubmissionValidationError(
|
|
2406
|
-
"ENTRY_ECONOMICS_INPUTS_UNAVAILABLE"
|
|
2407
|
-
);
|
|
2408
|
-
if (limits.minimumNetRiskRatio != null && netRiskRatio <= limits.minimumNetRiskRatio)
|
|
2409
|
-
throw new EntrySubmissionValidationError(
|
|
2410
|
-
"ENTRY_RR_BELOW_MIN_AT_SUBMISSION"
|
|
2411
|
-
);
|
|
2412
|
-
const riskExceeded = limits.maxLossValue != null && plannedLossValue > limits.maxLossValue * (1 + 1e-9);
|
|
2413
|
-
return { netRiskRatio, plannedLossValue, riskExceeded };
|
|
2293
|
+
const pnl = direction === "LONG" ? (exitPrice - entryPrice) * qty : (entryPrice - exitPrice) * qty;
|
|
2294
|
+
return Number.isFinite(pnl) ? pnl : null;
|
|
2414
2295
|
};
|
|
2415
|
-
|
|
2416
|
-
|
|
2417
|
-
|
|
2418
|
-
|
|
2419
|
-
|
|
2420
|
-
|
|
2421
|
-
|
|
2422
|
-
|
|
2423
|
-
}
|
|
2424
|
-
if (
|
|
2425
|
-
return
|
|
2426
|
-
arrivalSnapshotTime: Date.now(),
|
|
2427
|
-
arrivalSource: "unavailable",
|
|
2428
|
-
bid: null,
|
|
2429
|
-
ask: null,
|
|
2430
|
-
arrivalMid: null,
|
|
2431
|
-
spreadBps: null
|
|
2432
|
-
};
|
|
2296
|
+
var createRuntimeOrderId = (strategy) => {
|
|
2297
|
+
const prefix = (0, import_trade.createRuntimeOrderLinkPrefix)(strategy);
|
|
2298
|
+
if (prefix === "tjs-") {
|
|
2299
|
+
return `tjs-${(0, import_node_crypto.randomUUID)().replace(/-/g, "").slice(0, 24).toLowerCase()}`;
|
|
2300
|
+
}
|
|
2301
|
+
return `${prefix}${toRandomOrderSuffix()}`;
|
|
2302
|
+
};
|
|
2303
|
+
var recordRuntimeTradeOpen = async (params) => {
|
|
2304
|
+
const { userName } = params;
|
|
2305
|
+
if (!userName) {
|
|
2306
|
+
return null;
|
|
2433
2307
|
}
|
|
2308
|
+
const record = {
|
|
2309
|
+
...params,
|
|
2310
|
+
entryCount: 1,
|
|
2311
|
+
lastEntryPrice: params.entryPrice,
|
|
2312
|
+
lastEntryQty: params.qty,
|
|
2313
|
+
lastEntryTimestamp: params.entryTimestamp,
|
|
2314
|
+
status: "active",
|
|
2315
|
+
currentPrice: params.entryPrice,
|
|
2316
|
+
currentPnl: 0,
|
|
2317
|
+
closedPnl: null,
|
|
2318
|
+
exitPrice: null,
|
|
2319
|
+
exitTimestamp: null,
|
|
2320
|
+
lastSyncedAt: now()
|
|
2321
|
+
};
|
|
2322
|
+
const dayKey = (0, import_time3.getRuntimeStorageDayKey)(record.entryTimestamp);
|
|
2323
|
+
const runtimeScopeId = record.deploymentId ?? record.accountId;
|
|
2434
2324
|
try {
|
|
2435
|
-
|
|
2436
|
-
|
|
2437
|
-
|
|
2438
|
-
|
|
2439
|
-
|
|
2440
|
-
|
|
2441
|
-
|
|
2442
|
-
|
|
2443
|
-
|
|
2444
|
-
|
|
2445
|
-
|
|
2446
|
-
|
|
2447
|
-
|
|
2448
|
-
|
|
2449
|
-
|
|
2325
|
+
await Promise.all([
|
|
2326
|
+
(0, import_redis2.setData)(import_redis2.redisKeys.runtimeTrade(userName, record.orderId), record, {
|
|
2327
|
+
expire: 0
|
|
2328
|
+
}),
|
|
2329
|
+
(0, import_redis2.setHashJsonField)(
|
|
2330
|
+
import_redis2.redisKeys.runtimeTradeBucket(userName, dayKey),
|
|
2331
|
+
record.orderId,
|
|
2332
|
+
record,
|
|
2333
|
+
{ expire: 0 }
|
|
2334
|
+
),
|
|
2335
|
+
(0, import_redis2.setData)(
|
|
2336
|
+
import_redis2.redisKeys.runtimeActiveTrade(userName, record.symbol, runtimeScopeId),
|
|
2337
|
+
{ orderId: record.orderId },
|
|
2338
|
+
{ expire: 0 }
|
|
2339
|
+
)
|
|
2340
|
+
]);
|
|
2450
2341
|
} catch (error) {
|
|
2451
|
-
import_logger5.logger.
|
|
2452
|
-
"runtime
|
|
2453
|
-
symbol,
|
|
2342
|
+
import_logger5.logger.error(
|
|
2343
|
+
"runtime trade open journal failed: %s %s",
|
|
2344
|
+
record.symbol,
|
|
2454
2345
|
error?.message || String(error)
|
|
2455
2346
|
);
|
|
2456
|
-
return {
|
|
2457
|
-
arrivalSnapshotTime: Date.now(),
|
|
2458
|
-
arrivalSource: "top_of_book_error",
|
|
2459
|
-
bid: null,
|
|
2460
|
-
ask: null,
|
|
2461
|
-
arrivalMid: null,
|
|
2462
|
-
spreadBps: null
|
|
2463
|
-
};
|
|
2464
2347
|
}
|
|
2348
|
+
return record;
|
|
2465
2349
|
};
|
|
2466
|
-
var
|
|
2467
|
-
|
|
2468
|
-
|
|
2469
|
-
|
|
2470
|
-
|
|
2471
|
-
|
|
2472
|
-
|
|
2473
|
-
|
|
2474
|
-
|
|
2475
|
-
|
|
2476
|
-
|
|
2477
|
-
|
|
2478
|
-
|
|
2479
|
-
|
|
2480
|
-
|
|
2481
|
-
|
|
2482
|
-
|
|
2483
|
-
}
|
|
2350
|
+
var recordRuntimeTradeIncrease = async (params) => {
|
|
2351
|
+
const {
|
|
2352
|
+
userName,
|
|
2353
|
+
strategy,
|
|
2354
|
+
symbol,
|
|
2355
|
+
direction,
|
|
2356
|
+
resultingQty,
|
|
2357
|
+
resultingEntryPrice,
|
|
2358
|
+
addedQty,
|
|
2359
|
+
addedEntryPrice,
|
|
2360
|
+
entryTimestamp,
|
|
2361
|
+
fee,
|
|
2362
|
+
accountId,
|
|
2363
|
+
deploymentId
|
|
2364
|
+
} = params;
|
|
2365
|
+
if (!userName) {
|
|
2366
|
+
return null;
|
|
2484
2367
|
}
|
|
2485
|
-
|
|
2486
|
-
|
|
2368
|
+
const existing = await getActiveRuntimeTrade({
|
|
2369
|
+
userName,
|
|
2370
|
+
symbol,
|
|
2371
|
+
accountId,
|
|
2372
|
+
deploymentId
|
|
2373
|
+
});
|
|
2374
|
+
if (!existing || existing.strategy !== strategy || existing.direction !== direction) {
|
|
2375
|
+
return null;
|
|
2487
2376
|
}
|
|
2488
|
-
|
|
2489
|
-
|
|
2490
|
-
|
|
2491
|
-
|
|
2492
|
-
|
|
2493
|
-
|
|
2494
|
-
|
|
2495
|
-
|
|
2496
|
-
|
|
2497
|
-
|
|
2498
|
-
|
|
2499
|
-
|
|
2500
|
-
|
|
2501
|
-
|
|
2502
|
-
|
|
2503
|
-
|
|
2504
|
-
|
|
2505
|
-
|
|
2506
|
-
arrivalSnapshotTime: Date.now(),
|
|
2507
|
-
arrivalSource: "simulation",
|
|
2508
|
-
bid: executionPrice,
|
|
2509
|
-
ask: executionPrice,
|
|
2510
|
-
arrivalMid: executionPrice,
|
|
2511
|
-
spreadBps: 0
|
|
2512
|
-
} : await getOrderArrivalSnapshot({
|
|
2513
|
-
connector,
|
|
2514
|
-
symbol,
|
|
2515
|
-
requireTimestamp: true
|
|
2516
|
-
});
|
|
2517
|
-
const age = Date.now() - snapshot2.arrivalSnapshotTime;
|
|
2518
|
-
if (snapshot2.bid == null || snapshot2.ask == null || !Number.isFinite(snapshot2.bid) || !Number.isFinite(snapshot2.ask) || snapshot2.bid <= 0 || snapshot2.ask < snapshot2.bid)
|
|
2519
|
-
throw new EntrySubmissionValidationError(
|
|
2520
|
-
"ENTRY_QUOTE_UNAVAILABLE_AT_SUBMISSION"
|
|
2521
|
-
);
|
|
2522
|
-
if (!Number.isFinite(age) || age < -MAX_ENTRY_QUOTE_AGE_MS || age > MAX_ENTRY_QUOTE_AGE_MS)
|
|
2523
|
-
throw new EntrySubmissionValidationError(
|
|
2524
|
-
"ENTRY_QUOTE_STALE_AT_SUBMISSION"
|
|
2525
|
-
);
|
|
2526
|
-
const price = direction === "LONG" ? snapshot2.ask : snapshot2.bid;
|
|
2527
|
-
validateEntryProtectionAtArrival({
|
|
2528
|
-
direction,
|
|
2529
|
-
signalPrice: price,
|
|
2530
|
-
bid: snapshot2.bid,
|
|
2531
|
-
ask: snapshot2.ask,
|
|
2532
|
-
arrivalMid: snapshot2.arrivalMid,
|
|
2533
|
-
takeProfits,
|
|
2534
|
-
stopLossPrice
|
|
2535
|
-
});
|
|
2536
|
-
const prices = takeProfits.map((tp) => tp.price);
|
|
2537
|
-
const economics2 = validateEntryExecution({
|
|
2538
|
-
direction,
|
|
2539
|
-
price,
|
|
2540
|
-
qty,
|
|
2541
|
-
stopLossPrice,
|
|
2542
|
-
takeProfitPrice: direction === "LONG" ? Math.max(...prices) : Math.min(...prices),
|
|
2543
|
-
limits
|
|
2544
|
-
});
|
|
2545
|
-
onValidated?.(snapshot2, economics2);
|
|
2377
|
+
const addedFee = typeof fee === "number" && Number.isFinite(fee) ? fee : 0;
|
|
2378
|
+
const openFee = (existing.openFee ?? existing.fee ?? 0) + addedFee;
|
|
2379
|
+
const totalFee = (existing.totalFee ?? existing.fee ?? 0) + addedFee;
|
|
2380
|
+
const next = {
|
|
2381
|
+
...existing,
|
|
2382
|
+
...params.orderLeverage ? { orderLeverage: params.orderLeverage } : {},
|
|
2383
|
+
qty: resultingQty,
|
|
2384
|
+
entryPrice: resultingEntryPrice,
|
|
2385
|
+
entryCount: Math.max(1, existing.entryCount ?? 1) + 1,
|
|
2386
|
+
lastEntryPrice: addedEntryPrice,
|
|
2387
|
+
lastEntryQty: addedQty,
|
|
2388
|
+
lastEntryTimestamp: entryTimestamp,
|
|
2389
|
+
currentPrice: resultingEntryPrice,
|
|
2390
|
+
currentPnl: 0,
|
|
2391
|
+
fee: openFee,
|
|
2392
|
+
openFee,
|
|
2393
|
+
totalFee,
|
|
2394
|
+
lastSyncedAt: now()
|
|
2546
2395
|
};
|
|
2547
|
-
|
|
2548
|
-
|
|
2549
|
-
|
|
2550
|
-
|
|
2551
|
-
|
|
2552
|
-
|
|
2553
|
-
|
|
2554
|
-
|
|
2555
|
-
|
|
2556
|
-
|
|
2557
|
-
|
|
2558
|
-
|
|
2559
|
-
|
|
2560
|
-
|
|
2561
|
-
|
|
2562
|
-
|
|
2563
|
-
|
|
2564
|
-
|
|
2565
|
-
}
|
|
2566
|
-
return trimmed.map(
|
|
2567
|
-
(item) => item && typeof item === "object" ? trimSeriesDeep(item) : item
|
|
2568
|
-
);
|
|
2569
|
-
}
|
|
2570
|
-
if (value && typeof value === "object") {
|
|
2571
|
-
return Object.fromEntries(
|
|
2572
|
-
Object.entries(value).map(([key, nested]) => [
|
|
2573
|
-
key,
|
|
2574
|
-
trimSeriesDeep(nested)
|
|
2575
|
-
])
|
|
2396
|
+
const dayKey = (0, import_time3.getRuntimeStorageDayKey)(existing.entryTimestamp);
|
|
2397
|
+
try {
|
|
2398
|
+
await Promise.all([
|
|
2399
|
+
(0, import_redis2.setData)(import_redis2.redisKeys.runtimeTrade(userName, existing.orderId), next, {
|
|
2400
|
+
expire: 0
|
|
2401
|
+
}),
|
|
2402
|
+
(0, import_redis2.setHashJsonField)(
|
|
2403
|
+
import_redis2.redisKeys.runtimeTradeBucket(userName, dayKey),
|
|
2404
|
+
existing.orderId,
|
|
2405
|
+
next,
|
|
2406
|
+
{ expire: 0 }
|
|
2407
|
+
)
|
|
2408
|
+
]);
|
|
2409
|
+
} catch (error) {
|
|
2410
|
+
import_logger5.logger.error(
|
|
2411
|
+
"runtime trade increase journal failed: %s %s",
|
|
2412
|
+
symbol,
|
|
2413
|
+
error?.message || String(error)
|
|
2576
2414
|
);
|
|
2577
2415
|
}
|
|
2578
|
-
return
|
|
2416
|
+
return next;
|
|
2579
2417
|
};
|
|
2580
|
-
var
|
|
2581
|
-
const
|
|
2582
|
-
if (
|
|
2583
|
-
return
|
|
2418
|
+
var getActiveRuntimeTrade = async (params) => {
|
|
2419
|
+
const { userName, symbol, accountId, deploymentId } = params;
|
|
2420
|
+
if (!userName) {
|
|
2421
|
+
return null;
|
|
2584
2422
|
}
|
|
2585
|
-
|
|
2586
|
-
|
|
2587
|
-
|
|
2588
|
-
|
|
2589
|
-
|
|
2590
|
-
if (!
|
|
2423
|
+
const activeRef = await (0, import_redis2.getData)(
|
|
2424
|
+
import_redis2.redisKeys.runtimeActiveTrade(userName, symbol, deploymentId ?? accountId),
|
|
2425
|
+
null
|
|
2426
|
+
);
|
|
2427
|
+
const orderId = String(activeRef?.orderId || "").trim();
|
|
2428
|
+
if (!orderId) {
|
|
2591
2429
|
return null;
|
|
2592
2430
|
}
|
|
2593
|
-
|
|
2594
|
-
|
|
2595
|
-
|
|
2596
|
-
|
|
2597
|
-
|
|
2598
|
-
|
|
2599
|
-
|
|
2600
|
-
|
|
2601
|
-
|
|
2602
|
-
};
|
|
2603
|
-
var buildMissingSpreadContext = () => ({
|
|
2604
|
-
source: "payload.additionalIndicators.baseContext.relative.execution.venueSpread",
|
|
2605
|
-
indicatorKey: "payload.additionalIndicators.baseContext.relative.execution.venueSpread",
|
|
2606
|
-
available: false,
|
|
2607
|
-
value: null,
|
|
2608
|
-
zScore: null,
|
|
2609
|
-
bps: null,
|
|
2610
|
-
absBps: null,
|
|
2611
|
-
bias: null,
|
|
2612
|
-
severity: null
|
|
2613
|
-
});
|
|
2614
|
-
var buildSpreadContextFromSignal = (signal) => {
|
|
2615
|
-
const baseContext = toRecord(signal.additionalIndicators?.baseContext);
|
|
2616
|
-
const relative = toRecord(baseContext?.relative);
|
|
2617
|
-
const execution = toRecord(relative?.execution);
|
|
2618
|
-
const spread = toFiniteNumber(execution?.venueSpread);
|
|
2619
|
-
const zScore = toFiniteNumber(execution?.venueSpreadZScore);
|
|
2620
|
-
if (spread == null) {
|
|
2621
|
-
return buildMissingSpreadContext();
|
|
2431
|
+
const existing = await (0, import_redis2.getData)(
|
|
2432
|
+
import_redis2.redisKeys.runtimeTrade(userName, orderId),
|
|
2433
|
+
null
|
|
2434
|
+
);
|
|
2435
|
+
if (!existing || existing.status !== "active") {
|
|
2436
|
+
await (0, import_redis2.delKey)(
|
|
2437
|
+
import_redis2.redisKeys.runtimeActiveTrade(userName, symbol, deploymentId ?? accountId)
|
|
2438
|
+
);
|
|
2439
|
+
return null;
|
|
2622
2440
|
}
|
|
2623
|
-
|
|
2624
|
-
const bps = roundTo(value * 1e4, 2);
|
|
2625
|
-
const absBps = Math.abs(bps);
|
|
2626
|
-
const bias = absBps < 1 ? "flat" : bps > 0 ? "coinbase_premium" : "binance_premium";
|
|
2627
|
-
const severity = absBps >= 20 ? "wide" : absBps >= 5 ? "elevated" : "normal";
|
|
2628
|
-
return {
|
|
2629
|
-
source: "payload.additionalIndicators.baseContext.relative.execution.venueSpread",
|
|
2630
|
-
indicatorKey: "payload.additionalIndicators.baseContext.relative.execution.venueSpread",
|
|
2631
|
-
available: true,
|
|
2632
|
-
value,
|
|
2633
|
-
zScore,
|
|
2634
|
-
bps,
|
|
2635
|
-
absBps,
|
|
2636
|
-
bias,
|
|
2637
|
-
severity
|
|
2638
|
-
};
|
|
2441
|
+
return existing;
|
|
2639
2442
|
};
|
|
2640
|
-
var
|
|
2641
|
-
const
|
|
2642
|
-
|
|
2643
|
-
|
|
2644
|
-
|
|
2645
|
-
|
|
2646
|
-
|
|
2647
|
-
|
|
2648
|
-
|
|
2649
|
-
|
|
2650
|
-
|
|
2651
|
-
|
|
2652
|
-
|
|
2653
|
-
|
|
2654
|
-
|
|
2655
|
-
signedVolumeZScore: null
|
|
2656
|
-
};
|
|
2443
|
+
var markRuntimeTradeClosed = async (params) => {
|
|
2444
|
+
const {
|
|
2445
|
+
userName,
|
|
2446
|
+
expectedOrderId,
|
|
2447
|
+
symbol,
|
|
2448
|
+
strategy,
|
|
2449
|
+
exitPrice,
|
|
2450
|
+
exitTimestamp,
|
|
2451
|
+
closedPnl,
|
|
2452
|
+
exitType,
|
|
2453
|
+
accountId,
|
|
2454
|
+
deploymentId
|
|
2455
|
+
} = params;
|
|
2456
|
+
if (!userName) {
|
|
2457
|
+
return null;
|
|
2657
2458
|
}
|
|
2658
|
-
|
|
2659
|
-
|
|
2660
|
-
|
|
2661
|
-
|
|
2662
|
-
|
|
2663
|
-
|
|
2664
|
-
|
|
2665
|
-
|
|
2666
|
-
|
|
2667
|
-
|
|
2668
|
-
};
|
|
2669
|
-
var buildTradeFlowContextFromSignal = (signal) => {
|
|
2670
|
-
const baseContext = toRecord(signal.additionalIndicators?.baseContext);
|
|
2671
|
-
const participation = toRecord(baseContext?.participation);
|
|
2672
|
-
const tradeFlow = toRecord(participation?.tradeFlow);
|
|
2673
|
-
if (!tradeFlow) {
|
|
2674
|
-
return {
|
|
2675
|
-
source: null,
|
|
2676
|
-
available: false,
|
|
2677
|
-
interval: null,
|
|
2678
|
-
stale: null,
|
|
2679
|
-
trades: null,
|
|
2680
|
-
buyPressurePct: null,
|
|
2681
|
-
netBaseDelta: null,
|
|
2682
|
-
netQuoteDelta: null
|
|
2683
|
-
};
|
|
2459
|
+
const existing = expectedOrderId ? await (0, import_redis2.getDataStrict)(
|
|
2460
|
+
import_redis2.redisKeys.runtimeTrade(userName, expectedOrderId)
|
|
2461
|
+
) : await getActiveRuntimeTrade({
|
|
2462
|
+
userName,
|
|
2463
|
+
symbol,
|
|
2464
|
+
accountId,
|
|
2465
|
+
deploymentId
|
|
2466
|
+
});
|
|
2467
|
+
if (!existing || expectedOrderId && existing.orderId !== expectedOrderId) {
|
|
2468
|
+
return null;
|
|
2684
2469
|
}
|
|
2685
|
-
|
|
2686
|
-
|
|
2687
|
-
|
|
2688
|
-
|
|
2689
|
-
|
|
2690
|
-
|
|
2691
|
-
|
|
2692
|
-
|
|
2693
|
-
|
|
2470
|
+
const orderId = existing.orderId;
|
|
2471
|
+
if (strategy && existing.strategy !== strategy) {
|
|
2472
|
+
return null;
|
|
2473
|
+
}
|
|
2474
|
+
const resolvedExitPrice = typeof exitPrice === "number" && Number.isFinite(exitPrice) ? exitPrice : existing.currentPrice ?? existing.entryPrice;
|
|
2475
|
+
const resolvedClosedPnl = typeof closedPnl === "number" && Number.isFinite(closedPnl) ? closedPnl : typeof resolvedExitPrice === "number" && Number.isFinite(resolvedExitPrice) ? calculateClosedPnl({
|
|
2476
|
+
direction: existing.direction,
|
|
2477
|
+
entryPrice: existing.entryPrice,
|
|
2478
|
+
exitPrice: resolvedExitPrice,
|
|
2479
|
+
qty: existing.qty
|
|
2480
|
+
}) : existing.closedPnl ?? existing.currentPnl ?? null;
|
|
2481
|
+
const next = expectedOrderId && existing.status === "closed" ? existing : {
|
|
2482
|
+
...existing,
|
|
2483
|
+
status: "closed",
|
|
2484
|
+
currentPrice: resolvedExitPrice,
|
|
2485
|
+
currentPnl: resolvedClosedPnl,
|
|
2486
|
+
closedPnl: resolvedClosedPnl,
|
|
2487
|
+
exitPrice: resolvedExitPrice,
|
|
2488
|
+
exitTimestamp: typeof exitTimestamp === "number" && Number.isFinite(exitTimestamp) ? exitTimestamp : now(),
|
|
2489
|
+
exitType: exitType ?? existing.exitType ?? null,
|
|
2490
|
+
lastSyncedAt: now()
|
|
2694
2491
|
};
|
|
2695
|
-
|
|
2696
|
-
|
|
2697
|
-
|
|
2698
|
-
|
|
2699
|
-
|
|
2700
|
-
|
|
2701
|
-
|
|
2702
|
-
|
|
2703
|
-
|
|
2704
|
-
|
|
2705
|
-
|
|
2706
|
-
|
|
2707
|
-
|
|
2708
|
-
|
|
2709
|
-
|
|
2710
|
-
|
|
2711
|
-
|
|
2712
|
-
|
|
2713
|
-
|
|
2714
|
-
|
|
2492
|
+
const dayKey = (0, import_time3.getRuntimeStorageDayKey)(existing.entryTimestamp);
|
|
2493
|
+
const closeDayKey = (0, import_time3.getRuntimeStorageDayKey)(next.exitTimestamp);
|
|
2494
|
+
try {
|
|
2495
|
+
await Promise.all([
|
|
2496
|
+
(expectedOrderId ? import_redis2.setDataStrict : import_redis2.setData)(
|
|
2497
|
+
import_redis2.redisKeys.runtimeTrade(userName, orderId),
|
|
2498
|
+
next,
|
|
2499
|
+
{
|
|
2500
|
+
expire: import_constants3.TTL_1M
|
|
2501
|
+
}
|
|
2502
|
+
),
|
|
2503
|
+
(expectedOrderId ? import_redis2.setHashJsonFieldStrict : import_redis2.setHashJsonField)(
|
|
2504
|
+
import_redis2.redisKeys.runtimeTradeBucket(userName, dayKey),
|
|
2505
|
+
orderId,
|
|
2506
|
+
next,
|
|
2507
|
+
{ expire: import_constants3.TTL_1M }
|
|
2508
|
+
),
|
|
2509
|
+
(expectedOrderId ? import_redis2.setHashJsonFieldStrict : import_redis2.setHashJsonField)(
|
|
2510
|
+
import_redis2.redisKeys.runtimeClosedTradeBucket(userName, closeDayKey),
|
|
2511
|
+
orderId,
|
|
2512
|
+
next,
|
|
2513
|
+
{ expire: import_constants3.TTL_1M }
|
|
2514
|
+
),
|
|
2515
|
+
expectedOrderId ? (0, import_redis2.deleteRuntimeTradeReference)(
|
|
2516
|
+
import_redis2.redisKeys.runtimeActiveTrade(
|
|
2517
|
+
userName,
|
|
2518
|
+
symbol,
|
|
2519
|
+
deploymentId ?? accountId
|
|
2520
|
+
),
|
|
2521
|
+
expectedOrderId
|
|
2522
|
+
) : (0, import_redis2.delKey)(
|
|
2523
|
+
import_redis2.redisKeys.runtimeActiveTrade(
|
|
2524
|
+
userName,
|
|
2525
|
+
symbol,
|
|
2526
|
+
deploymentId ?? accountId
|
|
2527
|
+
)
|
|
2528
|
+
)
|
|
2529
|
+
]);
|
|
2530
|
+
} catch (error) {
|
|
2531
|
+
if (expectedOrderId) throw error;
|
|
2532
|
+
import_logger5.logger.error(
|
|
2533
|
+
"runtime trade close journal failed: %s %s",
|
|
2534
|
+
symbol,
|
|
2535
|
+
error?.message || String(error)
|
|
2536
|
+
);
|
|
2715
2537
|
}
|
|
2716
|
-
return
|
|
2717
|
-
source: String(breadth.source ?? ""),
|
|
2718
|
-
available: true,
|
|
2719
|
-
universe: String(breadth.universe ?? ""),
|
|
2720
|
-
interval: String(breadth.interval ?? ""),
|
|
2721
|
-
stale: typeof breadth.stale === "boolean" ? breadth.stale : null,
|
|
2722
|
-
symbolsCount: toFiniteNumber(breadth.symbolsCount),
|
|
2723
|
-
advanceDeclineRatio: toFiniteNumber(breadth.advanceDeclineRatio),
|
|
2724
|
-
pctAboveMa20: toFiniteNumber(breadth.pctAboveMa20),
|
|
2725
|
-
pctAboveMa50: toFiniteNumber(breadth.pctAboveMa50),
|
|
2726
|
-
equalWeightedReturn: toFiniteNumber(breadth.equalWeightedReturn),
|
|
2727
|
-
volumeWeightedReturn: toFiniteNumber(breadth.volumeWeightedReturn),
|
|
2728
|
-
dispersion: toFiniteNumber(breadth.dispersion)
|
|
2729
|
-
};
|
|
2538
|
+
return next;
|
|
2730
2539
|
};
|
|
2731
|
-
|
|
2732
|
-
|
|
2733
|
-
|
|
2734
|
-
|
|
2735
|
-
|
|
2736
|
-
|
|
2737
|
-
|
|
2540
|
+
|
|
2541
|
+
// src/strategy/portfolioRuntime.ts
|
|
2542
|
+
var coordinators = /* @__PURE__ */ new WeakMap();
|
|
2543
|
+
async function observeRuntimePortfolio(connector, binding, asOf, reservations, observedNow = Date.now) {
|
|
2544
|
+
if (!connector.getOpenPositionPnl || !connector.getEntryExecutions)
|
|
2545
|
+
throw new Error(
|
|
2546
|
+
"Portfolio policies require position marks and entry executions"
|
|
2547
|
+
);
|
|
2548
|
+
const orders = await Promise.all(
|
|
2549
|
+
reservations.map(async (r) => {
|
|
2550
|
+
if (r.submitted && connector.getEntryOrderObservation) {
|
|
2551
|
+
const fact2 = await connector.getEntryOrderObservation({
|
|
2552
|
+
symbol: r.symbol,
|
|
2553
|
+
orderId: r.id
|
|
2554
|
+
});
|
|
2555
|
+
if (fact2) return fact2;
|
|
2556
|
+
}
|
|
2557
|
+
const executions = r.submitted ? await connector.getEntryExecutions({
|
|
2558
|
+
symbol: r.symbol,
|
|
2559
|
+
startTime: r.timestamp,
|
|
2560
|
+
endTime: asOf
|
|
2561
|
+
}) : [];
|
|
2562
|
+
const filledQty = executions.filter(
|
|
2563
|
+
(e) => (e.orderLinkId === r.id || e.orderId === r.id) && e.direction === r.direction
|
|
2564
|
+
).reduce((sum, e) => sum + e.qty, 0);
|
|
2738
2565
|
return {
|
|
2739
|
-
|
|
2740
|
-
|
|
2741
|
-
|
|
2742
|
-
|
|
2743
|
-
|
|
2744
|
-
|
|
2745
|
-
|
|
2746
|
-
|
|
2747
|
-
|
|
2748
|
-
|
|
2749
|
-
|
|
2750
|
-
|
|
2566
|
+
id: r.id,
|
|
2567
|
+
status: filledQty >= r.qty ? "filled" : filledQty > 0 ? "partial" : "pending",
|
|
2568
|
+
filledQty,
|
|
2569
|
+
positionId: r.id
|
|
2570
|
+
};
|
|
2571
|
+
})
|
|
2572
|
+
);
|
|
2573
|
+
const snapshots2 = await connector.getOpenPositionPnl();
|
|
2574
|
+
const positions = await Promise.all(
|
|
2575
|
+
snapshots2.map(async (p) => {
|
|
2576
|
+
const reserved = reservations.find(
|
|
2577
|
+
(r) => r.symbol === p.symbol && r.direction === p.direction && orders.some(
|
|
2578
|
+
(o) => o.id === r.id && o.filledQty > 0 && p.qty <= o.filledQty * (1 + 1e-8)
|
|
2579
|
+
)
|
|
2580
|
+
);
|
|
2581
|
+
const trade = await getActiveRuntimeTrade({
|
|
2582
|
+
...binding,
|
|
2583
|
+
symbol: p.symbol
|
|
2584
|
+
});
|
|
2585
|
+
const owner = trade && trade.direction === p.direction && p.qty <= trade.qty * (1 + 1e-8) && Math.abs(p.price - (trade.actualEntryPrice ?? trade.entryPrice)) <= p.price * 1e-8 ? trade : null;
|
|
2586
|
+
return {
|
|
2587
|
+
id: reserved?.id ?? owner?.orderId ?? `unknown:${p.symbol}:${p.direction}`,
|
|
2588
|
+
strategy: reserved?.strategy ?? owner?.strategy ?? null,
|
|
2589
|
+
symbol: p.symbol,
|
|
2590
|
+
direction: p.direction,
|
|
2591
|
+
qty: p.qty,
|
|
2592
|
+
entryPrice: p.price,
|
|
2593
|
+
markPrice: p.currentPrice,
|
|
2594
|
+
markedAt: p.observedAt ?? null,
|
|
2595
|
+
stopLossPrice: p.slPrice ?? p.stopLossPrice ?? null
|
|
2751
2596
|
};
|
|
2597
|
+
})
|
|
2598
|
+
);
|
|
2599
|
+
return { asOf: Math.max(asOf, observedNow()), positions, orders };
|
|
2600
|
+
}
|
|
2601
|
+
function persistentStore(key) {
|
|
2602
|
+
const raw = /* @__PURE__ */ new WeakMap();
|
|
2603
|
+
return {
|
|
2604
|
+
async read() {
|
|
2605
|
+
const value = await import_redis3.portfolioStateStorage.read(key);
|
|
2606
|
+
if (value === null) return null;
|
|
2607
|
+
const state = JSON.parse(value);
|
|
2608
|
+
(0, import_portfolio2.assertPortfolioState)(state);
|
|
2609
|
+
raw.set(state, value);
|
|
2610
|
+
return state;
|
|
2611
|
+
},
|
|
2612
|
+
async compare(previous, next) {
|
|
2613
|
+
const expected = previous === null ? null : raw.get(previous);
|
|
2614
|
+
if (expected === void 0)
|
|
2615
|
+
throw new Error("Portfolio CAS requires a storage snapshot");
|
|
2616
|
+
return import_redis3.portfolioStateStorage.compare(key, expected, JSON.stringify(next));
|
|
2752
2617
|
}
|
|
2753
|
-
return {
|
|
2754
|
-
source: String(breadth.source ?? ""),
|
|
2755
|
-
available: true,
|
|
2756
|
-
universe: String(breadth.universe ?? ""),
|
|
2757
|
-
interval: String(breadth.interval ?? ""),
|
|
2758
|
-
stale: typeof breadth.stale === "boolean" ? breadth.stale : null,
|
|
2759
|
-
symbolsCount: toFiniteNumber(breadth.symbolsCount),
|
|
2760
|
-
advanceDeclineRatio: toFiniteNumber(breadth.advanceDeclineRatio),
|
|
2761
|
-
pctAboveMa20: toFiniteNumber(breadth.pctAboveMa20),
|
|
2762
|
-
pctAboveMa50: toFiniteNumber(breadth.pctAboveMa50),
|
|
2763
|
-
equalWeightedReturn: toFiniteNumber(breadth.equalWeightedReturn),
|
|
2764
|
-
volumeWeightedReturn: toFiniteNumber(breadth.volumeWeightedReturn),
|
|
2765
|
-
dispersion: toFiniteNumber(breadth.dispersion)
|
|
2766
|
-
};
|
|
2767
2618
|
};
|
|
2768
|
-
|
|
2769
|
-
|
|
2770
|
-
|
|
2771
|
-
|
|
2772
|
-
|
|
2773
|
-
|
|
2619
|
+
}
|
|
2620
|
+
function getPortfolioRuntime({
|
|
2621
|
+
value,
|
|
2622
|
+
connectorName,
|
|
2623
|
+
strategy,
|
|
2624
|
+
connector,
|
|
2625
|
+
env,
|
|
2626
|
+
userName,
|
|
2627
|
+
accountId,
|
|
2628
|
+
deploymentId
|
|
2629
|
+
}) {
|
|
2630
|
+
const policy = (0, import_portfolio2.parsePortfolioPolicy)(value);
|
|
2631
|
+
if (!policy) return void 0;
|
|
2632
|
+
const replay = (env === "BACKTEST" || env === "PARITY") && Reflect.get(connector, "__tradejsReplayConnector") === true;
|
|
2633
|
+
if ((env === "BACKTEST" || env === "PARITY") && (!replay || !connector.getPortfolioObservation))
|
|
2634
|
+
throw new Error(
|
|
2635
|
+
"Portfolio policies require shared portfolio replay; isolated symbol backtests are unsupported"
|
|
2636
|
+
);
|
|
2637
|
+
if (!replay && (!userName || !accountId || !deploymentId || !connector.getOpenPositionPnl || !connector.getEntryExecutions))
|
|
2638
|
+
throw new Error(
|
|
2639
|
+
"Portfolio runtime requires exact user/account/deployment binding, marks and executions"
|
|
2640
|
+
);
|
|
2641
|
+
const binding = {
|
|
2642
|
+
userName,
|
|
2643
|
+
accountId,
|
|
2644
|
+
deploymentId
|
|
2774
2645
|
};
|
|
2775
|
-
|
|
2776
|
-
|
|
2777
|
-
const
|
|
2778
|
-
const
|
|
2779
|
-
|
|
2780
|
-
if (!
|
|
2781
|
-
|
|
2782
|
-
|
|
2783
|
-
|
|
2784
|
-
|
|
2785
|
-
|
|
2786
|
-
|
|
2787
|
-
|
|
2788
|
-
|
|
2789
|
-
|
|
2790
|
-
|
|
2791
|
-
|
|
2792
|
-
|
|
2793
|
-
|
|
2646
|
+
const scope = policy.scope === "strategy" ? strategy : policy.scope === "account" ? "account" : [...policy.scope.strategies].sort();
|
|
2647
|
+
const key = (0, import_node_crypto2.createHash)("sha256").update(JSON.stringify([binding, connectorName, connector.universe, scope])).digest("hex");
|
|
2648
|
+
const fingerprint3 = (0, import_portfolio2.portfolioFingerprint)(policy, strategy);
|
|
2649
|
+
const cacheKey = key;
|
|
2650
|
+
let registry = coordinators.get(connector);
|
|
2651
|
+
if (!registry) {
|
|
2652
|
+
registry = /* @__PURE__ */ new Map();
|
|
2653
|
+
coordinators.set(connector, registry);
|
|
2654
|
+
}
|
|
2655
|
+
const existing = registry.get(cacheKey);
|
|
2656
|
+
if (existing) {
|
|
2657
|
+
if (existing.fingerprint !== fingerprint3)
|
|
2658
|
+
throw new Error("Conflicting portfolio configuration for the same scope");
|
|
2659
|
+
return existing.coordinator;
|
|
2660
|
+
}
|
|
2661
|
+
const coordinator = createPortfolioCoordinator({
|
|
2662
|
+
policy,
|
|
2663
|
+
strategy,
|
|
2664
|
+
store: replay ? createMemoryPortfolioStore() : persistentStore(key),
|
|
2665
|
+
execution: {
|
|
2666
|
+
async confirmClosed(intent, asOf) {
|
|
2667
|
+
if (replay) return;
|
|
2668
|
+
const position = intent.position;
|
|
2669
|
+
await markRuntimeTradeClosed({
|
|
2670
|
+
...binding,
|
|
2671
|
+
strategy: position.strategy,
|
|
2672
|
+
expectedOrderId: position.id,
|
|
2673
|
+
symbol: position.symbol,
|
|
2674
|
+
exitPrice: position.markPrice,
|
|
2675
|
+
exitTimestamp: asOf,
|
|
2676
|
+
exitType: "exit"
|
|
2677
|
+
});
|
|
2678
|
+
},
|
|
2679
|
+
observe: (asOf, reservations) => replay ? connector.getPortfolioObservation(asOf, reservations) : observeRuntimePortfolio(connector, binding, asOf, reservations),
|
|
2680
|
+
async close(position, intent, asOf) {
|
|
2681
|
+
if (!replay) {
|
|
2682
|
+
const trade = await getActiveRuntimeTrade({
|
|
2683
|
+
...binding,
|
|
2684
|
+
symbol: position.symbol
|
|
2685
|
+
});
|
|
2686
|
+
if (!trade || trade.orderId !== position.id)
|
|
2687
|
+
throw new Error("Portfolio close ownership changed");
|
|
2688
|
+
}
|
|
2689
|
+
await connector.closePosition({
|
|
2690
|
+
symbol: position.symbol,
|
|
2691
|
+
direction: position.direction,
|
|
2692
|
+
price: position.markPrice,
|
|
2693
|
+
timestamp: asOf,
|
|
2694
|
+
signal: {
|
|
2695
|
+
signalId: `${position.id}:${intent.reason}`,
|
|
2696
|
+
strategy: position.strategy,
|
|
2697
|
+
symbol: position.symbol,
|
|
2698
|
+
direction: position.direction,
|
|
2699
|
+
interval: "1",
|
|
2700
|
+
timestamp: asOf,
|
|
2701
|
+
prices: {
|
|
2702
|
+
riskRatio: 0,
|
|
2703
|
+
currentPrice: position.markPrice,
|
|
2704
|
+
takeProfitPrice: position.markPrice,
|
|
2705
|
+
stopLossPrice: position.markPrice
|
|
2706
|
+
},
|
|
2707
|
+
figures: {},
|
|
2708
|
+
indicators: {},
|
|
2709
|
+
additionalIndicators: { portfolioExitReason: intent.reason }
|
|
2710
|
+
}
|
|
2711
|
+
});
|
|
2712
|
+
}
|
|
2713
|
+
}
|
|
2714
|
+
});
|
|
2715
|
+
registry.set(cacheKey, { fingerprint: fingerprint3, coordinator });
|
|
2716
|
+
return coordinator;
|
|
2717
|
+
}
|
|
2718
|
+
|
|
2719
|
+
// src/strategyHelpers/tickerProfiles.ts
|
|
2720
|
+
var import_tickerProfiles = require("@tradejs/infra/tickerProfiles");
|
|
2721
|
+
var import_tickerProfiles2 = require("@tradejs/core/tickerProfiles");
|
|
2722
|
+
|
|
2723
|
+
// src/strategy/manifests.ts
|
|
2724
|
+
var import_indicators = require("@tradejs/core/indicators");
|
|
2725
|
+
var SHARED_STRATEGY_REGISTRY_KEY = "__tradejsNodeSharedStrategyRegistryV1__";
|
|
2726
|
+
var sharedRegistryScope = globalThis;
|
|
2727
|
+
var sharedStrategyRegistry = sharedRegistryScope[SHARED_STRATEGY_REGISTRY_KEY] ?? (sharedRegistryScope[SHARED_STRATEGY_REGISTRY_KEY] = {
|
|
2728
|
+
registryStateByProjectRoot: /* @__PURE__ */ new Map()
|
|
2729
|
+
});
|
|
2730
|
+
var createStrategyRegistryState = () => ({
|
|
2731
|
+
strategyCreators: /* @__PURE__ */ new Map(),
|
|
2732
|
+
strategyManifestsMap: /* @__PURE__ */ new Map(),
|
|
2733
|
+
strategyEntriesMap: /* @__PURE__ */ new Map(),
|
|
2734
|
+
strategySourcesMap: /* @__PURE__ */ new Map(),
|
|
2735
|
+
strategyModuleResolutions: /* @__PURE__ */ new Map(),
|
|
2736
|
+
pluginsLoadPromise: null
|
|
2737
|
+
});
|
|
2738
|
+
var registryStateByProjectRoot2 = sharedStrategyRegistry.registryStateByProjectRoot;
|
|
2739
|
+
var getStrategyRegistryState = (cwd = getTradejsProjectCwd()) => {
|
|
2740
|
+
const projectRoot = getTradejsProjectCwd(cwd);
|
|
2741
|
+
let state = registryStateByProjectRoot2.get(projectRoot);
|
|
2742
|
+
if (!state) {
|
|
2743
|
+
state = createStrategyRegistryState();
|
|
2744
|
+
registryStateByProjectRoot2.set(projectRoot, state);
|
|
2794
2745
|
}
|
|
2795
2746
|
return {
|
|
2796
|
-
|
|
2797
|
-
|
|
2798
|
-
ratioReturn1h: toFiniteNumber(targetVsBtc.ratioReturn1h),
|
|
2799
|
-
ratioReturn4h: toFiniteNumber(targetVsBtc.ratioReturn4h),
|
|
2800
|
-
ratioReturn24h: toFiniteNumber(targetVsBtc.ratioReturn24h),
|
|
2801
|
-
alphaVsBtc1h: toFiniteNumber(targetVsBtc.alphaVsBtc1h),
|
|
2802
|
-
alphaVsBtc4h: toFiniteNumber(targetVsBtc.alphaVsBtc4h),
|
|
2803
|
-
alphaVsBtc24h: toFiniteNumber(targetVsBtc.alphaVsBtc24h),
|
|
2804
|
-
betaToBtc20: toFiniteNumber(targetVsBtc.betaToBtc20),
|
|
2805
|
-
correlationToBtc20: toFiniteNumber(targetVsBtc.correlationToBtc20),
|
|
2806
|
-
ratioTrend: typeof targetVsBtc.ratioTrend === "string" ? targetVsBtc.ratioTrend : null
|
|
2747
|
+
projectRoot,
|
|
2748
|
+
state
|
|
2807
2749
|
};
|
|
2808
2750
|
};
|
|
2809
|
-
var
|
|
2810
|
-
|
|
2811
|
-
|
|
2812
|
-
|
|
2813
|
-
|
|
2814
|
-
return {
|
|
2815
|
-
source: null,
|
|
2816
|
-
available: false,
|
|
2817
|
-
universe: null,
|
|
2818
|
-
interval: null,
|
|
2819
|
-
stale: null,
|
|
2820
|
-
regime: null,
|
|
2821
|
-
btcReturn24h: null,
|
|
2822
|
-
altBasketReturn24h: null,
|
|
2823
|
-
btcVsAltReturn24h: null,
|
|
2824
|
-
btcTurnoverShare24h: null,
|
|
2825
|
-
btcTurnoverShareChange24h: null,
|
|
2826
|
-
altVolToBtcVol24h: null,
|
|
2827
|
-
altDispersion24h: null
|
|
2828
|
-
};
|
|
2829
|
-
}
|
|
2751
|
+
var toUniqueModules2 = (modules = []) => [
|
|
2752
|
+
...new Set(modules.map((moduleName) => moduleName.trim()).filter(Boolean))
|
|
2753
|
+
];
|
|
2754
|
+
var getConfiguredPluginModuleNames = async (cwd = getTradejsProjectCwd()) => {
|
|
2755
|
+
const config = await loadTradejsConfig(cwd);
|
|
2830
2756
|
return {
|
|
2831
|
-
|
|
2832
|
-
|
|
2833
|
-
universe: String(btcAltRegime.universe ?? ""),
|
|
2834
|
-
interval: String(btcAltRegime.interval ?? ""),
|
|
2835
|
-
stale: typeof btcAltRegime.stale === "boolean" ? btcAltRegime.stale : null,
|
|
2836
|
-
regime: typeof btcAltRegime.regime === "string" ? btcAltRegime.regime : null,
|
|
2837
|
-
btcReturn24h: toFiniteNumber(btcAltRegime.btcReturn24h),
|
|
2838
|
-
altBasketReturn24h: toFiniteNumber(btcAltRegime.altBasketReturn24h),
|
|
2839
|
-
btcVsAltReturn24h: toFiniteNumber(btcAltRegime.btcVsAltReturn24h),
|
|
2840
|
-
btcTurnoverShare24h: toFiniteNumber(btcAltRegime.btcTurnoverShare24h),
|
|
2841
|
-
btcTurnoverShareChange24h: toFiniteNumber(
|
|
2842
|
-
btcAltRegime.btcTurnoverShareChange24h
|
|
2843
|
-
),
|
|
2844
|
-
altVolToBtcVol24h: toFiniteNumber(btcAltRegime.altVolToBtcVol24h),
|
|
2845
|
-
altDispersion24h: toFiniteNumber(btcAltRegime.altDispersion24h)
|
|
2757
|
+
strategyModules: toUniqueModules2(config.strategies),
|
|
2758
|
+
indicatorModules: toUniqueModules2(config.indicators)
|
|
2846
2759
|
};
|
|
2847
2760
|
};
|
|
2848
|
-
var
|
|
2849
|
-
|
|
2850
|
-
|
|
2851
|
-
const cmcGlobal = toRecord(relative?.cmcGlobal);
|
|
2852
|
-
if (!cmcGlobal) {
|
|
2853
|
-
return {
|
|
2854
|
-
source: null,
|
|
2855
|
-
available: false,
|
|
2856
|
-
interval: null,
|
|
2857
|
-
asOfTs: null,
|
|
2858
|
-
stale: null,
|
|
2859
|
-
totalMarketCapUsd: null,
|
|
2860
|
-
totalVolumeUsd: null,
|
|
2861
|
-
totalVolumeReportedUsd: null,
|
|
2862
|
-
altMarketCapUsd: null,
|
|
2863
|
-
altVolumeUsd: null,
|
|
2864
|
-
altVolumeReportedUsd: null,
|
|
2865
|
-
btcDominancePct: null,
|
|
2866
|
-
ethDominancePct: null,
|
|
2867
|
-
btcDominanceChange24hPct: null,
|
|
2868
|
-
ethDominanceChange24hPct: null,
|
|
2869
|
-
altMarketCapChange24hPct: null,
|
|
2870
|
-
altVolumeChange24hPct: null,
|
|
2871
|
-
activeCryptocurrencies: null,
|
|
2872
|
-
activeExchanges: null,
|
|
2873
|
-
activeMarketPairs: null,
|
|
2874
|
-
altLiquidityRegime: null
|
|
2875
|
-
};
|
|
2761
|
+
var extractModuleEntries = (moduleExport, key) => {
|
|
2762
|
+
if (!moduleExport || typeof moduleExport !== "object") {
|
|
2763
|
+
return null;
|
|
2876
2764
|
}
|
|
2877
|
-
|
|
2878
|
-
|
|
2879
|
-
|
|
2880
|
-
|
|
2881
|
-
|
|
2882
|
-
|
|
2883
|
-
|
|
2884
|
-
|
|
2885
|
-
|
|
2886
|
-
|
|
2887
|
-
|
|
2888
|
-
|
|
2889
|
-
|
|
2890
|
-
|
|
2891
|
-
|
|
2892
|
-
|
|
2893
|
-
|
|
2894
|
-
|
|
2895
|
-
|
|
2896
|
-
|
|
2897
|
-
|
|
2898
|
-
|
|
2765
|
+
const candidate = moduleExport;
|
|
2766
|
+
if (Array.isArray(candidate[key])) {
|
|
2767
|
+
return candidate[key];
|
|
2768
|
+
}
|
|
2769
|
+
const defaultExport = candidate.default;
|
|
2770
|
+
if (defaultExport && Array.isArray(defaultExport[key])) {
|
|
2771
|
+
return defaultExport[key];
|
|
2772
|
+
}
|
|
2773
|
+
return null;
|
|
2774
|
+
};
|
|
2775
|
+
var extractStrategyPluginDefinition = (moduleExport) => {
|
|
2776
|
+
const strategyEntries = extractModuleEntries(
|
|
2777
|
+
moduleExport,
|
|
2778
|
+
"strategyEntries"
|
|
2779
|
+
);
|
|
2780
|
+
return strategyEntries ? { strategyEntries } : null;
|
|
2781
|
+
};
|
|
2782
|
+
var extractIndicatorPluginDefinition = (moduleExport) => {
|
|
2783
|
+
const indicatorEntries = extractModuleEntries(
|
|
2784
|
+
moduleExport,
|
|
2785
|
+
"indicatorEntries"
|
|
2786
|
+
);
|
|
2787
|
+
return indicatorEntries ? { indicatorEntries } : null;
|
|
2788
|
+
};
|
|
2789
|
+
var validateStrategyEntries = (moduleName, entries, state) => {
|
|
2790
|
+
const issues = [];
|
|
2791
|
+
const names = /* @__PURE__ */ new Set();
|
|
2792
|
+
entries.forEach((entry, index) => {
|
|
2793
|
+
const entryPath = `${moduleName}.strategyEntries[${index}]`;
|
|
2794
|
+
const strategyName = entry?.manifest?.name;
|
|
2795
|
+
if (typeof strategyName !== "string" || !strategyName.trim()) {
|
|
2796
|
+
issues.push(`${entryPath}: manifest.name is required`);
|
|
2797
|
+
} else if (names.has(strategyName) || state?.strategyEntriesMap.has(strategyName)) {
|
|
2798
|
+
issues.push(`${entryPath}: duplicate strategy ${strategyName}`);
|
|
2799
|
+
} else {
|
|
2800
|
+
names.add(strategyName);
|
|
2801
|
+
}
|
|
2802
|
+
if (!entry || typeof entry !== "object" || !entry.defaults || typeof entry.defaults !== "object" || Array.isArray(entry.defaults)) {
|
|
2803
|
+
issues.push(`${entryPath}: defaults must be an object`);
|
|
2804
|
+
}
|
|
2805
|
+
if (typeof entry?.parseConfig !== "function") {
|
|
2806
|
+
issues.push(`${entryPath}: parseConfig is required`);
|
|
2807
|
+
}
|
|
2808
|
+
if (typeof entry?.createCore !== "function") {
|
|
2809
|
+
issues.push(`${entryPath}: createCore is required`);
|
|
2810
|
+
}
|
|
2811
|
+
});
|
|
2812
|
+
return issues;
|
|
2813
|
+
};
|
|
2814
|
+
var registerEntries2 = (entries, source, state) => {
|
|
2815
|
+
for (const entry of entries) {
|
|
2816
|
+
const strategyName = entry.manifest.name;
|
|
2817
|
+
state.strategyManifestsMap.set(strategyName, entry.manifest);
|
|
2818
|
+
state.strategyEntriesMap.set(strategyName, entry);
|
|
2819
|
+
state.strategySourcesMap.set(strategyName, source);
|
|
2820
|
+
materializeStrategyCreator(strategyName, state);
|
|
2821
|
+
}
|
|
2822
|
+
};
|
|
2823
|
+
var materializeStrategyCreator = (strategyName, state) => {
|
|
2824
|
+
if (state.strategyCreators.has(strategyName) || !sharedStrategyRegistry.strategyRuntimeFactory) {
|
|
2825
|
+
return;
|
|
2826
|
+
}
|
|
2827
|
+
const entry = state.strategyEntriesMap.get(strategyName);
|
|
2828
|
+
if (!entry) return;
|
|
2829
|
+
state.strategyCreators.set(
|
|
2830
|
+
strategyName,
|
|
2831
|
+
sharedStrategyRegistry.strategyRuntimeFactory({
|
|
2832
|
+
strategyName,
|
|
2833
|
+
defaults: entry.defaults,
|
|
2834
|
+
createCore: entry.createCore,
|
|
2835
|
+
manifest: entry.manifest,
|
|
2836
|
+
detectorKey: entry.detectorKey,
|
|
2837
|
+
detectorNoSignalSkipReason: entry.detectorNoSignalSkipReason,
|
|
2838
|
+
resolveRegisteredManifest: (name) => state.strategyManifestsMap.get(name)
|
|
2839
|
+
})
|
|
2840
|
+
);
|
|
2841
|
+
};
|
|
2842
|
+
var setStrategyRuntimeFactory = (factory) => {
|
|
2843
|
+
sharedStrategyRegistry.strategyRuntimeFactory = factory;
|
|
2844
|
+
for (const state of registryStateByProjectRoot2.values()) {
|
|
2845
|
+
for (const strategyName of state.strategyEntriesMap.keys()) {
|
|
2846
|
+
materializeStrategyCreator(strategyName, state);
|
|
2847
|
+
}
|
|
2848
|
+
}
|
|
2849
|
+
};
|
|
2850
|
+
var importStrategyPluginModule = async (moduleName, cwd = getTradejsProjectCwd()) => {
|
|
2851
|
+
if (typeof importTradejsModule === "function") {
|
|
2852
|
+
return importTradejsModule(moduleName, cwd);
|
|
2853
|
+
}
|
|
2854
|
+
return import(
|
|
2855
|
+
/* webpackIgnore: true */
|
|
2856
|
+
moduleName
|
|
2857
|
+
);
|
|
2858
|
+
};
|
|
2859
|
+
var resolveStrategyModuleEntry = async ({
|
|
2860
|
+
strategyName,
|
|
2861
|
+
moduleName,
|
|
2862
|
+
cwd
|
|
2863
|
+
}) => {
|
|
2864
|
+
const { projectRoot, state } = getStrategyRegistryState(cwd);
|
|
2865
|
+
const source = moduleName.trim();
|
|
2866
|
+
const resolvedModuleName = resolvePluginModuleSpecifier(source, projectRoot);
|
|
2867
|
+
const cacheKey = `${resolvedModuleName}\0${strategyName}`;
|
|
2868
|
+
const cached = state.strategyModuleResolutions.get(cacheKey);
|
|
2869
|
+
if (cached) return cached;
|
|
2870
|
+
const resolution = (async () => {
|
|
2871
|
+
const moduleExport = await importStrategyPluginModule(
|
|
2872
|
+
resolvedModuleName,
|
|
2873
|
+
projectRoot
|
|
2874
|
+
);
|
|
2875
|
+
const pluginDefinition = extractStrategyPluginDefinition(moduleExport);
|
|
2876
|
+
if (!pluginDefinition) {
|
|
2877
|
+
throw new Error(`${source}: export { strategyEntries } is missing`);
|
|
2878
|
+
}
|
|
2879
|
+
const issues = validateStrategyEntries(
|
|
2880
|
+
source,
|
|
2881
|
+
pluginDefinition.strategyEntries
|
|
2882
|
+
);
|
|
2883
|
+
if (issues.length > 0) {
|
|
2884
|
+
throw new Error(
|
|
2885
|
+
["Invalid TradeJS strategy module:", ...issues].join("\n")
|
|
2886
|
+
);
|
|
2887
|
+
}
|
|
2888
|
+
const entry = pluginDefinition.strategyEntries.find(
|
|
2889
|
+
(candidate) => candidate.manifest.name === strategyName
|
|
2890
|
+
);
|
|
2891
|
+
if (!entry) {
|
|
2892
|
+
throw new Error(`${source}: strategy ${strategyName} is missing`);
|
|
2893
|
+
}
|
|
2894
|
+
const factory = sharedStrategyRegistry.strategyRuntimeFactory;
|
|
2895
|
+
if (!factory) {
|
|
2896
|
+
throw new Error("Strategy runtime factory is not configured");
|
|
2897
|
+
}
|
|
2898
|
+
const manifestsByName = new Map(
|
|
2899
|
+
pluginDefinition.strategyEntries.map(
|
|
2900
|
+
(candidate) => [candidate.manifest.name, candidate.manifest]
|
|
2901
|
+
)
|
|
2902
|
+
);
|
|
2903
|
+
return {
|
|
2904
|
+
entry,
|
|
2905
|
+
creator: factory({
|
|
2906
|
+
strategyName,
|
|
2907
|
+
defaults: entry.defaults,
|
|
2908
|
+
createCore: entry.createCore,
|
|
2909
|
+
manifest: entry.manifest,
|
|
2910
|
+
detectorKey: entry.detectorKey,
|
|
2911
|
+
detectorNoSignalSkipReason: entry.detectorNoSignalSkipReason,
|
|
2912
|
+
resolveRegisteredManifest: (name) => manifestsByName.get(name) ?? state.strategyManifestsMap.get(name)
|
|
2913
|
+
}),
|
|
2914
|
+
source
|
|
2915
|
+
};
|
|
2916
|
+
})();
|
|
2917
|
+
state.strategyModuleResolutions.set(cacheKey, resolution);
|
|
2918
|
+
try {
|
|
2919
|
+
return await resolution;
|
|
2920
|
+
} catch (error) {
|
|
2921
|
+
state.strategyModuleResolutions.delete(cacheKey);
|
|
2922
|
+
throw error;
|
|
2923
|
+
}
|
|
2924
|
+
};
|
|
2925
|
+
var ensureStrategyPluginsLoaded = async (cwd = getTradejsProjectCwd()) => {
|
|
2926
|
+
const { projectRoot, state } = getStrategyRegistryState(cwd);
|
|
2927
|
+
if (!state.pluginsLoadPromise) {
|
|
2928
|
+
(0, import_indicators.resetIndicatorRegistryCache)(projectRoot);
|
|
2929
|
+
state.pluginsLoadPromise = (async () => {
|
|
2930
|
+
const { strategyModules, indicatorModules } = await getConfiguredPluginModuleNames(projectRoot);
|
|
2931
|
+
const strategySet = new Set(strategyModules);
|
|
2932
|
+
const indicatorSet = new Set(indicatorModules);
|
|
2933
|
+
const pluginModuleNames = [
|
|
2934
|
+
.../* @__PURE__ */ new Set([...strategyModules, ...indicatorModules])
|
|
2935
|
+
];
|
|
2936
|
+
if (!pluginModuleNames.length) {
|
|
2937
|
+
return;
|
|
2938
|
+
}
|
|
2939
|
+
const issues = [];
|
|
2940
|
+
for (const moduleName of pluginModuleNames) {
|
|
2941
|
+
try {
|
|
2942
|
+
const resolvedModuleName = resolvePluginModuleSpecifier(
|
|
2943
|
+
moduleName,
|
|
2944
|
+
projectRoot
|
|
2945
|
+
);
|
|
2946
|
+
const moduleExport = await importStrategyPluginModule(
|
|
2947
|
+
resolvedModuleName,
|
|
2948
|
+
projectRoot
|
|
2949
|
+
);
|
|
2950
|
+
if (strategySet.has(moduleName)) {
|
|
2951
|
+
const pluginDefinition = extractStrategyPluginDefinition(moduleExport);
|
|
2952
|
+
if (!pluginDefinition) {
|
|
2953
|
+
issues.push(
|
|
2954
|
+
`${moduleName}: export { strategyEntries } is missing`
|
|
2955
|
+
);
|
|
2956
|
+
} else {
|
|
2957
|
+
const entryIssues = validateStrategyEntries(
|
|
2958
|
+
moduleName,
|
|
2959
|
+
pluginDefinition.strategyEntries,
|
|
2960
|
+
state
|
|
2961
|
+
);
|
|
2962
|
+
issues.push(...entryIssues);
|
|
2963
|
+
if (entryIssues.length === 0) {
|
|
2964
|
+
registerEntries2(
|
|
2965
|
+
pluginDefinition.strategyEntries,
|
|
2966
|
+
moduleName,
|
|
2967
|
+
state
|
|
2968
|
+
);
|
|
2969
|
+
}
|
|
2970
|
+
}
|
|
2971
|
+
}
|
|
2972
|
+
if (indicatorSet.has(moduleName)) {
|
|
2973
|
+
const indicatorPluginDefinition = extractIndicatorPluginDefinition(moduleExport);
|
|
2974
|
+
if (!indicatorPluginDefinition) {
|
|
2975
|
+
issues.push(
|
|
2976
|
+
`${moduleName}: export { indicatorEntries } is missing`
|
|
2977
|
+
);
|
|
2978
|
+
} else {
|
|
2979
|
+
(0, import_indicators.registerIndicatorEntries)(
|
|
2980
|
+
indicatorPluginDefinition.indicatorEntries,
|
|
2981
|
+
moduleName,
|
|
2982
|
+
projectRoot
|
|
2983
|
+
);
|
|
2984
|
+
}
|
|
2985
|
+
}
|
|
2986
|
+
if (!strategySet.has(moduleName) && !indicatorSet.has(moduleName)) {
|
|
2987
|
+
issues.push(`${moduleName}: plugin is not declared in config`);
|
|
2988
|
+
}
|
|
2989
|
+
} catch (error) {
|
|
2990
|
+
issues.push(`${moduleName}: failed to import: ${String(error)}`);
|
|
2991
|
+
}
|
|
2992
|
+
}
|
|
2993
|
+
if (issues.length > 0) {
|
|
2994
|
+
throw new Error(
|
|
2995
|
+
["Invalid TradeJS plugin catalog:", ...issues].join("\n")
|
|
2996
|
+
);
|
|
2997
|
+
}
|
|
2998
|
+
})();
|
|
2999
|
+
}
|
|
3000
|
+
await state.pluginsLoadPromise;
|
|
3001
|
+
};
|
|
3002
|
+
var getStrategyCreator = async (name, cwd = getTradejsProjectCwd(), moduleName) => {
|
|
3003
|
+
if (moduleName?.trim()) {
|
|
3004
|
+
await ensureStrategyPluginsLoaded(cwd);
|
|
3005
|
+
return (await resolveStrategyModuleEntry({ strategyName: name, moduleName, cwd })).creator;
|
|
3006
|
+
}
|
|
3007
|
+
await ensureStrategyPluginsLoaded(cwd);
|
|
3008
|
+
const { state } = getStrategyRegistryState(cwd);
|
|
3009
|
+
return state.strategyCreators.get(name);
|
|
3010
|
+
};
|
|
3011
|
+
var getStrategyEntry = async (name, cwd = getTradejsProjectCwd(), moduleName) => {
|
|
3012
|
+
if (moduleName?.trim()) {
|
|
3013
|
+
await ensureStrategyPluginsLoaded(cwd);
|
|
3014
|
+
return (await resolveStrategyModuleEntry({ strategyName: name, moduleName, cwd })).entry;
|
|
3015
|
+
}
|
|
3016
|
+
await ensureStrategyPluginsLoaded(cwd);
|
|
3017
|
+
const { state } = getStrategyRegistryState(cwd);
|
|
3018
|
+
return state.strategyEntriesMap.get(name);
|
|
3019
|
+
};
|
|
3020
|
+
var getStrategyPluginSource = async (name, cwd = getTradejsProjectCwd(), moduleName) => {
|
|
3021
|
+
if (moduleName?.trim()) {
|
|
3022
|
+
await ensureStrategyPluginsLoaded(cwd);
|
|
3023
|
+
return (await resolveStrategyModuleEntry({ strategyName: name, moduleName, cwd })).source;
|
|
3024
|
+
}
|
|
3025
|
+
await ensureStrategyPluginsLoaded(cwd);
|
|
3026
|
+
const { state } = getStrategyRegistryState(cwd);
|
|
3027
|
+
return state.strategySourcesMap.get(name);
|
|
3028
|
+
};
|
|
3029
|
+
var getStrategyManifest = (name, cwd = getTradejsProjectCwd()) => {
|
|
3030
|
+
if (!name) {
|
|
3031
|
+
return void 0;
|
|
3032
|
+
}
|
|
3033
|
+
const { state } = getStrategyRegistryState(cwd);
|
|
3034
|
+
return state.strategyManifestsMap.get(name);
|
|
3035
|
+
};
|
|
3036
|
+
var strategies = new Proxy(
|
|
3037
|
+
{},
|
|
3038
|
+
{
|
|
3039
|
+
get: (_target, property) => {
|
|
3040
|
+
if (typeof property !== "string") {
|
|
3041
|
+
return void 0;
|
|
3042
|
+
}
|
|
3043
|
+
return getStrategyRegistryState().state.strategyCreators.get(property);
|
|
3044
|
+
},
|
|
3045
|
+
ownKeys: () => {
|
|
3046
|
+
return [...getStrategyRegistryState().state.strategyCreators.keys()];
|
|
3047
|
+
},
|
|
3048
|
+
getOwnPropertyDescriptor: () => ({
|
|
3049
|
+
enumerable: true,
|
|
3050
|
+
configurable: true
|
|
3051
|
+
})
|
|
3052
|
+
}
|
|
3053
|
+
);
|
|
3054
|
+
|
|
3055
|
+
// src/strategyHelpers/tickerProfiles.ts
|
|
3056
|
+
var snapshots = /* @__PURE__ */ new Map();
|
|
3057
|
+
var resolveTickerProfileSource = (strategy) => {
|
|
3058
|
+
const declared = getStrategyManifest(strategy)?.contextRequirements?.tickerProfiles;
|
|
3059
|
+
if (declared) return declared;
|
|
3060
|
+
const provider = process.env.TICKER_PROFILES_PROVIDER?.trim();
|
|
3061
|
+
const universeId = process.env.TICKER_PROFILES_UNIVERSE?.trim();
|
|
3062
|
+
if (!provider && !universeId) return void 0;
|
|
3063
|
+
if (!provider || !universeId)
|
|
3064
|
+
throw new Error(
|
|
3065
|
+
"Set both TICKER_PROFILES_PROVIDER and TICKER_PROFILES_UNIVERSE"
|
|
3066
|
+
);
|
|
3067
|
+
return { provider, universeId };
|
|
3068
|
+
};
|
|
3069
|
+
var loadTickerProfileContext = async ({
|
|
3070
|
+
projectRoot,
|
|
3071
|
+
provider,
|
|
3072
|
+
universeId,
|
|
3073
|
+
symbol,
|
|
3074
|
+
timestamp
|
|
3075
|
+
}) => {
|
|
3076
|
+
const asOf = (0, import_tickerProfiles2.tickerProfileMonth)(timestamp), key = JSON.stringify([projectRoot, provider, universeId, asOf]);
|
|
3077
|
+
let cached = snapshots.get(key);
|
|
3078
|
+
if (!cached || cached.expires < Date.now()) {
|
|
3079
|
+
const value = (0, import_tickerProfiles.readTickerProfiles)(projectRoot, provider, universeId, asOf).then((envelope) => {
|
|
3080
|
+
if (!envelope) return null;
|
|
3081
|
+
const s = envelope.snapshot;
|
|
3082
|
+
if (!(0, import_tickerProfiles2.isTickerProfileSnapshot)(s) || s.asOf !== asOf || s.provider !== provider || s.universeId !== universeId)
|
|
3083
|
+
throw new Error("Ticker profile identity/schema mismatch");
|
|
3084
|
+
const entry = snapshots.get(key);
|
|
3085
|
+
if (entry) entry.expires = Infinity;
|
|
3086
|
+
return new Map(
|
|
3087
|
+
s.profiles.map((p) => [
|
|
3088
|
+
p.symbol,
|
|
3089
|
+
Object.freeze({
|
|
3090
|
+
...p,
|
|
3091
|
+
definition: s.definition,
|
|
3092
|
+
provider,
|
|
3093
|
+
universeId,
|
|
3094
|
+
snapshotId: envelope.snapshotId
|
|
3095
|
+
})
|
|
3096
|
+
])
|
|
3097
|
+
);
|
|
3098
|
+
}).catch((error) => {
|
|
3099
|
+
snapshots.delete(key);
|
|
3100
|
+
throw error;
|
|
3101
|
+
});
|
|
3102
|
+
cached = { expires: Date.now() + 6e4, value };
|
|
3103
|
+
snapshots.set(key, cached);
|
|
3104
|
+
if (snapshots.size > 24) {
|
|
3105
|
+
const oldest = snapshots.keys().next().value;
|
|
3106
|
+
if (oldest) snapshots.delete(oldest);
|
|
3107
|
+
}
|
|
3108
|
+
}
|
|
3109
|
+
return (await cached.value)?.get(symbol);
|
|
3110
|
+
};
|
|
3111
|
+
var withTickerProfile = (baseContext, profile) => Object.defineProperty(
|
|
3112
|
+
Object.create(
|
|
3113
|
+
Object.getPrototypeOf(baseContext),
|
|
3114
|
+
Object.getOwnPropertyDescriptors(baseContext)
|
|
3115
|
+
),
|
|
3116
|
+
"tickerProfile",
|
|
3117
|
+
{ enumerable: true, configurable: true, value: profile }
|
|
3118
|
+
);
|
|
3119
|
+
var enrichSignalWithTickerProfile = async (signal) => {
|
|
3120
|
+
const source = resolveTickerProfileSource(signal.strategy);
|
|
3121
|
+
if (!source || signal.universe === "tradfi" || !signal.symbol.endsWith("USDT"))
|
|
3122
|
+
return;
|
|
3123
|
+
const profile = await loadTickerProfileContext({
|
|
3124
|
+
...source,
|
|
3125
|
+
projectRoot: getTradejsProjectCwd(),
|
|
3126
|
+
symbol: signal.symbol,
|
|
3127
|
+
timestamp: signal.timestamp
|
|
3128
|
+
});
|
|
3129
|
+
if (profile) {
|
|
3130
|
+
signal.additionalIndicators = {
|
|
3131
|
+
...signal.additionalIndicators,
|
|
3132
|
+
tickerProfile: profile
|
|
3133
|
+
};
|
|
3134
|
+
}
|
|
3135
|
+
};
|
|
3136
|
+
|
|
3137
|
+
// src/strategyRuntime.ts
|
|
3138
|
+
var import_jev6 = require("@tradejs/core/jev");
|
|
3139
|
+
|
|
3140
|
+
// src/jev.ts
|
|
3141
|
+
var import_promises = __toESM(require("fs/promises"));
|
|
3142
|
+
var import_node_path = __toESM(require("path"));
|
|
3143
|
+
var import_jev4 = require("@tradejs/core/jev");
|
|
3144
|
+
var import_jev5 = require("@tradejs/infra/jev");
|
|
3145
|
+
var import_userSettings = require("@tradejs/infra/userSettings");
|
|
3146
|
+
init_jevInput();
|
|
3147
|
+
init_jevInput();
|
|
3148
|
+
init_jevTraining();
|
|
3149
|
+
var resolveJevProvider = async (userName) => {
|
|
3150
|
+
const settings = await (0, import_userSettings.getUserSettings)(userName);
|
|
3151
|
+
const provider = {
|
|
3152
|
+
endpoint: settings.JEV_API_ENDPOINT || import_jev4.JEV_PROVIDERS[0].endpoint,
|
|
3153
|
+
model: settings.JEV_MODEL || import_jev4.JEV_PROVIDERS[0].model
|
|
3154
|
+
};
|
|
3155
|
+
(0, import_jev4.parseJevConfig)({ source: "provider", mode: "observe", provider });
|
|
3156
|
+
if (!settings.JEV_API_KEY)
|
|
3157
|
+
throw new Error("Configure the Jev API key in Account settings");
|
|
3158
|
+
return provider;
|
|
3159
|
+
};
|
|
3160
|
+
var emptyScores = () => Object.fromEntries(import_jev4.JEV_DIMENSIONS.map((key) => [key, null]));
|
|
3161
|
+
var pending = /* @__PURE__ */ new Map();
|
|
3162
|
+
var pendingResponses = /* @__PURE__ */ new Map();
|
|
3163
|
+
var evaluateJevInput = async ({
|
|
3164
|
+
input,
|
|
3165
|
+
config,
|
|
3166
|
+
userName,
|
|
3167
|
+
projectRoot
|
|
3168
|
+
}) => {
|
|
3169
|
+
config = (0, import_jev4.parseJevConfig)(config);
|
|
3170
|
+
const inputHash = (0, import_jev5.jevHash)(input);
|
|
3171
|
+
const questions = questionsForJevInput(input);
|
|
3172
|
+
const questionsHash = (0, import_jev5.jevHash)(questions);
|
|
3173
|
+
const base = {
|
|
3174
|
+
questionSet: import_jev4.JEV_QUESTION_SET,
|
|
3175
|
+
schema: "tradejs-signal-assessment/v5",
|
|
3176
|
+
source: config.source,
|
|
3177
|
+
mode: config.mode,
|
|
3178
|
+
inputHash,
|
|
3179
|
+
questionsHash,
|
|
3180
|
+
geometryStatus: input.geometryStatus,
|
|
3181
|
+
levelsValid: typeof input.features["signal.validLevels"] === "boolean" ? input.features["signal.validLevels"] : null
|
|
3182
|
+
};
|
|
3183
|
+
if (!input.questions.length)
|
|
3184
|
+
return {
|
|
3185
|
+
assessment: {
|
|
3186
|
+
...base,
|
|
3187
|
+
status: "unavailable",
|
|
3188
|
+
model: "",
|
|
3189
|
+
scores: emptyScores(),
|
|
3190
|
+
confidence: emptyScores()
|
|
3191
|
+
}
|
|
3192
|
+
};
|
|
3193
|
+
if (config.source === "local") {
|
|
3194
|
+
const contents = await import_promises.default.readFile(
|
|
3195
|
+
import_node_path.default.resolve(projectRoot, config.modelFile),
|
|
3196
|
+
"utf8"
|
|
3197
|
+
);
|
|
3198
|
+
if ((0, import_jev5.jevFileHash)(contents) !== config.modelSha256)
|
|
3199
|
+
throw new Error("Local Jev gate checksum mismatch");
|
|
3200
|
+
const { validateJevGateModel: validateJevGateModel2 } = await Promise.resolve().then(() => (init_jevTraining(), jevTraining_exports));
|
|
3201
|
+
const model = validateJevGateModel2(JSON.parse(contents));
|
|
3202
|
+
if (model.strategy !== input.strategy || model.questionsHash !== (0, import_jev5.jevHash)(JEV_QUESTIONS))
|
|
3203
|
+
throw new Error("Local Jev gate strategy or question version mismatch");
|
|
3204
|
+
const scores = Object.fromEntries(
|
|
3205
|
+
import_jev4.JEV_DIMENSIONS.map((key) => [
|
|
3206
|
+
key,
|
|
3207
|
+
input.questions.includes(key) && model.trees[key] ? (0, import_jev4.predictJevTree)(model.trees[key], input.features) : null
|
|
3208
|
+
])
|
|
3209
|
+
);
|
|
3210
|
+
return {
|
|
3211
|
+
assessment: {
|
|
3212
|
+
...base,
|
|
3213
|
+
status: "available",
|
|
3214
|
+
model: config.modelSha256,
|
|
3215
|
+
scores,
|
|
3216
|
+
confidence: emptyScores()
|
|
3217
|
+
}
|
|
3218
|
+
};
|
|
3219
|
+
}
|
|
3220
|
+
const provider = config.provider ?? await resolveJevProvider(userName);
|
|
3221
|
+
const id = (0, import_jev5.jevHash)({ inputHash, questionsHash, provider });
|
|
3222
|
+
const dir = import_node_path.default.resolve(projectRoot, config.recordsDir ?? "data/ai/jev");
|
|
3223
|
+
const file = import_node_path.default.join(dir, "records", `${id}.json`);
|
|
3224
|
+
const getRecord = async () => {
|
|
3225
|
+
const existing = await (0, import_jev5.readJevArtifact)(file);
|
|
3226
|
+
if (existing) {
|
|
3227
|
+
if (existing.schema !== "tradejs-jev-record/v4" || existing.id !== id || existing.inputHash !== inputHash || existing.questionsHash !== questionsHash || (0, import_jev5.jevHash)(existing.input) !== inputHash || (0, import_jev5.jevHash)(existing.provider) !== (0, import_jev5.jevHash)(provider))
|
|
3228
|
+
throw new Error("Jev recording identity mismatch");
|
|
3229
|
+
(0, import_jev5.validateJevResponse)(existing.response, input.questions);
|
|
3230
|
+
for (const key of import_jev4.JEV_DIMENSIONS) {
|
|
3231
|
+
const expected = input.questions.includes(key) ? existing.response.answers[key].score / (0, import_jev5.jevScoreMaximum)(key) : null;
|
|
3232
|
+
if (existing.scores[key] !== expected)
|
|
3233
|
+
throw new Error("Jev recording scores do not match the response");
|
|
3234
|
+
}
|
|
3235
|
+
return existing;
|
|
3236
|
+
}
|
|
3237
|
+
if (config.source === "recorded")
|
|
3238
|
+
throw new Error(
|
|
3239
|
+
`Missing Jev recording ${id} for ${input.strategy} ${input.symbol} at ${new Date(input.timestamp).toISOString()}. Recorded mode requires the same signal-time facts and provider; a shorter backtest can change warm-up indicators or prior trade state.`
|
|
3240
|
+
);
|
|
3241
|
+
const settings = await (0, import_userSettings.getUserSettings)(userName);
|
|
3242
|
+
if (!settings.JEV_API_KEY)
|
|
3243
|
+
throw new Error("Configure the Jev API key in Account settings");
|
|
3244
|
+
const apiKey = settings.JEV_API_KEY;
|
|
3245
|
+
const configuredProvider = await resolveJevProvider(userName);
|
|
3246
|
+
if (configuredProvider.endpoint !== provider.endpoint)
|
|
3247
|
+
throw new Error(
|
|
3248
|
+
"Jev account endpoint changed; update the frozen provider configuration"
|
|
3249
|
+
);
|
|
3250
|
+
const started = Date.now();
|
|
3251
|
+
const state = stateForJevInput(input);
|
|
3252
|
+
const responseId = (0, import_jev5.jevHash)({
|
|
3253
|
+
state,
|
|
3254
|
+
questionsHash,
|
|
3255
|
+
provider,
|
|
3256
|
+
user: (0, import_jev5.jevHash)(userName)
|
|
3257
|
+
});
|
|
3258
|
+
const responseFile = import_node_path.default.join(dir, "responses", `${responseId}.json`);
|
|
3259
|
+
let responsePromise = pendingResponses.get(responseFile);
|
|
3260
|
+
if (!responsePromise) {
|
|
3261
|
+
responsePromise = (async () => {
|
|
3262
|
+
const saved = await (0, import_jev5.readJevArtifact)(responseFile);
|
|
3263
|
+
if (saved) return (0, import_jev5.validateJevResponse)(saved, input.questions);
|
|
3264
|
+
const fresh = await (0, import_jev5.requestJev)(provider, apiKey, state, questions);
|
|
3265
|
+
await (0, import_jev5.writeJevArtifact)(responseFile, fresh);
|
|
3266
|
+
return fresh;
|
|
3267
|
+
})();
|
|
3268
|
+
pendingResponses.set(responseFile, responsePromise);
|
|
3269
|
+
}
|
|
3270
|
+
let response;
|
|
3271
|
+
try {
|
|
3272
|
+
response = await responsePromise;
|
|
3273
|
+
} finally {
|
|
3274
|
+
pendingResponses.delete(responseFile);
|
|
3275
|
+
}
|
|
3276
|
+
const scores = Object.fromEntries(
|
|
3277
|
+
import_jev4.JEV_DIMENSIONS.map((key) => [
|
|
3278
|
+
key,
|
|
3279
|
+
input.questions.includes(key) ? response.answers[key].score / (0, import_jev5.jevScoreMaximum)(key) : null
|
|
3280
|
+
])
|
|
3281
|
+
);
|
|
3282
|
+
const value = {
|
|
3283
|
+
schema: "tradejs-jev-record/v4",
|
|
3284
|
+
id,
|
|
3285
|
+
inputHash,
|
|
3286
|
+
questionsHash,
|
|
3287
|
+
provider,
|
|
3288
|
+
input,
|
|
3289
|
+
response,
|
|
3290
|
+
scores,
|
|
3291
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3292
|
+
elapsedMs: Date.now() - started
|
|
3293
|
+
};
|
|
3294
|
+
await (0, import_jev5.writeJevArtifact)(file, value);
|
|
3295
|
+
return await (0, import_jev5.readJevArtifact)(file);
|
|
3296
|
+
};
|
|
3297
|
+
const pendingKey = `${file}:${(0, import_jev5.jevHash)(userName)}`;
|
|
3298
|
+
let promise = pending.get(pendingKey);
|
|
3299
|
+
if (!promise) {
|
|
3300
|
+
promise = getRecord();
|
|
3301
|
+
pending.set(pendingKey, promise);
|
|
3302
|
+
}
|
|
3303
|
+
let record;
|
|
3304
|
+
try {
|
|
3305
|
+
record = await promise;
|
|
3306
|
+
} finally {
|
|
3307
|
+
pending.delete(pendingKey);
|
|
3308
|
+
}
|
|
3309
|
+
return {
|
|
3310
|
+
record,
|
|
3311
|
+
assessment: {
|
|
3312
|
+
...base,
|
|
3313
|
+
status: "available",
|
|
3314
|
+
recordId: id,
|
|
3315
|
+
model: record.response.model,
|
|
3316
|
+
scores: record.scores,
|
|
3317
|
+
confidence: Object.fromEntries(
|
|
3318
|
+
import_jev4.JEV_DIMENSIONS.map((key) => [
|
|
3319
|
+
key,
|
|
3320
|
+
record.response.answers[key]?.confidence ?? null
|
|
3321
|
+
])
|
|
3322
|
+
)
|
|
3323
|
+
}
|
|
3324
|
+
};
|
|
3325
|
+
};
|
|
3326
|
+
var assessSignalWithJev = async ({
|
|
3327
|
+
signal,
|
|
3328
|
+
config,
|
|
3329
|
+
userName,
|
|
3330
|
+
projectRoot,
|
|
3331
|
+
strict,
|
|
3332
|
+
scope = "runtime"
|
|
3333
|
+
}) => {
|
|
3334
|
+
let assessment;
|
|
3335
|
+
try {
|
|
3336
|
+
const input = buildJevInput(signal);
|
|
3337
|
+
({ assessment } = await evaluateJevInput({
|
|
3338
|
+
input,
|
|
3339
|
+
config,
|
|
3340
|
+
userName,
|
|
3341
|
+
projectRoot
|
|
3342
|
+
}));
|
|
3343
|
+
} catch (error) {
|
|
3344
|
+
if (strict) throw error;
|
|
3345
|
+
assessment = {
|
|
3346
|
+
questionSet: import_jev4.JEV_QUESTION_SET,
|
|
3347
|
+
schema: "tradejs-signal-assessment/v5",
|
|
3348
|
+
source: config.source,
|
|
3349
|
+
mode: config.mode,
|
|
3350
|
+
status: "unavailable",
|
|
3351
|
+
inputHash: "",
|
|
3352
|
+
questionsHash: "",
|
|
3353
|
+
geometryStatus: "absent",
|
|
3354
|
+
levelsValid: null,
|
|
3355
|
+
model: config.provider?.model ?? config.modelSha256 ?? "",
|
|
3356
|
+
scores: emptyScores(),
|
|
3357
|
+
confidence: emptyScores()
|
|
3358
|
+
};
|
|
3359
|
+
}
|
|
3360
|
+
signal.assessment = assessment;
|
|
3361
|
+
signal.additionalIndicators = {
|
|
3362
|
+
...signal.additionalIndicators,
|
|
3363
|
+
jev: (0, import_jev4.jevAssessmentFeatures)(assessment)
|
|
3364
|
+
};
|
|
3365
|
+
const identity = {
|
|
3366
|
+
signalId: signal.signalId,
|
|
3367
|
+
strategy: signal.strategy,
|
|
3368
|
+
symbol: signal.symbol,
|
|
3369
|
+
timestamp: signal.timestamp,
|
|
3370
|
+
scope,
|
|
3371
|
+
user: (0, import_jev5.jevHash)(userName),
|
|
3372
|
+
config,
|
|
3373
|
+
assessment
|
|
3374
|
+
};
|
|
3375
|
+
await (0, import_jev5.writeJevArtifact)(
|
|
3376
|
+
import_node_path.default.resolve(
|
|
3377
|
+
projectRoot,
|
|
3378
|
+
config.recordsDir ?? "data/ai/jev",
|
|
3379
|
+
"decisions",
|
|
3380
|
+
`${(0, import_jev5.jevHash)(identity)}.json`
|
|
2899
3381
|
),
|
|
2900
|
-
|
|
2901
|
-
|
|
2902
|
-
|
|
2903
|
-
|
|
2904
|
-
|
|
3382
|
+
identity
|
|
3383
|
+
);
|
|
3384
|
+
return assessment;
|
|
3385
|
+
};
|
|
3386
|
+
|
|
3387
|
+
// src/strategyRuntime.ts
|
|
3388
|
+
var import_constants7 = require("@tradejs/core/constants");
|
|
3389
|
+
var import_strategies6 = require("@tradejs/core/strategies");
|
|
3390
|
+
var import_logger14 = require("@tradejs/infra/logger");
|
|
3391
|
+
|
|
3392
|
+
// src/strategyHelpers/runtime.ts
|
|
3393
|
+
var import_logger12 = require("@tradejs/infra/logger");
|
|
3394
|
+
var import_constants5 = require("@tradejs/core/constants");
|
|
3395
|
+
var import_ml2 = require("@tradejs/infra/ml");
|
|
3396
|
+
|
|
3397
|
+
// src/strategy/entrySubmissionValidation.ts
|
|
3398
|
+
var import_logger6 = require("@tradejs/infra/logger");
|
|
3399
|
+
|
|
3400
|
+
// src/strategy/entryExecutionValidation.ts
|
|
3401
|
+
var EntrySubmissionValidationError = class extends Error {
|
|
3402
|
+
constructor() {
|
|
3403
|
+
super(...arguments);
|
|
3404
|
+
this.name = "EntrySubmissionValidationError";
|
|
3405
|
+
}
|
|
3406
|
+
};
|
|
3407
|
+
var resolveEntryExecutionLimits = (config, direction) => {
|
|
3408
|
+
const side = config[direction];
|
|
3409
|
+
const minimum = side && typeof side === "object" && "minRiskRatio" in side ? side.minRiskRatio : void 0;
|
|
3410
|
+
const maximum = config.MAX_LOSS_VALUE;
|
|
3411
|
+
if (minimum == null && maximum == null) return void 0;
|
|
3412
|
+
const minimumNetRiskRatio = minimum == null ? void 0 : Number(minimum);
|
|
3413
|
+
const maxLossValue = maximum == null ? void 0 : Number(maximum);
|
|
3414
|
+
const feeRate = Number(config.RISK_FEE_RATE ?? 0);
|
|
3415
|
+
const slippage = Number(config.RISK_SLIPPAGE_BPS ?? 0);
|
|
3416
|
+
const impact = Number(config.RISK_MARKET_IMPACT_BPS ?? 0);
|
|
3417
|
+
const slippageBps = slippage + impact;
|
|
3418
|
+
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)
|
|
3419
|
+
throw new Error("INVALID_ENTRY_EXECUTION_LIMITS");
|
|
3420
|
+
return { minimumNetRiskRatio, maxLossValue, feeRate, slippageBps };
|
|
3421
|
+
};
|
|
3422
|
+
var validateEntryExecution = ({
|
|
3423
|
+
direction,
|
|
3424
|
+
price,
|
|
3425
|
+
qty,
|
|
3426
|
+
stopLossPrice,
|
|
3427
|
+
takeProfitPrice,
|
|
3428
|
+
limits
|
|
3429
|
+
}) => {
|
|
3430
|
+
if (!Number.isFinite(price) || price <= 0 || !Number.isFinite(qty) || qty <= 0 || stopLossPrice == null || !Number.isFinite(stopLossPrice) || stopLossPrice <= 0 || !Number.isFinite(takeProfitPrice) || takeProfitPrice <= 0)
|
|
3431
|
+
throw new EntrySubmissionValidationError(
|
|
3432
|
+
"ENTRY_ECONOMICS_INPUTS_UNAVAILABLE"
|
|
3433
|
+
);
|
|
3434
|
+
const side = direction === "LONG" ? 1 : -1;
|
|
3435
|
+
if (side * (price - stopLossPrice) <= 0 || side * (takeProfitPrice - price) <= 0)
|
|
3436
|
+
throw new EntrySubmissionValidationError(
|
|
3437
|
+
"INVALID_ENTRY_PROTECTION_AT_SUBMISSION"
|
|
3438
|
+
);
|
|
3439
|
+
const slippage = limits.slippageBps / 1e4;
|
|
3440
|
+
const cost = (value, sign) => value * slippage + value * (1 + sign * slippage) * limits.feeRate;
|
|
3441
|
+
const entryCost = cost(price, side);
|
|
3442
|
+
const lossPerUnit = Math.abs(price - stopLossPrice) + entryCost + cost(stopLossPrice, -side);
|
|
3443
|
+
const rewardPerUnit = Math.max(
|
|
3444
|
+
0,
|
|
3445
|
+
Math.abs(takeProfitPrice - price) - entryCost - cost(takeProfitPrice, -side)
|
|
3446
|
+
);
|
|
3447
|
+
const netRiskRatio = rewardPerUnit / lossPerUnit;
|
|
3448
|
+
const plannedLossValue = qty * lossPerUnit;
|
|
3449
|
+
if (!Number.isFinite(netRiskRatio) || !Number.isFinite(plannedLossValue))
|
|
3450
|
+
throw new EntrySubmissionValidationError(
|
|
3451
|
+
"ENTRY_ECONOMICS_INPUTS_UNAVAILABLE"
|
|
3452
|
+
);
|
|
3453
|
+
const belowMinimumNetRiskRatio = limits.minimumNetRiskRatio != null && netRiskRatio <= limits.minimumNetRiskRatio;
|
|
3454
|
+
const riskExceeded = limits.maxLossValue != null && plannedLossValue > limits.maxLossValue * (1 + 1e-9);
|
|
3455
|
+
return {
|
|
3456
|
+
netRiskRatio,
|
|
3457
|
+
plannedLossValue,
|
|
3458
|
+
riskExceeded,
|
|
3459
|
+
belowMinimumNetRiskRatio
|
|
2905
3460
|
};
|
|
2906
3461
|
};
|
|
2907
|
-
|
|
2908
|
-
|
|
2909
|
-
|
|
2910
|
-
|
|
2911
|
-
|
|
3462
|
+
|
|
3463
|
+
// src/strategy/entrySubmissionValidation.ts
|
|
3464
|
+
var MAX_ENTRY_QUOTE_AGE_MS = 5e3;
|
|
3465
|
+
var toFiniteNumberOrNull = (value) => typeof value === "number" && Number.isFinite(value) ? value : null;
|
|
3466
|
+
var getOrderArrivalSnapshot = async ({
|
|
3467
|
+
connector,
|
|
3468
|
+
symbol,
|
|
3469
|
+
requireTimestamp = false
|
|
3470
|
+
}) => {
|
|
3471
|
+
if (typeof connector.getTopOfBookTicker !== "function") {
|
|
3472
|
+
return {
|
|
3473
|
+
arrivalSnapshotTime: Date.now(),
|
|
3474
|
+
arrivalSource: "unavailable",
|
|
3475
|
+
bid: null,
|
|
3476
|
+
ask: null,
|
|
3477
|
+
arrivalMid: null,
|
|
3478
|
+
spreadBps: null
|
|
3479
|
+
};
|
|
3480
|
+
}
|
|
3481
|
+
try {
|
|
3482
|
+
const ticker = await connector.getTopOfBookTicker(symbol);
|
|
3483
|
+
const arrivalSnapshotTime = toFiniteNumberOrNull(ticker?.timestamp);
|
|
3484
|
+
const timestampUnavailable = requireTimestamp && arrivalSnapshotTime == null;
|
|
3485
|
+
const bid = timestampUnavailable ? null : toFiniteNumberOrNull(ticker?.bidPrice);
|
|
3486
|
+
const ask = timestampUnavailable ? null : toFiniteNumberOrNull(ticker?.askPrice);
|
|
3487
|
+
const arrivalMid = bid != null && ask != null ? (bid + ask) / 2 : null;
|
|
3488
|
+
const spreadBps = bid != null && ask != null && arrivalMid != null && arrivalMid > 0 ? (ask - bid) / arrivalMid * 1e4 : null;
|
|
3489
|
+
return {
|
|
3490
|
+
arrivalSnapshotTime: arrivalSnapshotTime ?? Date.now(),
|
|
3491
|
+
arrivalSource: "top_of_book",
|
|
3492
|
+
bid,
|
|
3493
|
+
ask,
|
|
3494
|
+
arrivalMid,
|
|
3495
|
+
spreadBps
|
|
3496
|
+
};
|
|
3497
|
+
} catch (error) {
|
|
3498
|
+
import_logger6.logger.warn(
|
|
3499
|
+
"runtime order arrival snapshot failed: %s %s",
|
|
3500
|
+
symbol,
|
|
3501
|
+
error?.message || String(error)
|
|
3502
|
+
);
|
|
2912
3503
|
return {
|
|
2913
|
-
|
|
2914
|
-
|
|
2915
|
-
|
|
2916
|
-
|
|
2917
|
-
|
|
2918
|
-
|
|
2919
|
-
ethMarketCapUsd: null,
|
|
2920
|
-
btcVolumeUsd: null,
|
|
2921
|
-
ethVolumeUsd: null,
|
|
2922
|
-
btcVolumeToMarketCap: null,
|
|
2923
|
-
ethVolumeToMarketCap: null,
|
|
2924
|
-
ethBtcMarketCapRatio: null,
|
|
2925
|
-
ethBtcMarketCapRatioChange24hPct: null,
|
|
2926
|
-
ethVsBtcVolumeRatio: null,
|
|
2927
|
-
referenceLiquidityRegime: null
|
|
3504
|
+
arrivalSnapshotTime: Date.now(),
|
|
3505
|
+
arrivalSource: "top_of_book_error",
|
|
3506
|
+
bid: null,
|
|
3507
|
+
ask: null,
|
|
3508
|
+
arrivalMid: null,
|
|
3509
|
+
spreadBps: null
|
|
2928
3510
|
};
|
|
2929
3511
|
}
|
|
2930
|
-
|
|
2931
|
-
|
|
2932
|
-
|
|
2933
|
-
|
|
2934
|
-
|
|
2935
|
-
|
|
2936
|
-
|
|
2937
|
-
|
|
2938
|
-
|
|
2939
|
-
|
|
2940
|
-
|
|
2941
|
-
|
|
2942
|
-
|
|
2943
|
-
|
|
2944
|
-
|
|
2945
|
-
|
|
2946
|
-
|
|
2947
|
-
|
|
2948
|
-
|
|
2949
|
-
|
|
2950
|
-
|
|
2951
|
-
)
|
|
2952
|
-
|
|
2953
|
-
|
|
3512
|
+
};
|
|
3513
|
+
var validateEntryProtectionAtArrival = ({
|
|
3514
|
+
direction,
|
|
3515
|
+
signalPrice,
|
|
3516
|
+
bid,
|
|
3517
|
+
ask,
|
|
3518
|
+
arrivalMid,
|
|
3519
|
+
takeProfits,
|
|
3520
|
+
stopLossPrice
|
|
3521
|
+
}) => {
|
|
3522
|
+
const entryReference = direction === "LONG" ? ask ?? arrivalMid ?? signalPrice : bid ?? arrivalMid ?? signalPrice;
|
|
3523
|
+
const stopReference = direction === "LONG" ? bid ?? arrivalMid ?? signalPrice : ask ?? arrivalMid ?? signalPrice;
|
|
3524
|
+
if (!Number.isFinite(entryReference) || entryReference <= 0 || !Number.isFinite(stopReference) || stopReference <= 0) {
|
|
3525
|
+
throw new Error("INVALID_ENTRY_REFERENCE_AT_ARRIVAL");
|
|
3526
|
+
}
|
|
3527
|
+
for (const takeProfit of takeProfits) {
|
|
3528
|
+
if (!Number.isFinite(takeProfit.price) || takeProfit.price <= 0 || (direction === "LONG" ? takeProfit.price <= entryReference : takeProfit.price >= entryReference)) {
|
|
3529
|
+
throw new Error("TAKE_PROFIT_CROSSED_BEFORE_ENTRY");
|
|
3530
|
+
}
|
|
3531
|
+
}
|
|
3532
|
+
if (stopLossPrice != null && (!Number.isFinite(stopLossPrice) || stopLossPrice <= 0 || (direction === "LONG" ? stopLossPrice >= stopReference : stopLossPrice <= stopReference))) {
|
|
3533
|
+
throw new Error("STOP_LOSS_CROSSED_BEFORE_ENTRY");
|
|
3534
|
+
}
|
|
3535
|
+
};
|
|
3536
|
+
var createEntrySubmissionValidation = ({
|
|
3537
|
+
connector,
|
|
3538
|
+
symbol,
|
|
3539
|
+
direction,
|
|
3540
|
+
takeProfits,
|
|
3541
|
+
stopLossPrice,
|
|
3542
|
+
limits,
|
|
3543
|
+
onValidated
|
|
3544
|
+
}) => {
|
|
3545
|
+
if (!limits) return void 0;
|
|
3546
|
+
if (!connector.capabilities?.orderSubmissionValidation)
|
|
3547
|
+
throw new EntrySubmissionValidationError(
|
|
3548
|
+
"ENTRY_SUBMISSION_VALIDATION_UNSUPPORTED"
|
|
3549
|
+
);
|
|
3550
|
+
return async ({ qty, executionPrice }) => {
|
|
3551
|
+
const simulated = Reflect.get(connector, "__tradejsTestConnector") === true;
|
|
3552
|
+
const snapshot2 = simulated && executionPrice != null ? {
|
|
3553
|
+
arrivalSnapshotTime: Date.now(),
|
|
3554
|
+
arrivalSource: "simulation",
|
|
3555
|
+
bid: executionPrice,
|
|
3556
|
+
ask: executionPrice,
|
|
3557
|
+
arrivalMid: executionPrice,
|
|
3558
|
+
spreadBps: 0
|
|
3559
|
+
} : await getOrderArrivalSnapshot({
|
|
3560
|
+
connector,
|
|
3561
|
+
symbol,
|
|
3562
|
+
requireTimestamp: true
|
|
3563
|
+
});
|
|
3564
|
+
const age = Date.now() - snapshot2.arrivalSnapshotTime;
|
|
3565
|
+
if (snapshot2.bid == null || snapshot2.ask == null || !Number.isFinite(snapshot2.bid) || !Number.isFinite(snapshot2.ask) || snapshot2.bid <= 0 || snapshot2.ask < snapshot2.bid)
|
|
3566
|
+
throw new EntrySubmissionValidationError(
|
|
3567
|
+
"ENTRY_QUOTE_UNAVAILABLE_AT_SUBMISSION"
|
|
3568
|
+
);
|
|
3569
|
+
if (!Number.isFinite(age) || age < -MAX_ENTRY_QUOTE_AGE_MS || age > MAX_ENTRY_QUOTE_AGE_MS)
|
|
3570
|
+
throw new EntrySubmissionValidationError(
|
|
3571
|
+
"ENTRY_QUOTE_STALE_AT_SUBMISSION"
|
|
3572
|
+
);
|
|
3573
|
+
const price = direction === "LONG" ? snapshot2.ask : snapshot2.bid;
|
|
3574
|
+
validateEntryProtectionAtArrival({
|
|
3575
|
+
direction,
|
|
3576
|
+
signalPrice: price,
|
|
3577
|
+
bid: snapshot2.bid,
|
|
3578
|
+
ask: snapshot2.ask,
|
|
3579
|
+
arrivalMid: snapshot2.arrivalMid,
|
|
3580
|
+
takeProfits,
|
|
3581
|
+
stopLossPrice
|
|
3582
|
+
});
|
|
3583
|
+
const prices = takeProfits.map((tp) => tp.price);
|
|
3584
|
+
const economics2 = validateEntryExecution({
|
|
3585
|
+
direction,
|
|
3586
|
+
price,
|
|
3587
|
+
qty,
|
|
3588
|
+
stopLossPrice,
|
|
3589
|
+
takeProfitPrice: direction === "LONG" ? Math.max(...prices) : Math.min(...prices),
|
|
3590
|
+
limits
|
|
3591
|
+
});
|
|
3592
|
+
if (economics2.belowMinimumNetRiskRatio) {
|
|
3593
|
+
import_logger6.logger.warn(
|
|
3594
|
+
"entry submission warning: %s %s ENTRY_RR_BELOW_MIN_AT_SUBMISSION (RR %s, threshold %s)",
|
|
3595
|
+
symbol,
|
|
3596
|
+
direction,
|
|
3597
|
+
economics2.netRiskRatio,
|
|
3598
|
+
limits.minimumNetRiskRatio
|
|
3599
|
+
);
|
|
3600
|
+
}
|
|
3601
|
+
onValidated?.(snapshot2, economics2);
|
|
2954
3602
|
};
|
|
2955
3603
|
};
|
|
2956
|
-
|
|
3604
|
+
|
|
3605
|
+
// src/ai.ts
|
|
3606
|
+
var import_aiLanguages2 = require("@tradejs/core/aiLanguages");
|
|
3607
|
+
var import_redis4 = require("@tradejs/infra/redis");
|
|
3608
|
+
|
|
3609
|
+
// src/strategyAdapters/ai.ts
|
|
3610
|
+
var import_indicators2 = require("@tradejs/core/indicators");
|
|
3611
|
+
var import_aiFeatures = require("@tradejs/core/aiFeatures");
|
|
3612
|
+
|
|
3613
|
+
// src/aiShared.ts
|
|
3614
|
+
var MAX_AI_SERIES_POINTS = 5;
|
|
3615
|
+
var COMPACT_INDICATORS_SNAPSHOT_SYMBOL = /* @__PURE__ */ Symbol.for(
|
|
3616
|
+
"tradejs.indicators.compactSnapshot"
|
|
3617
|
+
);
|
|
3618
|
+
var COMPACT_INDICATORS_SNAPSHOT_KEY = "__tradejsCompactIndicatorsSnapshot";
|
|
3619
|
+
var trimSeriesDeep = (value) => {
|
|
3620
|
+
if (Array.isArray(value)) {
|
|
3621
|
+
const trimmed = value.slice(-MAX_AI_SERIES_POINTS);
|
|
3622
|
+
const isMatrix = trimmed.every((item) => Array.isArray(item));
|
|
3623
|
+
if (isMatrix) {
|
|
3624
|
+
return trimmed;
|
|
3625
|
+
}
|
|
3626
|
+
return trimmed.map(
|
|
3627
|
+
(item) => item && typeof item === "object" ? trimSeriesDeep(item) : item
|
|
3628
|
+
);
|
|
3629
|
+
}
|
|
3630
|
+
if (value && typeof value === "object") {
|
|
3631
|
+
return Object.fromEntries(
|
|
3632
|
+
Object.entries(value).map(([key, nested]) => [
|
|
3633
|
+
key,
|
|
3634
|
+
trimSeriesDeep(nested)
|
|
3635
|
+
])
|
|
3636
|
+
);
|
|
3637
|
+
}
|
|
3638
|
+
return value;
|
|
3639
|
+
};
|
|
3640
|
+
var buildCompactAiIndicatorsSnapshot = (value) => {
|
|
3641
|
+
const compactSnapshot = value && typeof value === "object" ? value[COMPACT_INDICATORS_SNAPSHOT_SYMBOL] ?? value[COMPACT_INDICATORS_SNAPSHOT_KEY] : void 0;
|
|
3642
|
+
if (typeof compactSnapshot === "function") {
|
|
3643
|
+
return compactSnapshot({ limit: MAX_AI_SERIES_POINTS });
|
|
3644
|
+
}
|
|
3645
|
+
return trimSeriesDeep(value);
|
|
3646
|
+
};
|
|
3647
|
+
|
|
3648
|
+
// src/aiMarketContext.ts
|
|
3649
|
+
var toRecord = (value) => {
|
|
3650
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
3651
|
+
return null;
|
|
3652
|
+
}
|
|
3653
|
+
return value;
|
|
3654
|
+
};
|
|
3655
|
+
var toFiniteNumber = (value) => {
|
|
3656
|
+
const numeric = typeof value === "number" ? value : typeof value === "string" && value.trim() ? Number(value) : Number.NaN;
|
|
3657
|
+
return Number.isFinite(numeric) ? numeric : null;
|
|
3658
|
+
};
|
|
3659
|
+
var roundTo = (value, decimals) => {
|
|
3660
|
+
const factor = 10 ** decimals;
|
|
3661
|
+
return Math.round(value * factor) / factor;
|
|
3662
|
+
};
|
|
3663
|
+
var buildMissingSpreadContext = () => ({
|
|
3664
|
+
source: "payload.additionalIndicators.baseContext.relative.execution.venueSpread",
|
|
3665
|
+
indicatorKey: "payload.additionalIndicators.baseContext.relative.execution.venueSpread",
|
|
3666
|
+
available: false,
|
|
3667
|
+
value: null,
|
|
3668
|
+
zScore: null,
|
|
3669
|
+
bps: null,
|
|
3670
|
+
absBps: null,
|
|
3671
|
+
bias: null,
|
|
3672
|
+
severity: null
|
|
3673
|
+
});
|
|
3674
|
+
var buildSpreadContextFromSignal = (signal) => {
|
|
2957
3675
|
const baseContext = toRecord(signal.additionalIndicators?.baseContext);
|
|
2958
3676
|
const relative = toRecord(baseContext?.relative);
|
|
2959
|
-
const
|
|
2960
|
-
|
|
2961
|
-
|
|
2962
|
-
|
|
2963
|
-
|
|
2964
|
-
interval: null,
|
|
2965
|
-
asOfTs: null,
|
|
2966
|
-
stale: null,
|
|
2967
|
-
exchangesCount: null,
|
|
2968
|
-
totalVolumeUsd: null,
|
|
2969
|
-
totalVolumeChange24hPct: null,
|
|
2970
|
-
binanceVolumeUsd: null,
|
|
2971
|
-
binanceVolumeShare: null,
|
|
2972
|
-
topExchangeVolumeShare: null,
|
|
2973
|
-
liquidityRegime: null
|
|
2974
|
-
};
|
|
3677
|
+
const execution = toRecord(relative?.execution);
|
|
3678
|
+
const spread = toFiniteNumber(execution?.venueSpread);
|
|
3679
|
+
const zScore = toFiniteNumber(execution?.venueSpreadZScore);
|
|
3680
|
+
if (spread == null) {
|
|
3681
|
+
return buildMissingSpreadContext();
|
|
2975
3682
|
}
|
|
3683
|
+
const value = roundTo(spread, 8);
|
|
3684
|
+
const bps = roundTo(value * 1e4, 2);
|
|
3685
|
+
const absBps = Math.abs(bps);
|
|
3686
|
+
const bias = absBps < 1 ? "flat" : bps > 0 ? "coinbase_premium" : "binance_premium";
|
|
3687
|
+
const severity = absBps >= 20 ? "wide" : absBps >= 5 ? "elevated" : "normal";
|
|
2976
3688
|
return {
|
|
2977
|
-
source:
|
|
3689
|
+
source: "payload.additionalIndicators.baseContext.relative.execution.venueSpread",
|
|
3690
|
+
indicatorKey: "payload.additionalIndicators.baseContext.relative.execution.venueSpread",
|
|
2978
3691
|
available: true,
|
|
2979
|
-
|
|
2980
|
-
|
|
2981
|
-
|
|
2982
|
-
|
|
2983
|
-
|
|
2984
|
-
|
|
2985
|
-
cmcExchangeLiquidity.totalVolumeChange24hPct
|
|
2986
|
-
),
|
|
2987
|
-
binanceVolumeUsd: toFiniteNumber(cmcExchangeLiquidity.binanceVolumeUsd),
|
|
2988
|
-
binanceVolumeShare: toFiniteNumber(cmcExchangeLiquidity.binanceVolumeShare),
|
|
2989
|
-
topExchangeVolumeShare: toFiniteNumber(
|
|
2990
|
-
cmcExchangeLiquidity.topExchangeVolumeShare
|
|
2991
|
-
),
|
|
2992
|
-
liquidityRegime: typeof cmcExchangeLiquidity.liquidityRegime === "string" ? cmcExchangeLiquidity.liquidityRegime : null
|
|
3692
|
+
value,
|
|
3693
|
+
zScore,
|
|
3694
|
+
bps,
|
|
3695
|
+
absBps,
|
|
3696
|
+
bias,
|
|
3697
|
+
severity
|
|
2993
3698
|
};
|
|
2994
3699
|
};
|
|
2995
|
-
var
|
|
3700
|
+
var buildTrueDeltaContextFromSignal = (signal) => {
|
|
2996
3701
|
const baseContext = toRecord(signal.additionalIndicators?.baseContext);
|
|
2997
|
-
const
|
|
2998
|
-
const
|
|
2999
|
-
|
|
3702
|
+
const participation = toRecord(baseContext?.participation);
|
|
3703
|
+
const delta = toRecord(participation?.delta);
|
|
3704
|
+
const source = String(delta?.source ?? "");
|
|
3705
|
+
const isTrueDeltaSource = source === "kline_taker_volume" || source === "agg_trades" || source === "trades";
|
|
3706
|
+
if (!delta || !isTrueDeltaSource) {
|
|
3000
3707
|
return {
|
|
3001
|
-
source: null,
|
|
3708
|
+
source: source || null,
|
|
3002
3709
|
available: false,
|
|
3003
|
-
|
|
3004
|
-
|
|
3005
|
-
|
|
3006
|
-
|
|
3007
|
-
|
|
3008
|
-
|
|
3009
|
-
classification: null,
|
|
3010
|
-
sentimentRegime: null
|
|
3710
|
+
buyPressurePct: null,
|
|
3711
|
+
buyVolume: null,
|
|
3712
|
+
sellVolume: null,
|
|
3713
|
+
netDelta: null,
|
|
3714
|
+
deltaPct: null,
|
|
3715
|
+
signedVolumeZScore: null
|
|
3011
3716
|
};
|
|
3012
3717
|
}
|
|
3013
3718
|
return {
|
|
3014
|
-
source
|
|
3719
|
+
source,
|
|
3015
3720
|
available: true,
|
|
3016
|
-
|
|
3017
|
-
|
|
3018
|
-
|
|
3019
|
-
|
|
3020
|
-
|
|
3021
|
-
|
|
3022
|
-
classification: typeof cmcFearGreed.classification === "string" ? cmcFearGreed.classification : null,
|
|
3023
|
-
sentimentRegime: typeof cmcFearGreed.sentimentRegime === "string" ? cmcFearGreed.sentimentRegime : null
|
|
3721
|
+
buyPressurePct: toFiniteNumber(delta.buyPressurePct),
|
|
3722
|
+
buyVolume: toFiniteNumber(delta.buyVolume),
|
|
3723
|
+
sellVolume: toFiniteNumber(delta.sellVolume),
|
|
3724
|
+
netDelta: toFiniteNumber(delta.netDelta),
|
|
3725
|
+
deltaPct: toFiniteNumber(delta.deltaPct),
|
|
3726
|
+
signedVolumeZScore: toFiniteNumber(delta.signedVolumeZScore)
|
|
3024
3727
|
};
|
|
3025
3728
|
};
|
|
3026
|
-
var
|
|
3729
|
+
var buildTradeFlowContextFromSignal = (signal) => {
|
|
3027
3730
|
const baseContext = toRecord(signal.additionalIndicators?.baseContext);
|
|
3028
|
-
const
|
|
3029
|
-
const
|
|
3030
|
-
if (!
|
|
3731
|
+
const participation = toRecord(baseContext?.participation);
|
|
3732
|
+
const tradeFlow = toRecord(participation?.tradeFlow);
|
|
3733
|
+
if (!tradeFlow) {
|
|
3031
3734
|
return {
|
|
3032
3735
|
source: null,
|
|
3033
3736
|
available: false,
|
|
3034
3737
|
interval: null,
|
|
3035
|
-
asOfTs: null,
|
|
3036
3738
|
stale: null,
|
|
3037
|
-
|
|
3038
|
-
|
|
3039
|
-
|
|
3040
|
-
|
|
3041
|
-
cmc20Value: null,
|
|
3042
|
-
cmc20Change24hPct: null,
|
|
3043
|
-
cmc20TopConstituentSymbol: null,
|
|
3044
|
-
cmc20TopConstituentWeightPct: null,
|
|
3045
|
-
cmc20ToCmc100Ratio: null,
|
|
3046
|
-
cmc20ToCmc100RatioChange24hPct: null,
|
|
3047
|
-
indexRegime: null
|
|
3739
|
+
trades: null,
|
|
3740
|
+
buyPressurePct: null,
|
|
3741
|
+
netBaseDelta: null,
|
|
3742
|
+
netQuoteDelta: null
|
|
3048
3743
|
};
|
|
3049
3744
|
}
|
|
3050
3745
|
return {
|
|
3051
|
-
source: String(
|
|
3746
|
+
source: String(tradeFlow.source ?? ""),
|
|
3052
3747
|
available: true,
|
|
3053
|
-
interval:
|
|
3054
|
-
|
|
3055
|
-
|
|
3056
|
-
|
|
3057
|
-
|
|
3058
|
-
|
|
3059
|
-
cmc100TopConstituentWeightPct: toFiniteNumber(
|
|
3060
|
-
cmcIndexes.cmc100TopConstituentWeightPct
|
|
3061
|
-
),
|
|
3062
|
-
cmc20Value: toFiniteNumber(cmcIndexes.cmc20Value),
|
|
3063
|
-
cmc20Change24hPct: toFiniteNumber(cmcIndexes.cmc20Change24hPct),
|
|
3064
|
-
cmc20TopConstituentSymbol: typeof cmcIndexes.cmc20TopConstituentSymbol === "string" ? cmcIndexes.cmc20TopConstituentSymbol : null,
|
|
3065
|
-
cmc20TopConstituentWeightPct: toFiniteNumber(
|
|
3066
|
-
cmcIndexes.cmc20TopConstituentWeightPct
|
|
3067
|
-
),
|
|
3068
|
-
cmc20ToCmc100Ratio: toFiniteNumber(cmcIndexes.cmc20ToCmc100Ratio),
|
|
3069
|
-
cmc20ToCmc100RatioChange24hPct: toFiniteNumber(
|
|
3070
|
-
cmcIndexes.cmc20ToCmc100RatioChange24hPct
|
|
3071
|
-
),
|
|
3072
|
-
indexRegime: typeof cmcIndexes.indexRegime === "string" ? cmcIndexes.indexRegime : null
|
|
3748
|
+
interval: String(tradeFlow.interval ?? ""),
|
|
3749
|
+
stale: typeof tradeFlow.stale === "boolean" ? tradeFlow.stale : null,
|
|
3750
|
+
trades: toFiniteNumber(tradeFlow.trades),
|
|
3751
|
+
buyPressurePct: toFiniteNumber(tradeFlow.buyPressurePct),
|
|
3752
|
+
netBaseDelta: toFiniteNumber(tradeFlow.netBaseDelta),
|
|
3753
|
+
netQuoteDelta: toFiniteNumber(tradeFlow.netQuoteDelta)
|
|
3073
3754
|
};
|
|
3074
3755
|
};
|
|
3075
|
-
var
|
|
3756
|
+
var buildMarketBreadthContextFromSignal = (signal) => {
|
|
3076
3757
|
const baseContext = toRecord(signal.additionalIndicators?.baseContext);
|
|
3077
3758
|
const relative = toRecord(baseContext?.relative);
|
|
3078
|
-
const
|
|
3079
|
-
|
|
3080
|
-
const tradeFlowBySymbol = toRecord(refs?.tradeFlowBySymbol);
|
|
3081
|
-
const primaryTradeFlow = primaryReferenceSymbol != null ? toRecord(tradeFlowBySymbol?.[primaryReferenceSymbol]) : null;
|
|
3082
|
-
if (!refs) {
|
|
3759
|
+
const breadth = toRecord(relative?.marketBreadth);
|
|
3760
|
+
if (!breadth) {
|
|
3083
3761
|
return {
|
|
3084
3762
|
source: null,
|
|
3085
3763
|
available: false,
|
|
3086
|
-
|
|
3087
|
-
|
|
3088
|
-
|
|
3089
|
-
|
|
3764
|
+
universe: null,
|
|
3765
|
+
interval: null,
|
|
3766
|
+
stale: null,
|
|
3767
|
+
symbolsCount: null,
|
|
3768
|
+
advanceDeclineRatio: null,
|
|
3769
|
+
pctAboveMa20: null,
|
|
3770
|
+
pctAboveMa50: null,
|
|
3771
|
+
equalWeightedReturn: null,
|
|
3772
|
+
volumeWeightedReturn: null,
|
|
3773
|
+
dispersion: null
|
|
3090
3774
|
};
|
|
3091
3775
|
}
|
|
3092
3776
|
return {
|
|
3093
|
-
source: String(
|
|
3777
|
+
source: String(breadth.source ?? ""),
|
|
3094
3778
|
available: true,
|
|
3095
|
-
|
|
3096
|
-
|
|
3097
|
-
|
|
3098
|
-
|
|
3099
|
-
),
|
|
3100
|
-
|
|
3101
|
-
|
|
3102
|
-
|
|
3103
|
-
|
|
3104
|
-
|
|
3105
|
-
binanceCoinbaseSpread: buildSpreadContextFromSignal(signal)
|
|
3106
|
-
},
|
|
3107
|
-
participation: {
|
|
3108
|
-
trueDelta: buildTrueDeltaContextFromSignal(signal),
|
|
3109
|
-
tradeFlow: buildTradeFlowContextFromSignal(signal)
|
|
3110
|
-
},
|
|
3111
|
-
relative: {
|
|
3112
|
-
marketBreadth: buildMarketBreadthContextFromSignal(signal),
|
|
3113
|
-
marketBreadths: buildMarketBreadthsContextFromSignal(signal),
|
|
3114
|
-
targetVsBtc: buildTargetVsBtcContextFromSignal(signal),
|
|
3115
|
-
btcAltRegime: buildBtcAltRegimeContextFromSignal(signal),
|
|
3116
|
-
cmcGlobal: buildCmcGlobalContextFromSignal(signal),
|
|
3117
|
-
cmcReferenceAssets: buildCmcReferenceAssetsContextFromSignal(signal),
|
|
3118
|
-
cmcExchangeLiquidity: buildCmcExchangeLiquidityContextFromSignal(signal),
|
|
3119
|
-
cmcFearGreed: buildCmcFearGreedContextFromSignal(signal),
|
|
3120
|
-
cmcIndexes: buildCmcIndexesContextFromSignal(signal),
|
|
3121
|
-
referenceTradeFlow: buildReferenceTradeFlowContextFromSignal(signal)
|
|
3122
|
-
}
|
|
3123
|
-
});
|
|
3124
|
-
|
|
3125
|
-
// src/strategy/manifests.ts
|
|
3126
|
-
var import_indicators = require("@tradejs/core/indicators");
|
|
3127
|
-
var SHARED_STRATEGY_REGISTRY_KEY = "__tradejsNodeSharedStrategyRegistryV1__";
|
|
3128
|
-
var sharedRegistryScope = globalThis;
|
|
3129
|
-
var sharedStrategyRegistry = sharedRegistryScope[SHARED_STRATEGY_REGISTRY_KEY] ?? (sharedRegistryScope[SHARED_STRATEGY_REGISTRY_KEY] = {
|
|
3130
|
-
registryStateByProjectRoot: /* @__PURE__ */ new Map()
|
|
3131
|
-
});
|
|
3132
|
-
var createStrategyRegistryState = () => ({
|
|
3133
|
-
strategyCreators: /* @__PURE__ */ new Map(),
|
|
3134
|
-
strategyManifestsMap: /* @__PURE__ */ new Map(),
|
|
3135
|
-
strategyEntriesMap: /* @__PURE__ */ new Map(),
|
|
3136
|
-
strategySourcesMap: /* @__PURE__ */ new Map(),
|
|
3137
|
-
strategyModuleResolutions: /* @__PURE__ */ new Map(),
|
|
3138
|
-
pluginsLoadPromise: null
|
|
3139
|
-
});
|
|
3140
|
-
var registryStateByProjectRoot2 = sharedStrategyRegistry.registryStateByProjectRoot;
|
|
3141
|
-
var getStrategyRegistryState = (cwd = getTradejsProjectCwd()) => {
|
|
3142
|
-
const projectRoot = getTradejsProjectCwd(cwd);
|
|
3143
|
-
let state = registryStateByProjectRoot2.get(projectRoot);
|
|
3144
|
-
if (!state) {
|
|
3145
|
-
state = createStrategyRegistryState();
|
|
3146
|
-
registryStateByProjectRoot2.set(projectRoot, state);
|
|
3147
|
-
}
|
|
3148
|
-
return {
|
|
3149
|
-
projectRoot,
|
|
3150
|
-
state
|
|
3151
|
-
};
|
|
3152
|
-
};
|
|
3153
|
-
var toUniqueModules2 = (modules = []) => [
|
|
3154
|
-
...new Set(modules.map((moduleName) => moduleName.trim()).filter(Boolean))
|
|
3155
|
-
];
|
|
3156
|
-
var getConfiguredPluginModuleNames = async (cwd = getTradejsProjectCwd()) => {
|
|
3157
|
-
const config = await loadTradejsConfig(cwd);
|
|
3158
|
-
return {
|
|
3159
|
-
strategyModules: toUniqueModules2(config.strategies),
|
|
3160
|
-
indicatorModules: toUniqueModules2(config.indicators)
|
|
3779
|
+
universe: String(breadth.universe ?? ""),
|
|
3780
|
+
interval: String(breadth.interval ?? ""),
|
|
3781
|
+
stale: typeof breadth.stale === "boolean" ? breadth.stale : null,
|
|
3782
|
+
symbolsCount: toFiniteNumber(breadth.symbolsCount),
|
|
3783
|
+
advanceDeclineRatio: toFiniteNumber(breadth.advanceDeclineRatio),
|
|
3784
|
+
pctAboveMa20: toFiniteNumber(breadth.pctAboveMa20),
|
|
3785
|
+
pctAboveMa50: toFiniteNumber(breadth.pctAboveMa50),
|
|
3786
|
+
equalWeightedReturn: toFiniteNumber(breadth.equalWeightedReturn),
|
|
3787
|
+
volumeWeightedReturn: toFiniteNumber(breadth.volumeWeightedReturn),
|
|
3788
|
+
dispersion: toFiniteNumber(breadth.dispersion)
|
|
3161
3789
|
};
|
|
3162
3790
|
};
|
|
3163
|
-
var
|
|
3164
|
-
|
|
3165
|
-
|
|
3166
|
-
|
|
3167
|
-
const
|
|
3168
|
-
|
|
3169
|
-
|
|
3170
|
-
|
|
3171
|
-
|
|
3172
|
-
|
|
3173
|
-
|
|
3174
|
-
|
|
3175
|
-
|
|
3176
|
-
|
|
3177
|
-
|
|
3178
|
-
|
|
3179
|
-
|
|
3180
|
-
|
|
3181
|
-
|
|
3182
|
-
|
|
3183
|
-
};
|
|
3184
|
-
var extractIndicatorPluginDefinition = (moduleExport) => {
|
|
3185
|
-
const indicatorEntries = extractModuleEntries(
|
|
3186
|
-
moduleExport,
|
|
3187
|
-
"indicatorEntries"
|
|
3188
|
-
);
|
|
3189
|
-
return indicatorEntries ? { indicatorEntries } : null;
|
|
3190
|
-
};
|
|
3191
|
-
var validateStrategyEntries = (moduleName, entries, state) => {
|
|
3192
|
-
const issues = [];
|
|
3193
|
-
const names = /* @__PURE__ */ new Set();
|
|
3194
|
-
entries.forEach((entry, index) => {
|
|
3195
|
-
const entryPath = `${moduleName}.strategyEntries[${index}]`;
|
|
3196
|
-
const strategyName = entry?.manifest?.name;
|
|
3197
|
-
if (typeof strategyName !== "string" || !strategyName.trim()) {
|
|
3198
|
-
issues.push(`${entryPath}: manifest.name is required`);
|
|
3199
|
-
} else if (names.has(strategyName) || state?.strategyEntriesMap.has(strategyName)) {
|
|
3200
|
-
issues.push(`${entryPath}: duplicate strategy ${strategyName}`);
|
|
3201
|
-
} else {
|
|
3202
|
-
names.add(strategyName);
|
|
3203
|
-
}
|
|
3204
|
-
if (!entry || typeof entry !== "object" || !entry.defaults || typeof entry.defaults !== "object" || Array.isArray(entry.defaults)) {
|
|
3205
|
-
issues.push(`${entryPath}: defaults must be an object`);
|
|
3206
|
-
}
|
|
3207
|
-
if (typeof entry?.parseConfig !== "function") {
|
|
3208
|
-
issues.push(`${entryPath}: parseConfig is required`);
|
|
3209
|
-
}
|
|
3210
|
-
if (typeof entry?.createCore !== "function") {
|
|
3211
|
-
issues.push(`${entryPath}: createCore is required`);
|
|
3791
|
+
var buildMarketBreadthsContextFromSignal = (signal) => {
|
|
3792
|
+
const baseContext = toRecord(signal.additionalIndicators?.baseContext);
|
|
3793
|
+
const relative = toRecord(baseContext?.relative);
|
|
3794
|
+
const breadths = toRecord(relative?.marketBreadths);
|
|
3795
|
+
const build = (key) => {
|
|
3796
|
+
const breadth = toRecord(breadths?.[key]);
|
|
3797
|
+
if (!breadth) {
|
|
3798
|
+
return {
|
|
3799
|
+
source: null,
|
|
3800
|
+
available: false,
|
|
3801
|
+
universe: null,
|
|
3802
|
+
interval: null,
|
|
3803
|
+
stale: null,
|
|
3804
|
+
symbolsCount: null,
|
|
3805
|
+
advanceDeclineRatio: null,
|
|
3806
|
+
pctAboveMa20: null,
|
|
3807
|
+
pctAboveMa50: null,
|
|
3808
|
+
equalWeightedReturn: null,
|
|
3809
|
+
volumeWeightedReturn: null,
|
|
3810
|
+
dispersion: null
|
|
3811
|
+
};
|
|
3212
3812
|
}
|
|
3213
|
-
|
|
3214
|
-
|
|
3215
|
-
|
|
3216
|
-
|
|
3217
|
-
|
|
3218
|
-
|
|
3219
|
-
|
|
3220
|
-
|
|
3221
|
-
|
|
3222
|
-
|
|
3223
|
-
|
|
3813
|
+
return {
|
|
3814
|
+
source: String(breadth.source ?? ""),
|
|
3815
|
+
available: true,
|
|
3816
|
+
universe: String(breadth.universe ?? ""),
|
|
3817
|
+
interval: String(breadth.interval ?? ""),
|
|
3818
|
+
stale: typeof breadth.stale === "boolean" ? breadth.stale : null,
|
|
3819
|
+
symbolsCount: toFiniteNumber(breadth.symbolsCount),
|
|
3820
|
+
advanceDeclineRatio: toFiniteNumber(breadth.advanceDeclineRatio),
|
|
3821
|
+
pctAboveMa20: toFiniteNumber(breadth.pctAboveMa20),
|
|
3822
|
+
pctAboveMa50: toFiniteNumber(breadth.pctAboveMa50),
|
|
3823
|
+
equalWeightedReturn: toFiniteNumber(breadth.equalWeightedReturn),
|
|
3824
|
+
volumeWeightedReturn: toFiniteNumber(breadth.volumeWeightedReturn),
|
|
3825
|
+
dispersion: toFiniteNumber(breadth.dispersion)
|
|
3826
|
+
};
|
|
3827
|
+
};
|
|
3828
|
+
return {
|
|
3829
|
+
top5: build("top5"),
|
|
3830
|
+
top10: build("top10"),
|
|
3831
|
+
top30: build("top30"),
|
|
3832
|
+
top50: build("top50"),
|
|
3833
|
+
top100: build("top100")
|
|
3834
|
+
};
|
|
3224
3835
|
};
|
|
3225
|
-
var
|
|
3226
|
-
|
|
3227
|
-
|
|
3228
|
-
}
|
|
3229
|
-
|
|
3230
|
-
|
|
3231
|
-
|
|
3232
|
-
|
|
3233
|
-
|
|
3234
|
-
|
|
3235
|
-
|
|
3236
|
-
|
|
3237
|
-
|
|
3238
|
-
|
|
3239
|
-
|
|
3240
|
-
|
|
3241
|
-
|
|
3242
|
-
);
|
|
3836
|
+
var buildTargetVsReference = (signal, asset) => {
|
|
3837
|
+
const base = toRecord(signal.additionalIndicators?.baseContext);
|
|
3838
|
+
const relative = toRecord(base?.relative);
|
|
3839
|
+
const target = toRecord(relative?.[`targetVs${asset}`]);
|
|
3840
|
+
return {
|
|
3841
|
+
source: target ? String(target.source ?? "") : null,
|
|
3842
|
+
available: Boolean(target),
|
|
3843
|
+
ratioReturn1h: toFiniteNumber(target?.ratioReturn1h),
|
|
3844
|
+
ratioReturn4h: toFiniteNumber(target?.ratioReturn4h),
|
|
3845
|
+
ratioReturn24h: toFiniteNumber(target?.ratioReturn24h),
|
|
3846
|
+
alpha1h: toFiniteNumber(target?.[`alphaVs${asset}1h`]),
|
|
3847
|
+
alpha4h: toFiniteNumber(target?.[`alphaVs${asset}4h`]),
|
|
3848
|
+
alpha24h: toFiniteNumber(target?.[`alphaVs${asset}24h`]),
|
|
3849
|
+
beta: toFiniteNumber(target?.[`betaTo${asset}20`]),
|
|
3850
|
+
correlation: toFiniteNumber(target?.[`correlationTo${asset}20`]),
|
|
3851
|
+
ratioTrend: typeof target?.ratioTrend === "string" ? target.ratioTrend : null
|
|
3852
|
+
};
|
|
3243
3853
|
};
|
|
3244
|
-
var
|
|
3245
|
-
|
|
3246
|
-
|
|
3247
|
-
|
|
3248
|
-
|
|
3249
|
-
|
|
3250
|
-
|
|
3854
|
+
var buildTargetVsBtcContextFromSignal = (signal) => {
|
|
3855
|
+
const { alpha1h, alpha4h, alpha24h, beta, correlation, ...shared } = buildTargetVsReference(signal, "Btc");
|
|
3856
|
+
return {
|
|
3857
|
+
...shared,
|
|
3858
|
+
alphaVsBtc1h: alpha1h,
|
|
3859
|
+
alphaVsBtc4h: alpha4h,
|
|
3860
|
+
alphaVsBtc24h: alpha24h,
|
|
3861
|
+
betaToBtc20: beta,
|
|
3862
|
+
correlationToBtc20: correlation
|
|
3863
|
+
};
|
|
3251
3864
|
};
|
|
3252
|
-
var
|
|
3253
|
-
|
|
3254
|
-
|
|
3255
|
-
|
|
3256
|
-
|
|
3257
|
-
|
|
3258
|
-
|
|
3259
|
-
|
|
3865
|
+
var buildTargetVsEthContextFromSignal = (signal) => {
|
|
3866
|
+
const { alpha1h, alpha4h, alpha24h, beta, correlation, ...shared } = buildTargetVsReference(signal, "Eth");
|
|
3867
|
+
return {
|
|
3868
|
+
...shared,
|
|
3869
|
+
alphaVsEth1h: alpha1h,
|
|
3870
|
+
alphaVsEth4h: alpha4h,
|
|
3871
|
+
alphaVsEth24h: alpha24h,
|
|
3872
|
+
betaToEth20: beta,
|
|
3873
|
+
correlationToEth20: correlation
|
|
3874
|
+
};
|
|
3260
3875
|
};
|
|
3261
|
-
var
|
|
3262
|
-
|
|
3263
|
-
|
|
3264
|
-
|
|
3265
|
-
|
|
3266
|
-
const { projectRoot, state } = getStrategyRegistryState(cwd);
|
|
3267
|
-
const source = moduleName.trim();
|
|
3268
|
-
const resolvedModuleName = resolvePluginModuleSpecifier(source, projectRoot);
|
|
3269
|
-
const cacheKey = `${resolvedModuleName}\0${strategyName}`;
|
|
3270
|
-
const cached = state.strategyModuleResolutions.get(cacheKey);
|
|
3271
|
-
if (cached) return cached;
|
|
3272
|
-
const resolution = (async () => {
|
|
3273
|
-
const moduleExport = await importStrategyPluginModule(
|
|
3274
|
-
resolvedModuleName,
|
|
3275
|
-
projectRoot
|
|
3276
|
-
);
|
|
3277
|
-
const pluginDefinition = extractStrategyPluginDefinition(moduleExport);
|
|
3278
|
-
if (!pluginDefinition) {
|
|
3279
|
-
throw new Error(`${source}: export { strategyEntries } is missing`);
|
|
3280
|
-
}
|
|
3281
|
-
const issues = validateStrategyEntries(
|
|
3282
|
-
source,
|
|
3283
|
-
pluginDefinition.strategyEntries
|
|
3284
|
-
);
|
|
3285
|
-
if (issues.length > 0) {
|
|
3286
|
-
throw new Error(
|
|
3287
|
-
["Invalid TradeJS strategy module:", ...issues].join("\n")
|
|
3288
|
-
);
|
|
3289
|
-
}
|
|
3290
|
-
const entry = pluginDefinition.strategyEntries.find(
|
|
3291
|
-
(candidate) => candidate.manifest.name === strategyName
|
|
3292
|
-
);
|
|
3293
|
-
if (!entry) {
|
|
3294
|
-
throw new Error(`${source}: strategy ${strategyName} is missing`);
|
|
3295
|
-
}
|
|
3296
|
-
const factory = sharedStrategyRegistry.strategyRuntimeFactory;
|
|
3297
|
-
if (!factory) {
|
|
3298
|
-
throw new Error("Strategy runtime factory is not configured");
|
|
3299
|
-
}
|
|
3300
|
-
const manifestsByName = new Map(
|
|
3301
|
-
pluginDefinition.strategyEntries.map(
|
|
3302
|
-
(candidate) => [candidate.manifest.name, candidate.manifest]
|
|
3303
|
-
)
|
|
3304
|
-
);
|
|
3876
|
+
var buildBtcAltRegimeContextFromSignal = (signal) => {
|
|
3877
|
+
const baseContext = toRecord(signal.additionalIndicators?.baseContext);
|
|
3878
|
+
const relative = toRecord(baseContext?.relative);
|
|
3879
|
+
const btcAltRegime = toRecord(relative?.btcAltRegime);
|
|
3880
|
+
if (!btcAltRegime) {
|
|
3305
3881
|
return {
|
|
3306
|
-
|
|
3307
|
-
|
|
3308
|
-
|
|
3309
|
-
|
|
3310
|
-
|
|
3311
|
-
|
|
3312
|
-
|
|
3313
|
-
|
|
3314
|
-
|
|
3315
|
-
|
|
3316
|
-
|
|
3882
|
+
source: null,
|
|
3883
|
+
available: false,
|
|
3884
|
+
universe: null,
|
|
3885
|
+
interval: null,
|
|
3886
|
+
stale: null,
|
|
3887
|
+
regime: null,
|
|
3888
|
+
btcReturn24h: null,
|
|
3889
|
+
altBasketReturn24h: null,
|
|
3890
|
+
btcVsAltReturn24h: null,
|
|
3891
|
+
btcTurnoverShare24h: null,
|
|
3892
|
+
btcTurnoverShareChange24h: null,
|
|
3893
|
+
altVolToBtcVol24h: null,
|
|
3894
|
+
altDispersion24h: null
|
|
3317
3895
|
};
|
|
3318
|
-
})();
|
|
3319
|
-
state.strategyModuleResolutions.set(cacheKey, resolution);
|
|
3320
|
-
try {
|
|
3321
|
-
return await resolution;
|
|
3322
|
-
} catch (error) {
|
|
3323
|
-
state.strategyModuleResolutions.delete(cacheKey);
|
|
3324
|
-
throw error;
|
|
3325
3896
|
}
|
|
3897
|
+
return {
|
|
3898
|
+
source: String(btcAltRegime.source ?? ""),
|
|
3899
|
+
available: true,
|
|
3900
|
+
universe: String(btcAltRegime.universe ?? ""),
|
|
3901
|
+
interval: String(btcAltRegime.interval ?? ""),
|
|
3902
|
+
stale: typeof btcAltRegime.stale === "boolean" ? btcAltRegime.stale : null,
|
|
3903
|
+
regime: typeof btcAltRegime.regime === "string" ? btcAltRegime.regime : null,
|
|
3904
|
+
btcReturn24h: toFiniteNumber(btcAltRegime.btcReturn24h),
|
|
3905
|
+
altBasketReturn24h: toFiniteNumber(btcAltRegime.altBasketReturn24h),
|
|
3906
|
+
btcVsAltReturn24h: toFiniteNumber(btcAltRegime.btcVsAltReturn24h),
|
|
3907
|
+
btcTurnoverShare24h: toFiniteNumber(btcAltRegime.btcTurnoverShare24h),
|
|
3908
|
+
btcTurnoverShareChange24h: toFiniteNumber(
|
|
3909
|
+
btcAltRegime.btcTurnoverShareChange24h
|
|
3910
|
+
),
|
|
3911
|
+
altVolToBtcVol24h: toFiniteNumber(btcAltRegime.altVolToBtcVol24h),
|
|
3912
|
+
altDispersion24h: toFiniteNumber(btcAltRegime.altDispersion24h)
|
|
3913
|
+
};
|
|
3326
3914
|
};
|
|
3327
|
-
var
|
|
3328
|
-
const
|
|
3329
|
-
|
|
3330
|
-
|
|
3331
|
-
|
|
3332
|
-
|
|
3333
|
-
|
|
3334
|
-
|
|
3335
|
-
|
|
3336
|
-
|
|
3337
|
-
|
|
3338
|
-
|
|
3339
|
-
|
|
3340
|
-
|
|
3341
|
-
|
|
3342
|
-
|
|
3343
|
-
|
|
3344
|
-
|
|
3345
|
-
|
|
3346
|
-
|
|
3347
|
-
|
|
3348
|
-
|
|
3349
|
-
|
|
3350
|
-
|
|
3351
|
-
|
|
3352
|
-
|
|
3353
|
-
|
|
3354
|
-
|
|
3355
|
-
issues.push(
|
|
3356
|
-
`${moduleName}: export { strategyEntries } is missing`
|
|
3357
|
-
);
|
|
3358
|
-
} else {
|
|
3359
|
-
const entryIssues = validateStrategyEntries(
|
|
3360
|
-
moduleName,
|
|
3361
|
-
pluginDefinition.strategyEntries,
|
|
3362
|
-
state
|
|
3363
|
-
);
|
|
3364
|
-
issues.push(...entryIssues);
|
|
3365
|
-
if (entryIssues.length === 0) {
|
|
3366
|
-
registerEntries2(
|
|
3367
|
-
pluginDefinition.strategyEntries,
|
|
3368
|
-
moduleName,
|
|
3369
|
-
state
|
|
3370
|
-
);
|
|
3371
|
-
}
|
|
3372
|
-
}
|
|
3373
|
-
}
|
|
3374
|
-
if (indicatorSet.has(moduleName)) {
|
|
3375
|
-
const indicatorPluginDefinition = extractIndicatorPluginDefinition(moduleExport);
|
|
3376
|
-
if (!indicatorPluginDefinition) {
|
|
3377
|
-
issues.push(
|
|
3378
|
-
`${moduleName}: export { indicatorEntries } is missing`
|
|
3379
|
-
);
|
|
3380
|
-
} else {
|
|
3381
|
-
(0, import_indicators.registerIndicatorEntries)(
|
|
3382
|
-
indicatorPluginDefinition.indicatorEntries,
|
|
3383
|
-
moduleName,
|
|
3384
|
-
projectRoot
|
|
3385
|
-
);
|
|
3386
|
-
}
|
|
3387
|
-
}
|
|
3388
|
-
if (!strategySet.has(moduleName) && !indicatorSet.has(moduleName)) {
|
|
3389
|
-
issues.push(`${moduleName}: plugin is not declared in config`);
|
|
3390
|
-
}
|
|
3391
|
-
} catch (error) {
|
|
3392
|
-
issues.push(`${moduleName}: failed to import: ${String(error)}`);
|
|
3393
|
-
}
|
|
3394
|
-
}
|
|
3395
|
-
if (issues.length > 0) {
|
|
3396
|
-
throw new Error(
|
|
3397
|
-
["Invalid TradeJS plugin catalog:", ...issues].join("\n")
|
|
3398
|
-
);
|
|
3399
|
-
}
|
|
3400
|
-
})();
|
|
3915
|
+
var buildCmcGlobalContextFromSignal = (signal) => {
|
|
3916
|
+
const baseContext = toRecord(signal.additionalIndicators?.baseContext);
|
|
3917
|
+
const relative = toRecord(baseContext?.relative);
|
|
3918
|
+
const cmcGlobal = toRecord(relative?.cmcGlobal);
|
|
3919
|
+
if (!cmcGlobal) {
|
|
3920
|
+
return {
|
|
3921
|
+
source: null,
|
|
3922
|
+
available: false,
|
|
3923
|
+
interval: null,
|
|
3924
|
+
asOfTs: null,
|
|
3925
|
+
stale: null,
|
|
3926
|
+
totalMarketCapUsd: null,
|
|
3927
|
+
totalVolumeUsd: null,
|
|
3928
|
+
totalVolumeReportedUsd: null,
|
|
3929
|
+
altMarketCapUsd: null,
|
|
3930
|
+
altVolumeUsd: null,
|
|
3931
|
+
altVolumeReportedUsd: null,
|
|
3932
|
+
btcDominancePct: null,
|
|
3933
|
+
ethDominancePct: null,
|
|
3934
|
+
btcDominanceChange24hPct: null,
|
|
3935
|
+
ethDominanceChange24hPct: null,
|
|
3936
|
+
altMarketCapChange24hPct: null,
|
|
3937
|
+
altVolumeChange24hPct: null,
|
|
3938
|
+
activeCryptocurrencies: null,
|
|
3939
|
+
activeExchanges: null,
|
|
3940
|
+
activeMarketPairs: null,
|
|
3941
|
+
altLiquidityRegime: null
|
|
3942
|
+
};
|
|
3401
3943
|
}
|
|
3402
|
-
|
|
3944
|
+
return {
|
|
3945
|
+
source: String(cmcGlobal.source ?? ""),
|
|
3946
|
+
available: true,
|
|
3947
|
+
interval: typeof cmcGlobal.interval === "string" ? cmcGlobal.interval : null,
|
|
3948
|
+
asOfTs: toFiniteNumber(cmcGlobal.asOfTs),
|
|
3949
|
+
stale: typeof cmcGlobal.stale === "boolean" ? cmcGlobal.stale : null,
|
|
3950
|
+
totalMarketCapUsd: toFiniteNumber(cmcGlobal.totalMarketCapUsd),
|
|
3951
|
+
totalVolumeUsd: toFiniteNumber(cmcGlobal.totalVolumeUsd),
|
|
3952
|
+
totalVolumeReportedUsd: toFiniteNumber(cmcGlobal.totalVolumeReportedUsd),
|
|
3953
|
+
altMarketCapUsd: toFiniteNumber(cmcGlobal.altMarketCapUsd),
|
|
3954
|
+
altVolumeUsd: toFiniteNumber(cmcGlobal.altVolumeUsd),
|
|
3955
|
+
altVolumeReportedUsd: toFiniteNumber(cmcGlobal.altVolumeReportedUsd),
|
|
3956
|
+
btcDominancePct: toFiniteNumber(cmcGlobal.btcDominancePct),
|
|
3957
|
+
ethDominancePct: toFiniteNumber(cmcGlobal.ethDominancePct),
|
|
3958
|
+
btcDominanceChange24hPct: toFiniteNumber(
|
|
3959
|
+
cmcGlobal.btcDominanceChange24hPct
|
|
3960
|
+
),
|
|
3961
|
+
ethDominanceChange24hPct: toFiniteNumber(
|
|
3962
|
+
cmcGlobal.ethDominanceChange24hPct
|
|
3963
|
+
),
|
|
3964
|
+
altMarketCapChange24hPct: toFiniteNumber(
|
|
3965
|
+
cmcGlobal.altMarketCapChange24hPct
|
|
3966
|
+
),
|
|
3967
|
+
altVolumeChange24hPct: toFiniteNumber(cmcGlobal.altVolumeChange24hPct),
|
|
3968
|
+
activeCryptocurrencies: toFiniteNumber(cmcGlobal.activeCryptocurrencies),
|
|
3969
|
+
activeExchanges: toFiniteNumber(cmcGlobal.activeExchanges),
|
|
3970
|
+
activeMarketPairs: toFiniteNumber(cmcGlobal.activeMarketPairs),
|
|
3971
|
+
altLiquidityRegime: typeof cmcGlobal.altLiquidityRegime === "string" ? cmcGlobal.altLiquidityRegime : null
|
|
3972
|
+
};
|
|
3403
3973
|
};
|
|
3404
|
-
var
|
|
3405
|
-
|
|
3406
|
-
|
|
3407
|
-
|
|
3974
|
+
var buildCmcReferenceAssetsContextFromSignal = (signal) => {
|
|
3975
|
+
const baseContext = toRecord(signal.additionalIndicators?.baseContext);
|
|
3976
|
+
const relative = toRecord(baseContext?.relative);
|
|
3977
|
+
const cmcReferenceAssets = toRecord(relative?.cmcReferenceAssets);
|
|
3978
|
+
if (!cmcReferenceAssets) {
|
|
3979
|
+
return {
|
|
3980
|
+
source: null,
|
|
3981
|
+
available: false,
|
|
3982
|
+
interval: null,
|
|
3983
|
+
asOfTs: null,
|
|
3984
|
+
stale: null,
|
|
3985
|
+
btcMarketCapUsd: null,
|
|
3986
|
+
ethMarketCapUsd: null,
|
|
3987
|
+
btcVolumeUsd: null,
|
|
3988
|
+
ethVolumeUsd: null,
|
|
3989
|
+
btcVolumeToMarketCap: null,
|
|
3990
|
+
ethVolumeToMarketCap: null,
|
|
3991
|
+
ethBtcMarketCapRatio: null,
|
|
3992
|
+
ethBtcMarketCapRatioChange24hPct: null,
|
|
3993
|
+
ethVsBtcVolumeRatio: null,
|
|
3994
|
+
referenceLiquidityRegime: null
|
|
3995
|
+
};
|
|
3408
3996
|
}
|
|
3409
|
-
|
|
3410
|
-
|
|
3411
|
-
|
|
3997
|
+
return {
|
|
3998
|
+
source: String(cmcReferenceAssets.source ?? ""),
|
|
3999
|
+
available: true,
|
|
4000
|
+
interval: typeof cmcReferenceAssets.interval === "string" ? cmcReferenceAssets.interval : null,
|
|
4001
|
+
asOfTs: toFiniteNumber(cmcReferenceAssets.asOfTs),
|
|
4002
|
+
stale: typeof cmcReferenceAssets.stale === "boolean" ? cmcReferenceAssets.stale : null,
|
|
4003
|
+
btcMarketCapUsd: toFiniteNumber(cmcReferenceAssets.btcMarketCapUsd),
|
|
4004
|
+
ethMarketCapUsd: toFiniteNumber(cmcReferenceAssets.ethMarketCapUsd),
|
|
4005
|
+
btcVolumeUsd: toFiniteNumber(cmcReferenceAssets.btcVolumeUsd),
|
|
4006
|
+
ethVolumeUsd: toFiniteNumber(cmcReferenceAssets.ethVolumeUsd),
|
|
4007
|
+
btcVolumeToMarketCap: toFiniteNumber(
|
|
4008
|
+
cmcReferenceAssets.btcVolumeToMarketCap
|
|
4009
|
+
),
|
|
4010
|
+
ethVolumeToMarketCap: toFiniteNumber(
|
|
4011
|
+
cmcReferenceAssets.ethVolumeToMarketCap
|
|
4012
|
+
),
|
|
4013
|
+
ethBtcMarketCapRatio: toFiniteNumber(
|
|
4014
|
+
cmcReferenceAssets.ethBtcMarketCapRatio
|
|
4015
|
+
),
|
|
4016
|
+
ethBtcMarketCapRatioChange24hPct: toFiniteNumber(
|
|
4017
|
+
cmcReferenceAssets.ethBtcMarketCapRatioChange24hPct
|
|
4018
|
+
),
|
|
4019
|
+
ethVsBtcVolumeRatio: toFiniteNumber(cmcReferenceAssets.ethVsBtcVolumeRatio),
|
|
4020
|
+
referenceLiquidityRegime: typeof cmcReferenceAssets.referenceLiquidityRegime === "string" ? cmcReferenceAssets.referenceLiquidityRegime : null
|
|
4021
|
+
};
|
|
3412
4022
|
};
|
|
3413
|
-
var
|
|
3414
|
-
|
|
3415
|
-
|
|
3416
|
-
|
|
4023
|
+
var buildCmcExchangeLiquidityContextFromSignal = (signal) => {
|
|
4024
|
+
const baseContext = toRecord(signal.additionalIndicators?.baseContext);
|
|
4025
|
+
const relative = toRecord(baseContext?.relative);
|
|
4026
|
+
const cmcExchangeLiquidity = toRecord(relative?.cmcExchangeLiquidity);
|
|
4027
|
+
if (!cmcExchangeLiquidity) {
|
|
4028
|
+
return {
|
|
4029
|
+
source: null,
|
|
4030
|
+
available: false,
|
|
4031
|
+
interval: null,
|
|
4032
|
+
asOfTs: null,
|
|
4033
|
+
stale: null,
|
|
4034
|
+
exchangesCount: null,
|
|
4035
|
+
totalVolumeUsd: null,
|
|
4036
|
+
totalVolumeChange24hPct: null,
|
|
4037
|
+
binanceVolumeUsd: null,
|
|
4038
|
+
binanceVolumeShare: null,
|
|
4039
|
+
topExchangeVolumeShare: null,
|
|
4040
|
+
liquidityRegime: null
|
|
4041
|
+
};
|
|
3417
4042
|
}
|
|
3418
|
-
|
|
3419
|
-
|
|
3420
|
-
|
|
4043
|
+
return {
|
|
4044
|
+
source: String(cmcExchangeLiquidity.source ?? ""),
|
|
4045
|
+
available: true,
|
|
4046
|
+
interval: typeof cmcExchangeLiquidity.interval === "string" ? cmcExchangeLiquidity.interval : null,
|
|
4047
|
+
asOfTs: toFiniteNumber(cmcExchangeLiquidity.asOfTs),
|
|
4048
|
+
stale: typeof cmcExchangeLiquidity.stale === "boolean" ? cmcExchangeLiquidity.stale : null,
|
|
4049
|
+
exchangesCount: toFiniteNumber(cmcExchangeLiquidity.exchangesCount),
|
|
4050
|
+
totalVolumeUsd: toFiniteNumber(cmcExchangeLiquidity.totalVolumeUsd),
|
|
4051
|
+
totalVolumeChange24hPct: toFiniteNumber(
|
|
4052
|
+
cmcExchangeLiquidity.totalVolumeChange24hPct
|
|
4053
|
+
),
|
|
4054
|
+
binanceVolumeUsd: toFiniteNumber(cmcExchangeLiquidity.binanceVolumeUsd),
|
|
4055
|
+
binanceVolumeShare: toFiniteNumber(cmcExchangeLiquidity.binanceVolumeShare),
|
|
4056
|
+
topExchangeVolumeShare: toFiniteNumber(
|
|
4057
|
+
cmcExchangeLiquidity.topExchangeVolumeShare
|
|
4058
|
+
),
|
|
4059
|
+
liquidityRegime: typeof cmcExchangeLiquidity.liquidityRegime === "string" ? cmcExchangeLiquidity.liquidityRegime : null
|
|
4060
|
+
};
|
|
3421
4061
|
};
|
|
3422
|
-
var
|
|
3423
|
-
|
|
3424
|
-
|
|
3425
|
-
|
|
4062
|
+
var buildCmcFearGreedContextFromSignal = (signal) => {
|
|
4063
|
+
const baseContext = toRecord(signal.additionalIndicators?.baseContext);
|
|
4064
|
+
const relative = toRecord(baseContext?.relative);
|
|
4065
|
+
const cmcFearGreed = toRecord(relative?.cmcFearGreed);
|
|
4066
|
+
if (!cmcFearGreed) {
|
|
4067
|
+
return {
|
|
4068
|
+
source: null,
|
|
4069
|
+
available: false,
|
|
4070
|
+
interval: null,
|
|
4071
|
+
asOfTs: null,
|
|
4072
|
+
stale: null,
|
|
4073
|
+
value: null,
|
|
4074
|
+
valueChange24h: null,
|
|
4075
|
+
valueChange7d: null,
|
|
4076
|
+
classification: null,
|
|
4077
|
+
sentimentRegime: null
|
|
4078
|
+
};
|
|
3426
4079
|
}
|
|
3427
|
-
|
|
3428
|
-
|
|
3429
|
-
|
|
4080
|
+
return {
|
|
4081
|
+
source: String(cmcFearGreed.source ?? ""),
|
|
4082
|
+
available: true,
|
|
4083
|
+
interval: typeof cmcFearGreed.interval === "string" ? cmcFearGreed.interval : null,
|
|
4084
|
+
asOfTs: toFiniteNumber(cmcFearGreed.asOfTs),
|
|
4085
|
+
stale: typeof cmcFearGreed.stale === "boolean" ? cmcFearGreed.stale : null,
|
|
4086
|
+
value: toFiniteNumber(cmcFearGreed.value),
|
|
4087
|
+
valueChange24h: toFiniteNumber(cmcFearGreed.valueChange24h),
|
|
4088
|
+
valueChange7d: toFiniteNumber(cmcFearGreed.valueChange7d),
|
|
4089
|
+
classification: typeof cmcFearGreed.classification === "string" ? cmcFearGreed.classification : null,
|
|
4090
|
+
sentimentRegime: typeof cmcFearGreed.sentimentRegime === "string" ? cmcFearGreed.sentimentRegime : null
|
|
4091
|
+
};
|
|
3430
4092
|
};
|
|
3431
|
-
var
|
|
3432
|
-
|
|
3433
|
-
|
|
4093
|
+
var buildCmcIndexesContextFromSignal = (signal) => {
|
|
4094
|
+
const baseContext = toRecord(signal.additionalIndicators?.baseContext);
|
|
4095
|
+
const relative = toRecord(baseContext?.relative);
|
|
4096
|
+
const cmcIndexes = toRecord(relative?.cmcIndexes);
|
|
4097
|
+
if (!cmcIndexes) {
|
|
4098
|
+
return {
|
|
4099
|
+
source: null,
|
|
4100
|
+
available: false,
|
|
4101
|
+
interval: null,
|
|
4102
|
+
asOfTs: null,
|
|
4103
|
+
stale: null,
|
|
4104
|
+
cmc100Value: null,
|
|
4105
|
+
cmc100Change24hPct: null,
|
|
4106
|
+
cmc100TopConstituentSymbol: null,
|
|
4107
|
+
cmc100TopConstituentWeightPct: null,
|
|
4108
|
+
cmc20Value: null,
|
|
4109
|
+
cmc20Change24hPct: null,
|
|
4110
|
+
cmc20TopConstituentSymbol: null,
|
|
4111
|
+
cmc20TopConstituentWeightPct: null,
|
|
4112
|
+
cmc20ToCmc100Ratio: null,
|
|
4113
|
+
cmc20ToCmc100RatioChange24hPct: null,
|
|
4114
|
+
indexRegime: null
|
|
4115
|
+
};
|
|
3434
4116
|
}
|
|
3435
|
-
|
|
3436
|
-
|
|
4117
|
+
return {
|
|
4118
|
+
source: String(cmcIndexes.source ?? ""),
|
|
4119
|
+
available: true,
|
|
4120
|
+
interval: typeof cmcIndexes.interval === "string" ? cmcIndexes.interval : null,
|
|
4121
|
+
asOfTs: toFiniteNumber(cmcIndexes.asOfTs),
|
|
4122
|
+
stale: typeof cmcIndexes.stale === "boolean" ? cmcIndexes.stale : null,
|
|
4123
|
+
cmc100Value: toFiniteNumber(cmcIndexes.cmc100Value),
|
|
4124
|
+
cmc100Change24hPct: toFiniteNumber(cmcIndexes.cmc100Change24hPct),
|
|
4125
|
+
cmc100TopConstituentSymbol: typeof cmcIndexes.cmc100TopConstituentSymbol === "string" ? cmcIndexes.cmc100TopConstituentSymbol : null,
|
|
4126
|
+
cmc100TopConstituentWeightPct: toFiniteNumber(
|
|
4127
|
+
cmcIndexes.cmc100TopConstituentWeightPct
|
|
4128
|
+
),
|
|
4129
|
+
cmc20Value: toFiniteNumber(cmcIndexes.cmc20Value),
|
|
4130
|
+
cmc20Change24hPct: toFiniteNumber(cmcIndexes.cmc20Change24hPct),
|
|
4131
|
+
cmc20TopConstituentSymbol: typeof cmcIndexes.cmc20TopConstituentSymbol === "string" ? cmcIndexes.cmc20TopConstituentSymbol : null,
|
|
4132
|
+
cmc20TopConstituentWeightPct: toFiniteNumber(
|
|
4133
|
+
cmcIndexes.cmc20TopConstituentWeightPct
|
|
4134
|
+
),
|
|
4135
|
+
cmc20ToCmc100Ratio: toFiniteNumber(cmcIndexes.cmc20ToCmc100Ratio),
|
|
4136
|
+
cmc20ToCmc100RatioChange24hPct: toFiniteNumber(
|
|
4137
|
+
cmcIndexes.cmc20ToCmc100RatioChange24hPct
|
|
4138
|
+
),
|
|
4139
|
+
indexRegime: typeof cmcIndexes.indexRegime === "string" ? cmcIndexes.indexRegime : null
|
|
4140
|
+
};
|
|
4141
|
+
};
|
|
4142
|
+
var buildReferenceTradeFlowContextFromSignal = (signal) => {
|
|
4143
|
+
const baseContext = toRecord(signal.additionalIndicators?.baseContext);
|
|
4144
|
+
const relative = toRecord(baseContext?.relative);
|
|
4145
|
+
const refs = toRecord(relative?.referenceTradeFlow);
|
|
4146
|
+
const primaryReferenceSymbol = typeof refs?.primaryReferenceSymbol === "string" ? refs.primaryReferenceSymbol : null;
|
|
4147
|
+
const tradeFlowBySymbol = toRecord(refs?.tradeFlowBySymbol);
|
|
4148
|
+
const primaryTradeFlow = primaryReferenceSymbol != null ? toRecord(tradeFlowBySymbol?.[primaryReferenceSymbol]) : null;
|
|
4149
|
+
if (!refs) {
|
|
4150
|
+
return {
|
|
4151
|
+
source: null,
|
|
4152
|
+
available: false,
|
|
4153
|
+
primaryReferenceSymbol: null,
|
|
4154
|
+
referenceSymbols: [],
|
|
4155
|
+
primaryTradeFlowBuyPressurePct: null,
|
|
4156
|
+
primaryTradeFlowStale: null
|
|
4157
|
+
};
|
|
4158
|
+
}
|
|
4159
|
+
return {
|
|
4160
|
+
source: String(refs.source ?? ""),
|
|
4161
|
+
available: true,
|
|
4162
|
+
primaryReferenceSymbol,
|
|
4163
|
+
referenceSymbols: Array.isArray(refs.referenceSymbols) ? refs.referenceSymbols.map(String) : [],
|
|
4164
|
+
primaryTradeFlowBuyPressurePct: toFiniteNumber(
|
|
4165
|
+
primaryTradeFlow?.buyPressurePct
|
|
4166
|
+
),
|
|
4167
|
+
primaryTradeFlowStale: typeof primaryTradeFlow?.stale === "boolean" ? primaryTradeFlow.stale : null
|
|
4168
|
+
};
|
|
3437
4169
|
};
|
|
3438
|
-
var
|
|
3439
|
-
{
|
|
3440
|
-
|
|
3441
|
-
|
|
3442
|
-
|
|
3443
|
-
|
|
3444
|
-
|
|
3445
|
-
|
|
3446
|
-
|
|
3447
|
-
|
|
3448
|
-
|
|
3449
|
-
|
|
3450
|
-
|
|
3451
|
-
|
|
3452
|
-
|
|
3453
|
-
|
|
4170
|
+
var buildAiMarketContext = (signal) => ({
|
|
4171
|
+
execution: {
|
|
4172
|
+
binanceCoinbaseSpread: buildSpreadContextFromSignal(signal)
|
|
4173
|
+
},
|
|
4174
|
+
participation: {
|
|
4175
|
+
trueDelta: buildTrueDeltaContextFromSignal(signal),
|
|
4176
|
+
tradeFlow: buildTradeFlowContextFromSignal(signal)
|
|
4177
|
+
},
|
|
4178
|
+
relative: {
|
|
4179
|
+
marketBreadth: buildMarketBreadthContextFromSignal(signal),
|
|
4180
|
+
marketBreadths: buildMarketBreadthsContextFromSignal(signal),
|
|
4181
|
+
targetVsBtc: buildTargetVsBtcContextFromSignal(signal),
|
|
4182
|
+
targetVsEth: buildTargetVsEthContextFromSignal(signal),
|
|
4183
|
+
btcAltRegime: buildBtcAltRegimeContextFromSignal(signal),
|
|
4184
|
+
cmcGlobal: buildCmcGlobalContextFromSignal(signal),
|
|
4185
|
+
cmcReferenceAssets: buildCmcReferenceAssetsContextFromSignal(signal),
|
|
4186
|
+
cmcExchangeLiquidity: buildCmcExchangeLiquidityContextFromSignal(signal),
|
|
4187
|
+
cmcFearGreed: buildCmcFearGreedContextFromSignal(signal),
|
|
4188
|
+
cmcIndexes: buildCmcIndexesContextFromSignal(signal),
|
|
4189
|
+
referenceTradeFlow: buildReferenceTradeFlowContextFromSignal(signal)
|
|
3454
4190
|
}
|
|
3455
|
-
);
|
|
4191
|
+
});
|
|
3456
4192
|
|
|
3457
4193
|
// src/strategy/policyProfiles.ts
|
|
3458
4194
|
var profileMatches = (profile, universe, assetClass) => {
|
|
@@ -3554,12 +4290,61 @@ var buildBaseAiPayload = (signal) => {
|
|
|
3554
4290
|
};
|
|
3555
4291
|
};
|
|
3556
4292
|
var defaultAiAdapter = {};
|
|
4293
|
+
var tickerProfileFeatureIds = new Set(
|
|
4294
|
+
import_aiFeatures.TICKER_PROFILE_AI_FEATURES.map(({ id }) => id)
|
|
4295
|
+
);
|
|
3557
4296
|
var getStrategyAiAdapter = (strategy, profileId) => getStrategyProfileAiAdapter(getStrategyManifest(strategy), profileId) ?? defaultAiAdapter;
|
|
3558
4297
|
var getSignalAiAdapter = (signal) => getStrategyAiAdapter(signal.strategy, signal.policyProfileId);
|
|
3559
4298
|
var buildAiPayloadByStrategy = (signal) => {
|
|
3560
4299
|
const basePayload = buildBaseAiPayload(signal);
|
|
3561
4300
|
const adapter = getSignalAiAdapter(signal);
|
|
3562
|
-
|
|
4301
|
+
const payload = adapter.buildPayload?.({ signal, basePayload }) ?? basePayload;
|
|
4302
|
+
const custom = getStrategyManifest(signal.strategy)?.aiFeatures ?? [];
|
|
4303
|
+
const frozen = signal.aiFeatureReplay?.snapshot;
|
|
4304
|
+
if (frozen) (0, import_aiFeatures.validateAiFeatureSnapshot)(frozen);
|
|
4305
|
+
const tickerProfile = signal.additionalIndicators?.tickerProfile ?? signal.additionalIndicators?.baseContext?.tickerProfile;
|
|
4306
|
+
const hasProfile = Boolean(
|
|
4307
|
+
tickerProfile || frozen?.definitions.some(({ id }) => tickerProfileFeatureIds.has(id))
|
|
4308
|
+
);
|
|
4309
|
+
const indicatorFeatures = (0, import_indicators2.getRegisteredIndicatorEntries)(
|
|
4310
|
+
getTradejsProjectCwd()
|
|
4311
|
+
).flatMap((entry) => entry.indicator.enabled ? entry.aiFeatures ?? [] : []);
|
|
4312
|
+
const hasPortfolio = Boolean(
|
|
4313
|
+
signal.additionalIndicators?.portfolioContext || frozen?.definitions.some(
|
|
4314
|
+
({ id }) => import_aiFeatures.PORTFOLIO_AI_FEATURES.some((d) => d.id === id)
|
|
4315
|
+
)
|
|
4316
|
+
);
|
|
4317
|
+
const definitions = [
|
|
4318
|
+
...hasPortfolio ? import_aiFeatures.PORTFOLIO_AI_FEATURES : [],
|
|
4319
|
+
...hasProfile ? import_aiFeatures.TICKER_PROFILE_AI_FEATURES : [],
|
|
4320
|
+
...indicatorFeatures,
|
|
4321
|
+
...custom
|
|
4322
|
+
];
|
|
4323
|
+
if (!definitions.length && !signal.aiFeatureReplay?.snapshot) return payload;
|
|
4324
|
+
(0, import_aiFeatures.validateAiFeatureDefinitions)(definitions);
|
|
4325
|
+
if (signal.aiFeatureReplay && !frozen) {
|
|
4326
|
+
throw new Error(
|
|
4327
|
+
"Dataset has no frozen AI features; regenerate the decision-time export"
|
|
4328
|
+
);
|
|
4329
|
+
}
|
|
4330
|
+
if (frozen && JSON.stringify(frozen.definitions) !== JSON.stringify(definitions)) {
|
|
4331
|
+
throw new Error(
|
|
4332
|
+
"AI feature definition changed; regenerate the decision-time export"
|
|
4333
|
+
);
|
|
4334
|
+
}
|
|
4335
|
+
return {
|
|
4336
|
+
...payload,
|
|
4337
|
+
featureSnapshot: frozen ?? (0, import_aiFeatures.projectAiFeatures)(definitions, {
|
|
4338
|
+
signal: tickerProfile ? {
|
|
4339
|
+
...signal,
|
|
4340
|
+
additionalIndicators: {
|
|
4341
|
+
...signal.additionalIndicators,
|
|
4342
|
+
tickerProfile
|
|
4343
|
+
}
|
|
4344
|
+
} : signal,
|
|
4345
|
+
payload
|
|
4346
|
+
})
|
|
4347
|
+
};
|
|
3563
4348
|
};
|
|
3564
4349
|
var buildAiSystemPromptAddonByStrategy = (signal) => getSignalAiAdapter(signal).buildSystemPromptAddon?.({ signal }) ?? "";
|
|
3565
4350
|
var buildAiHumanPromptAddonByStrategy = (signal, payload) => getSignalAiAdapter(signal).buildHumanPromptAddon?.({
|
|
@@ -3967,358 +4752,110 @@ var runAiPrompt = async ({ systemPrompt, humanPrompt }, options = {}) => {
|
|
|
3967
4752
|
);
|
|
3968
4753
|
const response = await invokeAiPromptChat({
|
|
3969
4754
|
userName: options.userName,
|
|
3970
|
-
model: options.model,
|
|
3971
|
-
messages: [
|
|
3972
|
-
{ role: "system", content: systemPrompt },
|
|
3973
|
-
{
|
|
3974
|
-
role: "system",
|
|
3975
|
-
content: `Write all user-visible text fields in ${responseLanguage}. Keep field names and JSON syntax unchanged.`
|
|
3976
|
-
},
|
|
3977
|
-
{ role: "user", content: humanPrompt }
|
|
3978
|
-
]
|
|
3979
|
-
});
|
|
3980
|
-
const parsed = parseAIResponse(response.content);
|
|
3981
|
-
const normalized = normalizeAnalysis(parsed);
|
|
3982
|
-
if (!options.signal) {
|
|
3983
|
-
return normalized;
|
|
3984
|
-
}
|
|
3985
|
-
return postProcessAiAnalysisByStrategy(
|
|
3986
|
-
options.signal,
|
|
3987
|
-
normalized,
|
|
3988
|
-
options.payload
|
|
3989
|
-
);
|
|
3990
|
-
};
|
|
3991
|
-
var runAiPromptLocal = async (signal, options = {}) => {
|
|
3992
|
-
const payload = options.payload ?? buildAiPayload(signal);
|
|
3993
|
-
const gateContext = getDeterministicAiGateContext(payload);
|
|
3994
|
-
const signalDirection = getSignalDirection(signal);
|
|
3995
|
-
const deterministicQuality = getDeterministicQuality(gateContext);
|
|
3996
|
-
const approvalAllowedNow = typeof gateContext?.approvalAllowedNow === "boolean" ? gateContext.approvalAllowedNow : deterministicQuality >= 4;
|
|
3997
|
-
return postProcessLocalAiAnalysisByStrategy(
|
|
3998
|
-
signal,
|
|
3999
|
-
{
|
|
4000
|
-
direction: approvalAllowedNow ? signalDirection : null,
|
|
4001
|
-
quality: deterministicQuality,
|
|
4002
|
-
needRetest: !approvalAllowedNow,
|
|
4003
|
-
retestPrice: null,
|
|
4004
|
-
takeProfitPrice: approvalAllowedNow ? signal.prices?.takeProfitPrice ?? null : null,
|
|
4005
|
-
stopLossPrice: approvalAllowedNow ? signal.prices?.stopLossPrice ?? null : null
|
|
4006
|
-
},
|
|
4007
|
-
payload
|
|
4008
|
-
);
|
|
4009
|
-
};
|
|
4010
|
-
var askAI = async (signal, options = {}) => {
|
|
4011
|
-
const { symbol } = signal;
|
|
4012
|
-
const payload = buildAiPayload(signal);
|
|
4013
|
-
const content = await runAiPrompt(
|
|
4014
|
-
{
|
|
4015
|
-
systemPrompt: buildAiSystemPrompt(signal),
|
|
4016
|
-
humanPrompt: buildAiHumanPrompt(signal, payload)
|
|
4017
|
-
},
|
|
4018
|
-
{
|
|
4019
|
-
...options,
|
|
4020
|
-
signal,
|
|
4021
|
-
payload
|
|
4022
|
-
}
|
|
4023
|
-
);
|
|
4024
|
-
await (0, import_redis2.setData)(import_redis2.redisKeys.analysis(symbol, signal.signalId), content);
|
|
4025
|
-
return content;
|
|
4026
|
-
};
|
|
4027
|
-
|
|
4028
|
-
// src/strategyAdapters/ml.ts
|
|
4029
|
-
var defaultMlAdapter = {
|
|
4030
|
-
normalizeStrategyConfig: (strategyConfig) => strategyConfig
|
|
4031
|
-
};
|
|
4032
|
-
var getStrategyMlAdapter = (strategy, profileId) => {
|
|
4033
|
-
const strategyAdapter = getStrategyProfileMlAdapter(
|
|
4034
|
-
getStrategyManifest(strategy),
|
|
4035
|
-
profileId
|
|
4036
|
-
);
|
|
4037
|
-
if (!strategyAdapter) return defaultMlAdapter;
|
|
4038
|
-
return {
|
|
4039
|
-
...defaultMlAdapter,
|
|
4040
|
-
...strategyAdapter
|
|
4041
|
-
};
|
|
4042
|
-
};
|
|
4043
|
-
|
|
4044
|
-
// src/mlPayload.ts
|
|
4045
|
-
var normalizeStrategyConfig = (strategyConfig, strategyName, profileId) => {
|
|
4046
|
-
return getStrategyMlAdapter(
|
|
4047
|
-
strategyName,
|
|
4048
|
-
profileId
|
|
4049
|
-
).normalizeStrategyConfig?.(strategyConfig);
|
|
4050
|
-
};
|
|
4051
|
-
var buildMlPayload = (payload) => {
|
|
4052
|
-
const strategyName = payload.signal?.strategy ?? payload.context?.strategyName;
|
|
4053
|
-
const profileId = payload.signal?.policyProfileId;
|
|
4054
|
-
const mlAdapter = getStrategyMlAdapter(strategyName, profileId);
|
|
4055
|
-
const normalizedSignal = mlAdapter.normalizeSignal?.(payload.signal) ?? payload.signal;
|
|
4056
|
-
const nextSignal = {
|
|
4057
|
-
...normalizedSignal,
|
|
4058
|
-
indicators: {
|
|
4059
|
-
...normalizedSignal?.indicators ?? {}
|
|
4060
|
-
}
|
|
4061
|
-
};
|
|
4062
|
-
const nextContext = payload.context ? {
|
|
4063
|
-
...payload.context,
|
|
4064
|
-
strategyConfig: normalizeStrategyConfig(
|
|
4065
|
-
payload.context.strategyConfig,
|
|
4066
|
-
strategyName,
|
|
4067
|
-
profileId
|
|
4068
|
-
)
|
|
4069
|
-
} : void 0;
|
|
4070
|
-
return {
|
|
4071
|
-
signal: nextSignal,
|
|
4072
|
-
context: nextContext
|
|
4073
|
-
};
|
|
4074
|
-
};
|
|
4075
|
-
|
|
4076
|
-
// src/runtimeJournal.ts
|
|
4077
|
-
var import_node_crypto = require("crypto");
|
|
4078
|
-
var import_constants3 = require("@tradejs/core/constants");
|
|
4079
|
-
var import_time3 = require("@tradejs/core/time");
|
|
4080
|
-
var import_trade = require("@tradejs/core/trade");
|
|
4081
|
-
var import_logger6 = require("@tradejs/infra/logger");
|
|
4082
|
-
var import_redis3 = require("@tradejs/infra/redis");
|
|
4083
|
-
var now = () => Date.now();
|
|
4084
|
-
var toRandomOrderSuffix = () => (0, import_node_crypto.randomUUID)().replace(/-/g, "").slice(0, 12).toLowerCase();
|
|
4085
|
-
var calculateClosedPnl = ({
|
|
4086
|
-
direction,
|
|
4087
|
-
entryPrice,
|
|
4088
|
-
exitPrice,
|
|
4089
|
-
qty
|
|
4090
|
-
}) => {
|
|
4091
|
-
const pnl = direction === "LONG" ? (exitPrice - entryPrice) * qty : (entryPrice - exitPrice) * qty;
|
|
4092
|
-
return Number.isFinite(pnl) ? pnl : null;
|
|
4093
|
-
};
|
|
4094
|
-
var createRuntimeOrderId = (strategy) => {
|
|
4095
|
-
const prefix = (0, import_trade.createRuntimeOrderLinkPrefix)(strategy);
|
|
4096
|
-
if (prefix === "tjs-") {
|
|
4097
|
-
return `tjs-${(0, import_node_crypto.randomUUID)().replace(/-/g, "").slice(0, 24).toLowerCase()}`;
|
|
4098
|
-
}
|
|
4099
|
-
return `${prefix}${toRandomOrderSuffix()}`;
|
|
4100
|
-
};
|
|
4101
|
-
var recordRuntimeTradeOpen = async (params) => {
|
|
4102
|
-
const { userName } = params;
|
|
4103
|
-
if (!userName) {
|
|
4104
|
-
return null;
|
|
4105
|
-
}
|
|
4106
|
-
const record = {
|
|
4107
|
-
...params,
|
|
4108
|
-
entryCount: 1,
|
|
4109
|
-
lastEntryPrice: params.entryPrice,
|
|
4110
|
-
lastEntryQty: params.qty,
|
|
4111
|
-
lastEntryTimestamp: params.entryTimestamp,
|
|
4112
|
-
status: "active",
|
|
4113
|
-
currentPrice: params.entryPrice,
|
|
4114
|
-
currentPnl: 0,
|
|
4115
|
-
closedPnl: null,
|
|
4116
|
-
exitPrice: null,
|
|
4117
|
-
exitTimestamp: null,
|
|
4118
|
-
lastSyncedAt: now()
|
|
4119
|
-
};
|
|
4120
|
-
const dayKey = (0, import_time3.getRuntimeStorageDayKey)(record.entryTimestamp);
|
|
4121
|
-
const runtimeScopeId = record.deploymentId ?? record.accountId;
|
|
4122
|
-
try {
|
|
4123
|
-
await Promise.all([
|
|
4124
|
-
(0, import_redis3.setData)(import_redis3.redisKeys.runtimeTrade(userName, record.orderId), record, {
|
|
4125
|
-
expire: 0
|
|
4126
|
-
}),
|
|
4127
|
-
(0, import_redis3.setHashJsonField)(
|
|
4128
|
-
import_redis3.redisKeys.runtimeTradeBucket(userName, dayKey),
|
|
4129
|
-
record.orderId,
|
|
4130
|
-
record,
|
|
4131
|
-
{ expire: 0 }
|
|
4132
|
-
),
|
|
4133
|
-
(0, import_redis3.setData)(
|
|
4134
|
-
import_redis3.redisKeys.runtimeActiveTrade(userName, record.symbol, runtimeScopeId),
|
|
4135
|
-
{ orderId: record.orderId },
|
|
4136
|
-
{ expire: 0 }
|
|
4137
|
-
)
|
|
4138
|
-
]);
|
|
4139
|
-
} catch (error) {
|
|
4140
|
-
import_logger6.logger.error(
|
|
4141
|
-
"runtime trade open journal failed: %s %s",
|
|
4142
|
-
record.symbol,
|
|
4143
|
-
error?.message || String(error)
|
|
4144
|
-
);
|
|
4145
|
-
}
|
|
4146
|
-
return record;
|
|
4147
|
-
};
|
|
4148
|
-
var recordRuntimeTradeIncrease = async (params) => {
|
|
4149
|
-
const {
|
|
4150
|
-
userName,
|
|
4151
|
-
strategy,
|
|
4152
|
-
symbol,
|
|
4153
|
-
direction,
|
|
4154
|
-
resultingQty,
|
|
4155
|
-
resultingEntryPrice,
|
|
4156
|
-
addedQty,
|
|
4157
|
-
addedEntryPrice,
|
|
4158
|
-
entryTimestamp,
|
|
4159
|
-
fee,
|
|
4160
|
-
accountId,
|
|
4161
|
-
deploymentId
|
|
4162
|
-
} = params;
|
|
4163
|
-
if (!userName) {
|
|
4164
|
-
return null;
|
|
4165
|
-
}
|
|
4166
|
-
const existing = await getActiveRuntimeTrade({
|
|
4167
|
-
userName,
|
|
4168
|
-
symbol,
|
|
4169
|
-
accountId,
|
|
4170
|
-
deploymentId
|
|
4755
|
+
model: options.model,
|
|
4756
|
+
messages: [
|
|
4757
|
+
{ role: "system", content: systemPrompt },
|
|
4758
|
+
{
|
|
4759
|
+
role: "system",
|
|
4760
|
+
content: `Write all user-visible text fields in ${responseLanguage}. Keep field names and JSON syntax unchanged.`
|
|
4761
|
+
},
|
|
4762
|
+
{ role: "user", content: humanPrompt }
|
|
4763
|
+
]
|
|
4171
4764
|
});
|
|
4172
|
-
|
|
4173
|
-
|
|
4174
|
-
|
|
4175
|
-
|
|
4176
|
-
const openFee = (existing.openFee ?? existing.fee ?? 0) + addedFee;
|
|
4177
|
-
const totalFee = (existing.totalFee ?? existing.fee ?? 0) + addedFee;
|
|
4178
|
-
const next = {
|
|
4179
|
-
...existing,
|
|
4180
|
-
...params.orderLeverage ? { orderLeverage: params.orderLeverage } : {},
|
|
4181
|
-
qty: resultingQty,
|
|
4182
|
-
entryPrice: resultingEntryPrice,
|
|
4183
|
-
entryCount: Math.max(1, existing.entryCount ?? 1) + 1,
|
|
4184
|
-
lastEntryPrice: addedEntryPrice,
|
|
4185
|
-
lastEntryQty: addedQty,
|
|
4186
|
-
lastEntryTimestamp: entryTimestamp,
|
|
4187
|
-
currentPrice: resultingEntryPrice,
|
|
4188
|
-
currentPnl: 0,
|
|
4189
|
-
fee: openFee,
|
|
4190
|
-
openFee,
|
|
4191
|
-
totalFee,
|
|
4192
|
-
lastSyncedAt: now()
|
|
4193
|
-
};
|
|
4194
|
-
const dayKey = (0, import_time3.getRuntimeStorageDayKey)(existing.entryTimestamp);
|
|
4195
|
-
try {
|
|
4196
|
-
await Promise.all([
|
|
4197
|
-
(0, import_redis3.setData)(import_redis3.redisKeys.runtimeTrade(userName, existing.orderId), next, {
|
|
4198
|
-
expire: 0
|
|
4199
|
-
}),
|
|
4200
|
-
(0, import_redis3.setHashJsonField)(
|
|
4201
|
-
import_redis3.redisKeys.runtimeTradeBucket(userName, dayKey),
|
|
4202
|
-
existing.orderId,
|
|
4203
|
-
next,
|
|
4204
|
-
{ expire: 0 }
|
|
4205
|
-
)
|
|
4206
|
-
]);
|
|
4207
|
-
} catch (error) {
|
|
4208
|
-
import_logger6.logger.error(
|
|
4209
|
-
"runtime trade increase journal failed: %s %s",
|
|
4210
|
-
symbol,
|
|
4211
|
-
error?.message || String(error)
|
|
4212
|
-
);
|
|
4765
|
+
const parsed = parseAIResponse(response.content);
|
|
4766
|
+
const normalized = normalizeAnalysis(parsed);
|
|
4767
|
+
if (!options.signal) {
|
|
4768
|
+
return normalized;
|
|
4213
4769
|
}
|
|
4214
|
-
return
|
|
4770
|
+
return postProcessAiAnalysisByStrategy(
|
|
4771
|
+
options.signal,
|
|
4772
|
+
normalized,
|
|
4773
|
+
options.payload
|
|
4774
|
+
);
|
|
4215
4775
|
};
|
|
4216
|
-
var
|
|
4217
|
-
const
|
|
4218
|
-
|
|
4219
|
-
|
|
4220
|
-
|
|
4221
|
-
const
|
|
4222
|
-
|
|
4223
|
-
|
|
4776
|
+
var runAiPromptLocal = async (signal, options = {}) => {
|
|
4777
|
+
const payload = options.payload ?? buildAiPayload(signal);
|
|
4778
|
+
const gateContext = getDeterministicAiGateContext(payload);
|
|
4779
|
+
const signalDirection = getSignalDirection(signal);
|
|
4780
|
+
const deterministicQuality = getDeterministicQuality(gateContext);
|
|
4781
|
+
const approvalAllowedNow = typeof gateContext?.approvalAllowedNow === "boolean" ? gateContext.approvalAllowedNow : deterministicQuality >= 4;
|
|
4782
|
+
return postProcessLocalAiAnalysisByStrategy(
|
|
4783
|
+
signal,
|
|
4784
|
+
{
|
|
4785
|
+
direction: approvalAllowedNow ? signalDirection : null,
|
|
4786
|
+
quality: deterministicQuality,
|
|
4787
|
+
needRetest: !approvalAllowedNow,
|
|
4788
|
+
retestPrice: null,
|
|
4789
|
+
takeProfitPrice: approvalAllowedNow ? signal.prices?.takeProfitPrice ?? null : null,
|
|
4790
|
+
stopLossPrice: approvalAllowedNow ? signal.prices?.stopLossPrice ?? null : null
|
|
4791
|
+
},
|
|
4792
|
+
payload
|
|
4224
4793
|
);
|
|
4225
|
-
|
|
4226
|
-
|
|
4227
|
-
|
|
4228
|
-
|
|
4229
|
-
const
|
|
4230
|
-
|
|
4231
|
-
|
|
4794
|
+
};
|
|
4795
|
+
var askAI = async (signal, options = {}) => {
|
|
4796
|
+
const { symbol } = signal;
|
|
4797
|
+
const payload = buildAiPayload(signal);
|
|
4798
|
+
const content = await runAiPrompt(
|
|
4799
|
+
{
|
|
4800
|
+
systemPrompt: buildAiSystemPrompt(signal),
|
|
4801
|
+
humanPrompt: buildAiHumanPrompt(signal, payload)
|
|
4802
|
+
},
|
|
4803
|
+
{
|
|
4804
|
+
...options,
|
|
4805
|
+
signal,
|
|
4806
|
+
payload
|
|
4807
|
+
}
|
|
4232
4808
|
);
|
|
4233
|
-
|
|
4234
|
-
|
|
4235
|
-
import_redis3.redisKeys.runtimeActiveTrade(userName, symbol, deploymentId ?? accountId)
|
|
4236
|
-
);
|
|
4237
|
-
return null;
|
|
4238
|
-
}
|
|
4239
|
-
return existing;
|
|
4809
|
+
await (0, import_redis4.setData)(import_redis4.redisKeys.analysis(symbol, signal.signalId), content);
|
|
4810
|
+
return content;
|
|
4240
4811
|
};
|
|
4241
|
-
|
|
4242
|
-
|
|
4243
|
-
|
|
4244
|
-
|
|
4245
|
-
|
|
4246
|
-
|
|
4247
|
-
|
|
4248
|
-
|
|
4249
|
-
|
|
4250
|
-
|
|
4251
|
-
|
|
4252
|
-
|
|
4253
|
-
|
|
4254
|
-
|
|
4255
|
-
}
|
|
4256
|
-
|
|
4257
|
-
|
|
4258
|
-
|
|
4259
|
-
|
|
4260
|
-
|
|
4261
|
-
|
|
4262
|
-
|
|
4263
|
-
|
|
4264
|
-
|
|
4265
|
-
|
|
4266
|
-
|
|
4267
|
-
|
|
4268
|
-
|
|
4269
|
-
const
|
|
4270
|
-
const
|
|
4271
|
-
|
|
4272
|
-
|
|
4273
|
-
|
|
4274
|
-
|
|
4275
|
-
}
|
|
4276
|
-
const
|
|
4277
|
-
...
|
|
4278
|
-
|
|
4279
|
-
|
|
4280
|
-
|
|
4281
|
-
|
|
4282
|
-
|
|
4283
|
-
|
|
4284
|
-
|
|
4285
|
-
|
|
4812
|
+
|
|
4813
|
+
// src/strategyAdapters/ml.ts
|
|
4814
|
+
var defaultMlAdapter = {
|
|
4815
|
+
normalizeStrategyConfig: (strategyConfig) => strategyConfig
|
|
4816
|
+
};
|
|
4817
|
+
var getStrategyMlAdapter = (strategy, profileId) => {
|
|
4818
|
+
const strategyAdapter = getStrategyProfileMlAdapter(
|
|
4819
|
+
getStrategyManifest(strategy),
|
|
4820
|
+
profileId
|
|
4821
|
+
);
|
|
4822
|
+
if (!strategyAdapter) return defaultMlAdapter;
|
|
4823
|
+
return {
|
|
4824
|
+
...defaultMlAdapter,
|
|
4825
|
+
...strategyAdapter
|
|
4826
|
+
};
|
|
4827
|
+
};
|
|
4828
|
+
|
|
4829
|
+
// src/mlPayload.ts
|
|
4830
|
+
var normalizeStrategyConfig = (strategyConfig, strategyName, profileId) => {
|
|
4831
|
+
return getStrategyMlAdapter(
|
|
4832
|
+
strategyName,
|
|
4833
|
+
profileId
|
|
4834
|
+
).normalizeStrategyConfig?.(strategyConfig);
|
|
4835
|
+
};
|
|
4836
|
+
var buildMlPayload = (payload) => {
|
|
4837
|
+
const strategyName = payload.signal?.strategy ?? payload.context?.strategyName;
|
|
4838
|
+
const profileId = payload.signal?.policyProfileId;
|
|
4839
|
+
const mlAdapter = getStrategyMlAdapter(strategyName, profileId);
|
|
4840
|
+
const normalizedSignal = mlAdapter.normalizeSignal?.(payload.signal) ?? payload.signal;
|
|
4841
|
+
const nextSignal = {
|
|
4842
|
+
...normalizedSignal,
|
|
4843
|
+
indicators: {
|
|
4844
|
+
...normalizedSignal?.indicators ?? {}
|
|
4845
|
+
}
|
|
4846
|
+
};
|
|
4847
|
+
const nextContext = payload.context ? {
|
|
4848
|
+
...payload.context,
|
|
4849
|
+
strategyConfig: normalizeStrategyConfig(
|
|
4850
|
+
payload.context.strategyConfig,
|
|
4851
|
+
strategyName,
|
|
4852
|
+
profileId
|
|
4853
|
+
)
|
|
4854
|
+
} : void 0;
|
|
4855
|
+
return {
|
|
4856
|
+
signal: nextSignal,
|
|
4857
|
+
context: nextContext
|
|
4286
4858
|
};
|
|
4287
|
-
const dayKey = (0, import_time3.getRuntimeStorageDayKey)(existing.entryTimestamp);
|
|
4288
|
-
const closeDayKey = (0, import_time3.getRuntimeStorageDayKey)(next.exitTimestamp);
|
|
4289
|
-
try {
|
|
4290
|
-
await Promise.all([
|
|
4291
|
-
(0, import_redis3.setData)(import_redis3.redisKeys.runtimeTrade(userName, orderId), next, {
|
|
4292
|
-
expire: import_constants3.TTL_1M
|
|
4293
|
-
}),
|
|
4294
|
-
(0, import_redis3.setHashJsonField)(
|
|
4295
|
-
import_redis3.redisKeys.runtimeTradeBucket(userName, dayKey),
|
|
4296
|
-
orderId,
|
|
4297
|
-
next,
|
|
4298
|
-
{ expire: import_constants3.TTL_1M }
|
|
4299
|
-
),
|
|
4300
|
-
(0, import_redis3.setHashJsonField)(
|
|
4301
|
-
import_redis3.redisKeys.runtimeClosedTradeBucket(userName, closeDayKey),
|
|
4302
|
-
orderId,
|
|
4303
|
-
next,
|
|
4304
|
-
{ expire: import_constants3.TTL_1M }
|
|
4305
|
-
),
|
|
4306
|
-
(0, import_redis3.delKey)(
|
|
4307
|
-
import_redis3.redisKeys.runtimeActiveTrade(
|
|
4308
|
-
userName,
|
|
4309
|
-
symbol,
|
|
4310
|
-
deploymentId ?? accountId
|
|
4311
|
-
)
|
|
4312
|
-
)
|
|
4313
|
-
]);
|
|
4314
|
-
} catch (error) {
|
|
4315
|
-
import_logger6.logger.error(
|
|
4316
|
-
"runtime trade close journal failed: %s %s",
|
|
4317
|
-
symbol,
|
|
4318
|
-
error?.message || String(error)
|
|
4319
|
-
);
|
|
4320
|
-
}
|
|
4321
|
-
return next;
|
|
4322
4859
|
};
|
|
4323
4860
|
|
|
4324
4861
|
// src/strategyHelpers/marketContextStages.ts
|
|
@@ -4330,7 +4867,7 @@ var import_logger7 = require("@tradejs/infra/logger");
|
|
|
4330
4867
|
var import_strategies = require("@tradejs/core/strategies");
|
|
4331
4868
|
|
|
4332
4869
|
// src/binanceBreadthUniverses.ts
|
|
4333
|
-
var
|
|
4870
|
+
var import_node_crypto3 = require("crypto");
|
|
4334
4871
|
|
|
4335
4872
|
// src/config/binanceBreadthUniverses.json
|
|
4336
4873
|
var binanceBreadthUniverses_default = {
|
|
@@ -4569,7 +5106,7 @@ var BINANCE_BREADTH_UNIVERSE_KEYS = [
|
|
|
4569
5106
|
"top50",
|
|
4570
5107
|
"top100"
|
|
4571
5108
|
];
|
|
4572
|
-
var fingerprint = (value, length) => (0,
|
|
5109
|
+
var fingerprint = (value, length) => (0, import_node_crypto3.createHash)("sha256").update(JSON.stringify(value)).digest("hex").slice(0, length);
|
|
4573
5110
|
var normalizeSymbols = (symbols) => symbols.map((symbol) => symbol.trim().toUpperCase()).filter(Boolean);
|
|
4574
5111
|
var buildBinanceBreadthUniverseSnapshot = ({
|
|
4575
5112
|
rankedSymbols,
|
|
@@ -5593,7 +6130,7 @@ var enrichSignalWithCoinMarketCapContext = async (params) => {
|
|
|
5593
6130
|
};
|
|
5594
6131
|
|
|
5595
6132
|
// src/strategyHelpers/derivativesContext.ts
|
|
5596
|
-
var
|
|
6133
|
+
var import_indicators3 = require("@tradejs/core/indicators");
|
|
5597
6134
|
var import_data3 = require("@tradejs/core/data");
|
|
5598
6135
|
var import_strategies3 = require("@tradejs/core/strategies");
|
|
5599
6136
|
var import_constants4 = require("@tradejs/core/constants");
|
|
@@ -5627,7 +6164,7 @@ var parseLookbackMs = () => {
|
|
|
5627
6164
|
};
|
|
5628
6165
|
var withHourlyFallbackRows = (rowsByInterval) => ({
|
|
5629
6166
|
"15m": rowsByInterval["15m"] ?? [],
|
|
5630
|
-
"1h": (0,
|
|
6167
|
+
"1h": (0, import_indicators3.buildCoinalyzeHourlyRowsWithFallback)({
|
|
5631
6168
|
rows15m: rowsByInterval["15m"],
|
|
5632
6169
|
fallbackRows1h: rowsByInterval["1h"]
|
|
5633
6170
|
})
|
|
@@ -5755,7 +6292,7 @@ var enrichSignalWithDerivativesContext = async (params) => {
|
|
|
5755
6292
|
const targetSymbol = normalizeSymbol(signal.symbol);
|
|
5756
6293
|
const lookbackMs = parseLookbackMs();
|
|
5757
6294
|
const decisionTimeMs = signal.timestamp + (0, import_data3.intervalToMs)(signal.interval);
|
|
5758
|
-
const derivativesEndMs = (0,
|
|
6295
|
+
const derivativesEndMs = (0, import_indicators3.getLastClosedDerivativesBarStartMs)(
|
|
5759
6296
|
decisionTimeMs,
|
|
5760
6297
|
"15m"
|
|
5761
6298
|
);
|
|
@@ -5770,7 +6307,7 @@ var enrichSignalWithDerivativesContext = async (params) => {
|
|
|
5770
6307
|
});
|
|
5771
6308
|
return [
|
|
5772
6309
|
symbol,
|
|
5773
|
-
(0,
|
|
6310
|
+
(0, import_indicators3.buildDerivativesContext)({
|
|
5774
6311
|
symbol,
|
|
5775
6312
|
direction: signal.direction,
|
|
5776
6313
|
timestamp: derivativesEndMs,
|
|
@@ -5797,7 +6334,7 @@ var enrichSignalWithDerivativesContext = async (params) => {
|
|
|
5797
6334
|
lookbackMs,
|
|
5798
6335
|
...params.abortSignal ? { signal: params.abortSignal } : {}
|
|
5799
6336
|
});
|
|
5800
|
-
const context = (0,
|
|
6337
|
+
const context = (0, import_indicators3.buildDerivativesContext)({
|
|
5801
6338
|
symbol: targetSymbol,
|
|
5802
6339
|
direction: signal.direction,
|
|
5803
6340
|
timestamp: derivativesEndMs,
|
|
@@ -5845,7 +6382,7 @@ var import_hyperliquidWhales2 = require("@tradejs/infra/timescale/hyperliquidWha
|
|
|
5845
6382
|
var import_logger10 = require("@tradejs/infra/logger");
|
|
5846
6383
|
|
|
5847
6384
|
// src/hyperliquidWhaleUniverse.ts
|
|
5848
|
-
var
|
|
6385
|
+
var import_node_crypto4 = require("crypto");
|
|
5849
6386
|
|
|
5850
6387
|
// src/config/hyperliquidPerpUniverse.json
|
|
5851
6388
|
var hyperliquidPerpUniverse_default = {
|
|
@@ -6017,7 +6554,7 @@ var hyperliquidWhales_default = {
|
|
|
6017
6554
|
};
|
|
6018
6555
|
|
|
6019
6556
|
// src/hyperliquidWhaleUniverse.ts
|
|
6020
|
-
var fingerprint2 = (value) => (0,
|
|
6557
|
+
var fingerprint2 = (value) => (0, import_node_crypto4.createHash)("sha256").update(JSON.stringify(value)).digest("hex").slice(0, 16);
|
|
6021
6558
|
var validateUnique = (values, label) => {
|
|
6022
6559
|
if (new Set(values).size !== values.length) {
|
|
6023
6560
|
throw new Error(`Hyperliquid ${label} snapshot contains duplicates`);
|
|
@@ -6504,6 +7041,7 @@ var enrichSignalWithMarketContextStages = async ({
|
|
|
6504
7041
|
})
|
|
6505
7042
|
});
|
|
6506
7043
|
}
|
|
7044
|
+
await enrichSignalWithTickerProfile(signal);
|
|
6507
7045
|
const results = [];
|
|
6508
7046
|
for (const stage of stages) {
|
|
6509
7047
|
if (abortSignal?.aborted) break;
|
|
@@ -6551,12 +7089,12 @@ var findReplayAiAnalysis = ({
|
|
|
6551
7089
|
direction,
|
|
6552
7090
|
ai
|
|
6553
7091
|
}) => {
|
|
6554
|
-
const
|
|
6555
|
-
if (!Array.isArray(
|
|
7092
|
+
const snapshots2 = ai?.replayAnalyses;
|
|
7093
|
+
if (!Array.isArray(snapshots2) || !snapshots2.length) {
|
|
6556
7094
|
return void 0;
|
|
6557
7095
|
}
|
|
6558
7096
|
let best = null;
|
|
6559
|
-
for (const snapshot2 of
|
|
7097
|
+
for (const snapshot2 of snapshots2) {
|
|
6560
7098
|
if (snapshot2.symbol !== signal.symbol || snapshot2.direction !== direction || snapshot2.strategy && snapshot2.strategy !== signal.strategy) {
|
|
6561
7099
|
continue;
|
|
6562
7100
|
}
|
|
@@ -6716,7 +7254,9 @@ var executeEntryOrder = async ({
|
|
|
6716
7254
|
beforePlaceOrder,
|
|
6717
7255
|
recordRuntimeTrade = true,
|
|
6718
7256
|
leverage,
|
|
6719
|
-
executionLimits
|
|
7257
|
+
executionLimits,
|
|
7258
|
+
markPortfolioSubmitted,
|
|
7259
|
+
rejectPortfolioSubmission
|
|
6720
7260
|
}) => {
|
|
6721
7261
|
await beforePlaceOrder?.();
|
|
6722
7262
|
const previousPosition = positionIntent === "increase" ? await connector.getPosition(symbol) : null;
|
|
@@ -6789,7 +7329,13 @@ var executeEntryOrder = async ({
|
|
|
6789
7329
|
...typeof leverage === "number" && Number.isFinite(leverage) ? { leverage } : {},
|
|
6790
7330
|
orderId,
|
|
6791
7331
|
signal,
|
|
6792
|
-
...
|
|
7332
|
+
...rejectPortfolioSubmission ? { onRejected: rejectPortfolioSubmission } : {},
|
|
7333
|
+
...beforeSubmit || markPortfolioSubmitted ? {
|
|
7334
|
+
beforeSubmit: async (context) => {
|
|
7335
|
+
await beforeSubmit?.(context);
|
|
7336
|
+
await markPortfolioSubmitted?.(context.qty);
|
|
7337
|
+
}
|
|
7338
|
+
} : {}
|
|
6793
7339
|
});
|
|
6794
7340
|
const orderAckTime = Date.now();
|
|
6795
7341
|
const placedQty = typeof signal.orderQty === "number" && Number.isFinite(signal.orderQty) && signal.orderQty > 0 ? signal.orderQty : qty;
|
|
@@ -7777,7 +8323,7 @@ var handleProtectDecision = async ({
|
|
|
7777
8323
|
}
|
|
7778
8324
|
return decision.code;
|
|
7779
8325
|
};
|
|
7780
|
-
var
|
|
8326
|
+
var executeAdmittedEntryDecision = async ({
|
|
7781
8327
|
connector,
|
|
7782
8328
|
symbol,
|
|
7783
8329
|
decision,
|
|
@@ -7791,7 +8337,9 @@ var executeEntryDecision = async ({
|
|
|
7791
8337
|
ai,
|
|
7792
8338
|
recordRuntimeJournal,
|
|
7793
8339
|
invokeStageHooks,
|
|
7794
|
-
notifyRuntimeError
|
|
8340
|
+
notifyRuntimeError,
|
|
8341
|
+
markPortfolioSubmitted,
|
|
8342
|
+
rejectPortfolioSubmission
|
|
7795
8343
|
}) => {
|
|
7796
8344
|
const signal = decision.signal;
|
|
7797
8345
|
const beforePlaceOrder = async () => {
|
|
@@ -7838,6 +8386,8 @@ var executeEntryDecision = async ({
|
|
|
7838
8386
|
...Number.isFinite(Number(hookCtx.strategyConfig.LEVERAGE)) ? { leverage: Number(hookCtx.strategyConfig.LEVERAGE) } : {},
|
|
7839
8387
|
signal,
|
|
7840
8388
|
beforePlaceOrder,
|
|
8389
|
+
markPortfolioSubmitted,
|
|
8390
|
+
rejectPortfolioSubmission,
|
|
7841
8391
|
recordRuntimeTrade: recordRuntimeJournal,
|
|
7842
8392
|
executionLimits: resolveEntryExecutionLimits(
|
|
7843
8393
|
hookCtx.strategyConfig,
|
|
@@ -7978,6 +8528,37 @@ var executeEntryDecision = async ({
|
|
|
7978
8528
|
}
|
|
7979
8529
|
return signal ?? decision.code;
|
|
7980
8530
|
};
|
|
8531
|
+
async function executeEntryDecision(params) {
|
|
8532
|
+
if (!params.portfolio) return executeAdmittedEntryDecision(params);
|
|
8533
|
+
const { decision, portfolio } = params;
|
|
8534
|
+
const signal = decision.signal;
|
|
8535
|
+
if (!signal || decision.orderPlan.positionIntent === "increase")
|
|
8536
|
+
return "PORTFOLIO_REQUIRES_NEW_SIGNAL_POSITION";
|
|
8537
|
+
signal.orderId ??= createRuntimeOrderId(signal.strategy);
|
|
8538
|
+
const result = await portfolio.submit(
|
|
8539
|
+
params.portfolioTime ?? decision.entryContext.timestamp,
|
|
8540
|
+
{
|
|
8541
|
+
id: signal.orderId,
|
|
8542
|
+
signalId: signal.signalId,
|
|
8543
|
+
strategy: signal.strategy,
|
|
8544
|
+
symbol: params.symbol,
|
|
8545
|
+
direction: decision.entryContext.direction,
|
|
8546
|
+
qty: decision.orderPlan.qty,
|
|
8547
|
+
timestamp: decision.entryContext.timestamp
|
|
8548
|
+
},
|
|
8549
|
+
(markPortfolioSubmitted, rejectPortfolioSubmission) => executeAdmittedEntryDecision({
|
|
8550
|
+
...params,
|
|
8551
|
+
markPortfolioSubmitted,
|
|
8552
|
+
rejectPortfolioSubmission
|
|
8553
|
+
})
|
|
8554
|
+
);
|
|
8555
|
+
if (result.reason) {
|
|
8556
|
+
signal.orderStatus = "failed";
|
|
8557
|
+
signal.orderFailureReason = result.reason;
|
|
8558
|
+
return signal;
|
|
8559
|
+
}
|
|
8560
|
+
return result.value;
|
|
8561
|
+
}
|
|
7981
8562
|
|
|
7982
8563
|
// src/strategyRuntime.ts
|
|
7983
8564
|
var cloneWithPropertyDescriptors = (value) => Object.create(
|
|
@@ -8005,6 +8586,7 @@ var createStrategyRuntime = ({
|
|
|
8005
8586
|
};
|
|
8006
8587
|
const creator = async ({
|
|
8007
8588
|
userName,
|
|
8589
|
+
connectorName,
|
|
8008
8590
|
config: baseConfig,
|
|
8009
8591
|
symbol,
|
|
8010
8592
|
universe: requestedUniverse,
|
|
@@ -8043,6 +8625,17 @@ var createStrategyRuntime = ({
|
|
|
8043
8625
|
const projectConfig = await loadTradejsConfig(projectRoot);
|
|
8044
8626
|
const projectHooks = projectConfig.hooks;
|
|
8045
8627
|
const env = String(config.ENV ?? "BACKTEST");
|
|
8628
|
+
const portfolio = getPortfolioRuntime({
|
|
8629
|
+
connectorName,
|
|
8630
|
+
value: baseConfig?.PORTFOLIO_POLICY ?? config.PORTFOLIO_POLICY,
|
|
8631
|
+
strategy: strategyName,
|
|
8632
|
+
connector,
|
|
8633
|
+
env,
|
|
8634
|
+
userName,
|
|
8635
|
+
accountId,
|
|
8636
|
+
deploymentId
|
|
8637
|
+
});
|
|
8638
|
+
const portfolioContext = portfolio ? (now2) => isRuntimeOrderExecutionEnabled({ config, env, connector }) ? portfolio.tick(now2) : portfolio.context(now2) : void 0;
|
|
8046
8639
|
const researchPolicyValue = baseConfig?.RESEARCH_ENTRY_POLICY ?? config.RESEARCH_ENTRY_POLICY;
|
|
8047
8640
|
const researchEntryPolicy = researchPolicyValue == null ? void 0 : parseResearchEntryPolicy(researchPolicyValue);
|
|
8048
8641
|
if (researchEntryPolicy && (env !== "BACKTEST" || Reflect.get(connector, "__tradejsTestConnector") !== true))
|
|
@@ -8341,6 +8934,20 @@ var createStrategyRuntime = ({
|
|
|
8341
8934
|
interval: decisionInterval
|
|
8342
8935
|
}) => {
|
|
8343
8936
|
if (!baseContext) return void 0;
|
|
8937
|
+
const profileSource = strategyManifest?.contextRequirements?.tickerProfiles;
|
|
8938
|
+
if (profileSource) {
|
|
8939
|
+
const profile = await loadTickerProfileContext({
|
|
8940
|
+
...profileSource,
|
|
8941
|
+
projectRoot,
|
|
8942
|
+
symbol: decisionSymbol,
|
|
8943
|
+
timestamp: candle.timestamp
|
|
8944
|
+
});
|
|
8945
|
+
if (!profile)
|
|
8946
|
+
throw new Error(
|
|
8947
|
+
"Required ticker profile is unavailable at decision time"
|
|
8948
|
+
);
|
|
8949
|
+
baseContext = withTickerProfile(baseContext, profile);
|
|
8950
|
+
}
|
|
8344
8951
|
if (!coreContextRequirements.has("hyperliquidWhales")) {
|
|
8345
8952
|
return baseContext;
|
|
8346
8953
|
}
|
|
@@ -8508,6 +9115,8 @@ var createStrategyRuntime = ({
|
|
|
8508
9115
|
runtime: pending2.runtime
|
|
8509
9116
|
});
|
|
8510
9117
|
return executeEntryDecision({
|
|
9118
|
+
portfolio,
|
|
9119
|
+
portfolioTime: env === "BACKTEST" || env === "PARITY" ? candle.timestamp : Date.now(),
|
|
8511
9120
|
connector,
|
|
8512
9121
|
symbol,
|
|
8513
9122
|
decision: pending2.decision,
|
|
@@ -8525,6 +9134,10 @@ var createStrategyRuntime = ({
|
|
|
8525
9134
|
});
|
|
8526
9135
|
};
|
|
8527
9136
|
const runWithDecisionOverride = async (candle, btcCandle, options = {}) => {
|
|
9137
|
+
if (portfolioContext)
|
|
9138
|
+
await portfolioContext(
|
|
9139
|
+
env === "BACKTEST" || env === "PARITY" ? candle.timestamp : Date.now()
|
|
9140
|
+
);
|
|
8528
9141
|
appendCurrentMarketData(candle, btcCandle, options.ethCandle);
|
|
8529
9142
|
const ethCandle = resolveEthCandle(candle, options.ethCandle);
|
|
8530
9143
|
indicatorsState.setCurrentBar(candle, btcCandle, ethCandle);
|
|
@@ -8695,6 +9308,13 @@ var createStrategyRuntime = ({
|
|
|
8695
9308
|
});
|
|
8696
9309
|
const signal = decision.signal;
|
|
8697
9310
|
if (signal) {
|
|
9311
|
+
if (portfolio)
|
|
9312
|
+
signal.additionalIndicators = {
|
|
9313
|
+
...signal.additionalIndicators,
|
|
9314
|
+
portfolioContext: await portfolioContext(
|
|
9315
|
+
env === "BACKTEST" || env === "PARITY" ? decision.entryContext.timestamp : Date.now()
|
|
9316
|
+
)
|
|
9317
|
+
};
|
|
8698
9318
|
if (runtimeLineage) signal.runtimeLineage = runtimeLineage;
|
|
8699
9319
|
if (universe) signal.universe = universe;
|
|
8700
9320
|
if (assetClass) signal.assetClass = assetClass;
|
|
@@ -8934,6 +9554,8 @@ var createStrategyRuntime = ({
|
|
|
8934
9554
|
});
|
|
8935
9555
|
}
|
|
8936
9556
|
return executeEntryDecision({
|
|
9557
|
+
portfolio,
|
|
9558
|
+
portfolioTime: env === "BACKTEST" || env === "PARITY" ? candle.timestamp : Date.now(),
|
|
8937
9559
|
connector,
|
|
8938
9560
|
symbol,
|
|
8939
9561
|
decision,
|
|
@@ -9045,7 +9667,7 @@ var normalizeForCanonicalJson = (value) => {
|
|
|
9045
9667
|
}
|
|
9046
9668
|
return value;
|
|
9047
9669
|
};
|
|
9048
|
-
var revision = (prefix, value) => `${prefix}:${(0,
|
|
9670
|
+
var revision = (prefix, value) => `${prefix}:${(0, import_node_crypto5.createHash)("sha256").update(JSON.stringify(normalizeForCanonicalJson(value))).digest("hex").slice(0, 16)}`;
|
|
9049
9671
|
var computeStrategyRevision = ({
|
|
9050
9672
|
strategyName,
|
|
9051
9673
|
strategyPackage,
|
|
@@ -9630,18 +10252,18 @@ var loadRuntimeTrades = async (userName, {
|
|
|
9630
10252
|
const [bucketTradeGroups, legacyKeys] = await Promise.all([
|
|
9631
10253
|
Promise.all(
|
|
9632
10254
|
dayKeys.map(
|
|
9633
|
-
(dayKey) => (0,
|
|
9634
|
-
|
|
10255
|
+
(dayKey) => (0, import_redis5.getHashJsonValues)(
|
|
10256
|
+
import_redis5.redisKeys.runtimeTradeBucket(userName, dayKey)
|
|
9635
10257
|
)
|
|
9636
10258
|
)
|
|
9637
10259
|
),
|
|
9638
|
-
(0,
|
|
10260
|
+
(0, import_redis5.getKeys)(import_redis5.redisKeys.runtimeTrades(userName))
|
|
9639
10261
|
]);
|
|
9640
10262
|
const legacyTradeKeys = legacyKeys.filter(
|
|
9641
|
-
(key) => !key.startsWith(
|
|
10263
|
+
(key) => !key.startsWith(import_redis5.redisKeys.runtimeTradeBuckets(userName))
|
|
9642
10264
|
);
|
|
9643
10265
|
const legacyTrades = await Promise.all(
|
|
9644
|
-
legacyTradeKeys.map((key) => (0,
|
|
10266
|
+
legacyTradeKeys.map((key) => (0, import_redis5.getData)(key, null))
|
|
9645
10267
|
);
|
|
9646
10268
|
const dedupedTrades = /* @__PURE__ */ new Map();
|
|
9647
10269
|
for (const trade of [...legacyTrades, ...bucketTradeGroups.flat()]) {
|
|
@@ -9728,8 +10350,8 @@ var loadExchangeRange = async ({
|
|
|
9728
10350
|
}
|
|
9729
10351
|
};
|
|
9730
10352
|
var loadActiveRuntimeOrderIds = async (userName) => {
|
|
9731
|
-
const keys = await (0,
|
|
9732
|
-
const refs = await Promise.all(keys.map((key) => (0,
|
|
10353
|
+
const keys = await (0, import_redis5.getKeys)(import_redis5.redisKeys.runtimeActiveTrades(userName));
|
|
10354
|
+
const refs = await Promise.all(keys.map((key) => (0, import_redis5.getData)(key, null)));
|
|
9733
10355
|
return new Set(
|
|
9734
10356
|
refs.map(
|
|
9735
10357
|
(ref) => typeof ref?.orderId === "string" && ref.orderId.trim() ? ref.orderId.trim() : null
|