mirror of
https://github.com/mauricioabh/arbpulse.git
synced 2026-07-27 15:47:43 +00:00
2e8744ccf0
Real-time BTC cross-exchange arbitrage detection (Kraken, Bybit, OKX, Binance) with React dashboard, GitHub Actions CI, and documented Fly.io deploy workflow. Co-authored-by: Cursor <cursoragent@cursor.com>
185 lines
4.6 KiB
TypeScript
185 lines
4.6 KiB
TypeScript
/**
|
|
* Domain types for the cross-exchange BTC arbitrage engine.
|
|
* All prices are quoted in USDT (BTC/USDT on every exchange — apples-to-apples,
|
|
* no USD/USDT basis distortion).
|
|
*/
|
|
|
|
export type ExchangeId = "kraken" | "bybit" | "okx" | "binance";
|
|
|
|
export const EXCHANGE_IDS: ExchangeId[] = ["kraken", "bybit", "okx", "binance"];
|
|
|
|
/** A single order book price level. */
|
|
export interface Level {
|
|
price: number;
|
|
qty: number;
|
|
}
|
|
|
|
/** Normalized order book for one exchange. Bids desc, asks asc. */
|
|
export interface OrderBook {
|
|
exchange: ExchangeId;
|
|
bids: Level[];
|
|
asks: Level[];
|
|
/** Local receive timestamp (ms epoch). */
|
|
recvTs: number;
|
|
/** Exchange-provided timestamp if available (ms epoch). */
|
|
exchangeTs: number | null;
|
|
}
|
|
|
|
/** Connection status of an exchange feed. */
|
|
export type FeedStatus = "connecting" | "live" | "stale" | "down";
|
|
|
|
/** Best bid/ask snapshot exposed to the UI. */
|
|
export interface BestQuote {
|
|
exchange: ExchangeId;
|
|
bid: number | null;
|
|
bidQty: number | null;
|
|
ask: number | null;
|
|
askQty: number | null;
|
|
recvTs: number | null;
|
|
status: FeedStatus;
|
|
/** ms since last update. */
|
|
ageMs: number | null;
|
|
}
|
|
|
|
/**
|
|
* Why an opportunity was or wasn't executed.
|
|
* Exhaustive union — handle every case.
|
|
*/
|
|
export type OpportunityStatus =
|
|
| "executed"
|
|
| "executed_partial"
|
|
| "rejected_fees"
|
|
| "rejected_liquidity"
|
|
| "rejected_risk"
|
|
| "rejected_flicker"
|
|
| "rejected_stale"
|
|
| "pending_confirm";
|
|
|
|
export interface Opportunity {
|
|
id: string;
|
|
ts: number;
|
|
buyExchange: ExchangeId;
|
|
sellExchange: ExchangeId;
|
|
/** Best (top-of-book) ask on the buy side, before walking depth. */
|
|
topBuyAsk: number;
|
|
/** Best (top-of-book) bid on the sell side, before walking depth. */
|
|
topSellBid: number;
|
|
/** Volume actually evaluated (BTC), after liquidity + wallet caps. */
|
|
volumeBtc: number;
|
|
/** Volume-weighted average buy price for volumeBtc. */
|
|
buyVwap: number;
|
|
/** Volume-weighted average sell price for volumeBtc. */
|
|
sellVwap: number;
|
|
grossSpread: number;
|
|
grossSpreadPct: number;
|
|
feeBuy: number;
|
|
feeSell: number;
|
|
netProfit: number;
|
|
netProfitPct: number;
|
|
status: OpportunityStatus;
|
|
reason: string;
|
|
/** True when generated by the synthetic demo injector. */
|
|
demo: boolean;
|
|
}
|
|
|
|
/** An executed (simulated) trade. */
|
|
export interface Trade {
|
|
id: string;
|
|
ts: number;
|
|
buyExchange: ExchangeId;
|
|
sellExchange: ExchangeId;
|
|
volumeBtc: number;
|
|
requestedBtc: number;
|
|
buyVwap: number;
|
|
sellVwap: number;
|
|
/** Buy VWAP after simulated latency drift. */
|
|
execBuyVwap: number;
|
|
/** Sell VWAP after simulated latency drift. */
|
|
execSellVwap: number;
|
|
feeBuy: number;
|
|
feeSell: number;
|
|
/** Net realized P&L in USDT (negative = loss). */
|
|
netProfit: number;
|
|
netProfitPct: number;
|
|
partial: boolean;
|
|
demo: boolean;
|
|
}
|
|
|
|
/** Per-exchange simulated wallet (pre-positioned inventory model). */
|
|
export interface Wallet {
|
|
exchange: ExchangeId;
|
|
usdt: number;
|
|
btc: number;
|
|
}
|
|
|
|
export interface RebalanceEvent {
|
|
id: string;
|
|
ts: number;
|
|
fromExchange: ExchangeId;
|
|
toExchange: ExchangeId;
|
|
asset: "BTC" | "USDT";
|
|
amount: number;
|
|
withdrawalFee: number;
|
|
reason: string;
|
|
}
|
|
|
|
export type CircuitState = "running" | "paused" | "tripped";
|
|
|
|
export interface EngineStats {
|
|
uptimeMs: number;
|
|
ticksProcessed: number;
|
|
opportunitiesDetected: number;
|
|
tradesExecuted: number;
|
|
tradesRejected: number;
|
|
realizedPnl: number;
|
|
consecutiveLosses: number;
|
|
circuit: CircuitState;
|
|
demoMode: boolean;
|
|
/** Average engine processing time per tick (ms). */
|
|
avgTickMs: number;
|
|
}
|
|
|
|
/** Full snapshot pushed to the dashboard over SSE. */
|
|
export interface StateSnapshot {
|
|
ts: number;
|
|
quotes: BestQuote[];
|
|
wallets: Wallet[];
|
|
stats: EngineStats;
|
|
recentOpportunities: Opportunity[];
|
|
recentTrades: Trade[];
|
|
rebalances: RebalanceEvent[];
|
|
pnlSeries: PnlPoint[];
|
|
config: PublicConfig;
|
|
}
|
|
|
|
export interface PnlPoint {
|
|
ts: number;
|
|
pnl: number;
|
|
}
|
|
|
|
/** Partial update body for PATCH /api/config. */
|
|
export interface ConfigPatch {
|
|
minNetProfitPct?: number;
|
|
maxTradeBtc?: number;
|
|
flickerConfirmMs?: number;
|
|
activeExchanges?: Partial<Record<ExchangeId, boolean>>;
|
|
}
|
|
|
|
/** Engine config surfaced to the UI (no secrets). */
|
|
export interface PublicConfig {
|
|
minNetProfitPct: number;
|
|
maxTradeBtc: number;
|
|
staleMs: number;
|
|
flickerConfirmMs: number;
|
|
latencyMs: number;
|
|
activeExchanges: Record<ExchangeId, boolean>;
|
|
defaults: {
|
|
minNetProfitPct: number;
|
|
maxTradeBtc: number;
|
|
flickerConfirmMs: number;
|
|
activeExchanges: Record<ExchangeId, boolean>;
|
|
};
|
|
takerFees: Record<ExchangeId, number>;
|
|
withdrawalFeesBtc: Record<ExchangeId, number>;
|
|
}
|