@senpi/runtime 1.0.95-dev.runtime-phase-2.20260420121147 → 1.0.95-dev.runtime-phase-2.20260420130250

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.
Files changed (58) hide show
  1. package/dist/cli/guide-commands.js +1 -1
  2. package/dist/constants/dsl/index.d.ts +12 -0
  3. package/dist/constants/dsl/index.d.ts.map +1 -1
  4. package/dist/constants/dsl/index.js +12 -0
  5. package/dist/constants/dsl/index.js.map +1 -1
  6. package/dist/dsl/engine-client/event-mapping.d.ts +16 -0
  7. package/dist/dsl/engine-client/event-mapping.d.ts.map +1 -1
  8. package/dist/dsl/engine-client/event-mapping.js +4 -0
  9. package/dist/dsl/engine-client/event-mapping.js.map +1 -1
  10. package/dist/dsl/engine-client/index.d.ts +1 -1
  11. package/dist/dsl/engine-client/index.d.ts.map +1 -1
  12. package/dist/dsl/events/build-closed-event.d.ts +15 -0
  13. package/dist/dsl/events/build-closed-event.d.ts.map +1 -0
  14. package/dist/dsl/events/build-closed-event.js +29 -0
  15. package/dist/dsl/events/build-closed-event.js.map +1 -0
  16. package/dist/dsl/events/handlers.d.ts.map +1 -1
  17. package/dist/dsl/events/handlers.js +3 -5
  18. package/dist/dsl/events/handlers.js.map +1 -1
  19. package/dist/dsl/index.js +1 -1
  20. package/dist/dsl/index.js.map +1 -1
  21. package/dist/dsl/monitor/process-one-position.d.ts.map +1 -1
  22. package/dist/dsl/monitor/process-one-position.js +107 -38
  23. package/dist/dsl/monitor/process-one-position.js.map +1 -1
  24. package/dist/dsl/notifications/close-resolver.d.ts +85 -0
  25. package/dist/dsl/notifications/close-resolver.d.ts.map +1 -0
  26. package/dist/dsl/notifications/close-resolver.js +302 -0
  27. package/dist/dsl/notifications/close-resolver.js.map +1 -0
  28. package/dist/dsl/notifications/format-utils.d.ts +24 -0
  29. package/dist/dsl/notifications/format-utils.d.ts.map +1 -0
  30. package/dist/dsl/notifications/format-utils.js +102 -0
  31. package/dist/dsl/notifications/format-utils.js.map +1 -0
  32. package/dist/dsl/notifications/formatter.d.ts +23 -4
  33. package/dist/dsl/notifications/formatter.d.ts.map +1 -1
  34. package/dist/dsl/notifications/formatter.js +109 -34
  35. package/dist/dsl/notifications/formatter.js.map +1 -1
  36. package/dist/dsl/notifications/index.d.ts +7 -0
  37. package/dist/dsl/notifications/index.d.ts.map +1 -1
  38. package/dist/dsl/notifications/index.js +22 -10
  39. package/dist/dsl/notifications/index.js.map +1 -1
  40. package/dist/dsl/notifications/types.d.ts +27 -0
  41. package/dist/dsl/notifications/types.d.ts.map +1 -0
  42. package/dist/dsl/notifications/types.js +5 -0
  43. package/dist/dsl/notifications/types.js.map +1 -0
  44. package/dist/dsl/plugin/index.d.ts.map +1 -1
  45. package/dist/dsl/plugin/index.js +26 -0
  46. package/dist/dsl/plugin/index.js.map +1 -1
  47. package/dist/scripts/run-dsl-standalone-live.js +1 -1
  48. package/dist/scripts/run-dsl-standalone-live.js.map +1 -1
  49. package/dist/scripts/run-dsl-standalone-mock.js +1 -1
  50. package/dist/scripts/run-dsl-standalone-mock.js.map +1 -1
  51. package/dist/senpi/client.d.ts.map +1 -1
  52. package/dist/senpi/client.js +2 -0
  53. package/dist/senpi/client.js.map +1 -1
  54. package/dist/types/dsl/index.d.ts +47 -0
  55. package/dist/types/dsl/index.d.ts.map +1 -1
  56. package/dist/types/strategy.d.ts +2 -0
  57. package/dist/types/strategy.d.ts.map +1 -1
  58. package/package.json +1 -1
