@tradejs/node 3.1.29-beta.290 → 3.1.29-beta.292
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 +2594 -1904
- package/dist/backtest.mjs +3 -2
- package/dist/{chunk-PB2KHBGH.mjs → chunk-EVKQ4DI6.mjs} +461 -33
- package/dist/chunk-NJEYNGRU.mjs +226 -0
- package/dist/{chunk-UL5Z46JL.mjs → chunk-PYJWXAYR.mjs} +97 -30
- package/dist/{chunk-BO42QWAB.mjs → chunk-W6PPAADK.mjs} +1 -1
- package/dist/cli.js +834 -773
- package/dist/cli.mjs +1 -1
- package/dist/diagnostics.js +2611 -1921
- package/dist/diagnostics.mjs +4 -3
- package/dist/mcp.d.mts +1 -1
- package/dist/mcp.d.ts +1 -1
- package/dist/mcp.js +2634 -1944
- package/dist/mcp.mjs +4 -3
- package/dist/portfolio.d.mts +28 -0
- package/dist/portfolio.d.ts +28 -0
- package/dist/portfolio.js +243 -0
- package/dist/portfolio.mjs +9 -0
- package/dist/registry.js +2614 -1924
- package/dist/registry.mjs +3 -2
- package/dist/runtimeDashboard.js +2335 -1643
- package/dist/runtimeDashboard.mjs +4 -3
- package/dist/runtimeStrategies.js +2615 -1925
- package/dist/runtimeStrategies.mjs +4 -3
- package/dist/strategies.d.mts +3 -1
- package/dist/strategies.d.ts +3 -1
- package/dist/strategies.js +2153 -1463
- package/dist/strategies.mjs +3 -2
- package/package.json +10 -5
package/dist/runtimeDashboard.js
CHANGED
|
@@ -784,7 +784,7 @@ var import_time4 = require("@tradejs/core/time");
|
|
|
784
784
|
var import_logger15 = require("@tradejs/infra/logger");
|
|
785
785
|
var import_runtimeDeploymentEvents = require("@tradejs/infra/runtimeDeploymentEvents");
|
|
786
786
|
var import_tradingAccounts3 = require("@tradejs/infra/tradingAccounts");
|
|
787
|
-
var
|
|
787
|
+
var import_redis5 = require("@tradejs/infra/redis");
|
|
788
788
|
|
|
789
789
|
// src/connectorsRegistry.ts
|
|
790
790
|
var import_logger4 = require("@tradejs/infra/logger");
|
|
@@ -2083,1376 +2083,2156 @@ 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
|
+
let retry = 0;
|
|
2229
|
+
let attemptId = entry.id;
|
|
2230
|
+
let needsReservation = false;
|
|
2231
|
+
try {
|
|
2232
|
+
const value = await send(
|
|
2233
|
+
async (qty) => {
|
|
2234
|
+
if (!Number.isFinite(qty) || qty <= 0)
|
|
2235
|
+
throw new Error("Invalid submitted portfolio quantity");
|
|
2236
|
+
if (needsReservation) {
|
|
2237
|
+
attemptId = `${entry.id.slice(0, 30)}r${++retry}`;
|
|
2238
|
+
const admission = await transact(
|
|
2239
|
+
now2,
|
|
2240
|
+
(s, o) => {
|
|
2241
|
+
const r = (0, import_portfolio.reservePortfolioEntry)(policy, strategy, s, o, {
|
|
2242
|
+
...entry,
|
|
2243
|
+
id: attemptId,
|
|
2244
|
+
qty,
|
|
2245
|
+
submitted: true,
|
|
2246
|
+
oppositeHandled: false
|
|
2247
|
+
});
|
|
2248
|
+
return { state: r.state, result: r.reason };
|
|
2249
|
+
},
|
|
2250
|
+
true
|
|
2251
|
+
);
|
|
2252
|
+
if (admission.result) throw new Error(admission.result);
|
|
2253
|
+
needsReservation = false;
|
|
2254
|
+
submitted = true;
|
|
2255
|
+
return attemptId;
|
|
2256
|
+
}
|
|
2257
|
+
const update = await updateReservation(now2, attemptId, true, qty);
|
|
2258
|
+
if (!update.result)
|
|
2259
|
+
throw new Error("PORTFOLIO_SUBMISSION_NO_LONGER_ADMITTED");
|
|
2260
|
+
submitted = true;
|
|
2261
|
+
return attemptId;
|
|
2262
|
+
},
|
|
2263
|
+
async (retrying = false) => {
|
|
2264
|
+
await transact(
|
|
2265
|
+
now2,
|
|
2266
|
+
(s) => ({
|
|
2267
|
+
state: {
|
|
2268
|
+
...s,
|
|
2269
|
+
reservations: s.reservations.filter(
|
|
2270
|
+
(r) => r.id !== attemptId
|
|
2271
|
+
),
|
|
2272
|
+
completedSignals: {
|
|
2273
|
+
...Object.fromEntries(
|
|
2274
|
+
Object.entries(s.completedSignals).filter(
|
|
2275
|
+
([id]) => !retrying || id !== entry.signalId
|
|
2276
|
+
)
|
|
2277
|
+
),
|
|
2278
|
+
...retrying ? {} : { [entry.signalId]: now2 }
|
|
2279
|
+
}
|
|
2280
|
+
},
|
|
2281
|
+
result: void 0
|
|
2282
|
+
}),
|
|
2283
|
+
true
|
|
2284
|
+
);
|
|
2285
|
+
submitted = false;
|
|
2286
|
+
needsReservation = retrying;
|
|
2287
|
+
}
|
|
2288
|
+
);
|
|
2289
|
+
if (!submitted) await updateReservation(now2, attemptId, false);
|
|
2290
|
+
else await tick(now2, true);
|
|
2291
|
+
return { value };
|
|
2292
|
+
} catch (error) {
|
|
2293
|
+
if (!submitted) await updateReservation(now2, attemptId, false);
|
|
2294
|
+
throw error;
|
|
2295
|
+
}
|
|
2278
2296
|
}
|
|
2279
2297
|
};
|
|
2280
|
-
}
|
|
2281
|
-
|
|
2282
|
-
|
|
2283
|
-
|
|
2284
|
-
|
|
2285
|
-
|
|
2286
|
-
|
|
2287
|
-
|
|
2298
|
+
}
|
|
2299
|
+
function createMemoryPortfolioStore() {
|
|
2300
|
+
let state = null;
|
|
2301
|
+
return {
|
|
2302
|
+
read: async () => state === null ? null : structuredClone(state),
|
|
2303
|
+
compare: async (expected, next) => {
|
|
2304
|
+
if ((state?.revision ?? -1) !== (expected?.revision ?? -1)) return false;
|
|
2305
|
+
state = structuredClone(next);
|
|
2306
|
+
return true;
|
|
2307
|
+
}
|
|
2308
|
+
};
|
|
2309
|
+
}
|
|
2310
|
+
|
|
2311
|
+
// src/runtimeJournal.ts
|
|
2312
|
+
var import_node_crypto = require("crypto");
|
|
2313
|
+
var import_constants3 = require("@tradejs/core/constants");
|
|
2314
|
+
var import_time3 = require("@tradejs/core/time");
|
|
2315
|
+
var import_trade = require("@tradejs/core/trade");
|
|
2316
|
+
var import_logger5 = require("@tradejs/infra/logger");
|
|
2317
|
+
var import_redis2 = require("@tradejs/infra/redis");
|
|
2318
|
+
var now = () => Date.now();
|
|
2319
|
+
var toRandomOrderSuffix = () => (0, import_node_crypto.randomUUID)().replace(/-/g, "").slice(0, 12).toLowerCase();
|
|
2320
|
+
var calculateClosedPnl = ({
|
|
2321
|
+
direction,
|
|
2322
|
+
entryPrice,
|
|
2323
|
+
exitPrice,
|
|
2324
|
+
qty
|
|
2288
2325
|
}) => {
|
|
2289
|
-
|
|
2290
|
-
|
|
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)
|
|
2319
|
-
};
|
|
2320
|
-
const identity = {
|
|
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;
|
|
2326
|
+
const pnl = direction === "LONG" ? (exitPrice - entryPrice) * qty : (entryPrice - exitPrice) * qty;
|
|
2327
|
+
return Number.isFinite(pnl) ? pnl : null;
|
|
2340
2328
|
};
|
|
2341
|
-
|
|
2342
|
-
|
|
2343
|
-
|
|
2344
|
-
|
|
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");
|
|
2351
|
-
|
|
2352
|
-
// src/strategy/entrySubmissionValidation.ts
|
|
2353
|
-
var import_logger5 = require("@tradejs/infra/logger");
|
|
2354
|
-
|
|
2355
|
-
// src/strategy/entryExecutionValidation.ts
|
|
2356
|
-
var EntrySubmissionValidationError = class extends Error {
|
|
2357
|
-
constructor() {
|
|
2358
|
-
super(...arguments);
|
|
2359
|
-
this.name = "EntrySubmissionValidationError";
|
|
2329
|
+
var createRuntimeOrderId = (strategy) => {
|
|
2330
|
+
const prefix = (0, import_trade.createRuntimeOrderLinkPrefix)(strategy);
|
|
2331
|
+
if (prefix === "tjs-") {
|
|
2332
|
+
return `tjs-${(0, import_node_crypto.randomUUID)().replace(/-/g, "").slice(0, 24).toLowerCase()}`;
|
|
2360
2333
|
}
|
|
2334
|
+
return `${prefix}${toRandomOrderSuffix()}`;
|
|
2361
2335
|
};
|
|
2362
|
-
var
|
|
2363
|
-
const
|
|
2364
|
-
|
|
2365
|
-
|
|
2366
|
-
|
|
2367
|
-
const
|
|
2368
|
-
|
|
2369
|
-
|
|
2370
|
-
|
|
2371
|
-
|
|
2372
|
-
|
|
2373
|
-
|
|
2374
|
-
|
|
2375
|
-
|
|
2376
|
-
|
|
2377
|
-
|
|
2378
|
-
|
|
2379
|
-
|
|
2380
|
-
|
|
2381
|
-
|
|
2382
|
-
|
|
2383
|
-
|
|
2384
|
-
|
|
2385
|
-
|
|
2386
|
-
|
|
2387
|
-
|
|
2388
|
-
|
|
2389
|
-
|
|
2390
|
-
|
|
2391
|
-
|
|
2392
|
-
|
|
2393
|
-
|
|
2394
|
-
|
|
2395
|
-
|
|
2396
|
-
|
|
2397
|
-
|
|
2398
|
-
|
|
2399
|
-
|
|
2400
|
-
|
|
2401
|
-
|
|
2402
|
-
|
|
2403
|
-
|
|
2404
|
-
|
|
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"
|
|
2336
|
+
var recordRuntimeTradeOpen = async (params) => {
|
|
2337
|
+
const { userName } = params;
|
|
2338
|
+
if (!userName) {
|
|
2339
|
+
return null;
|
|
2340
|
+
}
|
|
2341
|
+
const record = {
|
|
2342
|
+
...params,
|
|
2343
|
+
entryCount: 1,
|
|
2344
|
+
lastEntryPrice: params.entryPrice,
|
|
2345
|
+
lastEntryQty: params.qty,
|
|
2346
|
+
lastEntryTimestamp: params.entryTimestamp,
|
|
2347
|
+
status: "active",
|
|
2348
|
+
currentPrice: params.entryPrice,
|
|
2349
|
+
currentPnl: 0,
|
|
2350
|
+
closedPnl: null,
|
|
2351
|
+
exitPrice: null,
|
|
2352
|
+
exitTimestamp: null,
|
|
2353
|
+
lastSyncedAt: now()
|
|
2354
|
+
};
|
|
2355
|
+
const dayKey = (0, import_time3.getRuntimeStorageDayKey)(record.entryTimestamp);
|
|
2356
|
+
const runtimeScopeId = record.deploymentId ?? record.accountId;
|
|
2357
|
+
try {
|
|
2358
|
+
await Promise.all([
|
|
2359
|
+
(0, import_redis2.setData)(import_redis2.redisKeys.runtimeTrade(userName, record.orderId), record, {
|
|
2360
|
+
expire: 0
|
|
2361
|
+
}),
|
|
2362
|
+
(0, import_redis2.setHashJsonField)(
|
|
2363
|
+
import_redis2.redisKeys.runtimeTradeBucket(userName, dayKey),
|
|
2364
|
+
record.orderId,
|
|
2365
|
+
record,
|
|
2366
|
+
{ expire: 0 }
|
|
2367
|
+
),
|
|
2368
|
+
(0, import_redis2.setData)(
|
|
2369
|
+
import_redis2.redisKeys.runtimeActiveTrade(userName, record.symbol, runtimeScopeId),
|
|
2370
|
+
{ orderId: record.orderId },
|
|
2371
|
+
{ expire: 0 }
|
|
2372
|
+
)
|
|
2373
|
+
]);
|
|
2374
|
+
} catch (error) {
|
|
2375
|
+
import_logger5.logger.error(
|
|
2376
|
+
"runtime trade open journal failed: %s %s",
|
|
2377
|
+
record.symbol,
|
|
2378
|
+
error?.message || String(error)
|
|
2411
2379
|
);
|
|
2412
|
-
|
|
2413
|
-
return
|
|
2380
|
+
}
|
|
2381
|
+
return record;
|
|
2414
2382
|
};
|
|
2415
|
-
|
|
2416
|
-
|
|
2417
|
-
|
|
2418
|
-
|
|
2419
|
-
|
|
2420
|
-
|
|
2421
|
-
|
|
2422
|
-
|
|
2423
|
-
|
|
2424
|
-
|
|
2425
|
-
|
|
2426
|
-
|
|
2427
|
-
|
|
2428
|
-
|
|
2429
|
-
|
|
2430
|
-
|
|
2431
|
-
|
|
2432
|
-
|
|
2383
|
+
var recordRuntimeTradeIncrease = async (params) => {
|
|
2384
|
+
const {
|
|
2385
|
+
userName,
|
|
2386
|
+
strategy,
|
|
2387
|
+
symbol,
|
|
2388
|
+
direction,
|
|
2389
|
+
resultingQty,
|
|
2390
|
+
resultingEntryPrice,
|
|
2391
|
+
addedQty,
|
|
2392
|
+
addedEntryPrice,
|
|
2393
|
+
entryTimestamp,
|
|
2394
|
+
fee,
|
|
2395
|
+
accountId,
|
|
2396
|
+
deploymentId
|
|
2397
|
+
} = params;
|
|
2398
|
+
if (!userName) {
|
|
2399
|
+
return null;
|
|
2400
|
+
}
|
|
2401
|
+
const existing = await getActiveRuntimeTrade({
|
|
2402
|
+
userName,
|
|
2403
|
+
symbol,
|
|
2404
|
+
accountId,
|
|
2405
|
+
deploymentId
|
|
2406
|
+
});
|
|
2407
|
+
if (!existing || existing.strategy !== strategy || existing.direction !== direction) {
|
|
2408
|
+
return null;
|
|
2433
2409
|
}
|
|
2410
|
+
const addedFee = typeof fee === "number" && Number.isFinite(fee) ? fee : 0;
|
|
2411
|
+
const openFee = (existing.openFee ?? existing.fee ?? 0) + addedFee;
|
|
2412
|
+
const totalFee = (existing.totalFee ?? existing.fee ?? 0) + addedFee;
|
|
2413
|
+
const next = {
|
|
2414
|
+
...existing,
|
|
2415
|
+
...params.orderLeverage ? { orderLeverage: params.orderLeverage } : {},
|
|
2416
|
+
qty: resultingQty,
|
|
2417
|
+
entryPrice: resultingEntryPrice,
|
|
2418
|
+
entryCount: Math.max(1, existing.entryCount ?? 1) + 1,
|
|
2419
|
+
lastEntryPrice: addedEntryPrice,
|
|
2420
|
+
lastEntryQty: addedQty,
|
|
2421
|
+
lastEntryTimestamp: entryTimestamp,
|
|
2422
|
+
currentPrice: resultingEntryPrice,
|
|
2423
|
+
currentPnl: 0,
|
|
2424
|
+
fee: openFee,
|
|
2425
|
+
openFee,
|
|
2426
|
+
totalFee,
|
|
2427
|
+
lastSyncedAt: now()
|
|
2428
|
+
};
|
|
2429
|
+
const dayKey = (0, import_time3.getRuntimeStorageDayKey)(existing.entryTimestamp);
|
|
2434
2430
|
try {
|
|
2435
|
-
|
|
2436
|
-
|
|
2437
|
-
|
|
2438
|
-
|
|
2439
|
-
|
|
2440
|
-
|
|
2441
|
-
|
|
2442
|
-
|
|
2443
|
-
|
|
2444
|
-
|
|
2445
|
-
|
|
2446
|
-
ask,
|
|
2447
|
-
arrivalMid,
|
|
2448
|
-
spreadBps
|
|
2449
|
-
};
|
|
2431
|
+
await Promise.all([
|
|
2432
|
+
(0, import_redis2.setData)(import_redis2.redisKeys.runtimeTrade(userName, existing.orderId), next, {
|
|
2433
|
+
expire: 0
|
|
2434
|
+
}),
|
|
2435
|
+
(0, import_redis2.setHashJsonField)(
|
|
2436
|
+
import_redis2.redisKeys.runtimeTradeBucket(userName, dayKey),
|
|
2437
|
+
existing.orderId,
|
|
2438
|
+
next,
|
|
2439
|
+
{ expire: 0 }
|
|
2440
|
+
)
|
|
2441
|
+
]);
|
|
2450
2442
|
} catch (error) {
|
|
2451
|
-
import_logger5.logger.
|
|
2452
|
-
"runtime
|
|
2443
|
+
import_logger5.logger.error(
|
|
2444
|
+
"runtime trade increase journal failed: %s %s",
|
|
2453
2445
|
symbol,
|
|
2454
2446
|
error?.message || String(error)
|
|
2455
2447
|
);
|
|
2456
|
-
return {
|
|
2457
|
-
arrivalSnapshotTime: Date.now(),
|
|
2458
|
-
arrivalSource: "top_of_book_error",
|
|
2459
|
-
bid: null,
|
|
2460
|
-
ask: null,
|
|
2461
|
-
arrivalMid: null,
|
|
2462
|
-
spreadBps: null
|
|
2463
|
-
};
|
|
2464
2448
|
}
|
|
2449
|
+
return next;
|
|
2465
2450
|
};
|
|
2466
|
-
var
|
|
2467
|
-
|
|
2468
|
-
|
|
2469
|
-
|
|
2470
|
-
ask,
|
|
2471
|
-
arrivalMid,
|
|
2472
|
-
takeProfits,
|
|
2473
|
-
stopLossPrice
|
|
2474
|
-
}) => {
|
|
2475
|
-
const entryReference = direction === "LONG" ? ask ?? arrivalMid ?? signalPrice : bid ?? arrivalMid ?? signalPrice;
|
|
2476
|
-
const stopReference = direction === "LONG" ? bid ?? arrivalMid ?? signalPrice : ask ?? arrivalMid ?? signalPrice;
|
|
2477
|
-
if (!Number.isFinite(entryReference) || entryReference <= 0 || !Number.isFinite(stopReference) || stopReference <= 0) {
|
|
2478
|
-
throw new Error("INVALID_ENTRY_REFERENCE_AT_ARRIVAL");
|
|
2479
|
-
}
|
|
2480
|
-
for (const takeProfit of takeProfits) {
|
|
2481
|
-
if (!Number.isFinite(takeProfit.price) || takeProfit.price <= 0 || (direction === "LONG" ? takeProfit.price <= entryReference : takeProfit.price >= entryReference)) {
|
|
2482
|
-
throw new Error("TAKE_PROFIT_CROSSED_BEFORE_ENTRY");
|
|
2483
|
-
}
|
|
2484
|
-
}
|
|
2485
|
-
if (stopLossPrice != null && (!Number.isFinite(stopLossPrice) || stopLossPrice <= 0 || (direction === "LONG" ? stopLossPrice >= stopReference : stopLossPrice <= stopReference))) {
|
|
2486
|
-
throw new Error("STOP_LOSS_CROSSED_BEFORE_ENTRY");
|
|
2451
|
+
var getActiveRuntimeTrade = async (params) => {
|
|
2452
|
+
const { userName, symbol, accountId, deploymentId } = params;
|
|
2453
|
+
if (!userName) {
|
|
2454
|
+
return null;
|
|
2487
2455
|
}
|
|
2488
|
-
|
|
2489
|
-
|
|
2490
|
-
|
|
2491
|
-
|
|
2492
|
-
|
|
2493
|
-
|
|
2494
|
-
|
|
2495
|
-
limits,
|
|
2496
|
-
onValidated
|
|
2497
|
-
}) => {
|
|
2498
|
-
if (!limits) return void 0;
|
|
2499
|
-
if (!connector.capabilities?.orderSubmissionValidation)
|
|
2500
|
-
throw new EntrySubmissionValidationError(
|
|
2501
|
-
"ENTRY_SUBMISSION_VALIDATION_UNSUPPORTED"
|
|
2502
|
-
);
|
|
2503
|
-
return async ({ qty, executionPrice }) => {
|
|
2504
|
-
const simulated = Reflect.get(connector, "__tradejsTestConnector") === true;
|
|
2505
|
-
const snapshot2 = simulated && executionPrice != null ? {
|
|
2506
|
-
arrivalSnapshotTime: Date.now(),
|
|
2507
|
-
arrivalSource: "simulation",
|
|
2508
|
-
bid: executionPrice,
|
|
2509
|
-
ask: executionPrice,
|
|
2510
|
-
arrivalMid: executionPrice,
|
|
2511
|
-
spreadBps: 0
|
|
2512
|
-
} : await getOrderArrivalSnapshot({
|
|
2513
|
-
connector,
|
|
2514
|
-
symbol,
|
|
2515
|
-
requireTimestamp: true
|
|
2516
|
-
});
|
|
2517
|
-
const age = Date.now() - snapshot2.arrivalSnapshotTime;
|
|
2518
|
-
if (snapshot2.bid == null || snapshot2.ask == null || !Number.isFinite(snapshot2.bid) || !Number.isFinite(snapshot2.ask) || snapshot2.bid <= 0 || snapshot2.ask < snapshot2.bid)
|
|
2519
|
-
throw new EntrySubmissionValidationError(
|
|
2520
|
-
"ENTRY_QUOTE_UNAVAILABLE_AT_SUBMISSION"
|
|
2521
|
-
);
|
|
2522
|
-
if (!Number.isFinite(age) || age < -MAX_ENTRY_QUOTE_AGE_MS || age > MAX_ENTRY_QUOTE_AGE_MS)
|
|
2523
|
-
throw new EntrySubmissionValidationError(
|
|
2524
|
-
"ENTRY_QUOTE_STALE_AT_SUBMISSION"
|
|
2525
|
-
);
|
|
2526
|
-
const price = direction === "LONG" ? snapshot2.ask : snapshot2.bid;
|
|
2527
|
-
validateEntryProtectionAtArrival({
|
|
2528
|
-
direction,
|
|
2529
|
-
signalPrice: price,
|
|
2530
|
-
bid: snapshot2.bid,
|
|
2531
|
-
ask: snapshot2.ask,
|
|
2532
|
-
arrivalMid: snapshot2.arrivalMid,
|
|
2533
|
-
takeProfits,
|
|
2534
|
-
stopLossPrice
|
|
2535
|
-
});
|
|
2536
|
-
const prices = takeProfits.map((tp) => tp.price);
|
|
2537
|
-
const economics2 = validateEntryExecution({
|
|
2538
|
-
direction,
|
|
2539
|
-
price,
|
|
2540
|
-
qty,
|
|
2541
|
-
stopLossPrice,
|
|
2542
|
-
takeProfitPrice: direction === "LONG" ? Math.max(...prices) : Math.min(...prices),
|
|
2543
|
-
limits
|
|
2544
|
-
});
|
|
2545
|
-
onValidated?.(snapshot2, economics2);
|
|
2546
|
-
};
|
|
2547
|
-
};
|
|
2548
|
-
|
|
2549
|
-
// src/ai.ts
|
|
2550
|
-
var import_aiLanguages2 = require("@tradejs/core/aiLanguages");
|
|
2551
|
-
var import_redis2 = require("@tradejs/infra/redis");
|
|
2552
|
-
|
|
2553
|
-
// src/aiShared.ts
|
|
2554
|
-
var MAX_AI_SERIES_POINTS = 5;
|
|
2555
|
-
var COMPACT_INDICATORS_SNAPSHOT_SYMBOL = /* @__PURE__ */ Symbol.for(
|
|
2556
|
-
"tradejs.indicators.compactSnapshot"
|
|
2557
|
-
);
|
|
2558
|
-
var COMPACT_INDICATORS_SNAPSHOT_KEY = "__tradejsCompactIndicatorsSnapshot";
|
|
2559
|
-
var trimSeriesDeep = (value) => {
|
|
2560
|
-
if (Array.isArray(value)) {
|
|
2561
|
-
const trimmed = value.slice(-MAX_AI_SERIES_POINTS);
|
|
2562
|
-
const isMatrix = trimmed.every((item) => Array.isArray(item));
|
|
2563
|
-
if (isMatrix) {
|
|
2564
|
-
return trimmed;
|
|
2565
|
-
}
|
|
2566
|
-
return trimmed.map(
|
|
2567
|
-
(item) => item && typeof item === "object" ? trimSeriesDeep(item) : item
|
|
2568
|
-
);
|
|
2456
|
+
const activeRef = await (0, import_redis2.getData)(
|
|
2457
|
+
import_redis2.redisKeys.runtimeActiveTrade(userName, symbol, deploymentId ?? accountId),
|
|
2458
|
+
null
|
|
2459
|
+
);
|
|
2460
|
+
const orderId = String(activeRef?.orderId || "").trim();
|
|
2461
|
+
if (!orderId) {
|
|
2462
|
+
return null;
|
|
2569
2463
|
}
|
|
2570
|
-
|
|
2571
|
-
|
|
2572
|
-
|
|
2573
|
-
|
|
2574
|
-
|
|
2575
|
-
|
|
2464
|
+
const existing = await (0, import_redis2.getData)(
|
|
2465
|
+
import_redis2.redisKeys.runtimeTrade(userName, orderId),
|
|
2466
|
+
null
|
|
2467
|
+
);
|
|
2468
|
+
if (!existing || existing.status !== "active") {
|
|
2469
|
+
await (0, import_redis2.delKey)(
|
|
2470
|
+
import_redis2.redisKeys.runtimeActiveTrade(userName, symbol, deploymentId ?? accountId)
|
|
2576
2471
|
);
|
|
2472
|
+
return null;
|
|
2577
2473
|
}
|
|
2578
|
-
return
|
|
2474
|
+
return existing;
|
|
2579
2475
|
};
|
|
2580
|
-
var
|
|
2581
|
-
const
|
|
2582
|
-
|
|
2583
|
-
|
|
2476
|
+
var markRuntimeTradeClosed = async (params) => {
|
|
2477
|
+
const {
|
|
2478
|
+
userName,
|
|
2479
|
+
expectedOrderId,
|
|
2480
|
+
symbol,
|
|
2481
|
+
strategy,
|
|
2482
|
+
exitPrice,
|
|
2483
|
+
exitTimestamp,
|
|
2484
|
+
closedPnl,
|
|
2485
|
+
exitType,
|
|
2486
|
+
accountId,
|
|
2487
|
+
deploymentId
|
|
2488
|
+
} = params;
|
|
2489
|
+
if (!userName) {
|
|
2490
|
+
return null;
|
|
2584
2491
|
}
|
|
2585
|
-
|
|
2586
|
-
|
|
2587
|
-
|
|
2588
|
-
|
|
2589
|
-
|
|
2590
|
-
|
|
2492
|
+
const existing = expectedOrderId ? await (0, import_redis2.getDataStrict)(
|
|
2493
|
+
import_redis2.redisKeys.runtimeTrade(userName, expectedOrderId)
|
|
2494
|
+
) : await getActiveRuntimeTrade({
|
|
2495
|
+
userName,
|
|
2496
|
+
symbol,
|
|
2497
|
+
accountId,
|
|
2498
|
+
deploymentId
|
|
2499
|
+
});
|
|
2500
|
+
if (!existing || expectedOrderId && existing.orderId !== expectedOrderId) {
|
|
2591
2501
|
return null;
|
|
2592
2502
|
}
|
|
2593
|
-
|
|
2594
|
-
|
|
2595
|
-
|
|
2596
|
-
const numeric = typeof value === "number" ? value : typeof value === "string" && value.trim() ? Number(value) : Number.NaN;
|
|
2597
|
-
return Number.isFinite(numeric) ? numeric : null;
|
|
2598
|
-
};
|
|
2599
|
-
var roundTo = (value, decimals) => {
|
|
2600
|
-
const factor = 10 ** decimals;
|
|
2601
|
-
return Math.round(value * factor) / factor;
|
|
2602
|
-
};
|
|
2603
|
-
var buildMissingSpreadContext = () => ({
|
|
2604
|
-
source: "payload.additionalIndicators.baseContext.relative.execution.venueSpread",
|
|
2605
|
-
indicatorKey: "payload.additionalIndicators.baseContext.relative.execution.venueSpread",
|
|
2606
|
-
available: false,
|
|
2607
|
-
value: null,
|
|
2608
|
-
zScore: null,
|
|
2609
|
-
bps: null,
|
|
2610
|
-
absBps: null,
|
|
2611
|
-
bias: null,
|
|
2612
|
-
severity: null
|
|
2613
|
-
});
|
|
2614
|
-
var buildSpreadContextFromSignal = (signal) => {
|
|
2615
|
-
const baseContext = toRecord(signal.additionalIndicators?.baseContext);
|
|
2616
|
-
const relative = toRecord(baseContext?.relative);
|
|
2617
|
-
const execution = toRecord(relative?.execution);
|
|
2618
|
-
const spread = toFiniteNumber(execution?.venueSpread);
|
|
2619
|
-
const zScore = toFiniteNumber(execution?.venueSpreadZScore);
|
|
2620
|
-
if (spread == null) {
|
|
2621
|
-
return buildMissingSpreadContext();
|
|
2503
|
+
const orderId = existing.orderId;
|
|
2504
|
+
if (strategy && existing.strategy !== strategy) {
|
|
2505
|
+
return null;
|
|
2622
2506
|
}
|
|
2623
|
-
const
|
|
2624
|
-
const
|
|
2625
|
-
|
|
2626
|
-
|
|
2627
|
-
|
|
2628
|
-
|
|
2629
|
-
|
|
2630
|
-
|
|
2631
|
-
|
|
2632
|
-
|
|
2633
|
-
|
|
2634
|
-
|
|
2635
|
-
|
|
2636
|
-
|
|
2637
|
-
|
|
2507
|
+
const resolvedExitPrice = typeof exitPrice === "number" && Number.isFinite(exitPrice) ? exitPrice : existing.currentPrice ?? existing.entryPrice;
|
|
2508
|
+
const resolvedClosedPnl = typeof closedPnl === "number" && Number.isFinite(closedPnl) ? closedPnl : typeof resolvedExitPrice === "number" && Number.isFinite(resolvedExitPrice) ? calculateClosedPnl({
|
|
2509
|
+
direction: existing.direction,
|
|
2510
|
+
entryPrice: existing.entryPrice,
|
|
2511
|
+
exitPrice: resolvedExitPrice,
|
|
2512
|
+
qty: existing.qty
|
|
2513
|
+
}) : existing.closedPnl ?? existing.currentPnl ?? null;
|
|
2514
|
+
const next = expectedOrderId && existing.status === "closed" ? existing : {
|
|
2515
|
+
...existing,
|
|
2516
|
+
status: "closed",
|
|
2517
|
+
currentPrice: resolvedExitPrice,
|
|
2518
|
+
currentPnl: resolvedClosedPnl,
|
|
2519
|
+
closedPnl: resolvedClosedPnl,
|
|
2520
|
+
exitPrice: resolvedExitPrice,
|
|
2521
|
+
exitTimestamp: typeof exitTimestamp === "number" && Number.isFinite(exitTimestamp) ? exitTimestamp : now(),
|
|
2522
|
+
exitType: exitType ?? existing.exitType ?? null,
|
|
2523
|
+
lastSyncedAt: now()
|
|
2638
2524
|
};
|
|
2525
|
+
const dayKey = (0, import_time3.getRuntimeStorageDayKey)(existing.entryTimestamp);
|
|
2526
|
+
const closeDayKey = (0, import_time3.getRuntimeStorageDayKey)(next.exitTimestamp);
|
|
2527
|
+
try {
|
|
2528
|
+
await Promise.all([
|
|
2529
|
+
(expectedOrderId ? import_redis2.setDataStrict : import_redis2.setData)(
|
|
2530
|
+
import_redis2.redisKeys.runtimeTrade(userName, orderId),
|
|
2531
|
+
next,
|
|
2532
|
+
{
|
|
2533
|
+
expire: import_constants3.TTL_1M
|
|
2534
|
+
}
|
|
2535
|
+
),
|
|
2536
|
+
(expectedOrderId ? import_redis2.setHashJsonFieldStrict : import_redis2.setHashJsonField)(
|
|
2537
|
+
import_redis2.redisKeys.runtimeTradeBucket(userName, dayKey),
|
|
2538
|
+
orderId,
|
|
2539
|
+
next,
|
|
2540
|
+
{ expire: import_constants3.TTL_1M }
|
|
2541
|
+
),
|
|
2542
|
+
(expectedOrderId ? import_redis2.setHashJsonFieldStrict : import_redis2.setHashJsonField)(
|
|
2543
|
+
import_redis2.redisKeys.runtimeClosedTradeBucket(userName, closeDayKey),
|
|
2544
|
+
orderId,
|
|
2545
|
+
next,
|
|
2546
|
+
{ expire: import_constants3.TTL_1M }
|
|
2547
|
+
),
|
|
2548
|
+
expectedOrderId ? (0, import_redis2.deleteRuntimeTradeReference)(
|
|
2549
|
+
import_redis2.redisKeys.runtimeActiveTrade(
|
|
2550
|
+
userName,
|
|
2551
|
+
symbol,
|
|
2552
|
+
deploymentId ?? accountId
|
|
2553
|
+
),
|
|
2554
|
+
expectedOrderId
|
|
2555
|
+
) : (0, import_redis2.delKey)(
|
|
2556
|
+
import_redis2.redisKeys.runtimeActiveTrade(
|
|
2557
|
+
userName,
|
|
2558
|
+
symbol,
|
|
2559
|
+
deploymentId ?? accountId
|
|
2560
|
+
)
|
|
2561
|
+
)
|
|
2562
|
+
]);
|
|
2563
|
+
} catch (error) {
|
|
2564
|
+
if (expectedOrderId) throw error;
|
|
2565
|
+
import_logger5.logger.error(
|
|
2566
|
+
"runtime trade close journal failed: %s %s",
|
|
2567
|
+
symbol,
|
|
2568
|
+
error?.message || String(error)
|
|
2569
|
+
);
|
|
2570
|
+
}
|
|
2571
|
+
return next;
|
|
2639
2572
|
};
|
|
2640
|
-
|
|
2641
|
-
|
|
2642
|
-
|
|
2643
|
-
|
|
2644
|
-
|
|
2645
|
-
|
|
2646
|
-
|
|
2647
|
-
|
|
2648
|
-
|
|
2649
|
-
|
|
2650
|
-
|
|
2651
|
-
|
|
2652
|
-
|
|
2653
|
-
|
|
2654
|
-
|
|
2655
|
-
|
|
2656
|
-
|
|
2657
|
-
|
|
2573
|
+
|
|
2574
|
+
// src/strategy/portfolioRuntime.ts
|
|
2575
|
+
var coordinators = /* @__PURE__ */ new WeakMap();
|
|
2576
|
+
async function observeRuntimePortfolio(connector, binding, asOf, reservations, observedNow = Date.now) {
|
|
2577
|
+
if (!connector.getOpenPositionPnl || !connector.getEntryExecutions)
|
|
2578
|
+
throw new Error(
|
|
2579
|
+
"Portfolio policies require position marks and entry executions"
|
|
2580
|
+
);
|
|
2581
|
+
const orders = await Promise.all(
|
|
2582
|
+
reservations.map(async (r) => {
|
|
2583
|
+
if (r.submitted && connector.getEntryOrderObservation) {
|
|
2584
|
+
const fact2 = await connector.getEntryOrderObservation({
|
|
2585
|
+
symbol: r.symbol,
|
|
2586
|
+
orderId: r.id
|
|
2587
|
+
});
|
|
2588
|
+
if (fact2) return fact2;
|
|
2589
|
+
}
|
|
2590
|
+
const executions = r.submitted ? await connector.getEntryExecutions({
|
|
2591
|
+
symbol: r.symbol,
|
|
2592
|
+
startTime: r.timestamp,
|
|
2593
|
+
endTime: asOf
|
|
2594
|
+
}) : [];
|
|
2595
|
+
const filledQty = executions.filter(
|
|
2596
|
+
(e) => (e.orderLinkId === r.id || e.orderId === r.id) && e.direction === r.direction
|
|
2597
|
+
).reduce((sum, e) => sum + e.qty, 0);
|
|
2598
|
+
return {
|
|
2599
|
+
id: r.id,
|
|
2600
|
+
status: filledQty >= r.qty ? "filled" : filledQty > 0 ? "partial" : "pending",
|
|
2601
|
+
filledQty,
|
|
2602
|
+
positionId: r.id
|
|
2603
|
+
};
|
|
2604
|
+
})
|
|
2605
|
+
);
|
|
2606
|
+
const snapshots2 = await connector.getOpenPositionPnl();
|
|
2607
|
+
const positions = await Promise.all(
|
|
2608
|
+
snapshots2.map(async (p) => {
|
|
2609
|
+
const reserved = reservations.find(
|
|
2610
|
+
(r) => r.symbol === p.symbol && r.direction === p.direction && orders.some(
|
|
2611
|
+
(o) => o.id === r.id && o.filledQty > 0 && p.qty <= o.filledQty * (1 + 1e-8)
|
|
2612
|
+
)
|
|
2613
|
+
);
|
|
2614
|
+
const trade = await getActiveRuntimeTrade({
|
|
2615
|
+
...binding,
|
|
2616
|
+
symbol: p.symbol
|
|
2617
|
+
});
|
|
2618
|
+
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;
|
|
2619
|
+
return {
|
|
2620
|
+
id: reserved?.id ?? owner?.orderId ?? `unknown:${p.symbol}:${p.direction}`,
|
|
2621
|
+
strategy: reserved?.strategy ?? owner?.strategy ?? null,
|
|
2622
|
+
symbol: p.symbol,
|
|
2623
|
+
direction: p.direction,
|
|
2624
|
+
qty: p.qty,
|
|
2625
|
+
entryPrice: p.price,
|
|
2626
|
+
markPrice: p.currentPrice,
|
|
2627
|
+
markedAt: p.observedAt ?? null,
|
|
2628
|
+
stopLossPrice: p.slPrice ?? p.stopLossPrice ?? null
|
|
2629
|
+
};
|
|
2630
|
+
})
|
|
2631
|
+
);
|
|
2632
|
+
return { asOf: Math.max(asOf, observedNow()), positions, orders };
|
|
2633
|
+
}
|
|
2634
|
+
function persistentStore(key) {
|
|
2635
|
+
const raw = /* @__PURE__ */ new WeakMap();
|
|
2658
2636
|
return {
|
|
2659
|
-
|
|
2660
|
-
|
|
2661
|
-
|
|
2662
|
-
|
|
2663
|
-
|
|
2664
|
-
|
|
2665
|
-
|
|
2666
|
-
|
|
2637
|
+
async read() {
|
|
2638
|
+
const value = await import_redis3.portfolioStateStorage.read(key);
|
|
2639
|
+
if (value === null) return null;
|
|
2640
|
+
const state = JSON.parse(value);
|
|
2641
|
+
(0, import_portfolio2.assertPortfolioState)(state);
|
|
2642
|
+
raw.set(state, value);
|
|
2643
|
+
return state;
|
|
2644
|
+
},
|
|
2645
|
+
async compare(previous, next) {
|
|
2646
|
+
const expected = previous === null ? null : raw.get(previous);
|
|
2647
|
+
if (expected === void 0)
|
|
2648
|
+
throw new Error("Portfolio CAS requires a storage snapshot");
|
|
2649
|
+
return import_redis3.portfolioStateStorage.compare(key, expected, JSON.stringify(next));
|
|
2650
|
+
}
|
|
2667
2651
|
};
|
|
2668
|
-
}
|
|
2669
|
-
|
|
2670
|
-
|
|
2671
|
-
|
|
2672
|
-
|
|
2673
|
-
|
|
2674
|
-
|
|
2675
|
-
|
|
2676
|
-
|
|
2677
|
-
|
|
2678
|
-
|
|
2679
|
-
|
|
2680
|
-
|
|
2681
|
-
|
|
2682
|
-
|
|
2683
|
-
|
|
2684
|
-
|
|
2685
|
-
|
|
2686
|
-
|
|
2687
|
-
|
|
2688
|
-
|
|
2689
|
-
|
|
2690
|
-
|
|
2691
|
-
|
|
2692
|
-
|
|
2693
|
-
|
|
2652
|
+
}
|
|
2653
|
+
function getPortfolioRuntime({
|
|
2654
|
+
value,
|
|
2655
|
+
connectorName,
|
|
2656
|
+
strategy,
|
|
2657
|
+
connector,
|
|
2658
|
+
env,
|
|
2659
|
+
userName,
|
|
2660
|
+
accountId,
|
|
2661
|
+
deploymentId,
|
|
2662
|
+
symbol,
|
|
2663
|
+
canClose
|
|
2664
|
+
}) {
|
|
2665
|
+
const policy = (0, import_portfolio2.parsePortfolioPolicy)(value);
|
|
2666
|
+
if (!policy) return void 0;
|
|
2667
|
+
const replay = (env === "BACKTEST" || env === "PARITY") && Reflect.get(connector, "__tradejsReplayConnector") === true;
|
|
2668
|
+
if ((env === "BACKTEST" || env === "PARITY") && (!replay || !connector.getPortfolioObservation))
|
|
2669
|
+
throw new Error(
|
|
2670
|
+
"Portfolio policies require shared portfolio replay; isolated symbol backtests are unsupported"
|
|
2671
|
+
);
|
|
2672
|
+
if (!replay && (!userName || !accountId || !deploymentId || !connector.getOpenPositionPnl || !connector.getEntryExecutions))
|
|
2673
|
+
throw new Error(
|
|
2674
|
+
"Portfolio runtime requires exact user/account/deployment binding, marks and executions"
|
|
2675
|
+
);
|
|
2676
|
+
const binding = {
|
|
2677
|
+
userName,
|
|
2678
|
+
accountId,
|
|
2679
|
+
deploymentId
|
|
2694
2680
|
};
|
|
2695
|
-
|
|
2696
|
-
|
|
2697
|
-
const
|
|
2698
|
-
const
|
|
2699
|
-
|
|
2700
|
-
if (!
|
|
2701
|
-
|
|
2702
|
-
|
|
2703
|
-
|
|
2704
|
-
|
|
2705
|
-
|
|
2706
|
-
|
|
2707
|
-
|
|
2708
|
-
|
|
2709
|
-
|
|
2710
|
-
|
|
2711
|
-
|
|
2712
|
-
|
|
2713
|
-
|
|
2714
|
-
|
|
2681
|
+
const scope = policy.scope === "strategy" ? strategy : policy.scope === "account" ? "account" : [...policy.scope.strategies].sort();
|
|
2682
|
+
const key = (0, import_node_crypto2.createHash)("sha256").update(JSON.stringify([binding, connectorName, connector.universe, scope])).digest("hex");
|
|
2683
|
+
const fingerprint3 = (0, import_portfolio2.portfolioFingerprint)(policy, strategy);
|
|
2684
|
+
const cacheKey = key;
|
|
2685
|
+
let registry = coordinators.get(connector);
|
|
2686
|
+
if (!registry) {
|
|
2687
|
+
registry = /* @__PURE__ */ new Map();
|
|
2688
|
+
coordinators.set(connector, registry);
|
|
2689
|
+
}
|
|
2690
|
+
const existing = registry.get(cacheKey);
|
|
2691
|
+
if (existing) {
|
|
2692
|
+
if (existing.fingerprint !== fingerprint3)
|
|
2693
|
+
throw new Error("Conflicting portfolio configuration for the same scope");
|
|
2694
|
+
if (symbol && canClose)
|
|
2695
|
+
existing.permissions.set(JSON.stringify([strategy, symbol]), canClose);
|
|
2696
|
+
return existing.coordinator;
|
|
2697
|
+
}
|
|
2698
|
+
const permissions = /* @__PURE__ */ new Map();
|
|
2699
|
+
if (symbol && canClose)
|
|
2700
|
+
permissions.set(JSON.stringify([strategy, symbol]), canClose);
|
|
2701
|
+
const coordinator = createPortfolioCoordinator({
|
|
2702
|
+
policy,
|
|
2703
|
+
strategy,
|
|
2704
|
+
store: replay ? createMemoryPortfolioStore() : persistentStore(key),
|
|
2705
|
+
execution: {
|
|
2706
|
+
async confirmClosed(intent, asOf) {
|
|
2707
|
+
if (replay) return;
|
|
2708
|
+
const position = intent.position;
|
|
2709
|
+
await markRuntimeTradeClosed({
|
|
2710
|
+
...binding,
|
|
2711
|
+
strategy: position.strategy,
|
|
2712
|
+
expectedOrderId: position.id,
|
|
2713
|
+
symbol: position.symbol,
|
|
2714
|
+
exitPrice: position.markPrice,
|
|
2715
|
+
exitTimestamp: asOf,
|
|
2716
|
+
exitType: "exit"
|
|
2717
|
+
});
|
|
2718
|
+
},
|
|
2719
|
+
observe: (asOf, reservations) => replay ? connector.getPortfolioObservation(asOf, reservations) : observeRuntimePortfolio(connector, binding, asOf, reservations),
|
|
2720
|
+
async close(position, intent, asOf) {
|
|
2721
|
+
const permission = permissions.get(
|
|
2722
|
+
JSON.stringify([position.strategy, position.symbol])
|
|
2723
|
+
);
|
|
2724
|
+
if (!permission || !await permission(position, intent, asOf)) return;
|
|
2725
|
+
if (!replay) {
|
|
2726
|
+
const trade = await getActiveRuntimeTrade({
|
|
2727
|
+
...binding,
|
|
2728
|
+
symbol: position.symbol
|
|
2729
|
+
});
|
|
2730
|
+
if (!trade || trade.orderId !== position.id)
|
|
2731
|
+
throw new Error("Portfolio close ownership changed");
|
|
2732
|
+
}
|
|
2733
|
+
await connector.closePosition({
|
|
2734
|
+
symbol: position.symbol,
|
|
2735
|
+
direction: position.direction,
|
|
2736
|
+
price: position.markPrice,
|
|
2737
|
+
timestamp: asOf,
|
|
2738
|
+
signal: {
|
|
2739
|
+
signalId: `${position.id}:${intent.reason}`,
|
|
2740
|
+
strategy: position.strategy,
|
|
2741
|
+
symbol: position.symbol,
|
|
2742
|
+
direction: position.direction,
|
|
2743
|
+
interval: "1",
|
|
2744
|
+
timestamp: asOf,
|
|
2745
|
+
prices: {
|
|
2746
|
+
riskRatio: 0,
|
|
2747
|
+
currentPrice: position.markPrice,
|
|
2748
|
+
takeProfitPrice: position.markPrice,
|
|
2749
|
+
stopLossPrice: position.markPrice
|
|
2750
|
+
},
|
|
2751
|
+
figures: {},
|
|
2752
|
+
indicators: {},
|
|
2753
|
+
additionalIndicators: { portfolioExitReason: intent.reason }
|
|
2754
|
+
}
|
|
2755
|
+
});
|
|
2756
|
+
}
|
|
2757
|
+
}
|
|
2758
|
+
});
|
|
2759
|
+
registry.set(cacheKey, { fingerprint: fingerprint3, coordinator, permissions });
|
|
2760
|
+
return coordinator;
|
|
2761
|
+
}
|
|
2762
|
+
|
|
2763
|
+
// src/strategyHelpers/tickerProfiles.ts
|
|
2764
|
+
var import_tickerProfiles = require("@tradejs/infra/tickerProfiles");
|
|
2765
|
+
var import_tickerProfiles2 = require("@tradejs/core/tickerProfiles");
|
|
2766
|
+
|
|
2767
|
+
// src/strategy/manifests.ts
|
|
2768
|
+
var import_indicators = require("@tradejs/core/indicators");
|
|
2769
|
+
var SHARED_STRATEGY_REGISTRY_KEY = "__tradejsNodeSharedStrategyRegistryV1__";
|
|
2770
|
+
var sharedRegistryScope = globalThis;
|
|
2771
|
+
var sharedStrategyRegistry = sharedRegistryScope[SHARED_STRATEGY_REGISTRY_KEY] ?? (sharedRegistryScope[SHARED_STRATEGY_REGISTRY_KEY] = {
|
|
2772
|
+
registryStateByProjectRoot: /* @__PURE__ */ new Map()
|
|
2773
|
+
});
|
|
2774
|
+
var createStrategyRegistryState = () => ({
|
|
2775
|
+
strategyCreators: /* @__PURE__ */ new Map(),
|
|
2776
|
+
strategyManifestsMap: /* @__PURE__ */ new Map(),
|
|
2777
|
+
strategyEntriesMap: /* @__PURE__ */ new Map(),
|
|
2778
|
+
strategySourcesMap: /* @__PURE__ */ new Map(),
|
|
2779
|
+
strategyModuleResolutions: /* @__PURE__ */ new Map(),
|
|
2780
|
+
pluginsLoadPromise: null
|
|
2781
|
+
});
|
|
2782
|
+
var registryStateByProjectRoot2 = sharedStrategyRegistry.registryStateByProjectRoot;
|
|
2783
|
+
var getStrategyRegistryState = (cwd = getTradejsProjectCwd()) => {
|
|
2784
|
+
const projectRoot = getTradejsProjectCwd(cwd);
|
|
2785
|
+
let state = registryStateByProjectRoot2.get(projectRoot);
|
|
2786
|
+
if (!state) {
|
|
2787
|
+
state = createStrategyRegistryState();
|
|
2788
|
+
registryStateByProjectRoot2.set(projectRoot, state);
|
|
2715
2789
|
}
|
|
2716
2790
|
return {
|
|
2717
|
-
|
|
2718
|
-
|
|
2719
|
-
universe: String(breadth.universe ?? ""),
|
|
2720
|
-
interval: String(breadth.interval ?? ""),
|
|
2721
|
-
stale: typeof breadth.stale === "boolean" ? breadth.stale : null,
|
|
2722
|
-
symbolsCount: toFiniteNumber(breadth.symbolsCount),
|
|
2723
|
-
advanceDeclineRatio: toFiniteNumber(breadth.advanceDeclineRatio),
|
|
2724
|
-
pctAboveMa20: toFiniteNumber(breadth.pctAboveMa20),
|
|
2725
|
-
pctAboveMa50: toFiniteNumber(breadth.pctAboveMa50),
|
|
2726
|
-
equalWeightedReturn: toFiniteNumber(breadth.equalWeightedReturn),
|
|
2727
|
-
volumeWeightedReturn: toFiniteNumber(breadth.volumeWeightedReturn),
|
|
2728
|
-
dispersion: toFiniteNumber(breadth.dispersion)
|
|
2791
|
+
projectRoot,
|
|
2792
|
+
state
|
|
2729
2793
|
};
|
|
2730
2794
|
};
|
|
2731
|
-
var
|
|
2732
|
-
|
|
2733
|
-
|
|
2734
|
-
|
|
2735
|
-
const
|
|
2736
|
-
const breadth = toRecord(breadths?.[key]);
|
|
2737
|
-
if (!breadth) {
|
|
2738
|
-
return {
|
|
2739
|
-
source: null,
|
|
2740
|
-
available: false,
|
|
2741
|
-
universe: null,
|
|
2742
|
-
interval: null,
|
|
2743
|
-
stale: null,
|
|
2744
|
-
symbolsCount: null,
|
|
2745
|
-
advanceDeclineRatio: null,
|
|
2746
|
-
pctAboveMa20: null,
|
|
2747
|
-
pctAboveMa50: null,
|
|
2748
|
-
equalWeightedReturn: null,
|
|
2749
|
-
volumeWeightedReturn: null,
|
|
2750
|
-
dispersion: null
|
|
2751
|
-
};
|
|
2752
|
-
}
|
|
2753
|
-
return {
|
|
2754
|
-
source: String(breadth.source ?? ""),
|
|
2755
|
-
available: true,
|
|
2756
|
-
universe: String(breadth.universe ?? ""),
|
|
2757
|
-
interval: String(breadth.interval ?? ""),
|
|
2758
|
-
stale: typeof breadth.stale === "boolean" ? breadth.stale : null,
|
|
2759
|
-
symbolsCount: toFiniteNumber(breadth.symbolsCount),
|
|
2760
|
-
advanceDeclineRatio: toFiniteNumber(breadth.advanceDeclineRatio),
|
|
2761
|
-
pctAboveMa20: toFiniteNumber(breadth.pctAboveMa20),
|
|
2762
|
-
pctAboveMa50: toFiniteNumber(breadth.pctAboveMa50),
|
|
2763
|
-
equalWeightedReturn: toFiniteNumber(breadth.equalWeightedReturn),
|
|
2764
|
-
volumeWeightedReturn: toFiniteNumber(breadth.volumeWeightedReturn),
|
|
2765
|
-
dispersion: toFiniteNumber(breadth.dispersion)
|
|
2766
|
-
};
|
|
2767
|
-
};
|
|
2795
|
+
var toUniqueModules2 = (modules = []) => [
|
|
2796
|
+
...new Set(modules.map((moduleName) => moduleName.trim()).filter(Boolean))
|
|
2797
|
+
];
|
|
2798
|
+
var getConfiguredPluginModuleNames = async (cwd = getTradejsProjectCwd()) => {
|
|
2799
|
+
const config = await loadTradejsConfig(cwd);
|
|
2768
2800
|
return {
|
|
2769
|
-
|
|
2770
|
-
|
|
2771
|
-
top30: build("top30"),
|
|
2772
|
-
top50: build("top50"),
|
|
2773
|
-
top100: build("top100")
|
|
2801
|
+
strategyModules: toUniqueModules2(config.strategies),
|
|
2802
|
+
indicatorModules: toUniqueModules2(config.indicators)
|
|
2774
2803
|
};
|
|
2775
2804
|
};
|
|
2776
|
-
var
|
|
2777
|
-
|
|
2778
|
-
|
|
2779
|
-
const targetVsBtc = toRecord(relative?.targetVsBtc);
|
|
2780
|
-
if (!targetVsBtc) {
|
|
2781
|
-
return {
|
|
2782
|
-
source: null,
|
|
2783
|
-
available: false,
|
|
2784
|
-
ratioReturn1h: null,
|
|
2785
|
-
ratioReturn4h: null,
|
|
2786
|
-
ratioReturn24h: null,
|
|
2787
|
-
alphaVsBtc1h: null,
|
|
2788
|
-
alphaVsBtc4h: null,
|
|
2789
|
-
alphaVsBtc24h: null,
|
|
2790
|
-
betaToBtc20: null,
|
|
2791
|
-
correlationToBtc20: null,
|
|
2792
|
-
ratioTrend: null
|
|
2793
|
-
};
|
|
2805
|
+
var extractModuleEntries = (moduleExport, key) => {
|
|
2806
|
+
if (!moduleExport || typeof moduleExport !== "object") {
|
|
2807
|
+
return null;
|
|
2794
2808
|
}
|
|
2795
|
-
|
|
2796
|
-
|
|
2797
|
-
|
|
2798
|
-
ratioReturn1h: toFiniteNumber(targetVsBtc.ratioReturn1h),
|
|
2799
|
-
ratioReturn4h: toFiniteNumber(targetVsBtc.ratioReturn4h),
|
|
2800
|
-
ratioReturn24h: toFiniteNumber(targetVsBtc.ratioReturn24h),
|
|
2801
|
-
alphaVsBtc1h: toFiniteNumber(targetVsBtc.alphaVsBtc1h),
|
|
2802
|
-
alphaVsBtc4h: toFiniteNumber(targetVsBtc.alphaVsBtc4h),
|
|
2803
|
-
alphaVsBtc24h: toFiniteNumber(targetVsBtc.alphaVsBtc24h),
|
|
2804
|
-
betaToBtc20: toFiniteNumber(targetVsBtc.betaToBtc20),
|
|
2805
|
-
correlationToBtc20: toFiniteNumber(targetVsBtc.correlationToBtc20),
|
|
2806
|
-
ratioTrend: typeof targetVsBtc.ratioTrend === "string" ? targetVsBtc.ratioTrend : null
|
|
2807
|
-
};
|
|
2808
|
-
};
|
|
2809
|
-
var buildBtcAltRegimeContextFromSignal = (signal) => {
|
|
2810
|
-
const baseContext = toRecord(signal.additionalIndicators?.baseContext);
|
|
2811
|
-
const relative = toRecord(baseContext?.relative);
|
|
2812
|
-
const btcAltRegime = toRecord(relative?.btcAltRegime);
|
|
2813
|
-
if (!btcAltRegime) {
|
|
2814
|
-
return {
|
|
2815
|
-
source: null,
|
|
2816
|
-
available: false,
|
|
2817
|
-
universe: null,
|
|
2818
|
-
interval: null,
|
|
2819
|
-
stale: null,
|
|
2820
|
-
regime: null,
|
|
2821
|
-
btcReturn24h: null,
|
|
2822
|
-
altBasketReturn24h: null,
|
|
2823
|
-
btcVsAltReturn24h: null,
|
|
2824
|
-
btcTurnoverShare24h: null,
|
|
2825
|
-
btcTurnoverShareChange24h: null,
|
|
2826
|
-
altVolToBtcVol24h: null,
|
|
2827
|
-
altDispersion24h: null
|
|
2828
|
-
};
|
|
2809
|
+
const candidate = moduleExport;
|
|
2810
|
+
if (Array.isArray(candidate[key])) {
|
|
2811
|
+
return candidate[key];
|
|
2829
2812
|
}
|
|
2830
|
-
|
|
2831
|
-
|
|
2832
|
-
|
|
2833
|
-
|
|
2834
|
-
|
|
2835
|
-
stale: typeof btcAltRegime.stale === "boolean" ? btcAltRegime.stale : null,
|
|
2836
|
-
regime: typeof btcAltRegime.regime === "string" ? btcAltRegime.regime : null,
|
|
2837
|
-
btcReturn24h: toFiniteNumber(btcAltRegime.btcReturn24h),
|
|
2838
|
-
altBasketReturn24h: toFiniteNumber(btcAltRegime.altBasketReturn24h),
|
|
2839
|
-
btcVsAltReturn24h: toFiniteNumber(btcAltRegime.btcVsAltReturn24h),
|
|
2840
|
-
btcTurnoverShare24h: toFiniteNumber(btcAltRegime.btcTurnoverShare24h),
|
|
2841
|
-
btcTurnoverShareChange24h: toFiniteNumber(
|
|
2842
|
-
btcAltRegime.btcTurnoverShareChange24h
|
|
2843
|
-
),
|
|
2844
|
-
altVolToBtcVol24h: toFiniteNumber(btcAltRegime.altVolToBtcVol24h),
|
|
2845
|
-
altDispersion24h: toFiniteNumber(btcAltRegime.altDispersion24h)
|
|
2846
|
-
};
|
|
2813
|
+
const defaultExport = candidate.default;
|
|
2814
|
+
if (defaultExport && Array.isArray(defaultExport[key])) {
|
|
2815
|
+
return defaultExport[key];
|
|
2816
|
+
}
|
|
2817
|
+
return null;
|
|
2847
2818
|
};
|
|
2848
|
-
var
|
|
2849
|
-
const
|
|
2850
|
-
|
|
2851
|
-
|
|
2852
|
-
|
|
2853
|
-
|
|
2854
|
-
|
|
2855
|
-
|
|
2856
|
-
|
|
2857
|
-
|
|
2858
|
-
|
|
2859
|
-
|
|
2860
|
-
|
|
2861
|
-
|
|
2862
|
-
|
|
2863
|
-
|
|
2864
|
-
|
|
2865
|
-
|
|
2866
|
-
|
|
2867
|
-
|
|
2868
|
-
|
|
2869
|
-
|
|
2870
|
-
|
|
2871
|
-
|
|
2872
|
-
|
|
2873
|
-
|
|
2874
|
-
|
|
2819
|
+
var extractStrategyPluginDefinition = (moduleExport) => {
|
|
2820
|
+
const strategyEntries = extractModuleEntries(
|
|
2821
|
+
moduleExport,
|
|
2822
|
+
"strategyEntries"
|
|
2823
|
+
);
|
|
2824
|
+
return strategyEntries ? { strategyEntries } : null;
|
|
2825
|
+
};
|
|
2826
|
+
var extractIndicatorPluginDefinition = (moduleExport) => {
|
|
2827
|
+
const indicatorEntries = extractModuleEntries(
|
|
2828
|
+
moduleExport,
|
|
2829
|
+
"indicatorEntries"
|
|
2830
|
+
);
|
|
2831
|
+
return indicatorEntries ? { indicatorEntries } : null;
|
|
2832
|
+
};
|
|
2833
|
+
var validateStrategyEntries = (moduleName, entries, state) => {
|
|
2834
|
+
const issues = [];
|
|
2835
|
+
const names = /* @__PURE__ */ new Set();
|
|
2836
|
+
entries.forEach((entry, index) => {
|
|
2837
|
+
const entryPath = `${moduleName}.strategyEntries[${index}]`;
|
|
2838
|
+
const strategyName = entry?.manifest?.name;
|
|
2839
|
+
if (typeof strategyName !== "string" || !strategyName.trim()) {
|
|
2840
|
+
issues.push(`${entryPath}: manifest.name is required`);
|
|
2841
|
+
} else if (names.has(strategyName) || state?.strategyEntriesMap.has(strategyName)) {
|
|
2842
|
+
issues.push(`${entryPath}: duplicate strategy ${strategyName}`);
|
|
2843
|
+
} else {
|
|
2844
|
+
names.add(strategyName);
|
|
2845
|
+
}
|
|
2846
|
+
if (!entry || typeof entry !== "object" || !entry.defaults || typeof entry.defaults !== "object" || Array.isArray(entry.defaults)) {
|
|
2847
|
+
issues.push(`${entryPath}: defaults must be an object`);
|
|
2848
|
+
}
|
|
2849
|
+
if (typeof entry?.parseConfig !== "function") {
|
|
2850
|
+
issues.push(`${entryPath}: parseConfig is required`);
|
|
2851
|
+
}
|
|
2852
|
+
if (typeof entry?.createCore !== "function") {
|
|
2853
|
+
issues.push(`${entryPath}: createCore is required`);
|
|
2854
|
+
}
|
|
2855
|
+
});
|
|
2856
|
+
return issues;
|
|
2857
|
+
};
|
|
2858
|
+
var registerEntries2 = (entries, source, state) => {
|
|
2859
|
+
for (const entry of entries) {
|
|
2860
|
+
const strategyName = entry.manifest.name;
|
|
2861
|
+
state.strategyManifestsMap.set(strategyName, entry.manifest);
|
|
2862
|
+
state.strategyEntriesMap.set(strategyName, entry);
|
|
2863
|
+
state.strategySourcesMap.set(strategyName, source);
|
|
2864
|
+
materializeStrategyCreator(strategyName, state);
|
|
2865
|
+
}
|
|
2866
|
+
};
|
|
2867
|
+
var materializeStrategyCreator = (strategyName, state) => {
|
|
2868
|
+
if (state.strategyCreators.has(strategyName) || !sharedStrategyRegistry.strategyRuntimeFactory) {
|
|
2869
|
+
return;
|
|
2870
|
+
}
|
|
2871
|
+
const entry = state.strategyEntriesMap.get(strategyName);
|
|
2872
|
+
if (!entry) return;
|
|
2873
|
+
state.strategyCreators.set(
|
|
2874
|
+
strategyName,
|
|
2875
|
+
sharedStrategyRegistry.strategyRuntimeFactory({
|
|
2876
|
+
strategyName,
|
|
2877
|
+
defaults: entry.defaults,
|
|
2878
|
+
createCore: entry.createCore,
|
|
2879
|
+
manifest: entry.manifest,
|
|
2880
|
+
detectorKey: entry.detectorKey,
|
|
2881
|
+
detectorNoSignalSkipReason: entry.detectorNoSignalSkipReason,
|
|
2882
|
+
resolveRegisteredManifest: (name) => state.strategyManifestsMap.get(name)
|
|
2883
|
+
})
|
|
2884
|
+
);
|
|
2885
|
+
};
|
|
2886
|
+
var setStrategyRuntimeFactory = (factory) => {
|
|
2887
|
+
sharedStrategyRegistry.strategyRuntimeFactory = factory;
|
|
2888
|
+
for (const state of registryStateByProjectRoot2.values()) {
|
|
2889
|
+
for (const strategyName of state.strategyEntriesMap.keys()) {
|
|
2890
|
+
materializeStrategyCreator(strategyName, state);
|
|
2891
|
+
}
|
|
2892
|
+
}
|
|
2893
|
+
};
|
|
2894
|
+
var importStrategyPluginModule = async (moduleName, cwd = getTradejsProjectCwd()) => {
|
|
2895
|
+
if (typeof importTradejsModule === "function") {
|
|
2896
|
+
return importTradejsModule(moduleName, cwd);
|
|
2897
|
+
}
|
|
2898
|
+
return import(
|
|
2899
|
+
/* webpackIgnore: true */
|
|
2900
|
+
moduleName
|
|
2901
|
+
);
|
|
2902
|
+
};
|
|
2903
|
+
var resolveStrategyModuleEntry = async ({
|
|
2904
|
+
strategyName,
|
|
2905
|
+
moduleName,
|
|
2906
|
+
cwd
|
|
2907
|
+
}) => {
|
|
2908
|
+
const { projectRoot, state } = getStrategyRegistryState(cwd);
|
|
2909
|
+
const source = moduleName.trim();
|
|
2910
|
+
const resolvedModuleName = resolvePluginModuleSpecifier(source, projectRoot);
|
|
2911
|
+
const cacheKey = `${resolvedModuleName}\0${strategyName}`;
|
|
2912
|
+
const cached = state.strategyModuleResolutions.get(cacheKey);
|
|
2913
|
+
if (cached) return cached;
|
|
2914
|
+
const resolution = (async () => {
|
|
2915
|
+
const moduleExport = await importStrategyPluginModule(
|
|
2916
|
+
resolvedModuleName,
|
|
2917
|
+
projectRoot
|
|
2918
|
+
);
|
|
2919
|
+
const pluginDefinition = extractStrategyPluginDefinition(moduleExport);
|
|
2920
|
+
if (!pluginDefinition) {
|
|
2921
|
+
throw new Error(`${source}: export { strategyEntries } is missing`);
|
|
2922
|
+
}
|
|
2923
|
+
const issues = validateStrategyEntries(
|
|
2924
|
+
source,
|
|
2925
|
+
pluginDefinition.strategyEntries
|
|
2926
|
+
);
|
|
2927
|
+
if (issues.length > 0) {
|
|
2928
|
+
throw new Error(
|
|
2929
|
+
["Invalid TradeJS strategy module:", ...issues].join("\n")
|
|
2930
|
+
);
|
|
2931
|
+
}
|
|
2932
|
+
const entry = pluginDefinition.strategyEntries.find(
|
|
2933
|
+
(candidate) => candidate.manifest.name === strategyName
|
|
2934
|
+
);
|
|
2935
|
+
if (!entry) {
|
|
2936
|
+
throw new Error(`${source}: strategy ${strategyName} is missing`);
|
|
2937
|
+
}
|
|
2938
|
+
const factory = sharedStrategyRegistry.strategyRuntimeFactory;
|
|
2939
|
+
if (!factory) {
|
|
2940
|
+
throw new Error("Strategy runtime factory is not configured");
|
|
2941
|
+
}
|
|
2942
|
+
const manifestsByName = new Map(
|
|
2943
|
+
pluginDefinition.strategyEntries.map(
|
|
2944
|
+
(candidate) => [candidate.manifest.name, candidate.manifest]
|
|
2945
|
+
)
|
|
2946
|
+
);
|
|
2947
|
+
return {
|
|
2948
|
+
entry,
|
|
2949
|
+
creator: factory({
|
|
2950
|
+
strategyName,
|
|
2951
|
+
defaults: entry.defaults,
|
|
2952
|
+
createCore: entry.createCore,
|
|
2953
|
+
manifest: entry.manifest,
|
|
2954
|
+
detectorKey: entry.detectorKey,
|
|
2955
|
+
detectorNoSignalSkipReason: entry.detectorNoSignalSkipReason,
|
|
2956
|
+
resolveRegisteredManifest: (name) => manifestsByName.get(name) ?? state.strategyManifestsMap.get(name)
|
|
2957
|
+
}),
|
|
2958
|
+
source
|
|
2959
|
+
};
|
|
2960
|
+
})();
|
|
2961
|
+
state.strategyModuleResolutions.set(cacheKey, resolution);
|
|
2962
|
+
try {
|
|
2963
|
+
return await resolution;
|
|
2964
|
+
} catch (error) {
|
|
2965
|
+
state.strategyModuleResolutions.delete(cacheKey);
|
|
2966
|
+
throw error;
|
|
2967
|
+
}
|
|
2968
|
+
};
|
|
2969
|
+
var ensureStrategyPluginsLoaded = async (cwd = getTradejsProjectCwd()) => {
|
|
2970
|
+
const { projectRoot, state } = getStrategyRegistryState(cwd);
|
|
2971
|
+
if (!state.pluginsLoadPromise) {
|
|
2972
|
+
(0, import_indicators.resetIndicatorRegistryCache)(projectRoot);
|
|
2973
|
+
state.pluginsLoadPromise = (async () => {
|
|
2974
|
+
const { strategyModules, indicatorModules } = await getConfiguredPluginModuleNames(projectRoot);
|
|
2975
|
+
const strategySet = new Set(strategyModules);
|
|
2976
|
+
const indicatorSet = new Set(indicatorModules);
|
|
2977
|
+
const pluginModuleNames = [
|
|
2978
|
+
.../* @__PURE__ */ new Set([...strategyModules, ...indicatorModules])
|
|
2979
|
+
];
|
|
2980
|
+
if (!pluginModuleNames.length) {
|
|
2981
|
+
return;
|
|
2982
|
+
}
|
|
2983
|
+
const issues = [];
|
|
2984
|
+
for (const moduleName of pluginModuleNames) {
|
|
2985
|
+
try {
|
|
2986
|
+
const resolvedModuleName = resolvePluginModuleSpecifier(
|
|
2987
|
+
moduleName,
|
|
2988
|
+
projectRoot
|
|
2989
|
+
);
|
|
2990
|
+
const moduleExport = await importStrategyPluginModule(
|
|
2991
|
+
resolvedModuleName,
|
|
2992
|
+
projectRoot
|
|
2993
|
+
);
|
|
2994
|
+
if (strategySet.has(moduleName)) {
|
|
2995
|
+
const pluginDefinition = extractStrategyPluginDefinition(moduleExport);
|
|
2996
|
+
if (!pluginDefinition) {
|
|
2997
|
+
issues.push(
|
|
2998
|
+
`${moduleName}: export { strategyEntries } is missing`
|
|
2999
|
+
);
|
|
3000
|
+
} else {
|
|
3001
|
+
const entryIssues = validateStrategyEntries(
|
|
3002
|
+
moduleName,
|
|
3003
|
+
pluginDefinition.strategyEntries,
|
|
3004
|
+
state
|
|
3005
|
+
);
|
|
3006
|
+
issues.push(...entryIssues);
|
|
3007
|
+
if (entryIssues.length === 0) {
|
|
3008
|
+
registerEntries2(
|
|
3009
|
+
pluginDefinition.strategyEntries,
|
|
3010
|
+
moduleName,
|
|
3011
|
+
state
|
|
3012
|
+
);
|
|
3013
|
+
}
|
|
3014
|
+
}
|
|
3015
|
+
}
|
|
3016
|
+
if (indicatorSet.has(moduleName)) {
|
|
3017
|
+
const indicatorPluginDefinition = extractIndicatorPluginDefinition(moduleExport);
|
|
3018
|
+
if (!indicatorPluginDefinition) {
|
|
3019
|
+
issues.push(
|
|
3020
|
+
`${moduleName}: export { indicatorEntries } is missing`
|
|
3021
|
+
);
|
|
3022
|
+
} else {
|
|
3023
|
+
(0, import_indicators.registerIndicatorEntries)(
|
|
3024
|
+
indicatorPluginDefinition.indicatorEntries,
|
|
3025
|
+
moduleName,
|
|
3026
|
+
projectRoot
|
|
3027
|
+
);
|
|
3028
|
+
}
|
|
3029
|
+
}
|
|
3030
|
+
if (!strategySet.has(moduleName) && !indicatorSet.has(moduleName)) {
|
|
3031
|
+
issues.push(`${moduleName}: plugin is not declared in config`);
|
|
3032
|
+
}
|
|
3033
|
+
} catch (error) {
|
|
3034
|
+
issues.push(`${moduleName}: failed to import: ${String(error)}`);
|
|
3035
|
+
}
|
|
3036
|
+
}
|
|
3037
|
+
if (issues.length > 0) {
|
|
3038
|
+
throw new Error(
|
|
3039
|
+
["Invalid TradeJS plugin catalog:", ...issues].join("\n")
|
|
3040
|
+
);
|
|
3041
|
+
}
|
|
3042
|
+
})();
|
|
3043
|
+
}
|
|
3044
|
+
await state.pluginsLoadPromise;
|
|
3045
|
+
};
|
|
3046
|
+
var getStrategyCreator = async (name, cwd = getTradejsProjectCwd(), moduleName) => {
|
|
3047
|
+
if (moduleName?.trim()) {
|
|
3048
|
+
await ensureStrategyPluginsLoaded(cwd);
|
|
3049
|
+
return (await resolveStrategyModuleEntry({ strategyName: name, moduleName, cwd })).creator;
|
|
3050
|
+
}
|
|
3051
|
+
await ensureStrategyPluginsLoaded(cwd);
|
|
3052
|
+
const { state } = getStrategyRegistryState(cwd);
|
|
3053
|
+
return state.strategyCreators.get(name);
|
|
3054
|
+
};
|
|
3055
|
+
var getStrategyEntry = async (name, cwd = getTradejsProjectCwd(), moduleName) => {
|
|
3056
|
+
if (moduleName?.trim()) {
|
|
3057
|
+
await ensureStrategyPluginsLoaded(cwd);
|
|
3058
|
+
return (await resolveStrategyModuleEntry({ strategyName: name, moduleName, cwd })).entry;
|
|
3059
|
+
}
|
|
3060
|
+
await ensureStrategyPluginsLoaded(cwd);
|
|
3061
|
+
const { state } = getStrategyRegistryState(cwd);
|
|
3062
|
+
return state.strategyEntriesMap.get(name);
|
|
3063
|
+
};
|
|
3064
|
+
var getStrategyPluginSource = async (name, cwd = getTradejsProjectCwd(), moduleName) => {
|
|
3065
|
+
if (moduleName?.trim()) {
|
|
3066
|
+
await ensureStrategyPluginsLoaded(cwd);
|
|
3067
|
+
return (await resolveStrategyModuleEntry({ strategyName: name, moduleName, cwd })).source;
|
|
3068
|
+
}
|
|
3069
|
+
await ensureStrategyPluginsLoaded(cwd);
|
|
3070
|
+
const { state } = getStrategyRegistryState(cwd);
|
|
3071
|
+
return state.strategySourcesMap.get(name);
|
|
3072
|
+
};
|
|
3073
|
+
var getStrategyManifest = (name, cwd = getTradejsProjectCwd()) => {
|
|
3074
|
+
if (!name) {
|
|
3075
|
+
return void 0;
|
|
3076
|
+
}
|
|
3077
|
+
const { state } = getStrategyRegistryState(cwd);
|
|
3078
|
+
return state.strategyManifestsMap.get(name);
|
|
3079
|
+
};
|
|
3080
|
+
var strategies = new Proxy(
|
|
3081
|
+
{},
|
|
3082
|
+
{
|
|
3083
|
+
get: (_target, property) => {
|
|
3084
|
+
if (typeof property !== "string") {
|
|
3085
|
+
return void 0;
|
|
3086
|
+
}
|
|
3087
|
+
return getStrategyRegistryState().state.strategyCreators.get(property);
|
|
3088
|
+
},
|
|
3089
|
+
ownKeys: () => {
|
|
3090
|
+
return [...getStrategyRegistryState().state.strategyCreators.keys()];
|
|
3091
|
+
},
|
|
3092
|
+
getOwnPropertyDescriptor: () => ({
|
|
3093
|
+
enumerable: true,
|
|
3094
|
+
configurable: true
|
|
3095
|
+
})
|
|
3096
|
+
}
|
|
3097
|
+
);
|
|
3098
|
+
|
|
3099
|
+
// src/strategyHelpers/tickerProfiles.ts
|
|
3100
|
+
var snapshots = /* @__PURE__ */ new Map();
|
|
3101
|
+
var resolveTickerProfileSource = (strategy) => {
|
|
3102
|
+
const declared = getStrategyManifest(strategy)?.contextRequirements?.tickerProfiles;
|
|
3103
|
+
if (declared) return declared;
|
|
3104
|
+
const provider = process.env.TICKER_PROFILES_PROVIDER?.trim();
|
|
3105
|
+
const universeId = process.env.TICKER_PROFILES_UNIVERSE?.trim();
|
|
3106
|
+
if (!provider && !universeId) return void 0;
|
|
3107
|
+
if (!provider || !universeId)
|
|
3108
|
+
throw new Error(
|
|
3109
|
+
"Set both TICKER_PROFILES_PROVIDER and TICKER_PROFILES_UNIVERSE"
|
|
3110
|
+
);
|
|
3111
|
+
return { provider, universeId };
|
|
3112
|
+
};
|
|
3113
|
+
var loadTickerProfileContext = async ({
|
|
3114
|
+
projectRoot,
|
|
3115
|
+
provider,
|
|
3116
|
+
universeId,
|
|
3117
|
+
symbol,
|
|
3118
|
+
timestamp
|
|
3119
|
+
}) => {
|
|
3120
|
+
const asOf = (0, import_tickerProfiles2.tickerProfileMonth)(timestamp), key = JSON.stringify([projectRoot, provider, universeId, asOf]);
|
|
3121
|
+
let cached = snapshots.get(key);
|
|
3122
|
+
if (!cached || cached.expires < Date.now()) {
|
|
3123
|
+
const value = (0, import_tickerProfiles.readTickerProfiles)(projectRoot, provider, universeId, asOf).then((envelope) => {
|
|
3124
|
+
if (!envelope) return null;
|
|
3125
|
+
const s = envelope.snapshot;
|
|
3126
|
+
if (!(0, import_tickerProfiles2.isTickerProfileSnapshot)(s) || s.asOf !== asOf || s.provider !== provider || s.universeId !== universeId)
|
|
3127
|
+
throw new Error("Ticker profile identity/schema mismatch");
|
|
3128
|
+
const entry = snapshots.get(key);
|
|
3129
|
+
if (entry) entry.expires = Infinity;
|
|
3130
|
+
return new Map(
|
|
3131
|
+
s.profiles.map((p) => [
|
|
3132
|
+
p.symbol,
|
|
3133
|
+
Object.freeze({
|
|
3134
|
+
...p,
|
|
3135
|
+
definition: s.definition,
|
|
3136
|
+
provider,
|
|
3137
|
+
universeId,
|
|
3138
|
+
snapshotId: envelope.snapshotId
|
|
3139
|
+
})
|
|
3140
|
+
])
|
|
3141
|
+
);
|
|
3142
|
+
}).catch((error) => {
|
|
3143
|
+
snapshots.delete(key);
|
|
3144
|
+
throw error;
|
|
3145
|
+
});
|
|
3146
|
+
cached = { expires: Date.now() + 6e4, value };
|
|
3147
|
+
snapshots.set(key, cached);
|
|
3148
|
+
if (snapshots.size > 24) {
|
|
3149
|
+
const oldest = snapshots.keys().next().value;
|
|
3150
|
+
if (oldest) snapshots.delete(oldest);
|
|
3151
|
+
}
|
|
3152
|
+
}
|
|
3153
|
+
return (await cached.value)?.get(symbol);
|
|
3154
|
+
};
|
|
3155
|
+
var withTickerProfile = (baseContext, profile) => Object.defineProperty(
|
|
3156
|
+
Object.create(
|
|
3157
|
+
Object.getPrototypeOf(baseContext),
|
|
3158
|
+
Object.getOwnPropertyDescriptors(baseContext)
|
|
3159
|
+
),
|
|
3160
|
+
"tickerProfile",
|
|
3161
|
+
{ enumerable: true, configurable: true, value: profile }
|
|
3162
|
+
);
|
|
3163
|
+
var enrichSignalWithTickerProfile = async (signal) => {
|
|
3164
|
+
const source = resolveTickerProfileSource(signal.strategy);
|
|
3165
|
+
if (!source || signal.universe === "tradfi" || !signal.symbol.endsWith("USDT"))
|
|
3166
|
+
return;
|
|
3167
|
+
const profile = await loadTickerProfileContext({
|
|
3168
|
+
...source,
|
|
3169
|
+
projectRoot: getTradejsProjectCwd(),
|
|
3170
|
+
symbol: signal.symbol,
|
|
3171
|
+
timestamp: signal.timestamp
|
|
3172
|
+
});
|
|
3173
|
+
if (profile) {
|
|
3174
|
+
signal.additionalIndicators = {
|
|
3175
|
+
...signal.additionalIndicators,
|
|
3176
|
+
tickerProfile: profile
|
|
3177
|
+
};
|
|
3178
|
+
}
|
|
3179
|
+
};
|
|
3180
|
+
|
|
3181
|
+
// src/strategyRuntime.ts
|
|
3182
|
+
var import_jev6 = require("@tradejs/core/jev");
|
|
3183
|
+
|
|
3184
|
+
// src/jev.ts
|
|
3185
|
+
var import_promises = __toESM(require("fs/promises"));
|
|
3186
|
+
var import_node_path = __toESM(require("path"));
|
|
3187
|
+
var import_jev4 = require("@tradejs/core/jev");
|
|
3188
|
+
var import_jev5 = require("@tradejs/infra/jev");
|
|
3189
|
+
var import_userSettings = require("@tradejs/infra/userSettings");
|
|
3190
|
+
init_jevInput();
|
|
3191
|
+
init_jevInput();
|
|
3192
|
+
init_jevTraining();
|
|
3193
|
+
var resolveJevProvider = async (userName) => {
|
|
3194
|
+
const settings = await (0, import_userSettings.getUserSettings)(userName);
|
|
3195
|
+
const provider = {
|
|
3196
|
+
endpoint: settings.JEV_API_ENDPOINT || import_jev4.JEV_PROVIDERS[0].endpoint,
|
|
3197
|
+
model: settings.JEV_MODEL || import_jev4.JEV_PROVIDERS[0].model
|
|
3198
|
+
};
|
|
3199
|
+
(0, import_jev4.parseJevConfig)({ source: "provider", mode: "observe", provider });
|
|
3200
|
+
if (!settings.JEV_API_KEY)
|
|
3201
|
+
throw new Error("Configure the Jev API key in Account settings");
|
|
3202
|
+
return provider;
|
|
3203
|
+
};
|
|
3204
|
+
var emptyScores = () => Object.fromEntries(import_jev4.JEV_DIMENSIONS.map((key) => [key, null]));
|
|
3205
|
+
var pending = /* @__PURE__ */ new Map();
|
|
3206
|
+
var pendingResponses = /* @__PURE__ */ new Map();
|
|
3207
|
+
var evaluateJevInput = async ({
|
|
3208
|
+
input,
|
|
3209
|
+
config,
|
|
3210
|
+
userName,
|
|
3211
|
+
projectRoot
|
|
3212
|
+
}) => {
|
|
3213
|
+
config = (0, import_jev4.parseJevConfig)(config);
|
|
3214
|
+
const inputHash = (0, import_jev5.jevHash)(input);
|
|
3215
|
+
const questions = questionsForJevInput(input);
|
|
3216
|
+
const questionsHash = (0, import_jev5.jevHash)(questions);
|
|
3217
|
+
const base = {
|
|
3218
|
+
questionSet: import_jev4.JEV_QUESTION_SET,
|
|
3219
|
+
schema: "tradejs-signal-assessment/v5",
|
|
3220
|
+
source: config.source,
|
|
3221
|
+
mode: config.mode,
|
|
3222
|
+
inputHash,
|
|
3223
|
+
questionsHash,
|
|
3224
|
+
geometryStatus: input.geometryStatus,
|
|
3225
|
+
levelsValid: typeof input.features["signal.validLevels"] === "boolean" ? input.features["signal.validLevels"] : null
|
|
3226
|
+
};
|
|
3227
|
+
if (!input.questions.length)
|
|
3228
|
+
return {
|
|
3229
|
+
assessment: {
|
|
3230
|
+
...base,
|
|
3231
|
+
status: "unavailable",
|
|
3232
|
+
model: "",
|
|
3233
|
+
scores: emptyScores(),
|
|
3234
|
+
confidence: emptyScores()
|
|
3235
|
+
}
|
|
3236
|
+
};
|
|
3237
|
+
if (config.source === "local") {
|
|
3238
|
+
const contents = await import_promises.default.readFile(
|
|
3239
|
+
import_node_path.default.resolve(projectRoot, config.modelFile),
|
|
3240
|
+
"utf8"
|
|
3241
|
+
);
|
|
3242
|
+
if ((0, import_jev5.jevFileHash)(contents) !== config.modelSha256)
|
|
3243
|
+
throw new Error("Local Jev gate checksum mismatch");
|
|
3244
|
+
const { validateJevGateModel: validateJevGateModel2 } = await Promise.resolve().then(() => (init_jevTraining(), jevTraining_exports));
|
|
3245
|
+
const model = validateJevGateModel2(JSON.parse(contents));
|
|
3246
|
+
if (model.strategy !== input.strategy || model.questionsHash !== (0, import_jev5.jevHash)(JEV_QUESTIONS))
|
|
3247
|
+
throw new Error("Local Jev gate strategy or question version mismatch");
|
|
3248
|
+
const scores = Object.fromEntries(
|
|
3249
|
+
import_jev4.JEV_DIMENSIONS.map((key) => [
|
|
3250
|
+
key,
|
|
3251
|
+
input.questions.includes(key) && model.trees[key] ? (0, import_jev4.predictJevTree)(model.trees[key], input.features) : null
|
|
3252
|
+
])
|
|
3253
|
+
);
|
|
3254
|
+
return {
|
|
3255
|
+
assessment: {
|
|
3256
|
+
...base,
|
|
3257
|
+
status: "available",
|
|
3258
|
+
model: config.modelSha256,
|
|
3259
|
+
scores,
|
|
3260
|
+
confidence: emptyScores()
|
|
3261
|
+
}
|
|
3262
|
+
};
|
|
3263
|
+
}
|
|
3264
|
+
const provider = config.provider ?? await resolveJevProvider(userName);
|
|
3265
|
+
const id = (0, import_jev5.jevHash)({ inputHash, questionsHash, provider });
|
|
3266
|
+
const dir = import_node_path.default.resolve(projectRoot, config.recordsDir ?? "data/ai/jev");
|
|
3267
|
+
const file = import_node_path.default.join(dir, "records", `${id}.json`);
|
|
3268
|
+
const getRecord = async () => {
|
|
3269
|
+
const existing = await (0, import_jev5.readJevArtifact)(file);
|
|
3270
|
+
if (existing) {
|
|
3271
|
+
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))
|
|
3272
|
+
throw new Error("Jev recording identity mismatch");
|
|
3273
|
+
(0, import_jev5.validateJevResponse)(existing.response, input.questions);
|
|
3274
|
+
for (const key of import_jev4.JEV_DIMENSIONS) {
|
|
3275
|
+
const expected = input.questions.includes(key) ? existing.response.answers[key].score / (0, import_jev5.jevScoreMaximum)(key) : null;
|
|
3276
|
+
if (existing.scores[key] !== expected)
|
|
3277
|
+
throw new Error("Jev recording scores do not match the response");
|
|
3278
|
+
}
|
|
3279
|
+
return existing;
|
|
3280
|
+
}
|
|
3281
|
+
if (config.source === "recorded")
|
|
3282
|
+
throw new Error(
|
|
3283
|
+
`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.`
|
|
3284
|
+
);
|
|
3285
|
+
const settings = await (0, import_userSettings.getUserSettings)(userName);
|
|
3286
|
+
if (!settings.JEV_API_KEY)
|
|
3287
|
+
throw new Error("Configure the Jev API key in Account settings");
|
|
3288
|
+
const apiKey = settings.JEV_API_KEY;
|
|
3289
|
+
const configuredProvider = await resolveJevProvider(userName);
|
|
3290
|
+
if (configuredProvider.endpoint !== provider.endpoint)
|
|
3291
|
+
throw new Error(
|
|
3292
|
+
"Jev account endpoint changed; update the frozen provider configuration"
|
|
3293
|
+
);
|
|
3294
|
+
const started = Date.now();
|
|
3295
|
+
const state = stateForJevInput(input);
|
|
3296
|
+
const responseId = (0, import_jev5.jevHash)({
|
|
3297
|
+
state,
|
|
3298
|
+
questionsHash,
|
|
3299
|
+
provider,
|
|
3300
|
+
user: (0, import_jev5.jevHash)(userName)
|
|
3301
|
+
});
|
|
3302
|
+
const responseFile = import_node_path.default.join(dir, "responses", `${responseId}.json`);
|
|
3303
|
+
let responsePromise = pendingResponses.get(responseFile);
|
|
3304
|
+
if (!responsePromise) {
|
|
3305
|
+
responsePromise = (async () => {
|
|
3306
|
+
const saved = await (0, import_jev5.readJevArtifact)(responseFile);
|
|
3307
|
+
if (saved) return (0, import_jev5.validateJevResponse)(saved, input.questions);
|
|
3308
|
+
const fresh = await (0, import_jev5.requestJev)(provider, apiKey, state, questions);
|
|
3309
|
+
await (0, import_jev5.writeJevArtifact)(responseFile, fresh);
|
|
3310
|
+
return fresh;
|
|
3311
|
+
})();
|
|
3312
|
+
pendingResponses.set(responseFile, responsePromise);
|
|
3313
|
+
}
|
|
3314
|
+
let response;
|
|
3315
|
+
try {
|
|
3316
|
+
response = await responsePromise;
|
|
3317
|
+
} finally {
|
|
3318
|
+
pendingResponses.delete(responseFile);
|
|
3319
|
+
}
|
|
3320
|
+
const scores = Object.fromEntries(
|
|
3321
|
+
import_jev4.JEV_DIMENSIONS.map((key) => [
|
|
3322
|
+
key,
|
|
3323
|
+
input.questions.includes(key) ? response.answers[key].score / (0, import_jev5.jevScoreMaximum)(key) : null
|
|
3324
|
+
])
|
|
3325
|
+
);
|
|
3326
|
+
const value = {
|
|
3327
|
+
schema: "tradejs-jev-record/v4",
|
|
3328
|
+
id,
|
|
3329
|
+
inputHash,
|
|
3330
|
+
questionsHash,
|
|
3331
|
+
provider,
|
|
3332
|
+
input,
|
|
3333
|
+
response,
|
|
3334
|
+
scores,
|
|
3335
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3336
|
+
elapsedMs: Date.now() - started
|
|
3337
|
+
};
|
|
3338
|
+
await (0, import_jev5.writeJevArtifact)(file, value);
|
|
3339
|
+
return await (0, import_jev5.readJevArtifact)(file);
|
|
3340
|
+
};
|
|
3341
|
+
const pendingKey = `${file}:${(0, import_jev5.jevHash)(userName)}`;
|
|
3342
|
+
let promise = pending.get(pendingKey);
|
|
3343
|
+
if (!promise) {
|
|
3344
|
+
promise = getRecord();
|
|
3345
|
+
pending.set(pendingKey, promise);
|
|
3346
|
+
}
|
|
3347
|
+
let record;
|
|
3348
|
+
try {
|
|
3349
|
+
record = await promise;
|
|
3350
|
+
} finally {
|
|
3351
|
+
pending.delete(pendingKey);
|
|
3352
|
+
}
|
|
3353
|
+
return {
|
|
3354
|
+
record,
|
|
3355
|
+
assessment: {
|
|
3356
|
+
...base,
|
|
3357
|
+
status: "available",
|
|
3358
|
+
recordId: id,
|
|
3359
|
+
model: record.response.model,
|
|
3360
|
+
scores: record.scores,
|
|
3361
|
+
confidence: Object.fromEntries(
|
|
3362
|
+
import_jev4.JEV_DIMENSIONS.map((key) => [
|
|
3363
|
+
key,
|
|
3364
|
+
record.response.answers[key]?.confidence ?? null
|
|
3365
|
+
])
|
|
3366
|
+
)
|
|
3367
|
+
}
|
|
3368
|
+
};
|
|
3369
|
+
};
|
|
3370
|
+
var assessSignalWithJev = async ({
|
|
3371
|
+
signal,
|
|
3372
|
+
config,
|
|
3373
|
+
userName,
|
|
3374
|
+
projectRoot,
|
|
3375
|
+
strict,
|
|
3376
|
+
scope = "runtime"
|
|
3377
|
+
}) => {
|
|
3378
|
+
let assessment;
|
|
3379
|
+
try {
|
|
3380
|
+
const input = buildJevInput(signal);
|
|
3381
|
+
({ assessment } = await evaluateJevInput({
|
|
3382
|
+
input,
|
|
3383
|
+
config,
|
|
3384
|
+
userName,
|
|
3385
|
+
projectRoot
|
|
3386
|
+
}));
|
|
3387
|
+
} catch (error) {
|
|
3388
|
+
if (strict) throw error;
|
|
3389
|
+
assessment = {
|
|
3390
|
+
questionSet: import_jev4.JEV_QUESTION_SET,
|
|
3391
|
+
schema: "tradejs-signal-assessment/v5",
|
|
3392
|
+
source: config.source,
|
|
3393
|
+
mode: config.mode,
|
|
3394
|
+
status: "unavailable",
|
|
3395
|
+
inputHash: "",
|
|
3396
|
+
questionsHash: "",
|
|
3397
|
+
geometryStatus: "absent",
|
|
3398
|
+
levelsValid: null,
|
|
3399
|
+
model: config.provider?.model ?? config.modelSha256 ?? "",
|
|
3400
|
+
scores: emptyScores(),
|
|
3401
|
+
confidence: emptyScores()
|
|
2875
3402
|
};
|
|
2876
3403
|
}
|
|
2877
|
-
|
|
2878
|
-
|
|
2879
|
-
|
|
2880
|
-
|
|
2881
|
-
|
|
2882
|
-
|
|
2883
|
-
|
|
2884
|
-
|
|
2885
|
-
|
|
2886
|
-
|
|
2887
|
-
|
|
2888
|
-
|
|
2889
|
-
|
|
2890
|
-
|
|
2891
|
-
|
|
2892
|
-
|
|
2893
|
-
|
|
2894
|
-
|
|
2895
|
-
|
|
2896
|
-
|
|
2897
|
-
|
|
2898
|
-
cmcGlobal.altMarketCapChange24hPct
|
|
3404
|
+
signal.assessment = assessment;
|
|
3405
|
+
signal.additionalIndicators = {
|
|
3406
|
+
...signal.additionalIndicators,
|
|
3407
|
+
jev: (0, import_jev4.jevAssessmentFeatures)(assessment)
|
|
3408
|
+
};
|
|
3409
|
+
const identity = {
|
|
3410
|
+
signalId: signal.signalId,
|
|
3411
|
+
strategy: signal.strategy,
|
|
3412
|
+
symbol: signal.symbol,
|
|
3413
|
+
timestamp: signal.timestamp,
|
|
3414
|
+
scope,
|
|
3415
|
+
user: (0, import_jev5.jevHash)(userName),
|
|
3416
|
+
config,
|
|
3417
|
+
assessment
|
|
3418
|
+
};
|
|
3419
|
+
await (0, import_jev5.writeJevArtifact)(
|
|
3420
|
+
import_node_path.default.resolve(
|
|
3421
|
+
projectRoot,
|
|
3422
|
+
config.recordsDir ?? "data/ai/jev",
|
|
3423
|
+
"decisions",
|
|
3424
|
+
`${(0, import_jev5.jevHash)(identity)}.json`
|
|
2899
3425
|
),
|
|
2900
|
-
|
|
2901
|
-
|
|
2902
|
-
|
|
2903
|
-
|
|
2904
|
-
|
|
3426
|
+
identity
|
|
3427
|
+
);
|
|
3428
|
+
return assessment;
|
|
3429
|
+
};
|
|
3430
|
+
|
|
3431
|
+
// src/strategyRuntime.ts
|
|
3432
|
+
var import_constants7 = require("@tradejs/core/constants");
|
|
3433
|
+
var import_strategies6 = require("@tradejs/core/strategies");
|
|
3434
|
+
var import_logger14 = require("@tradejs/infra/logger");
|
|
3435
|
+
|
|
3436
|
+
// src/strategyHelpers/runtime.ts
|
|
3437
|
+
var import_logger12 = require("@tradejs/infra/logger");
|
|
3438
|
+
var import_constants5 = require("@tradejs/core/constants");
|
|
3439
|
+
var import_ml2 = require("@tradejs/infra/ml");
|
|
3440
|
+
|
|
3441
|
+
// src/strategy/entrySubmissionValidation.ts
|
|
3442
|
+
var import_logger6 = require("@tradejs/infra/logger");
|
|
3443
|
+
|
|
3444
|
+
// src/strategy/entryExecutionValidation.ts
|
|
3445
|
+
var EntrySubmissionValidationError = class extends Error {
|
|
3446
|
+
constructor() {
|
|
3447
|
+
super(...arguments);
|
|
3448
|
+
this.name = "EntrySubmissionValidationError";
|
|
3449
|
+
}
|
|
3450
|
+
};
|
|
3451
|
+
var resolveEntryExecutionLimits = (config, direction) => {
|
|
3452
|
+
const side = config[direction];
|
|
3453
|
+
const minimum = side && typeof side === "object" && "minRiskRatio" in side ? side.minRiskRatio : void 0;
|
|
3454
|
+
const maximum = config.MAX_LOSS_VALUE;
|
|
3455
|
+
if (minimum == null && maximum == null) return void 0;
|
|
3456
|
+
const minimumNetRiskRatio = minimum == null ? void 0 : Number(minimum);
|
|
3457
|
+
const maxLossValue = maximum == null ? void 0 : Number(maximum);
|
|
3458
|
+
const feeRate = Number(config.RISK_FEE_RATE ?? 0);
|
|
3459
|
+
const slippage = Number(config.RISK_SLIPPAGE_BPS ?? 0);
|
|
3460
|
+
const impact = Number(config.RISK_MARKET_IMPACT_BPS ?? 0);
|
|
3461
|
+
const slippageBps = slippage + impact;
|
|
3462
|
+
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)
|
|
3463
|
+
throw new Error("INVALID_ENTRY_EXECUTION_LIMITS");
|
|
3464
|
+
return { minimumNetRiskRatio, maxLossValue, feeRate, slippageBps };
|
|
3465
|
+
};
|
|
3466
|
+
var validateEntryExecution = ({
|
|
3467
|
+
direction,
|
|
3468
|
+
price,
|
|
3469
|
+
qty,
|
|
3470
|
+
stopLossPrice,
|
|
3471
|
+
takeProfitPrice,
|
|
3472
|
+
limits
|
|
3473
|
+
}) => {
|
|
3474
|
+
if (!Number.isFinite(price) || price <= 0 || !Number.isFinite(qty) || qty <= 0 || stopLossPrice == null || !Number.isFinite(stopLossPrice) || stopLossPrice <= 0 || !Number.isFinite(takeProfitPrice) || takeProfitPrice <= 0)
|
|
3475
|
+
throw new EntrySubmissionValidationError(
|
|
3476
|
+
"ENTRY_ECONOMICS_INPUTS_UNAVAILABLE"
|
|
3477
|
+
);
|
|
3478
|
+
const side = direction === "LONG" ? 1 : -1;
|
|
3479
|
+
if (side * (price - stopLossPrice) <= 0 || side * (takeProfitPrice - price) <= 0)
|
|
3480
|
+
throw new EntrySubmissionValidationError(
|
|
3481
|
+
"INVALID_ENTRY_PROTECTION_AT_SUBMISSION"
|
|
3482
|
+
);
|
|
3483
|
+
const slippage = limits.slippageBps / 1e4;
|
|
3484
|
+
const cost = (value, sign) => value * slippage + value * (1 + sign * slippage) * limits.feeRate;
|
|
3485
|
+
const entryCost = cost(price, side);
|
|
3486
|
+
const lossPerUnit = Math.abs(price - stopLossPrice) + entryCost + cost(stopLossPrice, -side);
|
|
3487
|
+
const rewardPerUnit = Math.max(
|
|
3488
|
+
0,
|
|
3489
|
+
Math.abs(takeProfitPrice - price) - entryCost - cost(takeProfitPrice, -side)
|
|
3490
|
+
);
|
|
3491
|
+
const netRiskRatio = rewardPerUnit / lossPerUnit;
|
|
3492
|
+
const plannedLossValue = qty * lossPerUnit;
|
|
3493
|
+
if (!Number.isFinite(netRiskRatio) || !Number.isFinite(plannedLossValue))
|
|
3494
|
+
throw new EntrySubmissionValidationError(
|
|
3495
|
+
"ENTRY_ECONOMICS_INPUTS_UNAVAILABLE"
|
|
3496
|
+
);
|
|
3497
|
+
const belowMinimumNetRiskRatio = limits.minimumNetRiskRatio != null && netRiskRatio <= limits.minimumNetRiskRatio;
|
|
3498
|
+
const riskExceeded = limits.maxLossValue != null && plannedLossValue > limits.maxLossValue * (1 + 1e-9);
|
|
3499
|
+
return {
|
|
3500
|
+
netRiskRatio,
|
|
3501
|
+
plannedLossValue,
|
|
3502
|
+
riskExceeded,
|
|
3503
|
+
belowMinimumNetRiskRatio
|
|
3504
|
+
};
|
|
3505
|
+
};
|
|
3506
|
+
|
|
3507
|
+
// src/strategy/entrySubmissionValidation.ts
|
|
3508
|
+
var MAX_ENTRY_QUOTE_AGE_MS = 5e3;
|
|
3509
|
+
var toFiniteNumberOrNull = (value) => typeof value === "number" && Number.isFinite(value) ? value : null;
|
|
3510
|
+
var getOrderArrivalSnapshot = async ({
|
|
3511
|
+
connector,
|
|
3512
|
+
symbol,
|
|
3513
|
+
requireTimestamp = false
|
|
3514
|
+
}) => {
|
|
3515
|
+
if (typeof connector.getTopOfBookTicker !== "function") {
|
|
3516
|
+
return {
|
|
3517
|
+
arrivalSnapshotTime: Date.now(),
|
|
3518
|
+
arrivalSource: "unavailable",
|
|
3519
|
+
bid: null,
|
|
3520
|
+
ask: null,
|
|
3521
|
+
arrivalMid: null,
|
|
3522
|
+
spreadBps: null
|
|
3523
|
+
};
|
|
3524
|
+
}
|
|
3525
|
+
try {
|
|
3526
|
+
const ticker = await connector.getTopOfBookTicker(symbol);
|
|
3527
|
+
const arrivalSnapshotTime = toFiniteNumberOrNull(ticker?.timestamp);
|
|
3528
|
+
const timestampUnavailable = requireTimestamp && arrivalSnapshotTime == null;
|
|
3529
|
+
const bid = timestampUnavailable ? null : toFiniteNumberOrNull(ticker?.bidPrice);
|
|
3530
|
+
const ask = timestampUnavailable ? null : toFiniteNumberOrNull(ticker?.askPrice);
|
|
3531
|
+
const arrivalMid = bid != null && ask != null ? (bid + ask) / 2 : null;
|
|
3532
|
+
const spreadBps = bid != null && ask != null && arrivalMid != null && arrivalMid > 0 ? (ask - bid) / arrivalMid * 1e4 : null;
|
|
3533
|
+
return {
|
|
3534
|
+
arrivalSnapshotTime: arrivalSnapshotTime ?? Date.now(),
|
|
3535
|
+
arrivalSource: "top_of_book",
|
|
3536
|
+
bid,
|
|
3537
|
+
ask,
|
|
3538
|
+
arrivalMid,
|
|
3539
|
+
spreadBps
|
|
3540
|
+
};
|
|
3541
|
+
} catch (error) {
|
|
3542
|
+
import_logger6.logger.warn(
|
|
3543
|
+
"runtime order arrival snapshot failed: %s %s",
|
|
3544
|
+
symbol,
|
|
3545
|
+
error?.message || String(error)
|
|
3546
|
+
);
|
|
3547
|
+
return {
|
|
3548
|
+
arrivalSnapshotTime: Date.now(),
|
|
3549
|
+
arrivalSource: "top_of_book_error",
|
|
3550
|
+
bid: null,
|
|
3551
|
+
ask: null,
|
|
3552
|
+
arrivalMid: null,
|
|
3553
|
+
spreadBps: null
|
|
3554
|
+
};
|
|
3555
|
+
}
|
|
3556
|
+
};
|
|
3557
|
+
var validateEntryProtectionAtArrival = ({
|
|
3558
|
+
direction,
|
|
3559
|
+
signalPrice,
|
|
3560
|
+
bid,
|
|
3561
|
+
ask,
|
|
3562
|
+
arrivalMid,
|
|
3563
|
+
takeProfits,
|
|
3564
|
+
stopLossPrice
|
|
3565
|
+
}) => {
|
|
3566
|
+
const entryReference = direction === "LONG" ? ask ?? arrivalMid ?? signalPrice : bid ?? arrivalMid ?? signalPrice;
|
|
3567
|
+
const stopReference = direction === "LONG" ? bid ?? arrivalMid ?? signalPrice : ask ?? arrivalMid ?? signalPrice;
|
|
3568
|
+
if (!Number.isFinite(entryReference) || entryReference <= 0 || !Number.isFinite(stopReference) || stopReference <= 0) {
|
|
3569
|
+
throw new Error("INVALID_ENTRY_REFERENCE_AT_ARRIVAL");
|
|
3570
|
+
}
|
|
3571
|
+
for (const takeProfit of takeProfits) {
|
|
3572
|
+
if (!Number.isFinite(takeProfit.price) || takeProfit.price <= 0 || (direction === "LONG" ? takeProfit.price <= entryReference : takeProfit.price >= entryReference)) {
|
|
3573
|
+
throw new Error("TAKE_PROFIT_CROSSED_BEFORE_ENTRY");
|
|
3574
|
+
}
|
|
3575
|
+
}
|
|
3576
|
+
if (stopLossPrice != null && (!Number.isFinite(stopLossPrice) || stopLossPrice <= 0 || (direction === "LONG" ? stopLossPrice >= stopReference : stopLossPrice <= stopReference))) {
|
|
3577
|
+
throw new Error("STOP_LOSS_CROSSED_BEFORE_ENTRY");
|
|
3578
|
+
}
|
|
3579
|
+
};
|
|
3580
|
+
var createEntrySubmissionValidation = ({
|
|
3581
|
+
connector,
|
|
3582
|
+
symbol,
|
|
3583
|
+
direction,
|
|
3584
|
+
takeProfits,
|
|
3585
|
+
stopLossPrice,
|
|
3586
|
+
limits,
|
|
3587
|
+
onValidated
|
|
3588
|
+
}) => {
|
|
3589
|
+
if (!limits) return void 0;
|
|
3590
|
+
if (!connector.capabilities?.orderSubmissionValidation)
|
|
3591
|
+
throw new EntrySubmissionValidationError(
|
|
3592
|
+
"ENTRY_SUBMISSION_VALIDATION_UNSUPPORTED"
|
|
3593
|
+
);
|
|
3594
|
+
return async ({ qty, executionPrice }) => {
|
|
3595
|
+
const simulated = Reflect.get(connector, "__tradejsTestConnector") === true;
|
|
3596
|
+
const snapshot2 = simulated && executionPrice != null ? {
|
|
3597
|
+
arrivalSnapshotTime: Date.now(),
|
|
3598
|
+
arrivalSource: "simulation",
|
|
3599
|
+
bid: executionPrice,
|
|
3600
|
+
ask: executionPrice,
|
|
3601
|
+
arrivalMid: executionPrice,
|
|
3602
|
+
spreadBps: 0
|
|
3603
|
+
} : await getOrderArrivalSnapshot({
|
|
3604
|
+
connector,
|
|
3605
|
+
symbol,
|
|
3606
|
+
requireTimestamp: true
|
|
3607
|
+
});
|
|
3608
|
+
const age = Date.now() - snapshot2.arrivalSnapshotTime;
|
|
3609
|
+
if (snapshot2.bid == null || snapshot2.ask == null || !Number.isFinite(snapshot2.bid) || !Number.isFinite(snapshot2.ask) || snapshot2.bid <= 0 || snapshot2.ask < snapshot2.bid)
|
|
3610
|
+
throw new EntrySubmissionValidationError(
|
|
3611
|
+
"ENTRY_QUOTE_UNAVAILABLE_AT_SUBMISSION"
|
|
3612
|
+
);
|
|
3613
|
+
if (!Number.isFinite(age) || age < -MAX_ENTRY_QUOTE_AGE_MS || age > MAX_ENTRY_QUOTE_AGE_MS)
|
|
3614
|
+
throw new EntrySubmissionValidationError(
|
|
3615
|
+
"ENTRY_QUOTE_STALE_AT_SUBMISSION"
|
|
3616
|
+
);
|
|
3617
|
+
const price = direction === "LONG" ? snapshot2.ask : snapshot2.bid;
|
|
3618
|
+
validateEntryProtectionAtArrival({
|
|
3619
|
+
direction,
|
|
3620
|
+
signalPrice: price,
|
|
3621
|
+
bid: snapshot2.bid,
|
|
3622
|
+
ask: snapshot2.ask,
|
|
3623
|
+
arrivalMid: snapshot2.arrivalMid,
|
|
3624
|
+
takeProfits,
|
|
3625
|
+
stopLossPrice
|
|
3626
|
+
});
|
|
3627
|
+
const prices = takeProfits.map((tp) => tp.price);
|
|
3628
|
+
const economics2 = validateEntryExecution({
|
|
3629
|
+
direction,
|
|
3630
|
+
price,
|
|
3631
|
+
qty,
|
|
3632
|
+
stopLossPrice,
|
|
3633
|
+
takeProfitPrice: direction === "LONG" ? Math.max(...prices) : Math.min(...prices),
|
|
3634
|
+
limits
|
|
3635
|
+
});
|
|
3636
|
+
if (economics2.belowMinimumNetRiskRatio) {
|
|
3637
|
+
import_logger6.logger.warn(
|
|
3638
|
+
"entry submission warning: %s %s ENTRY_RR_BELOW_MIN_AT_SUBMISSION (RR %s, threshold %s)",
|
|
3639
|
+
symbol,
|
|
3640
|
+
direction,
|
|
3641
|
+
economics2.netRiskRatio,
|
|
3642
|
+
limits.minimumNetRiskRatio
|
|
3643
|
+
);
|
|
3644
|
+
}
|
|
3645
|
+
onValidated?.(snapshot2, economics2);
|
|
2905
3646
|
};
|
|
2906
3647
|
};
|
|
2907
|
-
|
|
3648
|
+
|
|
3649
|
+
// src/ai.ts
|
|
3650
|
+
var import_aiLanguages2 = require("@tradejs/core/aiLanguages");
|
|
3651
|
+
var import_redis4 = require("@tradejs/infra/redis");
|
|
3652
|
+
|
|
3653
|
+
// src/strategyAdapters/ai.ts
|
|
3654
|
+
var import_indicators2 = require("@tradejs/core/indicators");
|
|
3655
|
+
var import_aiFeatures = require("@tradejs/core/aiFeatures");
|
|
3656
|
+
|
|
3657
|
+
// src/aiShared.ts
|
|
3658
|
+
var MAX_AI_SERIES_POINTS = 5;
|
|
3659
|
+
var COMPACT_INDICATORS_SNAPSHOT_SYMBOL = /* @__PURE__ */ Symbol.for(
|
|
3660
|
+
"tradejs.indicators.compactSnapshot"
|
|
3661
|
+
);
|
|
3662
|
+
var COMPACT_INDICATORS_SNAPSHOT_KEY = "__tradejsCompactIndicatorsSnapshot";
|
|
3663
|
+
var trimSeriesDeep = (value) => {
|
|
3664
|
+
if (Array.isArray(value)) {
|
|
3665
|
+
const trimmed = value.slice(-MAX_AI_SERIES_POINTS);
|
|
3666
|
+
const isMatrix = trimmed.every((item) => Array.isArray(item));
|
|
3667
|
+
if (isMatrix) {
|
|
3668
|
+
return trimmed;
|
|
3669
|
+
}
|
|
3670
|
+
return trimmed.map(
|
|
3671
|
+
(item) => item && typeof item === "object" ? trimSeriesDeep(item) : item
|
|
3672
|
+
);
|
|
3673
|
+
}
|
|
3674
|
+
if (value && typeof value === "object") {
|
|
3675
|
+
return Object.fromEntries(
|
|
3676
|
+
Object.entries(value).map(([key, nested]) => [
|
|
3677
|
+
key,
|
|
3678
|
+
trimSeriesDeep(nested)
|
|
3679
|
+
])
|
|
3680
|
+
);
|
|
3681
|
+
}
|
|
3682
|
+
return value;
|
|
3683
|
+
};
|
|
3684
|
+
var buildCompactAiIndicatorsSnapshot = (value) => {
|
|
3685
|
+
const compactSnapshot = value && typeof value === "object" ? value[COMPACT_INDICATORS_SNAPSHOT_SYMBOL] ?? value[COMPACT_INDICATORS_SNAPSHOT_KEY] : void 0;
|
|
3686
|
+
if (typeof compactSnapshot === "function") {
|
|
3687
|
+
return compactSnapshot({ limit: MAX_AI_SERIES_POINTS });
|
|
3688
|
+
}
|
|
3689
|
+
return trimSeriesDeep(value);
|
|
3690
|
+
};
|
|
3691
|
+
|
|
3692
|
+
// src/aiMarketContext.ts
|
|
3693
|
+
var toRecord = (value) => {
|
|
3694
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
3695
|
+
return null;
|
|
3696
|
+
}
|
|
3697
|
+
return value;
|
|
3698
|
+
};
|
|
3699
|
+
var toFiniteNumber = (value) => {
|
|
3700
|
+
const numeric = typeof value === "number" ? value : typeof value === "string" && value.trim() ? Number(value) : Number.NaN;
|
|
3701
|
+
return Number.isFinite(numeric) ? numeric : null;
|
|
3702
|
+
};
|
|
3703
|
+
var roundTo = (value, decimals) => {
|
|
3704
|
+
const factor = 10 ** decimals;
|
|
3705
|
+
return Math.round(value * factor) / factor;
|
|
3706
|
+
};
|
|
3707
|
+
var buildMissingSpreadContext = () => ({
|
|
3708
|
+
source: "payload.additionalIndicators.baseContext.relative.execution.venueSpread",
|
|
3709
|
+
indicatorKey: "payload.additionalIndicators.baseContext.relative.execution.venueSpread",
|
|
3710
|
+
available: false,
|
|
3711
|
+
value: null,
|
|
3712
|
+
zScore: null,
|
|
3713
|
+
bps: null,
|
|
3714
|
+
absBps: null,
|
|
3715
|
+
bias: null,
|
|
3716
|
+
severity: null
|
|
3717
|
+
});
|
|
3718
|
+
var buildSpreadContextFromSignal = (signal) => {
|
|
2908
3719
|
const baseContext = toRecord(signal.additionalIndicators?.baseContext);
|
|
2909
3720
|
const relative = toRecord(baseContext?.relative);
|
|
2910
|
-
const
|
|
2911
|
-
|
|
2912
|
-
|
|
2913
|
-
|
|
2914
|
-
|
|
2915
|
-
interval: null,
|
|
2916
|
-
asOfTs: null,
|
|
2917
|
-
stale: null,
|
|
2918
|
-
btcMarketCapUsd: null,
|
|
2919
|
-
ethMarketCapUsd: null,
|
|
2920
|
-
btcVolumeUsd: null,
|
|
2921
|
-
ethVolumeUsd: null,
|
|
2922
|
-
btcVolumeToMarketCap: null,
|
|
2923
|
-
ethVolumeToMarketCap: null,
|
|
2924
|
-
ethBtcMarketCapRatio: null,
|
|
2925
|
-
ethBtcMarketCapRatioChange24hPct: null,
|
|
2926
|
-
ethVsBtcVolumeRatio: null,
|
|
2927
|
-
referenceLiquidityRegime: null
|
|
2928
|
-
};
|
|
3721
|
+
const execution = toRecord(relative?.execution);
|
|
3722
|
+
const spread = toFiniteNumber(execution?.venueSpread);
|
|
3723
|
+
const zScore = toFiniteNumber(execution?.venueSpreadZScore);
|
|
3724
|
+
if (spread == null) {
|
|
3725
|
+
return buildMissingSpreadContext();
|
|
2929
3726
|
}
|
|
3727
|
+
const value = roundTo(spread, 8);
|
|
3728
|
+
const bps = roundTo(value * 1e4, 2);
|
|
3729
|
+
const absBps = Math.abs(bps);
|
|
3730
|
+
const bias = absBps < 1 ? "flat" : bps > 0 ? "coinbase_premium" : "binance_premium";
|
|
3731
|
+
const severity = absBps >= 20 ? "wide" : absBps >= 5 ? "elevated" : "normal";
|
|
2930
3732
|
return {
|
|
2931
|
-
source:
|
|
3733
|
+
source: "payload.additionalIndicators.baseContext.relative.execution.venueSpread",
|
|
3734
|
+
indicatorKey: "payload.additionalIndicators.baseContext.relative.execution.venueSpread",
|
|
2932
3735
|
available: true,
|
|
2933
|
-
|
|
2934
|
-
|
|
2935
|
-
|
|
2936
|
-
|
|
2937
|
-
|
|
2938
|
-
|
|
2939
|
-
ethVolumeUsd: toFiniteNumber(cmcReferenceAssets.ethVolumeUsd),
|
|
2940
|
-
btcVolumeToMarketCap: toFiniteNumber(
|
|
2941
|
-
cmcReferenceAssets.btcVolumeToMarketCap
|
|
2942
|
-
),
|
|
2943
|
-
ethVolumeToMarketCap: toFiniteNumber(
|
|
2944
|
-
cmcReferenceAssets.ethVolumeToMarketCap
|
|
2945
|
-
),
|
|
2946
|
-
ethBtcMarketCapRatio: toFiniteNumber(
|
|
2947
|
-
cmcReferenceAssets.ethBtcMarketCapRatio
|
|
2948
|
-
),
|
|
2949
|
-
ethBtcMarketCapRatioChange24hPct: toFiniteNumber(
|
|
2950
|
-
cmcReferenceAssets.ethBtcMarketCapRatioChange24hPct
|
|
2951
|
-
),
|
|
2952
|
-
ethVsBtcVolumeRatio: toFiniteNumber(cmcReferenceAssets.ethVsBtcVolumeRatio),
|
|
2953
|
-
referenceLiquidityRegime: typeof cmcReferenceAssets.referenceLiquidityRegime === "string" ? cmcReferenceAssets.referenceLiquidityRegime : null
|
|
3736
|
+
value,
|
|
3737
|
+
zScore,
|
|
3738
|
+
bps,
|
|
3739
|
+
absBps,
|
|
3740
|
+
bias,
|
|
3741
|
+
severity
|
|
2954
3742
|
};
|
|
2955
3743
|
};
|
|
2956
|
-
var
|
|
3744
|
+
var buildTrueDeltaContextFromSignal = (signal) => {
|
|
2957
3745
|
const baseContext = toRecord(signal.additionalIndicators?.baseContext);
|
|
2958
|
-
const
|
|
2959
|
-
const
|
|
2960
|
-
|
|
3746
|
+
const participation = toRecord(baseContext?.participation);
|
|
3747
|
+
const delta = toRecord(participation?.delta);
|
|
3748
|
+
const source = String(delta?.source ?? "");
|
|
3749
|
+
const isTrueDeltaSource = source === "kline_taker_volume" || source === "agg_trades" || source === "trades";
|
|
3750
|
+
if (!delta || !isTrueDeltaSource) {
|
|
2961
3751
|
return {
|
|
2962
|
-
source: null,
|
|
3752
|
+
source: source || null,
|
|
2963
3753
|
available: false,
|
|
2964
|
-
|
|
2965
|
-
|
|
2966
|
-
|
|
2967
|
-
|
|
2968
|
-
|
|
2969
|
-
|
|
2970
|
-
binanceVolumeUsd: null,
|
|
2971
|
-
binanceVolumeShare: null,
|
|
2972
|
-
topExchangeVolumeShare: null,
|
|
2973
|
-
liquidityRegime: null
|
|
3754
|
+
buyPressurePct: null,
|
|
3755
|
+
buyVolume: null,
|
|
3756
|
+
sellVolume: null,
|
|
3757
|
+
netDelta: null,
|
|
3758
|
+
deltaPct: null,
|
|
3759
|
+
signedVolumeZScore: null
|
|
2974
3760
|
};
|
|
2975
3761
|
}
|
|
2976
3762
|
return {
|
|
2977
|
-
source
|
|
3763
|
+
source,
|
|
2978
3764
|
available: true,
|
|
2979
|
-
|
|
2980
|
-
|
|
2981
|
-
|
|
2982
|
-
|
|
2983
|
-
|
|
2984
|
-
|
|
2985
|
-
cmcExchangeLiquidity.totalVolumeChange24hPct
|
|
2986
|
-
),
|
|
2987
|
-
binanceVolumeUsd: toFiniteNumber(cmcExchangeLiquidity.binanceVolumeUsd),
|
|
2988
|
-
binanceVolumeShare: toFiniteNumber(cmcExchangeLiquidity.binanceVolumeShare),
|
|
2989
|
-
topExchangeVolumeShare: toFiniteNumber(
|
|
2990
|
-
cmcExchangeLiquidity.topExchangeVolumeShare
|
|
2991
|
-
),
|
|
2992
|
-
liquidityRegime: typeof cmcExchangeLiquidity.liquidityRegime === "string" ? cmcExchangeLiquidity.liquidityRegime : null
|
|
3765
|
+
buyPressurePct: toFiniteNumber(delta.buyPressurePct),
|
|
3766
|
+
buyVolume: toFiniteNumber(delta.buyVolume),
|
|
3767
|
+
sellVolume: toFiniteNumber(delta.sellVolume),
|
|
3768
|
+
netDelta: toFiniteNumber(delta.netDelta),
|
|
3769
|
+
deltaPct: toFiniteNumber(delta.deltaPct),
|
|
3770
|
+
signedVolumeZScore: toFiniteNumber(delta.signedVolumeZScore)
|
|
2993
3771
|
};
|
|
2994
3772
|
};
|
|
2995
|
-
var
|
|
3773
|
+
var buildTradeFlowContextFromSignal = (signal) => {
|
|
2996
3774
|
const baseContext = toRecord(signal.additionalIndicators?.baseContext);
|
|
2997
|
-
const
|
|
2998
|
-
const
|
|
2999
|
-
if (!
|
|
3775
|
+
const participation = toRecord(baseContext?.participation);
|
|
3776
|
+
const tradeFlow = toRecord(participation?.tradeFlow);
|
|
3777
|
+
if (!tradeFlow) {
|
|
3000
3778
|
return {
|
|
3001
3779
|
source: null,
|
|
3002
3780
|
available: false,
|
|
3003
3781
|
interval: null,
|
|
3004
|
-
asOfTs: null,
|
|
3005
3782
|
stale: null,
|
|
3006
|
-
|
|
3007
|
-
|
|
3008
|
-
|
|
3009
|
-
|
|
3010
|
-
sentimentRegime: null
|
|
3783
|
+
trades: null,
|
|
3784
|
+
buyPressurePct: null,
|
|
3785
|
+
netBaseDelta: null,
|
|
3786
|
+
netQuoteDelta: null
|
|
3011
3787
|
};
|
|
3012
3788
|
}
|
|
3013
3789
|
return {
|
|
3014
|
-
source: String(
|
|
3790
|
+
source: String(tradeFlow.source ?? ""),
|
|
3015
3791
|
available: true,
|
|
3016
|
-
interval:
|
|
3017
|
-
|
|
3018
|
-
|
|
3019
|
-
|
|
3020
|
-
|
|
3021
|
-
|
|
3022
|
-
classification: typeof cmcFearGreed.classification === "string" ? cmcFearGreed.classification : null,
|
|
3023
|
-
sentimentRegime: typeof cmcFearGreed.sentimentRegime === "string" ? cmcFearGreed.sentimentRegime : null
|
|
3792
|
+
interval: String(tradeFlow.interval ?? ""),
|
|
3793
|
+
stale: typeof tradeFlow.stale === "boolean" ? tradeFlow.stale : null,
|
|
3794
|
+
trades: toFiniteNumber(tradeFlow.trades),
|
|
3795
|
+
buyPressurePct: toFiniteNumber(tradeFlow.buyPressurePct),
|
|
3796
|
+
netBaseDelta: toFiniteNumber(tradeFlow.netBaseDelta),
|
|
3797
|
+
netQuoteDelta: toFiniteNumber(tradeFlow.netQuoteDelta)
|
|
3024
3798
|
};
|
|
3025
3799
|
};
|
|
3026
|
-
var
|
|
3800
|
+
var buildMarketBreadthContextFromSignal = (signal) => {
|
|
3027
3801
|
const baseContext = toRecord(signal.additionalIndicators?.baseContext);
|
|
3028
3802
|
const relative = toRecord(baseContext?.relative);
|
|
3029
|
-
const
|
|
3030
|
-
if (!
|
|
3803
|
+
const breadth = toRecord(relative?.marketBreadth);
|
|
3804
|
+
if (!breadth) {
|
|
3031
3805
|
return {
|
|
3032
3806
|
source: null,
|
|
3033
3807
|
available: false,
|
|
3808
|
+
universe: null,
|
|
3034
3809
|
interval: null,
|
|
3035
|
-
asOfTs: null,
|
|
3036
3810
|
stale: null,
|
|
3037
|
-
|
|
3038
|
-
|
|
3039
|
-
|
|
3040
|
-
|
|
3041
|
-
|
|
3042
|
-
|
|
3043
|
-
|
|
3044
|
-
cmc20TopConstituentWeightPct: null,
|
|
3045
|
-
cmc20ToCmc100Ratio: null,
|
|
3046
|
-
cmc20ToCmc100RatioChange24hPct: null,
|
|
3047
|
-
indexRegime: null
|
|
3811
|
+
symbolsCount: null,
|
|
3812
|
+
advanceDeclineRatio: null,
|
|
3813
|
+
pctAboveMa20: null,
|
|
3814
|
+
pctAboveMa50: null,
|
|
3815
|
+
equalWeightedReturn: null,
|
|
3816
|
+
volumeWeightedReturn: null,
|
|
3817
|
+
dispersion: null
|
|
3048
3818
|
};
|
|
3049
3819
|
}
|
|
3050
3820
|
return {
|
|
3051
|
-
source: String(
|
|
3821
|
+
source: String(breadth.source ?? ""),
|
|
3052
3822
|
available: true,
|
|
3053
|
-
|
|
3054
|
-
|
|
3055
|
-
stale: typeof
|
|
3056
|
-
|
|
3057
|
-
|
|
3058
|
-
|
|
3059
|
-
|
|
3060
|
-
|
|
3061
|
-
),
|
|
3062
|
-
|
|
3063
|
-
cmc20Change24hPct: toFiniteNumber(cmcIndexes.cmc20Change24hPct),
|
|
3064
|
-
cmc20TopConstituentSymbol: typeof cmcIndexes.cmc20TopConstituentSymbol === "string" ? cmcIndexes.cmc20TopConstituentSymbol : null,
|
|
3065
|
-
cmc20TopConstituentWeightPct: toFiniteNumber(
|
|
3066
|
-
cmcIndexes.cmc20TopConstituentWeightPct
|
|
3067
|
-
),
|
|
3068
|
-
cmc20ToCmc100Ratio: toFiniteNumber(cmcIndexes.cmc20ToCmc100Ratio),
|
|
3069
|
-
cmc20ToCmc100RatioChange24hPct: toFiniteNumber(
|
|
3070
|
-
cmcIndexes.cmc20ToCmc100RatioChange24hPct
|
|
3071
|
-
),
|
|
3072
|
-
indexRegime: typeof cmcIndexes.indexRegime === "string" ? cmcIndexes.indexRegime : null
|
|
3823
|
+
universe: String(breadth.universe ?? ""),
|
|
3824
|
+
interval: String(breadth.interval ?? ""),
|
|
3825
|
+
stale: typeof breadth.stale === "boolean" ? breadth.stale : null,
|
|
3826
|
+
symbolsCount: toFiniteNumber(breadth.symbolsCount),
|
|
3827
|
+
advanceDeclineRatio: toFiniteNumber(breadth.advanceDeclineRatio),
|
|
3828
|
+
pctAboveMa20: toFiniteNumber(breadth.pctAboveMa20),
|
|
3829
|
+
pctAboveMa50: toFiniteNumber(breadth.pctAboveMa50),
|
|
3830
|
+
equalWeightedReturn: toFiniteNumber(breadth.equalWeightedReturn),
|
|
3831
|
+
volumeWeightedReturn: toFiniteNumber(breadth.volumeWeightedReturn),
|
|
3832
|
+
dispersion: toFiniteNumber(breadth.dispersion)
|
|
3073
3833
|
};
|
|
3074
3834
|
};
|
|
3075
|
-
var
|
|
3835
|
+
var buildMarketBreadthsContextFromSignal = (signal) => {
|
|
3076
3836
|
const baseContext = toRecord(signal.additionalIndicators?.baseContext);
|
|
3077
3837
|
const relative = toRecord(baseContext?.relative);
|
|
3078
|
-
const
|
|
3079
|
-
const
|
|
3080
|
-
|
|
3081
|
-
|
|
3082
|
-
|
|
3838
|
+
const breadths = toRecord(relative?.marketBreadths);
|
|
3839
|
+
const build = (key) => {
|
|
3840
|
+
const breadth = toRecord(breadths?.[key]);
|
|
3841
|
+
if (!breadth) {
|
|
3842
|
+
return {
|
|
3843
|
+
source: null,
|
|
3844
|
+
available: false,
|
|
3845
|
+
universe: null,
|
|
3846
|
+
interval: null,
|
|
3847
|
+
stale: null,
|
|
3848
|
+
symbolsCount: null,
|
|
3849
|
+
advanceDeclineRatio: null,
|
|
3850
|
+
pctAboveMa20: null,
|
|
3851
|
+
pctAboveMa50: null,
|
|
3852
|
+
equalWeightedReturn: null,
|
|
3853
|
+
volumeWeightedReturn: null,
|
|
3854
|
+
dispersion: null
|
|
3855
|
+
};
|
|
3856
|
+
}
|
|
3083
3857
|
return {
|
|
3084
|
-
source:
|
|
3085
|
-
available:
|
|
3086
|
-
|
|
3087
|
-
|
|
3088
|
-
|
|
3089
|
-
|
|
3858
|
+
source: String(breadth.source ?? ""),
|
|
3859
|
+
available: true,
|
|
3860
|
+
universe: String(breadth.universe ?? ""),
|
|
3861
|
+
interval: String(breadth.interval ?? ""),
|
|
3862
|
+
stale: typeof breadth.stale === "boolean" ? breadth.stale : null,
|
|
3863
|
+
symbolsCount: toFiniteNumber(breadth.symbolsCount),
|
|
3864
|
+
advanceDeclineRatio: toFiniteNumber(breadth.advanceDeclineRatio),
|
|
3865
|
+
pctAboveMa20: toFiniteNumber(breadth.pctAboveMa20),
|
|
3866
|
+
pctAboveMa50: toFiniteNumber(breadth.pctAboveMa50),
|
|
3867
|
+
equalWeightedReturn: toFiniteNumber(breadth.equalWeightedReturn),
|
|
3868
|
+
volumeWeightedReturn: toFiniteNumber(breadth.volumeWeightedReturn),
|
|
3869
|
+
dispersion: toFiniteNumber(breadth.dispersion)
|
|
3090
3870
|
};
|
|
3091
|
-
}
|
|
3092
|
-
return {
|
|
3093
|
-
source: String(refs.source ?? ""),
|
|
3094
|
-
available: true,
|
|
3095
|
-
primaryReferenceSymbol,
|
|
3096
|
-
referenceSymbols: Array.isArray(refs.referenceSymbols) ? refs.referenceSymbols.map(String) : [],
|
|
3097
|
-
primaryTradeFlowBuyPressurePct: toFiniteNumber(
|
|
3098
|
-
primaryTradeFlow?.buyPressurePct
|
|
3099
|
-
),
|
|
3100
|
-
primaryTradeFlowStale: typeof primaryTradeFlow?.stale === "boolean" ? primaryTradeFlow.stale : null
|
|
3101
3871
|
};
|
|
3102
|
-
};
|
|
3103
|
-
var buildAiMarketContext = (signal) => ({
|
|
3104
|
-
execution: {
|
|
3105
|
-
binanceCoinbaseSpread: buildSpreadContextFromSignal(signal)
|
|
3106
|
-
},
|
|
3107
|
-
participation: {
|
|
3108
|
-
trueDelta: buildTrueDeltaContextFromSignal(signal),
|
|
3109
|
-
tradeFlow: buildTradeFlowContextFromSignal(signal)
|
|
3110
|
-
},
|
|
3111
|
-
relative: {
|
|
3112
|
-
marketBreadth: buildMarketBreadthContextFromSignal(signal),
|
|
3113
|
-
marketBreadths: buildMarketBreadthsContextFromSignal(signal),
|
|
3114
|
-
targetVsBtc: buildTargetVsBtcContextFromSignal(signal),
|
|
3115
|
-
btcAltRegime: buildBtcAltRegimeContextFromSignal(signal),
|
|
3116
|
-
cmcGlobal: buildCmcGlobalContextFromSignal(signal),
|
|
3117
|
-
cmcReferenceAssets: buildCmcReferenceAssetsContextFromSignal(signal),
|
|
3118
|
-
cmcExchangeLiquidity: buildCmcExchangeLiquidityContextFromSignal(signal),
|
|
3119
|
-
cmcFearGreed: buildCmcFearGreedContextFromSignal(signal),
|
|
3120
|
-
cmcIndexes: buildCmcIndexesContextFromSignal(signal),
|
|
3121
|
-
referenceTradeFlow: buildReferenceTradeFlowContextFromSignal(signal)
|
|
3122
|
-
}
|
|
3123
|
-
});
|
|
3124
|
-
|
|
3125
|
-
// src/strategy/manifests.ts
|
|
3126
|
-
var import_indicators = require("@tradejs/core/indicators");
|
|
3127
|
-
var SHARED_STRATEGY_REGISTRY_KEY = "__tradejsNodeSharedStrategyRegistryV1__";
|
|
3128
|
-
var sharedRegistryScope = globalThis;
|
|
3129
|
-
var sharedStrategyRegistry = sharedRegistryScope[SHARED_STRATEGY_REGISTRY_KEY] ?? (sharedRegistryScope[SHARED_STRATEGY_REGISTRY_KEY] = {
|
|
3130
|
-
registryStateByProjectRoot: /* @__PURE__ */ new Map()
|
|
3131
|
-
});
|
|
3132
|
-
var createStrategyRegistryState = () => ({
|
|
3133
|
-
strategyCreators: /* @__PURE__ */ new Map(),
|
|
3134
|
-
strategyManifestsMap: /* @__PURE__ */ new Map(),
|
|
3135
|
-
strategyEntriesMap: /* @__PURE__ */ new Map(),
|
|
3136
|
-
strategySourcesMap: /* @__PURE__ */ new Map(),
|
|
3137
|
-
strategyModuleResolutions: /* @__PURE__ */ new Map(),
|
|
3138
|
-
pluginsLoadPromise: null
|
|
3139
|
-
});
|
|
3140
|
-
var registryStateByProjectRoot2 = sharedStrategyRegistry.registryStateByProjectRoot;
|
|
3141
|
-
var getStrategyRegistryState = (cwd = getTradejsProjectCwd()) => {
|
|
3142
|
-
const projectRoot = getTradejsProjectCwd(cwd);
|
|
3143
|
-
let state = registryStateByProjectRoot2.get(projectRoot);
|
|
3144
|
-
if (!state) {
|
|
3145
|
-
state = createStrategyRegistryState();
|
|
3146
|
-
registryStateByProjectRoot2.set(projectRoot, state);
|
|
3147
|
-
}
|
|
3148
3872
|
return {
|
|
3149
|
-
|
|
3150
|
-
|
|
3873
|
+
top5: build("top5"),
|
|
3874
|
+
top10: build("top10"),
|
|
3875
|
+
top30: build("top30"),
|
|
3876
|
+
top50: build("top50"),
|
|
3877
|
+
top100: build("top100")
|
|
3151
3878
|
};
|
|
3152
3879
|
};
|
|
3153
|
-
var
|
|
3154
|
-
|
|
3155
|
-
|
|
3156
|
-
|
|
3157
|
-
const config = await loadTradejsConfig(cwd);
|
|
3880
|
+
var buildTargetVsReference = (signal, asset) => {
|
|
3881
|
+
const base = toRecord(signal.additionalIndicators?.baseContext);
|
|
3882
|
+
const relative = toRecord(base?.relative);
|
|
3883
|
+
const target = toRecord(relative?.[`targetVs${asset}`]);
|
|
3158
3884
|
return {
|
|
3159
|
-
|
|
3160
|
-
|
|
3885
|
+
source: target ? String(target.source ?? "") : null,
|
|
3886
|
+
available: Boolean(target),
|
|
3887
|
+
ratioReturn1h: toFiniteNumber(target?.ratioReturn1h),
|
|
3888
|
+
ratioReturn4h: toFiniteNumber(target?.ratioReturn4h),
|
|
3889
|
+
ratioReturn24h: toFiniteNumber(target?.ratioReturn24h),
|
|
3890
|
+
alpha1h: toFiniteNumber(target?.[`alphaVs${asset}1h`]),
|
|
3891
|
+
alpha4h: toFiniteNumber(target?.[`alphaVs${asset}4h`]),
|
|
3892
|
+
alpha24h: toFiniteNumber(target?.[`alphaVs${asset}24h`]),
|
|
3893
|
+
beta: toFiniteNumber(target?.[`betaTo${asset}20`]),
|
|
3894
|
+
correlation: toFiniteNumber(target?.[`correlationTo${asset}20`]),
|
|
3895
|
+
ratioTrend: typeof target?.ratioTrend === "string" ? target.ratioTrend : null
|
|
3161
3896
|
};
|
|
3162
3897
|
};
|
|
3163
|
-
var
|
|
3164
|
-
|
|
3165
|
-
|
|
3166
|
-
|
|
3167
|
-
|
|
3168
|
-
|
|
3169
|
-
|
|
3170
|
-
|
|
3171
|
-
|
|
3172
|
-
|
|
3173
|
-
return defaultExport[key];
|
|
3174
|
-
}
|
|
3175
|
-
return null;
|
|
3176
|
-
};
|
|
3177
|
-
var extractStrategyPluginDefinition = (moduleExport) => {
|
|
3178
|
-
const strategyEntries = extractModuleEntries(
|
|
3179
|
-
moduleExport,
|
|
3180
|
-
"strategyEntries"
|
|
3181
|
-
);
|
|
3182
|
-
return strategyEntries ? { strategyEntries } : null;
|
|
3183
|
-
};
|
|
3184
|
-
var extractIndicatorPluginDefinition = (moduleExport) => {
|
|
3185
|
-
const indicatorEntries = extractModuleEntries(
|
|
3186
|
-
moduleExport,
|
|
3187
|
-
"indicatorEntries"
|
|
3188
|
-
);
|
|
3189
|
-
return indicatorEntries ? { indicatorEntries } : null;
|
|
3190
|
-
};
|
|
3191
|
-
var validateStrategyEntries = (moduleName, entries, state) => {
|
|
3192
|
-
const issues = [];
|
|
3193
|
-
const names = /* @__PURE__ */ new Set();
|
|
3194
|
-
entries.forEach((entry, index) => {
|
|
3195
|
-
const entryPath = `${moduleName}.strategyEntries[${index}]`;
|
|
3196
|
-
const strategyName = entry?.manifest?.name;
|
|
3197
|
-
if (typeof strategyName !== "string" || !strategyName.trim()) {
|
|
3198
|
-
issues.push(`${entryPath}: manifest.name is required`);
|
|
3199
|
-
} else if (names.has(strategyName) || state?.strategyEntriesMap.has(strategyName)) {
|
|
3200
|
-
issues.push(`${entryPath}: duplicate strategy ${strategyName}`);
|
|
3201
|
-
} else {
|
|
3202
|
-
names.add(strategyName);
|
|
3203
|
-
}
|
|
3204
|
-
if (!entry || typeof entry !== "object" || !entry.defaults || typeof entry.defaults !== "object" || Array.isArray(entry.defaults)) {
|
|
3205
|
-
issues.push(`${entryPath}: defaults must be an object`);
|
|
3206
|
-
}
|
|
3207
|
-
if (typeof entry?.parseConfig !== "function") {
|
|
3208
|
-
issues.push(`${entryPath}: parseConfig is required`);
|
|
3209
|
-
}
|
|
3210
|
-
if (typeof entry?.createCore !== "function") {
|
|
3211
|
-
issues.push(`${entryPath}: createCore is required`);
|
|
3212
|
-
}
|
|
3213
|
-
});
|
|
3214
|
-
return issues;
|
|
3215
|
-
};
|
|
3216
|
-
var registerEntries2 = (entries, source, state) => {
|
|
3217
|
-
for (const entry of entries) {
|
|
3218
|
-
const strategyName = entry.manifest.name;
|
|
3219
|
-
state.strategyManifestsMap.set(strategyName, entry.manifest);
|
|
3220
|
-
state.strategyEntriesMap.set(strategyName, entry);
|
|
3221
|
-
state.strategySourcesMap.set(strategyName, source);
|
|
3222
|
-
materializeStrategyCreator(strategyName, state);
|
|
3223
|
-
}
|
|
3224
|
-
};
|
|
3225
|
-
var materializeStrategyCreator = (strategyName, state) => {
|
|
3226
|
-
if (state.strategyCreators.has(strategyName) || !sharedStrategyRegistry.strategyRuntimeFactory) {
|
|
3227
|
-
return;
|
|
3228
|
-
}
|
|
3229
|
-
const entry = state.strategyEntriesMap.get(strategyName);
|
|
3230
|
-
if (!entry) return;
|
|
3231
|
-
state.strategyCreators.set(
|
|
3232
|
-
strategyName,
|
|
3233
|
-
sharedStrategyRegistry.strategyRuntimeFactory({
|
|
3234
|
-
strategyName,
|
|
3235
|
-
defaults: entry.defaults,
|
|
3236
|
-
createCore: entry.createCore,
|
|
3237
|
-
manifest: entry.manifest,
|
|
3238
|
-
detectorKey: entry.detectorKey,
|
|
3239
|
-
detectorNoSignalSkipReason: entry.detectorNoSignalSkipReason,
|
|
3240
|
-
resolveRegisteredManifest: (name) => state.strategyManifestsMap.get(name)
|
|
3241
|
-
})
|
|
3242
|
-
);
|
|
3898
|
+
var buildTargetVsBtcContextFromSignal = (signal) => {
|
|
3899
|
+
const { alpha1h, alpha4h, alpha24h, beta, correlation, ...shared } = buildTargetVsReference(signal, "Btc");
|
|
3900
|
+
return {
|
|
3901
|
+
...shared,
|
|
3902
|
+
alphaVsBtc1h: alpha1h,
|
|
3903
|
+
alphaVsBtc4h: alpha4h,
|
|
3904
|
+
alphaVsBtc24h: alpha24h,
|
|
3905
|
+
betaToBtc20: beta,
|
|
3906
|
+
correlationToBtc20: correlation
|
|
3907
|
+
};
|
|
3243
3908
|
};
|
|
3244
|
-
var
|
|
3245
|
-
|
|
3246
|
-
|
|
3247
|
-
|
|
3248
|
-
|
|
3249
|
-
|
|
3250
|
-
|
|
3909
|
+
var buildTargetVsEthContextFromSignal = (signal) => {
|
|
3910
|
+
const { alpha1h, alpha4h, alpha24h, beta, correlation, ...shared } = buildTargetVsReference(signal, "Eth");
|
|
3911
|
+
return {
|
|
3912
|
+
...shared,
|
|
3913
|
+
alphaVsEth1h: alpha1h,
|
|
3914
|
+
alphaVsEth4h: alpha4h,
|
|
3915
|
+
alphaVsEth24h: alpha24h,
|
|
3916
|
+
betaToEth20: beta,
|
|
3917
|
+
correlationToEth20: correlation
|
|
3918
|
+
};
|
|
3251
3919
|
};
|
|
3252
|
-
var
|
|
3253
|
-
|
|
3254
|
-
|
|
3920
|
+
var buildBtcAltRegimeContextFromSignal = (signal) => {
|
|
3921
|
+
const baseContext = toRecord(signal.additionalIndicators?.baseContext);
|
|
3922
|
+
const relative = toRecord(baseContext?.relative);
|
|
3923
|
+
const btcAltRegime = toRecord(relative?.btcAltRegime);
|
|
3924
|
+
if (!btcAltRegime) {
|
|
3925
|
+
return {
|
|
3926
|
+
source: null,
|
|
3927
|
+
available: false,
|
|
3928
|
+
universe: null,
|
|
3929
|
+
interval: null,
|
|
3930
|
+
stale: null,
|
|
3931
|
+
regime: null,
|
|
3932
|
+
btcReturn24h: null,
|
|
3933
|
+
altBasketReturn24h: null,
|
|
3934
|
+
btcVsAltReturn24h: null,
|
|
3935
|
+
btcTurnoverShare24h: null,
|
|
3936
|
+
btcTurnoverShareChange24h: null,
|
|
3937
|
+
altVolToBtcVol24h: null,
|
|
3938
|
+
altDispersion24h: null
|
|
3939
|
+
};
|
|
3255
3940
|
}
|
|
3256
|
-
return
|
|
3257
|
-
|
|
3258
|
-
|
|
3259
|
-
|
|
3941
|
+
return {
|
|
3942
|
+
source: String(btcAltRegime.source ?? ""),
|
|
3943
|
+
available: true,
|
|
3944
|
+
universe: String(btcAltRegime.universe ?? ""),
|
|
3945
|
+
interval: String(btcAltRegime.interval ?? ""),
|
|
3946
|
+
stale: typeof btcAltRegime.stale === "boolean" ? btcAltRegime.stale : null,
|
|
3947
|
+
regime: typeof btcAltRegime.regime === "string" ? btcAltRegime.regime : null,
|
|
3948
|
+
btcReturn24h: toFiniteNumber(btcAltRegime.btcReturn24h),
|
|
3949
|
+
altBasketReturn24h: toFiniteNumber(btcAltRegime.altBasketReturn24h),
|
|
3950
|
+
btcVsAltReturn24h: toFiniteNumber(btcAltRegime.btcVsAltReturn24h),
|
|
3951
|
+
btcTurnoverShare24h: toFiniteNumber(btcAltRegime.btcTurnoverShare24h),
|
|
3952
|
+
btcTurnoverShareChange24h: toFiniteNumber(
|
|
3953
|
+
btcAltRegime.btcTurnoverShareChange24h
|
|
3954
|
+
),
|
|
3955
|
+
altVolToBtcVol24h: toFiniteNumber(btcAltRegime.altVolToBtcVol24h),
|
|
3956
|
+
altDispersion24h: toFiniteNumber(btcAltRegime.altDispersion24h)
|
|
3957
|
+
};
|
|
3260
3958
|
};
|
|
3261
|
-
var
|
|
3262
|
-
|
|
3263
|
-
|
|
3264
|
-
|
|
3265
|
-
|
|
3266
|
-
const { projectRoot, state } = getStrategyRegistryState(cwd);
|
|
3267
|
-
const source = moduleName.trim();
|
|
3268
|
-
const resolvedModuleName = resolvePluginModuleSpecifier(source, projectRoot);
|
|
3269
|
-
const cacheKey = `${resolvedModuleName}\0${strategyName}`;
|
|
3270
|
-
const cached = state.strategyModuleResolutions.get(cacheKey);
|
|
3271
|
-
if (cached) return cached;
|
|
3272
|
-
const resolution = (async () => {
|
|
3273
|
-
const moduleExport = await importStrategyPluginModule(
|
|
3274
|
-
resolvedModuleName,
|
|
3275
|
-
projectRoot
|
|
3276
|
-
);
|
|
3277
|
-
const pluginDefinition = extractStrategyPluginDefinition(moduleExport);
|
|
3278
|
-
if (!pluginDefinition) {
|
|
3279
|
-
throw new Error(`${source}: export { strategyEntries } is missing`);
|
|
3280
|
-
}
|
|
3281
|
-
const issues = validateStrategyEntries(
|
|
3282
|
-
source,
|
|
3283
|
-
pluginDefinition.strategyEntries
|
|
3284
|
-
);
|
|
3285
|
-
if (issues.length > 0) {
|
|
3286
|
-
throw new Error(
|
|
3287
|
-
["Invalid TradeJS strategy module:", ...issues].join("\n")
|
|
3288
|
-
);
|
|
3289
|
-
}
|
|
3290
|
-
const entry = pluginDefinition.strategyEntries.find(
|
|
3291
|
-
(candidate) => candidate.manifest.name === strategyName
|
|
3292
|
-
);
|
|
3293
|
-
if (!entry) {
|
|
3294
|
-
throw new Error(`${source}: strategy ${strategyName} is missing`);
|
|
3295
|
-
}
|
|
3296
|
-
const factory = sharedStrategyRegistry.strategyRuntimeFactory;
|
|
3297
|
-
if (!factory) {
|
|
3298
|
-
throw new Error("Strategy runtime factory is not configured");
|
|
3299
|
-
}
|
|
3300
|
-
const manifestsByName = new Map(
|
|
3301
|
-
pluginDefinition.strategyEntries.map(
|
|
3302
|
-
(candidate) => [candidate.manifest.name, candidate.manifest]
|
|
3303
|
-
)
|
|
3304
|
-
);
|
|
3959
|
+
var buildCmcGlobalContextFromSignal = (signal) => {
|
|
3960
|
+
const baseContext = toRecord(signal.additionalIndicators?.baseContext);
|
|
3961
|
+
const relative = toRecord(baseContext?.relative);
|
|
3962
|
+
const cmcGlobal = toRecord(relative?.cmcGlobal);
|
|
3963
|
+
if (!cmcGlobal) {
|
|
3305
3964
|
return {
|
|
3306
|
-
|
|
3307
|
-
|
|
3308
|
-
|
|
3309
|
-
|
|
3310
|
-
|
|
3311
|
-
|
|
3312
|
-
|
|
3313
|
-
|
|
3314
|
-
|
|
3315
|
-
|
|
3316
|
-
|
|
3965
|
+
source: null,
|
|
3966
|
+
available: false,
|
|
3967
|
+
interval: null,
|
|
3968
|
+
asOfTs: null,
|
|
3969
|
+
stale: null,
|
|
3970
|
+
totalMarketCapUsd: null,
|
|
3971
|
+
totalVolumeUsd: null,
|
|
3972
|
+
totalVolumeReportedUsd: null,
|
|
3973
|
+
altMarketCapUsd: null,
|
|
3974
|
+
altVolumeUsd: null,
|
|
3975
|
+
altVolumeReportedUsd: null,
|
|
3976
|
+
btcDominancePct: null,
|
|
3977
|
+
ethDominancePct: null,
|
|
3978
|
+
btcDominanceChange24hPct: null,
|
|
3979
|
+
ethDominanceChange24hPct: null,
|
|
3980
|
+
altMarketCapChange24hPct: null,
|
|
3981
|
+
altVolumeChange24hPct: null,
|
|
3982
|
+
activeCryptocurrencies: null,
|
|
3983
|
+
activeExchanges: null,
|
|
3984
|
+
activeMarketPairs: null,
|
|
3985
|
+
altLiquidityRegime: null
|
|
3317
3986
|
};
|
|
3318
|
-
})();
|
|
3319
|
-
state.strategyModuleResolutions.set(cacheKey, resolution);
|
|
3320
|
-
try {
|
|
3321
|
-
return await resolution;
|
|
3322
|
-
} catch (error) {
|
|
3323
|
-
state.strategyModuleResolutions.delete(cacheKey);
|
|
3324
|
-
throw error;
|
|
3325
3987
|
}
|
|
3988
|
+
return {
|
|
3989
|
+
source: String(cmcGlobal.source ?? ""),
|
|
3990
|
+
available: true,
|
|
3991
|
+
interval: typeof cmcGlobal.interval === "string" ? cmcGlobal.interval : null,
|
|
3992
|
+
asOfTs: toFiniteNumber(cmcGlobal.asOfTs),
|
|
3993
|
+
stale: typeof cmcGlobal.stale === "boolean" ? cmcGlobal.stale : null,
|
|
3994
|
+
totalMarketCapUsd: toFiniteNumber(cmcGlobal.totalMarketCapUsd),
|
|
3995
|
+
totalVolumeUsd: toFiniteNumber(cmcGlobal.totalVolumeUsd),
|
|
3996
|
+
totalVolumeReportedUsd: toFiniteNumber(cmcGlobal.totalVolumeReportedUsd),
|
|
3997
|
+
altMarketCapUsd: toFiniteNumber(cmcGlobal.altMarketCapUsd),
|
|
3998
|
+
altVolumeUsd: toFiniteNumber(cmcGlobal.altVolumeUsd),
|
|
3999
|
+
altVolumeReportedUsd: toFiniteNumber(cmcGlobal.altVolumeReportedUsd),
|
|
4000
|
+
btcDominancePct: toFiniteNumber(cmcGlobal.btcDominancePct),
|
|
4001
|
+
ethDominancePct: toFiniteNumber(cmcGlobal.ethDominancePct),
|
|
4002
|
+
btcDominanceChange24hPct: toFiniteNumber(
|
|
4003
|
+
cmcGlobal.btcDominanceChange24hPct
|
|
4004
|
+
),
|
|
4005
|
+
ethDominanceChange24hPct: toFiniteNumber(
|
|
4006
|
+
cmcGlobal.ethDominanceChange24hPct
|
|
4007
|
+
),
|
|
4008
|
+
altMarketCapChange24hPct: toFiniteNumber(
|
|
4009
|
+
cmcGlobal.altMarketCapChange24hPct
|
|
4010
|
+
),
|
|
4011
|
+
altVolumeChange24hPct: toFiniteNumber(cmcGlobal.altVolumeChange24hPct),
|
|
4012
|
+
activeCryptocurrencies: toFiniteNumber(cmcGlobal.activeCryptocurrencies),
|
|
4013
|
+
activeExchanges: toFiniteNumber(cmcGlobal.activeExchanges),
|
|
4014
|
+
activeMarketPairs: toFiniteNumber(cmcGlobal.activeMarketPairs),
|
|
4015
|
+
altLiquidityRegime: typeof cmcGlobal.altLiquidityRegime === "string" ? cmcGlobal.altLiquidityRegime : null
|
|
4016
|
+
};
|
|
3326
4017
|
};
|
|
3327
|
-
var
|
|
3328
|
-
const
|
|
3329
|
-
|
|
3330
|
-
|
|
3331
|
-
|
|
3332
|
-
|
|
3333
|
-
|
|
3334
|
-
|
|
3335
|
-
|
|
3336
|
-
|
|
3337
|
-
|
|
3338
|
-
|
|
3339
|
-
|
|
3340
|
-
|
|
3341
|
-
|
|
3342
|
-
|
|
3343
|
-
|
|
3344
|
-
|
|
3345
|
-
|
|
3346
|
-
|
|
3347
|
-
|
|
3348
|
-
|
|
3349
|
-
resolvedModuleName,
|
|
3350
|
-
projectRoot
|
|
3351
|
-
);
|
|
3352
|
-
if (strategySet.has(moduleName)) {
|
|
3353
|
-
const pluginDefinition = extractStrategyPluginDefinition(moduleExport);
|
|
3354
|
-
if (!pluginDefinition) {
|
|
3355
|
-
issues.push(
|
|
3356
|
-
`${moduleName}: export { strategyEntries } is missing`
|
|
3357
|
-
);
|
|
3358
|
-
} else {
|
|
3359
|
-
const entryIssues = validateStrategyEntries(
|
|
3360
|
-
moduleName,
|
|
3361
|
-
pluginDefinition.strategyEntries,
|
|
3362
|
-
state
|
|
3363
|
-
);
|
|
3364
|
-
issues.push(...entryIssues);
|
|
3365
|
-
if (entryIssues.length === 0) {
|
|
3366
|
-
registerEntries2(
|
|
3367
|
-
pluginDefinition.strategyEntries,
|
|
3368
|
-
moduleName,
|
|
3369
|
-
state
|
|
3370
|
-
);
|
|
3371
|
-
}
|
|
3372
|
-
}
|
|
3373
|
-
}
|
|
3374
|
-
if (indicatorSet.has(moduleName)) {
|
|
3375
|
-
const indicatorPluginDefinition = extractIndicatorPluginDefinition(moduleExport);
|
|
3376
|
-
if (!indicatorPluginDefinition) {
|
|
3377
|
-
issues.push(
|
|
3378
|
-
`${moduleName}: export { indicatorEntries } is missing`
|
|
3379
|
-
);
|
|
3380
|
-
} else {
|
|
3381
|
-
(0, import_indicators.registerIndicatorEntries)(
|
|
3382
|
-
indicatorPluginDefinition.indicatorEntries,
|
|
3383
|
-
moduleName,
|
|
3384
|
-
projectRoot
|
|
3385
|
-
);
|
|
3386
|
-
}
|
|
3387
|
-
}
|
|
3388
|
-
if (!strategySet.has(moduleName) && !indicatorSet.has(moduleName)) {
|
|
3389
|
-
issues.push(`${moduleName}: plugin is not declared in config`);
|
|
3390
|
-
}
|
|
3391
|
-
} catch (error) {
|
|
3392
|
-
issues.push(`${moduleName}: failed to import: ${String(error)}`);
|
|
3393
|
-
}
|
|
3394
|
-
}
|
|
3395
|
-
if (issues.length > 0) {
|
|
3396
|
-
throw new Error(
|
|
3397
|
-
["Invalid TradeJS plugin catalog:", ...issues].join("\n")
|
|
3398
|
-
);
|
|
3399
|
-
}
|
|
3400
|
-
})();
|
|
4018
|
+
var buildCmcReferenceAssetsContextFromSignal = (signal) => {
|
|
4019
|
+
const baseContext = toRecord(signal.additionalIndicators?.baseContext);
|
|
4020
|
+
const relative = toRecord(baseContext?.relative);
|
|
4021
|
+
const cmcReferenceAssets = toRecord(relative?.cmcReferenceAssets);
|
|
4022
|
+
if (!cmcReferenceAssets) {
|
|
4023
|
+
return {
|
|
4024
|
+
source: null,
|
|
4025
|
+
available: false,
|
|
4026
|
+
interval: null,
|
|
4027
|
+
asOfTs: null,
|
|
4028
|
+
stale: null,
|
|
4029
|
+
btcMarketCapUsd: null,
|
|
4030
|
+
ethMarketCapUsd: null,
|
|
4031
|
+
btcVolumeUsd: null,
|
|
4032
|
+
ethVolumeUsd: null,
|
|
4033
|
+
btcVolumeToMarketCap: null,
|
|
4034
|
+
ethVolumeToMarketCap: null,
|
|
4035
|
+
ethBtcMarketCapRatio: null,
|
|
4036
|
+
ethBtcMarketCapRatioChange24hPct: null,
|
|
4037
|
+
ethVsBtcVolumeRatio: null,
|
|
4038
|
+
referenceLiquidityRegime: null
|
|
4039
|
+
};
|
|
3401
4040
|
}
|
|
3402
|
-
|
|
4041
|
+
return {
|
|
4042
|
+
source: String(cmcReferenceAssets.source ?? ""),
|
|
4043
|
+
available: true,
|
|
4044
|
+
interval: typeof cmcReferenceAssets.interval === "string" ? cmcReferenceAssets.interval : null,
|
|
4045
|
+
asOfTs: toFiniteNumber(cmcReferenceAssets.asOfTs),
|
|
4046
|
+
stale: typeof cmcReferenceAssets.stale === "boolean" ? cmcReferenceAssets.stale : null,
|
|
4047
|
+
btcMarketCapUsd: toFiniteNumber(cmcReferenceAssets.btcMarketCapUsd),
|
|
4048
|
+
ethMarketCapUsd: toFiniteNumber(cmcReferenceAssets.ethMarketCapUsd),
|
|
4049
|
+
btcVolumeUsd: toFiniteNumber(cmcReferenceAssets.btcVolumeUsd),
|
|
4050
|
+
ethVolumeUsd: toFiniteNumber(cmcReferenceAssets.ethVolumeUsd),
|
|
4051
|
+
btcVolumeToMarketCap: toFiniteNumber(
|
|
4052
|
+
cmcReferenceAssets.btcVolumeToMarketCap
|
|
4053
|
+
),
|
|
4054
|
+
ethVolumeToMarketCap: toFiniteNumber(
|
|
4055
|
+
cmcReferenceAssets.ethVolumeToMarketCap
|
|
4056
|
+
),
|
|
4057
|
+
ethBtcMarketCapRatio: toFiniteNumber(
|
|
4058
|
+
cmcReferenceAssets.ethBtcMarketCapRatio
|
|
4059
|
+
),
|
|
4060
|
+
ethBtcMarketCapRatioChange24hPct: toFiniteNumber(
|
|
4061
|
+
cmcReferenceAssets.ethBtcMarketCapRatioChange24hPct
|
|
4062
|
+
),
|
|
4063
|
+
ethVsBtcVolumeRatio: toFiniteNumber(cmcReferenceAssets.ethVsBtcVolumeRatio),
|
|
4064
|
+
referenceLiquidityRegime: typeof cmcReferenceAssets.referenceLiquidityRegime === "string" ? cmcReferenceAssets.referenceLiquidityRegime : null
|
|
4065
|
+
};
|
|
3403
4066
|
};
|
|
3404
|
-
var
|
|
3405
|
-
|
|
3406
|
-
|
|
3407
|
-
|
|
4067
|
+
var buildCmcExchangeLiquidityContextFromSignal = (signal) => {
|
|
4068
|
+
const baseContext = toRecord(signal.additionalIndicators?.baseContext);
|
|
4069
|
+
const relative = toRecord(baseContext?.relative);
|
|
4070
|
+
const cmcExchangeLiquidity = toRecord(relative?.cmcExchangeLiquidity);
|
|
4071
|
+
if (!cmcExchangeLiquidity) {
|
|
4072
|
+
return {
|
|
4073
|
+
source: null,
|
|
4074
|
+
available: false,
|
|
4075
|
+
interval: null,
|
|
4076
|
+
asOfTs: null,
|
|
4077
|
+
stale: null,
|
|
4078
|
+
exchangesCount: null,
|
|
4079
|
+
totalVolumeUsd: null,
|
|
4080
|
+
totalVolumeChange24hPct: null,
|
|
4081
|
+
binanceVolumeUsd: null,
|
|
4082
|
+
binanceVolumeShare: null,
|
|
4083
|
+
topExchangeVolumeShare: null,
|
|
4084
|
+
liquidityRegime: null
|
|
4085
|
+
};
|
|
3408
4086
|
}
|
|
3409
|
-
|
|
3410
|
-
|
|
3411
|
-
|
|
4087
|
+
return {
|
|
4088
|
+
source: String(cmcExchangeLiquidity.source ?? ""),
|
|
4089
|
+
available: true,
|
|
4090
|
+
interval: typeof cmcExchangeLiquidity.interval === "string" ? cmcExchangeLiquidity.interval : null,
|
|
4091
|
+
asOfTs: toFiniteNumber(cmcExchangeLiquidity.asOfTs),
|
|
4092
|
+
stale: typeof cmcExchangeLiquidity.stale === "boolean" ? cmcExchangeLiquidity.stale : null,
|
|
4093
|
+
exchangesCount: toFiniteNumber(cmcExchangeLiquidity.exchangesCount),
|
|
4094
|
+
totalVolumeUsd: toFiniteNumber(cmcExchangeLiquidity.totalVolumeUsd),
|
|
4095
|
+
totalVolumeChange24hPct: toFiniteNumber(
|
|
4096
|
+
cmcExchangeLiquidity.totalVolumeChange24hPct
|
|
4097
|
+
),
|
|
4098
|
+
binanceVolumeUsd: toFiniteNumber(cmcExchangeLiquidity.binanceVolumeUsd),
|
|
4099
|
+
binanceVolumeShare: toFiniteNumber(cmcExchangeLiquidity.binanceVolumeShare),
|
|
4100
|
+
topExchangeVolumeShare: toFiniteNumber(
|
|
4101
|
+
cmcExchangeLiquidity.topExchangeVolumeShare
|
|
4102
|
+
),
|
|
4103
|
+
liquidityRegime: typeof cmcExchangeLiquidity.liquidityRegime === "string" ? cmcExchangeLiquidity.liquidityRegime : null
|
|
4104
|
+
};
|
|
3412
4105
|
};
|
|
3413
|
-
var
|
|
3414
|
-
|
|
3415
|
-
|
|
3416
|
-
|
|
4106
|
+
var buildCmcFearGreedContextFromSignal = (signal) => {
|
|
4107
|
+
const baseContext = toRecord(signal.additionalIndicators?.baseContext);
|
|
4108
|
+
const relative = toRecord(baseContext?.relative);
|
|
4109
|
+
const cmcFearGreed = toRecord(relative?.cmcFearGreed);
|
|
4110
|
+
if (!cmcFearGreed) {
|
|
4111
|
+
return {
|
|
4112
|
+
source: null,
|
|
4113
|
+
available: false,
|
|
4114
|
+
interval: null,
|
|
4115
|
+
asOfTs: null,
|
|
4116
|
+
stale: null,
|
|
4117
|
+
value: null,
|
|
4118
|
+
valueChange24h: null,
|
|
4119
|
+
valueChange7d: null,
|
|
4120
|
+
classification: null,
|
|
4121
|
+
sentimentRegime: null
|
|
4122
|
+
};
|
|
3417
4123
|
}
|
|
3418
|
-
|
|
3419
|
-
|
|
3420
|
-
|
|
4124
|
+
return {
|
|
4125
|
+
source: String(cmcFearGreed.source ?? ""),
|
|
4126
|
+
available: true,
|
|
4127
|
+
interval: typeof cmcFearGreed.interval === "string" ? cmcFearGreed.interval : null,
|
|
4128
|
+
asOfTs: toFiniteNumber(cmcFearGreed.asOfTs),
|
|
4129
|
+
stale: typeof cmcFearGreed.stale === "boolean" ? cmcFearGreed.stale : null,
|
|
4130
|
+
value: toFiniteNumber(cmcFearGreed.value),
|
|
4131
|
+
valueChange24h: toFiniteNumber(cmcFearGreed.valueChange24h),
|
|
4132
|
+
valueChange7d: toFiniteNumber(cmcFearGreed.valueChange7d),
|
|
4133
|
+
classification: typeof cmcFearGreed.classification === "string" ? cmcFearGreed.classification : null,
|
|
4134
|
+
sentimentRegime: typeof cmcFearGreed.sentimentRegime === "string" ? cmcFearGreed.sentimentRegime : null
|
|
4135
|
+
};
|
|
3421
4136
|
};
|
|
3422
|
-
var
|
|
3423
|
-
|
|
3424
|
-
|
|
3425
|
-
|
|
4137
|
+
var buildCmcIndexesContextFromSignal = (signal) => {
|
|
4138
|
+
const baseContext = toRecord(signal.additionalIndicators?.baseContext);
|
|
4139
|
+
const relative = toRecord(baseContext?.relative);
|
|
4140
|
+
const cmcIndexes = toRecord(relative?.cmcIndexes);
|
|
4141
|
+
if (!cmcIndexes) {
|
|
4142
|
+
return {
|
|
4143
|
+
source: null,
|
|
4144
|
+
available: false,
|
|
4145
|
+
interval: null,
|
|
4146
|
+
asOfTs: null,
|
|
4147
|
+
stale: null,
|
|
4148
|
+
cmc100Value: null,
|
|
4149
|
+
cmc100Change24hPct: null,
|
|
4150
|
+
cmc100TopConstituentSymbol: null,
|
|
4151
|
+
cmc100TopConstituentWeightPct: null,
|
|
4152
|
+
cmc20Value: null,
|
|
4153
|
+
cmc20Change24hPct: null,
|
|
4154
|
+
cmc20TopConstituentSymbol: null,
|
|
4155
|
+
cmc20TopConstituentWeightPct: null,
|
|
4156
|
+
cmc20ToCmc100Ratio: null,
|
|
4157
|
+
cmc20ToCmc100RatioChange24hPct: null,
|
|
4158
|
+
indexRegime: null
|
|
4159
|
+
};
|
|
3426
4160
|
}
|
|
3427
|
-
|
|
3428
|
-
|
|
3429
|
-
|
|
4161
|
+
return {
|
|
4162
|
+
source: String(cmcIndexes.source ?? ""),
|
|
4163
|
+
available: true,
|
|
4164
|
+
interval: typeof cmcIndexes.interval === "string" ? cmcIndexes.interval : null,
|
|
4165
|
+
asOfTs: toFiniteNumber(cmcIndexes.asOfTs),
|
|
4166
|
+
stale: typeof cmcIndexes.stale === "boolean" ? cmcIndexes.stale : null,
|
|
4167
|
+
cmc100Value: toFiniteNumber(cmcIndexes.cmc100Value),
|
|
4168
|
+
cmc100Change24hPct: toFiniteNumber(cmcIndexes.cmc100Change24hPct),
|
|
4169
|
+
cmc100TopConstituentSymbol: typeof cmcIndexes.cmc100TopConstituentSymbol === "string" ? cmcIndexes.cmc100TopConstituentSymbol : null,
|
|
4170
|
+
cmc100TopConstituentWeightPct: toFiniteNumber(
|
|
4171
|
+
cmcIndexes.cmc100TopConstituentWeightPct
|
|
4172
|
+
),
|
|
4173
|
+
cmc20Value: toFiniteNumber(cmcIndexes.cmc20Value),
|
|
4174
|
+
cmc20Change24hPct: toFiniteNumber(cmcIndexes.cmc20Change24hPct),
|
|
4175
|
+
cmc20TopConstituentSymbol: typeof cmcIndexes.cmc20TopConstituentSymbol === "string" ? cmcIndexes.cmc20TopConstituentSymbol : null,
|
|
4176
|
+
cmc20TopConstituentWeightPct: toFiniteNumber(
|
|
4177
|
+
cmcIndexes.cmc20TopConstituentWeightPct
|
|
4178
|
+
),
|
|
4179
|
+
cmc20ToCmc100Ratio: toFiniteNumber(cmcIndexes.cmc20ToCmc100Ratio),
|
|
4180
|
+
cmc20ToCmc100RatioChange24hPct: toFiniteNumber(
|
|
4181
|
+
cmcIndexes.cmc20ToCmc100RatioChange24hPct
|
|
4182
|
+
),
|
|
4183
|
+
indexRegime: typeof cmcIndexes.indexRegime === "string" ? cmcIndexes.indexRegime : null
|
|
4184
|
+
};
|
|
3430
4185
|
};
|
|
3431
|
-
var
|
|
3432
|
-
|
|
3433
|
-
|
|
4186
|
+
var buildReferenceTradeFlowContextFromSignal = (signal) => {
|
|
4187
|
+
const baseContext = toRecord(signal.additionalIndicators?.baseContext);
|
|
4188
|
+
const relative = toRecord(baseContext?.relative);
|
|
4189
|
+
const refs = toRecord(relative?.referenceTradeFlow);
|
|
4190
|
+
const primaryReferenceSymbol = typeof refs?.primaryReferenceSymbol === "string" ? refs.primaryReferenceSymbol : null;
|
|
4191
|
+
const tradeFlowBySymbol = toRecord(refs?.tradeFlowBySymbol);
|
|
4192
|
+
const primaryTradeFlow = primaryReferenceSymbol != null ? toRecord(tradeFlowBySymbol?.[primaryReferenceSymbol]) : null;
|
|
4193
|
+
if (!refs) {
|
|
4194
|
+
return {
|
|
4195
|
+
source: null,
|
|
4196
|
+
available: false,
|
|
4197
|
+
primaryReferenceSymbol: null,
|
|
4198
|
+
referenceSymbols: [],
|
|
4199
|
+
primaryTradeFlowBuyPressurePct: null,
|
|
4200
|
+
primaryTradeFlowStale: null
|
|
4201
|
+
};
|
|
3434
4202
|
}
|
|
3435
|
-
|
|
3436
|
-
|
|
4203
|
+
return {
|
|
4204
|
+
source: String(refs.source ?? ""),
|
|
4205
|
+
available: true,
|
|
4206
|
+
primaryReferenceSymbol,
|
|
4207
|
+
referenceSymbols: Array.isArray(refs.referenceSymbols) ? refs.referenceSymbols.map(String) : [],
|
|
4208
|
+
primaryTradeFlowBuyPressurePct: toFiniteNumber(
|
|
4209
|
+
primaryTradeFlow?.buyPressurePct
|
|
4210
|
+
),
|
|
4211
|
+
primaryTradeFlowStale: typeof primaryTradeFlow?.stale === "boolean" ? primaryTradeFlow.stale : null
|
|
4212
|
+
};
|
|
3437
4213
|
};
|
|
3438
|
-
var
|
|
3439
|
-
{
|
|
3440
|
-
|
|
3441
|
-
|
|
3442
|
-
|
|
3443
|
-
|
|
3444
|
-
|
|
3445
|
-
|
|
3446
|
-
|
|
3447
|
-
|
|
3448
|
-
|
|
3449
|
-
|
|
3450
|
-
|
|
3451
|
-
|
|
3452
|
-
|
|
3453
|
-
|
|
4214
|
+
var buildAiMarketContext = (signal) => ({
|
|
4215
|
+
execution: {
|
|
4216
|
+
binanceCoinbaseSpread: buildSpreadContextFromSignal(signal)
|
|
4217
|
+
},
|
|
4218
|
+
participation: {
|
|
4219
|
+
trueDelta: buildTrueDeltaContextFromSignal(signal),
|
|
4220
|
+
tradeFlow: buildTradeFlowContextFromSignal(signal)
|
|
4221
|
+
},
|
|
4222
|
+
relative: {
|
|
4223
|
+
marketBreadth: buildMarketBreadthContextFromSignal(signal),
|
|
4224
|
+
marketBreadths: buildMarketBreadthsContextFromSignal(signal),
|
|
4225
|
+
targetVsBtc: buildTargetVsBtcContextFromSignal(signal),
|
|
4226
|
+
targetVsEth: buildTargetVsEthContextFromSignal(signal),
|
|
4227
|
+
btcAltRegime: buildBtcAltRegimeContextFromSignal(signal),
|
|
4228
|
+
cmcGlobal: buildCmcGlobalContextFromSignal(signal),
|
|
4229
|
+
cmcReferenceAssets: buildCmcReferenceAssetsContextFromSignal(signal),
|
|
4230
|
+
cmcExchangeLiquidity: buildCmcExchangeLiquidityContextFromSignal(signal),
|
|
4231
|
+
cmcFearGreed: buildCmcFearGreedContextFromSignal(signal),
|
|
4232
|
+
cmcIndexes: buildCmcIndexesContextFromSignal(signal),
|
|
4233
|
+
referenceTradeFlow: buildReferenceTradeFlowContextFromSignal(signal)
|
|
3454
4234
|
}
|
|
3455
|
-
);
|
|
4235
|
+
});
|
|
3456
4236
|
|
|
3457
4237
|
// src/strategy/policyProfiles.ts
|
|
3458
4238
|
var profileMatches = (profile, universe, assetClass) => {
|
|
@@ -3554,12 +4334,61 @@ var buildBaseAiPayload = (signal) => {
|
|
|
3554
4334
|
};
|
|
3555
4335
|
};
|
|
3556
4336
|
var defaultAiAdapter = {};
|
|
4337
|
+
var tickerProfileFeatureIds = new Set(
|
|
4338
|
+
import_aiFeatures.TICKER_PROFILE_AI_FEATURES.map(({ id }) => id)
|
|
4339
|
+
);
|
|
3557
4340
|
var getStrategyAiAdapter = (strategy, profileId) => getStrategyProfileAiAdapter(getStrategyManifest(strategy), profileId) ?? defaultAiAdapter;
|
|
3558
4341
|
var getSignalAiAdapter = (signal) => getStrategyAiAdapter(signal.strategy, signal.policyProfileId);
|
|
3559
4342
|
var buildAiPayloadByStrategy = (signal) => {
|
|
3560
4343
|
const basePayload = buildBaseAiPayload(signal);
|
|
3561
4344
|
const adapter = getSignalAiAdapter(signal);
|
|
3562
|
-
|
|
4345
|
+
const payload = adapter.buildPayload?.({ signal, basePayload }) ?? basePayload;
|
|
4346
|
+
const custom = getStrategyManifest(signal.strategy)?.aiFeatures ?? [];
|
|
4347
|
+
const frozen = signal.aiFeatureReplay?.snapshot;
|
|
4348
|
+
if (frozen) (0, import_aiFeatures.validateAiFeatureSnapshot)(frozen);
|
|
4349
|
+
const tickerProfile = signal.additionalIndicators?.tickerProfile ?? signal.additionalIndicators?.baseContext?.tickerProfile;
|
|
4350
|
+
const hasProfile = Boolean(
|
|
4351
|
+
tickerProfile || frozen?.definitions.some(({ id }) => tickerProfileFeatureIds.has(id))
|
|
4352
|
+
);
|
|
4353
|
+
const indicatorFeatures = (0, import_indicators2.getRegisteredIndicatorEntries)(
|
|
4354
|
+
getTradejsProjectCwd()
|
|
4355
|
+
).flatMap((entry) => entry.indicator.enabled ? entry.aiFeatures ?? [] : []);
|
|
4356
|
+
const hasPortfolio = Boolean(
|
|
4357
|
+
signal.additionalIndicators?.portfolioContext || frozen?.definitions.some(
|
|
4358
|
+
({ id }) => import_aiFeatures.PORTFOLIO_AI_FEATURES.some((d) => d.id === id)
|
|
4359
|
+
)
|
|
4360
|
+
);
|
|
4361
|
+
const definitions = [
|
|
4362
|
+
...hasPortfolio ? import_aiFeatures.PORTFOLIO_AI_FEATURES : [],
|
|
4363
|
+
...hasProfile ? import_aiFeatures.TICKER_PROFILE_AI_FEATURES : [],
|
|
4364
|
+
...indicatorFeatures,
|
|
4365
|
+
...custom
|
|
4366
|
+
];
|
|
4367
|
+
if (!definitions.length && !signal.aiFeatureReplay?.snapshot) return payload;
|
|
4368
|
+
(0, import_aiFeatures.validateAiFeatureDefinitions)(definitions);
|
|
4369
|
+
if (signal.aiFeatureReplay && !frozen) {
|
|
4370
|
+
throw new Error(
|
|
4371
|
+
"Dataset has no frozen AI features; regenerate the decision-time export"
|
|
4372
|
+
);
|
|
4373
|
+
}
|
|
4374
|
+
if (frozen && JSON.stringify(frozen.definitions) !== JSON.stringify(definitions)) {
|
|
4375
|
+
throw new Error(
|
|
4376
|
+
"AI feature definition changed; regenerate the decision-time export"
|
|
4377
|
+
);
|
|
4378
|
+
}
|
|
4379
|
+
return {
|
|
4380
|
+
...payload,
|
|
4381
|
+
featureSnapshot: frozen ?? (0, import_aiFeatures.projectAiFeatures)(definitions, {
|
|
4382
|
+
signal: tickerProfile ? {
|
|
4383
|
+
...signal,
|
|
4384
|
+
additionalIndicators: {
|
|
4385
|
+
...signal.additionalIndicators,
|
|
4386
|
+
tickerProfile
|
|
4387
|
+
}
|
|
4388
|
+
} : signal,
|
|
4389
|
+
payload
|
|
4390
|
+
})
|
|
4391
|
+
};
|
|
3563
4392
|
};
|
|
3564
4393
|
var buildAiSystemPromptAddonByStrategy = (signal) => getSignalAiAdapter(signal).buildSystemPromptAddon?.({ signal }) ?? "";
|
|
3565
4394
|
var buildAiHumanPromptAddonByStrategy = (signal, payload) => getSignalAiAdapter(signal).buildHumanPromptAddon?.({
|
|
@@ -3945,380 +4774,132 @@ var buildAiPayload = (signal) => buildAiPayloadByStrategy(signal);
|
|
|
3945
4774
|
var getDeterministicAiGateContext = (payload) => {
|
|
3946
4775
|
const additionalIndicators = asRecord(payload.additionalIndicators);
|
|
3947
4776
|
const candidates = [
|
|
3948
|
-
additionalIndicators,
|
|
3949
|
-
...Object.values(additionalIndicators ?? {}).map(asRecord)
|
|
3950
|
-
].filter((value) => Boolean(value));
|
|
3951
|
-
return candidates.find(
|
|
3952
|
-
(candidate) => Array.isArray(candidate.approvalBlockReasons) || Array.isArray(candidate.riskAnnotations) || Array.isArray(candidate.structuralHardBlockReasons) || typeof candidate.approvalAllowedNow === "boolean"
|
|
3953
|
-
) ?? null;
|
|
3954
|
-
};
|
|
3955
|
-
var buildAiHumanPrompt = (signal, payload = buildAiPayload(signal)) => `
|
|
3956
|
-
Analyze the already computed internal signal for ${signal.symbol}. The original signal direction is ${signal.direction}.
|
|
3957
|
-
This is a structure-classification and audit task, not execution advice. Determine whether the current structure confirms the existing signal, how structurally coherent it is right now, whether an extra confirmation level is needed, and whether the already supplied levels in \`payload.signal.prices\` still look internally coherent. Do not replace the original thesis with a new one and do not invent new levels; return only the requested JSON.
|
|
3958
|
-
|
|
3959
|
-
Trade payload:
|
|
3960
|
-
${JSON.stringify(payload)}
|
|
3961
|
-
${buildAiHumanPromptAddonByStrategy(signal, payload)}
|
|
3962
|
-
`;
|
|
3963
|
-
var runAiPrompt = async ({ systemPrompt, humanPrompt }, options = {}) => {
|
|
3964
|
-
const settings = await getAiUserSettings(options.userName);
|
|
3965
|
-
const responseLanguage = (0, import_aiLanguages2.getAiResponseLanguagePromptName)(
|
|
3966
|
-
settings.AI_RESPONSE_LANGUAGE || import_aiLanguages2.DEFAULT_AI_RESPONSE_LANGUAGE
|
|
3967
|
-
);
|
|
3968
|
-
const response = await invokeAiPromptChat({
|
|
3969
|
-
userName: options.userName,
|
|
3970
|
-
model: options.model,
|
|
3971
|
-
messages: [
|
|
3972
|
-
{ role: "system", content: systemPrompt },
|
|
3973
|
-
{
|
|
3974
|
-
role: "system",
|
|
3975
|
-
content: `Write all user-visible text fields in ${responseLanguage}. Keep field names and JSON syntax unchanged.`
|
|
3976
|
-
},
|
|
3977
|
-
{ role: "user", content: humanPrompt }
|
|
3978
|
-
]
|
|
3979
|
-
});
|
|
3980
|
-
const parsed = parseAIResponse(response.content);
|
|
3981
|
-
const normalized = normalizeAnalysis(parsed);
|
|
3982
|
-
if (!options.signal) {
|
|
3983
|
-
return normalized;
|
|
3984
|
-
}
|
|
3985
|
-
return postProcessAiAnalysisByStrategy(
|
|
3986
|
-
options.signal,
|
|
3987
|
-
normalized,
|
|
3988
|
-
options.payload
|
|
3989
|
-
);
|
|
3990
|
-
};
|
|
3991
|
-
var runAiPromptLocal = async (signal, options = {}) => {
|
|
3992
|
-
const payload = options.payload ?? buildAiPayload(signal);
|
|
3993
|
-
const gateContext = getDeterministicAiGateContext(payload);
|
|
3994
|
-
const signalDirection = getSignalDirection(signal);
|
|
3995
|
-
const deterministicQuality = getDeterministicQuality(gateContext);
|
|
3996
|
-
const approvalAllowedNow = typeof gateContext?.approvalAllowedNow === "boolean" ? gateContext.approvalAllowedNow : deterministicQuality >= 4;
|
|
3997
|
-
return postProcessLocalAiAnalysisByStrategy(
|
|
3998
|
-
signal,
|
|
3999
|
-
{
|
|
4000
|
-
direction: approvalAllowedNow ? signalDirection : null,
|
|
4001
|
-
quality: deterministicQuality,
|
|
4002
|
-
needRetest: !approvalAllowedNow,
|
|
4003
|
-
retestPrice: null,
|
|
4004
|
-
takeProfitPrice: approvalAllowedNow ? signal.prices?.takeProfitPrice ?? null : null,
|
|
4005
|
-
stopLossPrice: approvalAllowedNow ? signal.prices?.stopLossPrice ?? null : null
|
|
4006
|
-
},
|
|
4007
|
-
payload
|
|
4008
|
-
);
|
|
4009
|
-
};
|
|
4010
|
-
var askAI = async (signal, options = {}) => {
|
|
4011
|
-
const { symbol } = signal;
|
|
4012
|
-
const payload = buildAiPayload(signal);
|
|
4013
|
-
const content = await runAiPrompt(
|
|
4014
|
-
{
|
|
4015
|
-
systemPrompt: buildAiSystemPrompt(signal),
|
|
4016
|
-
humanPrompt: buildAiHumanPrompt(signal, payload)
|
|
4017
|
-
},
|
|
4018
|
-
{
|
|
4019
|
-
...options,
|
|
4020
|
-
signal,
|
|
4021
|
-
payload
|
|
4022
|
-
}
|
|
4023
|
-
);
|
|
4024
|
-
await (0, import_redis2.setData)(import_redis2.redisKeys.analysis(symbol, signal.signalId), content);
|
|
4025
|
-
return content;
|
|
4026
|
-
};
|
|
4027
|
-
|
|
4028
|
-
// src/strategyAdapters/ml.ts
|
|
4029
|
-
var defaultMlAdapter = {
|
|
4030
|
-
normalizeStrategyConfig: (strategyConfig) => strategyConfig
|
|
4031
|
-
};
|
|
4032
|
-
var getStrategyMlAdapter = (strategy, profileId) => {
|
|
4033
|
-
const strategyAdapter = getStrategyProfileMlAdapter(
|
|
4034
|
-
getStrategyManifest(strategy),
|
|
4035
|
-
profileId
|
|
4036
|
-
);
|
|
4037
|
-
if (!strategyAdapter) return defaultMlAdapter;
|
|
4038
|
-
return {
|
|
4039
|
-
...defaultMlAdapter,
|
|
4040
|
-
...strategyAdapter
|
|
4041
|
-
};
|
|
4042
|
-
};
|
|
4043
|
-
|
|
4044
|
-
// src/mlPayload.ts
|
|
4045
|
-
var normalizeStrategyConfig = (strategyConfig, strategyName, profileId) => {
|
|
4046
|
-
return getStrategyMlAdapter(
|
|
4047
|
-
strategyName,
|
|
4048
|
-
profileId
|
|
4049
|
-
).normalizeStrategyConfig?.(strategyConfig);
|
|
4050
|
-
};
|
|
4051
|
-
var buildMlPayload = (payload) => {
|
|
4052
|
-
const strategyName = payload.signal?.strategy ?? payload.context?.strategyName;
|
|
4053
|
-
const profileId = payload.signal?.policyProfileId;
|
|
4054
|
-
const mlAdapter = getStrategyMlAdapter(strategyName, profileId);
|
|
4055
|
-
const normalizedSignal = mlAdapter.normalizeSignal?.(payload.signal) ?? payload.signal;
|
|
4056
|
-
const nextSignal = {
|
|
4057
|
-
...normalizedSignal,
|
|
4058
|
-
indicators: {
|
|
4059
|
-
...normalizedSignal?.indicators ?? {}
|
|
4060
|
-
}
|
|
4061
|
-
};
|
|
4062
|
-
const nextContext = payload.context ? {
|
|
4063
|
-
...payload.context,
|
|
4064
|
-
strategyConfig: normalizeStrategyConfig(
|
|
4065
|
-
payload.context.strategyConfig,
|
|
4066
|
-
strategyName,
|
|
4067
|
-
profileId
|
|
4068
|
-
)
|
|
4069
|
-
} : void 0;
|
|
4070
|
-
return {
|
|
4071
|
-
signal: nextSignal,
|
|
4072
|
-
context: nextContext
|
|
4073
|
-
};
|
|
4074
|
-
};
|
|
4075
|
-
|
|
4076
|
-
// src/runtimeJournal.ts
|
|
4077
|
-
var import_node_crypto = require("crypto");
|
|
4078
|
-
var import_constants3 = require("@tradejs/core/constants");
|
|
4079
|
-
var import_time3 = require("@tradejs/core/time");
|
|
4080
|
-
var import_trade = require("@tradejs/core/trade");
|
|
4081
|
-
var import_logger6 = require("@tradejs/infra/logger");
|
|
4082
|
-
var import_redis3 = require("@tradejs/infra/redis");
|
|
4083
|
-
var now = () => Date.now();
|
|
4084
|
-
var toRandomOrderSuffix = () => (0, import_node_crypto.randomUUID)().replace(/-/g, "").slice(0, 12).toLowerCase();
|
|
4085
|
-
var calculateClosedPnl = ({
|
|
4086
|
-
direction,
|
|
4087
|
-
entryPrice,
|
|
4088
|
-
exitPrice,
|
|
4089
|
-
qty
|
|
4090
|
-
}) => {
|
|
4091
|
-
const pnl = direction === "LONG" ? (exitPrice - entryPrice) * qty : (entryPrice - exitPrice) * qty;
|
|
4092
|
-
return Number.isFinite(pnl) ? pnl : null;
|
|
4093
|
-
};
|
|
4094
|
-
var createRuntimeOrderId = (strategy) => {
|
|
4095
|
-
const prefix = (0, import_trade.createRuntimeOrderLinkPrefix)(strategy);
|
|
4096
|
-
if (prefix === "tjs-") {
|
|
4097
|
-
return `tjs-${(0, import_node_crypto.randomUUID)().replace(/-/g, "").slice(0, 24).toLowerCase()}`;
|
|
4098
|
-
}
|
|
4099
|
-
return `${prefix}${toRandomOrderSuffix()}`;
|
|
4100
|
-
};
|
|
4101
|
-
var recordRuntimeTradeOpen = async (params) => {
|
|
4102
|
-
const { userName } = params;
|
|
4103
|
-
if (!userName) {
|
|
4104
|
-
return null;
|
|
4105
|
-
}
|
|
4106
|
-
const record = {
|
|
4107
|
-
...params,
|
|
4108
|
-
entryCount: 1,
|
|
4109
|
-
lastEntryPrice: params.entryPrice,
|
|
4110
|
-
lastEntryQty: params.qty,
|
|
4111
|
-
lastEntryTimestamp: params.entryTimestamp,
|
|
4112
|
-
status: "active",
|
|
4113
|
-
currentPrice: params.entryPrice,
|
|
4114
|
-
currentPnl: 0,
|
|
4115
|
-
closedPnl: null,
|
|
4116
|
-
exitPrice: null,
|
|
4117
|
-
exitTimestamp: null,
|
|
4118
|
-
lastSyncedAt: now()
|
|
4119
|
-
};
|
|
4120
|
-
const dayKey = (0, import_time3.getRuntimeStorageDayKey)(record.entryTimestamp);
|
|
4121
|
-
const runtimeScopeId = record.deploymentId ?? record.accountId;
|
|
4122
|
-
try {
|
|
4123
|
-
await Promise.all([
|
|
4124
|
-
(0, import_redis3.setData)(import_redis3.redisKeys.runtimeTrade(userName, record.orderId), record, {
|
|
4125
|
-
expire: 0
|
|
4126
|
-
}),
|
|
4127
|
-
(0, import_redis3.setHashJsonField)(
|
|
4128
|
-
import_redis3.redisKeys.runtimeTradeBucket(userName, dayKey),
|
|
4129
|
-
record.orderId,
|
|
4130
|
-
record,
|
|
4131
|
-
{ expire: 0 }
|
|
4132
|
-
),
|
|
4133
|
-
(0, import_redis3.setData)(
|
|
4134
|
-
import_redis3.redisKeys.runtimeActiveTrade(userName, record.symbol, runtimeScopeId),
|
|
4135
|
-
{ orderId: record.orderId },
|
|
4136
|
-
{ expire: 0 }
|
|
4137
|
-
)
|
|
4138
|
-
]);
|
|
4139
|
-
} catch (error) {
|
|
4140
|
-
import_logger6.logger.error(
|
|
4141
|
-
"runtime trade open journal failed: %s %s",
|
|
4142
|
-
record.symbol,
|
|
4143
|
-
error?.message || String(error)
|
|
4144
|
-
);
|
|
4145
|
-
}
|
|
4146
|
-
return record;
|
|
4147
|
-
};
|
|
4148
|
-
var recordRuntimeTradeIncrease = async (params) => {
|
|
4149
|
-
const {
|
|
4150
|
-
userName,
|
|
4151
|
-
strategy,
|
|
4152
|
-
symbol,
|
|
4153
|
-
direction,
|
|
4154
|
-
resultingQty,
|
|
4155
|
-
resultingEntryPrice,
|
|
4156
|
-
addedQty,
|
|
4157
|
-
addedEntryPrice,
|
|
4158
|
-
entryTimestamp,
|
|
4159
|
-
fee,
|
|
4160
|
-
accountId,
|
|
4161
|
-
deploymentId
|
|
4162
|
-
} = params;
|
|
4163
|
-
if (!userName) {
|
|
4164
|
-
return null;
|
|
4165
|
-
}
|
|
4166
|
-
const existing = await getActiveRuntimeTrade({
|
|
4167
|
-
userName,
|
|
4168
|
-
symbol,
|
|
4169
|
-
accountId,
|
|
4170
|
-
deploymentId
|
|
4171
|
-
});
|
|
4172
|
-
if (!existing || existing.strategy !== strategy || existing.direction !== direction) {
|
|
4173
|
-
return null;
|
|
4174
|
-
}
|
|
4175
|
-
const addedFee = typeof fee === "number" && Number.isFinite(fee) ? fee : 0;
|
|
4176
|
-
const openFee = (existing.openFee ?? existing.fee ?? 0) + addedFee;
|
|
4177
|
-
const totalFee = (existing.totalFee ?? existing.fee ?? 0) + addedFee;
|
|
4178
|
-
const next = {
|
|
4179
|
-
...existing,
|
|
4180
|
-
...params.orderLeverage ? { orderLeverage: params.orderLeverage } : {},
|
|
4181
|
-
qty: resultingQty,
|
|
4182
|
-
entryPrice: resultingEntryPrice,
|
|
4183
|
-
entryCount: Math.max(1, existing.entryCount ?? 1) + 1,
|
|
4184
|
-
lastEntryPrice: addedEntryPrice,
|
|
4185
|
-
lastEntryQty: addedQty,
|
|
4186
|
-
lastEntryTimestamp: entryTimestamp,
|
|
4187
|
-
currentPrice: resultingEntryPrice,
|
|
4188
|
-
currentPnl: 0,
|
|
4189
|
-
fee: openFee,
|
|
4190
|
-
openFee,
|
|
4191
|
-
totalFee,
|
|
4192
|
-
lastSyncedAt: now()
|
|
4193
|
-
};
|
|
4194
|
-
const dayKey = (0, import_time3.getRuntimeStorageDayKey)(existing.entryTimestamp);
|
|
4195
|
-
try {
|
|
4196
|
-
await Promise.all([
|
|
4197
|
-
(0, import_redis3.setData)(import_redis3.redisKeys.runtimeTrade(userName, existing.orderId), next, {
|
|
4198
|
-
expire: 0
|
|
4199
|
-
}),
|
|
4200
|
-
(0, import_redis3.setHashJsonField)(
|
|
4201
|
-
import_redis3.redisKeys.runtimeTradeBucket(userName, dayKey),
|
|
4202
|
-
existing.orderId,
|
|
4203
|
-
next,
|
|
4204
|
-
{ expire: 0 }
|
|
4205
|
-
)
|
|
4206
|
-
]);
|
|
4207
|
-
} catch (error) {
|
|
4208
|
-
import_logger6.logger.error(
|
|
4209
|
-
"runtime trade increase journal failed: %s %s",
|
|
4210
|
-
symbol,
|
|
4211
|
-
error?.message || String(error)
|
|
4212
|
-
);
|
|
4213
|
-
}
|
|
4214
|
-
return next;
|
|
4777
|
+
additionalIndicators,
|
|
4778
|
+
...Object.values(additionalIndicators ?? {}).map(asRecord)
|
|
4779
|
+
].filter((value) => Boolean(value));
|
|
4780
|
+
return candidates.find(
|
|
4781
|
+
(candidate) => Array.isArray(candidate.approvalBlockReasons) || Array.isArray(candidate.riskAnnotations) || Array.isArray(candidate.structuralHardBlockReasons) || typeof candidate.approvalAllowedNow === "boolean"
|
|
4782
|
+
) ?? null;
|
|
4215
4783
|
};
|
|
4216
|
-
var
|
|
4217
|
-
|
|
4218
|
-
|
|
4219
|
-
|
|
4220
|
-
|
|
4221
|
-
|
|
4222
|
-
|
|
4223
|
-
|
|
4784
|
+
var buildAiHumanPrompt = (signal, payload = buildAiPayload(signal)) => `
|
|
4785
|
+
Analyze the already computed internal signal for ${signal.symbol}. The original signal direction is ${signal.direction}.
|
|
4786
|
+
This is a structure-classification and audit task, not execution advice. Determine whether the current structure confirms the existing signal, how structurally coherent it is right now, whether an extra confirmation level is needed, and whether the already supplied levels in \`payload.signal.prices\` still look internally coherent. Do not replace the original thesis with a new one and do not invent new levels; return only the requested JSON.
|
|
4787
|
+
|
|
4788
|
+
Trade payload:
|
|
4789
|
+
${JSON.stringify(payload)}
|
|
4790
|
+
${buildAiHumanPromptAddonByStrategy(signal, payload)}
|
|
4791
|
+
`;
|
|
4792
|
+
var runAiPrompt = async ({ systemPrompt, humanPrompt }, options = {}) => {
|
|
4793
|
+
const settings = await getAiUserSettings(options.userName);
|
|
4794
|
+
const responseLanguage = (0, import_aiLanguages2.getAiResponseLanguagePromptName)(
|
|
4795
|
+
settings.AI_RESPONSE_LANGUAGE || import_aiLanguages2.DEFAULT_AI_RESPONSE_LANGUAGE
|
|
4224
4796
|
);
|
|
4225
|
-
const
|
|
4226
|
-
|
|
4227
|
-
|
|
4797
|
+
const response = await invokeAiPromptChat({
|
|
4798
|
+
userName: options.userName,
|
|
4799
|
+
model: options.model,
|
|
4800
|
+
messages: [
|
|
4801
|
+
{ role: "system", content: systemPrompt },
|
|
4802
|
+
{
|
|
4803
|
+
role: "system",
|
|
4804
|
+
content: `Write all user-visible text fields in ${responseLanguage}. Keep field names and JSON syntax unchanged.`
|
|
4805
|
+
},
|
|
4806
|
+
{ role: "user", content: humanPrompt }
|
|
4807
|
+
]
|
|
4808
|
+
});
|
|
4809
|
+
const parsed = parseAIResponse(response.content);
|
|
4810
|
+
const normalized = normalizeAnalysis(parsed);
|
|
4811
|
+
if (!options.signal) {
|
|
4812
|
+
return normalized;
|
|
4228
4813
|
}
|
|
4229
|
-
|
|
4230
|
-
|
|
4231
|
-
|
|
4814
|
+
return postProcessAiAnalysisByStrategy(
|
|
4815
|
+
options.signal,
|
|
4816
|
+
normalized,
|
|
4817
|
+
options.payload
|
|
4232
4818
|
);
|
|
4233
|
-
if (!existing || existing.status !== "active") {
|
|
4234
|
-
await (0, import_redis3.delKey)(
|
|
4235
|
-
import_redis3.redisKeys.runtimeActiveTrade(userName, symbol, deploymentId ?? accountId)
|
|
4236
|
-
);
|
|
4237
|
-
return null;
|
|
4238
|
-
}
|
|
4239
|
-
return existing;
|
|
4240
4819
|
};
|
|
4241
|
-
var
|
|
4242
|
-
const
|
|
4243
|
-
|
|
4244
|
-
|
|
4245
|
-
|
|
4246
|
-
|
|
4247
|
-
|
|
4248
|
-
|
|
4249
|
-
|
|
4250
|
-
|
|
4251
|
-
|
|
4252
|
-
|
|
4253
|
-
|
|
4254
|
-
|
|
4255
|
-
|
|
4256
|
-
|
|
4257
|
-
|
|
4258
|
-
|
|
4259
|
-
|
|
4260
|
-
|
|
4261
|
-
}
|
|
4262
|
-
|
|
4263
|
-
|
|
4264
|
-
|
|
4265
|
-
|
|
4266
|
-
|
|
4267
|
-
|
|
4268
|
-
|
|
4269
|
-
|
|
4270
|
-
|
|
4271
|
-
|
|
4272
|
-
|
|
4273
|
-
|
|
4274
|
-
|
|
4275
|
-
|
|
4276
|
-
|
|
4277
|
-
|
|
4278
|
-
|
|
4279
|
-
|
|
4280
|
-
|
|
4281
|
-
|
|
4282
|
-
|
|
4283
|
-
|
|
4284
|
-
|
|
4285
|
-
|
|
4820
|
+
var runAiPromptLocal = async (signal, options = {}) => {
|
|
4821
|
+
const payload = options.payload ?? buildAiPayload(signal);
|
|
4822
|
+
const gateContext = getDeterministicAiGateContext(payload);
|
|
4823
|
+
const signalDirection = getSignalDirection(signal);
|
|
4824
|
+
const deterministicQuality = getDeterministicQuality(gateContext);
|
|
4825
|
+
const approvalAllowedNow = typeof gateContext?.approvalAllowedNow === "boolean" ? gateContext.approvalAllowedNow : deterministicQuality >= 4;
|
|
4826
|
+
return postProcessLocalAiAnalysisByStrategy(
|
|
4827
|
+
signal,
|
|
4828
|
+
{
|
|
4829
|
+
direction: approvalAllowedNow ? signalDirection : null,
|
|
4830
|
+
quality: deterministicQuality,
|
|
4831
|
+
needRetest: !approvalAllowedNow,
|
|
4832
|
+
retestPrice: null,
|
|
4833
|
+
takeProfitPrice: approvalAllowedNow ? signal.prices?.takeProfitPrice ?? null : null,
|
|
4834
|
+
stopLossPrice: approvalAllowedNow ? signal.prices?.stopLossPrice ?? null : null
|
|
4835
|
+
},
|
|
4836
|
+
payload
|
|
4837
|
+
);
|
|
4838
|
+
};
|
|
4839
|
+
var askAI = async (signal, options = {}) => {
|
|
4840
|
+
const { symbol } = signal;
|
|
4841
|
+
const payload = buildAiPayload(signal);
|
|
4842
|
+
const content = await runAiPrompt(
|
|
4843
|
+
{
|
|
4844
|
+
systemPrompt: buildAiSystemPrompt(signal),
|
|
4845
|
+
humanPrompt: buildAiHumanPrompt(signal, payload)
|
|
4846
|
+
},
|
|
4847
|
+
{
|
|
4848
|
+
...options,
|
|
4849
|
+
signal,
|
|
4850
|
+
payload
|
|
4851
|
+
}
|
|
4852
|
+
);
|
|
4853
|
+
await (0, import_redis4.setData)(import_redis4.redisKeys.analysis(symbol, signal.signalId), content);
|
|
4854
|
+
return content;
|
|
4855
|
+
};
|
|
4856
|
+
|
|
4857
|
+
// src/strategyAdapters/ml.ts
|
|
4858
|
+
var defaultMlAdapter = {
|
|
4859
|
+
normalizeStrategyConfig: (strategyConfig) => strategyConfig
|
|
4860
|
+
};
|
|
4861
|
+
var getStrategyMlAdapter = (strategy, profileId) => {
|
|
4862
|
+
const strategyAdapter = getStrategyProfileMlAdapter(
|
|
4863
|
+
getStrategyManifest(strategy),
|
|
4864
|
+
profileId
|
|
4865
|
+
);
|
|
4866
|
+
if (!strategyAdapter) return defaultMlAdapter;
|
|
4867
|
+
return {
|
|
4868
|
+
...defaultMlAdapter,
|
|
4869
|
+
...strategyAdapter
|
|
4870
|
+
};
|
|
4871
|
+
};
|
|
4872
|
+
|
|
4873
|
+
// src/mlPayload.ts
|
|
4874
|
+
var normalizeStrategyConfig = (strategyConfig, strategyName, profileId) => {
|
|
4875
|
+
return getStrategyMlAdapter(
|
|
4876
|
+
strategyName,
|
|
4877
|
+
profileId
|
|
4878
|
+
).normalizeStrategyConfig?.(strategyConfig);
|
|
4879
|
+
};
|
|
4880
|
+
var buildMlPayload = (payload) => {
|
|
4881
|
+
const strategyName = payload.signal?.strategy ?? payload.context?.strategyName;
|
|
4882
|
+
const profileId = payload.signal?.policyProfileId;
|
|
4883
|
+
const mlAdapter = getStrategyMlAdapter(strategyName, profileId);
|
|
4884
|
+
const normalizedSignal = mlAdapter.normalizeSignal?.(payload.signal) ?? payload.signal;
|
|
4885
|
+
const nextSignal = {
|
|
4886
|
+
...normalizedSignal,
|
|
4887
|
+
indicators: {
|
|
4888
|
+
...normalizedSignal?.indicators ?? {}
|
|
4889
|
+
}
|
|
4890
|
+
};
|
|
4891
|
+
const nextContext = payload.context ? {
|
|
4892
|
+
...payload.context,
|
|
4893
|
+
strategyConfig: normalizeStrategyConfig(
|
|
4894
|
+
payload.context.strategyConfig,
|
|
4895
|
+
strategyName,
|
|
4896
|
+
profileId
|
|
4897
|
+
)
|
|
4898
|
+
} : void 0;
|
|
4899
|
+
return {
|
|
4900
|
+
signal: nextSignal,
|
|
4901
|
+
context: nextContext
|
|
4286
4902
|
};
|
|
4287
|
-
const dayKey = (0, import_time3.getRuntimeStorageDayKey)(existing.entryTimestamp);
|
|
4288
|
-
const closeDayKey = (0, import_time3.getRuntimeStorageDayKey)(next.exitTimestamp);
|
|
4289
|
-
try {
|
|
4290
|
-
await Promise.all([
|
|
4291
|
-
(0, import_redis3.setData)(import_redis3.redisKeys.runtimeTrade(userName, orderId), next, {
|
|
4292
|
-
expire: import_constants3.TTL_1M
|
|
4293
|
-
}),
|
|
4294
|
-
(0, import_redis3.setHashJsonField)(
|
|
4295
|
-
import_redis3.redisKeys.runtimeTradeBucket(userName, dayKey),
|
|
4296
|
-
orderId,
|
|
4297
|
-
next,
|
|
4298
|
-
{ expire: import_constants3.TTL_1M }
|
|
4299
|
-
),
|
|
4300
|
-
(0, import_redis3.setHashJsonField)(
|
|
4301
|
-
import_redis3.redisKeys.runtimeClosedTradeBucket(userName, closeDayKey),
|
|
4302
|
-
orderId,
|
|
4303
|
-
next,
|
|
4304
|
-
{ expire: import_constants3.TTL_1M }
|
|
4305
|
-
),
|
|
4306
|
-
(0, import_redis3.delKey)(
|
|
4307
|
-
import_redis3.redisKeys.runtimeActiveTrade(
|
|
4308
|
-
userName,
|
|
4309
|
-
symbol,
|
|
4310
|
-
deploymentId ?? accountId
|
|
4311
|
-
)
|
|
4312
|
-
)
|
|
4313
|
-
]);
|
|
4314
|
-
} catch (error) {
|
|
4315
|
-
import_logger6.logger.error(
|
|
4316
|
-
"runtime trade close journal failed: %s %s",
|
|
4317
|
-
symbol,
|
|
4318
|
-
error?.message || String(error)
|
|
4319
|
-
);
|
|
4320
|
-
}
|
|
4321
|
-
return next;
|
|
4322
4903
|
};
|
|
4323
4904
|
|
|
4324
4905
|
// src/strategyHelpers/marketContextStages.ts
|
|
@@ -4330,7 +4911,7 @@ var import_logger7 = require("@tradejs/infra/logger");
|
|
|
4330
4911
|
var import_strategies = require("@tradejs/core/strategies");
|
|
4331
4912
|
|
|
4332
4913
|
// src/binanceBreadthUniverses.ts
|
|
4333
|
-
var
|
|
4914
|
+
var import_node_crypto3 = require("crypto");
|
|
4334
4915
|
|
|
4335
4916
|
// src/config/binanceBreadthUniverses.json
|
|
4336
4917
|
var binanceBreadthUniverses_default = {
|
|
@@ -4569,7 +5150,7 @@ var BINANCE_BREADTH_UNIVERSE_KEYS = [
|
|
|
4569
5150
|
"top50",
|
|
4570
5151
|
"top100"
|
|
4571
5152
|
];
|
|
4572
|
-
var fingerprint = (value, length) => (0,
|
|
5153
|
+
var fingerprint = (value, length) => (0, import_node_crypto3.createHash)("sha256").update(JSON.stringify(value)).digest("hex").slice(0, length);
|
|
4573
5154
|
var normalizeSymbols = (symbols) => symbols.map((symbol) => symbol.trim().toUpperCase()).filter(Boolean);
|
|
4574
5155
|
var buildBinanceBreadthUniverseSnapshot = ({
|
|
4575
5156
|
rankedSymbols,
|
|
@@ -5593,7 +6174,7 @@ var enrichSignalWithCoinMarketCapContext = async (params) => {
|
|
|
5593
6174
|
};
|
|
5594
6175
|
|
|
5595
6176
|
// src/strategyHelpers/derivativesContext.ts
|
|
5596
|
-
var
|
|
6177
|
+
var import_indicators3 = require("@tradejs/core/indicators");
|
|
5597
6178
|
var import_data3 = require("@tradejs/core/data");
|
|
5598
6179
|
var import_strategies3 = require("@tradejs/core/strategies");
|
|
5599
6180
|
var import_constants4 = require("@tradejs/core/constants");
|
|
@@ -5627,7 +6208,7 @@ var parseLookbackMs = () => {
|
|
|
5627
6208
|
};
|
|
5628
6209
|
var withHourlyFallbackRows = (rowsByInterval) => ({
|
|
5629
6210
|
"15m": rowsByInterval["15m"] ?? [],
|
|
5630
|
-
"1h": (0,
|
|
6211
|
+
"1h": (0, import_indicators3.buildCoinalyzeHourlyRowsWithFallback)({
|
|
5631
6212
|
rows15m: rowsByInterval["15m"],
|
|
5632
6213
|
fallbackRows1h: rowsByInterval["1h"]
|
|
5633
6214
|
})
|
|
@@ -5755,7 +6336,7 @@ var enrichSignalWithDerivativesContext = async (params) => {
|
|
|
5755
6336
|
const targetSymbol = normalizeSymbol(signal.symbol);
|
|
5756
6337
|
const lookbackMs = parseLookbackMs();
|
|
5757
6338
|
const decisionTimeMs = signal.timestamp + (0, import_data3.intervalToMs)(signal.interval);
|
|
5758
|
-
const derivativesEndMs = (0,
|
|
6339
|
+
const derivativesEndMs = (0, import_indicators3.getLastClosedDerivativesBarStartMs)(
|
|
5759
6340
|
decisionTimeMs,
|
|
5760
6341
|
"15m"
|
|
5761
6342
|
);
|
|
@@ -5770,7 +6351,7 @@ var enrichSignalWithDerivativesContext = async (params) => {
|
|
|
5770
6351
|
});
|
|
5771
6352
|
return [
|
|
5772
6353
|
symbol,
|
|
5773
|
-
(0,
|
|
6354
|
+
(0, import_indicators3.buildDerivativesContext)({
|
|
5774
6355
|
symbol,
|
|
5775
6356
|
direction: signal.direction,
|
|
5776
6357
|
timestamp: derivativesEndMs,
|
|
@@ -5797,7 +6378,7 @@ var enrichSignalWithDerivativesContext = async (params) => {
|
|
|
5797
6378
|
lookbackMs,
|
|
5798
6379
|
...params.abortSignal ? { signal: params.abortSignal } : {}
|
|
5799
6380
|
});
|
|
5800
|
-
const context = (0,
|
|
6381
|
+
const context = (0, import_indicators3.buildDerivativesContext)({
|
|
5801
6382
|
symbol: targetSymbol,
|
|
5802
6383
|
direction: signal.direction,
|
|
5803
6384
|
timestamp: derivativesEndMs,
|
|
@@ -5845,7 +6426,7 @@ var import_hyperliquidWhales2 = require("@tradejs/infra/timescale/hyperliquidWha
|
|
|
5845
6426
|
var import_logger10 = require("@tradejs/infra/logger");
|
|
5846
6427
|
|
|
5847
6428
|
// src/hyperliquidWhaleUniverse.ts
|
|
5848
|
-
var
|
|
6429
|
+
var import_node_crypto4 = require("crypto");
|
|
5849
6430
|
|
|
5850
6431
|
// src/config/hyperliquidPerpUniverse.json
|
|
5851
6432
|
var hyperliquidPerpUniverse_default = {
|
|
@@ -6017,7 +6598,7 @@ var hyperliquidWhales_default = {
|
|
|
6017
6598
|
};
|
|
6018
6599
|
|
|
6019
6600
|
// src/hyperliquidWhaleUniverse.ts
|
|
6020
|
-
var fingerprint2 = (value) => (0,
|
|
6601
|
+
var fingerprint2 = (value) => (0, import_node_crypto4.createHash)("sha256").update(JSON.stringify(value)).digest("hex").slice(0, 16);
|
|
6021
6602
|
var validateUnique = (values, label) => {
|
|
6022
6603
|
if (new Set(values).size !== values.length) {
|
|
6023
6604
|
throw new Error(`Hyperliquid ${label} snapshot contains duplicates`);
|
|
@@ -6504,6 +7085,7 @@ var enrichSignalWithMarketContextStages = async ({
|
|
|
6504
7085
|
})
|
|
6505
7086
|
});
|
|
6506
7087
|
}
|
|
7088
|
+
await enrichSignalWithTickerProfile(signal);
|
|
6507
7089
|
const results = [];
|
|
6508
7090
|
for (const stage of stages) {
|
|
6509
7091
|
if (abortSignal?.aborted) break;
|
|
@@ -6551,12 +7133,12 @@ var findReplayAiAnalysis = ({
|
|
|
6551
7133
|
direction,
|
|
6552
7134
|
ai
|
|
6553
7135
|
}) => {
|
|
6554
|
-
const
|
|
6555
|
-
if (!Array.isArray(
|
|
7136
|
+
const snapshots2 = ai?.replayAnalyses;
|
|
7137
|
+
if (!Array.isArray(snapshots2) || !snapshots2.length) {
|
|
6556
7138
|
return void 0;
|
|
6557
7139
|
}
|
|
6558
7140
|
let best = null;
|
|
6559
|
-
for (const snapshot2 of
|
|
7141
|
+
for (const snapshot2 of snapshots2) {
|
|
6560
7142
|
if (snapshot2.symbol !== signal.symbol || snapshot2.direction !== direction || snapshot2.strategy && snapshot2.strategy !== signal.strategy) {
|
|
6561
7143
|
continue;
|
|
6562
7144
|
}
|
|
@@ -6716,11 +7298,13 @@ var executeEntryOrder = async ({
|
|
|
6716
7298
|
beforePlaceOrder,
|
|
6717
7299
|
recordRuntimeTrade = true,
|
|
6718
7300
|
leverage,
|
|
6719
|
-
executionLimits
|
|
7301
|
+
executionLimits,
|
|
7302
|
+
markPortfolioSubmitted,
|
|
7303
|
+
rejectPortfolioSubmission
|
|
6720
7304
|
}) => {
|
|
6721
7305
|
await beforePlaceOrder?.();
|
|
6722
7306
|
const previousPosition = positionIntent === "increase" ? await connector.getPosition(symbol) : null;
|
|
6723
|
-
|
|
7307
|
+
let orderId = signal.orderId || createRuntimeOrderId(signal.strategy);
|
|
6724
7308
|
const signalTimestamp = signal.timestamp;
|
|
6725
7309
|
const signalClosePrice = currentPrice;
|
|
6726
7310
|
signal.orderId = orderId;
|
|
@@ -6789,9 +7373,16 @@ var executeEntryOrder = async ({
|
|
|
6789
7373
|
...typeof leverage === "number" && Number.isFinite(leverage) ? { leverage } : {},
|
|
6790
7374
|
orderId,
|
|
6791
7375
|
signal,
|
|
6792
|
-
...
|
|
7376
|
+
...rejectPortfolioSubmission ? { onRejected: rejectPortfolioSubmission } : {},
|
|
7377
|
+
...beforeSubmit || markPortfolioSubmitted ? {
|
|
7378
|
+
beforeSubmit: async (context) => {
|
|
7379
|
+
await beforeSubmit?.(context);
|
|
7380
|
+
return markPortfolioSubmitted?.(context.qty);
|
|
7381
|
+
}
|
|
7382
|
+
} : {}
|
|
6793
7383
|
});
|
|
6794
7384
|
const orderAckTime = Date.now();
|
|
7385
|
+
if (markPortfolioSubmitted && signal.orderId) orderId = signal.orderId;
|
|
6795
7386
|
const placedQty = typeof signal.orderQty === "number" && Number.isFinite(signal.orderQty) && signal.orderQty > 0 ? signal.orderQty : qty;
|
|
6796
7387
|
const currentPosition = await connector.getPosition(symbol);
|
|
6797
7388
|
const fillTime = Date.now();
|
|
@@ -7777,7 +8368,7 @@ var handleProtectDecision = async ({
|
|
|
7777
8368
|
}
|
|
7778
8369
|
return decision.code;
|
|
7779
8370
|
};
|
|
7780
|
-
var
|
|
8371
|
+
var executeAdmittedEntryDecision = async ({
|
|
7781
8372
|
connector,
|
|
7782
8373
|
symbol,
|
|
7783
8374
|
decision,
|
|
@@ -7791,7 +8382,9 @@ var executeEntryDecision = async ({
|
|
|
7791
8382
|
ai,
|
|
7792
8383
|
recordRuntimeJournal,
|
|
7793
8384
|
invokeStageHooks,
|
|
7794
|
-
notifyRuntimeError
|
|
8385
|
+
notifyRuntimeError,
|
|
8386
|
+
markPortfolioSubmitted,
|
|
8387
|
+
rejectPortfolioSubmission
|
|
7795
8388
|
}) => {
|
|
7796
8389
|
const signal = decision.signal;
|
|
7797
8390
|
const beforePlaceOrder = async () => {
|
|
@@ -7838,6 +8431,8 @@ var executeEntryDecision = async ({
|
|
|
7838
8431
|
...Number.isFinite(Number(hookCtx.strategyConfig.LEVERAGE)) ? { leverage: Number(hookCtx.strategyConfig.LEVERAGE) } : {},
|
|
7839
8432
|
signal,
|
|
7840
8433
|
beforePlaceOrder,
|
|
8434
|
+
markPortfolioSubmitted,
|
|
8435
|
+
rejectPortfolioSubmission,
|
|
7841
8436
|
recordRuntimeTrade: recordRuntimeJournal,
|
|
7842
8437
|
executionLimits: resolveEntryExecutionLimits(
|
|
7843
8438
|
hookCtx.strategyConfig,
|
|
@@ -7978,6 +8573,37 @@ var executeEntryDecision = async ({
|
|
|
7978
8573
|
}
|
|
7979
8574
|
return signal ?? decision.code;
|
|
7980
8575
|
};
|
|
8576
|
+
async function executeEntryDecision(params) {
|
|
8577
|
+
if (!params.portfolio) return executeAdmittedEntryDecision(params);
|
|
8578
|
+
const { decision, portfolio } = params;
|
|
8579
|
+
const signal = decision.signal;
|
|
8580
|
+
if (!signal || decision.orderPlan.positionIntent === "increase")
|
|
8581
|
+
return "PORTFOLIO_REQUIRES_NEW_SIGNAL_POSITION";
|
|
8582
|
+
signal.orderId ??= createRuntimeOrderId(signal.strategy);
|
|
8583
|
+
const result = await portfolio.submit(
|
|
8584
|
+
params.portfolioTime ?? decision.entryContext.timestamp,
|
|
8585
|
+
{
|
|
8586
|
+
id: signal.orderId,
|
|
8587
|
+
signalId: signal.signalId,
|
|
8588
|
+
strategy: signal.strategy,
|
|
8589
|
+
symbol: params.symbol,
|
|
8590
|
+
direction: decision.entryContext.direction,
|
|
8591
|
+
qty: decision.orderPlan.qty,
|
|
8592
|
+
timestamp: decision.entryContext.timestamp
|
|
8593
|
+
},
|
|
8594
|
+
(markPortfolioSubmitted, rejectPortfolioSubmission) => executeAdmittedEntryDecision({
|
|
8595
|
+
...params,
|
|
8596
|
+
markPortfolioSubmitted,
|
|
8597
|
+
rejectPortfolioSubmission
|
|
8598
|
+
})
|
|
8599
|
+
);
|
|
8600
|
+
if (result.reason) {
|
|
8601
|
+
signal.orderStatus = "failed";
|
|
8602
|
+
signal.orderFailureReason = result.reason;
|
|
8603
|
+
return signal;
|
|
8604
|
+
}
|
|
8605
|
+
return result.value;
|
|
8606
|
+
}
|
|
7981
8607
|
|
|
7982
8608
|
// src/strategyRuntime.ts
|
|
7983
8609
|
var cloneWithPropertyDescriptors = (value) => Object.create(
|
|
@@ -8005,6 +8631,7 @@ var createStrategyRuntime = ({
|
|
|
8005
8631
|
};
|
|
8006
8632
|
const creator = async ({
|
|
8007
8633
|
userName,
|
|
8634
|
+
connectorName,
|
|
8008
8635
|
config: baseConfig,
|
|
8009
8636
|
symbol,
|
|
8010
8637
|
universe: requestedUniverse,
|
|
@@ -8043,6 +8670,37 @@ var createStrategyRuntime = ({
|
|
|
8043
8670
|
const projectConfig = await loadTradejsConfig(projectRoot);
|
|
8044
8671
|
const projectHooks = projectConfig.hooks;
|
|
8045
8672
|
const env = String(config.ENV ?? "BACKTEST");
|
|
8673
|
+
let portfolioMarket;
|
|
8674
|
+
const portfolio = getPortfolioRuntime({
|
|
8675
|
+
symbol,
|
|
8676
|
+
canClose: async (position, intent, asOf) => {
|
|
8677
|
+
if (!portfolioMarket || portfolioMarket.candle.timestamp > asOf || !isRuntimeOrderExecutionEnabled({ config, env, connector }))
|
|
8678
|
+
return false;
|
|
8679
|
+
const gate = await authorizeClose(
|
|
8680
|
+
{
|
|
8681
|
+
kind: "exit",
|
|
8682
|
+
code: intent.reason,
|
|
8683
|
+
closePlan: {
|
|
8684
|
+
direction: position.direction,
|
|
8685
|
+
price: position.markPrice,
|
|
8686
|
+
timestamp: asOf
|
|
8687
|
+
}
|
|
8688
|
+
},
|
|
8689
|
+
portfolioMarket,
|
|
8690
|
+
position.strategy
|
|
8691
|
+
);
|
|
8692
|
+
return gate?.allow !== false;
|
|
8693
|
+
},
|
|
8694
|
+
connectorName,
|
|
8695
|
+
value: baseConfig?.PORTFOLIO_POLICY ?? config.PORTFOLIO_POLICY,
|
|
8696
|
+
strategy: strategyName,
|
|
8697
|
+
connector,
|
|
8698
|
+
env,
|
|
8699
|
+
userName,
|
|
8700
|
+
accountId,
|
|
8701
|
+
deploymentId
|
|
8702
|
+
});
|
|
8703
|
+
const portfolioContext = portfolio ? (now2) => isRuntimeOrderExecutionEnabled({ config, env, connector }) ? portfolio.tick(now2) : portfolio.context(now2) : void 0;
|
|
8046
8704
|
const researchPolicyValue = baseConfig?.RESEARCH_ENTRY_POLICY ?? config.RESEARCH_ENTRY_POLICY;
|
|
8047
8705
|
const researchEntryPolicy = researchPolicyValue == null ? void 0 : parseResearchEntryPolicy(researchPolicyValue);
|
|
8048
8706
|
if (researchEntryPolicy && (env !== "BACKTEST" || Reflect.get(connector, "__tradejsTestConnector") !== true))
|
|
@@ -8207,7 +8865,10 @@ var createStrategyRuntime = ({
|
|
|
8207
8865
|
return invokeHook(stage, hook, params, errorContext);
|
|
8208
8866
|
};
|
|
8209
8867
|
const invokeGateHooks = async (stage, hook, params, errorContext = {}) => {
|
|
8210
|
-
const failureResult =
|
|
8868
|
+
const failureResult = {
|
|
8869
|
+
allow: false,
|
|
8870
|
+
reason: "ERROR"
|
|
8871
|
+
};
|
|
8211
8872
|
const projectResults = await invokeProjectHooks(
|
|
8212
8873
|
stage,
|
|
8213
8874
|
params,
|
|
@@ -8228,6 +8889,12 @@ var createStrategyRuntime = ({
|
|
|
8228
8889
|
failureResult
|
|
8229
8890
|
);
|
|
8230
8891
|
};
|
|
8892
|
+
const authorizeClose = (decision, market, owner = strategyName) => invokeGateHooks(
|
|
8893
|
+
"beforeClosePosition",
|
|
8894
|
+
resolveManifest(owner)?.hooks?.beforeClosePosition,
|
|
8895
|
+
{ ctx: getHookCtx(owner), market, decision },
|
|
8896
|
+
{ decision, market }
|
|
8897
|
+
);
|
|
8231
8898
|
const applyProjectAfterCoreDecisionHooks = async ({
|
|
8232
8899
|
hookCtx,
|
|
8233
8900
|
market,
|
|
@@ -8341,6 +9008,20 @@ var createStrategyRuntime = ({
|
|
|
8341
9008
|
interval: decisionInterval
|
|
8342
9009
|
}) => {
|
|
8343
9010
|
if (!baseContext) return void 0;
|
|
9011
|
+
const profileSource = strategyManifest?.contextRequirements?.tickerProfiles;
|
|
9012
|
+
if (profileSource) {
|
|
9013
|
+
const profile = await loadTickerProfileContext({
|
|
9014
|
+
...profileSource,
|
|
9015
|
+
projectRoot,
|
|
9016
|
+
symbol: decisionSymbol,
|
|
9017
|
+
timestamp: candle.timestamp
|
|
9018
|
+
});
|
|
9019
|
+
if (!profile)
|
|
9020
|
+
throw new Error(
|
|
9021
|
+
"Required ticker profile is unavailable at decision time"
|
|
9022
|
+
);
|
|
9023
|
+
baseContext = withTickerProfile(baseContext, profile);
|
|
9024
|
+
}
|
|
8344
9025
|
if (!coreContextRequirements.has("hyperliquidWhales")) {
|
|
8345
9026
|
return baseContext;
|
|
8346
9027
|
}
|
|
@@ -8508,6 +9189,8 @@ var createStrategyRuntime = ({
|
|
|
8508
9189
|
runtime: pending2.runtime
|
|
8509
9190
|
});
|
|
8510
9191
|
return executeEntryDecision({
|
|
9192
|
+
portfolio,
|
|
9193
|
+
portfolioTime: env === "BACKTEST" || env === "PARITY" ? candle.timestamp : Date.now(),
|
|
8511
9194
|
connector,
|
|
8512
9195
|
symbol,
|
|
8513
9196
|
decision: pending2.decision,
|
|
@@ -8525,6 +9208,11 @@ var createStrategyRuntime = ({
|
|
|
8525
9208
|
});
|
|
8526
9209
|
};
|
|
8527
9210
|
const runWithDecisionOverride = async (candle, btcCandle, options = {}) => {
|
|
9211
|
+
portfolioMarket = { candle, btcCandle };
|
|
9212
|
+
if (portfolioContext)
|
|
9213
|
+
await portfolioContext(
|
|
9214
|
+
env === "BACKTEST" || env === "PARITY" ? candle.timestamp : Date.now()
|
|
9215
|
+
);
|
|
8528
9216
|
appendCurrentMarketData(candle, btcCandle, options.ethCandle);
|
|
8529
9217
|
const ethCandle = resolveEthCandle(candle, options.ethCandle);
|
|
8530
9218
|
indicatorsState.setCurrentBar(candle, btcCandle, ethCandle);
|
|
@@ -8627,15 +9315,10 @@ var createStrategyRuntime = ({
|
|
|
8627
9315
|
if (!makeOrdersEnabled) {
|
|
8628
9316
|
return decision.code;
|
|
8629
9317
|
}
|
|
8630
|
-
const closeGate = await
|
|
8631
|
-
|
|
8632
|
-
|
|
8633
|
-
|
|
8634
|
-
ctx: decisionHookCtx,
|
|
8635
|
-
market,
|
|
8636
|
-
decision
|
|
8637
|
-
},
|
|
8638
|
-
{ decision, market }
|
|
9318
|
+
const closeGate = await authorizeClose(
|
|
9319
|
+
decision,
|
|
9320
|
+
market,
|
|
9321
|
+
decisionHookCtx.strategyName
|
|
8639
9322
|
);
|
|
8640
9323
|
if (closeGate?.allow === false) {
|
|
8641
9324
|
return closeGate.reason ? `CLOSE_BLOCKED_BY_HOOK:${closeGate.reason}` : "CLOSE_BLOCKED_BY_HOOK";
|
|
@@ -8695,6 +9378,13 @@ var createStrategyRuntime = ({
|
|
|
8695
9378
|
});
|
|
8696
9379
|
const signal = decision.signal;
|
|
8697
9380
|
if (signal) {
|
|
9381
|
+
if (portfolio)
|
|
9382
|
+
signal.additionalIndicators = {
|
|
9383
|
+
...signal.additionalIndicators,
|
|
9384
|
+
portfolioContext: await portfolioContext(
|
|
9385
|
+
env === "BACKTEST" || env === "PARITY" ? decision.entryContext.timestamp : Date.now()
|
|
9386
|
+
)
|
|
9387
|
+
};
|
|
8698
9388
|
if (runtimeLineage) signal.runtimeLineage = runtimeLineage;
|
|
8699
9389
|
if (universe) signal.universe = universe;
|
|
8700
9390
|
if (assetClass) signal.assetClass = assetClass;
|
|
@@ -8934,6 +9624,8 @@ var createStrategyRuntime = ({
|
|
|
8934
9624
|
});
|
|
8935
9625
|
}
|
|
8936
9626
|
return executeEntryDecision({
|
|
9627
|
+
portfolio,
|
|
9628
|
+
portfolioTime: env === "BACKTEST" || env === "PARITY" ? candle.timestamp : Date.now(),
|
|
8937
9629
|
connector,
|
|
8938
9630
|
symbol,
|
|
8939
9631
|
decision,
|
|
@@ -9045,7 +9737,7 @@ var normalizeForCanonicalJson = (value) => {
|
|
|
9045
9737
|
}
|
|
9046
9738
|
return value;
|
|
9047
9739
|
};
|
|
9048
|
-
var revision = (prefix, value) => `${prefix}:${(0,
|
|
9740
|
+
var revision = (prefix, value) => `${prefix}:${(0, import_node_crypto5.createHash)("sha256").update(JSON.stringify(normalizeForCanonicalJson(value))).digest("hex").slice(0, 16)}`;
|
|
9049
9741
|
var computeStrategyRevision = ({
|
|
9050
9742
|
strategyName,
|
|
9051
9743
|
strategyPackage,
|
|
@@ -9630,18 +10322,18 @@ var loadRuntimeTrades = async (userName, {
|
|
|
9630
10322
|
const [bucketTradeGroups, legacyKeys] = await Promise.all([
|
|
9631
10323
|
Promise.all(
|
|
9632
10324
|
dayKeys.map(
|
|
9633
|
-
(dayKey) => (0,
|
|
9634
|
-
|
|
10325
|
+
(dayKey) => (0, import_redis5.getHashJsonValues)(
|
|
10326
|
+
import_redis5.redisKeys.runtimeTradeBucket(userName, dayKey)
|
|
9635
10327
|
)
|
|
9636
10328
|
)
|
|
9637
10329
|
),
|
|
9638
|
-
(0,
|
|
10330
|
+
(0, import_redis5.getKeys)(import_redis5.redisKeys.runtimeTrades(userName))
|
|
9639
10331
|
]);
|
|
9640
10332
|
const legacyTradeKeys = legacyKeys.filter(
|
|
9641
|
-
(key) => !key.startsWith(
|
|
10333
|
+
(key) => !key.startsWith(import_redis5.redisKeys.runtimeTradeBuckets(userName))
|
|
9642
10334
|
);
|
|
9643
10335
|
const legacyTrades = await Promise.all(
|
|
9644
|
-
legacyTradeKeys.map((key) => (0,
|
|
10336
|
+
legacyTradeKeys.map((key) => (0, import_redis5.getData)(key, null))
|
|
9645
10337
|
);
|
|
9646
10338
|
const dedupedTrades = /* @__PURE__ */ new Map();
|
|
9647
10339
|
for (const trade of [...legacyTrades, ...bucketTradeGroups.flat()]) {
|
|
@@ -9728,8 +10420,8 @@ var loadExchangeRange = async ({
|
|
|
9728
10420
|
}
|
|
9729
10421
|
};
|
|
9730
10422
|
var loadActiveRuntimeOrderIds = async (userName) => {
|
|
9731
|
-
const keys = await (0,
|
|
9732
|
-
const refs = await Promise.all(keys.map((key) => (0,
|
|
10423
|
+
const keys = await (0, import_redis5.getKeys)(import_redis5.redisKeys.runtimeActiveTrades(userName));
|
|
10424
|
+
const refs = await Promise.all(keys.map((key) => (0, import_redis5.getData)(key, null)));
|
|
9733
10425
|
return new Set(
|
|
9734
10426
|
refs.map(
|
|
9735
10427
|
(ref) => typeof ref?.orderId === "string" && ref.orderId.trim() ? ref.orderId.trim() : null
|