@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.
@@ -0,0 +1,226 @@
1
+ // src/portfolio.ts
2
+ import {
3
+ buildPortfolioContext,
4
+ createPortfolioState,
5
+ portfolioFingerprint,
6
+ reconcilePortfolio,
7
+ reservePortfolioEntry,
8
+ portfolioCloseCommands,
9
+ validatePortfolioObservation
10
+ } from "@tradejs/core/portfolio";
11
+ function createPortfolioCoordinator({
12
+ policy,
13
+ strategy,
14
+ store,
15
+ execution
16
+ }) {
17
+ const fingerprint = portfolioFingerprint(policy, strategy);
18
+ let cache;
19
+ async function transact(now, change, force = false) {
20
+ for (let attempt = 0; attempt < 16; attempt++) {
21
+ const previous = await store.read();
22
+ if (previous && previous.fingerprint !== fingerprint)
23
+ throw new Error(
24
+ "Portfolio policy changed under existing state; reconcile the persisted scope before changing configuration"
25
+ );
26
+ const base = previous ?? createPortfolioState(fingerprint);
27
+ const bucket = Math.floor(now / policy.observationIntervalMs);
28
+ const observation = !force && cache?.revision === base.revision && cache.bucket === bucket ? cache.observation : await execution.observe(now, base.reservations);
29
+ const reconciled = reconcilePortfolio(
30
+ policy,
31
+ strategy,
32
+ base,
33
+ observation
34
+ );
35
+ for (const intent of base.closing) {
36
+ if (!observation.positions.some(
37
+ (p) => p.symbol === intent.position.symbol
38
+ ))
39
+ await execution.confirmClosed?.(intent, observation.asOf);
40
+ }
41
+ const changed = change(reconciled, observation);
42
+ const next = { ...changed.state, revision: base.revision + 1 };
43
+ if (await store.compare(previous, next)) {
44
+ cache = { revision: next.revision, bucket, observation };
45
+ return { state: next, observation, result: changed.result };
46
+ }
47
+ cache = void 0;
48
+ }
49
+ throw new Error("Portfolio state contention; entry was not submitted");
50
+ }
51
+ async function tick(now, force = false) {
52
+ const claimed = await transact(
53
+ now,
54
+ (state, observation) => {
55
+ const commands = portfolioCloseCommands(policy, state, observation);
56
+ return {
57
+ state: {
58
+ ...state,
59
+ closing: state.closing.map(
60
+ (c) => commands.some((x) => x.intent.positionId === c.positionId) ? { ...c, lastAttemptAt: observation.asOf } : c
61
+ )
62
+ },
63
+ result: commands
64
+ };
65
+ },
66
+ force
67
+ );
68
+ for (const command of claimed.result)
69
+ await execution.close(
70
+ command.position,
71
+ command.intent,
72
+ claimed.observation.asOf
73
+ );
74
+ if (claimed.result.length) {
75
+ cache = void 0;
76
+ const refreshed = await transact(
77
+ now,
78
+ (state) => ({ state, result: void 0 }),
79
+ true
80
+ );
81
+ return buildPortfolioContext(
82
+ policy,
83
+ strategy,
84
+ refreshed.state,
85
+ refreshed.observation
86
+ );
87
+ }
88
+ return buildPortfolioContext(
89
+ policy,
90
+ strategy,
91
+ claimed.state,
92
+ claimed.observation
93
+ );
94
+ }
95
+ async function updateReservation(now, id, submitted, qty) {
96
+ return transact(
97
+ now,
98
+ (s, o) => {
99
+ const exists = s.reservations.some((r) => r.id === id);
100
+ const blocked = submitted && (!exists || s.closing.length > 0 || o.asOf <= s.blockedThrough);
101
+ return {
102
+ state: {
103
+ ...s,
104
+ reservations: s.reservations.flatMap(
105
+ (r) => r.id !== id ? [r] : submitted && !blocked ? [{ ...r, submitted: true, qty: qty ?? r.qty }] : []
106
+ )
107
+ },
108
+ result: !blocked
109
+ };
110
+ },
111
+ true
112
+ );
113
+ }
114
+ return {
115
+ async context(now) {
116
+ const state = await store.read() ?? createPortfolioState(fingerprint);
117
+ if (state.fingerprint !== fingerprint)
118
+ throw new Error("Portfolio policy changed under existing state");
119
+ const observation = await execution.observe(now, state.reservations);
120
+ validatePortfolioObservation(observation);
121
+ return buildPortfolioContext(policy, strategy, state, observation);
122
+ },
123
+ tick,
124
+ async submit(now, entry, send) {
125
+ await tick(now);
126
+ const admitted = await transact(
127
+ now,
128
+ (s, o) => {
129
+ const r = reservePortfolioEntry(policy, strategy, s, o, {
130
+ ...entry,
131
+ submitted: false,
132
+ oppositeHandled: false
133
+ });
134
+ return { state: r.state, result: r.reason };
135
+ },
136
+ true
137
+ );
138
+ if (admitted.result) return { reason: admitted.result };
139
+ let submitted = false;
140
+ let retry = 0;
141
+ let attemptId = entry.id;
142
+ let needsReservation = false;
143
+ try {
144
+ const value = await send(
145
+ async (qty) => {
146
+ if (!Number.isFinite(qty) || qty <= 0)
147
+ throw new Error("Invalid submitted portfolio quantity");
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);
170
+ if (!update.result)
171
+ throw new Error("PORTFOLIO_SUBMISSION_NO_LONGER_ADMITTED");
172
+ submitted = true;
173
+ return attemptId;
174
+ },
175
+ async (retrying = false) => {
176
+ await transact(
177
+ now,
178
+ (s) => ({
179
+ state: {
180
+ ...s,
181
+ reservations: s.reservations.filter(
182
+ (r) => r.id !== attemptId
183
+ ),
184
+ completedSignals: {
185
+ ...Object.fromEntries(
186
+ Object.entries(s.completedSignals).filter(
187
+ ([id]) => !retrying || id !== entry.signalId
188
+ )
189
+ ),
190
+ ...retrying ? {} : { [entry.signalId]: now }
191
+ }
192
+ },
193
+ result: void 0
194
+ }),
195
+ true
196
+ );
197
+ submitted = false;
198
+ needsReservation = retrying;
199
+ }
200
+ );
201
+ if (!submitted) await updateReservation(now, attemptId, false);
202
+ else await tick(now, true);
203
+ return { value };
204
+ } catch (error) {
205
+ if (!submitted) await updateReservation(now, attemptId, false);
206
+ throw error;
207
+ }
208
+ }
209
+ };
210
+ }
211
+ function createMemoryPortfolioStore() {
212
+ let state = null;
213
+ return {
214
+ read: async () => state === null ? null : structuredClone(state),
215
+ compare: async (expected, next) => {
216
+ if ((state?.revision ?? -1) !== (expected?.revision ?? -1)) return false;
217
+ state = structuredClone(next);
218
+ return true;
219
+ }
220
+ };
221
+ }
222
+
223
+ export {
224
+ createPortfolioCoordinator,
225
+ createMemoryPortfolioStore
226
+ };
@@ -12,6 +12,16 @@ import {
12
12
  } from "@tradejs/core/aiLanguages";