@@ -0,0 +1,302 @@
1
+ /**
2
+ * Deferred close notifications: match `discovery_get_trader_history` rows to closed DSL positions
3
+ * and send enriched PnL / exit / fees, with a timeout fallback when history lags.
4
+ */
5
+ import { randomUUID } from "node:crypto";
6
+ import { mkdir, readdir, readFile, unlink, writeFile } from "node:fs/promises";
7
+ import { join } from "node:path";
8
+ import { CLOSE_RESOLVE_POLL_MS, CLOSE_RESOLVE_TIMEOUT_MS, } from "../../constants/dsl/index.js";
9
+ import { normalizeDslCoinDex } from "../../utils/perp-coin.js";
10
+ import { createLogger } from "../../utils/logger.js";
11
+ import { formatDslClosedEnriched, formatDslClosedFallback } from "./formatter.js";
12
+ const logger = createLogger("[senpi-core][dsl-close-resolver]");
13
+ /** Match tolerance (ms) between DSL timestamps and trade history `openTime` / `closedTime`. */
14
+ export const MATCH_OPEN_TIME_MS = 120_000;
15
+ export const MATCH_CLOSE_TIME_MS = 120_000;
16
+ /** Page size for paginated trade-history fetches. */
17
+ const TRADE_PAGE_SIZE = 20;
18
+ /** Safety cap on pages to prevent runaway pagination against a misbehaving API. */
19
+ const MAX_TRADE_PAGES = 10;
20
+ /**
21
+ * Finds the trade row that corresponds to a closed DSL position (coin/dex/direction + open/close times).
22
+ *
23
+ * Edge case: two closes for the same coin/direction within {@link MATCH_OPEN_TIME_MS} / {@link MATCH_CLOSE_TIME_MS}
24
+ * could both score the same row; follow-up could track claimed trade IDs per resolver pass if needed.
25
+ */
26
+ export function matchTrade(pending, trades) {
27
+ const p = normalizeDslCoinDex(pending.asset, pending.dex);
28
+ const createdMs = Date.parse(pending.createdAt);
29
+ const closedMs = Date.parse(pending.closedAt);
30
+ if (!Number.isFinite(createdMs) || !Number.isFinite(closedMs))
31
+ return undefined;
32
+ let best;
33
+ let bestScore = Infinity;
34
+ for (const t of trades) {
35
+ const tcd = normalizeDslCoinDex(t.coin, t.dex ?? "");
36
+ if (tcd.coin !== p.coin || tcd.dex !== p.dex)
37
+ continue;
38
+ if (t.direction !== pending.direction)
39
+ continue;
40
+ const openDelta = Math.abs(t.openTime - createdMs);
41
+ const closeDelta = Math.abs(t.closedTime - closedMs);
42
+ if (openDelta > MATCH_OPEN_TIME_MS || closeDelta > MATCH_CLOSE_TIME_MS)
43
+ continue;
44
+ const score = openDelta + closeDelta;
45
+ if (score < bestScore) {
46
+ bestScore = score;
47
+ best = t;
48
+ }
49
+ }
50
+ return best;
51
+ }
52
+ /** Builds a pending record from a bus payload (also used for immediate fallback when no resolver). */
53
+ export function buildPendingCloseFromEvent(e, id) {
54
+ const margin = e.margin ??
55
+ (e.leverage && e.leverage > 0 && e.entryPrice != null && e.size != null
56
+ ? (e.size * e.entryPrice) / e.leverage
57
+ : 0);
58
+ return {
59
+ id,
60
+ address: e.address,
61
+ asset: e.asset,
62
+ dex: e.dex ?? "",
63
+ direction: e.direction ?? "LONG",
64
+ entryPrice: e.entryPrice ?? 0,
65
+ leverage: e.leverage ?? 1,
66
+ margin,
67
+ size: e.size ?? 0,
68
+ closeReason: e.closeReason,
69
+ createdAt: e.createdAt ?? e.timestamp,
70
+ closedAt: e.timestamp,
71
+ lastPrice: e.lastPrice ?? 0,
72
+ currentROE: e.currentROE ?? 0,
73
+ peakROE: e.peakROE ?? 0,
74
+ currentTierIndex: e.currentTierIndex ?? null,
75
+ enqueuedAt: Date.now(),
76
+ retryCount: 0,
77
+ resolved: false,
78
+ };
79
+ }
80
+ /**
81
+ * Polls trade history and sends Telegram lines when a pending close is enriched or times out.
82
+ */
83
+ export class CloseNotificationResolver {
84
+ pendingDir;
85
+ getStrategy;
86
+ notify;
87
+ pending = new Map();
88
+ timer = null;
89
+ pollMs = CLOSE_RESOLVE_POLL_MS;
90
+ /** Prevents overlapping `tick()` runs when a poll takes longer than `pollMs` (await yields). */
91
+ tickInFlight = false;
92
+ constructor(opts) {
93
+ this.pendingDir = opts.pendingDir;
94
+ this.getStrategy = opts.getStrategy;
95
+ this.notify = opts.notify;
96
+ }
97
+ /**
98
+ * Loads pending JSON from disk and starts the polling loop.
99
+ * Awaits the first {@link tick} so callers (tests, plugin init) do not observe stale in-memory state.
100
+ */
101
+ async start(pollIntervalMs = CLOSE_RESOLVE_POLL_MS) {
102
+ this.pollMs = pollIntervalMs;
103
+ await mkdir(this.pendingDir, { recursive: true });
104
+ await this.hydrateFromDisk();
105
+ if (this.timer)
106
+ return;
107
+ this.timer = setInterval(() => {
108
+ void this.runTickSafe();
109
+ }, this.pollMs);
110
+ await this.runTickSafe();
111
+ }
112
+ /**
113
+ * Runs one poll pass; swallows errors so `setInterval` callbacks never surface as unhandled
114
+ * rejections. The initial call from {@link start} is awaited so the first `tick` completes
115
+ * before `start` resolves. Skips if the previous tick is still in progress so slow MCP calls
116
+ * cannot overlap and double-notify.
117
+ */
118
+ async runTickSafe() {
119
+ if (this.tickInFlight)
120
+ return;
121
+ this.tickInFlight = true;
122
+ try {
123
+ await this.tick();
124
+ }
125
+ catch (err) {
126
+ logger.warn("[dsl-close-resolver] tick failed", { err });
127
+ }
128
+ finally {
129
+ this.tickInFlight = false;
130
+ }
131
+ }
132
+ stop() {
133
+ if (this.timer) {
134
+ clearInterval(this.timer);
135
+ this.timer = null;
136
+ }
137
+ }
138
+ /**
139
+ * Queue a close for enrichment (writes disk, in-memory map).
140
+ *
141
+ * On disk failure after the in-memory enqueue, the row stays in `pending` so polling can still
142
+ * resolve or time out; failures are logged and never propagate (subscriber uses fire-and-forget).
143
+ */
144
+ async enqueue(event) {
145
+ const id = randomUUID();
146
+ const rec = buildPendingCloseFromEvent(event, id);
147
+ this.pending.set(id, rec);
148
+ try {
149
+ await mkdir(this.pendingDir, { recursive: true });
150
+ await this.persist(rec);
151
+ }
152
+ catch (err) {
153
+ logger.warn("[dsl-close-resolver] enqueue disk write failed; pending close kept in memory", {
154
+ err,
155
+ id,
156
+ });
157
+ }
158
+ }
159
+ async hydrateFromDisk() {
160
+ let names;
161
+ try {
162
+ names = await readdir(this.pendingDir);
163
+ }
164
+ catch {
165
+ return;
166
+ }
167
+ for (const name of names) {
168
+ if (!name.endsWith(".json"))
169
+ continue;
170
+ try {
171
+ const raw = await readFile(join(this.pendingDir, name), "utf-8");
172
+ const rec = JSON.parse(raw);
173
+ if (rec.resolved) {
174
+ await unlink(join(this.pendingDir, name)).catch(() => { });
175
+ continue;
176
+ }
177
+ this.pending.set(rec.id, rec);
178
+ }
179
+ catch (err) {
180
+ logger.warn("[dsl-close-resolver] skip bad pending file", { name, err });
181
+ }
182
+ }
183
+ }
184
+ async persist(rec) {
185
+ const path = join(this.pendingDir, `${rec.id}.json`);
186
+ await writeFile(path, JSON.stringify(rec, null, 2), "utf-8");
187
+ }
188
+ async removeFile(id) {
189
+ await unlink(join(this.pendingDir, `${id}.json`)).catch(() => { });
190
+ }
191
+ async tick() {
192
+ for (const id of [...this.pending.keys()]) {
193
+ const rec = this.pending.get(id);
194
+ if (!rec || rec.resolved)
195
+ continue;
196
+ try {
197
+ await this.resolveOne(rec);
198
+ }
199
+ catch (err) {
200
+ logger.warn("[dsl-close-resolver] resolveOne failed", { id: rec.id, err });
201
+ await this.bumpRetryAndMaybeFallback(rec);
202
+ }
203
+ }
204
+ }
205
+ /**
206
+ * Increments retry count and sends the no-PnL fallback once the enqueue age exceeds the timeout.
207
+ * Used after unexpected errors so a pending row cannot wedge the resolver forever.
208
+ */
209
+ async bumpRetryAndMaybeFallback(rec) {
210
+ rec.retryCount += 1;
211
+ const elapsed = Date.now() - rec.enqueuedAt;
212
+ if (elapsed >= CLOSE_RESOLVE_TIMEOUT_MS && this.pending.has(rec.id) && !rec.resolved) {
213
+ this.notify(formatDslClosedFallback(rec));
214
+ await this.finish(rec);
215
+ }
216
+ }
217
+ /**
218
+ * Paginates `fetchPastTrades` (sorted by closedTime DESC) until a match is found or
219
+ * trades fall before the position's open time. Returns the matched trade, `undefined`
220
+ * when no match was found across all pages, or `"error"` on fetch failure.
221
+ *
222
+ * NOTE: offset-based pagination with descending closedTime can skip or duplicate a row
223
+ * when a new trade lands between page fetches (row shifts down by one). Cursor-style
224
+ * pagination (closedTime < lastSeen) would avoid this but the upstream API does not
225
+ * support it. At typical trade cadence and the ±120s match tolerance this is unlikely
226
+ * to cause a missed notification.
227
+ */
228
+ async fetchAndMatch(strategy, rec) {
229
+ const cutoffMs = Date.parse(rec.createdAt) - MATCH_OPEN_TIME_MS;
230
+ let offset = 0;
231
+ for (let page = 0; page < MAX_TRADE_PAGES; page++) {
232
+ let trades;
233
+ try {
234
+ trades = await strategy.fetchPastTrades({
235
+ limit: TRADE_PAGE_SIZE,
236
+ offset,
237
+ sort_by: "closedTime",
238
+ sort_direction: "desc",
239
+ });
240
+ }
241
+ catch (err) {
242
+ logger.warn("[dsl-close-resolver] fetchPastTrades failed", { id: rec.id, page, err });
243
+ return "error";
244
+ }
245
+ if (!trades?.length)
246
+ return undefined;
247
+ const hit = matchTrade(rec, trades);
248
+ if (hit)
249
+ return hit;
250
+ // Oldest trade in this page — if it predates the position open window, stop.
251
+ const oldestClosedTime = Math.min(...trades.map((t) => t.closedTime));
252
+ if (oldestClosedTime < cutoffMs)
253
+ return undefined;
254
+ // Partial page means we've exhausted results.
255
+ if (trades.length < TRADE_PAGE_SIZE)
256
+ return undefined;
257
+ offset += TRADE_PAGE_SIZE;
258
+ }
259
+ return undefined;
260
+ }
261
+ async resolveOne(rec) {
262
+ const elapsed = Date.now() - rec.enqueuedAt;
263
+ const strategy = this.getStrategy(rec.address);
264
+ if (!strategy) {
265
+ rec.retryCount += 1;
266
+ if (elapsed >= CLOSE_RESOLVE_TIMEOUT_MS) {
267
+ this.notify(formatDslClosedFallback(rec));
268
+ await this.finish(rec);
269
+ }
270
+ return;
271
+ }
272
+ const hit = await this.fetchAndMatch(strategy, rec);
273
+ if (hit === "error") {
274
+ rec.retryCount += 1;
275
+ if (elapsed >= CLOSE_RESOLVE_TIMEOUT_MS) {
276
+ this.notify(formatDslClosedFallback(rec));
277
+ await this.finish(rec);
278
+ }
279
+ return;
280
+ }
281
+ if (hit) {
282
+ this.notify(formatDslClosedEnriched(rec, {
283
+ realizedPnl: hit.realizedPnl,
284
+ closePx: hit.closePx,
285
+ totalFees: hit.totalFees,
286
+ }));
287
+ await this.finish(rec);
288
+ return;
289
+ }
290
+ rec.retryCount += 1;
291
+ if (elapsed >= CLOSE_RESOLVE_TIMEOUT_MS) {
292
+ this.notify(formatDslClosedFallback(rec));
293
+ await this.finish(rec);
294
+ }
295
+ }
296
+ async finish(rec) {
297
+ rec.resolved = true;
298
+ this.pending.delete(rec.id);
299
+ await this.removeFile(rec.id);
300
+ }
301
+ }
302
+ //# sourceMappingURL=close-resolver.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"close-resolver.js","sourceRoot":"","sources":["../../../src/dsl/notifications/close-resolver.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,24 @@
1
+ /**
2
+ * User-facing number formatting for DSL Telegram notifications (smart decimals, signs, duration).
3
+ */
4
+ /**
5
+ * Smart USD price: BTC/ETH — whole dollars + grouping; alts — 2–4 decimals by magnitude.
6
+ */
7
+ export declare function fmtUsdPrice(price: number, asset?: string): string;
8
+ /**
9
+ * Signed P/L in USD with two decimals (+$80.27, -$48.15).
10
+ */
11
+ export declare function fmtUsdPnl(amount: number): string;
12
+ /** ROE percent with one decimal and sign (+6.6%, -3.5%). */
13
+ export declare function fmtRoe(pct: number): string;
14
+ /**
15
+ * Human duration between two ISO timestamps: `45m`, `2h 30m`, `1d 2h`.
16
+ */
17
+ export declare function fmtDuration(startIso: string, endIso: string): string;
18
+ /** Collateral display: trim meaningless trailing zeros (e.g. $500, $493.20). */
19
+ export declare function fmtMargin(amount: number): string;
20
+ /**
21
+ * Signed distance from entry to protective floor as a negative percentage (user-facing buffer).
22
+ */
23
+ export declare function fmtFloorDistancePct(entry: number, floor: number, direction: "LONG" | "SHORT"): string;
24
+ //# sourceMappingURL=format-utils.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"format-utils.d.ts","sourceRoot":"","sources":["../../../src/dsl/notifications/format-utils.ts"],"names":[],"mappings":"AAAA;;GAEG;AAmBH;;GAEG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CAYjE;AAED;;GAEG;AACH,wBAAgB,SAAS,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAIhD;AAED,4DAA4D;AAC5D,wBAAgB,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAI1C;AAED;;GAEG;AACH,wBAAgB,WAAW,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAiBpE;AAED,gFAAgF;AAChF,wBAAgB,SAAS,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAKhD;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CACjC,KAAK,EAAE,MAAM,EACb,KAAK,EAAE,MAAM,EACb,SAAS,EAAE,MAAM,GAAG,OAAO,GAC1B,MAAM,CAUR"}
@@ -0,0 +1,102 @@
1
+ /**
2
+ * User-facing number formatting for DSL Telegram notifications (smart decimals, signs, duration).
3
+ */
4
+ /**
5
+ * Strip `xyz:` prefix for major-coin detection.
6
+ */
7
+ function baseSymbol(asset) {
8
+ const t = asset.trim();
9
+ return t.toLowerCase().startsWith("xyz:") ? t.slice(4) : t;
10
+ }
11
+ function formatUsdWithCommas(n, fractionDigits) {
12
+ return new Intl.NumberFormat("en-US", {
13
+ style: "currency",
14
+ currency: "USD",
15
+ minimumFractionDigits: fractionDigits,
16
+ maximumFractionDigits: fractionDigits,
17
+ }).format(n);
18
+ }
19
+ /**
20
+ * Smart USD price: BTC/ETH — whole dollars + grouping; alts — 2–4 decimals by magnitude.
21
+ */
22
+ export function fmtUsdPrice(price, asset) {
23
+ if (!Number.isFinite(price))
24
+ return "$0";
25
+ const base = asset != null ? baseSymbol(asset).toUpperCase() : "";
26
+ if (base === "BTC" || base === "ETH") {
27
+ return formatUsdWithCommas(Math.round(price), 0);
28
+ }
29
+ const ap = Math.abs(price);
30
+ let digits = 2;
31
+ if (ap > 0 && ap < 1)
32
+ digits = 4;
33
+ else if (ap < 100)
34
+ digits = 2;
35
+ else
36
+ digits = 2;
37
+ return formatUsdWithCommas(price, digits);
38
+ }
39
+ /**
40
+ * Signed P/L in USD with two decimals (+$80.27, -$48.15).
41
+ */
42
+ export function fmtUsdPnl(amount) {
43
+ if (!Number.isFinite(amount))
44
+ return "$0.00";
45
+ const sign = amount >= 0 ? "+" : "";
46
+ return `${sign}${formatUsdWithCommas(amount, 2)}`;
47
+ }
48
+ /** ROE percent with one decimal and sign (+6.6%, -3.5%). */
49
+ export function fmtRoe(pct) {
50
+ if (!Number.isFinite(pct))
51
+ return "0.0%";
52
+ const sign = pct >= 0 ? "+" : "";
53
+ return `${sign}${pct.toFixed(1)}%`;
54
+ }
55
+ /**
56
+ * Human duration between two ISO timestamps: `45m`, `2h 30m`, `1d 2h`.
57
+ */
58
+ export function fmtDuration(startIso, endIso) {
59
+ const a = Date.parse(startIso);
60
+ const b = Date.parse(endIso);
61
+ if (!Number.isFinite(a) || !Number.isFinite(b))
62
+ return "—";
63
+ let ms = Math.max(0, b - a);
64
+ const days = Math.floor(ms / (24 * 60 * 60 * 1000));
65
+ ms -= days * 24 * 60 * 60 * 1000;
66
+ const hours = Math.floor(ms / (60 * 60 * 1000));
67
+ ms -= hours * 60 * 60 * 1000;
68
+ const minutes = Math.floor(ms / (60 * 1000));
69
+ if (days > 0) {
70
+ return hours > 0 ? `${days}d ${hours}h` : `${days}d`;
71
+ }
72
+ if (hours > 0) {
73
+ return minutes > 0 ? `${hours}h ${minutes}m` : `${hours}h`;
74
+ }
75
+ return `${minutes}m`;
76
+ }
77
+ /** Collateral display: trim meaningless trailing zeros (e.g. $500, $493.20). */
78
+ export function fmtMargin(amount) {
79
+ if (!Number.isFinite(amount))
80
+ return "$0";
81
+ const ap = Math.abs(amount);
82
+ const digits = ap >= 100 ? 0 : ap >= 1 ? 2 : 4;
83
+ return formatUsdWithCommas(amount, digits);
84
+ }
85
+ /**
86
+ * Signed distance from entry to protective floor as a negative percentage (user-facing buffer).
87
+ */
88
+ export function fmtFloorDistancePct(entry, floor, direction) {
89
+ if (!Number.isFinite(entry) || entry === 0 || !Number.isFinite(floor))
90
+ return "0.0%";
91
+ let pct;
92
+ if (direction === "LONG") {
93
+ pct = ((floor - entry) / entry) * 100;
94
+ }
95
+ else {
96
+ pct = ((entry - floor) / entry) * 100;
97
+ }
98
+ if (pct > 0)
99
+ pct = -pct;
100
+ return `${pct.toFixed(1)}%`;
101
+ }
102
+ //# sourceMappingURL=format-utils.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"format-utils.js","sourceRoot":"","sources":["../../../src/dsl/notifications/format-utils.ts"],"names":[],"mappings":"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"}
@@ -1,18 +1,37 @@
1
1
  /**
2
2
  * Plain-text messages for DSL lifecycle Telegram notifications.
3
3
  */
