mirror of
https://github.com/discountry/ritmex-bot.git
synced 2026-09-11 17:28:08 +00:00
Enhance CLI command mode for ritmex-bot (#23)
- Introduced a new command mode for `ritmex-bot`, allowing agent-friendly structured trading operations without entering the Ink interactive menu. - Updated `package.json` to include versioning and set the project as public with a new CLI entry point. - Enhanced documentation in both English and Chinese to provide comprehensive usage instructions for the new command mode. - Added a new executable script for `ritmex-bot` to facilitate command execution. - Improved error handling and command parsing for better user experience and clarity in command execution.
This commit is contained in:
@@ -0,0 +1,163 @@
|
||||
import type {
|
||||
AccountListener,
|
||||
ConnectionEventListener,
|
||||
DepthListener,
|
||||
ExchangeAdapter,
|
||||
ExchangePrecision,
|
||||
FundingRateListener,
|
||||
KlineListener,
|
||||
OrderListener,
|
||||
RestHealthListener,
|
||||
TickerListener,
|
||||
} from "./adapter";
|
||||
import type {
|
||||
AsterAccountSnapshot,
|
||||
AsterOrder,
|
||||
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();
|
||||
}
|
||||
|
||||
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<AsterOrder> {
|
||||
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<AsterOrder[]> {
|
||||
if (!this.inner.queryOpenOrders) return [];
|
||||
return this.inner.queryOpenOrders();
|
||||
}
|
||||
|
||||
async queryAccountSnapshot(): Promise<AsterAccountSnapshot | 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): AsterOrder {
|
||||
const now = Date.now();
|
||||
const orderId = `dry-run-${now}-${counter}`;
|
||||
return {
|
||||
orderId,
|
||||
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);
|
||||
}
|
||||
Reference in New Issue
Block a user