feat: 添加基础网格策略支持,更新环境配置示例和文档,增强 CLI 和 UI 界面

This commit is contained in:
discountry
2025-10-07 01:40:06 +08:00
parent d7a95ceb36
commit 5e65c7025d
11 changed files with 1222 additions and 12 deletions
+65
View File
@@ -52,6 +52,15 @@ function parseNumber(value: string | undefined, fallback: number): number {
return Number.isFinite(next) ? next : fallback;
}
function parseBoolean(value: string | undefined, fallback: boolean): boolean {
if (!value) return fallback;
const normalized = value.trim().toLowerCase();
if (!normalized) return fallback;
if (normalized === "1" || normalized === "true" || normalized === "yes" || normalized === "on") return true;
if (normalized === "0" || normalized === "false" || normalized === "no" || normalized === "off") return false;
return fallback;
}
export const tradingConfig: TradingConfig = {
symbol: resolveSymbolFromEnv(),
tradeAmount: parseNumber(process.env.TRADE_AMOUNT, 0.001),
@@ -106,6 +115,26 @@ export interface BasisArbConfig {
takerFeeRate: number;
}
export type GridDirection = "both" | "long" | "short";
export interface GridConfig {
symbol: string;
lowerPrice: number;
upperPrice: number;
gridLevels: number;
orderSize: number;
maxPositionSize: number;
refreshIntervalMs: number;
maxLogEntries: number;
priceTick: number;
qtyStep: number;
direction: GridDirection;
stopLossPct: number;
restartTriggerPct: number;
autoRestart: boolean;
gridMode: "geometric";
}
const resolveBasisSymbol = (envKeys: string[], fallback: string): string => {
for (const key of envKeys) {
const value = process.env[key];
@@ -130,6 +159,42 @@ export const basisConfig: BasisArbConfig = {
takerFeeRate: parseNumber(process.env.BASIS_TAKER_FEE_RATE, 0.0004),
};
const resolveGridDirection = (raw: string | undefined, fallback: GridDirection): GridDirection => {
if (!raw) return fallback;
const normalized = raw.trim().toLowerCase();
if (normalized === "long" || normalized === "long-only") return "long";
if (normalized === "short" || normalized === "short-only") return "short";
if (normalized === "both" || normalized === "dual" || normalized === "bi" || normalized === "two-way") return "both";
return fallback;
};
const resolveGridMaxPosition = (orderSize: number, levels: number): number => {
const fallback = Math.max(orderSize * Math.max(levels - 1, 1), orderSize);
const raw = process.env.GRID_MAX_POSITION_SIZE ?? process.env.GRID_MAX_POSITION ?? process.env.GRID_POSITION_CAP;
const parsed = parseNumber(raw, fallback);
return parsed > 0 ? parsed : fallback;
};
export const gridConfig: GridConfig = {
symbol: resolveSymbolFromEnv(),
lowerPrice: parseNumber(process.env.GRID_LOWER_PRICE ?? process.env.GRID_LOWER_BOUND, 0),
upperPrice: parseNumber(process.env.GRID_UPPER_PRICE ?? process.env.GRID_UPPER_BOUND, 0),
gridLevels: Math.max(2, Math.floor(parseNumber(process.env.GRID_LEVELS, 10))),
orderSize: parseNumber(process.env.GRID_ORDER_SIZE, parseNumber(process.env.TRADE_AMOUNT, 0.001)),
maxPositionSize: 0, // placeholder, replaced below
refreshIntervalMs: parseNumber(process.env.GRID_REFRESH_INTERVAL_MS, 1_000),
maxLogEntries: parseNumber(process.env.GRID_MAX_LOG_ENTRIES, 200),
priceTick: parseNumber(process.env.GRID_PRICE_TICK ?? process.env.PRICE_TICK, 0.1),
qtyStep: parseNumber(process.env.GRID_QTY_STEP ?? process.env.QTY_STEP, 0.001),
direction: resolveGridDirection(process.env.GRID_DIRECTION, "both"),
stopLossPct: Math.max(0, parseNumber(process.env.GRID_STOP_LOSS_PCT, 0.01)),
restartTriggerPct: Math.max(0, parseNumber(process.env.GRID_RESTART_TRIGGER_PCT, 0.01)),
autoRestart: parseBoolean(process.env.GRID_AUTO_RESTART_ENABLED ?? process.env.GRID_ENABLE_AUTO_RESTART, true),
gridMode: "geometric",
};
gridConfig.maxPositionSize = resolveGridMaxPosition(gridConfig.orderSize, gridConfig.gridLevels);
export function isBasisStrategyEnabled(): boolean {
const raw = process.env.ENABLE_BASIS_STRATEGY;
if (!raw) return false;