4
- import type { DslCreatedEvent, DslClosedEvent, DslPhaseChangedEvent, DslTierAdvancedEvent, DslSlUpdatedEvent } from "../../types/dsl/index.js";
5
- export declare function formatDslCreated(e: DslCreatedEvent): string;
6
- export declare function formatDslPhaseChanged(e: DslPhaseChangedEvent): string;
4
+ import type { DslCreatedEvent, DslTierAdvancedEvent, DslSlUpdatedEvent, DslHeartbeatEvent } from "../../types/dsl/index.js";
5
+ import type { PendingCloseNotification } from "./types.js";
6
+ /**
7
+ * User-facing line for `dsl.created`. Returns `null` when the position change should stay silent
8
+ * (increase / decrease / flip rebuilds).
9
+ */
10
+ export declare function formatDslCreated(e: DslCreatedEvent): string | null;
11
+ /**
12
+ * Tier / profit-lock notification.
13
+ */
7
14
  export declare function formatDslTierAdvanced(e: DslTierAdvancedEvent): string;
8
15
  export declare function formatDslSlUpdated(e: DslSlUpdatedEvent): string;
9
16
  export declare function formatDslClosePending(e: {
10
17
  asset: string;
11
18
  attempt: number;
12
19
  }): string;
13
- export declare function formatDslClosed(e: DslClosedEvent): string;
20
+ export declare function plainEnglishCloseReason(closeReason: string): string;
21
+ export interface CloseEnrichment {
22
+ realizedPnl: number;
23
+ closePx: number;
24
+ totalFees: number;
25
+ }
26
+ /**
27
+ * Full three-line close when trade history matched.
28
+ */
29
+ export declare function formatDslClosedEnriched(p: PendingCloseNotification, x: CloseEnrichment): string;
30
+ /** Timeout path: no reliable exit PnL from history. */
31
+ export declare function formatDslClosedFallback(p: PendingCloseNotification): string;
14
32
  export declare function formatDslDeleted(e: {
15
33
  asset: string;
16
34
  dex?: string;
17
35
  }): string;
