/** * StrategyStateImpl — live, lazy-loading domain object backed by SenpiClient MCP calls. * * Design: fetch*() always hit MCP and update internal state. * get*() sync getters return whatever was last fetched. * The caller decides when freshness matters. */ import type { SenpiClient } from "../senpi/client.js"; import type { StrategyConfig, StrategyState, ClearinghouseState, OpenPosition, PastTrade, OpenOrder, StrategyDetails } from "../types/strategy.js"; import type { CreatePositionRequest, CreatePositionResult, EditPositionResult, OrderStatus, RatchetStopPosition, RatchetStopEvent } from "../senpi/types.js"; /** * Whether `positions` already holds this market — the dedupe rule, as a pure function over a * snapshot rather than over whatever a StrategyState last cached. * * Lifted out of {@link StrategyStateImpl.hasOpenPosition} so a caller holding a freshly * fetched view can apply the SAME rule to it. The open path must: it decides against the view * it just read, and asking the state object would answer from its own cache — a different * book, and the bug this function exists to make impossible to reintroduce. * * The dex rule is preserved exactly: an exact `(coin, dex)` match wins, and only when the * caller omitted `dex` does a lone row on some other book count as a match. An explicit * `dex: "main"` query is never satisfied by an `xyz` row. * * @param positions Snapshot rows; `undefined` (an unknown book) matches nothing. */ export declare function hasOpenPositionIn(positions: readonly OpenPosition[] | undefined, coin: string, dex?: string): boolean; export declare class StrategyStateImpl implements StrategyState { private senpi; readonly address: string; readonly config: StrategyConfig; private _clearinghouse; private _openOrders; private _pastTrades; private _details; /** * `"coin:field"` keys already warned about as malformed. Rate-limits the per-tick * malformed-field warning to once per (coin, field); cleared for a field when it * parses cleanly again, so a recurrence after recovery is surfaced anew. */ private readonly _warnedMalformedFields; constructor(config: StrategyConfig, senpi: SenpiClient); getClearinghouseState(): ClearinghouseState | undefined; getOpenPositions(): OpenPosition[] | undefined; getPastTrades(): PastTrade[] | undefined; getOpenOrders(): OpenOrder[] | undefined; /** * Free slots according to the LAST FETCHED view — 0 when no view has been fetched. * * Fails closed deliberately: an absent `_clearinghouse` means the book is UNKNOWN (never * read, or the last read failed — `fetchClearinghouseState` clears the cache on failure), * not empty. Reporting `config.slots` there reads as "nothing is open" and is how a caller * that never fetched ends up opening against a book nobody looked at. A caller that wants * the configured cap should read `config.slots`; a caller that wants the live count should * fetch first. */ getAvailableSlots(): number; getDetails(): StrategyDetails | undefined; hasOpenPosition(coin: string, dex?: string): boolean; /** * `opts.signal` bounds the whole read — the client's stale/unreadable retry ladder included. * An aborted read is an UNKNOWN view, never a stale one: the catch below clears the cache and * returns undefined, so a caller on a deadline fails closed instead of acting on an old book. */ fetchClearinghouseState(opts?: { retryUnreadable?: boolean; signal?: AbortSignal; }): Promise; fetchPastTrades(opts?: { limit?: number; offset?: number; sort_by?: string; sort_direction?: string; }): Promise; fetchOpenOrders(): Promise; fetchDetails(): Promise; refresh(): Promise; cancelOrder(orderId: number): Promise; closePosition(coin: string, reason: string, dex?: string, orderOptions?: { orderType?: import('../senpi/types.js').CloseOrderType; feeOptimizedLimitOptions?: import('../senpi/types.js').FeeOptimizedLimitOptions; closeLimitMinutes?: number; closePeakRoe?: number; }): Promise; createPosition(params: CreatePositionRequest): Promise; editPosition(payload: { coin: string; dex?: string; stopLoss?: { triggerPx: number; }; }): Promise; getOrderStatus(orderId: number, opts?: { timeoutMs?: number; }): Promise; private resolveStrategyId; addRatchetStop(params: { asset: string; dex?: string; direction?: "LONG" | "SHORT"; entryPrice?: number; size?: number; leverage?: number; ratchetStopConfig: { tiered: { tiers: Array<{ triggerRoe: number; lockRoe: number; }>; }; }; }): Promise<{ success: boolean; position?: RatchetStopPosition; }>; deleteRatchetStop(params: { asset: string; dex?: string; }): Promise<{ success: boolean; }>; queryRatchetStops(params?: { asset?: string; dex?: string; status?: "ACTIVE" | "PAUSED" | "DELETED" | "ALL"; }): Promise; queryRatchetStopEvents(params?: { positionId?: string; since?: string; sortOrder?: "ASC" | "DESC"; }): Promise; } //# sourceMappingURL=strategy-state.d.ts.map