mirror of
https://github.com/discountry/ritmex-bot.git
synced 2026-09-09 08:18:07 +00:00
* feat(grid): add grid shift, exchange stops, and reconcile safeguards * docs(grid): document smart-follow shift and new grid env vars
168 lines
4.5 KiB
TypeScript
168 lines
4.5 KiB
TypeScript
import type {
|
|
AccountListener,
|
|
ConnectionEventListener,
|
|
DepthListener,
|
|
ExchangeAdapter,
|
|
ExchangePrecision,
|
|
FundingRateListener,
|
|
KlineListener,
|
|
OrderListener,
|
|
RestHealthListener,
|
|
TickerListener,
|
|
} from "./adapter";
|
|
import type {
|
|
AccountSnapshot,
|
|
Order,
|
|
CreateOrderParams,
|
|
} from "./types";
|
|
|
|
export interface DryRunAction {
|
|
method:
|
|
| "createOrder"
|
|
| "cancelOrder"
|
|
| "cancelOrders"
|
|
| "cancelAllOrders"
|
|
| "changeMarginMode"
|
|
| "forceCancelAllOrders";
|
|
params: unknown;
|
|
at: number;
|
|
}
|
|
|
|
export class DryRunExchangeAdapter implements ExchangeAdapter {
|
|
readonly id: string;
|
|
readonly actions: DryRunAction[] = [];
|
|
private syntheticCounter = 0;
|
|
|
|
constructor(private readonly inner: ExchangeAdapter) {
|
|
this.id = inner.id;
|
|
}
|
|
|
|
supportsTrailingStops(): boolean {
|
|
return this.inner.supportsTrailingStops();
|
|
}
|
|
|
|
supportsTriggerOrders(): boolean {
|
|
return this.inner.supportsTriggerOrders?.() ?? false;
|
|
}
|
|
|
|
watchAccount(cb: AccountListener): void {
|
|
this.inner.watchAccount(cb);
|
|
}
|
|
|
|
watchOrders(cb: OrderListener): void {
|
|
this.inner.watchOrders(cb);
|
|
}
|
|
|
|
watchDepth(symbol: string, cb: DepthListener): void {
|
|
this.inner.watchDepth(symbol, cb);
|
|
}
|
|
|
|
watchTicker(symbol: string, cb: TickerListener): void {
|
|
this.inner.watchTicker(symbol, cb);
|
|
}
|
|
|
|
watchKlines(symbol: string, interval: string, cb: KlineListener): void {
|
|
this.inner.watchKlines(symbol, interval, cb);
|
|
}
|
|
|
|
watchFundingRate(symbol: string, cb: FundingRateListener): void {
|
|
if (!this.inner.watchFundingRate) return;
|
|
this.inner.watchFundingRate(symbol, cb);
|
|
}
|
|
|
|
async createOrder(params: CreateOrderParams): Promise<Order> {
|
|
this.record("createOrder", params);
|
|
return createSyntheticOrder(params, ++this.syntheticCounter);
|
|
}
|
|
|
|
async cancelOrder(params: { symbol: string; orderId: number | string }): Promise<void> {
|
|
this.record("cancelOrder", params);
|
|
}
|
|
|
|
async cancelOrders(params: { symbol: string; orderIdList: Array<number | string> }): Promise<void> {
|
|
this.record("cancelOrders", params);
|
|
}
|
|
|
|
async cancelAllOrders(params: { symbol: string }): Promise<void> {
|
|
this.record("cancelAllOrders", params);
|
|
}
|
|
|
|
async getPrecision(): Promise<ExchangePrecision | null> {
|
|
if (!this.inner.getPrecision) return null;
|
|
return this.inner.getPrecision();
|
|
}
|
|
|
|
onConnectionEvent(listener: ConnectionEventListener): void {
|
|
this.inner.onConnectionEvent?.(listener);
|
|
}
|
|
|
|
offConnectionEvent(listener: ConnectionEventListener): void {
|
|
this.inner.offConnectionEvent?.(listener);
|
|
}
|
|
|
|
onRestHealthEvent(listener: RestHealthListener): void {
|
|
this.inner.onRestHealthEvent?.(listener);
|
|
}
|
|
|
|
offRestHealthEvent(listener: RestHealthListener): void {
|
|
this.inner.offRestHealthEvent?.(listener);
|
|
}
|
|
|
|
async queryOpenOrders(): Promise<Order[]> {
|
|
if (!this.inner.queryOpenOrders) return [];
|
|
return this.inner.queryOpenOrders();
|
|
}
|
|
|
|
async queryAccountSnapshot(): Promise<AccountSnapshot | null> {
|
|
if (!this.inner.queryAccountSnapshot) return null;
|
|
return this.inner.queryAccountSnapshot();
|
|
}
|
|
|
|
async changeMarginMode(params: { symbol: string; marginMode: "isolated" | "cross" }): Promise<void> {
|
|
this.record("changeMarginMode", params);
|
|
}
|
|
|
|
async forceCancelAllOrders(): Promise<boolean> {
|
|
this.record("forceCancelAllOrders", {});
|
|
return true;
|
|
}
|
|
|
|
private record(method: DryRunAction["method"], params: unknown): void {
|
|
this.actions.push({ method, params, at: Date.now() });
|
|
}
|
|
}
|
|
|
|
function createSyntheticOrder(params: CreateOrderParams, counter: number): Order {
|
|
const now = Date.now();
|
|
const orderId = params.clientOrderId ?? `dry-run-${now}-${counter}`;
|
|
return {
|
|
orderId,
|
|
clientOrderId: params.clientOrderId ?? orderId,
|
|
symbol: params.symbol,
|
|
side: params.side,
|
|
type: params.type,
|
|
status: "NEW",
|
|
price: toStringNumber(params.price),
|
|
origQty: toStringNumber(params.quantity),
|
|
executedQty: "0",
|
|
stopPrice: toStringNumber(params.stopPrice),
|
|
time: now,
|
|
updateTime: now,
|
|
reduceOnly: params.reduceOnly === "true",
|
|
closePosition: params.closePosition === "true",
|
|
activationPrice: toOptionalString(params.activationPrice),
|
|
timeInForce: params.timeInForce,
|
|
workingType: "MARK_PRICE",
|
|
};
|
|
}
|
|
|
|
function toStringNumber(value: number | undefined): string {
|
|
if (value == null) return "0";
|
|
return String(value);
|
|
}
|
|
|
|
function toOptionalString(value: number | undefined): string | undefined {
|
|
if (value == null) return undefined;
|
|
return String(value);
|
|
}
|