diff --git a/src/config.ts b/src/config.ts index 44a0e45..f40fd66 100644 --- a/src/config.ts +++ b/src/config.ts @@ -126,6 +126,7 @@ export interface GridConfig { spacingPct: number; stopLossBufferPct: number; maxLogEntries: number; + maxPositionSize: number; } const resolveBasisSymbol = (envKeys: string[], fallback: string): string => { @@ -153,18 +154,28 @@ export const basisConfig: BasisArbConfig = { arbAmount: parseNumber(process.env.ARB_AMOUNT, parseNumber(process.env.TRADE_AMOUNT, 0)), }; +const resolveGridMaxPosition = (tradeAmount: number): number => { + const fallback = Math.max(tradeAmount * 10, tradeAmount); + 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(), tradeAmount: parseNumber(process.env.TRADE_AMOUNT, 0.001), refreshIntervalMs: parseNumber(process.env.GRID_REFRESH_INTERVAL_MS, 800), 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), - levelsPerSide: Math.max(5, Math.floor(parseNumber(process.env.GRID_LEVELS_PER_SIDE, 36))), + levelsPerSide: Math.max(5, Math.floor(parseNumber(process.env.GRID_LEVELS_PER_SIDE, 15))), spacingPct: Math.max(0.0001, parseNumber(process.env.GRID_SPACING_PCT, 0.00025)), stopLossBufferPct: Math.max(0.001, parseNumber(process.env.GRID_STOP_BUFFER_PCT, 0.003)), maxLogEntries: parseNumber(process.env.GRID_MAX_LOG_ENTRIES, 200), + maxPositionSize: 0, // placeholder updated below }; +gridConfig.maxPositionSize = resolveGridMaxPosition(gridConfig.tradeAmount); + export function isBasisStrategyEnabled(): boolean { const raw = process.env.ENABLE_BASIS_STRATEGY; if (!raw) return false; diff --git a/src/strategy/grid-engine.ts b/src/strategy/grid-engine.ts index e407156..440eadd 100644 --- a/src/strategy/grid-engine.ts +++ b/src/strategy/grid-engine.ts @@ -381,6 +381,7 @@ export class GridEngine { private buildDesiredOrders(): DesiredGridOrder[] { if (!this.gridReady || this.gridSpacing == null) return []; const desired: DesiredGridOrder[] = []; + const absPos = Math.abs(this.position.positionAmt); for (const level of this.levels) { if (level.blockedUntil && this.now() < level.blockedUntil) { continue; @@ -406,6 +407,20 @@ export class GridEngine { }); } } + // Apply max position guard: if net仓位已达上限,跳过会增大仓位的开仓单 + if (this.config.maxPositionSize > 0) { + return desired.filter((order) => { + if (order.intent !== "ENTRY") return true; + // opening in same direction as current position increases abs position + if (this.position.positionAmt >= 0 && order.side === "BUY") { + return absPos + order.amount <= this.config.maxPositionSize + EPSILON; + } + if (this.position.positionAmt <= 0 && order.side === "SELL") { + return absPos + order.amount <= this.config.maxPositionSize + EPSILON; + } + return true; + }); + } return desired; } diff --git a/src/ui/GridApp.tsx b/src/ui/GridApp.tsx index 12ee042..34dd18a 100644 --- a/src/ui/GridApp.tsx +++ b/src/ui/GridApp.tsx @@ -139,7 +139,7 @@ export function GridApp({ onExit }: GridAppProps) { 网格配置 - 单笔数量: {formatNumber(gridConfig.tradeAmount, 6)} | 每侧格子数: {gridConfig.levelsPerSide} + 单笔数量: {formatNumber(gridConfig.tradeAmount, 6)} | 每侧格子数: {gridConfig.levelsPerSide} | 最大仓位: {formatNumber(gridConfig.maxPositionSize, 6)} 网格步长: {(gridConfig.spacingPct * 100).toFixed(3)}% | 止损缓冲: {(gridConfig.stopLossBufferPct * 100).toFixed(3)}%