13
13
  import { setData, redisKeys } from "@tradejs/infra/redis";
14
14
 
15
+ // src/strategyAdapters/ai.ts
16
+ import { getRegisteredIndicatorEntries } from "@tradejs/core/indicators";
17
+ import {
18
+ projectAiFeatures,
19
+ TICKER_PROFILE_AI_FEATURES,
20
+ PORTFOLIO_AI_FEATURES,
21
+ validateAiFeatureDefinitions,
22
+ validateAiFeatureSnapshot
23
+ } from "@tradejs/core/aiFeatures";
24
+
15
25
  // src/aiShared.ts
16
26
  var MAX_AI_SERIES_POINTS = 5;
17
27
  var COMPACT_INDICATORS_SNAPSHOT_SYMBOL = /* @__PURE__ */ Symbol.for(
@@ -235,37 +245,44 @@ var buildMarketBreadthsContextFromSignal = (signal) => {
235
245
  top100: build("top100")
236
246
  };
237
247
  };
248
+ var buildTargetVsReference = (signal, asset) => {
249
+ const base = toRecord(signal.additionalIndicators?.baseContext);
250
+ const relative = toRecord(base?.relative);
251
+ const target = toRecord(relative?.[`targetVs${asset}`]);
252
+ return {
253
+ source: target ? String(target.source ?? "") : null,
254
+ available: Boolean(target),
255
+ ratioReturn1h: toFiniteNumber(target?.ratioReturn1h),
256
+ ratioReturn4h: toFiniteNumber(target?.ratioReturn4h),
257
+ ratioReturn24h: toFiniteNumber(target?.ratioReturn24h),
258
+ alpha1h: toFiniteNumber(target?.[`alphaVs${asset}1h`]),
259
+ alpha4h: toFiniteNumber(target?.[`alphaVs${asset}4h`]),
260
+ alpha24h: toFiniteNumber(target?.[`alphaVs${asset}24h`]),
261
+ beta: toFiniteNumber(target?.[`betaTo${asset}20`]),
262
+ correlation: toFiniteNumber(target?.[`correlationTo${asset}20`]),
263
+ ratioTrend: typeof target?.ratioTrend === "string" ? target.ratioTrend : null
264
+ };
265
+ };
238
266
  var buildTargetVsBtcContextFromSignal = (signal) => {
239
- const baseContext = toRecord(signal.additionalIndicators?.baseContext);
240
- const relative = toRecord(baseContext?.relative);
241
- const targetVsBtc = toRecord(relative?.targetVsBtc);
242
- if (!targetVsBtc) {
243
- return {
244
- source: null,
245
- available: false,
246
- ratioReturn1h: null,
247
- ratioReturn4h: null,
248
- ratioReturn24h: null,
249
- alphaVsBtc1h: null,
250
- alphaVsBtc4h: null,
251
- alphaVsBtc24h: null,
252
- betaToBtc20: null,
253
- correlationToBtc20: null,
254
- ratioTrend: null
255
- };
256
- }
267
+ const { alpha1h, alpha4h, alpha24h, beta, correlation, ...shared } = buildTargetVsReference(signal, "Btc");
257
268
  return {
258
- source: String(targetVsBtc.source ?? ""),
259
- available: true,
260
- ratioReturn1h: toFiniteNumber(targetVsBtc.ratioReturn1h),
261
- ratioReturn4h: toFiniteNumber(targetVsBtc.ratioReturn4h),
262
- ratioReturn24h: toFiniteNumber(targetVsBtc.ratioReturn24h),
263
- alphaVsBtc1h: toFiniteNumber(targetVsBtc.alphaVsBtc1h),
264
- alphaVsBtc4h: toFiniteNumber(targetVsBtc.alphaVsBtc4h),
265
- alphaVsBtc24h: toFiniteNumber(targetVsBtc.alphaVsBtc24h),
266
- betaToBtc20: toFiniteNumber(targetVsBtc.betaToBtc20),
267
- correlationToBtc20: toFiniteNumber(targetVsBtc.correlationToBtc20),
268
- ratioTrend: typeof targetVsBtc.ratioTrend === "string" ? targetVsBtc.ratioTrend : null
269
+ ...shared,
270
+ alphaVsBtc1h: alpha1h,
271
+ alphaVsBtc4h: alpha4h,
272
+ alphaVsBtc24h: alpha24h,
273
+ betaToBtc20: beta,
274
+ correlationToBtc20: correlation
275
+ };
276
+ };
277
+ var buildTargetVsEthContextFromSignal = (signal) => {
278
+ const { alpha1h, alpha4h, alpha24h, beta, correlation, ...shared } = buildTargetVsReference(signal, "Eth");
279
+ return {
280
+ ...shared,
281
+ alphaVsEth1h: alpha1h,
282
+ alphaVsEth4h: alpha4h,
283
+ alphaVsEth24h: alpha24h,
284
+ betaToEth20: beta,
285
+ correlationToEth20: correlation
269
286
  };
270
287
  };