36
+ export declare function formatDslHeartbeat(e: DslHeartbeatEvent): string;
18
37
  //# sourceMappingURL=formatter.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"formatter.d.ts","sourceRoot":"","sources":["../../../src/dsl/notifications/formatter.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EACV,eAAe,EACf,cAAc,EACd,oBAAoB,EACpB,oBAAoB,EACpB,iBAAiB,EAClB,MAAM,0BAA0B,CAAC;AA0BlC,wBAAgB,gBAAgB,CAAC,CAAC,EAAE,eAAe,GAAG,MAAM,CAU3D;AAED,wBAAgB,qBAAqB,CAAC,CAAC,EAAE,oBAAoB,GAAG,MAAM,CAGrE;AAED,wBAAgB,qBAAqB,CAAC,CAAC,EAAE,oBAAoB,GAAG,MAAM,CAGrE;AAED,wBAAgB,kBAAkB,CAAC,CAAC,EAAE,iBAAiB,GAAG,MAAM,CAG/D;AAED,wBAAgB,qBAAqB,CAAC,CAAC,EAAE;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,GAAG,MAAM,CAEnF;AAiBD,wBAAgB,eAAe,CAAC,CAAC,EAAE,cAAc,GAAG,MAAM,CAOzD;AAED,wBAAgB,gBAAgB,CAAC,CAAC,EAAE;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,GAAG,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,MAAM,CAG3E"}
