@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/backtest.js
CHANGED
|
@@ -937,39 +937,72 @@ function createPortfolioCoordinator({
|
|
|
937
937
|
);
|
|
938
938
|
if (admitted.result) return { reason: admitted.result };
|
|
939
939
|
let submitted = false;
|
|
940
|
+
let retry = 0;
|
|
941
|
+
let attemptId = entry.id;
|
|
942
|
+
let needsReservation = false;
|
|
940
943
|
try {
|
|
941
944
|
const value = await send(
|
|
942
945
|
async (qty) => {
|
|
943
946
|
if (!Number.isFinite(qty) || qty <= 0)
|
|
944
947
|
throw new Error("Invalid submitted portfolio quantity");
|
|
945
|
-
|
|
946
|
-
|
|
948
|
+
if (needsReservation) {
|
|
949
|
+
attemptId = `${entry.id.slice(0, 30)}r${++retry}`;
|
|
950
|
+
const admission = await transact(
|
|
951
|
+
now2,
|
|
952
|
+
(s, o) => {
|
|
953
|
+
const r = (0, import_portfolio.reservePortfolioEntry)(policy, strategy, s, o, {
|
|
954
|
+
...entry,
|
|
955
|
+
id: attemptId,
|
|
956
|
+
qty,
|
|
957
|
+
submitted: true,
|
|
958
|
+
oppositeHandled: false
|
|
959
|
+
});
|
|
960
|
+
return { state: r.state, result: r.reason };
|
|
961
|
+
},
|
|
962
|
+
true
|
|
963
|
+
);
|
|
964
|
+
if (admission.result) throw new Error(admission.result);
|
|
965
|
+
needsReservation = false;
|
|
966
|
+
submitted = true;
|
|
967
|
+
return attemptId;
|
|
968
|
+
}
|
|
969
|
+
const update = await updateReservation(now2, attemptId, true, qty);
|
|
947
970
|
if (!update.result)
|
|
948
971
|
throw new Error("PORTFOLIO_SUBMISSION_NO_LONGER_ADMITTED");
|
|
972
|
+
submitted = true;
|
|
973
|
+
return attemptId;
|
|
949
974
|
},
|
|
950
|
-
async () => {
|
|
975
|
+
async (retrying = false) => {
|
|
951
976
|
await transact(
|
|
952
977
|
now2,
|
|
953
978
|
(s) => ({
|
|
954
979
|
state: {
|
|
955
980
|
...s,
|
|
956
|
-
reservations: s.reservations.filter(
|
|
981
|
+
reservations: s.reservations.filter(
|
|
982
|
+
(r) => r.id !== attemptId
|
|
983
|
+
),
|
|
957
984
|
completedSignals: {
|
|
958
|
-
...
|
|
959
|
-
|
|
985
|
+
...Object.fromEntries(
|
|
986
|
+
Object.entries(s.completedSignals).filter(
|
|
987
|
+
([id]) => !retrying || id !== entry.signalId
|
|
988
|
+
)
|
|
989
|
+
),
|
|
990
|
+
...retrying ? {} : { [entry.signalId]: now2 }
|
|
960
991
|
}
|
|
961
992
|
},
|
|
962
993
|
result: void 0
|
|
963
994
|
}),
|
|
964
995
|
true
|
|
965
996
|
);
|
|
997
|
+
submitted = false;
|
|
998
|
+
needsReservation = retrying;
|
|
966
999
|
}
|
|
967
1000
|
);
|
|
968
|
-
if (!submitted) await updateReservation(now2,
|
|
1001
|
+
if (!submitted) await updateReservation(now2, attemptId, false);
|
|
969
1002
|
else await tick(now2, true);
|
|
970
1003
|
return { value };
|
|
971
1004
|
} catch (error) {
|
|
972
|
-
if (!submitted) await updateReservation(now2,
|
|
1005
|
+
if (!submitted) await updateReservation(now2, attemptId, false);
|
|
973
1006
|
throw error;
|
|
974
1007
|
}
|
|
975
1008
|
}
|
|
@@ -1337,7 +1370,9 @@ function getPortfolioRuntime({
|
|
|
1337
1370
|
env,
|
|
1338
1371
|
userName,
|
|
1339
1372
|
accountId,
|
|
1340
|
-
deploymentId
|
|
1373
|
+
deploymentId,
|
|
1374
|
+
symbol,
|
|
1375
|
+
canClose
|
|
1341
1376
|
}) {
|
|
1342
1377
|
const policy = (0, import_portfolio2.parsePortfolioPolicy)(value);
|
|
1343
1378
|
if (!policy) return void 0;
|
|
@@ -1368,8 +1403,13 @@ function getPortfolioRuntime({
|
|
|
1368
1403
|
if (existing) {
|
|
1369
1404
|
if (existing.fingerprint !== fingerprint3)
|
|
1370
1405
|
throw new Error("Conflicting portfolio configuration for the same scope");
|
|
1406
|
+
if (symbol && canClose)
|
|
1407
|
+
existing.permissions.set(JSON.stringify([strategy, symbol]), canClose);
|
|
1371
1408
|
return existing.coordinator;
|
|
1372
1409
|
}
|
|
1410
|
+
const permissions = /* @__PURE__ */ new Map();
|
|
1411
|
+
if (symbol && canClose)
|
|
1412
|
+
permissions.set(JSON.stringify([strategy, symbol]), canClose);
|
|
1373
1413
|
const coordinator = createPortfolioCoordinator({
|
|
1374
1414
|
policy,
|
|
1375
1415
|
strategy,
|
|
@@ -1390,6 +1430,10 @@ function getPortfolioRuntime({
|
|
|
1390
1430
|
},
|
|
1391
1431
|
observe: (asOf, reservations) => replay ? connector.getPortfolioObservation(asOf, reservations) : observeRuntimePortfolio(connector, binding, asOf, reservations),
|
|
1392
1432
|
async close(position, intent, asOf) {
|
|
1433
|
+
const permission = permissions.get(
|
|
1434
|
+
JSON.stringify([position.strategy, position.symbol])
|
|
1435
|
+
);
|
|
1436
|
+
if (!permission || !await permission(position, intent, asOf)) return;
|
|
1393
1437
|
if (!replay) {
|
|
1394
1438
|
const trade = await getActiveRuntimeTrade({
|
|
1395
1439
|
...binding,
|
|
@@ -1424,7 +1468,7 @@ function getPortfolioRuntime({
|
|
|
1424
1468
|
}
|
|
1425
1469
|
}
|
|
1426
1470
|
});
|
|
1427
|
-
registry.set(cacheKey, { fingerprint: fingerprint3, coordinator });
|
|
1471
|
+
registry.set(cacheKey, { fingerprint: fingerprint3, coordinator, permissions });
|
|
1428
1472
|
return coordinator;
|
|
1429
1473
|
}
|
|
1430
1474
|
|
|
@@ -6241,7 +6285,7 @@ var executeEntryOrder = async ({
|
|
|
6241
6285
|
}) => {
|
|
6242
6286
|
await beforePlaceOrder?.();
|
|
6243
6287
|
const previousPosition = positionIntent === "increase" ? await connector.getPosition(symbol) : null;
|
|
6244
|
-
|
|
6288
|
+
let orderId = signal.orderId || createRuntimeOrderId(signal.strategy);
|
|
6245
6289
|
const signalTimestamp = signal.timestamp;
|
|
6246
6290
|
const signalClosePrice = currentPrice;
|
|
6247
6291
|
signal.orderId = orderId;
|
|
@@ -6314,11 +6358,12 @@ var executeEntryOrder = async ({
|
|
|
6314
6358
|
...beforeSubmit || markPortfolioSubmitted ? {
|
|
6315
6359
|
beforeSubmit: async (context) => {
|
|
6316
6360
|
await beforeSubmit?.(context);
|
|
6317
|
-
|
|
6361
|
+
return markPortfolioSubmitted?.(context.qty);
|
|
6318
6362
|
}
|
|
6319
6363
|
} : {}
|
|
6320
6364
|
});
|
|
6321
6365
|
const orderAckTime = Date.now();
|
|
6366
|
+
if (markPortfolioSubmitted && signal.orderId) orderId = signal.orderId;
|
|
6322
6367
|
const placedQty = typeof signal.orderQty === "number" && Number.isFinite(signal.orderQty) && signal.orderQty > 0 ? signal.orderQty : qty;
|
|
6323
6368
|
const currentPosition = await connector.getPosition(symbol);
|
|
6324
6369
|
const fillTime = Date.now();
|
|
@@ -7609,7 +7654,27 @@ var createStrategyRuntime = ({
|
|
|
7609
7654
|
const projectConfig = await loadTradejsConfig(projectRoot);
|
|
7610
7655
|
const projectHooks = projectConfig.hooks;
|
|
7611
7656
|
const env = String(config.ENV ?? "BACKTEST");
|
|
7657
|
+
let portfolioMarket;
|
|
7612
7658
|
const portfolio = getPortfolioRuntime({
|
|
7659
|
+
symbol,
|
|
7660
|
+
canClose: async (position, intent, asOf) => {
|
|
7661
|
+
if (!portfolioMarket || portfolioMarket.candle.timestamp > asOf || !isRuntimeOrderExecutionEnabled({ config, env, connector }))
|
|
7662
|
+
return false;
|
|
7663
|
+
const gate = await authorizeClose(
|
|
7664
|
+
{
|
|
7665
|
+
kind: "exit",
|
|
7666
|
+
code: intent.reason,
|
|
7667
|
+
closePlan: {
|
|
7668
|
+
direction: position.direction,
|
|
7669
|
+
price: position.markPrice,
|
|
7670
|
+
timestamp: asOf
|
|
7671
|
+
}
|
|
7672
|
+
},
|
|
7673
|
+
portfolioMarket,
|
|
7674
|
+
position.strategy
|
|
7675
|
+
);
|
|
7676
|
+
return gate?.allow !== false;
|
|
7677
|
+
},
|
|
7613
7678
|
connectorName,
|
|
7614
7679
|
value: baseConfig?.PORTFOLIO_POLICY ?? config.PORTFOLIO_POLICY,
|
|
7615
7680
|
strategy: strategyName,
|
|
@@ -7784,7 +7849,10 @@ var createStrategyRuntime = ({
|
|
|
7784
7849
|
return invokeHook(stage, hook, params, errorContext);
|
|
7785
7850
|
};
|
|
7786
7851
|
const invokeGateHooks = async (stage, hook, params, errorContext = {}) => {
|
|
7787
|
-
const failureResult =
|
|
7852
|
+
const failureResult = {
|
|
7853
|
+
allow: false,
|
|
7854
|
+
reason: "ERROR"
|
|
7855
|
+
};
|
|
7788
7856
|
const projectResults = await invokeProjectHooks(
|
|
7789
7857
|
stage,
|
|
7790
7858
|
params,
|
|
@@ -7805,6 +7873,12 @@ var createStrategyRuntime = ({
|
|
|
7805
7873
|
failureResult
|
|
7806
7874
|
);
|
|
7807
7875
|
};
|
|
7876
|
+
const authorizeClose = (decision, market, owner = strategyName) => invokeGateHooks(
|
|
7877
|
+
"beforeClosePosition",
|
|
7878
|
+
resolveManifest(owner)?.hooks?.beforeClosePosition,
|
|
7879
|
+
{ ctx: getHookCtx(owner), market, decision },
|
|
7880
|
+
{ decision, market }
|
|
7881
|
+
);
|
|
7808
7882
|
const applyProjectAfterCoreDecisionHooks = async ({
|
|
7809
7883
|
hookCtx,
|
|
7810
7884
|
market,
|
|
@@ -8118,6 +8192,7 @@ var createStrategyRuntime = ({
|
|
|
8118
8192
|
});
|
|
8119
8193
|
};
|
|
8120
8194
|
const runWithDecisionOverride = async (candle, btcCandle, options = {}) => {
|
|
8195
|
+
portfolioMarket = { candle, btcCandle };
|
|
8121
8196
|
if (portfolioContext)
|
|
8122
8197
|
await portfolioContext(
|
|
8123
8198
|
env === "BACKTEST" || env === "PARITY" ? candle.timestamp : Date.now()
|
|
@@ -8224,15 +8299,10 @@ var createStrategyRuntime = ({
|
|
|
8224
8299
|
if (!makeOrdersEnabled) {
|
|
8225
8300
|
return decision.code;
|
|
8226
8301
|
}
|
|
8227
|
-
const closeGate = await
|
|
8228
|
-
|
|
8229
|
-
|
|
8230
|
-
|
|
8231
|
-
ctx: decisionHookCtx,
|
|
8232
|
-
market,
|
|
8233
|
-
decision
|
|
8234
|
-
},
|
|
8235
|
-
{ decision, market }
|
|
8302
|
+
const closeGate = await authorizeClose(
|
|
8303
|
+
decision,
|
|
8304
|
+
market,
|
|
8305
|
+
decisionHookCtx.strategyName
|
|
8236
8306
|
);
|
|
8237
8307
|
if (closeGate?.allow === false) {
|
|
8238
8308
|
return closeGate.reason ? `CLOSE_BLOCKED_BY_HOOK:${closeGate.reason}` : "CLOSE_BLOCKED_BY_HOOK";
|
package/dist/backtest.mjs
CHANGED
|
@@ -10,8 +10,8 @@ import {
|
|
|
10
10
|
evaluateResearchEntryPolicy,
|
|
11
11
|
matchesResearchEntryRules,
|
|
12
12
|
parseResearchEntryPolicy
|
|
13
|
-
} from "./chunk-
|
|
14
|
-
import "./chunk-
|
|
13
|
+
} from "./chunk-EVKQ4DI6.mjs";
|
|
14
|
+
import "./chunk-NJEYNGRU.mjs";
|
|
15
15
|
import {
|
|
16
16
|
buildAiPayload,
|
|
17
17
|
getStrategyCreator
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
createMemoryPortfolioStore,
|
|
3
3
|
createPortfolioCoordinator
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-NJEYNGRU.mjs";
|
|
5
5
|
import {
|
|
6
6
|
askAI,
|
|
7
7
|
buildAiPayload,
|
|
@@ -3237,7 +3237,7 @@ var executeEntryOrder = async ({
|
|
|
3237
3237
|
}) => {
|
|
3238
3238
|
await beforePlaceOrder?.();
|
|
3239
3239
|
const previousPosition = positionIntent === "increase" ? await connector.getPosition(symbol) : null;
|
|
3240
|
-
|
|
3240
|
+
let orderId = signal.orderId || createRuntimeOrderId(signal.strategy);
|
|
3241
3241
|
const signalTimestamp = signal.timestamp;
|
|
3242
3242
|
const signalClosePrice = currentPrice;
|
|
3243
3243
|
signal.orderId = orderId;
|
|
@@ -3310,11 +3310,12 @@ var executeEntryOrder = async ({
|
|
|
3310
3310
|
...beforeSubmit || markPortfolioSubmitted ? {
|
|
3311
3311
|
beforeSubmit: async (context) => {
|
|
3312
3312
|
await beforeSubmit?.(context);
|
|
3313
|
-
|
|
3313
|
+
return markPortfolioSubmitted?.(context.qty);
|
|
3314
3314
|
}
|
|
3315
3315
|
} : {}
|
|
3316
3316
|
});
|
|
3317
3317
|
const orderAckTime = Date.now();
|
|
3318
|
+
if (markPortfolioSubmitted && signal.orderId) orderId = signal.orderId;
|
|
3318
3319
|
const placedQty = typeof signal.orderQty === "number" && Number.isFinite(signal.orderQty) && signal.orderQty > 0 ? signal.orderQty : qty;
|
|
3319
3320
|
const currentPosition = await connector.getPosition(symbol);
|
|
3320
3321
|
const fillTime = Date.now();
|
|
@@ -3681,7 +3682,9 @@ function getPortfolioRuntime({
|
|
|
3681
3682
|
env,
|
|
3682
3683
|
userName,
|
|
3683
3684
|
accountId,
|
|
3684
|
-
deploymentId
|
|
3685
|
+
deploymentId,
|
|
3686
|
+
symbol,
|
|
3687
|
+
canClose
|
|
3685
3688
|
}) {
|
|
3686
3689
|
const policy = parsePortfolioPolicy(value);
|
|
3687
3690
|
if (!policy) return void 0;
|
|
@@ -3712,8 +3715,13 @@ function getPortfolioRuntime({
|
|
|
3712
3715
|
if (existing) {
|
|
3713
3716
|
if (existing.fingerprint !== fingerprint3)
|
|
3714
3717
|
throw new Error("Conflicting portfolio configuration for the same scope");
|
|
3718
|
+
if (symbol && canClose)
|
|
3719
|
+
existing.permissions.set(JSON.stringify([strategy, symbol]), canClose);
|
|
3715
3720
|
return existing.coordinator;
|
|
3716
3721
|
}
|
|
3722
|
+
const permissions = /* @__PURE__ */ new Map();
|
|
3723
|
+
if (symbol && canClose)
|
|
3724
|
+
permissions.set(JSON.stringify([strategy, symbol]), canClose);
|
|
3717
3725
|
const coordinator = createPortfolioCoordinator({
|
|
3718
3726
|
policy,
|
|
3719
3727
|
strategy,
|
|
@@ -3734,6 +3742,10 @@ function getPortfolioRuntime({
|
|
|
3734
3742
|
},
|
|
3735
3743
|
observe: (asOf, reservations) => replay ? connector.getPortfolioObservation(asOf, reservations) : observeRuntimePortfolio(connector, binding, asOf, reservations),
|
|
3736
3744
|
async close(position, intent, asOf) {
|
|
3745
|
+
const permission = permissions.get(
|
|
3746
|
+
JSON.stringify([position.strategy, position.symbol])
|
|
3747
|
+
);
|
|
3748
|
+
if (!permission || !await permission(position, intent, asOf)) return;
|
|
3737
3749
|
if (!replay) {
|
|
3738
3750
|
const trade = await getActiveRuntimeTrade({
|
|
3739
3751
|
...binding,
|
|
@@ -3768,7 +3780,7 @@ function getPortfolioRuntime({
|
|
|
3768
3780
|
}
|
|
3769
3781
|
}
|
|
3770
3782
|
});
|
|
3771
|
-
registry.set(cacheKey, { fingerprint: fingerprint3, coordinator });
|
|
3783
|
+
registry.set(cacheKey, { fingerprint: fingerprint3, coordinator, permissions });
|
|
3772
3784
|
return coordinator;
|
|
3773
3785
|
}
|
|
3774
3786
|
|
|
@@ -4809,7 +4821,27 @@ var createStrategyRuntime = ({
|
|
|
4809
4821
|
const projectConfig = await loadTradejsConfig(projectRoot);
|
|
4810
4822
|
const projectHooks = projectConfig.hooks;
|
|
4811
4823
|
const env = String(config.ENV ?? "BACKTEST");
|
|
4824
|
+
let portfolioMarket;
|
|
4812
4825
|
const portfolio = getPortfolioRuntime({
|
|
4826
|
+
symbol,
|
|
4827
|
+
canClose: async (position, intent, asOf) => {
|
|
4828
|
+
if (!portfolioMarket || portfolioMarket.candle.timestamp > asOf || !isRuntimeOrderExecutionEnabled({ config, env, connector }))
|
|
4829
|
+
return false;
|
|
4830
|
+
const gate = await authorizeClose(
|
|
4831
|
+
{
|
|
4832
|
+
kind: "exit",
|
|
4833
|
+
code: intent.reason,
|
|
4834
|
+
closePlan: {
|
|
4835
|
+
direction: position.direction,
|
|
4836
|
+
price: position.markPrice,
|
|
4837
|
+
timestamp: asOf
|
|
4838
|
+
}
|
|
4839
|
+
},
|
|
4840
|
+
portfolioMarket,
|
|
4841
|
+
position.strategy
|
|
4842
|
+
);
|
|
4843
|
+
return gate?.allow !== false;
|
|
4844
|
+
},
|
|
4813
4845
|
connectorName,
|
|
4814
4846
|
value: baseConfig?.PORTFOLIO_POLICY ?? config.PORTFOLIO_POLICY,
|
|
4815
4847
|
strategy: strategyName,
|
|
@@ -4984,7 +5016,10 @@ var createStrategyRuntime = ({
|
|
|
4984
5016
|
return invokeHook(stage, hook, params, errorContext);
|
|
4985
5017
|
};
|
|
4986
5018
|
const invokeGateHooks = async (stage, hook, params, errorContext = {}) => {
|
|
4987
|
-
const failureResult =
|
|
5019
|
+
const failureResult = {
|
|
5020
|
+
allow: false,
|
|
5021
|
+
reason: "ERROR"
|
|
5022
|
+
};
|
|
4988
5023
|
const projectResults = await invokeProjectHooks(
|
|
4989
5024
|
stage,
|
|
4990
5025
|
params,
|
|
@@ -5005,6 +5040,12 @@ var createStrategyRuntime = ({
|
|
|
5005
5040
|
failureResult
|
|
5006
5041
|
);
|
|
5007
5042
|
};
|
|
5043
|
+
const authorizeClose = (decision, market, owner = strategyName) => invokeGateHooks(
|
|
5044
|
+
"beforeClosePosition",
|
|
5045
|
+
resolveManifest(owner)?.hooks?.beforeClosePosition,
|
|
5046
|
+
{ ctx: getHookCtx(owner), market, decision },
|
|
5047
|
+
{ decision, market }
|
|
5048
|
+
);
|
|
5008
5049
|
const applyProjectAfterCoreDecisionHooks = async ({
|
|
5009
5050
|
hookCtx,
|
|
5010
5051
|
market,
|
|
@@ -5318,6 +5359,7 @@ var createStrategyRuntime = ({
|
|
|
5318
5359
|
});
|
|
5319
5360
|
};
|
|
5320
5361
|
const runWithDecisionOverride = async (candle, btcCandle, options = {}) => {
|
|
5362
|
+
portfolioMarket = { candle, btcCandle };
|
|
5321
5363
|
if (portfolioContext)
|
|
5322
5364
|
await portfolioContext(
|
|
5323
5365
|
env === "BACKTEST" || env === "PARITY" ? candle.timestamp : Date.now()
|
|
@@ -5424,15 +5466,10 @@ var createStrategyRuntime = ({
|
|
|
5424
5466
|
if (!makeOrdersEnabled) {
|
|
5425
5467
|
return decision.code;
|
|
5426
5468
|
}
|
|
5427
|
-
const closeGate = await
|
|
5428
|
-
|
|
5429
|
-
|
|
5430
|
-
|
|
5431
|
-
ctx: decisionHookCtx,
|
|
5432
|
-
market,
|
|
5433
|
-
decision
|
|
5434
|
-
},
|
|
5435
|
-
{ decision, market }
|
|
5469
|
+
const closeGate = await authorizeClose(
|
|
5470
|
+
decision,
|
|
5471
|
+
market,
|
|
5472
|
+
decisionHookCtx.strategyName
|
|
5436
5473
|
);
|
|
5437
5474
|
if (closeGate?.allow === false) {
|
|
5438
5475
|
return closeGate.reason ? `CLOSE_BLOCKED_BY_HOOK:${closeGate.reason}` : "CLOSE_BLOCKED_BY_HOOK";
|
|
@@ -137,39 +137,72 @@ function createPortfolioCoordinator({
|
|
|
137
137
|
);
|
|
138
138
|
if (admitted.result) return { reason: admitted.result };
|
|
139
139
|
let submitted = false;
|
|
140
|
+
let retry = 0;
|
|
141
|
+
let attemptId = entry.id;
|
|
142
|
+
let needsReservation = false;
|
|
140
143
|
try {
|
|
141
144
|
const value = await send(
|
|
142
145
|
async (qty) => {
|
|
143
146
|
if (!Number.isFinite(qty) || qty <= 0)
|
|
144
147
|
throw new Error("Invalid submitted portfolio quantity");
|
|
145
|
-
|
|
146
|
-
|
|
148
|
+
if (needsReservation) {
|
|
149
|
+
attemptId = `${entry.id.slice(0, 30)}r${++retry}`;
|
|
150
|
+
const admission = await transact(
|
|
151
|
+
now,
|
|
152
|
+
(s, o) => {
|
|
153
|
+
const r = reservePortfolioEntry(policy, strategy, s, o, {
|
|
154
|
+
...entry,
|
|
155
|
+
id: attemptId,
|
|
156
|
+
qty,
|
|
157
|
+
submitted: true,
|
|
158
|
+
oppositeHandled: false
|
|
159
|
+
});
|
|
160
|
+
return { state: r.state, result: r.reason };
|
|
161
|
+
},
|
|
162
|
+
true
|
|
163
|
+
);
|
|
164
|
+
if (admission.result) throw new Error(admission.result);
|
|
165
|
+
needsReservation = false;
|
|
166
|
+
submitted = true;
|
|
167
|
+
return attemptId;
|
|
168
|
+
}
|
|
169
|
+
const update = await updateReservation(now, attemptId, true, qty);
|
|
147
170
|
if (!update.result)
|
|
148
171
|
throw new Error("PORTFOLIO_SUBMISSION_NO_LONGER_ADMITTED");
|
|
172
|
+
submitted = true;
|
|
173
|
+
return attemptId;
|
|
149
174
|
},
|
|
150
|
-
async () => {
|
|
175
|
+
async (retrying = false) => {
|
|
151
176
|
await transact(
|
|
152
177
|
now,
|
|
153
178
|
(s) => ({
|
|
154
179
|
state: {
|
|
155
180
|
...s,
|
|
156
|
-
reservations: s.reservations.filter(
|
|
181
|
+
reservations: s.reservations.filter(
|
|
182
|
+
(r) => r.id !== attemptId
|
|
183
|
+
),
|
|
157
184
|
completedSignals: {
|
|
158
|
-
...
|
|
159
|
-
|
|
185
|
+
...Object.fromEntries(
|
|
186
|
+
Object.entries(s.completedSignals).filter(
|
|
187
|
+
([id]) => !retrying || id !== entry.signalId
|
|
188
|
+
)
|
|
189
|
+
),
|
|
190
|
+
...retrying ? {} : { [entry.signalId]: now }
|
|
160
191
|
}
|
|
161
192
|
},
|
|
162
193
|
result: void 0
|
|
163
194
|
}),
|
|
164
195
|
true
|
|
165
196
|
);
|
|
197
|
+
submitted = false;
|
|
198
|
+
needsReservation = retrying;
|
|
166
199
|
}
|
|
167
200
|
);
|
|
168
|
-
if (!submitted) await updateReservation(now,
|
|
201
|
+
if (!submitted) await updateReservation(now, attemptId, false);
|
|
169
202
|
else await tick(now, true);
|
|
170
203
|
return { value };
|
|
171
204
|
} catch (error) {
|
|
172
|
-
if (!submitted) await updateReservation(now,
|
|
205
|
+
if (!submitted) await updateReservation(now, attemptId, false);
|
|
173
206
|
throw error;
|
|
174
207
|
}
|
|
175
208
|
}
|