@tradejs/node 3.0.1 → 3.1.1
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/README.md +4 -0
- package/dist/ai.d.mts +26 -4
- package/dist/ai.d.ts +26 -4
- package/dist/ai.js +162 -165
- package/dist/ai.mjs +4 -1
- package/dist/backtest.js +515 -188
- package/dist/backtest.mjs +6 -4
- package/dist/chunk-3TWULKHV.mjs +595 -0
- package/dist/chunk-FB5NUEOQ.mjs +295 -0
- package/dist/chunk-LAJ7NA3Q.mjs +377 -0
- package/dist/{chunk-VRXU4E4O.mjs → chunk-SEQJ6V6B.mjs} +165 -446
- package/dist/{chunk-OGAWBO3Z.mjs → chunk-W26Y6IRP.mjs} +22 -3
- package/dist/chunk-XN7BC7XK.mjs +295 -0
- package/dist/cli.js +152 -161
- package/dist/cli.mjs +4 -2
- package/dist/connectors.d.mts +5 -2
- package/dist/connectors.d.ts +5 -2
- package/dist/connectors.js +329 -8
- package/dist/connectors.mjs +5 -1
- package/dist/registry-DHTLjQcr.d.mts +17 -0
- package/dist/registry-DHTLjQcr.d.ts +17 -0
- package/dist/registry.d.mts +2 -15
- package/dist/registry.d.ts +2 -15
- package/dist/registry.js +176 -161
- package/dist/registry.mjs +5 -2
- package/dist/runtimeDashboard.d.mts +12 -0
- package/dist/runtimeDashboard.d.ts +12 -0
- package/dist/runtimeDashboard.js +8502 -0
- package/dist/runtimeDashboard.mjs +1000 -0
- package/dist/runtimeStrategies.d.mts +38 -0
- package/dist/runtimeStrategies.d.ts +38 -0
- package/dist/runtimeStrategies.js +798 -0
- package/dist/runtimeStrategies.mjs +264 -0
- package/dist/runtimeTrades.d.mts +31 -0
- package/dist/runtimeTrades.d.ts +31 -0
- package/dist/runtimeTrades.js +321 -0
- package/dist/runtimeTrades.mjs +11 -0
- package/dist/strategies.d.mts +2 -2
- package/dist/strategies.d.ts +2 -2
- package/dist/strategies.js +194 -165
- package/dist/strategies.mjs +24 -379
- package/package.json +27 -6
- package/dist/chunk-V3YMKE4I.mjs +0 -271
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
import {
|
|
2
|
+
getStrategyCreator,
|
|
3
|
+
getStrategyPluginSource
|
|
4
|
+
} from "./chunk-XN7BC7XK.mjs";
|
|
5
|
+
import "./chunk-WS5DYEVZ.mjs";
|
|
6
|
+
import "./chunk-Y6FXYEAI.mjs";
|
|
7
|
+
|
|
8
|
+
// src/runtimeStrategies.ts
|
|
9
|
+
import { readFile } from "fs/promises";
|
|
10
|
+
import path from "path";
|
|
11
|
+
import { getData, redisKeys } from "@tradejs/infra/redis";
|
|
12
|
+
import { getRuntimeStrategyRelease } from "@tradejs/infra/runtimeStrategyReleases";
|
|
13
|
+
import { loadRuntimeStrategyConfigs } from "@tradejs/infra/runtimeStrategyConfigs";
|
|
14
|
+
import { resolveTradingAccount } from "@tradejs/infra/tradingAccounts";
|
|
15
|
+
var readPackageManifest = async (projectRoot) => {
|
|
16
|
+
const candidates = [
|
|
17
|
+
process.env.TRADEJS_RUNTIME_PACKAGE_MANIFEST,
|
|
18
|
+
path.join(projectRoot, "runtime-package-manifest.json"),
|
|
19
|
+
"/app/runtime-package-manifest.json"
|
|
20
|
+
].filter((candidate) => Boolean(candidate));
|
|
21
|
+
for (const candidate of candidates) {
|
|
22
|
+
try {
|
|
23
|
+
return JSON.parse(
|
|
24
|
+
await readFile(candidate, "utf8")
|
|
25
|
+
);
|
|
26
|
+
} catch {
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
return { packages: {} };
|
|
30
|
+
};
|
|
31
|
+
var resolveInstalledPackageVersion = async (projectRoot, packageName, manifest) => {
|
|
32
|
+
if (!packageName || packageName === "runtime") return null;
|
|
33
|
+
const manifestVersion = manifest.packages?.[packageName];
|
|
34
|
+
if (manifestVersion) return manifestVersion;
|
|
35
|
+
try {
|
|
36
|
+
const packageJsonPath = path.join(
|
|
37
|
+
projectRoot,
|
|
38
|
+
"node_modules",
|
|
39
|
+
...packageName.split("/"),
|
|
40
|
+
"package.json"
|
|
41
|
+
);
|
|
42
|
+
const packageJson = JSON.parse(await readFile(packageJsonPath, "utf8"));
|
|
43
|
+
return packageJson.version ?? null;
|
|
44
|
+
} catch {
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
var validateReleaseRuntimeCompatibility = async ({
|
|
49
|
+
release,
|
|
50
|
+
projectRoot,
|
|
51
|
+
packageManifest
|
|
52
|
+
}) => {
|
|
53
|
+
const installedStrategyVersion = await resolveInstalledPackageVersion(
|
|
54
|
+
projectRoot,
|
|
55
|
+
release.strategyPackage,
|
|
56
|
+
packageManifest
|
|
57
|
+
);
|
|
58
|
+
if (release.strategyPackageVersion && installedStrategyVersion && release.strategyPackageVersion !== installedStrategyVersion) {
|
|
59
|
+
throw new Error(
|
|
60
|
+
`${release.strategyName} v${release.releaseVersion} requires ${release.strategyPackage}@${release.strategyPackageVersion}, image has ${installedStrategyVersion}`
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
const installedRuntimeVersion = packageManifest.packages?.["@tradejs/node"];
|
|
64
|
+
if (release.runtimePackageVersion && installedRuntimeVersion && release.runtimePackageVersion !== installedRuntimeVersion) {
|
|
65
|
+
throw new Error(
|
|
66
|
+
`${release.strategyName} v${release.releaseVersion} requires @tradejs/node@${release.runtimePackageVersion}, image has ${installedRuntimeVersion}`
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
var resolveAccountId = async ({
|
|
71
|
+
userName,
|
|
72
|
+
deployment,
|
|
73
|
+
connectorName,
|
|
74
|
+
universe,
|
|
75
|
+
legacyAccountId
|
|
76
|
+
}) => {
|
|
77
|
+
const requestedAccountId = deployment?.accountId ?? legacyAccountId;
|
|
78
|
+
const account = await resolveTradingAccount({
|
|
79
|
+
userName,
|
|
80
|
+
accountId: requestedAccountId,
|
|
81
|
+
provider: deployment?.provider ?? connectorName,
|
|
82
|
+
universe
|
|
83
|
+
});
|
|
84
|
+
return account?.id ?? requestedAccountId;
|
|
85
|
+
};
|
|
86
|
+
var loadVersionedRuntimeStrategies = async ({
|
|
87
|
+
userName,
|
|
88
|
+
projectRoot,
|
|
89
|
+
deployment,
|
|
90
|
+
connectorName
|
|
91
|
+
}) => {
|
|
92
|
+
const packageManifest = await readPackageManifest(projectRoot);
|
|
93
|
+
return Promise.all(
|
|
94
|
+
deployment.strategies.map(async (reference) => {
|
|
95
|
+
if (!Number.isSafeInteger(reference.releaseVersion) || !reference.releaseVersion) {
|
|
96
|
+
throw new Error(
|
|
97
|
+
`Deployment ${deployment.id} strategy ${reference.strategyName} has no releaseVersion`
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
if (reference.config && Object.keys(reference.config).length) {
|
|
101
|
+
throw new Error(
|
|
102
|
+
`Deployment ${deployment.id} must not embed config for ${reference.strategyName}`
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
const release = await getRuntimeStrategyRelease(
|
|
106
|
+
userName,
|
|
107
|
+
reference.strategyName,
|
|
108
|
+
reference.releaseVersion
|
|
109
|
+
);
|
|
110
|
+
if (!release) {
|
|
111
|
+
throw new Error(
|
|
112
|
+
`Runtime release not found: ${reference.strategyName} v${reference.releaseVersion}`
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
await validateReleaseRuntimeCompatibility({
|
|
116
|
+
release,
|
|
117
|
+
projectRoot,
|
|
118
|
+
packageManifest
|
|
119
|
+
});
|
|
120
|
+
const strategyCreator = await getStrategyCreator(
|
|
121
|
+
reference.strategyName,
|
|
122
|
+
projectRoot
|
|
123
|
+
);
|
|
124
|
+
if (!strategyCreator) {
|
|
125
|
+
throw new Error(`Unknown strategy: ${reference.strategyName}`);
|
|
126
|
+
}
|
|
127
|
+
const interval = String(release.config.INTERVAL);
|
|
128
|
+
const universe = release.config.UNIVERSE;
|
|
129
|
+
const accountId = await resolveAccountId({
|
|
130
|
+
userName,
|
|
131
|
+
deployment,
|
|
132
|
+
connectorName,
|
|
133
|
+
universe
|
|
134
|
+
});
|
|
135
|
+
return {
|
|
136
|
+
strategyName: reference.strategyName,
|
|
137
|
+
releaseVersion: release.releaseVersion,
|
|
138
|
+
controlState: reference.controlState ?? "active",
|
|
139
|
+
interval,
|
|
140
|
+
universe,
|
|
141
|
+
accountId,
|
|
142
|
+
strategyPackage: release.strategyPackage,
|
|
143
|
+
strategyPackageVersion: release.strategyPackageVersion,
|
|
144
|
+
runtimePackageVersion: release.runtimePackageVersion,
|
|
145
|
+
strategyCreator,
|
|
146
|
+
sourceStrategyConfig: release.config,
|
|
147
|
+
strategyConfig: release.config,
|
|
148
|
+
// Symbol result configs are mutable legacy overlays and are not read by v2.
|
|
149
|
+
strategyResults: {}
|
|
150
|
+
};
|
|
151
|
+
})
|
|
152
|
+
);
|
|
153
|
+
};
|
|
154
|
+
var loadLegacyRuntimeStrategies = async ({
|
|
155
|
+
userName,
|
|
156
|
+
projectRoot,
|
|
157
|
+
deployment,
|
|
158
|
+
connectorName
|
|
159
|
+
}) => {
|
|
160
|
+
const deploymentStrategies = new Map(
|
|
161
|
+
(deployment?.strategies ?? []).map((strategy) => [
|
|
162
|
+
strategy.strategyName,
|
|
163
|
+
strategy
|
|
164
|
+
])
|
|
165
|
+
);
|
|
166
|
+
const candidates = await Promise.all(
|
|
167
|
+
(await loadRuntimeStrategyConfigs(userName)).map(async (record) => {
|
|
168
|
+
const binding = deploymentStrategies.get(record.strategyName);
|
|
169
|
+
if (binding?.enabled === false || record.strategyConfig.ENABLE === false) {
|
|
170
|
+
return null;
|
|
171
|
+
}
|
|
172
|
+
const universe = record.strategyConfig.UNIVERSE === "tradfi" ? "tradfi" : "crypto";
|
|
173
|
+
const interval = String(
|
|
174
|
+
record.strategyConfig.INTERVAL ?? "15"
|
|
175
|
+
);
|
|
176
|
+
const accountId = await resolveAccountId({
|
|
177
|
+
userName,
|
|
178
|
+
deployment,
|
|
179
|
+
connectorName,
|
|
180
|
+
universe,
|
|
181
|
+
legacyAccountId: typeof record.strategyConfig.ACCOUNT_ID === "string" ? record.strategyConfig.ACCOUNT_ID : void 0
|
|
182
|
+
});
|
|
183
|
+
const [strategyCreator, strategyResults] = await Promise.all([
|
|
184
|
+
getStrategyCreator(record.strategyName, projectRoot),
|
|
185
|
+
getData(redisKeys.strategyResults(userName, record.strategyName), {})
|
|
186
|
+
]);
|
|
187
|
+
if (!strategyCreator) return null;
|
|
188
|
+
return {
|
|
189
|
+
strategyName: record.strategyName,
|
|
190
|
+
configId: record.configId,
|
|
191
|
+
controlState: "active",
|
|
192
|
+
interval,
|
|
193
|
+
universe,
|
|
194
|
+
accountId,
|
|
195
|
+
strategyCreator,
|
|
196
|
+
sourceStrategyConfig: record.strategyConfig,
|
|
197
|
+
strategyConfig: record.strategyConfig,
|
|
198
|
+
strategyResults: strategyResults ?? {}
|
|
199
|
+
};
|
|
200
|
+
})
|
|
201
|
+
);
|
|
202
|
+
return candidates.filter(Boolean);
|
|
203
|
+
};
|
|
204
|
+
var loadResolvedRuntimeStrategies = async ({
|
|
205
|
+
userName,
|
|
206
|
+
projectRoot,
|
|
207
|
+
deployment,
|
|
208
|
+
connectorName = "bybit",
|
|
209
|
+
universe,
|
|
210
|
+
accountId,
|
|
211
|
+
interval
|
|
212
|
+
}) => {
|
|
213
|
+
const hasVersionedReferences = Boolean(
|
|
214
|
+
deployment?.strategies.some((strategy) => strategy.releaseVersion != null)
|
|
215
|
+
);
|
|
216
|
+
if (hasVersionedReferences && deployment?.strategies.some((strategy) => strategy.releaseVersion == null)) {
|
|
217
|
+
throw new Error(
|
|
218
|
+
`Deployment ${deployment.id} mixes legacy configs and versioned releases`
|
|
219
|
+
);
|
|
220
|
+
}
|
|
221
|
+
const strategies = hasVersionedReferences ? await loadVersionedRuntimeStrategies({
|
|
222
|
+
userName,
|
|
223
|
+
projectRoot,
|
|
224
|
+
deployment,
|
|
225
|
+
connectorName
|
|
226
|
+
}) : await loadLegacyRuntimeStrategies({
|
|
227
|
+
userName,
|
|
228
|
+
projectRoot,
|
|
229
|
+
deployment,
|
|
230
|
+
connectorName
|
|
231
|
+
});
|
|
232
|
+
const filtered = strategies.filter(
|
|
233
|
+
(candidate) => (!universe || candidate.universe === universe) && (!interval || String(candidate.interval) === String(interval)) && (!accountId || candidate.accountId === accountId)
|
|
234
|
+
);
|
|
235
|
+
const identities = /* @__PURE__ */ new Set();
|
|
236
|
+
for (const candidate of filtered) {
|
|
237
|
+
const identity = `${candidate.strategyName}:${candidate.accountId ?? "default"}`;
|
|
238
|
+
if (identities.has(identity)) {
|
|
239
|
+
throw new Error(`Runtime strategy conflict: ${identity}`);
|
|
240
|
+
}
|
|
241
|
+
identities.add(identity);
|
|
242
|
+
}
|
|
243
|
+
return filtered;
|
|
244
|
+
};
|
|
245
|
+
var getRuntimeStrategyPackageMetadata = async ({
|
|
246
|
+
strategyName,
|
|
247
|
+
projectRoot
|
|
248
|
+
}) => {
|
|
249
|
+
const packageManifest = await readPackageManifest(projectRoot);
|
|
250
|
+
const strategyPackage = await getStrategyPluginSource(strategyName, projectRoot) ?? null;
|
|
251
|
+
return {
|
|
252
|
+
strategyPackage,
|
|
253
|
+
strategyPackageVersion: await resolveInstalledPackageVersion(
|
|
254
|
+
projectRoot,
|
|
255
|
+
strategyPackage,
|
|
256
|
+
packageManifest
|
|
257
|
+
),
|
|
258
|
+
runtimePackageVersion: packageManifest.packages?.["@tradejs/node"] ?? null
|
|
259
|
+
};
|
|
260
|
+
};
|
|
261
|
+
export {
|
|
262
|
+
getRuntimeStrategyPackageMetadata,
|
|
263
|
+
loadResolvedRuntimeStrategies
|
|
264
|
+
};
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { ClosedPnlRecord, RuntimeTradeRecord, ExchangeEntryRecord, PositionPnlSnapshot } from '@tradejs/types';
|
|
2
|
+
|
|
3
|
+
type ClosedPnlRecordWithOrderLinkId = ClosedPnlRecord & {
|
|
4
|
+
direction?: RuntimeTradeRecord['direction'];
|
|
5
|
+
entryTimestamp?: number;
|
|
6
|
+
orderLinkId?: string;
|
|
7
|
+
};
|
|
8
|
+
declare const takeExactClosedPnlMatch: ({ exactByOrderLinkId, exactByOrderId, symbolBuckets, orderLinkId, orderId, }: {
|
|
9
|
+
exactByOrderLinkId: Map<string, ClosedPnlRecordWithOrderLinkId>;
|
|
10
|
+
exactByOrderId: Map<string, ClosedPnlRecordWithOrderLinkId>;
|
|
11
|
+
symbolBuckets: Map<string, ClosedPnlRecordWithOrderLinkId[]>;
|
|
12
|
+
orderLinkId?: string | null;
|
|
13
|
+
orderId?: string | null;
|
|
14
|
+
}) => ClosedPnlRecordWithOrderLinkId | null;
|
|
15
|
+
declare const takeClosedPnlMatch: ({ exactByOrderLinkId, exactByOrderId, symbolBuckets, trade, }: {
|
|
16
|
+
exactByOrderLinkId: Map<string, ClosedPnlRecordWithOrderLinkId>;
|
|
17
|
+
exactByOrderId?: Map<string, ClosedPnlRecordWithOrderLinkId>;
|
|
18
|
+
symbolBuckets: Map<string, ClosedPnlRecordWithOrderLinkId[]>;
|
|
19
|
+
trade: RuntimeTradeRecord;
|
|
20
|
+
}) => ClosedPnlRecordWithOrderLinkId | null;
|
|
21
|
+
|
|
22
|
+
declare const buildExchangeFallbackRuntimeTrades: ({ entryRows, closedPnlRows, openPositions, strategyNames, existingTrades, endTime, }: {
|
|
23
|
+
entryRows: ExchangeEntryRecord[];
|
|
24
|
+
closedPnlRows: ClosedPnlRecordWithOrderLinkId[];
|
|
25
|
+
openPositions: PositionPnlSnapshot[];
|
|
26
|
+
strategyNames: string[];
|
|
27
|
+
existingTrades: RuntimeTradeRecord[];
|
|
28
|
+
endTime: number;
|
|
29
|
+
}) => RuntimeTradeRecord[];
|
|
30
|
+
|
|
31
|
+
export { type ClosedPnlRecordWithOrderLinkId, buildExchangeFallbackRuntimeTrades, takeClosedPnlMatch, takeExactClosedPnlMatch };
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { ClosedPnlRecord, RuntimeTradeRecord, ExchangeEntryRecord, PositionPnlSnapshot } from '@tradejs/types';
|
|
2
|
+
|
|
3
|
+
type ClosedPnlRecordWithOrderLinkId = ClosedPnlRecord & {
|
|
4
|
+
direction?: RuntimeTradeRecord['direction'];
|
|
5
|
+
entryTimestamp?: number;
|
|
6
|
+
orderLinkId?: string;
|
|
7
|
+
};
|
|
8
|
+
declare const takeExactClosedPnlMatch: ({ exactByOrderLinkId, exactByOrderId, symbolBuckets, orderLinkId, orderId, }: {
|
|
9
|
+
exactByOrderLinkId: Map<string, ClosedPnlRecordWithOrderLinkId>;
|
|
10
|
+
exactByOrderId: Map<string, ClosedPnlRecordWithOrderLinkId>;
|
|
11
|
+
symbolBuckets: Map<string, ClosedPnlRecordWithOrderLinkId[]>;
|
|
12
|
+
orderLinkId?: string | null;
|
|
13
|
+
orderId?: string | null;
|
|
14
|
+
}) => ClosedPnlRecordWithOrderLinkId | null;
|
|
15
|
+
declare const takeClosedPnlMatch: ({ exactByOrderLinkId, exactByOrderId, symbolBuckets, trade, }: {
|
|
16
|
+
exactByOrderLinkId: Map<string, ClosedPnlRecordWithOrderLinkId>;
|
|
17
|
+
exactByOrderId?: Map<string, ClosedPnlRecordWithOrderLinkId>;
|
|
18
|
+
symbolBuckets: Map<string, ClosedPnlRecordWithOrderLinkId[]>;
|
|
19
|
+
trade: RuntimeTradeRecord;
|
|
20
|
+
}) => ClosedPnlRecordWithOrderLinkId | null;
|
|
21
|
+
|
|
22
|
+
declare const buildExchangeFallbackRuntimeTrades: ({ entryRows, closedPnlRows, openPositions, strategyNames, existingTrades, endTime, }: {
|
|
23
|
+
entryRows: ExchangeEntryRecord[];
|
|
24
|
+
closedPnlRows: ClosedPnlRecordWithOrderLinkId[];
|
|
25
|
+
openPositions: PositionPnlSnapshot[];
|
|
26
|
+
strategyNames: string[];
|
|
27
|
+
existingTrades: RuntimeTradeRecord[];
|
|
28
|
+
endTime: number;
|
|
29
|
+
}) => RuntimeTradeRecord[];
|
|
30
|
+
|
|
31
|
+
export { type ClosedPnlRecordWithOrderLinkId, buildExchangeFallbackRuntimeTrades, takeClosedPnlMatch, takeExactClosedPnlMatch };
|
|
@@ -0,0 +1,321 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/runtimeTrades.ts
|
|
21
|
+
var runtimeTrades_exports = {};
|
|
22
|
+
__export(runtimeTrades_exports, {
|
|
23
|
+
buildExchangeFallbackRuntimeTrades: () => buildExchangeFallbackRuntimeTrades,
|
|
24
|
+
takeClosedPnlMatch: () => takeClosedPnlMatch,
|
|
25
|
+
takeExactClosedPnlMatch: () => takeExactClosedPnlMatch
|
|
26
|
+
});
|
|
27
|
+
module.exports = __toCommonJS(runtimeTrades_exports);
|
|
28
|
+
var import_runtimeTrades = require("@tradejs/core/runtimeTrades");
|
|
29
|
+
|
|
30
|
+
// src/runtimeTradeReconciliation.ts
|
|
31
|
+
var toNonEmptyString = (value) => typeof value === "string" && value.trim() ? value.trim() : null;
|
|
32
|
+
var removeFromExactMaps = (exactByOrderLinkId, exactByOrderId, row) => {
|
|
33
|
+
for (const [key, value] of exactByOrderLinkId) {
|
|
34
|
+
if (value === row) exactByOrderLinkId.delete(key);
|
|
35
|
+
}
|
|
36
|
+
for (const [key, value] of exactByOrderId) {
|
|
37
|
+
if (value === row) exactByOrderId.delete(key);
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
var removeFromSymbolBuckets = (buckets, row) => {
|
|
41
|
+
const rows = buckets.get(row.symbol);
|
|
42
|
+
const index = rows?.findIndex((candidate) => candidate === row) ?? -1;
|
|
43
|
+
if (index >= 0) rows?.splice(index, 1);
|
|
44
|
+
};
|
|
45
|
+
var takeExactClosedPnlMatch = ({
|
|
46
|
+
exactByOrderLinkId,
|
|
47
|
+
exactByOrderId,
|
|
48
|
+
symbolBuckets,
|
|
49
|
+
orderLinkId,
|
|
50
|
+
orderId
|
|
51
|
+
}) => {
|
|
52
|
+
const keys = [
|
|
53
|
+
[exactByOrderLinkId, orderLinkId],
|
|
54
|
+
[exactByOrderId, orderId]
|
|
55
|
+
];
|
|
56
|
+
for (const [bucket, key] of keys) {
|
|
57
|
+
const normalizedKey = toNonEmptyString(key);
|
|
58
|
+
if (!normalizedKey) continue;
|
|
59
|
+
const match = bucket.get(normalizedKey);
|
|
60
|
+
if (!match) continue;
|
|
61
|
+
removeFromExactMaps(exactByOrderLinkId, exactByOrderId, match);
|
|
62
|
+
removeFromSymbolBuckets(symbolBuckets, match);
|
|
63
|
+
return match;
|
|
64
|
+
}
|
|
65
|
+
return null;
|
|
66
|
+
};
|
|
67
|
+
var takeClosedPnlMatch = ({
|
|
68
|
+
exactByOrderLinkId,
|
|
69
|
+
exactByOrderId = /* @__PURE__ */ new Map(),
|
|
70
|
+
symbolBuckets,
|
|
71
|
+
trade
|
|
72
|
+
}) => {
|
|
73
|
+
const exactMatch = takeExactClosedPnlMatch({
|
|
74
|
+
exactByOrderLinkId,
|
|
75
|
+
exactByOrderId,
|
|
76
|
+
symbolBuckets,
|
|
77
|
+
orderLinkId: trade.orderId,
|
|
78
|
+
orderId: trade.orderId
|
|
79
|
+
});
|
|
80
|
+
if (exactMatch) return exactMatch;
|
|
81
|
+
const rows = symbolBuckets.get(trade.symbol);
|
|
82
|
+
if (!rows?.length) return null;
|
|
83
|
+
const minimumClosedAt = trade.entryTimestamp - 5 * 6e4;
|
|
84
|
+
const matchIndex = rows.reduce((bestIndex, row2, index) => {
|
|
85
|
+
if (!Number.isFinite(row2.closedAt) || row2.closedAt < minimumClosedAt || row2.direction && row2.direction !== trade.direction) {
|
|
86
|
+
return bestIndex;
|
|
87
|
+
}
|
|
88
|
+
if (bestIndex < 0) return index;
|
|
89
|
+
return row2.closedAt < rows[bestIndex].closedAt ? index : bestIndex;
|
|
90
|
+
}, -1);
|
|
91
|
+
if (matchIndex < 0) return null;
|
|
92
|
+
const [row] = rows.splice(matchIndex, 1);
|
|
93
|
+
if (row) removeFromExactMaps(exactByOrderLinkId, exactByOrderId, row);
|
|
94
|
+
return row ?? null;
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
// src/runtimeTrades.ts
|
|
98
|
+
var toNonEmptyString2 = (value) => typeof value === "string" && value.trim() ? value.trim() : null;
|
|
99
|
+
var roundValue = (value, digits = 2) => {
|
|
100
|
+
if (!Number.isFinite(value)) return 0;
|
|
101
|
+
const factor = 10 ** digits;
|
|
102
|
+
return Math.round(value * factor) / factor;
|
|
103
|
+
};
|
|
104
|
+
var removeExactMatches = (exactByOrderLinkId, exactByOrderId, row) => {
|
|
105
|
+
if (row.orderLinkId) exactByOrderLinkId.delete(row.orderLinkId);
|
|
106
|
+
if (row.orderId) exactByOrderId.delete(row.orderId);
|
|
107
|
+
};
|
|
108
|
+
var takeClosedPnlMatchForEntry = ({
|
|
109
|
+
exactByOrderLinkId,
|
|
110
|
+
exactByOrderId,
|
|
111
|
+
symbolBuckets,
|
|
112
|
+
entry
|
|
113
|
+
}) => {
|
|
114
|
+
const exactMatch = takeExactClosedPnlMatch({
|
|
115
|
+
exactByOrderLinkId,
|
|
116
|
+
exactByOrderId,
|
|
117
|
+
symbolBuckets,
|
|
118
|
+
orderLinkId: entry.orderLinkId,
|
|
119
|
+
orderId: entry.orderId
|
|
120
|
+
});
|
|
121
|
+
if (exactMatch) return exactMatch;
|
|
122
|
+
const rows = symbolBuckets.get(entry.symbol);
|
|
123
|
+
if (!rows?.length) return null;
|
|
124
|
+
const minimumClosedAt = entry.entryTimestamp - 5 * 6e4;
|
|
125
|
+
const matchIndex = rows.reduce((bestIndex, row, index) => {
|
|
126
|
+
if (!Number.isFinite(row.closedAt) || row.closedAt < minimumClosedAt || row.direction && row.direction !== entry.direction) {
|
|
127
|
+
return bestIndex;
|
|
128
|
+
}
|
|
129
|
+
if (bestIndex < 0) return index;
|
|
130
|
+
return row.closedAt < rows[bestIndex].closedAt ? index : bestIndex;
|
|
131
|
+
}, -1);
|
|
132
|
+
if (matchIndex < 0) return null;
|
|
133
|
+
const [match] = rows.splice(matchIndex, 1);
|
|
134
|
+
if (match) removeExactMatches(exactByOrderLinkId, exactByOrderId, match);
|
|
135
|
+
return match ?? null;
|
|
136
|
+
};
|
|
137
|
+
var aggregateExchangeEntriesByOrder = (entryRows) => {
|
|
138
|
+
const grouped = /* @__PURE__ */ new Map();
|
|
139
|
+
entryRows.forEach((entry, index) => {
|
|
140
|
+
const orderLinkId = toNonEmptyString2(entry.orderLinkId);
|
|
141
|
+
const orderId = toNonEmptyString2(entry.orderId);
|
|
142
|
+
const key = orderLinkId || orderId || `${entry.symbol}:${entry.direction}:${entry.entryTimestamp}:${index}`;
|
|
143
|
+
const existing = grouped.get(key);
|
|
144
|
+
const hasPrice = Number.isFinite(entry.qty) && typeof entry.entryPrice === "number" && Number.isFinite(entry.entryPrice);
|
|
145
|
+
if (!existing) {
|
|
146
|
+
grouped.set(key, {
|
|
147
|
+
...entry,
|
|
148
|
+
qty: Number.isFinite(entry.qty) ? entry.qty : 0,
|
|
149
|
+
pricingQty: hasPrice ? entry.qty : 0,
|
|
150
|
+
pricingNotional: hasPrice ? entry.qty * (entry.entryPrice ?? 0) : 0
|
|
151
|
+
});
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
existing.qty += Number.isFinite(entry.qty) ? entry.qty : 0;
|
|
155
|
+
existing.entryTimestamp = Math.min(
|
|
156
|
+
existing.entryTimestamp,
|
|
157
|
+
entry.entryTimestamp
|
|
158
|
+
);
|
|
159
|
+
if (hasPrice) {
|
|
160
|
+
existing.pricingQty += entry.qty;
|
|
161
|
+
existing.pricingNotional += entry.qty * (entry.entryPrice ?? 0);
|
|
162
|
+
}
|
|
163
|
+
});
|
|
164
|
+
return [...grouped.values()].map(({ pricingQty, pricingNotional, ...entry }) => ({
|
|
165
|
+
...entry,
|
|
166
|
+
qty: roundValue(entry.qty, 8),
|
|
167
|
+
entryPrice: pricingQty > 0 ? roundValue(pricingNotional / pricingQty, 8) : null
|
|
168
|
+
})).sort((left, right) => left.entryTimestamp - right.entryTimestamp);
|
|
169
|
+
};
|
|
170
|
+
var resolveStrategy = ({
|
|
171
|
+
orderLinkId,
|
|
172
|
+
orderId,
|
|
173
|
+
strategyNameByOrderId,
|
|
174
|
+
strategyNames
|
|
175
|
+
}) => (orderLinkId ? strategyNameByOrderId.get(orderLinkId) : null) ?? (orderId ? strategyNameByOrderId.get(orderId) : null) ?? (0, import_runtimeTrades.resolveStrategyNameByOrderLinkId)({ orderLinkId, strategyNames });
|
|
176
|
+
var buildRiskLevels = (position) => {
|
|
177
|
+
const takeProfitPrice = position?.takeProfitPrice;
|
|
178
|
+
const stopLossPrice = position?.stopLossPrice;
|
|
179
|
+
if ((typeof takeProfitPrice !== "number" || !Number.isFinite(takeProfitPrice)) && (typeof stopLossPrice !== "number" || !Number.isFinite(stopLossPrice))) {
|
|
180
|
+
return null;
|
|
181
|
+
}
|
|
182
|
+
return {
|
|
183
|
+
...typeof takeProfitPrice === "number" && Number.isFinite(takeProfitPrice) ? { takeProfitPrice } : {},
|
|
184
|
+
...typeof stopLossPrice === "number" && Number.isFinite(stopLossPrice) ? { stopLossPrice } : {}
|
|
185
|
+
};
|
|
186
|
+
};
|
|
187
|
+
var buildExchangeFallbackRuntimeTrades = ({
|
|
188
|
+
entryRows,
|
|
189
|
+
closedPnlRows,
|
|
190
|
+
openPositions,
|
|
191
|
+
strategyNames,
|
|
192
|
+
existingTrades,
|
|
193
|
+
endTime
|
|
194
|
+
}) => {
|
|
195
|
+
if (!entryRows.length && !closedPnlRows.length) return [];
|
|
196
|
+
const strategyNameByOrderId = new Map(
|
|
197
|
+
existingTrades.filter(
|
|
198
|
+
(trade) => Boolean(trade.orderId?.trim() && trade.strategy?.trim())
|
|
199
|
+
).map((trade) => [trade.orderId, trade.strategy])
|
|
200
|
+
);
|
|
201
|
+
const strategyNamesPool = [
|
|
202
|
+
.../* @__PURE__ */ new Set([
|
|
203
|
+
...strategyNames,
|
|
204
|
+
...existingTrades.map(({ strategy }) => strategy)
|
|
205
|
+
])
|
|
206
|
+
];
|
|
207
|
+
const openPositionBySymbol = new Map(
|
|
208
|
+
openPositions.map((position) => [position.symbol, position])
|
|
209
|
+
);
|
|
210
|
+
const existingOrderIds = new Set(
|
|
211
|
+
existingTrades.map(({ orderId }) => toNonEmptyString2(orderId)).filter((value) => value != null)
|
|
212
|
+
);
|
|
213
|
+
const exactByOrderLinkId = new Map(
|
|
214
|
+
closedPnlRows.filter((row) => Boolean(row.orderLinkId)).map((row) => [row.orderLinkId, row])
|
|
215
|
+
);
|
|
216
|
+
const exactByOrderId = new Map(
|
|
217
|
+
closedPnlRows.filter((row) => Boolean(row.orderId)).map((row) => [row.orderId, row])
|
|
218
|
+
);
|
|
219
|
+
const symbolBuckets = /* @__PURE__ */ new Map();
|
|
220
|
+
for (const row of closedPnlRows) {
|
|
221
|
+
const bucket = symbolBuckets.get(row.symbol) ?? [];
|
|
222
|
+
bucket.push(row);
|
|
223
|
+
symbolBuckets.set(row.symbol, bucket);
|
|
224
|
+
}
|
|
225
|
+
const fallbackTrades = aggregateExchangeEntriesByOrder(entryRows).map((entry) => {
|
|
226
|
+
const orderLinkId = toNonEmptyString2(entry.orderLinkId);
|
|
227
|
+
const orderId = toNonEmptyString2(entry.orderId);
|
|
228
|
+
const runtimeOrderId = orderLinkId ?? orderId;
|
|
229
|
+
if (!runtimeOrderId || existingOrderIds.has(runtimeOrderId)) return null;
|
|
230
|
+
const strategy = resolveStrategy({
|
|
231
|
+
orderLinkId,
|
|
232
|
+
orderId,
|
|
233
|
+
strategyNameByOrderId,
|
|
234
|
+
strategyNames: strategyNamesPool
|
|
235
|
+
});
|
|
236
|
+
if (!strategy) return null;
|
|
237
|
+
const closed = takeClosedPnlMatchForEntry({
|
|
238
|
+
exactByOrderLinkId,
|
|
239
|
+
exactByOrderId,
|
|
240
|
+
symbolBuckets,
|
|
241
|
+
entry
|
|
242
|
+
});
|
|
243
|
+
const position = openPositionBySymbol.get(entry.symbol);
|
|
244
|
+
const isActive = !closed && position?.direction === entry.direction && Number.isFinite(position.currentPrice) && Number.isFinite(position.unrealizedPnl);
|
|
245
|
+
const entryPrice = typeof entry.entryPrice === "number" && Number.isFinite(entry.entryPrice) ? entry.entryPrice : typeof closed?.entryPrice === "number" && Number.isFinite(closed.entryPrice) ? closed.entryPrice : null;
|
|
246
|
+
if (entryPrice == null) return null;
|
|
247
|
+
return {
|
|
248
|
+
orderId: runtimeOrderId,
|
|
249
|
+
strategy,
|
|
250
|
+
symbol: entry.symbol,
|
|
251
|
+
direction: entry.direction,
|
|
252
|
+
qty: entry.qty,
|
|
253
|
+
entryPrice,
|
|
254
|
+
actualEntryPrice: closed?.entryPrice ?? entry.entryPrice ?? null,
|
|
255
|
+
entryTimestamp: entry.entryTimestamp,
|
|
256
|
+
status: isActive ? "active" : "closed",
|
|
257
|
+
currentPrice: isActive ? position?.currentPrice ?? null : closed?.exitPrice ?? null,
|
|
258
|
+
currentPnl: isActive ? position?.unrealizedPnl ?? null : closed?.closedPnl ?? null,
|
|
259
|
+
closedPnl: isActive ? null : closed?.closedPnl ?? null,
|
|
260
|
+
exitPrice: isActive ? null : closed?.exitPrice ?? null,
|
|
261
|
+
actualExitPrice: isActive ? null : closed?.exitPrice ?? null,
|
|
262
|
+
exitTimestamp: isActive ? null : closed?.closedAt ?? null,
|
|
263
|
+
aiAnalysis: isActive ? buildRiskLevels(position) : null,
|
|
264
|
+
openFee: closed?.openFee ?? entry.openFee ?? null,
|
|
265
|
+
closeFee: closed?.closeFee ?? entry.closeFee ?? null,
|
|
266
|
+
fundingFee: closed?.fundingFee ?? entry.fundingFee ?? null,
|
|
267
|
+
totalFee: closed?.totalFee ?? entry.totalFee ?? null,
|
|
268
|
+
lastSyncedAt: endTime
|
|
269
|
+
};
|
|
270
|
+
}).filter((trade) => trade != null);
|
|
271
|
+
const usedOrderIds = /* @__PURE__ */ new Set([
|
|
272
|
+
...existingOrderIds,
|
|
273
|
+
...fallbackTrades.map(({ orderId }) => orderId)
|
|
274
|
+
]);
|
|
275
|
+
const remainingClosedTrades = [...symbolBuckets.values()].flat().map((row) => {
|
|
276
|
+
const orderLinkId = toNonEmptyString2(row.orderLinkId);
|
|
277
|
+
const orderId = toNonEmptyString2(row.orderId);
|
|
278
|
+
const runtimeOrderId = orderLinkId ?? orderId;
|
|
279
|
+
if (!runtimeOrderId || usedOrderIds.has(runtimeOrderId)) return null;
|
|
280
|
+
const strategy = resolveStrategy({
|
|
281
|
+
orderLinkId,
|
|
282
|
+
orderId,
|
|
283
|
+
strategyNameByOrderId,
|
|
284
|
+
strategyNames: strategyNamesPool
|
|
285
|
+
});
|
|
286
|
+
if (!strategy || row.entryPrice == null || !Number.isFinite(row.entryPrice) || !row.direction) {
|
|
287
|
+
return null;
|
|
288
|
+
}
|
|
289
|
+
return {
|
|
290
|
+
orderId: runtimeOrderId,
|
|
291
|
+
strategy,
|
|
292
|
+
symbol: row.symbol,
|
|
293
|
+
direction: row.direction,
|
|
294
|
+
qty: row.qty,
|
|
295
|
+
entryPrice: row.entryPrice,
|
|
296
|
+
actualEntryPrice: row.entryPrice,
|
|
297
|
+
entryTimestamp: typeof row.entryTimestamp === "number" && Number.isFinite(row.entryTimestamp) ? row.entryTimestamp : row.closedAt,
|
|
298
|
+
status: "closed",
|
|
299
|
+
currentPrice: row.exitPrice,
|
|
300
|
+
currentPnl: row.closedPnl,
|
|
301
|
+
closedPnl: row.closedPnl,
|
|
302
|
+
exitPrice: row.exitPrice,
|
|
303
|
+
actualExitPrice: row.exitPrice,
|
|
304
|
+
exitTimestamp: row.closedAt,
|
|
305
|
+
openFee: row.openFee ?? null,
|
|
306
|
+
closeFee: row.closeFee ?? null,
|
|
307
|
+
fundingFee: row.fundingFee ?? null,
|
|
308
|
+
totalFee: row.totalFee ?? null,
|
|
309
|
+
lastSyncedAt: endTime
|
|
310
|
+
};
|
|
311
|
+
}).filter((trade) => trade != null);
|
|
312
|
+
return [...fallbackTrades, ...remainingClosedTrades].sort(
|
|
313
|
+
(left, right) => left.entryTimestamp - right.entryTimestamp
|
|
314
|
+
);
|
|
315
|
+
};
|
|
316
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
317
|
+
0 && (module.exports = {
|
|
318
|
+
buildExchangeFallbackRuntimeTrades,
|
|
319
|
+
takeClosedPnlMatch,
|
|
320
|
+
takeExactClosedPnlMatch
|
|
321
|
+
});
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import {
|
|
2
|
+
buildExchangeFallbackRuntimeTrades,
|
|
3
|
+
takeClosedPnlMatch,
|
|
4
|
+
takeExactClosedPnlMatch
|
|
5
|
+
} from "./chunk-FB5NUEOQ.mjs";
|
|
6
|
+
import "./chunk-Y6FXYEAI.mjs";
|
|
7
|
+
export {
|
|
8
|
+
buildExchangeFallbackRuntimeTrades,
|
|
9
|
+
takeClosedPnlMatch,
|
|
10
|
+
takeExactClosedPnlMatch
|
|
11
|
+
};
|
package/dist/strategies.d.mts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export * from '@tradejs/core/strategies';
|
|
2
|
-
export { DEFAULT_AI_MODEL, MAX_AI_SERIES_POINTS, askAI, buildAiHumanPrompt, buildAiPayload, buildAiPrompts, buildAiSystemPrompt, buildCompactAiIndicatorsSnapshot, getDeterministicAiGateContext, getOpenRouterModelKwargs, resetAiRuntimeCache, runAiPrompt, runAiPromptLocal, trimSeriesDeep } from './ai.mjs';
|
|
3
|
-
export { ensureIndicatorPluginsLoaded, ensureStrategyPluginsLoaded, getAvailableStrategyNames, getRegisteredManifests, getRegisteredStrategies, getStrategyCreator, getStrategyManifest, isKnownStrategy, registerStrategyEntries, resetStrategyRegistryCache, strategies } from './registry.mjs';
|
|
2
|
+
export { AiChatMessage, DEFAULT_AI_MODEL, InvokeAiChatOptions, MAX_AI_SERIES_POINTS, askAI, buildAiHumanPrompt, buildAiPayload, buildAiPrompts, buildAiSystemPrompt, buildCompactAiIndicatorsSnapshot, getDeterministicAiGateContext, getOpenRouterModelKwargs, invokeAiChat, resetAiRuntimeCache, runAiPrompt, runAiPromptLocal, trimSeriesDeep } from './ai.mjs';
|
|
3
|
+
export { e as ensureIndicatorPluginsLoaded, a as ensureStrategyPluginsLoaded, g as getAvailableStrategyNames, b as getRegisteredManifests, c as getRegisteredStrategies, d as getStrategyCreator, f as getStrategyDefaults, h as getStrategyManifest, i as getStrategyPluginSource, j as isKnownStrategy, r as registerStrategyEntries, k as resetStrategyRegistryCache, s as strategies } from './registry-DHTLjQcr.mjs';
|
|
4
4
|
import { StrategyConfig, CreateStrategyCore, StrategyManifest, StrategyCreator, RuntimeStrategyConfigSnapshot, Signal, Direction, StrategyRuntimeMlOptions, StrategyRuntimeAiOptions, Connector, Tp, MarketFeatureInterval, StrategyEntrySignalContext } from '@tradejs/types';
|
|
5
5
|
import { TradejsConfigOnBarHook, TradejsConfigBeforeSignalsHook } from '@tradejs/core/config';
|
|
6
6
|
|
package/dist/strategies.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export * from '@tradejs/core/strategies';
|
|
2
|
-
export { DEFAULT_AI_MODEL, MAX_AI_SERIES_POINTS, askAI, buildAiHumanPrompt, buildAiPayload, buildAiPrompts, buildAiSystemPrompt, buildCompactAiIndicatorsSnapshot, getDeterministicAiGateContext, getOpenRouterModelKwargs, resetAiRuntimeCache, runAiPrompt, runAiPromptLocal, trimSeriesDeep } from './ai.js';
|
|
3
|
-
export { ensureIndicatorPluginsLoaded, ensureStrategyPluginsLoaded, getAvailableStrategyNames, getRegisteredManifests, getRegisteredStrategies, getStrategyCreator, getStrategyManifest, isKnownStrategy, registerStrategyEntries, resetStrategyRegistryCache, strategies } from './registry.js';
|
|
2
|
+
export { AiChatMessage, DEFAULT_AI_MODEL, InvokeAiChatOptions, MAX_AI_SERIES_POINTS, askAI, buildAiHumanPrompt, buildAiPayload, buildAiPrompts, buildAiSystemPrompt, buildCompactAiIndicatorsSnapshot, getDeterministicAiGateContext, getOpenRouterModelKwargs, invokeAiChat, resetAiRuntimeCache, runAiPrompt, runAiPromptLocal, trimSeriesDeep } from './ai.js';
|
|
3
|
+
export { e as ensureIndicatorPluginsLoaded, a as ensureStrategyPluginsLoaded, g as getAvailableStrategyNames, b as getRegisteredManifests, c as getRegisteredStrategies, d as getStrategyCreator, f as getStrategyDefaults, h as getStrategyManifest, i as getStrategyPluginSource, j as isKnownStrategy, r as registerStrategyEntries, k as resetStrategyRegistryCache, s as strategies } from './registry-DHTLjQcr.js';
|
|
4
4
|
import { StrategyConfig, CreateStrategyCore, StrategyManifest, StrategyCreator, RuntimeStrategyConfigSnapshot, Signal, Direction, StrategyRuntimeMlOptions, StrategyRuntimeAiOptions, Connector, Tp, MarketFeatureInterval, StrategyEntrySignalContext } from '@tradejs/types';
|
|
5
5
|
import { TradejsConfigOnBarHook, TradejsConfigBeforeSignalsHook } from '@tradejs/core/config';
|
|
6
6
|
|