271
288
  var buildBtcAltRegimeContextFromSignal = (signal) => {
@@ -574,6 +591,7 @@ var buildAiMarketContext = (signal) => ({
574
591
  marketBreadth: buildMarketBreadthContextFromSignal(signal),
575
592
  marketBreadths: buildMarketBreadthsContextFromSignal(signal),
576
593
  targetVsBtc: buildTargetVsBtcContextFromSignal(signal),
594
+ targetVsEth: buildTargetVsEthContextFromSignal(signal),
577
595
  btcAltRegime: buildBtcAltRegimeContextFromSignal(signal),
578
596
  cmcGlobal: buildCmcGlobalContextFromSignal(signal),
579
597
  cmcReferenceAssets: buildCmcReferenceAssetsContextFromSignal(signal),
@@ -1061,12 +1079,61 @@ var buildBaseAiPayload = (signal) => {
1061
1079
  };
1062
1080
  };
1063
1081
  var defaultAiAdapter = {};
1082
+ var tickerProfileFeatureIds = new Set(
1083
+ TICKER_PROFILE_AI_FEATURES.map(({ id }) => id)
1084
+ );
1064
1085
  var getStrategyAiAdapter = (strategy, profileId) => getStrategyProfileAiAdapter(getStrategyManifest(strategy), profileId) ?? defaultAiAdapter;
1065
1086
  var getSignalAiAdapter = (signal) => getStrategyAiAdapter(signal.strategy, signal.policyProfileId);
1066
1087
  var buildAiPayloadByStrategy = (signal) => {
1067
1088
  const basePayload = buildBaseAiPayload(signal);
1068
1089
  const adapter = getSignalAiAdapter(signal);
1069
- return adapter.buildPayload?.({ signal, basePayload }) ?? basePayload;
1090
+ const payload = adapter.buildPayload?.({ signal, basePayload }) ?? basePayload;
1091
+ const custom = getStrategyManifest(signal.strategy)?.aiFeatures ?? [];
1092
+ const frozen = signal.aiFeatureReplay?.snapshot;
1093
+ if (frozen) validateAiFeatureSnapshot(frozen);
1094
+ const tickerProfile = signal.additionalIndicators?.tickerProfile ?? signal.additionalIndicators?.baseContext?.tickerProfile;
1095
+ const hasProfile = Boolean(
1096
+ tickerProfile || frozen?.definitions.some(({ id }) => tickerProfileFeatureIds.has(id))
1097
+ );
1098
+ const indicatorFeatures = getRegisteredIndicatorEntries(
1099
+ getTradejsProjectCwd()
1100
+ ).flatMap((entry) => entry.indicator.enabled ? entry.aiFeatures ?? [] : []);
1101
+ const hasPortfolio = Boolean(
1102
+ signal.additionalIndicators?.portfolioContext || frozen?.definitions.some(
1103
+ ({ id }) => PORTFOLIO_AI_FEATURES.some((d) => d.id === id)
1104
+ )
1105
+ );
1106
+ const definitions = [
1107
+ ...hasPortfolio ? PORTFOLIO_AI_FEATURES : [],
1108
+ ...hasProfile ? TICKER_PROFILE_AI_FEATURES : [],
1109
+ ...indicatorFeatures,
1110
+ ...custom
1111
+ ];
1112
+ if (!definitions.length && !signal.aiFeatureReplay?.snapshot) return payload;
1113
+ validateAiFeatureDefinitions(definitions);
1114
+ if (signal.aiFeatureReplay && !frozen) {
1115
+ throw new Error(
1116
+ "Dataset has no frozen AI features; regenerate the decision-time export"
1117
+ );
1118
+ }
1119
+ if (frozen && JSON.stringify(frozen.definitions) !== JSON.stringify(definitions)) {
1120
+ throw new Error(
1121
+ "AI feature definition changed; regenerate the decision-time export"
1122
+ );
1123
+ }
1124
+ return {
1125
+ ...payload,
1126
+ featureSnapshot: frozen ?? projectAiFeatures(definitions, {
1127
+ signal: tickerProfile ? {
1128
+ ...signal,
1129
+ additionalIndicators: {
1130
+ ...signal.additionalIndicators,
1131
+ tickerProfile
1132
+ }
1133
+ } : signal,
1134
+ payload
1135
+ })
1136
+ };
1070
1137
  };
1071
1138
  var buildAiSystemPromptAddonByStrategy = (signal) => getSignalAiAdapter(signal).buildSystemPromptAddon?.({ signal }) ?? "";
1072
1139
  var buildAiHumanPromptAddonByStrategy = (signal, payload) => getSignalAiAdapter(signal).buildHumanPromptAddon?.({
@@ -2,7 +2,7 @@ import {
2
2
  getStrategyCreator,
3
3
  getStrategyEntry,
4
4
  getStrategyPluginSource
5
- } from "./chunk-UL5Z46JL.mjs";
5
+ } from "./chunk-PYJWXAYR.mjs";
6
6
  import {
7
7
  loadTradejsConfig
8
8
  } from "./chunk-LZDXRXIU.mjs";