/** * Correlates the two bus events a single DSL raise can produce so the * notification dispatch sends EITHER "Profit locked" OR "Stop raised", never * both (PF-2). * * On one raise tick a position can emit `TIER_ADVANCED` (→ Profit locked) and, * when the exchange SL is synced, `SL_UPDATED` (→ Stop raised). TIER_ADVANCED * always precedes SL_UPDATED on both emit paths (local monitor: synchronous * emit order; backend poll: ASC-sorted, tier-before-SL), so the tier handler * records intent here and the SL handler consults it. * * Correlation is per position (`__positionId`, falling back to `address::asset`) * and keyed on the events' OWN timestamps, not wall-clock time: * - Local path: both events carry the SAME tick `timestamp` string (an * `editPosition` network call sits between the two emits, so their * wall-clock gap can be seconds — event-time comparison is immune to that). * - Backend path: the two events are separate records whose `createdAt` may * differ by a few ms within one evaluation → matched by a small tolerance. * * The tolerance stays well below the minimum monitor interval (5s) so a later, * genuinely-independent raise's SL — whose timestamp is a different tick, ≥ 5s * away — is never mistaken for the same raise. Wall-clock time is used ONLY to * garbage-collect orphan markers whose paired SL never arrives. */ /** * Max spread (ms) between a tier's timestamp and its paired SL's timestamp for * them to count as the same raise. Covers the backend path's few-ms `createdAt` * spread; MUST stay below the minimum monitor interval (MIN_INTERVAL_SECONDS = * 5s) so two independent raises for one position never collide. */ export declare const RAISE_CORRELATION_TOLERANCE_MS = 2000; /** * Marker lifetime (ms, wall-clock) before garbage-collection. Bounds memory when * a profit-lock tier's paired SL never arrives (e.g. a Phase-1 tier that syncs no * exchange SL). Generous: past the correlation tolerance a marker can only match * via exact-timestamp equality, which an independent later raise never produces. */ export declare const RAISE_MARKER_TTL_MS = 60000; /** Hard size backstop: if the map ever grows past this, oldest entries are evicted. */ export declare const RAISE_MAX_ENTRIES = 5000; /** Per-position correlation key: cross-flow position id, else address::asset. */ export declare function raiseKey(ev: { __positionId?: string; address?: string; asset?: string; }): string; export interface RaiseCorrelatorOptions { /** Injectable clock for TTL/GC in tests. Defaults to `Date.now`. */ now?: () => number; toleranceMs?: number; ttlMs?: number; maxEntries?: number; } /** * Records profit-lock intent per raise and answers whether a following * stop-raise belongs to the same raise (and must therefore be suppressed). */ export declare class RaiseCorrelator { private readonly markers; private readonly now; private readonly toleranceMs; private readonly ttlMs; private readonly maxEntries; constructor(opts?: RaiseCorrelatorOptions); /** * A tier advanced with locked profit (> 0). Remember it so the paired * stop-raise for the SAME raise is suppressed. A newer raise overwrites the * position's prior marker. */ recordProfitLock(key: string, tierTimestamp: string): void; /** * A tier advanced with NO locked profit (<= 0) → PF-2 "else" branch: no * Profit-locked message and the paired stop-raise must go through. Drop any * stale marker so it cannot suppress this position's next stop-raise. */ clear(key: string): void; /** * True when this stop-raise is the SL half of a profit-locking raise already * announced as "Profit locked" → suppress it. Consumes the marker (once). */ shouldSuppressStopRaise(key: string, slTimestamp: string): boolean; /** Test/observability helper. */ size(): number; /** Remove expired entries; if still at capacity, evict the oldest. */ private evict; } //# sourceMappingURL=raise-correlator.d.ts.map