@tradejs/app 3.0.1 → 3.1.0

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.
@@ -1,85 +0,0 @@
1
- import type {
2
- Interval,
3
- MarketUniverse,
4
- RuntimeTradeRecord,
5
- SimpleOrderLogData,
6
- StrategyConfig,
7
- StrategyEvidenceTimeline,
8
- TestStat,
9
- } from '@tradejs/types';
10
-
11
- export interface RuntimeStrategyTradeSummary {
12
- totalTrades: number;
13
- activeTrades: number;
14
- closedTrades: number;
15
- wins: number;
16
- losses: number;
17
- activePnl: number;
18
- closedPnl: number;
19
- totalPnl: number;
20
- symbolConcentrationTop1: number | null;
21
- symbolConcentrationTop5: number | null;
22
- }
23
-
24
- export interface RuntimeStrategyTradeView {
25
- orderId: string;
26
- symbol: string;
27
- direction: RuntimeTradeRecord['direction'];
28
- status: RuntimeTradeRecord['status'];
29
- qty: number;
30
- entryTimestamp: number;
31
- entryPrice: number;
32
- actualEntryPrice: number | null;
33
- exitTimestamp: number | null;
34
- exitPrice: number | null;
35
- actualExitPrice: number | null;
36
- currentPrice: number | null;
37
- pnl: number | null;
38
- durationHours: number | null;
39
- entrySlippagePercent: number | null;
40
- exitSlippagePercent: number | null;
41
- exitType: RuntimeTradeRecord['exitType'] | null;
42
- takeProfitPrice: number | null;
43
- stopLossPrice: number | null;
44
- takeProfitPercent: number | null;
45
- stopLossPercent: number | null;
46
- openFee: number | null;
47
- closeFee: number | null;
48
- fundingFee: number | null;
49
- totalFee: number | null;
50
- lastSyncedAt: number | null;
51
- }
52
-
53
- export interface RuntimeStrategyView {
54
- runtimeKey: string;
55
- strategyName: string;
56
- configId: string;
57
- interval: Interval;
58
- universe: MarketUniverse;
59
- accountId?: string;
60
- accountLabel?: string;
61
- deploymentId?: string;
62
- policyProfileId?: string;
63
- connected: boolean;
64
- enabled: boolean;
65
- config: StrategyConfig | null;
66
- symbols: string[];
67
- stat: TestStat;
68
- summary: RuntimeStrategyTradeSummary;
69
- orderLog: SimpleOrderLogData;
70
- evidenceTimeline: StrategyEvidenceTimeline;
71
- recentTrades: RuntimeStrategyTradeView[];
72
- orders: RuntimeStrategyTradeView[];
73
- }
74
-
75
- export interface RuntimeStrategiesResponse {
76
- provider: string;
77
- hours: number;
78
- generatedAt: number;
79
- dataSources?: {
80
- localTrades: number;
81
- exchangeFallbackTrades: number;
82
- exchangeErrors: string[];
83
- };
84
- strategies: RuntimeStrategyView[];
85
- }
@@ -1,264 +0,0 @@
1
- import type {
2
- MarketUniverse,
3
- RuntimeLineage,
4
- RuntimeTradeRecord,
5
- } from '@tradejs/types';
6
-
7
- export interface RuntimeStrategyLineageScope {
8
- strategy: string;
9
- symbol: string;
10
- runtimeConfigId?: string;
11
- lineage: RuntimeLineage & { maxLossValue?: number | null };
12
- firstTimestamp: number;
13
- lastTimestamp: number;
14
- }
15
-
16
- export interface RuntimeStrategyAiGateChange {
17
- timestamp: number;
18
- previousFingerprint: string;
19
- fingerprint: string;
20
- }
21
-
22
- export interface RuntimeStrategyMaxLossValueChange {
23
- timestamp: number;
24
- previousValue: number;
25
- value: number;
26
- }
27
-
28
- export interface RuntimeStrategyMaxLossValueTimeline {
29
- observedFrom: number | null;
30
- initialValue: number | null;
31
- changes: RuntimeStrategyMaxLossValueChange[];
32
- }
33
-
34
- export interface RuntimeStrategyAccountScope {
35
- strategyName: string;
36
- configId: string;
37
- universe: MarketUniverse;
38
- accountId?: string;
39
- }
40
-
41
- export const buildRuntimeStrategyIdentityKey = ({
42
- strategyName,
43
- configId,
44
- universe,
45
- accountId,
46
- deploymentId,
47
- policyProfileId,
48
- }: {
49
- strategyName: string;
50
- configId?: string;
51
- universe?: MarketUniverse;
52
- accountId?: string;
53
- deploymentId?: string;
54
- policyProfileId?: string;
55
- }) =>
56
- [
57
- strategyName,
58
- configId ?? 'config',
59
- universe ?? 'crypto',
60
- accountId ?? 'default',
61
- deploymentId ?? 'default',
62
- policyProfileId ?? 'default',
63
- ].join(':');
64
-
65
- export const assignLegacyRuntimeTradeAccountScopes = (
66
- trades: RuntimeTradeRecord[],
67
- scopes: RuntimeStrategyAccountScope[],
68
- ): RuntimeTradeRecord[] =>
69
- trades.map((trade) => {
70
- if (trade.accountId || trade.deploymentId) return trade;
71
- const matchingAccountIds = new Set(
72
- scopes
73
- .filter(
74
- (scope) =>
75
- scope.strategyName === trade.strategy &&
76
- scope.configId === (trade.runtimeConfigId ?? 'config') &&
77
- scope.universe === (trade.universe ?? 'crypto'),
78
- )
79
- .map((scope) => scope.accountId)
80
- .filter((accountId): accountId is string => Boolean(accountId)),
81
- );
82
- return matchingAccountIds.size === 1
83
- ? { ...trade, accountId: [...matchingAccountIds][0] }
84
- : trade;
85
- });
86
-
87
- export const getRuntimeStrategyAiGateObservedFrom = ({
88
- scopes,
89
- strategyName,
90
- configId,
91
- endTime,
92
- }: {
93
- scopes: RuntimeStrategyLineageScope[];
94
- strategyName: string;
95
- configId?: string;
96
- endTime: number;
97
- }) => {
98
- const normalizedConfigId = configId ?? 'config';
99
- let observedFrom: number | null = null;
100
- for (const scope of scopes) {
101
- if (
102
- scope.strategy !== strategyName ||
103
- (scope.runtimeConfigId ?? 'config') !== normalizedConfigId ||
104
- scope.firstTimestamp > endTime
105
- ) {
106
- continue;
107
- }
108
- observedFrom =
109
- observedFrom == null
110
- ? scope.firstTimestamp
111
- : Math.min(observedFrom, scope.firstTimestamp);
112
- }
113
- return observedFrom;
114
- };
115
-
116
- export const buildRuntimeStrategyMaxLossValueTimeline = ({
117
- scopes,
118
- strategyName,
119
- configId,
120
- startTime,
121
- endTime,
122
- }: {
123
- scopes: RuntimeStrategyLineageScope[];
124
- strategyName: string;
125
- configId?: string;
126
- startTime: number;
127
- endTime: number;
128
- }): RuntimeStrategyMaxLossValueTimeline => {
129
- const normalizedConfigId = configId ?? 'config';
130
- const observationsByTimestamp = new Map<
131
- number,
132
- { value: number; lastTimestamp: number }
133
- >();
134
- for (const scope of scopes) {
135
- const value = scope.lineage.maxLossValue;
136
- if (
137
- scope.strategy !== strategyName ||
138
- (scope.runtimeConfigId ?? 'config') !== normalizedConfigId ||
139
- scope.firstTimestamp > endTime ||
140
- typeof value !== 'number' ||
141
- !Number.isFinite(value)
142
- ) {
143
- continue;
144
- }
145
- const existing = observationsByTimestamp.get(scope.firstTimestamp);
146
- if (
147
- !existing ||
148
- scope.lastTimestamp > existing.lastTimestamp ||
149
- (scope.lastTimestamp === existing.lastTimestamp && value > existing.value)
150
- ) {
151
- observationsByTimestamp.set(scope.firstTimestamp, {
152
- value,
153
- lastTimestamp: scope.lastTimestamp,
154
- });
155
- }
156
- }
157
-
158
- const changes: RuntimeStrategyMaxLossValueChange[] = [];
159
- let observedFrom: number | null = null;
160
- let initialValue: number | null = null;
161
- let currentValue: number | null = null;
162
- for (const [timestamp, observation] of [
163
- ...observationsByTimestamp.entries(),
164
- ].sort(([left], [right]) => left - right)) {
165
- if (currentValue == null) {
166
- observedFrom = timestamp;
167
- initialValue = observation.value;
168
- currentValue = observation.value;
169
- continue;
170
- }
171
- if (observation.value === currentValue) continue;
172
- if (timestamp >= startTime) {
173
- changes.push({
174
- timestamp,
175
- previousValue: currentValue,
176
- value: observation.value,
177
- });
178
- }
179
- currentValue = observation.value;
180
- }
181
- return { observedFrom, initialValue, changes };
182
- };
183
-
184
- export const isRuntimeStrategyLineageScope = (
185
- value: unknown,
186
- ): value is RuntimeStrategyLineageScope => {
187
- if (!value || typeof value !== 'object') return false;
188
- const record = value as Record<string, unknown>;
189
- const lineage = record.lineage as Record<string, unknown> | undefined;
190
- return (
191
- typeof record.strategy === 'string' &&
192
- typeof record.symbol === 'string' &&
193
- typeof record.firstTimestamp === 'number' &&
194
- Number.isFinite(record.firstTimestamp) &&
195
- typeof record.lastTimestamp === 'number' &&
196
- Number.isFinite(record.lastTimestamp) &&
197
- lineage != null &&
198
- typeof lineage.gateFingerprint === 'string' &&
199
- lineage.gateFingerprint.trim().length > 0
200
- );
201
- };
202
-
203
- export const buildRuntimeStrategyAiGateChanges = ({
204
- scopes,
205
- strategyName,
206
- configId,
207
- startTime,
208
- endTime,
209
- }: {
210
- scopes: RuntimeStrategyLineageScope[];
211
- strategyName: string;
212
- configId?: string;
213
- startTime: number;
214
- endTime: number;
215
- }): RuntimeStrategyAiGateChange[] => {
216
- const normalizedConfigId = configId ?? 'config';
217
- const observationsByTimestamp = new Map<
218
- number,
219
- { fingerprint: string; lastTimestamp: number }
220
- >();
221
- for (const scope of scopes) {
222
- if (
223
- scope.strategy !== strategyName ||
224
- (scope.runtimeConfigId ?? 'config') !== normalizedConfigId ||
225
- scope.firstTimestamp > endTime
226
- ) {
227
- continue;
228
- }
229
- const fingerprint = scope.lineage.gateFingerprint.trim();
230
- const existing = observationsByTimestamp.get(scope.firstTimestamp);
231
- if (
232
- !existing ||
233
- scope.lastTimestamp > existing.lastTimestamp ||
234
- (scope.lastTimestamp === existing.lastTimestamp &&
235
- fingerprint > existing.fingerprint)
236
- ) {
237
- observationsByTimestamp.set(scope.firstTimestamp, {
238
- fingerprint,
239
- lastTimestamp: scope.lastTimestamp,
240
- });
241
- }
242
- }
243
-
244
- const changes: RuntimeStrategyAiGateChange[] = [];
245
- let currentFingerprint: string | null = null;
246
- for (const [timestamp, observation] of [
247
- ...observationsByTimestamp.entries(),
248
- ].sort(([left], [right]) => left - right)) {
249
- if (currentFingerprint == null) {
250
- currentFingerprint = observation.fingerprint;
251
- continue;
252
- }
253
- if (observation.fingerprint === currentFingerprint) continue;
254
- if (timestamp >= startTime) {
255
- changes.push({
256
- timestamp,
257
- previousFingerprint: currentFingerprint,
258
- fingerprint: observation.fingerprint,
259
- });
260
- }
261
- currentFingerprint = observation.fingerprint;
262
- }
263
- return changes;
264
- };
@@ -1,113 +0,0 @@
1
- import type { ClosedPnlRecord, RuntimeTradeRecord } from '@tradejs/types';
2
-
3
- export type ClosedPnlRecordWithOrderLinkId = ClosedPnlRecord & {
4
- direction?: RuntimeTradeRecord['direction'];
5
- entryTimestamp?: number;
6
- orderLinkId?: string;
7
- };
8
-
9
- const toNonEmptyString = (value: unknown) =>
10
- typeof value === 'string' && value.trim() ? value.trim() : null;
11
-
12
- const removeFromExactMaps = ({
13
- exactByOrderLinkId,
14
- exactByOrderId,
15
- row,
16
- }: {
17
- exactByOrderLinkId: Map<string, ClosedPnlRecordWithOrderLinkId>;
18
- exactByOrderId: Map<string, ClosedPnlRecordWithOrderLinkId>;
19
- row: ClosedPnlRecordWithOrderLinkId;
20
- }) => {
21
- for (const [key, value] of exactByOrderLinkId) {
22
- if (value === row) exactByOrderLinkId.delete(key);
23
- }
24
- for (const [key, value] of exactByOrderId) {
25
- if (value === row) exactByOrderId.delete(key);
26
- }
27
- };
28
-
29
- const removeFromSymbolBuckets = (
30
- buckets: Map<string, ClosedPnlRecordWithOrderLinkId[]>,
31
- row: ClosedPnlRecordWithOrderLinkId,
32
- ) => {
33
- const rows = buckets.get(row.symbol);
34
- const index = rows?.findIndex((candidate) => candidate === row) ?? -1;
35
- if (index >= 0) rows?.splice(index, 1);
36
- };
37
-
38
- export const takeExactClosedPnlMatch = ({
39
- exactByOrderLinkId,
40
- exactByOrderId,
41
- symbolBuckets,
42
- orderLinkId,
43
- orderId,
44
- }: {
45
- exactByOrderLinkId: Map<string, ClosedPnlRecordWithOrderLinkId>;
46
- exactByOrderId: Map<string, ClosedPnlRecordWithOrderLinkId>;
47
- symbolBuckets: Map<string, ClosedPnlRecordWithOrderLinkId[]>;
48
- orderLinkId?: string | null;
49
- orderId?: string | null;
50
- }) => {
51
- const exactKeys: Array<
52
- [Map<string, ClosedPnlRecordWithOrderLinkId>, string | null | undefined]
53
- > = [
54
- [exactByOrderLinkId, orderLinkId],
55
- [exactByOrderId, orderId],
56
- ];
57
- for (const [bucket, key] of exactKeys) {
58
- const normalizedKey = toNonEmptyString(key);
59
- if (!normalizedKey) continue;
60
- const exactMatch = bucket.get(normalizedKey);
61
- if (!exactMatch) continue;
62
- removeFromExactMaps({
63
- exactByOrderLinkId,
64
- exactByOrderId,
65
- row: exactMatch,
66
- });
67
- removeFromSymbolBuckets(symbolBuckets, exactMatch);
68
- return exactMatch;
69
- }
70
- return null;
71
- };
72
-
73
- export const takeClosedPnlMatch = ({
74
- exactByOrderLinkId,
75
- exactByOrderId = new Map<string, ClosedPnlRecordWithOrderLinkId>(),
76
- symbolBuckets,
77
- trade,
78
- }: {
79
- exactByOrderLinkId: Map<string, ClosedPnlRecordWithOrderLinkId>;
80
- exactByOrderId?: Map<string, ClosedPnlRecordWithOrderLinkId>;
81
- symbolBuckets: Map<string, ClosedPnlRecordWithOrderLinkId[]>;
82
- trade: RuntimeTradeRecord;
83
- }) => {
84
- const exactMatch = takeExactClosedPnlMatch({
85
- exactByOrderLinkId,
86
- exactByOrderId,
87
- symbolBuckets,
88
- orderLinkId: trade.orderId,
89
- orderId: trade.orderId,
90
- });
91
- if (exactMatch) return exactMatch;
92
-
93
- const rows = symbolBuckets.get(trade.symbol);
94
- if (!rows?.length) return null;
95
- const minimumClosedAt = trade.entryTimestamp - 5 * 60_000;
96
- const matchIndex = rows.reduce((bestIndex, row, index) => {
97
- if (
98
- !Number.isFinite(row.closedAt) ||
99
- row.closedAt < minimumClosedAt ||
100
- (row.direction && row.direction !== trade.direction)
101
- ) {
102
- return bestIndex;
103
- }
104
- if (bestIndex < 0) return index;
105
- return row.closedAt < rows[bestIndex].closedAt ? index : bestIndex;
106
- }, -1);
107
- if (matchIndex < 0) return null;
108
- const [row] = rows.splice(matchIndex, 1);
109
- if (row) {
110
- removeFromExactMaps({ exactByOrderLinkId, exactByOrderId, row });
111
- }
112
- return row ?? null;
113
- };