@tradejs/node 3.1.29-beta.289 → 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 +2504 -1883
- package/dist/backtest.mjs +3 -2
- package/dist/{chunk-KYGJBMKE.mjs → chunk-HRON2EAI.mjs} +432 -40
- 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 +837 -775
- package/dist/cli.mjs +4 -3
- package/dist/diagnostics.js +2532 -1911
- package/dist/diagnostics.mjs +4 -3
- package/dist/mcp.d.mts +4 -2
- package/dist/mcp.d.ts +4 -2
- package/dist/mcp.js +2614 -1971
- package/dist/mcp.mjs +27 -5
- 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 +2091 -1470
- package/dist/registry.mjs +3 -2
- package/dist/runtimeDashboard.js +2255 -1632
- package/dist/runtimeDashboard.mjs +4 -3
- package/dist/runtimeStrategies.js +2417 -1796
- package/dist/runtimeStrategies.mjs +4 -3
- package/dist/strategies.d.mts +3 -1
- package/dist/strategies.d.ts +3 -1
- package/dist/strategies.js +2106 -1485
- 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,1379 +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
|
-
if (limits.maxLossValue != null && plannedLossValue > limits.maxLossValue * (1 + 1e-9))
|
|
2413
|
-
throw new EntrySubmissionValidationError(
|
|
2414
|
-
"ENTRY_RISK_EXCEEDS_MAX_LOSS_AT_SUBMISSION"
|
|
2415
|
-
);
|
|
2416
|
-
return { netRiskRatio, plannedLossValue };
|
|
2293
|
+
const pnl = direction === "LONG" ? (exitPrice - entryPrice) * qty : (entryPrice - exitPrice) * qty;
|
|
2294
|
+
return Number.isFinite(pnl) ? pnl : null;
|
|
2417
2295
|
};
|
|
2418
|
-
|
|
2419
|
-
|
|
2420
|
-
|
|
2421
|
-
|
|
2422
|
-
|
|
2423
|
-
|
|
2424
|
-
|
|
2425
|
-
|
|
2426
|
-
}
|
|
2427
|
-
if (
|
|
2428
|
-
return
|
|
2429
|
-
arrivalSnapshotTime: Date.now(),
|
|
2430
|
-
arrivalSource: "unavailable",
|
|
2431
|
-
bid: null,
|
|
2432
|
-
ask: null,
|
|
2433
|
-
arrivalMid: null,
|
|
2434
|
-
spreadBps: null
|
|
2435
|
-
};
|
|
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;
|
|
2436
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;
|
|
2437
2324
|
try {
|
|
2438
|
-
|
|
2439
|
-
|
|
2440
|
-
|
|
2441
|
-
|
|
2442
|
-
|
|
2443
|
-
|
|
2444
|
-
|
|
2445
|
-
|
|
2446
|
-
|
|
2447
|
-
|
|
2448
|
-
|
|
2449
|
-
|
|
2450
|
-
|
|
2451
|
-
|
|
2452
|
-
|
|
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
|
+
]);
|
|
2453
2341
|
} catch (error) {
|
|
2454
|
-
import_logger5.logger.
|
|
2455
|
-
"runtime
|
|
2456
|
-
symbol,
|
|
2342
|
+
import_logger5.logger.error(
|
|
2343
|
+
"runtime trade open journal failed: %s %s",
|
|
2344
|
+
record.symbol,
|
|
2457
2345
|
error?.message || String(error)
|
|
2458
2346
|
);
|
|
2459
|
-
return {
|
|
2460
|
-
arrivalSnapshotTime: Date.now(),
|
|
2461
|
-
arrivalSource: "top_of_book_error",
|
|
2462
|
-
bid: null,
|
|
2463
|
-
ask: null,
|
|
2464
|
-
arrivalMid: null,
|
|
2465
|
-
spreadBps: null
|
|
2466
|
-
};
|
|
2467
2347
|
}
|
|
2348
|
+
return record;
|
|
2468
2349
|
};
|
|
2469
|
-
var
|
|
2470
|
-
|
|
2471
|
-
|
|
2472
|
-
|
|
2473
|
-
|
|
2474
|
-
|
|
2475
|
-
|
|
2476
|
-
|
|
2477
|
-
|
|
2478
|
-
|
|
2479
|
-
|
|
2480
|
-
|
|
2481
|
-
|
|
2482
|
-
|
|
2483
|
-
|
|
2484
|
-
|
|
2485
|
-
|
|
2486
|
-
}
|
|
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;
|
|
2487
2367
|
}
|
|
2488
|
-
|
|
2489
|
-
|
|
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;
|
|
2490
2376
|
}
|
|
2491
|
-
|
|
2492
|
-
|
|
2493
|
-
|
|
2494
|
-
|
|
2495
|
-
|
|
2496
|
-
|
|
2497
|
-
|
|
2498
|
-
|
|
2499
|
-
|
|
2500
|
-
|
|
2501
|
-
|
|
2502
|
-
|
|
2503
|
-
|
|
2504
|
-
|
|
2505
|
-
|
|
2506
|
-
|
|
2507
|
-
|
|
2508
|
-
|
|
2509
|
-
arrivalSnapshotTime: Date.now(),
|
|
2510
|
-
arrivalSource: "simulation",
|
|
2511
|
-
bid: executionPrice,
|
|
2512
|
-
ask: executionPrice,
|
|
2513
|
-
arrivalMid: executionPrice,
|
|
2514
|
-
spreadBps: 0
|
|
2515
|
-
} : await getOrderArrivalSnapshot({
|
|
2516
|
-
connector,
|
|
2517
|
-
symbol,
|
|
2518
|
-
requireTimestamp: true
|
|
2519
|
-
});
|
|
2520
|
-
const age = Date.now() - snapshot2.arrivalSnapshotTime;
|
|
2521
|
-
if (snapshot2.bid == null || snapshot2.ask == null || !Number.isFinite(snapshot2.bid) || !Number.isFinite(snapshot2.ask) || snapshot2.bid <= 0 || snapshot2.ask < snapshot2.bid)
|
|
2522
|
-
throw new EntrySubmissionValidationError(
|
|
2523
|
-
"ENTRY_QUOTE_UNAVAILABLE_AT_SUBMISSION"
|
|
2524
|
-
);
|
|
2525
|
-
if (!Number.isFinite(age) || age < -MAX_ENTRY_QUOTE_AGE_MS || age > MAX_ENTRY_QUOTE_AGE_MS)
|
|
2526
|
-
throw new EntrySubmissionValidationError(
|
|
2527
|
-
"ENTRY_QUOTE_STALE_AT_SUBMISSION"
|
|
2528
|
-
);
|
|
2529
|
-
const price = direction === "LONG" ? snapshot2.ask : snapshot2.bid;
|
|
2530
|
-
validateEntryProtectionAtArrival({
|
|
2531
|
-
direction,
|
|
2532
|
-
signalPrice: price,
|
|
2533
|
-
bid: snapshot2.bid,
|
|
2534
|
-
ask: snapshot2.ask,
|
|
2535
|
-
arrivalMid: snapshot2.arrivalMid,
|
|
2536
|
-
takeProfits,
|
|
2537
|
-
stopLossPrice
|
|
2538
|
-
});
|
|
2539
|
-
const prices = takeProfits.map((tp) => tp.price);
|
|
2540
|
-
const economics2 = validateEntryExecution({
|
|
2541
|
-
direction,
|
|
2542
|
-
price,
|
|
2543
|
-
qty,
|
|
2544
|
-
stopLossPrice,
|
|
2545
|
-
takeProfitPrice: direction === "LONG" ? Math.max(...prices) : Math.min(...prices),
|
|
2546
|
-
limits
|
|
2547
|
-
});
|
|
2548
|
-
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()
|
|
2549
2395
|
};
|
|
2550
|
-
|
|
2551
|
-
|
|
2552
|
-
|
|
2553
|
-
|
|
2554
|
-
|
|
2555
|
-
|
|
2556
|
-
|
|
2557
|
-
|
|
2558
|
-
|
|
2559
|
-
|
|
2560
|
-
|
|
2561
|
-
|
|
2562
|
-
|
|
2563
|
-
|
|
2564
|
-
|
|
2565
|
-
|
|
2566
|
-
|
|
2567
|
-
|
|
2568
|
-
}
|
|
2569
|
-
return trimmed.map(
|
|
2570
|
-
(item) => item && typeof item === "object" ? trimSeriesDeep(item) : item
|
|
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)
|
|
2571
2414
|
);
|
|
2572
2415
|
}
|
|
2573
|
-
|
|
2574
|
-
|
|
2575
|
-
|
|
2576
|
-
|
|
2577
|
-
|
|
2578
|
-
|
|
2416
|
+
return next;
|
|
2417
|
+
};
|
|
2418
|
+
var getActiveRuntimeTrade = async (params) => {
|
|
2419
|
+
const { userName, symbol, accountId, deploymentId } = params;
|
|
2420
|
+
if (!userName) {
|
|
2421
|
+
return null;
|
|
2422
|
+
}
|
|
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) {
|
|
2429
|
+
return null;
|
|
2430
|
+
}
|
|
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)
|
|
2579
2438
|
);
|
|
2439
|
+
return null;
|
|
2580
2440
|
}
|
|
2581
|
-
return
|
|
2441
|
+
return existing;
|
|
2582
2442
|
};
|
|
2583
|
-
var
|
|
2584
|
-
const
|
|
2585
|
-
|
|
2586
|
-
|
|
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;
|
|
2587
2458
|
}
|
|
2588
|
-
|
|
2589
|
-
|
|
2590
|
-
|
|
2591
|
-
|
|
2592
|
-
|
|
2593
|
-
|
|
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) {
|
|
2594
2468
|
return null;
|
|
2595
2469
|
}
|
|
2596
|
-
|
|
2597
|
-
|
|
2598
|
-
|
|
2599
|
-
const numeric = typeof value === "number" ? value : typeof value === "string" && value.trim() ? Number(value) : Number.NaN;
|
|
2600
|
-
return Number.isFinite(numeric) ? numeric : null;
|
|
2601
|
-
};
|
|
2602
|
-
var roundTo = (value, decimals) => {
|
|
2603
|
-
const factor = 10 ** decimals;
|
|
2604
|
-
return Math.round(value * factor) / factor;
|
|
2605
|
-
};
|
|
2606
|
-
var buildMissingSpreadContext = () => ({
|
|
2607
|
-
source: "payload.additionalIndicators.baseContext.relative.execution.venueSpread",
|
|
2608
|
-
indicatorKey: "payload.additionalIndicators.baseContext.relative.execution.venueSpread",
|
|
2609
|
-
available: false,
|
|
2610
|
-
value: null,
|
|
2611
|
-
zScore: null,
|
|
2612
|
-
bps: null,
|
|
2613
|
-
absBps: null,
|
|
2614
|
-
bias: null,
|
|
2615
|
-
severity: null
|
|
2616
|
-
});
|
|
2617
|
-
var buildSpreadContextFromSignal = (signal) => {
|
|
2618
|
-
const baseContext = toRecord(signal.additionalIndicators?.baseContext);
|
|
2619
|
-
const relative = toRecord(baseContext?.relative);
|
|
2620
|
-
const execution = toRecord(relative?.execution);
|
|
2621
|
-
const spread = toFiniteNumber(execution?.venueSpread);
|
|
2622
|
-
const zScore = toFiniteNumber(execution?.venueSpreadZScore);
|
|
2623
|
-
if (spread == null) {
|
|
2624
|
-
return buildMissingSpreadContext();
|
|
2470
|
+
const orderId = existing.orderId;
|
|
2471
|
+
if (strategy && existing.strategy !== strategy) {
|
|
2472
|
+
return null;
|
|
2625
2473
|
}
|
|
2626
|
-
const
|
|
2627
|
-
const
|
|
2628
|
-
|
|
2629
|
-
|
|
2630
|
-
|
|
2631
|
-
|
|
2632
|
-
|
|
2633
|
-
|
|
2634
|
-
|
|
2635
|
-
|
|
2636
|
-
|
|
2637
|
-
|
|
2638
|
-
|
|
2639
|
-
|
|
2640
|
-
|
|
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()
|
|
2641
2491
|
};
|
|
2642
|
-
|
|
2643
|
-
|
|
2644
|
-
|
|
2645
|
-
|
|
2646
|
-
|
|
2647
|
-
|
|
2648
|
-
|
|
2649
|
-
|
|
2650
|
-
|
|
2651
|
-
|
|
2652
|
-
|
|
2653
|
-
|
|
2654
|
-
|
|
2655
|
-
|
|
2656
|
-
|
|
2657
|
-
|
|
2658
|
-
|
|
2659
|
-
|
|
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
|
+
);
|
|
2660
2537
|
}
|
|
2661
|
-
return
|
|
2662
|
-
source,
|
|
2663
|
-
available: true,
|
|
2664
|
-
buyPressurePct: toFiniteNumber(delta.buyPressurePct),
|
|
2665
|
-
buyVolume: toFiniteNumber(delta.buyVolume),
|
|
2666
|
-
sellVolume: toFiniteNumber(delta.sellVolume),
|
|
2667
|
-
netDelta: toFiniteNumber(delta.netDelta),
|
|
2668
|
-
deltaPct: toFiniteNumber(delta.deltaPct),
|
|
2669
|
-
signedVolumeZScore: toFiniteNumber(delta.signedVolumeZScore)
|
|
2670
|
-
};
|
|
2671
|
-
};
|
|
2672
|
-
var buildTradeFlowContextFromSignal = (signal) => {
|
|
2673
|
-
const baseContext = toRecord(signal.additionalIndicators?.baseContext);
|
|
2674
|
-
const participation = toRecord(baseContext?.participation);
|
|
2675
|
-
const tradeFlow = toRecord(participation?.tradeFlow);
|
|
2676
|
-
if (!tradeFlow) {
|
|
2677
|
-
return {
|
|
2678
|
-
source: null,
|
|
2679
|
-
available: false,
|
|
2680
|
-
interval: null,
|
|
2681
|
-
stale: null,
|
|
2682
|
-
trades: null,
|
|
2683
|
-
buyPressurePct: null,
|
|
2684
|
-
netBaseDelta: null,
|
|
2685
|
-
netQuoteDelta: null
|
|
2686
|
-
};
|
|
2687
|
-
}
|
|
2688
|
-
return {
|
|
2689
|
-
source: String(tradeFlow.source ?? ""),
|
|
2690
|
-
available: true,
|
|
2691
|
-
interval: String(tradeFlow.interval ?? ""),
|
|
2692
|
-
stale: typeof tradeFlow.stale === "boolean" ? tradeFlow.stale : null,
|
|
2693
|
-
trades: toFiniteNumber(tradeFlow.trades),
|
|
2694
|
-
buyPressurePct: toFiniteNumber(tradeFlow.buyPressurePct),
|
|
2695
|
-
netBaseDelta: toFiniteNumber(tradeFlow.netBaseDelta),
|
|
2696
|
-
netQuoteDelta: toFiniteNumber(tradeFlow.netQuoteDelta)
|
|
2697
|
-
};
|
|
2698
|
-
};
|
|
2699
|
-
var buildMarketBreadthContextFromSignal = (signal) => {
|
|
2700
|
-
const baseContext = toRecord(signal.additionalIndicators?.baseContext);
|
|
2701
|
-
const relative = toRecord(baseContext?.relative);
|
|
2702
|
-
const breadth = toRecord(relative?.marketBreadth);
|
|
2703
|
-
if (!breadth) {
|
|
2704
|
-
return {
|
|
2705
|
-
source: null,
|
|
2706
|
-
available: false,
|
|
2707
|
-
universe: null,
|
|
2708
|
-
interval: null,
|
|
2709
|
-
stale: null,
|
|
2710
|
-
symbolsCount: null,
|
|
2711
|
-
advanceDeclineRatio: null,
|
|
2712
|
-
pctAboveMa20: null,
|
|
2713
|
-
pctAboveMa50: null,
|
|
2714
|
-
equalWeightedReturn: null,
|
|
2715
|
-
volumeWeightedReturn: null,
|
|
2716
|
-
dispersion: null
|
|
2717
|
-
};
|
|
2718
|
-
}
|
|
2719
|
-
return {
|
|
2720
|
-
source: String(breadth.source ?? ""),
|
|
2721
|
-
available: true,
|
|
2722
|
-
universe: String(breadth.universe ?? ""),
|
|
2723
|
-
interval: String(breadth.interval ?? ""),
|
|
2724
|
-
stale: typeof breadth.stale === "boolean" ? breadth.stale : null,
|
|
2725
|
-
symbolsCount: toFiniteNumber(breadth.symbolsCount),
|
|
2726
|
-
advanceDeclineRatio: toFiniteNumber(breadth.advanceDeclineRatio),
|
|
2727
|
-
pctAboveMa20: toFiniteNumber(breadth.pctAboveMa20),
|
|
2728
|
-
pctAboveMa50: toFiniteNumber(breadth.pctAboveMa50),
|
|
2729
|
-
equalWeightedReturn: toFiniteNumber(breadth.equalWeightedReturn),
|
|
2730
|
-
volumeWeightedReturn: toFiniteNumber(breadth.volumeWeightedReturn),
|
|
2731
|
-
dispersion: toFiniteNumber(breadth.dispersion)
|
|
2732
|
-
};
|
|
2538
|
+
return next;
|
|
2733
2539
|
};
|
|
2734
|
-
|
|
2735
|
-
|
|
2736
|
-
|
|
2737
|
-
|
|
2738
|
-
|
|
2739
|
-
|
|
2740
|
-
|
|
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);
|
|
2741
2565
|
return {
|
|
2742
|
-
|
|
2743
|
-
|
|
2744
|
-
|
|
2745
|
-
|
|
2746
|
-
|
|
2747
|
-
|
|
2748
|
-
|
|
2749
|
-
|
|
2750
|
-
|
|
2751
|
-
|
|
2752
|
-
|
|
2753
|
-
|
|
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
|
|
2754
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));
|
|
2755
2617
|
}
|
|
2756
|
-
return {
|
|
2757
|
-
source: String(breadth.source ?? ""),
|
|
2758
|
-
available: true,
|
|
2759
|
-
universe: String(breadth.universe ?? ""),
|
|
2760
|
-
interval: String(breadth.interval ?? ""),
|
|
2761
|
-
stale: typeof breadth.stale === "boolean" ? breadth.stale : null,
|
|
2762
|
-
symbolsCount: toFiniteNumber(breadth.symbolsCount),
|
|
2763
|
-
advanceDeclineRatio: toFiniteNumber(breadth.advanceDeclineRatio),
|
|
2764
|
-
pctAboveMa20: toFiniteNumber(breadth.pctAboveMa20),
|
|
2765
|
-
pctAboveMa50: toFiniteNumber(breadth.pctAboveMa50),
|
|
2766
|
-
equalWeightedReturn: toFiniteNumber(breadth.equalWeightedReturn),
|
|
2767
|
-
volumeWeightedReturn: toFiniteNumber(breadth.volumeWeightedReturn),
|
|
2768
|
-
dispersion: toFiniteNumber(breadth.dispersion)
|
|
2769
|
-
};
|
|
2770
2618
|
};
|
|
2771
|
-
|
|
2772
|
-
|
|
2773
|
-
|
|
2774
|
-
|
|
2775
|
-
|
|
2776
|
-
|
|
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
|
|
2777
2645
|
};
|
|
2778
|
-
|
|
2779
|
-
|
|
2780
|
-
const
|
|
2781
|
-
const
|
|
2782
|
-
|
|
2783
|
-
if (!
|
|
2784
|
-
|
|
2785
|
-
|
|
2786
|
-
|
|
2787
|
-
|
|
2788
|
-
|
|
2789
|
-
|
|
2790
|
-
|
|
2791
|
-
|
|
2792
|
-
|
|
2793
|
-
|
|
2794
|
-
|
|
2795
|
-
|
|
2796
|
-
|
|
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);
|
|
2797
2745
|
}
|
|
2798
2746
|
return {
|
|
2799
|
-
|
|
2800
|
-
|
|
2801
|
-
ratioReturn1h: toFiniteNumber(targetVsBtc.ratioReturn1h),
|
|
2802
|
-
ratioReturn4h: toFiniteNumber(targetVsBtc.ratioReturn4h),
|
|
2803
|
-
ratioReturn24h: toFiniteNumber(targetVsBtc.ratioReturn24h),
|
|
2804
|
-
alphaVsBtc1h: toFiniteNumber(targetVsBtc.alphaVsBtc1h),
|
|
2805
|
-
alphaVsBtc4h: toFiniteNumber(targetVsBtc.alphaVsBtc4h),
|
|
2806
|
-
alphaVsBtc24h: toFiniteNumber(targetVsBtc.alphaVsBtc24h),
|
|
2807
|
-
betaToBtc20: toFiniteNumber(targetVsBtc.betaToBtc20),
|
|
2808
|
-
correlationToBtc20: toFiniteNumber(targetVsBtc.correlationToBtc20),
|
|
2809
|
-
ratioTrend: typeof targetVsBtc.ratioTrend === "string" ? targetVsBtc.ratioTrend : null
|
|
2747
|
+
projectRoot,
|
|
2748
|
+
state
|
|
2810
2749
|
};
|
|
2811
2750
|
};
|
|
2812
|
-
var
|
|
2813
|
-
|
|
2814
|
-
|
|
2815
|
-
|
|
2816
|
-
|
|
2817
|
-
return {
|
|
2818
|
-
source: null,
|
|
2819
|
-
available: false,
|
|
2820
|
-
universe: null,
|
|
2821
|
-
interval: null,
|
|
2822
|
-
stale: null,
|
|
2823
|
-
regime: null,
|
|
2824
|
-
btcReturn24h: null,
|
|
2825
|
-
altBasketReturn24h: null,
|
|
2826
|
-
btcVsAltReturn24h: null,
|
|
2827
|
-
btcTurnoverShare24h: null,
|
|
2828
|
-
btcTurnoverShareChange24h: null,
|
|
2829
|
-
altVolToBtcVol24h: null,
|
|
2830
|
-
altDispersion24h: null
|
|
2831
|
-
};
|
|
2832
|
-
}
|
|
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);
|
|
2833
2756
|
return {
|
|
2834
|
-
|
|
2835
|
-
|
|
2836
|
-
universe: String(btcAltRegime.universe ?? ""),
|
|
2837
|
-
interval: String(btcAltRegime.interval ?? ""),
|
|
2838
|
-
stale: typeof btcAltRegime.stale === "boolean" ? btcAltRegime.stale : null,
|
|
2839
|
-
regime: typeof btcAltRegime.regime === "string" ? btcAltRegime.regime : null,
|
|
2840
|
-
btcReturn24h: toFiniteNumber(btcAltRegime.btcReturn24h),
|
|
2841
|
-
altBasketReturn24h: toFiniteNumber(btcAltRegime.altBasketReturn24h),
|
|
2842
|
-
btcVsAltReturn24h: toFiniteNumber(btcAltRegime.btcVsAltReturn24h),
|
|
2843
|
-
btcTurnoverShare24h: toFiniteNumber(btcAltRegime.btcTurnoverShare24h),
|
|
2844
|
-
btcTurnoverShareChange24h: toFiniteNumber(
|
|
2845
|
-
btcAltRegime.btcTurnoverShareChange24h
|
|
2846
|
-
),
|
|
2847
|
-
altVolToBtcVol24h: toFiniteNumber(btcAltRegime.altVolToBtcVol24h),
|
|
2848
|
-
altDispersion24h: toFiniteNumber(btcAltRegime.altDispersion24h)
|
|
2757
|
+
strategyModules: toUniqueModules2(config.strategies),
|
|
2758
|
+
indicatorModules: toUniqueModules2(config.indicators)
|
|
2849
2759
|
};
|
|
2850
2760
|
};
|
|
2851
|
-
var
|
|
2852
|
-
|
|
2853
|
-
|
|
2854
|
-
|
|
2855
|
-
|
|
2856
|
-
|
|
2857
|
-
|
|
2858
|
-
|
|
2859
|
-
|
|
2860
|
-
|
|
2861
|
-
|
|
2862
|
-
|
|
2863
|
-
|
|
2864
|
-
|
|
2865
|
-
|
|
2866
|
-
|
|
2867
|
-
|
|
2868
|
-
|
|
2869
|
-
|
|
2870
|
-
|
|
2871
|
-
|
|
2872
|
-
|
|
2873
|
-
|
|
2874
|
-
|
|
2875
|
-
|
|
2876
|
-
|
|
2877
|
-
|
|
2878
|
-
|
|
2879
|
-
|
|
2880
|
-
|
|
2881
|
-
|
|
2882
|
-
|
|
2883
|
-
|
|
2884
|
-
|
|
2885
|
-
|
|
2886
|
-
|
|
2887
|
-
|
|
2888
|
-
|
|
2889
|
-
|
|
2890
|
-
|
|
2891
|
-
|
|
2892
|
-
|
|
2893
|
-
|
|
2894
|
-
|
|
2895
|
-
|
|
2896
|
-
|
|
2897
|
-
|
|
2898
|
-
|
|
2899
|
-
|
|
2900
|
-
|
|
2901
|
-
|
|
2761
|
+
var extractModuleEntries = (moduleExport, key) => {
|
|
2762
|
+
if (!moduleExport || typeof moduleExport !== "object") {
|
|
2763
|
+
return null;
|
|
2764
|
+
}
|
|
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`
|
|
2902
3381
|
),
|
|
2903
|
-
|
|
2904
|
-
|
|
2905
|
-
|
|
2906
|
-
|
|
2907
|
-
|
|
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
|
|
2908
3460
|
};
|
|
2909
3461
|
};
|
|
2910
|
-
|
|
2911
|
-
|
|
2912
|
-
|
|
2913
|
-
|
|
2914
|
-
|
|
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
|
+
);
|
|
2915
3503
|
return {
|
|
2916
|
-
|
|
2917
|
-
|
|
2918
|
-
|
|
2919
|
-
|
|
2920
|
-
|
|
2921
|
-
|
|
2922
|
-
ethMarketCapUsd: null,
|
|
2923
|
-
btcVolumeUsd: null,
|
|
2924
|
-
ethVolumeUsd: null,
|
|
2925
|
-
btcVolumeToMarketCap: null,
|
|
2926
|
-
ethVolumeToMarketCap: null,
|
|
2927
|
-
ethBtcMarketCapRatio: null,
|
|
2928
|
-
ethBtcMarketCapRatioChange24hPct: null,
|
|
2929
|
-
ethVsBtcVolumeRatio: null,
|
|
2930
|
-
referenceLiquidityRegime: null
|
|
3504
|
+
arrivalSnapshotTime: Date.now(),
|
|
3505
|
+
arrivalSource: "top_of_book_error",
|
|
3506
|
+
bid: null,
|
|
3507
|
+
ask: null,
|
|
3508
|
+
arrivalMid: null,
|
|
3509
|
+
spreadBps: null
|
|
2931
3510
|
};
|
|
2932
3511
|
}
|
|
2933
|
-
|
|
2934
|
-
|
|
2935
|
-
|
|
2936
|
-
|
|
2937
|
-
|
|
2938
|
-
|
|
2939
|
-
|
|
2940
|
-
|
|
2941
|
-
|
|
2942
|
-
|
|
2943
|
-
|
|
2944
|
-
|
|
2945
|
-
|
|
2946
|
-
|
|
2947
|
-
|
|
2948
|
-
|
|
2949
|
-
|
|
2950
|
-
|
|
2951
|
-
|
|
2952
|
-
|
|
2953
|
-
|
|
2954
|
-
)
|
|
2955
|
-
|
|
2956
|
-
|
|
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);
|
|
2957
3602
|
};
|
|
2958
3603
|
};
|
|
2959
|
-
|
|
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) => {
|
|
2960
3675
|
const baseContext = toRecord(signal.additionalIndicators?.baseContext);
|
|
2961
3676
|
const relative = toRecord(baseContext?.relative);
|
|
2962
|
-
const
|
|
2963
|
-
|
|
2964
|
-
|
|
2965
|
-
|
|
2966
|
-
|
|
2967
|
-
interval: null,
|
|
2968
|
-
asOfTs: null,
|
|
2969
|
-
stale: null,
|
|
2970
|
-
exchangesCount: null,
|
|
2971
|
-
totalVolumeUsd: null,
|
|
2972
|
-
totalVolumeChange24hPct: null,
|
|
2973
|
-
binanceVolumeUsd: null,
|
|
2974
|
-
binanceVolumeShare: null,
|
|
2975
|
-
topExchangeVolumeShare: null,
|
|
2976
|
-
liquidityRegime: null
|
|
2977
|
-
};
|
|
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();
|
|
2978
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";
|
|
2979
3688
|
return {
|
|
2980
|
-
source:
|
|
3689
|
+
source: "payload.additionalIndicators.baseContext.relative.execution.venueSpread",
|
|
3690
|
+
indicatorKey: "payload.additionalIndicators.baseContext.relative.execution.venueSpread",
|
|
2981
3691
|
available: true,
|
|
2982
|
-
|
|
2983
|
-
|
|
2984
|
-
|
|
2985
|
-
|
|
2986
|
-
|
|
2987
|
-
|
|
2988
|
-
cmcExchangeLiquidity.totalVolumeChange24hPct
|
|
2989
|
-
),
|
|
2990
|
-
binanceVolumeUsd: toFiniteNumber(cmcExchangeLiquidity.binanceVolumeUsd),
|
|
2991
|
-
binanceVolumeShare: toFiniteNumber(cmcExchangeLiquidity.binanceVolumeShare),
|
|
2992
|
-
topExchangeVolumeShare: toFiniteNumber(
|
|
2993
|
-
cmcExchangeLiquidity.topExchangeVolumeShare
|
|
2994
|
-
),
|
|
2995
|
-
liquidityRegime: typeof cmcExchangeLiquidity.liquidityRegime === "string" ? cmcExchangeLiquidity.liquidityRegime : null
|
|
3692
|
+
value,
|
|
3693
|
+
zScore,
|
|
3694
|
+
bps,
|
|
3695
|
+
absBps,
|
|
3696
|
+
bias,
|
|
3697
|
+
severity
|
|
2996
3698
|
};
|
|
2997
3699
|
};
|
|
2998
|
-
var
|
|
2999
|
-
const baseContext = toRecord(signal.additionalIndicators?.baseContext);
|
|
3000
|
-
const
|
|
3001
|
-
const
|
|
3002
|
-
|
|
3700
|
+
var buildTrueDeltaContextFromSignal = (signal) => {
|
|
3701
|
+
const baseContext = toRecord(signal.additionalIndicators?.baseContext);
|
|
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) {
|
|
3003
3707
|
return {
|
|
3004
|
-
source: null,
|
|
3708
|
+
source: source || null,
|
|
3005
3709
|
available: false,
|
|
3006
|
-
|
|
3007
|
-
|
|
3008
|
-
|
|
3009
|
-
|
|
3010
|
-
|
|
3011
|
-
|
|
3012
|
-
classification: null,
|
|
3013
|
-
sentimentRegime: null
|
|
3710
|
+
buyPressurePct: null,
|
|
3711
|
+
buyVolume: null,
|
|
3712
|
+
sellVolume: null,
|
|
3713
|
+
netDelta: null,
|
|
3714
|
+
deltaPct: null,
|
|
3715
|
+
signedVolumeZScore: null
|
|
3014
3716
|
};
|
|
3015
3717
|
}
|
|
3016
3718
|
return {
|
|
3017
|
-
source
|
|
3719
|
+
source,
|
|
3018
3720
|
available: true,
|
|
3019
|
-
|
|
3020
|
-
|
|
3021
|
-
|
|
3022
|
-
|
|
3023
|
-
|
|
3024
|
-
|
|
3025
|
-
classification: typeof cmcFearGreed.classification === "string" ? cmcFearGreed.classification : null,
|
|
3026
|
-
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)
|
|
3027
3727
|
};
|
|
3028
3728
|
};
|
|
3029
|
-
var
|
|
3729
|
+
var buildTradeFlowContextFromSignal = (signal) => {
|
|
3030
3730
|
const baseContext = toRecord(signal.additionalIndicators?.baseContext);
|
|
3031
|
-
const
|
|
3032
|
-
const
|
|
3033
|
-
if (!
|
|
3731
|
+
const participation = toRecord(baseContext?.participation);
|
|
3732
|
+
const tradeFlow = toRecord(participation?.tradeFlow);
|
|
3733
|
+
if (!tradeFlow) {
|
|
3034
3734
|
return {
|
|
3035
3735
|
source: null,
|
|
3036
3736
|
available: false,
|
|
3037
3737
|
interval: null,
|
|
3038
|
-
asOfTs: null,
|
|
3039
3738
|
stale: null,
|
|
3040
|
-
|
|
3041
|
-
|
|
3042
|
-
|
|
3043
|
-
|
|
3044
|
-
cmc20Value: null,
|
|
3045
|
-
cmc20Change24hPct: null,
|
|
3046
|
-
cmc20TopConstituentSymbol: null,
|
|
3047
|
-
cmc20TopConstituentWeightPct: null,
|
|
3048
|
-
cmc20ToCmc100Ratio: null,
|
|
3049
|
-
cmc20ToCmc100RatioChange24hPct: null,
|
|
3050
|
-
indexRegime: null
|
|
3739
|
+
trades: null,
|
|
3740
|
+
buyPressurePct: null,
|
|
3741
|
+
netBaseDelta: null,
|
|
3742
|
+
netQuoteDelta: null
|
|
3051
3743
|
};
|
|
3052
3744
|
}
|
|
3053
3745
|
return {
|
|
3054
|
-
source: String(
|
|
3746
|
+
source: String(tradeFlow.source ?? ""),
|
|
3055
3747
|
available: true,
|
|
3056
|
-
interval:
|
|
3057
|
-
|
|
3058
|
-
|
|
3059
|
-
|
|
3060
|
-
|
|
3061
|
-
|
|
3062
|
-
cmc100TopConstituentWeightPct: toFiniteNumber(
|
|
3063
|
-
cmcIndexes.cmc100TopConstituentWeightPct
|
|
3064
|
-
),
|
|
3065
|
-
cmc20Value: toFiniteNumber(cmcIndexes.cmc20Value),
|
|
3066
|
-
cmc20Change24hPct: toFiniteNumber(cmcIndexes.cmc20Change24hPct),
|
|
3067
|
-
cmc20TopConstituentSymbol: typeof cmcIndexes.cmc20TopConstituentSymbol === "string" ? cmcIndexes.cmc20TopConstituentSymbol : null,
|
|
3068
|
-
cmc20TopConstituentWeightPct: toFiniteNumber(
|
|
3069
|
-
cmcIndexes.cmc20TopConstituentWeightPct
|
|
3070
|
-
),
|
|
3071
|
-
cmc20ToCmc100Ratio: toFiniteNumber(cmcIndexes.cmc20ToCmc100Ratio),
|
|
3072
|
-
cmc20ToCmc100RatioChange24hPct: toFiniteNumber(
|
|
3073
|
-
cmcIndexes.cmc20ToCmc100RatioChange24hPct
|
|
3074
|
-
),
|
|
3075
|
-
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)
|
|
3076
3754
|
};
|
|
3077
3755
|
};
|
|
3078
|
-
var
|
|
3756
|
+
var buildMarketBreadthContextFromSignal = (signal) => {
|
|
3079
3757
|
const baseContext = toRecord(signal.additionalIndicators?.baseContext);
|
|
3080
3758
|
const relative = toRecord(baseContext?.relative);
|
|
3081
|
-
const
|
|
3082
|
-
|
|
3083
|
-
const tradeFlowBySymbol = toRecord(refs?.tradeFlowBySymbol);
|
|
3084
|
-
const primaryTradeFlow = primaryReferenceSymbol != null ? toRecord(tradeFlowBySymbol?.[primaryReferenceSymbol]) : null;
|
|
3085
|
-
if (!refs) {
|
|
3759
|
+
const breadth = toRecord(relative?.marketBreadth);
|
|
3760
|
+
if (!breadth) {
|
|
3086
3761
|
return {
|
|
3087
3762
|
source: null,
|
|
3088
3763
|
available: false,
|
|
3089
|
-
|
|
3090
|
-
|
|
3091
|
-
|
|
3092
|
-
|
|
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
|
|
3093
3774
|
};
|
|
3094
3775
|
}
|
|
3095
3776
|
return {
|
|
3096
|
-
source: String(
|
|
3777
|
+
source: String(breadth.source ?? ""),
|
|
3097
3778
|
available: true,
|
|
3098
|
-
|
|
3099
|
-
|
|
3100
|
-
|
|
3101
|
-
|
|
3102
|
-
),
|
|
3103
|
-
|
|
3104
|
-
|
|
3105
|
-
|
|
3106
|
-
|
|
3107
|
-
|
|
3108
|
-
binanceCoinbaseSpread: buildSpreadContextFromSignal(signal)
|
|
3109
|
-
},
|
|
3110
|
-
participation: {
|
|
3111
|
-
trueDelta: buildTrueDeltaContextFromSignal(signal),
|
|
3112
|
-
tradeFlow: buildTradeFlowContextFromSignal(signal)
|
|
3113
|
-
},
|
|
3114
|
-
relative: {
|
|
3115
|
-
marketBreadth: buildMarketBreadthContextFromSignal(signal),
|
|
3116
|
-
marketBreadths: buildMarketBreadthsContextFromSignal(signal),
|
|
3117
|
-
targetVsBtc: buildTargetVsBtcContextFromSignal(signal),
|
|
3118
|
-
btcAltRegime: buildBtcAltRegimeContextFromSignal(signal),
|
|
3119
|
-
cmcGlobal: buildCmcGlobalContextFromSignal(signal),
|
|
3120
|
-
cmcReferenceAssets: buildCmcReferenceAssetsContextFromSignal(signal),
|
|
3121
|
-
cmcExchangeLiquidity: buildCmcExchangeLiquidityContextFromSignal(signal),
|
|
3122
|
-
cmcFearGreed: buildCmcFearGreedContextFromSignal(signal),
|
|
3123
|
-
cmcIndexes: buildCmcIndexesContextFromSignal(signal),
|
|
3124
|
-
referenceTradeFlow: buildReferenceTradeFlowContextFromSignal(signal)
|
|
3125
|
-
}
|
|
3126
|
-
});
|
|
3127
|
-
|
|
3128
|
-
// src/strategy/manifests.ts
|
|
3129
|
-
var import_indicators = require("@tradejs/core/indicators");
|
|
3130
|
-
var SHARED_STRATEGY_REGISTRY_KEY = "__tradejsNodeSharedStrategyRegistryV1__";
|
|
3131
|
-
var sharedRegistryScope = globalThis;
|
|
3132
|
-
var sharedStrategyRegistry = sharedRegistryScope[SHARED_STRATEGY_REGISTRY_KEY] ?? (sharedRegistryScope[SHARED_STRATEGY_REGISTRY_KEY] = {
|
|
3133
|
-
registryStateByProjectRoot: /* @__PURE__ */ new Map()
|
|
3134
|
-
});
|
|
3135
|
-
var createStrategyRegistryState = () => ({
|
|
3136
|
-
strategyCreators: /* @__PURE__ */ new Map(),
|
|
3137
|
-
strategyManifestsMap: /* @__PURE__ */ new Map(),
|
|
3138
|
-
strategyEntriesMap: /* @__PURE__ */ new Map(),
|
|
3139
|
-
strategySourcesMap: /* @__PURE__ */ new Map(),
|
|
3140
|
-
strategyModuleResolutions: /* @__PURE__ */ new Map(),
|
|
3141
|
-
pluginsLoadPromise: null
|
|
3142
|
-
});
|
|
3143
|
-
var registryStateByProjectRoot2 = sharedStrategyRegistry.registryStateByProjectRoot;
|
|
3144
|
-
var getStrategyRegistryState = (cwd = getTradejsProjectCwd()) => {
|
|
3145
|
-
const projectRoot = getTradejsProjectCwd(cwd);
|
|
3146
|
-
let state = registryStateByProjectRoot2.get(projectRoot);
|
|
3147
|
-
if (!state) {
|
|
3148
|
-
state = createStrategyRegistryState();
|
|
3149
|
-
registryStateByProjectRoot2.set(projectRoot, state);
|
|
3150
|
-
}
|
|
3151
|
-
return {
|
|
3152
|
-
projectRoot,
|
|
3153
|
-
state
|
|
3154
|
-
};
|
|
3155
|
-
};
|
|
3156
|
-
var toUniqueModules2 = (modules = []) => [
|
|
3157
|
-
...new Set(modules.map((moduleName) => moduleName.trim()).filter(Boolean))
|
|
3158
|
-
];
|
|
3159
|
-
var getConfiguredPluginModuleNames = async (cwd = getTradejsProjectCwd()) => {
|
|
3160
|
-
const config = await loadTradejsConfig(cwd);
|
|
3161
|
-
return {
|
|
3162
|
-
strategyModules: toUniqueModules2(config.strategies),
|
|
3163
|
-
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)
|
|
3164
3789
|
};
|
|
3165
3790
|
};
|
|
3166
|
-
var
|
|
3167
|
-
|
|
3168
|
-
|
|
3169
|
-
|
|
3170
|
-
const
|
|
3171
|
-
|
|
3172
|
-
|
|
3173
|
-
|
|
3174
|
-
|
|
3175
|
-
|
|
3176
|
-
|
|
3177
|
-
|
|
3178
|
-
|
|
3179
|
-
|
|
3180
|
-
|
|
3181
|
-
|
|
3182
|
-
|
|
3183
|
-
|
|
3184
|
-
|
|
3185
|
-
|
|
3186
|
-
};
|
|
3187
|
-
var extractIndicatorPluginDefinition = (moduleExport) => {
|
|
3188
|
-
const indicatorEntries = extractModuleEntries(
|
|
3189
|
-
moduleExport,
|
|
3190
|
-
"indicatorEntries"
|
|
3191
|
-
);
|
|
3192
|
-
return indicatorEntries ? { indicatorEntries } : null;
|
|
3193
|
-
};
|
|
3194
|
-
var validateStrategyEntries = (moduleName, entries, state) => {
|
|
3195
|
-
const issues = [];
|
|
3196
|
-
const names = /* @__PURE__ */ new Set();
|
|
3197
|
-
entries.forEach((entry, index) => {
|
|
3198
|
-
const entryPath = `${moduleName}.strategyEntries[${index}]`;
|
|
3199
|
-
const strategyName = entry?.manifest?.name;
|
|
3200
|
-
if (typeof strategyName !== "string" || !strategyName.trim()) {
|
|
3201
|
-
issues.push(`${entryPath}: manifest.name is required`);
|
|
3202
|
-
} else if (names.has(strategyName) || state?.strategyEntriesMap.has(strategyName)) {
|
|
3203
|
-
issues.push(`${entryPath}: duplicate strategy ${strategyName}`);
|
|
3204
|
-
} else {
|
|
3205
|
-
names.add(strategyName);
|
|
3206
|
-
}
|
|
3207
|
-
if (!entry || typeof entry !== "object" || !entry.defaults || typeof entry.defaults !== "object" || Array.isArray(entry.defaults)) {
|
|
3208
|
-
issues.push(`${entryPath}: defaults must be an object`);
|
|
3209
|
-
}
|
|
3210
|
-
if (typeof entry?.parseConfig !== "function") {
|
|
3211
|
-
issues.push(`${entryPath}: parseConfig is required`);
|
|
3212
|
-
}
|
|
3213
|
-
if (typeof entry?.createCore !== "function") {
|
|
3214
|
-
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
|
+
};
|
|
3215
3812
|
}
|
|
3216
|
-
|
|
3217
|
-
|
|
3218
|
-
|
|
3219
|
-
|
|
3220
|
-
|
|
3221
|
-
|
|
3222
|
-
|
|
3223
|
-
|
|
3224
|
-
|
|
3225
|
-
|
|
3226
|
-
|
|
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
|
+
};
|
|
3227
3835
|
};
|
|
3228
|
-
var
|
|
3229
|
-
|
|
3230
|
-
|
|
3231
|
-
}
|
|
3232
|
-
|
|
3233
|
-
|
|
3234
|
-
|
|
3235
|
-
|
|
3236
|
-
|
|
3237
|
-
|
|
3238
|
-
|
|
3239
|
-
|
|
3240
|
-
|
|
3241
|
-
|
|
3242
|
-
|
|
3243
|
-
|
|
3244
|
-
|
|
3245
|
-
);
|
|
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
|
+
};
|
|
3246
3853
|
};
|
|
3247
|
-
var
|
|
3248
|
-
|
|
3249
|
-
|
|
3250
|
-
|
|
3251
|
-
|
|
3252
|
-
|
|
3253
|
-
|
|
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
|
+
};
|
|
3254
3864
|
};
|
|
3255
|
-
var
|
|
3256
|
-
|
|
3257
|
-
|
|
3258
|
-
|
|
3259
|
-
|
|
3260
|
-
|
|
3261
|
-
|
|
3262
|
-
|
|
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
|
+
};
|
|
3263
3875
|
};
|
|
3264
|
-
var
|
|
3265
|
-
|
|
3266
|
-
|
|
3267
|
-
|
|
3268
|
-
|
|
3269
|
-
const { projectRoot, state } = getStrategyRegistryState(cwd);
|
|
3270
|
-
const source = moduleName.trim();
|
|
3271
|
-
const resolvedModuleName = resolvePluginModuleSpecifier(source, projectRoot);
|
|
3272
|
-
const cacheKey = `${resolvedModuleName}\0${strategyName}`;
|
|
3273
|
-
const cached = state.strategyModuleResolutions.get(cacheKey);
|
|
3274
|
-
if (cached) return cached;
|
|
3275
|
-
const resolution = (async () => {
|
|
3276
|
-
const moduleExport = await importStrategyPluginModule(
|
|
3277
|
-
resolvedModuleName,
|
|
3278
|
-
projectRoot
|
|
3279
|
-
);
|
|
3280
|
-
const pluginDefinition = extractStrategyPluginDefinition(moduleExport);
|
|
3281
|
-
if (!pluginDefinition) {
|
|
3282
|
-
throw new Error(`${source}: export { strategyEntries } is missing`);
|
|
3283
|
-
}
|
|
3284
|
-
const issues = validateStrategyEntries(
|
|
3285
|
-
source,
|
|
3286
|
-
pluginDefinition.strategyEntries
|
|
3287
|
-
);
|
|
3288
|
-
if (issues.length > 0) {
|
|
3289
|
-
throw new Error(
|
|
3290
|
-
["Invalid TradeJS strategy module:", ...issues].join("\n")
|
|
3291
|
-
);
|
|
3292
|
-
}
|
|
3293
|
-
const entry = pluginDefinition.strategyEntries.find(
|
|
3294
|
-
(candidate) => candidate.manifest.name === strategyName
|
|
3295
|
-
);
|
|
3296
|
-
if (!entry) {
|
|
3297
|
-
throw new Error(`${source}: strategy ${strategyName} is missing`);
|
|
3298
|
-
}
|
|
3299
|
-
const factory = sharedStrategyRegistry.strategyRuntimeFactory;
|
|
3300
|
-
if (!factory) {
|
|
3301
|
-
throw new Error("Strategy runtime factory is not configured");
|
|
3302
|
-
}
|
|
3303
|
-
const manifestsByName = new Map(
|
|
3304
|
-
pluginDefinition.strategyEntries.map(
|
|
3305
|
-
(candidate) => [candidate.manifest.name, candidate.manifest]
|
|
3306
|
-
)
|
|
3307
|
-
);
|
|
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) {
|
|
3308
3881
|
return {
|
|
3309
|
-
|
|
3310
|
-
|
|
3311
|
-
|
|
3312
|
-
|
|
3313
|
-
|
|
3314
|
-
|
|
3315
|
-
|
|
3316
|
-
|
|
3317
|
-
|
|
3318
|
-
|
|
3319
|
-
|
|
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
|
|
3320
3895
|
};
|
|
3321
|
-
})();
|
|
3322
|
-
state.strategyModuleResolutions.set(cacheKey, resolution);
|
|
3323
|
-
try {
|
|
3324
|
-
return await resolution;
|
|
3325
|
-
} catch (error) {
|
|
3326
|
-
state.strategyModuleResolutions.delete(cacheKey);
|
|
3327
|
-
throw error;
|
|
3328
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
|
+
};
|
|
3329
3914
|
};
|
|
3330
|
-
var
|
|
3331
|
-
const
|
|
3332
|
-
|
|
3333
|
-
|
|
3334
|
-
|
|
3335
|
-
|
|
3336
|
-
|
|
3337
|
-
|
|
3338
|
-
|
|
3339
|
-
|
|
3340
|
-
|
|
3341
|
-
|
|
3342
|
-
|
|
3343
|
-
|
|
3344
|
-
|
|
3345
|
-
|
|
3346
|
-
|
|
3347
|
-
|
|
3348
|
-
|
|
3349
|
-
|
|
3350
|
-
|
|
3351
|
-
|
|
3352
|
-
|
|
3353
|
-
|
|
3354
|
-
|
|
3355
|
-
|
|
3356
|
-
|
|
3357
|
-
|
|
3358
|
-
issues.push(
|
|
3359
|
-
`${moduleName}: export { strategyEntries } is missing`
|
|
3360
|
-
);
|
|
3361
|
-
} else {
|
|
3362
|
-
const entryIssues = validateStrategyEntries(
|
|
3363
|
-
moduleName,
|
|
3364
|
-
pluginDefinition.strategyEntries,
|
|
3365
|
-
state
|
|
3366
|
-
);
|
|
3367
|
-
issues.push(...entryIssues);
|
|
3368
|
-
if (entryIssues.length === 0) {
|
|
3369
|
-
registerEntries2(
|
|
3370
|
-
pluginDefinition.strategyEntries,
|
|
3371
|
-
moduleName,
|
|
3372
|
-
state
|
|
3373
|
-
);
|
|
3374
|
-
}
|
|
3375
|
-
}
|
|
3376
|
-
}
|
|
3377
|
-
if (indicatorSet.has(moduleName)) {
|
|
3378
|
-
const indicatorPluginDefinition = extractIndicatorPluginDefinition(moduleExport);
|
|
3379
|
-
if (!indicatorPluginDefinition) {
|
|
3380
|
-
issues.push(
|
|
3381
|
-
`${moduleName}: export { indicatorEntries } is missing`
|
|
3382
|
-
);
|
|
3383
|
-
} else {
|
|
3384
|
-
(0, import_indicators.registerIndicatorEntries)(
|
|
3385
|
-
indicatorPluginDefinition.indicatorEntries,
|
|
3386
|
-
moduleName,
|
|
3387
|
-
projectRoot
|
|
3388
|
-
);
|
|
3389
|
-
}
|
|
3390
|
-
}
|
|
3391
|
-
if (!strategySet.has(moduleName) && !indicatorSet.has(moduleName)) {
|
|
3392
|
-
issues.push(`${moduleName}: plugin is not declared in config`);
|
|
3393
|
-
}
|
|
3394
|
-
} catch (error) {
|
|
3395
|
-
issues.push(`${moduleName}: failed to import: ${String(error)}`);
|
|
3396
|
-
}
|
|
3397
|
-
}
|
|
3398
|
-
if (issues.length > 0) {
|
|
3399
|
-
throw new Error(
|
|
3400
|
-
["Invalid TradeJS plugin catalog:", ...issues].join("\n")
|
|
3401
|
-
);
|
|
3402
|
-
}
|
|
3403
|
-
})();
|
|
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
|
+
};
|
|
3404
3943
|
}
|
|
3405
|
-
|
|
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
|
+
};
|
|
3406
3973
|
};
|
|
3407
|
-
var
|
|
3408
|
-
|
|
3409
|
-
|
|
3410
|
-
|
|
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
|
+
};
|
|
3411
3996
|
}
|
|
3412
|
-
|
|
3413
|
-
|
|
3414
|
-
|
|
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
|
+
};
|
|
3415
4022
|
};
|
|
3416
|
-
var
|
|
3417
|
-
|
|
3418
|
-
|
|
3419
|
-
|
|
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
|
+
};
|
|
3420
4042
|
}
|
|
3421
|
-
|
|
3422
|
-
|
|
3423
|
-
|
|
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
|
+
};
|
|
3424
4061
|
};
|
|
3425
|
-
var
|
|
3426
|
-
|
|
3427
|
-
|
|
3428
|
-
|
|
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
|
+
};
|
|
3429
4079
|
}
|
|
3430
|
-
|
|
3431
|
-
|
|
3432
|
-
|
|
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
|
+
};
|
|
3433
4092
|
};
|
|
3434
|
-
var
|
|
3435
|
-
|
|
3436
|
-
|
|
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
|
+
};
|
|
3437
4116
|
}
|
|
3438
|
-
|
|
3439
|
-
|
|
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
|
+
};
|
|
3440
4169
|
};
|
|
3441
|
-
var
|
|
3442
|
-
{
|
|
3443
|
-
|
|
3444
|
-
|
|
3445
|
-
|
|
3446
|
-
|
|
3447
|
-
|
|
3448
|
-
|
|
3449
|
-
|
|
3450
|
-
|
|
3451
|
-
|
|
3452
|
-
|
|
3453
|
-
|
|
3454
|
-
|
|
3455
|
-
|
|
3456
|
-
|
|
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)
|
|
3457
4190
|
}
|
|
3458
|
-
);
|
|
4191
|
+
});
|
|
3459
4192
|
|
|
3460
4193
|
// src/strategy/policyProfiles.ts
|
|
3461
4194
|
var profileMatches = (profile, universe, assetClass) => {
|
|
@@ -3557,12 +4290,61 @@ var buildBaseAiPayload = (signal) => {
|
|
|
3557
4290
|
};
|
|
3558
4291
|
};
|
|
3559
4292
|
var defaultAiAdapter = {};
|
|
4293
|
+
var tickerProfileFeatureIds = new Set(
|
|
4294
|
+
import_aiFeatures.TICKER_PROFILE_AI_FEATURES.map(({ id }) => id)
|
|
4295
|
+
);
|
|
3560
4296
|
var getStrategyAiAdapter = (strategy, profileId) => getStrategyProfileAiAdapter(getStrategyManifest(strategy), profileId) ?? defaultAiAdapter;
|
|
3561
4297
|
var getSignalAiAdapter = (signal) => getStrategyAiAdapter(signal.strategy, signal.policyProfileId);
|
|
3562
4298
|
var buildAiPayloadByStrategy = (signal) => {
|
|
3563
4299
|
const basePayload = buildBaseAiPayload(signal);
|
|
3564
4300
|
const adapter = getSignalAiAdapter(signal);
|
|
3565
|
-
|
|
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
|
+
};
|
|
3566
4348
|
};
|
|
3567
4349
|
var buildAiSystemPromptAddonByStrategy = (signal) => getSignalAiAdapter(signal).buildSystemPromptAddon?.({ signal }) ?? "";
|
|
3568
4350
|
var buildAiHumanPromptAddonByStrategy = (signal, payload) => getSignalAiAdapter(signal).buildHumanPromptAddon?.({
|
|
@@ -3972,356 +4754,108 @@ var runAiPrompt = async ({ systemPrompt, humanPrompt }, options = {}) => {
|
|
|
3972
4754
|
userName: options.userName,
|
|
3973
4755
|
model: options.model,
|
|
3974
4756
|
messages: [
|
|
3975
|
-
{ role: "system", content: systemPrompt },
|
|
3976
|
-
{
|
|
3977
|
-
role: "system",
|
|
3978
|
-
content: `Write all user-visible text fields in ${responseLanguage}. Keep field names and JSON syntax unchanged.`
|
|
3979
|
-
},
|
|
3980
|
-
{ role: "user", content: humanPrompt }
|
|
3981
|
-
]
|
|
3982
|
-
});
|
|
3983
|
-
const parsed = parseAIResponse(response.content);
|
|
3984
|
-
const normalized = normalizeAnalysis(parsed);
|
|
3985
|
-
if (!options.signal) {
|
|
3986
|
-
return normalized;
|
|
3987
|
-
}
|
|
3988
|
-
return postProcessAiAnalysisByStrategy(
|
|
3989
|
-
options.signal,
|
|
3990
|
-
normalized,
|
|
3991
|
-
options.payload
|
|
3992
|
-
);
|
|
3993
|
-
};
|
|
3994
|
-
var runAiPromptLocal = async (signal, options = {}) => {
|
|
3995
|
-
const payload = options.payload ?? buildAiPayload(signal);
|
|
3996
|
-
const gateContext = getDeterministicAiGateContext(payload);
|
|
3997
|
-
const signalDirection = getSignalDirection(signal);
|
|
3998
|
-
const deterministicQuality = getDeterministicQuality(gateContext);
|
|
3999
|
-
const approvalAllowedNow = typeof gateContext?.approvalAllowedNow === "boolean" ? gateContext.approvalAllowedNow : deterministicQuality >= 4;
|
|
4000
|
-
return postProcessLocalAiAnalysisByStrategy(
|
|
4001
|
-
signal,
|
|
4002
|
-
{
|
|
4003
|
-
direction: approvalAllowedNow ? signalDirection : null,
|
|
4004
|
-
quality: deterministicQuality,
|
|
4005
|
-
needRetest: !approvalAllowedNow,
|
|
4006
|
-
retestPrice: null,
|
|
4007
|
-
takeProfitPrice: approvalAllowedNow ? signal.prices?.takeProfitPrice ?? null : null,
|
|
4008
|
-
stopLossPrice: approvalAllowedNow ? signal.prices?.stopLossPrice ?? null : null
|
|
4009
|
-
},
|
|
4010
|
-
payload
|
|
4011
|
-
);
|
|
4012
|
-
};
|
|
4013
|
-
var askAI = async (signal, options = {}) => {
|
|
4014
|
-
const { symbol } = signal;
|
|
4015
|
-
const payload = buildAiPayload(signal);
|
|
4016
|
-
const content = await runAiPrompt(
|
|
4017
|
-
{
|
|
4018
|
-
systemPrompt: buildAiSystemPrompt(signal),
|
|
4019
|
-
humanPrompt: buildAiHumanPrompt(signal, payload)
|
|
4020
|
-
},
|
|
4021
|
-
{
|
|
4022
|
-
...options,
|
|
4023
|
-
signal,
|
|
4024
|
-
payload
|
|
4025
|
-
}
|
|
4026
|
-
);
|
|
4027
|
-
await (0, import_redis2.setData)(import_redis2.redisKeys.analysis(symbol, signal.signalId), content);
|
|
4028
|
-
return content;
|
|
4029
|
-
};
|
|
4030
|
-
|
|
4031
|
-
// src/strategyAdapters/ml.ts
|
|
4032
|
-
var defaultMlAdapter = {
|
|
4033
|
-
normalizeStrategyConfig: (strategyConfig) => strategyConfig
|
|
4034
|
-
};
|
|
4035
|
-
var getStrategyMlAdapter = (strategy, profileId) => {
|
|
4036
|
-
const strategyAdapter = getStrategyProfileMlAdapter(
|
|
4037
|
-
getStrategyManifest(strategy),
|
|
4038
|
-
profileId
|
|
4039
|
-
);
|
|
4040
|
-
if (!strategyAdapter) return defaultMlAdapter;
|
|
4041
|
-
return {
|
|
4042
|
-
...defaultMlAdapter,
|
|
4043
|
-
...strategyAdapter
|
|
4044
|
-
};
|
|
4045
|
-
};
|
|
4046
|
-
|
|
4047
|
-
// src/mlPayload.ts
|
|
4048
|
-
var normalizeStrategyConfig = (strategyConfig, strategyName, profileId) => {
|
|
4049
|
-
return getStrategyMlAdapter(
|
|
4050
|
-
strategyName,
|
|
4051
|
-
profileId
|
|
4052
|
-
).normalizeStrategyConfig?.(strategyConfig);
|
|
4053
|
-
};
|
|
4054
|
-
var buildMlPayload = (payload) => {
|
|
4055
|
-
const strategyName = payload.signal?.strategy ?? payload.context?.strategyName;
|
|
4056
|
-
const profileId = payload.signal?.policyProfileId;
|
|
4057
|
-
const mlAdapter = getStrategyMlAdapter(strategyName, profileId);
|
|
4058
|
-
const normalizedSignal = mlAdapter.normalizeSignal?.(payload.signal) ?? payload.signal;
|
|
4059
|
-
const nextSignal = {
|
|
4060
|
-
...normalizedSignal,
|
|
4061
|
-
indicators: {
|
|
4062
|
-
...normalizedSignal?.indicators ?? {}
|
|
4063
|
-
}
|
|
4064
|
-
};
|
|
4065
|
-
const nextContext = payload.context ? {
|
|
4066
|
-
...payload.context,
|
|
4067
|
-
strategyConfig: normalizeStrategyConfig(
|
|
4068
|
-
payload.context.strategyConfig,
|
|
4069
|
-
strategyName,
|
|
4070
|
-
profileId
|
|
4071
|
-
)
|
|
4072
|
-
} : void 0;
|
|
4073
|
-
return {
|
|
4074
|
-
signal: nextSignal,
|
|
4075
|
-
context: nextContext
|
|
4076
|
-
};
|
|
4077
|
-
};
|
|
4078
|
-
|
|
4079
|
-
// src/runtimeJournal.ts
|
|
4080
|
-
var import_node_crypto = require("crypto");
|
|
4081
|
-
var import_constants3 = require("@tradejs/core/constants");
|
|
4082
|
-
var import_time3 = require("@tradejs/core/time");
|
|
4083
|
-
var import_trade = require("@tradejs/core/trade");
|
|
4084
|
-
var import_logger6 = require("@tradejs/infra/logger");
|
|
4085
|
-
var import_redis3 = require("@tradejs/infra/redis");
|
|
4086
|
-
var now = () => Date.now();
|
|
4087
|
-
var toRandomOrderSuffix = () => (0, import_node_crypto.randomUUID)().replace(/-/g, "").slice(0, 12).toLowerCase();
|
|
4088
|
-
var calculateClosedPnl = ({
|
|
4089
|
-
direction,
|
|
4090
|
-
entryPrice,
|
|
4091
|
-
exitPrice,
|
|
4092
|
-
qty
|
|
4093
|
-
}) => {
|
|
4094
|
-
const pnl = direction === "LONG" ? (exitPrice - entryPrice) * qty : (entryPrice - exitPrice) * qty;
|
|
4095
|
-
return Number.isFinite(pnl) ? pnl : null;
|
|
4096
|
-
};
|
|
4097
|
-
var createRuntimeOrderId = (strategy) => {
|
|
4098
|
-
const prefix = (0, import_trade.createRuntimeOrderLinkPrefix)(strategy);
|
|
4099
|
-
if (prefix === "tjs-") {
|
|
4100
|
-
return `tjs-${(0, import_node_crypto.randomUUID)().replace(/-/g, "").slice(0, 24).toLowerCase()}`;
|
|
4101
|
-
}
|
|
4102
|
-
return `${prefix}${toRandomOrderSuffix()}`;
|
|
4103
|
-
};
|
|
4104
|
-
var recordRuntimeTradeOpen = async (params) => {
|
|
4105
|
-
const { userName } = params;
|
|
4106
|
-
if (!userName) {
|
|
4107
|
-
return null;
|
|
4108
|
-
}
|
|
4109
|
-
const record = {
|
|
4110
|
-
...params,
|
|
4111
|
-
entryCount: 1,
|
|
4112
|
-
lastEntryPrice: params.entryPrice,
|
|
4113
|
-
lastEntryQty: params.qty,
|
|
4114
|
-
lastEntryTimestamp: params.entryTimestamp,
|
|
4115
|
-
status: "active",
|
|
4116
|
-
currentPrice: params.entryPrice,
|
|
4117
|
-
currentPnl: 0,
|
|
4118
|
-
closedPnl: null,
|
|
4119
|
-
exitPrice: null,
|
|
4120
|
-
exitTimestamp: null,
|
|
4121
|
-
lastSyncedAt: now()
|
|
4122
|
-
};
|
|
4123
|
-
const dayKey = (0, import_time3.getRuntimeStorageDayKey)(record.entryTimestamp);
|
|
4124
|
-
const runtimeScopeId = record.deploymentId ?? record.accountId;
|
|
4125
|
-
try {
|
|
4126
|
-
await Promise.all([
|
|
4127
|
-
(0, import_redis3.setData)(import_redis3.redisKeys.runtimeTrade(userName, record.orderId), record, {
|
|
4128
|
-
expire: 0
|
|
4129
|
-
}),
|
|
4130
|
-
(0, import_redis3.setHashJsonField)(
|
|
4131
|
-
import_redis3.redisKeys.runtimeTradeBucket(userName, dayKey),
|
|
4132
|
-
record.orderId,
|
|
4133
|
-
record,
|
|
4134
|
-
{ expire: 0 }
|
|
4135
|
-
),
|
|
4136
|
-
(0, import_redis3.setData)(
|
|
4137
|
-
import_redis3.redisKeys.runtimeActiveTrade(userName, record.symbol, runtimeScopeId),
|
|
4138
|
-
{ orderId: record.orderId },
|
|
4139
|
-
{ expire: 0 }
|
|
4140
|
-
)
|
|
4141
|
-
]);
|
|
4142
|
-
} catch (error) {
|
|
4143
|
-
import_logger6.logger.error(
|
|
4144
|
-
"runtime trade open journal failed: %s %s",
|
|
4145
|
-
record.symbol,
|
|
4146
|
-
error?.message || String(error)
|
|
4147
|
-
);
|
|
4148
|
-
}
|
|
4149
|
-
return record;
|
|
4150
|
-
};
|
|
4151
|
-
var recordRuntimeTradeIncrease = async (params) => {
|
|
4152
|
-
const {
|
|
4153
|
-
userName,
|
|
4154
|
-
strategy,
|
|
4155
|
-
symbol,
|
|
4156
|
-
direction,
|
|
4157
|
-
resultingQty,
|
|
4158
|
-
resultingEntryPrice,
|
|
4159
|
-
addedQty,
|
|
4160
|
-
addedEntryPrice,
|
|
4161
|
-
entryTimestamp,
|
|
4162
|
-
fee,
|
|
4163
|
-
accountId,
|
|
4164
|
-
deploymentId
|
|
4165
|
-
} = params;
|
|
4166
|
-
if (!userName) {
|
|
4167
|
-
return null;
|
|
4168
|
-
}
|
|
4169
|
-
const existing = await getActiveRuntimeTrade({
|
|
4170
|
-
userName,
|
|
4171
|
-
symbol,
|
|
4172
|
-
accountId,
|
|
4173
|
-
deploymentId
|
|
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
|
+
]
|
|
4174
4764
|
});
|
|
4175
|
-
|
|
4176
|
-
|
|
4177
|
-
|
|
4178
|
-
|
|
4179
|
-
const openFee = (existing.openFee ?? existing.fee ?? 0) + addedFee;
|
|
4180
|
-
const totalFee = (existing.totalFee ?? existing.fee ?? 0) + addedFee;
|
|
4181
|
-
const next = {
|
|
4182
|
-
...existing,
|
|
4183
|
-
...params.orderLeverage ? { orderLeverage: params.orderLeverage } : {},
|
|
4184
|
-
qty: resultingQty,
|
|
4185
|
-
entryPrice: resultingEntryPrice,
|
|
4186
|
-
entryCount: Math.max(1, existing.entryCount ?? 1) + 1,
|
|
4187
|
-
lastEntryPrice: addedEntryPrice,
|
|
4188
|
-
lastEntryQty: addedQty,
|
|
4189
|
-
lastEntryTimestamp: entryTimestamp,
|
|
4190
|
-
currentPrice: resultingEntryPrice,
|
|
4191
|
-
currentPnl: 0,
|
|
4192
|
-
fee: openFee,
|
|
4193
|
-
openFee,
|
|
4194
|
-
totalFee,
|
|
4195
|
-
lastSyncedAt: now()
|
|
4196
|
-
};
|
|
4197
|
-
const dayKey = (0, import_time3.getRuntimeStorageDayKey)(existing.entryTimestamp);
|
|
4198
|
-
try {
|
|
4199
|
-
await Promise.all([
|
|
4200
|
-
(0, import_redis3.setData)(import_redis3.redisKeys.runtimeTrade(userName, existing.orderId), next, {
|
|
4201
|
-
expire: 0
|
|
4202
|
-
}),
|
|
4203
|
-
(0, import_redis3.setHashJsonField)(
|
|
4204
|
-
import_redis3.redisKeys.runtimeTradeBucket(userName, dayKey),
|
|
4205
|
-
existing.orderId,
|
|
4206
|
-
next,
|
|
4207
|
-
{ expire: 0 }
|
|
4208
|
-
)
|
|
4209
|
-
]);
|
|
4210
|
-
} catch (error) {
|
|
4211
|
-
import_logger6.logger.error(
|
|
4212
|
-
"runtime trade increase journal failed: %s %s",
|
|
4213
|
-
symbol,
|
|
4214
|
-
error?.message || String(error)
|
|
4215
|
-
);
|
|
4765
|
+
const parsed = parseAIResponse(response.content);
|
|
4766
|
+
const normalized = normalizeAnalysis(parsed);
|
|
4767
|
+
if (!options.signal) {
|
|
4768
|
+
return normalized;
|
|
4216
4769
|
}
|
|
4217
|
-
return
|
|
4770
|
+
return postProcessAiAnalysisByStrategy(
|
|
4771
|
+
options.signal,
|
|
4772
|
+
normalized,
|
|
4773
|
+
options.payload
|
|
4774
|
+
);
|
|
4218
4775
|
};
|
|
4219
|
-
var
|
|
4220
|
-
const
|
|
4221
|
-
|
|
4222
|
-
|
|
4223
|
-
|
|
4224
|
-
const
|
|
4225
|
-
|
|
4226
|
-
|
|
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
|
|
4227
4793
|
);
|
|
4228
|
-
|
|
4229
|
-
|
|
4230
|
-
|
|
4231
|
-
|
|
4232
|
-
const
|
|
4233
|
-
|
|
4234
|
-
|
|
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
|
+
}
|
|
4235
4808
|
);
|
|
4236
|
-
|
|
4237
|
-
|
|
4238
|
-
import_redis3.redisKeys.runtimeActiveTrade(userName, symbol, deploymentId ?? accountId)
|
|
4239
|
-
);
|
|
4240
|
-
return null;
|
|
4241
|
-
}
|
|
4242
|
-
return existing;
|
|
4809
|
+
await (0, import_redis4.setData)(import_redis4.redisKeys.analysis(symbol, signal.signalId), content);
|
|
4810
|
+
return content;
|
|
4243
4811
|
};
|
|
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
|
-
|
|
4270
|
-
|
|
4271
|
-
|
|
4272
|
-
const
|
|
4273
|
-
const
|
|
4274
|
-
|
|
4275
|
-
|
|
4276
|
-
|
|
4277
|
-
|
|
4278
|
-
}
|
|
4279
|
-
const
|
|
4280
|
-
...
|
|
4281
|
-
|
|
4282
|
-
|
|
4283
|
-
|
|
4284
|
-
|
|
4285
|
-
|
|
4286
|
-
|
|
4287
|
-
|
|
4288
|
-
|
|
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
|
|
4289
4858
|
};
|
|
4290
|
-
const dayKey = (0, import_time3.getRuntimeStorageDayKey)(existing.entryTimestamp);
|
|
4291
|
-
const closeDayKey = (0, import_time3.getRuntimeStorageDayKey)(next.exitTimestamp);
|
|
4292
|
-
try {
|
|
4293
|
-
await Promise.all([
|
|
4294
|
-
(0, import_redis3.setData)(import_redis3.redisKeys.runtimeTrade(userName, orderId), next, {
|
|
4295
|
-
expire: import_constants3.TTL_1M
|
|
4296
|
-
}),
|
|
4297
|
-
(0, import_redis3.setHashJsonField)(
|
|
4298
|
-
import_redis3.redisKeys.runtimeTradeBucket(userName, dayKey),
|
|
4299
|
-
orderId,
|
|
4300
|
-
next,
|
|
4301
|
-
{ expire: import_constants3.TTL_1M }
|
|
4302
|
-
),
|
|
4303
|
-
(0, import_redis3.setHashJsonField)(
|
|
4304
|
-
import_redis3.redisKeys.runtimeClosedTradeBucket(userName, closeDayKey),
|
|
4305
|
-
orderId,
|
|
4306
|
-
next,
|
|
4307
|
-
{ expire: import_constants3.TTL_1M }
|
|
4308
|
-
),
|
|
4309
|
-
(0, import_redis3.delKey)(
|
|
4310
|
-
import_redis3.redisKeys.runtimeActiveTrade(
|
|
4311
|
-
userName,
|
|
4312
|
-
symbol,
|
|
4313
|
-
deploymentId ?? accountId
|
|
4314
|
-
)
|
|
4315
|
-
)
|
|
4316
|
-
]);
|
|
4317
|
-
} catch (error) {
|
|
4318
|
-
import_logger6.logger.error(
|
|
4319
|
-
"runtime trade close journal failed: %s %s",
|
|
4320
|
-
symbol,
|
|
4321
|
-
error?.message || String(error)
|
|
4322
|
-
);
|
|
4323
|
-
}
|
|
4324
|
-
return next;
|
|
4325
4859
|
};
|
|
4326
4860
|
|
|
4327
4861
|
// src/strategyHelpers/marketContextStages.ts
|
|
@@ -4333,7 +4867,7 @@ var import_logger7 = require("@tradejs/infra/logger");
|
|
|
4333
4867
|
var import_strategies = require("@tradejs/core/strategies");
|
|
4334
4868
|
|
|
4335
4869
|
// src/binanceBreadthUniverses.ts
|
|
4336
|
-
var
|
|
4870
|
+
var import_node_crypto3 = require("crypto");
|
|
4337
4871
|
|
|
4338
4872
|
// src/config/binanceBreadthUniverses.json
|
|
4339
4873
|
var binanceBreadthUniverses_default = {
|
|
@@ -4572,7 +5106,7 @@ var BINANCE_BREADTH_UNIVERSE_KEYS = [
|
|
|
4572
5106
|
"top50",
|
|
4573
5107
|
"top100"
|
|
4574
5108
|
];
|
|
4575
|
-
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);
|
|
4576
5110
|
var normalizeSymbols = (symbols) => symbols.map((symbol) => symbol.trim().toUpperCase()).filter(Boolean);
|
|
4577
5111
|
var buildBinanceBreadthUniverseSnapshot = ({
|
|
4578
5112
|
rankedSymbols,
|
|
@@ -5596,7 +6130,7 @@ var enrichSignalWithCoinMarketCapContext = async (params) => {
|
|
|
5596
6130
|
};
|
|
5597
6131
|
|
|
5598
6132
|
// src/strategyHelpers/derivativesContext.ts
|
|
5599
|
-
var
|
|
6133
|
+
var import_indicators3 = require("@tradejs/core/indicators");
|
|
5600
6134
|
var import_data3 = require("@tradejs/core/data");
|
|
5601
6135
|
var import_strategies3 = require("@tradejs/core/strategies");
|
|
5602
6136
|
var import_constants4 = require("@tradejs/core/constants");
|
|
@@ -5630,7 +6164,7 @@ var parseLookbackMs = () => {
|
|
|
5630
6164
|
};
|
|
5631
6165
|
var withHourlyFallbackRows = (rowsByInterval) => ({
|
|
5632
6166
|
"15m": rowsByInterval["15m"] ?? [],
|
|
5633
|
-
"1h": (0,
|
|
6167
|
+
"1h": (0, import_indicators3.buildCoinalyzeHourlyRowsWithFallback)({
|
|
5634
6168
|
rows15m: rowsByInterval["15m"],
|
|
5635
6169
|
fallbackRows1h: rowsByInterval["1h"]
|
|
5636
6170
|
})
|
|
@@ -5758,7 +6292,7 @@ var enrichSignalWithDerivativesContext = async (params) => {
|
|
|
5758
6292
|
const targetSymbol = normalizeSymbol(signal.symbol);
|
|
5759
6293
|
const lookbackMs = parseLookbackMs();
|
|
5760
6294
|
const decisionTimeMs = signal.timestamp + (0, import_data3.intervalToMs)(signal.interval);
|
|
5761
|
-
const derivativesEndMs = (0,
|
|
6295
|
+
const derivativesEndMs = (0, import_indicators3.getLastClosedDerivativesBarStartMs)(
|
|
5762
6296
|
decisionTimeMs,
|
|
5763
6297
|
"15m"
|
|
5764
6298
|
);
|
|
@@ -5773,7 +6307,7 @@ var enrichSignalWithDerivativesContext = async (params) => {
|
|
|
5773
6307
|
});
|
|
5774
6308
|
return [
|
|
5775
6309
|
symbol,
|
|
5776
|
-
(0,
|
|
6310
|
+
(0, import_indicators3.buildDerivativesContext)({
|
|
5777
6311
|
symbol,
|
|
5778
6312
|
direction: signal.direction,
|
|
5779
6313
|
timestamp: derivativesEndMs,
|
|
@@ -5800,7 +6334,7 @@ var enrichSignalWithDerivativesContext = async (params) => {
|
|
|
5800
6334
|
lookbackMs,
|
|
5801
6335
|
...params.abortSignal ? { signal: params.abortSignal } : {}
|
|
5802
6336
|
});
|
|
5803
|
-
const context = (0,
|
|
6337
|
+
const context = (0, import_indicators3.buildDerivativesContext)({
|
|
5804
6338
|
symbol: targetSymbol,
|
|
5805
6339
|
direction: signal.direction,
|
|
5806
6340
|
timestamp: derivativesEndMs,
|
|
@@ -5848,7 +6382,7 @@ var import_hyperliquidWhales2 = require("@tradejs/infra/timescale/hyperliquidWha
|
|
|
5848
6382
|
var import_logger10 = require("@tradejs/infra/logger");
|
|
5849
6383
|
|
|
5850
6384
|
// src/hyperliquidWhaleUniverse.ts
|
|
5851
|
-
var
|
|
6385
|
+
var import_node_crypto4 = require("crypto");
|
|
5852
6386
|
|
|
5853
6387
|
// src/config/hyperliquidPerpUniverse.json
|
|
5854
6388
|
var hyperliquidPerpUniverse_default = {
|
|
@@ -6020,7 +6554,7 @@ var hyperliquidWhales_default = {
|
|
|
6020
6554
|
};
|
|
6021
6555
|
|
|
6022
6556
|
// src/hyperliquidWhaleUniverse.ts
|
|
6023
|
-
var fingerprint2 = (value) => (0,
|
|
6557
|
+
var fingerprint2 = (value) => (0, import_node_crypto4.createHash)("sha256").update(JSON.stringify(value)).digest("hex").slice(0, 16);
|
|
6024
6558
|
var validateUnique = (values, label) => {
|
|
6025
6559
|
if (new Set(values).size !== values.length) {
|
|
6026
6560
|
throw new Error(`Hyperliquid ${label} snapshot contains duplicates`);
|
|
@@ -6507,6 +7041,7 @@ var enrichSignalWithMarketContextStages = async ({
|
|
|
6507
7041
|
})
|
|
6508
7042
|
});
|
|
6509
7043
|
}
|
|
7044
|
+
await enrichSignalWithTickerProfile(signal);
|
|
6510
7045
|
const results = [];
|
|
6511
7046
|
for (const stage of stages) {
|
|
6512
7047
|
if (abortSignal?.aborted) break;
|
|
@@ -6554,12 +7089,12 @@ var findReplayAiAnalysis = ({
|
|
|
6554
7089
|
direction,
|
|
6555
7090
|
ai
|
|
6556
7091
|
}) => {
|
|
6557
|
-
const
|
|
6558
|
-
if (!Array.isArray(
|
|
7092
|
+
const snapshots2 = ai?.replayAnalyses;
|
|
7093
|
+
if (!Array.isArray(snapshots2) || !snapshots2.length) {
|
|
6559
7094
|
return void 0;
|
|
6560
7095
|
}
|
|
6561
7096
|
let best = null;
|
|
6562
|
-
for (const snapshot2 of
|
|
7097
|
+
for (const snapshot2 of snapshots2) {
|
|
6563
7098
|
if (snapshot2.symbol !== signal.symbol || snapshot2.direction !== direction || snapshot2.strategy && snapshot2.strategy !== signal.strategy) {
|
|
6564
7099
|
continue;
|
|
6565
7100
|
}
|
|
@@ -6719,7 +7254,9 @@ var executeEntryOrder = async ({
|
|
|
6719
7254
|
beforePlaceOrder,
|
|
6720
7255
|
recordRuntimeTrade = true,
|
|
6721
7256
|
leverage,
|
|
6722
|
-
executionLimits
|
|
7257
|
+
executionLimits,
|
|
7258
|
+
markPortfolioSubmitted,
|
|
7259
|
+
rejectPortfolioSubmission
|
|
6723
7260
|
}) => {
|
|
6724
7261
|
await beforePlaceOrder?.();
|
|
6725
7262
|
const previousPosition = positionIntent === "increase" ? await connector.getPosition(symbol) : null;
|
|
@@ -6752,12 +7289,12 @@ var executeEntryOrder = async ({
|
|
|
6752
7289
|
if (executionLimits?.maxLossValue != null) {
|
|
6753
7290
|
signal.riskDecision = {
|
|
6754
7291
|
stage: "order_plan",
|
|
6755
|
-
status: "approved",
|
|
6756
|
-
reason: "PLANNED_LOSS_WITHIN_MAX_LOSS_VALUE",
|
|
7292
|
+
status: economics2.riskExceeded ? "warning" : "approved",
|
|
7293
|
+
reason: economics2.riskExceeded ? "ENTRY_RISK_EXCEEDS_MAX_LOSS_AT_SUBMISSION" : "PLANNED_LOSS_WITHIN_MAX_LOSS_VALUE",
|
|
6757
7294
|
plannedLossValue: economics2.plannedLossValue,
|
|
6758
7295
|
maxLossValue: executionLimits.maxLossValue,
|
|
6759
7296
|
limitUtilization: economics2.plannedLossValue / executionLimits.maxLossValue,
|
|
6760
|
-
enforced:
|
|
7297
|
+
enforced: false
|
|
6761
7298
|
};
|
|
6762
7299
|
}
|
|
6763
7300
|
}
|
|
@@ -6792,7 +7329,13 @@ var executeEntryOrder = async ({
|
|
|
6792
7329
|
...typeof leverage === "number" && Number.isFinite(leverage) ? { leverage } : {},
|
|
6793
7330
|
orderId,
|
|
6794
7331
|
signal,
|
|
6795
|
-
...
|
|
7332
|
+
...rejectPortfolioSubmission ? { onRejected: rejectPortfolioSubmission } : {},
|
|
7333
|
+
...beforeSubmit || markPortfolioSubmitted ? {
|
|
7334
|
+
beforeSubmit: async (context) => {
|
|
7335
|
+
await beforeSubmit?.(context);
|
|
7336
|
+
await markPortfolioSubmitted?.(context.qty);
|
|
7337
|
+
}
|
|
7338
|
+
} : {}
|
|
6796
7339
|
});
|
|
6797
7340
|
const orderAckTime = Date.now();
|
|
6798
7341
|
const placedQty = typeof signal.orderQty === "number" && Number.isFinite(signal.orderQty) && signal.orderQty > 0 ? signal.orderQty : qty;
|
|
@@ -7780,7 +8323,7 @@ var handleProtectDecision = async ({
|
|
|
7780
8323
|
}
|
|
7781
8324
|
return decision.code;
|
|
7782
8325
|
};
|
|
7783
|
-
var
|
|
8326
|
+
var executeAdmittedEntryDecision = async ({
|
|
7784
8327
|
connector,
|
|
7785
8328
|
symbol,
|
|
7786
8329
|
decision,
|
|
@@ -7794,7 +8337,9 @@ var executeEntryDecision = async ({
|
|
|
7794
8337
|
ai,
|
|
7795
8338
|
recordRuntimeJournal,
|
|
7796
8339
|
invokeStageHooks,
|
|
7797
|
-
notifyRuntimeError
|
|
8340
|
+
notifyRuntimeError,
|
|
8341
|
+
markPortfolioSubmitted,
|
|
8342
|
+
rejectPortfolioSubmission
|
|
7798
8343
|
}) => {
|
|
7799
8344
|
const signal = decision.signal;
|
|
7800
8345
|
const beforePlaceOrder = async () => {
|
|
@@ -7841,6 +8386,8 @@ var executeEntryDecision = async ({
|
|
|
7841
8386
|
...Number.isFinite(Number(hookCtx.strategyConfig.LEVERAGE)) ? { leverage: Number(hookCtx.strategyConfig.LEVERAGE) } : {},
|
|
7842
8387
|
signal,
|
|
7843
8388
|
beforePlaceOrder,
|
|
8389
|
+
markPortfolioSubmitted,
|
|
8390
|
+
rejectPortfolioSubmission,
|
|
7844
8391
|
recordRuntimeTrade: recordRuntimeJournal,
|
|
7845
8392
|
executionLimits: resolveEntryExecutionLimits(
|
|
7846
8393
|
hookCtx.strategyConfig,
|
|
@@ -7981,6 +8528,37 @@ var executeEntryDecision = async ({
|
|
|
7981
8528
|
}
|
|
7982
8529
|
return signal ?? decision.code;
|
|
7983
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
|
+
}
|
|
7984
8562
|
|
|
7985
8563
|
// src/strategyRuntime.ts
|
|
7986
8564
|
var cloneWithPropertyDescriptors = (value) => Object.create(
|
|
@@ -8008,6 +8586,7 @@ var createStrategyRuntime = ({
|
|
|
8008
8586
|
};
|
|
8009
8587
|
const creator = async ({
|
|
8010
8588
|
userName,
|
|
8589
|
+
connectorName,
|
|
8011
8590
|
config: baseConfig,
|
|
8012
8591
|
symbol,
|
|
8013
8592
|
universe: requestedUniverse,
|
|
@@ -8046,6 +8625,17 @@ var createStrategyRuntime = ({
|
|
|
8046
8625
|
const projectConfig = await loadTradejsConfig(projectRoot);
|
|
8047
8626
|
const projectHooks = projectConfig.hooks;
|
|
8048
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;
|
|
8049
8639
|
const researchPolicyValue = baseConfig?.RESEARCH_ENTRY_POLICY ?? config.RESEARCH_ENTRY_POLICY;
|
|
8050
8640
|
const researchEntryPolicy = researchPolicyValue == null ? void 0 : parseResearchEntryPolicy(researchPolicyValue);
|
|
8051
8641
|
if (researchEntryPolicy && (env !== "BACKTEST" || Reflect.get(connector, "__tradejsTestConnector") !== true))
|
|
@@ -8083,6 +8673,9 @@ var createStrategyRuntime = ({
|
|
|
8083
8673
|
});
|
|
8084
8674
|
const strategyPolicyProfile = getPolicyProfile();
|
|
8085
8675
|
const indicatorPeriods = (0, import_strategies6.buildDefaultIndicatorPeriods)(config);
|
|
8676
|
+
const strategyConfig = { ...config };
|
|
8677
|
+
delete strategyConfig.RECORD_RUNTIME_TRADES;
|
|
8678
|
+
delete strategyConfig.SIMULATE_ORDERS;
|
|
8086
8679
|
const hookBase = {
|
|
8087
8680
|
connector,
|
|
8088
8681
|
strategyName,
|
|
@@ -8093,7 +8686,7 @@ var createStrategyRuntime = ({
|
|
|
8093
8686
|
accountId,
|
|
8094
8687
|
deploymentId,
|
|
8095
8688
|
policyProfileId: strategyPolicyProfile?.id ?? requestedPolicyProfileId,
|
|
8096
|
-
strategyConfig
|
|
8689
|
+
strategyConfig,
|
|
8097
8690
|
env,
|
|
8098
8691
|
isConfigFromBacktest
|
|
8099
8692
|
};
|
|
@@ -8161,7 +8754,7 @@ var createStrategyRuntime = ({
|
|
|
8161
8754
|
);
|
|
8162
8755
|
}
|
|
8163
8756
|
};
|
|
8164
|
-
const invokeHook = async (stage, hook, params, errorContext = {}) => {
|
|
8757
|
+
const invokeHook = async (stage, hook, params, errorContext = {}, failureResult) => {
|
|
8165
8758
|
if (!hook) {
|
|
8166
8759
|
return void 0;
|
|
8167
8760
|
}
|
|
@@ -8181,17 +8774,18 @@ var createStrategyRuntime = ({
|
|
|
8181
8774
|
entry: errorContext.entry,
|
|
8182
8775
|
market: errorContext.market
|
|
8183
8776
|
});
|
|
8184
|
-
return
|
|
8777
|
+
return failureResult;
|
|
8185
8778
|
}
|
|
8186
8779
|
};
|
|
8187
|
-
const invokeProjectHooks = async (stage, params, errorContext = {}) => {
|
|
8780
|
+
const invokeProjectHooks = async (stage, params, errorContext = {}, failureResult) => {
|
|
8188
8781
|
const results = [];
|
|
8189
8782
|
for (const hook of getProjectHookList(stage)) {
|
|
8190
8783
|
const result = await invokeHook(
|
|
8191
8784
|
stage,
|
|
8192
8785
|
hook,
|
|
8193
8786
|
params,
|
|
8194
|
-
errorContext
|
|
8787
|
+
errorContext,
|
|
8788
|
+
failureResult
|
|
8195
8789
|
);
|
|
8196
8790
|
if (result !== void 0) {
|
|
8197
8791
|
results.push(result);
|
|
@@ -8206,10 +8800,12 @@ var createStrategyRuntime = ({
|
|
|
8206
8800
|
return invokeHook(stage, hook, params, errorContext);
|
|
8207
8801
|
};
|
|
8208
8802
|
const invokeGateHooks = async (stage, hook, params, errorContext = {}) => {
|
|
8803
|
+
const failureResult = stage === "beforeEntryGate" ? { allow: false, reason: "ERROR" } : void 0;
|
|
8209
8804
|
const projectResults = await invokeProjectHooks(
|
|
8210
8805
|
stage,
|
|
8211
8806
|
params,
|
|
8212
|
-
errorContext
|
|
8807
|
+
errorContext,
|
|
8808
|
+
failureResult
|
|
8213
8809
|
);
|
|
8214
8810
|
const projectBlock = projectResults.find(
|
|
8215
8811
|
(result) => result?.allow === false
|
|
@@ -8221,7 +8817,8 @@ var createStrategyRuntime = ({
|
|
|
8221
8817
|
stage,
|
|
8222
8818
|
hook,
|
|
8223
8819
|
params,
|
|
8224
|
-
errorContext
|
|
8820
|
+
errorContext,
|
|
8821
|
+
failureResult
|
|
8225
8822
|
);
|
|
8226
8823
|
};
|
|
8227
8824
|
const applyProjectAfterCoreDecisionHooks = async ({
|
|
@@ -8337,6 +8934,20 @@ var createStrategyRuntime = ({
|
|
|
8337
8934
|
interval: decisionInterval
|
|
8338
8935
|
}) => {
|
|
8339
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
|
+
}
|
|
8340
8951
|
if (!coreContextRequirements.has("hyperliquidWhales")) {
|
|
8341
8952
|
return baseContext;
|
|
8342
8953
|
}
|
|
@@ -8367,11 +8978,8 @@ var createStrategyRuntime = ({
|
|
|
8367
8978
|
return enrichedBaseContext;
|
|
8368
8979
|
}
|
|
8369
8980
|
});
|
|
8370
|
-
const coreConfig = { ...config };
|
|
8371
|
-
delete coreConfig.RECORD_RUNTIME_TRADES;
|
|
8372
|
-
delete coreConfig.SIMULATE_ORDERS;
|
|
8373
8981
|
const core = await createCore({
|
|
8374
|
-
config:
|
|
8982
|
+
config: strategyConfig,
|
|
8375
8983
|
data,
|
|
8376
8984
|
strategyApi,
|
|
8377
8985
|
indicatorsState
|
|
@@ -8507,6 +9115,8 @@ var createStrategyRuntime = ({
|
|
|
8507
9115
|
runtime: pending2.runtime
|
|
8508
9116
|
});
|
|
8509
9117
|
return executeEntryDecision({
|
|
9118
|
+
portfolio,
|
|
9119
|
+
portfolioTime: env === "BACKTEST" || env === "PARITY" ? candle.timestamp : Date.now(),
|
|
8510
9120
|
connector,
|
|
8511
9121
|
symbol,
|
|
8512
9122
|
decision: pending2.decision,
|
|
@@ -8524,6 +9134,10 @@ var createStrategyRuntime = ({
|
|
|
8524
9134
|
});
|
|
8525
9135
|
};
|
|
8526
9136
|
const runWithDecisionOverride = async (candle, btcCandle, options = {}) => {
|
|
9137
|
+
if (portfolioContext)
|
|
9138
|
+
await portfolioContext(
|
|
9139
|
+
env === "BACKTEST" || env === "PARITY" ? candle.timestamp : Date.now()
|
|
9140
|
+
);
|
|
8527
9141
|
appendCurrentMarketData(candle, btcCandle, options.ethCandle);
|
|
8528
9142
|
const ethCandle = resolveEthCandle(candle, options.ethCandle);
|
|
8529
9143
|
indicatorsState.setCurrentBar(candle, btcCandle, ethCandle);
|
|
@@ -8694,6 +9308,13 @@ var createStrategyRuntime = ({
|
|
|
8694
9308
|
});
|
|
8695
9309
|
const signal = decision.signal;
|
|
8696
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
|
+
};
|
|
8697
9318
|
if (runtimeLineage) signal.runtimeLineage = runtimeLineage;
|
|
8698
9319
|
if (universe) signal.universe = universe;
|
|
8699
9320
|
if (assetClass) signal.assetClass = assetClass;
|
|
@@ -8933,6 +9554,8 @@ var createStrategyRuntime = ({
|
|
|
8933
9554
|
});
|
|
8934
9555
|
}
|
|
8935
9556
|
return executeEntryDecision({
|
|
9557
|
+
portfolio,
|
|
9558
|
+
portfolioTime: env === "BACKTEST" || env === "PARITY" ? candle.timestamp : Date.now(),
|
|
8936
9559
|
connector,
|
|
8937
9560
|
symbol,
|
|
8938
9561
|
decision,
|
|
@@ -9044,7 +9667,7 @@ var normalizeForCanonicalJson = (value) => {
|
|
|
9044
9667
|
}
|
|
9045
9668
|
return value;
|
|
9046
9669
|
};
|
|
9047
|
-
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)}`;
|
|
9048
9671
|
var computeStrategyRevision = ({
|
|
9049
9672
|
strategyName,
|
|
9050
9673
|
strategyPackage,
|
|
@@ -9629,18 +10252,18 @@ var loadRuntimeTrades = async (userName, {
|
|
|
9629
10252
|
const [bucketTradeGroups, legacyKeys] = await Promise.all([
|
|
9630
10253
|
Promise.all(
|
|
9631
10254
|
dayKeys.map(
|
|
9632
|
-
(dayKey) => (0,
|
|
9633
|
-
|
|
10255
|
+
(dayKey) => (0, import_redis5.getHashJsonValues)(
|
|
10256
|
+
import_redis5.redisKeys.runtimeTradeBucket(userName, dayKey)
|
|
9634
10257
|
)
|
|
9635
10258
|
)
|
|
9636
10259
|
),
|
|
9637
|
-
(0,
|
|
10260
|
+
(0, import_redis5.getKeys)(import_redis5.redisKeys.runtimeTrades(userName))
|
|
9638
10261
|
]);
|
|
9639
10262
|
const legacyTradeKeys = legacyKeys.filter(
|
|
9640
|
-
(key) => !key.startsWith(
|
|
10263
|
+
(key) => !key.startsWith(import_redis5.redisKeys.runtimeTradeBuckets(userName))
|
|
9641
10264
|
);
|
|
9642
10265
|
const legacyTrades = await Promise.all(
|
|
9643
|
-
legacyTradeKeys.map((key) => (0,
|
|
10266
|
+
legacyTradeKeys.map((key) => (0, import_redis5.getData)(key, null))
|
|
9644
10267
|
);
|
|
9645
10268
|
const dedupedTrades = /* @__PURE__ */ new Map();
|
|
9646
10269
|
for (const trade of [...legacyTrades, ...bucketTradeGroups.flat()]) {
|
|
@@ -9727,8 +10350,8 @@ var loadExchangeRange = async ({
|
|
|
9727
10350
|
}
|
|
9728
10351
|
};
|
|
9729
10352
|
var loadActiveRuntimeOrderIds = async (userName) => {
|
|
9730
|
-
const keys = await (0,
|
|
9731
|
-
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)));
|
|
9732
10355
|
return new Set(
|
|
9733
10356
|
refs.map(
|
|
9734
10357
|
(ref) => typeof ref?.orderId === "string" && ref.orderId.trim() ? ref.orderId.trim() : null
|