@tradejs/node 3.1.29-beta.291 → 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/backtest.js +92 -22
- package/dist/backtest.mjs +2 -2
- package/dist/{chunk-HRON2EAI.mjs → chunk-EVKQ4DI6.mjs} +52 -15
- package/dist/{chunk-UUNEWAT4.mjs → chunk-NJEYNGRU.mjs} +41 -8
- package/dist/diagnostics.js +92 -22
- package/dist/diagnostics.mjs +2 -2
- package/dist/mcp.js +92 -22
- package/dist/mcp.mjs +2 -2
- package/dist/portfolio.d.mts +1 -1
- package/dist/portfolio.d.ts +1 -1
- package/dist/portfolio.js +41 -8
- package/dist/portfolio.mjs +1 -1
- package/dist/registry.js +92 -22
- package/dist/registry.mjs +2 -2
- package/dist/runtimeDashboard.js +92 -22
- package/dist/runtimeDashboard.mjs +2 -2
- package/dist/runtimeStrategies.js +92 -22
- package/dist/runtimeStrategies.mjs +2 -2
- package/dist/strategies.d.mts +2 -2
- package/dist/strategies.d.ts +2 -2
- package/dist/strategies.js +92 -22
- package/dist/strategies.mjs +2 -2
- package/package.json +5 -5
package/dist/diagnostics.js
CHANGED
|
@@ -956,39 +956,72 @@ function createPortfolioCoordinator({
|
|
|
956
956
|
);
|
|
957
957
|
if (admitted.result) return { reason: admitted.result };
|
|
958
958
|
let submitted = false;
|
|
959
|
+
let retry = 0;
|
|
960
|
+
let attemptId = entry.id;
|
|
961
|
+
let needsReservation = false;
|
|
959
962
|
try {
|
|
960
963
|
const value = await send(
|
|
961
964
|
async (qty) => {
|
|
962
965
|
if (!Number.isFinite(qty) || qty <= 0)
|
|
963
966
|
throw new Error("Invalid submitted portfolio quantity");
|
|
964
|
-
|
|
965
|
-
|
|
967
|
+
if (needsReservation) {
|
|
968
|
+
attemptId = `${entry.id.slice(0, 30)}r${++retry}`;
|
|
969
|
+
const admission = await transact(
|
|
970
|
+
now2,
|
|
971
|
+
(s, o) => {
|
|
972
|
+
const r = (0, import_portfolio.reservePortfolioEntry)(policy, strategy, s, o, {
|
|
973
|
+
...entry,
|
|
974
|
+
id: attemptId,
|
|
975
|
+
qty,
|
|
976
|
+
submitted: true,
|
|
977
|
+
oppositeHandled: false
|
|
978
|
+
});
|
|
979
|
+
return { state: r.state, result: r.reason };
|
|
980
|
+
},
|
|
981
|
+
true
|
|
982
|
+
);
|
|
983
|
+
if (admission.result) throw new Error(admission.result);
|
|
984
|
+
needsReservation = false;
|
|
985
|
+
submitted = true;
|
|
986
|
+
return attemptId;
|
|
987
|
+
}
|
|
988
|
+
const update = await updateReservation(now2, attemptId, true, qty);
|
|
966
989
|
if (!update.result)
|
|
967
990
|
throw new Error("PORTFOLIO_SUBMISSION_NO_LONGER_ADMITTED");
|
|
991
|
+
submitted = true;
|
|
992
|
+
return attemptId;
|
|
968
993
|
},
|
|
969
|
-
async () => {
|
|
994
|
+
async (retrying = false) => {
|
|
970
995
|
await transact(
|
|
971
996
|
now2,
|
|
972
997
|
(s) => ({
|
|
973
998
|
state: {
|
|
974
999
|
...s,
|
|
975
|
-
reservations: s.reservations.filter(
|
|
1000
|
+
reservations: s.reservations.filter(
|
|
1001
|
+
(r) => r.id !== attemptId
|
|
1002
|
+
),
|
|
976
1003
|
completedSignals: {
|
|
977
|
-
...
|
|
978
|
-
|
|
1004
|
+
...Object.fromEntries(
|
|
1005
|
+
Object.entries(s.completedSignals).filter(
|
|
1006
|
+
([id]) => !retrying || id !== entry.signalId
|
|
1007
|
+
)
|
|
1008
|
+
),
|
|
1009
|
+
...retrying ? {} : { [entry.signalId]: now2 }
|
|
979
1010
|
}
|
|
980
1011
|
},
|
|
981
1012
|
result: void 0
|
|
982
1013
|
}),
|
|
983
1014
|
true
|
|
984
1015
|
);
|
|
1016
|
+
submitted = false;
|
|
1017
|
+
needsReservation = retrying;
|
|
985
1018
|
}
|
|
986
1019
|
);
|
|
987
|
-
if (!submitted) await updateReservation(now2,
|
|
1020
|
+
if (!submitted) await updateReservation(now2, attemptId, false);
|
|
988
1021
|
else await tick(now2, true);
|
|
989
1022
|
return { value };
|
|
990
1023
|
} catch (error) {
|
|
991
|
-
if (!submitted) await updateReservation(now2,
|
|
1024
|
+
if (!submitted) await updateReservation(now2, attemptId, false);
|
|
992
1025
|
throw error;
|
|
993
1026
|
}
|
|
994
1027
|
}
|
|
@@ -1356,7 +1389,9 @@ function getPortfolioRuntime({
|
|
|
1356
1389
|
env,
|
|
1357
1390
|
userName,
|
|
1358
1391
|
accountId,
|
|
1359
|
-
deploymentId
|
|
1392
|
+
deploymentId,
|
|
1393
|
+
symbol,
|
|
1394
|
+
canClose
|
|
1360
1395
|
}) {
|
|
1361
1396
|
const policy = (0, import_portfolio2.parsePortfolioPolicy)(value);
|
|
1362
1397
|
if (!policy) return void 0;
|
|
@@ -1387,8 +1422,13 @@ function getPortfolioRuntime({
|
|
|
1387
1422
|
if (existing) {
|
|
1388
1423
|
if (existing.fingerprint !== fingerprint3)
|
|
1389
1424
|
throw new Error("Conflicting portfolio configuration for the same scope");
|
|
1425
|
+
if (symbol && canClose)
|
|
1426
|
+
existing.permissions.set(JSON.stringify([strategy, symbol]), canClose);
|
|
1390
1427
|
return existing.coordinator;
|
|
1391
1428
|
}
|
|
1429
|
+
const permissions = /* @__PURE__ */ new Map();
|
|
1430
|
+
if (symbol && canClose)
|
|
1431
|
+
permissions.set(JSON.stringify([strategy, symbol]), canClose);
|
|
1392
1432
|
const coordinator = createPortfolioCoordinator({
|
|
1393
1433
|
policy,
|
|
1394
1434
|
strategy,
|
|
@@ -1409,6 +1449,10 @@ function getPortfolioRuntime({
|
|
|
1409
1449
|
},
|
|
1410
1450
|
observe: (asOf, reservations) => replay ? connector.getPortfolioObservation(asOf, reservations) : observeRuntimePortfolio(connector, binding, asOf, reservations),
|
|
1411
1451
|
async close(position, intent, asOf) {
|
|
1452
|
+
const permission = permissions.get(
|
|
1453
|
+
JSON.stringify([position.strategy, position.symbol])
|
|
1454
|
+
);
|
|
1455
|
+
if (!permission || !await permission(position, intent, asOf)) return;
|
|
1412
1456
|
if (!replay) {
|
|
1413
1457
|
const trade = await getActiveRuntimeTrade({
|
|
1414
1458
|
...binding,
|
|
@@ -1443,7 +1487,7 @@ function getPortfolioRuntime({
|
|
|
1443
1487
|
}
|
|
1444
1488
|
}
|
|
1445
1489
|
});
|
|
1446
|
-
registry.set(cacheKey, { fingerprint: fingerprint3, coordinator });
|
|
1490
|
+
registry.set(cacheKey, { fingerprint: fingerprint3, coordinator, permissions });
|
|
1447
1491
|
return coordinator;
|
|
1448
1492
|
}
|
|
1449
1493
|
|
|
@@ -6278,7 +6322,7 @@ var executeEntryOrder = async ({
|
|
|
6278
6322
|
}) => {
|
|
6279
6323
|
await beforePlaceOrder?.();
|
|
6280
6324
|
const previousPosition = positionIntent === "increase" ? await connector.getPosition(symbol) : null;
|
|
6281
|
-
|
|
6325
|
+
let orderId = signal.orderId || createRuntimeOrderId(signal.strategy);
|
|
6282
6326
|
const signalTimestamp = signal.timestamp;
|
|
6283
6327
|
const signalClosePrice = currentPrice;
|
|
6284
6328
|
signal.orderId = orderId;
|
|
@@ -6351,11 +6395,12 @@ var executeEntryOrder = async ({
|
|
|
6351
6395
|
...beforeSubmit || markPortfolioSubmitted ? {
|
|
6352
6396
|
beforeSubmit: async (context) => {
|
|
6353
6397
|
await beforeSubmit?.(context);
|
|
6354
|
-
|
|
6398
|
+
return markPortfolioSubmitted?.(context.qty);
|
|
6355
6399
|
}
|
|
6356
6400
|
} : {}
|
|
6357
6401
|
});
|
|
6358
6402
|
const orderAckTime = Date.now();
|
|
6403
|
+
if (markPortfolioSubmitted && signal.orderId) orderId = signal.orderId;
|
|
6359
6404
|
const placedQty = typeof signal.orderQty === "number" && Number.isFinite(signal.orderQty) && signal.orderQty > 0 ? signal.orderQty : qty;
|
|
6360
6405
|
const currentPosition = await connector.getPosition(symbol);
|
|
6361
6406
|
const fillTime = Date.now();
|
|
@@ -7643,7 +7688,27 @@ var createStrategyRuntime = ({
|
|
|
7643
7688
|
const projectConfig = await loadTradejsConfig(projectRoot);
|
|
7644
7689
|
const projectHooks = projectConfig.hooks;
|
|
7645
7690
|
const env = String(config.ENV ?? "BACKTEST");
|
|
7691
|
+
let portfolioMarket;
|
|
7646
7692
|
const portfolio = getPortfolioRuntime({
|
|
7693
|
+
symbol,
|
|
7694
|
+
canClose: async (position, intent, asOf) => {
|
|
7695
|
+
if (!portfolioMarket || portfolioMarket.candle.timestamp > asOf || !isRuntimeOrderExecutionEnabled({ config, env, connector }))
|
|
7696
|
+
return false;
|
|
7697
|
+
const gate = await authorizeClose(
|
|
7698
|
+
{
|
|
7699
|
+
kind: "exit",
|
|
7700
|
+
code: intent.reason,
|
|
7701
|
+
closePlan: {
|
|
7702
|
+
direction: position.direction,
|
|
7703
|
+
price: position.markPrice,
|
|
7704
|
+
timestamp: asOf
|
|
7705
|
+
}
|
|
7706
|
+
},
|
|
7707
|
+
portfolioMarket,
|
|
7708
|
+
position.strategy
|
|
7709
|
+
);
|
|
7710
|
+
return gate?.allow !== false;
|
|
7711
|
+
},
|
|
7647
7712
|
connectorName,
|
|
7648
7713
|
value: baseConfig?.PORTFOLIO_POLICY ?? config.PORTFOLIO_POLICY,
|
|
7649
7714
|
strategy: strategyName,
|
|
@@ -7818,7 +7883,10 @@ var createStrategyRuntime = ({
|
|
|
7818
7883
|
return invokeHook(stage, hook, params, errorContext);
|
|
7819
7884
|
};
|
|
7820
7885
|
const invokeGateHooks = async (stage, hook, params, errorContext = {}) => {
|
|
7821
|
-
const failureResult =
|
|
7886
|
+
const failureResult = {
|
|
7887
|
+
allow: false,
|
|
7888
|
+
reason: "ERROR"
|
|
7889
|
+
};
|
|
7822
7890
|
const projectResults = await invokeProjectHooks(
|
|
7823
7891
|
stage,
|
|
7824
7892
|
params,
|
|
@@ -7839,6 +7907,12 @@ var createStrategyRuntime = ({
|
|
|
7839
7907
|
failureResult
|
|
7840
7908
|
);
|
|
7841
7909
|
};
|
|
7910
|
+
const authorizeClose = (decision, market, owner = strategyName) => invokeGateHooks(
|
|
7911
|
+
"beforeClosePosition",
|
|
7912
|
+
resolveManifest(owner)?.hooks?.beforeClosePosition,
|
|
7913
|
+
{ ctx: getHookCtx(owner), market, decision },
|
|
7914
|
+
{ decision, market }
|
|
7915
|
+
);
|
|
7842
7916
|
const applyProjectAfterCoreDecisionHooks = async ({
|
|
7843
7917
|
hookCtx,
|
|
7844
7918
|
market,
|
|
@@ -8152,6 +8226,7 @@ var createStrategyRuntime = ({
|
|
|
8152
8226
|
});
|
|
8153
8227
|
};
|
|
8154
8228
|
const runWithDecisionOverride = async (candle, btcCandle, options = {}) => {
|
|
8229
|
+
portfolioMarket = { candle, btcCandle };
|
|
8155
8230
|
if (portfolioContext)
|
|
8156
8231
|
await portfolioContext(
|
|
8157
8232
|
env === "BACKTEST" || env === "PARITY" ? candle.timestamp : Date.now()
|
|
@@ -8258,15 +8333,10 @@ var createStrategyRuntime = ({
|
|
|
8258
8333
|
if (!makeOrdersEnabled) {
|
|
8259
8334
|
return decision.code;
|
|
8260
8335
|
}
|
|
8261
|
-
const closeGate = await
|
|
8262
|
-
|
|
8263
|
-
|
|
8264
|
-
|
|
8265
|
-
ctx: decisionHookCtx,
|
|
8266
|
-
market,
|
|
8267
|
-
decision
|
|
8268
|
-
},
|
|
8269
|
-
{ decision, market }
|
|
8336
|
+
const closeGate = await authorizeClose(
|
|
8337
|
+
decision,
|
|
8338
|
+
market,
|
|
8339
|
+
decisionHookCtx.strategyName
|
|
8270
8340
|
);
|
|
8271
8341
|
if (closeGate?.allow === false) {
|
|
8272
8342
|
return closeGate.reason ? `CLOSE_BLOCKED_BY_HOOK:${closeGate.reason}` : "CLOSE_BLOCKED_BY_HOOK";
|
package/dist/diagnostics.mjs
CHANGED
|
@@ -2,8 +2,8 @@ import {
|
|
|
2
2
|
getRuntimeDeployment,
|
|
3
3
|
loadResolvedRuntimeStrategies
|
|
4
4
|
} from "./chunk-W6PPAADK.mjs";
|
|
5
|
-
import "./chunk-
|
|
6
|
-
import "./chunk-
|
|
5
|
+
import "./chunk-EVKQ4DI6.mjs";
|
|
6
|
+
import "./chunk-NJEYNGRU.mjs";
|
|
7
7
|
import "./chunk-PYJWXAYR.mjs";
|
|
8
8
|
import "./chunk-LZDXRXIU.mjs";
|
|
9
9
|
import "./chunk-YR55Q355.mjs";
|
package/dist/mcp.js
CHANGED
|
@@ -950,39 +950,72 @@ function createPortfolioCoordinator({
|
|
|
950
950
|
);
|
|
951
951
|
if (admitted.result) return { reason: admitted.result };
|
|
952
952
|
let submitted = false;
|
|
953
|
+
let retry = 0;
|
|
954
|
+
let attemptId = entry.id;
|
|
955
|
+
let needsReservation = false;
|
|
953
956
|
try {
|
|
954
957
|
const value = await send(
|
|
955
958
|
async (qty) => {
|
|
956
959
|
if (!Number.isFinite(qty) || qty <= 0)
|
|
957
960
|
throw new Error("Invalid submitted portfolio quantity");
|
|
958
|
-
|
|
959
|
-
|
|
961
|
+
if (needsReservation) {
|
|
962
|
+
attemptId = `${entry.id.slice(0, 30)}r${++retry}`;
|
|
963
|
+
const admission = await transact(
|
|
964
|
+
now2,
|
|
965
|
+
(s, o) => {
|
|
966
|
+
const r = (0, import_portfolio.reservePortfolioEntry)(policy, strategy, s, o, {
|
|
967
|
+
...entry,
|
|
968
|
+
id: attemptId,
|
|
969
|
+
qty,
|
|
970
|
+
submitted: true,
|
|
971
|
+
oppositeHandled: false
|
|
972
|
+
});
|
|
973
|
+
return { state: r.state, result: r.reason };
|
|
974
|
+
},
|
|
975
|
+
true
|
|
976
|
+
);
|
|
977
|
+
if (admission.result) throw new Error(admission.result);
|
|
978
|
+
needsReservation = false;
|
|
979
|
+
submitted = true;
|
|
980
|
+
return attemptId;
|
|
981
|
+
}
|
|
982
|
+
const update = await updateReservation(now2, attemptId, true, qty);
|
|
960
983
|
if (!update.result)
|
|
961
984
|
throw new Error("PORTFOLIO_SUBMISSION_NO_LONGER_ADMITTED");
|
|
985
|
+
submitted = true;
|
|
986
|
+
return attemptId;
|
|
962
987
|
},
|
|
963
|
-
async () => {
|
|
988
|
+
async (retrying = false) => {
|
|
964
989
|
await transact(
|
|
965
990
|
now2,
|
|
966
991
|
(s) => ({
|
|
967
992
|
state: {
|
|
968
993
|
...s,
|
|
969
|
-
reservations: s.reservations.filter(
|
|
994
|
+
reservations: s.reservations.filter(
|
|
995
|
+
(r) => r.id !== attemptId
|
|
996
|
+
),
|
|
970
997
|
completedSignals: {
|
|
971
|
-
...
|
|
972
|
-
|
|
998
|
+
...Object.fromEntries(
|
|
999
|
+
Object.entries(s.completedSignals).filter(
|
|
1000
|
+
([id]) => !retrying || id !== entry.signalId
|
|
1001
|
+
)
|
|
1002
|
+
),
|
|
1003
|
+
...retrying ? {} : { [entry.signalId]: now2 }
|
|
973
1004
|
}
|
|
974
1005
|
},
|
|
975
1006
|
result: void 0
|
|
976
1007
|
}),
|
|
977
1008
|
true
|
|
978
1009
|
);
|
|
1010
|
+
submitted = false;
|
|
1011
|
+
needsReservation = retrying;
|
|
979
1012
|
}
|
|
980
1013
|
);
|
|
981
|
-
if (!submitted) await updateReservation(now2,
|
|
1014
|
+
if (!submitted) await updateReservation(now2, attemptId, false);
|
|
982
1015
|
else await tick(now2, true);
|
|
983
1016
|
return { value };
|
|
984
1017
|
} catch (error) {
|
|
985
|
-
if (!submitted) await updateReservation(now2,
|
|
1018
|
+
if (!submitted) await updateReservation(now2, attemptId, false);
|
|
986
1019
|
throw error;
|
|
987
1020
|
}
|
|
988
1021
|
}
|
|
@@ -1350,7 +1383,9 @@ function getPortfolioRuntime({
|
|
|
1350
1383
|
env,
|
|
1351
1384
|
userName,
|
|
1352
1385
|
accountId,
|
|
1353
|
-
deploymentId
|
|
1386
|
+
deploymentId,
|
|
1387
|
+
symbol,
|
|
1388
|
+
canClose
|
|
1354
1389
|
}) {
|
|
1355
1390
|
const policy = (0, import_portfolio2.parsePortfolioPolicy)(value);
|
|
1356
1391
|
if (!policy) return void 0;
|
|
@@ -1381,8 +1416,13 @@ function getPortfolioRuntime({
|
|
|
1381
1416
|
if (existing) {
|
|
1382
1417
|
if (existing.fingerprint !== fingerprint3)
|
|
1383
1418
|
throw new Error("Conflicting portfolio configuration for the same scope");
|
|
1419
|
+
if (symbol && canClose)
|
|
1420
|
+
existing.permissions.set(JSON.stringify([strategy, symbol]), canClose);
|
|
1384
1421
|
return existing.coordinator;
|
|
1385
1422
|
}
|
|
1423
|
+
const permissions = /* @__PURE__ */ new Map();
|
|
1424
|
+
if (symbol && canClose)
|
|
1425
|
+
permissions.set(JSON.stringify([strategy, symbol]), canClose);
|
|
1386
1426
|
const coordinator = createPortfolioCoordinator({
|
|
1387
1427
|
policy,
|
|
1388
1428
|
strategy,
|
|
@@ -1403,6 +1443,10 @@ function getPortfolioRuntime({
|
|
|
1403
1443
|
},
|
|
1404
1444
|
observe: (asOf, reservations) => replay ? connector.getPortfolioObservation(asOf, reservations) : observeRuntimePortfolio(connector, binding, asOf, reservations),
|
|
1405
1445
|
async close(position, intent, asOf) {
|
|
1446
|
+
const permission = permissions.get(
|
|
1447
|
+
JSON.stringify([position.strategy, position.symbol])
|
|
1448
|
+
);
|
|
1449
|
+
if (!permission || !await permission(position, intent, asOf)) return;
|
|
1406
1450
|
if (!replay) {
|
|
1407
1451
|
const trade = await getActiveRuntimeTrade({
|
|
1408
1452
|
...binding,
|
|
@@ -1437,7 +1481,7 @@ function getPortfolioRuntime({
|
|
|
1437
1481
|
}
|
|
1438
1482
|
}
|
|
1439
1483
|
});
|
|
1440
|
-
registry.set(cacheKey, { fingerprint: fingerprint3, coordinator });
|
|
1484
|
+
registry.set(cacheKey, { fingerprint: fingerprint3, coordinator, permissions });
|
|
1441
1485
|
return coordinator;
|
|
1442
1486
|
}
|
|
1443
1487
|
|
|
@@ -6273,7 +6317,7 @@ var executeEntryOrder = async ({
|
|
|
6273
6317
|
}) => {
|
|
6274
6318
|
await beforePlaceOrder?.();
|
|
6275
6319
|
const previousPosition = positionIntent === "increase" ? await connector.getPosition(symbol) : null;
|
|
6276
|
-
|
|
6320
|
+
let orderId = signal.orderId || createRuntimeOrderId(signal.strategy);
|
|
6277
6321
|
const signalTimestamp = signal.timestamp;
|
|
6278
6322
|
const signalClosePrice = currentPrice;
|
|
6279
6323
|
signal.orderId = orderId;
|
|
@@ -6346,11 +6390,12 @@ var executeEntryOrder = async ({
|
|
|
6346
6390
|
...beforeSubmit || markPortfolioSubmitted ? {
|
|
6347
6391
|
beforeSubmit: async (context) => {
|
|
6348
6392
|
await beforeSubmit?.(context);
|
|
6349
|
-
|
|
6393
|
+
return markPortfolioSubmitted?.(context.qty);
|
|
6350
6394
|
}
|
|
6351
6395
|
} : {}
|
|
6352
6396
|
});
|
|
6353
6397
|
const orderAckTime = Date.now();
|
|
6398
|
+
if (markPortfolioSubmitted && signal.orderId) orderId = signal.orderId;
|
|
6354
6399
|
const placedQty = typeof signal.orderQty === "number" && Number.isFinite(signal.orderQty) && signal.orderQty > 0 ? signal.orderQty : qty;
|
|
6355
6400
|
const currentPosition = await connector.getPosition(symbol);
|
|
6356
6401
|
const fillTime = Date.now();
|
|
@@ -7638,7 +7683,27 @@ var createStrategyRuntime = ({
|
|
|
7638
7683
|
const projectConfig = await loadTradejsConfig(projectRoot);
|
|
7639
7684
|
const projectHooks = projectConfig.hooks;
|
|
7640
7685
|
const env = String(config.ENV ?? "BACKTEST");
|
|
7686
|
+
let portfolioMarket;
|
|
7641
7687
|
const portfolio = getPortfolioRuntime({
|
|
7688
|
+
symbol,
|
|
7689
|
+
canClose: async (position, intent, asOf) => {
|
|
7690
|
+
if (!portfolioMarket || portfolioMarket.candle.timestamp > asOf || !isRuntimeOrderExecutionEnabled({ config, env, connector }))
|
|
7691
|
+
return false;
|
|
7692
|
+
const gate = await authorizeClose(
|
|
7693
|
+
{
|
|
7694
|
+
kind: "exit",
|
|
7695
|
+
code: intent.reason,
|
|
7696
|
+
closePlan: {
|
|
7697
|
+
direction: position.direction,
|
|
7698
|
+
price: position.markPrice,
|
|
7699
|
+
timestamp: asOf
|
|
7700
|
+
}
|
|
7701
|
+
},
|
|
7702
|
+
portfolioMarket,
|
|
7703
|
+
position.strategy
|
|
7704
|
+
);
|
|
7705
|
+
return gate?.allow !== false;
|
|
7706
|
+
},
|
|
7642
7707
|
connectorName,
|
|
7643
7708
|
value: baseConfig?.PORTFOLIO_POLICY ?? config.PORTFOLIO_POLICY,
|
|
7644
7709
|
strategy: strategyName,
|
|
@@ -7813,7 +7878,10 @@ var createStrategyRuntime = ({
|
|
|
7813
7878
|
return invokeHook(stage, hook, params, errorContext);
|
|
7814
7879
|
};
|
|
7815
7880
|
const invokeGateHooks = async (stage, hook, params, errorContext = {}) => {
|
|
7816
|
-
const failureResult =
|
|
7881
|
+
const failureResult = {
|
|
7882
|
+
allow: false,
|
|
7883
|
+
reason: "ERROR"
|
|
7884
|
+
};
|
|
7817
7885
|
const projectResults = await invokeProjectHooks(
|
|
7818
7886
|
stage,
|
|
7819
7887
|
params,
|
|
@@ -7834,6 +7902,12 @@ var createStrategyRuntime = ({
|
|
|
7834
7902
|
failureResult
|
|
7835
7903
|
);
|
|
7836
7904
|
};
|
|
7905
|
+
const authorizeClose = (decision, market, owner = strategyName) => invokeGateHooks(
|
|
7906
|
+
"beforeClosePosition",
|
|
7907
|
+
resolveManifest(owner)?.hooks?.beforeClosePosition,
|
|
7908
|
+
{ ctx: getHookCtx(owner), market, decision },
|
|
7909
|
+
{ decision, market }
|
|
7910
|
+
);
|
|
7837
7911
|
const applyProjectAfterCoreDecisionHooks = async ({
|
|
7838
7912
|
hookCtx,
|
|
7839
7913
|
market,
|
|
@@ -8147,6 +8221,7 @@ var createStrategyRuntime = ({
|
|
|
8147
8221
|
});
|
|
8148
8222
|
};
|
|
8149
8223
|
const runWithDecisionOverride = async (candle, btcCandle, options = {}) => {
|
|
8224
|
+
portfolioMarket = { candle, btcCandle };
|
|
8150
8225
|
if (portfolioContext)
|
|
8151
8226
|
await portfolioContext(
|
|
8152
8227
|
env === "BACKTEST" || env === "PARITY" ? candle.timestamp : Date.now()
|
|
@@ -8253,15 +8328,10 @@ var createStrategyRuntime = ({
|
|
|
8253
8328
|
if (!makeOrdersEnabled) {
|
|
8254
8329
|
return decision.code;
|
|
8255
8330
|
}
|
|
8256
|
-
const closeGate = await
|
|
8257
|
-
|
|
8258
|
-
|
|
8259
|
-
|
|
8260
|
-
ctx: decisionHookCtx,
|
|
8261
|
-
market,
|
|
8262
|
-
decision
|
|
8263
|
-
},
|
|
8264
|
-
{ decision, market }
|
|
8331
|
+
const closeGate = await authorizeClose(
|
|
8332
|
+
decision,
|
|
8333
|
+
market,
|
|
8334
|
+
decisionHookCtx.strategyName
|
|
8265
8335
|
);
|
|
8266
8336
|
if (closeGate?.allow === false) {
|
|
8267
8337
|
return closeGate.reason ? `CLOSE_BLOCKED_BY_HOOK:${closeGate.reason}` : "CLOSE_BLOCKED_BY_HOOK";
|
package/dist/mcp.mjs
CHANGED
|
@@ -7,8 +7,8 @@ import {
|
|
|
7
7
|
listRuntimeDeployments,
|
|
8
8
|
loadResolvedRuntimeStrategies
|
|
9
9
|
} from "./chunk-W6PPAADK.mjs";
|
|
10
|
-
import "./chunk-
|
|
11
|
-
import "./chunk-
|
|
10
|
+
import "./chunk-EVKQ4DI6.mjs";
|
|
11
|
+
import "./chunk-NJEYNGRU.mjs";
|
|
12
12
|
import {
|
|
13
13
|
ensureIndicatorPluginsLoaded
|
|
14
14
|
} from "./chunk-PYJWXAYR.mjs";
|
package/dist/portfolio.d.mts
CHANGED
|
@@ -16,7 +16,7 @@ declare function createPortfolioCoordinator({ policy, strategy, store, execution
|
|
|
16
16
|
}): {
|
|
17
17
|
context(now: number): Promise<_tradejs_types.PortfolioContext>;
|
|
18
18
|
tick: (now: number, force?: boolean) => Promise<_tradejs_types.PortfolioContext>;
|
|
19
|
-
submit<T>(now: number, entry: Omit<PortfolioReservation, "submitted" | "oppositeHandled">, send: (markSubmitted: (qty: number) => Promise<
|
|
19
|
+
submit<T>(now: number, entry: Omit<PortfolioReservation, "submitted" | "oppositeHandled">, send: (markSubmitted: (qty: number) => Promise<string>, markRejected: (retrying?: boolean) => Promise<void>) => Promise<T>): Promise<{
|
|
20
20
|
reason?: string;
|
|
21
21
|
value?: T;
|
|
22
22
|
}>;
|
package/dist/portfolio.d.ts
CHANGED
|
@@ -16,7 +16,7 @@ declare function createPortfolioCoordinator({ policy, strategy, store, execution
|
|
|
16
16
|
}): {
|
|
17
17
|
context(now: number): Promise<_tradejs_types.PortfolioContext>;
|
|
18
18
|
tick: (now: number, force?: boolean) => Promise<_tradejs_types.PortfolioContext>;
|
|
19
|
-
submit<T>(now: number, entry: Omit<PortfolioReservation, "submitted" | "oppositeHandled">, send: (markSubmitted: (qty: number) => Promise<
|
|
19
|
+
submit<T>(now: number, entry: Omit<PortfolioReservation, "submitted" | "oppositeHandled">, send: (markSubmitted: (qty: number) => Promise<string>, markRejected: (retrying?: boolean) => Promise<void>) => Promise<T>): Promise<{
|
|
20
20
|
reason?: string;
|
|
21
21
|
value?: T;
|
|
22
22
|
}>;
|
package/dist/portfolio.js
CHANGED
|
@@ -154,39 +154,72 @@ function createPortfolioCoordinator({
|
|
|
154
154
|
);
|
|
155
155
|
if (admitted.result) return { reason: admitted.result };
|
|
156
156
|
let submitted = false;
|
|
157
|
+
let retry = 0;
|
|
158
|
+
let attemptId = entry.id;
|
|
159
|
+
let needsReservation = false;
|
|
157
160
|
try {
|
|
158
161
|
const value = await send(
|
|
159
162
|
async (qty) => {
|
|
160
163
|
if (!Number.isFinite(qty) || qty <= 0)
|
|
161
164
|
throw new Error("Invalid submitted portfolio quantity");
|
|
162
|
-
|
|
163
|
-
|
|
165
|
+
if (needsReservation) {
|
|
166
|
+
attemptId = `${entry.id.slice(0, 30)}r${++retry}`;
|
|
167
|
+
const admission = await transact(
|
|
168
|
+
now,
|
|
169
|
+
(s, o) => {
|
|
170
|
+
const r = (0, import_portfolio.reservePortfolioEntry)(policy, strategy, s, o, {
|
|
171
|
+
...entry,
|
|
172
|
+
id: attemptId,
|
|
173
|
+
qty,
|
|
174
|
+
submitted: true,
|
|
175
|
+
oppositeHandled: false
|
|
176
|
+
});
|
|
177
|
+
return { state: r.state, result: r.reason };
|
|
178
|
+
},
|
|
179
|
+
true
|
|
180
|
+
);
|
|
181
|
+
if (admission.result) throw new Error(admission.result);
|
|
182
|
+
needsReservation = false;
|
|
183
|
+
submitted = true;
|
|
184
|
+
return attemptId;
|
|
185
|
+
}
|
|
186
|
+
const update = await updateReservation(now, attemptId, true, qty);
|
|
164
187
|
if (!update.result)
|
|
165
188
|
throw new Error("PORTFOLIO_SUBMISSION_NO_LONGER_ADMITTED");
|
|
189
|
+
submitted = true;
|
|
190
|
+
return attemptId;
|
|
166
191
|
},
|
|
167
|
-
async () => {
|
|
192
|
+
async (retrying = false) => {
|
|
168
193
|
await transact(
|
|
169
194
|
now,
|
|
170
195
|
(s) => ({
|
|
171
196
|
state: {
|
|
172
197
|
...s,
|
|
173
|
-
reservations: s.reservations.filter(
|
|
198
|
+
reservations: s.reservations.filter(
|
|
199
|
+
(r) => r.id !== attemptId
|
|
200
|
+
),
|
|
174
201
|
completedSignals: {
|
|
175
|
-
...
|
|
176
|
-
|
|
202
|
+
...Object.fromEntries(
|
|
203
|
+
Object.entries(s.completedSignals).filter(
|
|
204
|
+
([id]) => !retrying || id !== entry.signalId
|
|
205
|
+
)
|
|
206
|
+
),
|
|
207
|
+
...retrying ? {} : { [entry.signalId]: now }
|
|
177
208
|
}
|
|
178
209
|
},
|
|
179
210
|
result: void 0
|
|
180
211
|
}),
|
|
181
212
|
true
|
|
182
213
|
);
|
|
214
|
+
submitted = false;
|
|
215
|
+
needsReservation = retrying;
|
|
183
216
|
}
|
|
184
217
|
);
|
|
185
|
-
if (!submitted) await updateReservation(now,
|
|
218
|
+
if (!submitted) await updateReservation(now, attemptId, false);
|
|
186
219
|
else await tick(now, true);
|
|
187
220
|
return { value };
|
|
188
221
|
} catch (error) {
|
|
189
|
-
if (!submitted) await updateReservation(now,
|
|
222
|
+
if (!submitted) await updateReservation(now, attemptId, false);
|
|
190
223
|
throw error;
|
|
191
224
|
}
|
|
192
225
|
}
|