mirror of
https://github.com/discountry/ritmex-bot.git
synced 2026-09-11 17:28:08 +00:00
Integrate StandX exchange support by updating configuration files, adding environment variables, and enhancing documentation. Include new API endpoints and authentication details for StandX in README and dedicated documentation files. Update CLI and adapter logic to accommodate StandX functionalities.
This commit is contained in:
+4
-3
@@ -4,7 +4,7 @@ export interface CliOptions {
|
||||
strategy?: StrategyId;
|
||||
silent: boolean;
|
||||
help: boolean;
|
||||
exchange?: "aster" | "grvt" | "lighter" | "backpack" | "paradex" | "nado";
|
||||
exchange?: "aster" | "grvt" | "lighter" | "backpack" | "paradex" | "nado" | "standx";
|
||||
}
|
||||
|
||||
const STRATEGY_VALUES = new Set<StrategyId>([
|
||||
@@ -81,7 +81,8 @@ function assignExchange(options: CliOptions, raw: string): void {
|
||||
normalized === "lighter" ||
|
||||
normalized === "backpack" ||
|
||||
normalized === "paradex" ||
|
||||
normalized === "nado"
|
||||
normalized === "nado" ||
|
||||
normalized === "standx"
|
||||
) {
|
||||
options.exchange = normalized as CliOptions["exchange"];
|
||||
} else if (normalized === "gravity" || normalized === "grav" || normalized === "grv") {
|
||||
@@ -91,7 +92,7 @@ function assignExchange(options: CliOptions, raw: string): void {
|
||||
|
||||
export function printCliHelp(): void {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(`Usage: bun run index.ts [--strategy <trend|guardian|maker|offset-maker|basis|grid>] [--exchange <aster|grvt|lighter|backpack|paradex|nado>] [--silent]\n\n` +
|
||||
console.log(`Usage: bun run index.ts [--strategy <trend|guardian|maker|offset-maker|basis|grid>] [--exchange <aster|grvt|lighter|backpack|paradex|nado|standx>] [--silent]\n\n` +
|
||||
`Options:\n` +
|
||||
` --strategy, -s Automatically start the specified strategy without the interactive menu.\n` +
|
||||
` Aliases: offset, offset-maker for the offset maker engine.\n` +
|
||||
|
||||
@@ -92,8 +92,8 @@ const STRATEGY_FACTORIES: Record<StrategyId, StrategyRunner> = {
|
||||
throw new Error("Basis arbitrage strategy is disabled. Set ENABLE_BASIS_STRATEGY=true to enable it.");
|
||||
}
|
||||
const exchangeId = resolveExchangeId();
|
||||
if (exchangeId !== "aster" && exchangeId !== "nado") {
|
||||
throw new Error("Basis arbitrage strategy currently only supports the Aster and Nado exchanges");
|
||||
if (exchangeId !== "aster" && exchangeId !== "nado" && exchangeId !== "standx") {
|
||||
throw new Error("Basis arbitrage strategy currently only supports the Aster, Nado, and StandX exchanges");
|
||||
}
|
||||
const adapter = createAdapterOrThrow(basisConfig.futuresSymbol);
|
||||
const engine = new BasisArbEngine(basisConfig, adapter);
|
||||
|
||||
+13
-2
@@ -32,6 +32,7 @@ const SYMBOL_PRIORITY_BY_EXCHANGE: Record<SupportedExchangeId, { envKeys: string
|
||||
backpack: { envKeys: ["BACKPACK_SYMBOL", "TRADE_SYMBOL"], fallback: "BTCUSDC" },
|
||||
paradex: { envKeys: ["PARADEX_SYMBOL", "TRADE_SYMBOL"], fallback: "BTC/USDC" },
|
||||
nado: { envKeys: ["NADO_SYMBOL", "TRADE_SYMBOL"], fallback: "BTC-PERP" },
|
||||
standx: { envKeys: ["STANDX_SYMBOL", "TRADE_SYMBOL"], fallback: "BTC-USD" },
|
||||
};
|
||||
|
||||
export function resolveSymbolFromEnv(explicitExchangeId?: SupportedExchangeId | string | null): string {
|
||||
@@ -154,11 +155,21 @@ export const basisConfig: BasisArbConfig = {
|
||||
// Users can always override via BASIS_* env vars.
|
||||
futuresSymbol: resolveBasisSymbol(
|
||||
["BASIS_FUTURES_SYMBOL", "ASTER_FUTURES_SYMBOL", "ASTER_SYMBOL", "TRADE_SYMBOL"],
|
||||
(process.env.EXCHANGE ?? "").trim().toLowerCase() === "nado" ? "BTC-PERP" : "ASTERUSDT"
|
||||
(() => {
|
||||
const exchange = (process.env.EXCHANGE ?? "").trim().toLowerCase();
|
||||
if (exchange === "nado") return "BTC-PERP";
|
||||
if (exchange === "standx") return "BTC-USD";
|
||||
return "ASTERUSDT";
|
||||
})()
|
||||
),
|
||||
spotSymbol: resolveBasisSymbol(
|
||||
["BASIS_SPOT_SYMBOL", "ASTER_SPOT_SYMBOL", "ASTER_SYMBOL", "TRADE_SYMBOL"],
|
||||
(process.env.EXCHANGE ?? "").trim().toLowerCase() === "nado" ? "KBTC" : "ASTERUSDT"
|
||||
(() => {
|
||||
const exchange = (process.env.EXCHANGE ?? "").trim().toLowerCase();
|
||||
if (exchange === "nado") return "KBTC";
|
||||
if (exchange === "standx") return "BTC-USD";
|
||||
return "ASTERUSDT";
|
||||
})()
|
||||
),
|
||||
refreshIntervalMs: parseNumber(process.env.BASIS_REFRESH_INTERVAL_MS, 1000),
|
||||
maxLogEntries: parseNumber(process.env.BASIS_MAX_LOG_ENTRIES, 200),
|
||||
|
||||
@@ -5,6 +5,7 @@ import { LighterExchangeAdapter, type LighterCredentials } from "./lighter/adapt
|
||||
import { BackpackExchangeAdapter, type BackpackCredentials } from "./backpack/adapter";
|
||||
import { ParadexExchangeAdapter, type ParadexCredentials } from "./paradex/adapter";
|
||||
import { NadoExchangeAdapter, type NadoCredentials } from "./nado/adapter";
|
||||
import { StandxExchangeAdapter, type StandxCredentials } from "./standx/adapter";
|
||||
|
||||
export interface ExchangeFactoryOptions {
|
||||
symbol: string;
|
||||
@@ -15,9 +16,17 @@ export interface ExchangeFactoryOptions {
|
||||
backpack?: BackpackCredentials;
|
||||
paradex?: ParadexCredentials;
|
||||
nado?: NadoCredentials;
|
||||
standx?: StandxCredentials;
|
||||
}
|
||||
|
||||
export type SupportedExchangeId = "aster" | "grvt" | "lighter" | "backpack" | "paradex" | "nado";
|
||||
export type SupportedExchangeId =
|
||||
| "aster"
|
||||
| "grvt"
|
||||
| "lighter"
|
||||
| "backpack"
|
||||
| "paradex"
|
||||
| "nado"
|
||||
| "standx";
|
||||
|
||||
export function resolveExchangeId(value?: string | null): SupportedExchangeId {
|
||||
const fallback = (value ?? process.env.EXCHANGE ?? process.env.TRADE_EXCHANGE ?? "aster")
|
||||
@@ -29,6 +38,7 @@ export function resolveExchangeId(value?: string | null): SupportedExchangeId {
|
||||
if (fallback === "backpack") return "backpack";
|
||||
if (fallback === "paradex") return "paradex";
|
||||
if (fallback === "nado") return "nado";
|
||||
if (fallback === "standx") return "standx";
|
||||
return "aster";
|
||||
}
|
||||
|
||||
@@ -38,6 +48,7 @@ export function getExchangeDisplayName(id: SupportedExchangeId): string {
|
||||
if (id === "backpack") return "Backpack";
|
||||
if (id === "paradex") return "Paradex";
|
||||
if (id === "nado") return "Nado";
|
||||
if (id === "standx") return "StandX";
|
||||
return "AsterDex";
|
||||
}
|
||||
|
||||
@@ -58,5 +69,8 @@ export function createExchangeAdapter(options: ExchangeFactoryOptions): Exchange
|
||||
if (id === "nado") {
|
||||
return new NadoExchangeAdapter({ ...options.nado, symbol: options.symbol });
|
||||
}
|
||||
if (id === "standx") {
|
||||
return new StandxExchangeAdapter({ ...options.standx, symbol: options.symbol });
|
||||
}
|
||||
return new AsterExchangeAdapter({ ...options.aster, symbol: options.symbol });
|
||||
}
|
||||
|
||||
@@ -14,8 +14,9 @@ import * as grvtOrders from "./grvt/order";
|
||||
import * as lighterOrders from "./lighter/order";
|
||||
import * as paradexOrders from "./paradex/order";
|
||||
import * as nadoOrders from "./nado/order";
|
||||
import * as standxOrders from "./standx/order";
|
||||
|
||||
type ExchangeKey = "aster" | "backpack" | "grvt" | "lighter" | "paradex" | "nado";
|
||||
type ExchangeKey = "aster" | "backpack" | "grvt" | "lighter" | "paradex" | "nado" | "standx";
|
||||
|
||||
interface ExchangeOrderHandlers {
|
||||
limit(intent: LimitOrderIntent): Promise<AsterOrder>;
|
||||
@@ -68,9 +69,24 @@ const handlerMap: Record<ExchangeKey, ExchangeOrderHandlers> = {
|
||||
trailingStop: nadoOrders.createTrailingStopOrder,
|
||||
close: nadoOrders.createClosePositionOrder,
|
||||
},
|
||||
standx: {
|
||||
limit: standxOrders.createLimitOrder,
|
||||
market: standxOrders.createMarketOrder,
|
||||
stop: standxOrders.createStopOrder,
|
||||
trailingStop: standxOrders.createTrailingStopOrder,
|
||||
close: standxOrders.createClosePositionOrder,
|
||||
},
|
||||
};
|
||||
|
||||
const knownExchanges: ExchangeKey[] = ["aster", "backpack", "grvt", "lighter", "paradex", "nado"];
|
||||
const knownExchanges: ExchangeKey[] = [
|
||||
"aster",
|
||||
"backpack",
|
||||
"grvt",
|
||||
"lighter",
|
||||
"paradex",
|
||||
"nado",
|
||||
"standx",
|
||||
];
|
||||
|
||||
function normalizeExchangeId(value: string | undefined | null): string | undefined {
|
||||
if (!value) return undefined;
|
||||
|
||||
@@ -5,6 +5,7 @@ import type { LighterCredentials } from "./lighter/adapter";
|
||||
import type { BackpackCredentials } from "./backpack/adapter";
|
||||
import type { ParadexCredentials } from "./paradex/adapter";
|
||||
import type { NadoCredentials } from "./nado/adapter";
|
||||
import type { StandxCredentials } from "./standx/adapter";
|
||||
import { t } from "../i18n";
|
||||
import type { Address } from "viem";
|
||||
|
||||
@@ -42,6 +43,11 @@ export function buildAdapterFromEnv(options: BuildAdapterOptions): ExchangeAdapt
|
||||
return createExchangeAdapter({ exchange: id, symbol, nado: credentials });
|
||||
}
|
||||
|
||||
if (id === "standx") {
|
||||
const credentials = resolveStandxCredentials(symbol);
|
||||
return createExchangeAdapter({ exchange: id, symbol, standx: credentials });
|
||||
}
|
||||
|
||||
return createExchangeAdapter({ exchange: id, symbol, grvt: { symbol } });
|
||||
}
|
||||
|
||||
@@ -153,6 +159,21 @@ function resolveNadoCredentials(symbol: string): NadoCredentials {
|
||||
return credentials;
|
||||
}
|
||||
|
||||
function resolveStandxCredentials(symbol: string): StandxCredentials {
|
||||
const token = process.env.STANDX_TOKEN;
|
||||
if (!token) {
|
||||
throw new Error(t("env.missingStandx"));
|
||||
}
|
||||
return {
|
||||
token,
|
||||
symbol: process.env.STANDX_SYMBOL ?? symbol,
|
||||
baseUrl: process.env.STANDX_BASE_URL ?? undefined,
|
||||
wsUrl: process.env.STANDX_WS_URL ?? undefined,
|
||||
sessionId: process.env.STANDX_SESSION_ID ?? undefined,
|
||||
signingKey: process.env.STANDX_REQUEST_PRIVATE_KEY ?? undefined,
|
||||
};
|
||||
}
|
||||
|
||||
function isHex32(value: string): boolean {
|
||||
return /^0x[0-9a-fA-F]{64}$/.test(value.trim());
|
||||
}
|
||||
|
||||
@@ -0,0 +1,186 @@
|
||||
import { setTimeout, clearTimeout } from "timers";
|
||||
import type {
|
||||
AccountListener,
|
||||
DepthListener,
|
||||
ExchangeAdapter,
|
||||
ExchangePrecision,
|
||||
FundingRateListener,
|
||||
KlineListener,
|
||||
OrderListener,
|
||||
TickerListener,
|
||||
} from "../adapter";
|
||||
import type { AsterOrder, CreateOrderParams } from "../types";
|
||||
import { extractMessage } from "../../utils/errors";
|
||||
import { StandxGateway, type StandxGatewayOptions } from "./gateway";
|
||||
|
||||
export interface StandxCredentials {
|
||||
token?: string;
|
||||
symbol?: string;
|
||||
baseUrl?: string;
|
||||
wsUrl?: string;
|
||||
sessionId?: string;
|
||||
signingKey?: string;
|
||||
logger?: StandxGatewayOptions["logger"];
|
||||
}
|
||||
|
||||
export class StandxExchangeAdapter implements ExchangeAdapter {
|
||||
readonly id = "standx";
|
||||
|
||||
private readonly gateway: StandxGateway;
|
||||
private readonly symbol: string;
|
||||
private initPromise: Promise<void> | null = null;
|
||||
private readonly initContexts = new Set<string>();
|
||||
private retryTimer: ReturnType<typeof setTimeout> | null = null;
|
||||
private retryDelayMs = 3000;
|
||||
private lastInitErrorAt = 0;
|
||||
|
||||
constructor(credentials: StandxCredentials = {}) {
|
||||
const token = credentials.token ?? process.env.STANDX_TOKEN;
|
||||
if (!token) {
|
||||
throw new Error("Missing STANDX_TOKEN environment variable");
|
||||
}
|
||||
this.symbol = credentials.symbol ?? process.env.STANDX_SYMBOL ?? process.env.TRADE_SYMBOL ?? "BTC-USD";
|
||||
this.gateway = new StandxGateway({
|
||||
token,
|
||||
symbol: this.symbol,
|
||||
baseUrl: credentials.baseUrl,
|
||||
wsUrl: credentials.wsUrl,
|
||||
sessionId: credentials.sessionId,
|
||||
signingKey: credentials.signingKey,
|
||||
logger: credentials.logger,
|
||||
});
|
||||
}
|
||||
|
||||
supportsTrailingStops(): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
watchAccount(cb: AccountListener): void {
|
||||
void this.ensureInitialized("watchAccount");
|
||||
this.gateway.onAccount(this.safeInvoke("watchAccount", cb));
|
||||
}
|
||||
|
||||
watchOrders(cb: OrderListener): void {
|
||||
void this.ensureInitialized("watchOrders");
|
||||
this.gateway.onOrders(this.safeInvoke("watchOrders", cb));
|
||||
}
|
||||
|
||||
watchDepth(symbol: string, cb: DepthListener): void {
|
||||
void this.ensureInitialized("watchDepth");
|
||||
this.gateway.onDepth(symbol, this.safeInvoke("watchDepth", cb));
|
||||
}
|
||||
|
||||
watchTicker(symbol: string, cb: TickerListener): void {
|
||||
void this.ensureInitialized("watchTicker");
|
||||
this.gateway.onTicker(symbol, this.safeInvoke("watchTicker", cb));
|
||||
}
|
||||
|
||||
watchKlines(symbol: string, interval: string, cb: KlineListener): void {
|
||||
void this.ensureInitialized("watchKlines");
|
||||
this.gateway.onKlines(symbol, interval, this.safeInvoke("watchKlines", cb));
|
||||
}
|
||||
|
||||
watchFundingRate(symbol: string, cb: FundingRateListener): void {
|
||||
void this.ensureInitialized("watchFundingRate");
|
||||
this.gateway.onFundingRate(symbol, this.safeInvoke("watchFundingRate", cb));
|
||||
}
|
||||
|
||||
async createOrder(params: CreateOrderParams): Promise<AsterOrder> {
|
||||
await this.ensureInitialized("createOrder");
|
||||
return this.gateway.createOrder(params);
|
||||
}
|
||||
|
||||
async cancelOrder(params: { symbol: string; orderId: number | string }): Promise<void> {
|
||||
await this.ensureInitialized("cancelOrder");
|
||||
await this.gateway.cancelOrder(params);
|
||||
}
|
||||
|
||||
async cancelOrders(params: { symbol: string; orderIdList: Array<number | string> }): Promise<void> {
|
||||
await this.ensureInitialized("cancelOrders");
|
||||
await this.gateway.cancelOrders(params);
|
||||
}
|
||||
|
||||
async cancelAllOrders(params: { symbol: string }): Promise<void> {
|
||||
await this.ensureInitialized("cancelAllOrders");
|
||||
await this.gateway.cancelAllOrders(params);
|
||||
}
|
||||
|
||||
async getPrecision(): Promise<ExchangePrecision | null> {
|
||||
try {
|
||||
const precision = await this.gateway.getPrecision(this.symbol);
|
||||
if (!precision) return null;
|
||||
return {
|
||||
priceTick: precision.priceTick,
|
||||
qtyStep: precision.qtyStep,
|
||||
priceDecimals: precision.priceDecimals,
|
||||
sizeDecimals: precision.sizeDecimals,
|
||||
minBaseAmount: precision.minBaseAmount,
|
||||
};
|
||||
} catch (error) {
|
||||
console.error("[StandxExchangeAdapter] getPrecision failed", error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private safeInvoke<T extends (...args: any[]) => void>(context: string, cb: T): T {
|
||||
const wrapped = ((...args: any[]) => {
|
||||
try {
|
||||
cb(...args);
|
||||
} catch (error) {
|
||||
console.error(`[StandxExchangeAdapter] ${context} handler failed: ${extractMessage(error)}`);
|
||||
}
|
||||
}) as T;
|
||||
return wrapped;
|
||||
}
|
||||
|
||||
private ensureInitialized(context?: string): Promise<void> {
|
||||
if (!this.initPromise) {
|
||||
this.initContexts.clear();
|
||||
this.initPromise = this.gateway
|
||||
.ensureInitialized(this.symbol)
|
||||
.then((value) => {
|
||||
this.clearRetry();
|
||||
return value;
|
||||
})
|
||||
.catch((error) => {
|
||||
this.handleInitError("initialize", error);
|
||||
this.initPromise = null;
|
||||
this.scheduleRetry();
|
||||
throw error;
|
||||
});
|
||||
}
|
||||
if (context && !this.initContexts.has(context)) {
|
||||
this.initContexts.add(context);
|
||||
this.initPromise.catch((error) => {
|
||||
this.handleInitError(context, error);
|
||||
this.scheduleRetry();
|
||||
});
|
||||
}
|
||||
return this.initPromise;
|
||||
}
|
||||
|
||||
private scheduleRetry(): void {
|
||||
if (this.retryTimer) return;
|
||||
this.retryTimer = setTimeout(() => {
|
||||
this.retryTimer = null;
|
||||
if (this.initPromise) return;
|
||||
this.retryDelayMs = Math.min(this.retryDelayMs * 2, 60_000);
|
||||
void this.ensureInitialized("retry");
|
||||
}, this.retryDelayMs);
|
||||
}
|
||||
|
||||
private clearRetry(): void {
|
||||
if (this.retryTimer) {
|
||||
clearTimeout(this.retryTimer);
|
||||
this.retryTimer = null;
|
||||
}
|
||||
this.retryDelayMs = 3000;
|
||||
}
|
||||
|
||||
private handleInitError(context: string, error: unknown): void {
|
||||
const now = Date.now();
|
||||
if (now - this.lastInitErrorAt < 5000) return;
|
||||
this.lastInitErrorAt = now;
|
||||
console.error(`[StandxExchangeAdapter] ${context} failed`, error);
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,92 @@
|
||||
import type { AsterOrder, CreateOrderParams } from "../types";
|
||||
import type {
|
||||
BaseOrderIntent,
|
||||
ClosePositionIntent,
|
||||
LimitOrderIntent,
|
||||
MarketOrderIntent,
|
||||
StopOrderIntent,
|
||||
TrailingStopOrderIntent,
|
||||
} from "../order-schema";
|
||||
import { toStringBoolean } from "../order-schema";
|
||||
|
||||
function applyCommonFields(params: CreateOrderParams, intent: BaseOrderIntent): CreateOrderParams {
|
||||
if (params.quantity === undefined) {
|
||||
params.quantity = intent.quantity;
|
||||
}
|
||||
if (params.timeInForce === undefined && intent.timeInForce) {
|
||||
params.timeInForce = intent.timeInForce;
|
||||
}
|
||||
if (intent.reduceOnly !== undefined) {
|
||||
params.reduceOnly = toStringBoolean(intent.reduceOnly);
|
||||
}
|
||||
if (intent.closePosition !== undefined) {
|
||||
params.closePosition = toStringBoolean(intent.closePosition);
|
||||
}
|
||||
return params;
|
||||
}
|
||||
|
||||
export async function createLimitOrder(intent: LimitOrderIntent): Promise<AsterOrder> {
|
||||
const params: CreateOrderParams = applyCommonFields(
|
||||
{
|
||||
symbol: intent.symbol,
|
||||
side: intent.side,
|
||||
type: "LIMIT",
|
||||
quantity: intent.quantity,
|
||||
price: intent.price,
|
||||
timeInForce: intent.timeInForce ?? "GTX",
|
||||
},
|
||||
intent
|
||||
);
|
||||
return intent.adapter.createOrder(params);
|
||||
}
|
||||
|
||||
export async function createMarketOrder(intent: MarketOrderIntent): Promise<AsterOrder> {
|
||||
const params: CreateOrderParams = applyCommonFields(
|
||||
{
|
||||
symbol: intent.symbol,
|
||||
side: intent.side,
|
||||
type: "MARKET",
|
||||
quantity: intent.quantity,
|
||||
timeInForce: intent.timeInForce ?? "IOC",
|
||||
},
|
||||
intent
|
||||
);
|
||||
return intent.adapter.createOrder(params);
|
||||
}
|
||||
|
||||
export async function createStopOrder(intent: StopOrderIntent): Promise<AsterOrder> {
|
||||
const params: CreateOrderParams = applyCommonFields(
|
||||
{
|
||||
symbol: intent.symbol,
|
||||
side: intent.side,
|
||||
type: "STOP_MARKET",
|
||||
quantity: intent.quantity,
|
||||
stopPrice: intent.stopPrice,
|
||||
timeInForce: intent.timeInForce ?? "GTC",
|
||||
reduceOnly: toStringBoolean(intent.reduceOnly ?? true),
|
||||
closePosition: toStringBoolean(intent.closePosition ?? true),
|
||||
},
|
||||
intent
|
||||
);
|
||||
return intent.adapter.createOrder(params);
|
||||
}
|
||||
|
||||
export async function createTrailingStopOrder(_intent: TrailingStopOrderIntent): Promise<AsterOrder> {
|
||||
throw new Error("StandX exchange does not support trailing stop orders");
|
||||
}
|
||||
|
||||
export async function createClosePositionOrder(intent: ClosePositionIntent): Promise<AsterOrder> {
|
||||
const params: CreateOrderParams = applyCommonFields(
|
||||
{
|
||||
symbol: intent.symbol,
|
||||
side: intent.side,
|
||||
type: "MARKET",
|
||||
quantity: intent.quantity,
|
||||
reduceOnly: "true",
|
||||
closePosition: toStringBoolean(intent.closePosition ?? true),
|
||||
timeInForce: intent.timeInForce ?? "IOC",
|
||||
},
|
||||
intent
|
||||
);
|
||||
return intent.adapter.createOrder(params);
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
export interface StandxOrder {
|
||||
id?: number;
|
||||
cl_ord_id?: string;
|
||||
symbol: string;
|
||||
side: string;
|
||||
order_type: string;
|
||||
qty: string;
|
||||
price?: string;
|
||||
fill_qty?: string;
|
||||
fill_avg_price?: string;
|
||||
reduce_only?: boolean;
|
||||
time_in_force?: string;
|
||||
status?: string;
|
||||
created_at?: string;
|
||||
updated_at?: string;
|
||||
}
|
||||
|
||||
export interface StandxPosition {
|
||||
symbol: string;
|
||||
qty: string;
|
||||
entry_price?: string;
|
||||
mark_price?: string;
|
||||
upnl?: string;
|
||||
leverage?: string;
|
||||
liq_price?: string;
|
||||
margin_mode?: string;
|
||||
updated_at?: string;
|
||||
}
|
||||
|
||||
export interface StandxBalance {
|
||||
token: string;
|
||||
free?: string;
|
||||
locked?: string;
|
||||
total?: string;
|
||||
updated_at?: string;
|
||||
}
|
||||
|
||||
export interface StandxDepthBook {
|
||||
symbol: string;
|
||||
bids: [string, string][];
|
||||
asks: [string, string][];
|
||||
}
|
||||
|
||||
export interface StandxPrice {
|
||||
symbol: string;
|
||||
last_price?: string;
|
||||
mark_price?: string;
|
||||
index_price?: string;
|
||||
mid_price?: string;
|
||||
spread_bid?: string;
|
||||
spread_ask?: string;
|
||||
spread?: [string, string];
|
||||
time?: string;
|
||||
}
|
||||
|
||||
export interface StandxSymbolInfo {
|
||||
symbol: string;
|
||||
price_tick_decimals?: number;
|
||||
qty_tick_decimals?: number;
|
||||
min_order_qty?: string;
|
||||
max_order_qty?: string;
|
||||
depth_ticks?: string;
|
||||
}
|
||||
|
||||
export interface StandxSymbolMarket {
|
||||
symbol: string;
|
||||
funding_rate?: string;
|
||||
next_funding_time?: string;
|
||||
}
|
||||
|
||||
export interface StandxBalanceSnapshot {
|
||||
balance?: string;
|
||||
upnl?: string;
|
||||
cross_available?: string;
|
||||
cross_balance?: string;
|
||||
isolated_balance?: string;
|
||||
cross_upnl?: string;
|
||||
isolated_upnl?: string;
|
||||
locked?: string;
|
||||
}
|
||||
|
||||
export interface StandxKlineHistory {
|
||||
s?: string;
|
||||
t?: number[];
|
||||
o?: number[];
|
||||
h?: number[];
|
||||
l?: number[];
|
||||
c?: number[];
|
||||
v?: number[];
|
||||
}
|
||||
+6
-2
@@ -229,8 +229,8 @@ const translations: Record<string, TranslationEntry> = {
|
||||
"grid.direction.long": { zh: "多", en: "Long" },
|
||||
"grid.direction.short": { zh: "空", en: "Short" },
|
||||
"basis.onlyAster": {
|
||||
zh: "期现套利策略目前仅支持 Aster / Nado 交易所。请设置 EXCHANGE=aster 或 EXCHANGE=nado 后重试。",
|
||||
en: "Basis arbitrage currently supports only Aster and Nado. Set EXCHANGE=aster or EXCHANGE=nado and retry.",
|
||||
zh: "期现套利策略目前仅支持 Aster / Nado / StandX 交易所。请设置 EXCHANGE=aster 或 EXCHANGE=nado 或 EXCHANGE=standx 后重试。",
|
||||
en: "Basis arbitrage currently supports only Aster, Nado, and StandX. Set EXCHANGE=aster, EXCHANGE=nado, or EXCHANGE=standx and retry.",
|
||||
},
|
||||
"basis.startFailed": {
|
||||
zh: "无法启动期现套利策略: {message}",
|
||||
@@ -338,6 +338,10 @@ const translations: Record<string, TranslationEntry> = {
|
||||
zh: "NADO_SUBACCOUNT_OWNER / NADO_EVM_ADDRESS 必须是有效的 0x 开头 40 字节十六进制地址",
|
||||
en: "NADO_SUBACCOUNT_OWNER / NADO_EVM_ADDRESS must be a valid 0x-prefixed 40-byte hex address",
|
||||
},
|
||||
"env.missingStandx": {
|
||||
zh: "StandX 需要配置 STANDX_TOKEN",
|
||||
en: "StandX requires STANDX_TOKEN",
|
||||
},
|
||||
"log.subscribe.accountFail": {
|
||||
zh: "订阅账户失败: {error}",
|
||||
en: "Failed to subscribe account: {error}",
|
||||
|
||||
@@ -167,7 +167,7 @@ export class BasisArbEngine {
|
||||
}
|
||||
);
|
||||
|
||||
if (this.exchange.id === "nado") {
|
||||
if (this.exchange.id === "nado" || this.exchange.id === "standx") {
|
||||
safeSubscribe<AsterDepth>(
|
||||
this.exchange.watchDepth.bind(this.exchange, this.config.spotSymbol),
|
||||
(depth) => {
|
||||
|
||||
+1
-1
@@ -31,7 +31,7 @@ export function BasisApp({ onExit }: BasisAppProps) {
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (exchangeId !== "aster" && exchangeId !== "nado") {
|
||||
if (exchangeId !== "aster" && exchangeId !== "nado" && exchangeId !== "standx") {
|
||||
setError(new Error(t("basis.onlyAster")));
|
||||
return;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user