1
+ {"version":3,"file":"formatter.d.ts","sourceRoot":"","sources":["../../../src/dsl/notifications/formatter.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EACV,eAAe,EACf,oBAAoB,EACpB,iBAAiB,EACjB,iBAAiB,EAClB,MAAM,0BAA0B,CAAC;AAClC,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,YAAY,CAAC;AAmB3D;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,CAAC,EAAE,eAAe,GAAG,MAAM,GAAG,IAAI,CAQlE;AAED;;GAEG;AACH,wBAAgB,qBAAqB,CAAC,CAAC,EAAE,oBAAoB,GAAG,MAAM,CAerE;AAED,wBAAgB,kBAAkB,CAAC,CAAC,EAAE,iBAAiB,GAAG,MAAM,CAG/D;AAED,wBAAgB,qBAAqB,CAAC,CAAC,EAAE;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,GAAG,MAAM,CAEnF;AAwBD,wBAAgB,uBAAuB,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,CAEnE;AAwCD,MAAM,WAAW,eAAe;IAC9B,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED;;GAEG;AACH,wBAAgB,uBAAuB,CACrC,CAAC,EAAE,wBAAwB,EAC3B,CAAC,EAAE,eAAe,GACjB,MAAM,CAQR;AAED,uDAAuD;AACvD,wBAAgB,uBAAuB,CAAC,CAAC,EAAE,wBAAwB,GAAG,MAAM,CAK3E;AAED,wBAAgB,gBAAgB,CAAC,CAAC,EAAE;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,GAAG,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,MAAM,CAG3E;AAUD,wBAAgB,kBAAkB,CAAC,CAAC,EAAE,iBAAiB,GAAG,MAAM,CAY/D"}