From 76704b6bddb70b6b9d0865212c655633201d5d67 Mon Sep 17 00:00:00 2001 From: discountry Date: Tue, 20 Jan 2026 01:03:38 +0800 Subject: [PATCH] Enhance entry price logic in Maker and Liquidity Maker strategies - Added `entryDepthLevel` configuration option to `MakerConfig` and `LiquidityMakerConfig` for specifying order entry levels. - Implemented `getPricesAtLevel` utility function to retrieve bid and ask prices at specified depth levels. - Updated `MakerEngine`, `LiquidityMakerEngine`, and `OffsetMakerEngine` to utilize the new entry level logic for determining opening prices based on market depth. - Improved price handling to ensure more accurate order placements in varying market conditions. --- src/config.ts | 6 ++++ src/strategy/liquidity-maker-engine.ts | 14 +++++++-- src/strategy/maker-engine.ts | 14 +++++++-- src/strategy/offset-maker-engine.ts | 14 +++++++-- src/utils/price.ts | 40 ++++++++++++++++++++++++++ 5 files changed, 79 insertions(+), 9 deletions(-) diff --git a/src/config.ts b/src/config.ts index ea799e1..a3211cf 100644 --- a/src/config.ts +++ b/src/config.ts @@ -157,6 +157,8 @@ export interface MakerConfig { maxLogEntries: number; maxCloseSlippagePct: number; priceTick: number; + /** 开仓挂单档位:1=买1/卖1,2=买2/卖2,以此类推。仅影响无仓位时的开仓挂单,平仓逻辑不受影响。默认1 */ + entryDepthLevel: number; } export const makerConfig: MakerConfig = { @@ -172,6 +174,7 @@ export const makerConfig: MakerConfig = { 0.05 ), priceTick: parseNumber(process.env.MAKER_PRICE_TICK ?? process.env.PRICE_TICK, 0.1), + entryDepthLevel: Math.max(1, Math.floor(parseNumber(process.env.MAKER_ENTRY_DEPTH_LEVEL, 1))), }; export interface MakerPointsConfig { @@ -347,6 +350,8 @@ export interface LiquidityMakerConfig { closeTickOffset: number; /** 偏移判断阈值倍数,当一侧深度超出另一侧此倍数时取消薄端订单,默认2 */ depthImbalanceRatio: number; + /** 开仓挂单档位:1=买1/卖1,2=买2/卖2,以此类推。仅影响无仓位时的开仓挂单,平仓逻辑不受影响。默认1 */ + entryDepthLevel: number; } export const liquidityMakerConfig: LiquidityMakerConfig = { @@ -364,6 +369,7 @@ export const liquidityMakerConfig: LiquidityMakerConfig = { priceTick: parseNumber(process.env.LIQUIDITY_MAKER_PRICE_TICK ?? process.env.MAKER_PRICE_TICK ?? process.env.PRICE_TICK, 0.1), closeTickOffset: Math.max(1, Math.floor(parseNumber(process.env.LIQUIDITY_MAKER_CLOSE_TICK_OFFSET, 1))), depthImbalanceRatio: Math.max(1.1, parseNumber(process.env.LIQUIDITY_MAKER_DEPTH_IMBALANCE_RATIO, 2)), + entryDepthLevel: Math.max(1, Math.floor(parseNumber(process.env.MAKER_ENTRY_DEPTH_LEVEL, 1))), }; export function isBasisStrategyEnabled(): boolean { diff --git a/src/strategy/liquidity-maker-engine.ts b/src/strategy/liquidity-maker-engine.ts index d52995c..1d03593 100644 --- a/src/strategy/liquidity-maker-engine.ts +++ b/src/strategy/liquidity-maker-engine.ts @@ -14,7 +14,7 @@ import { isOrderActiveStatus } from "../utils/order-status"; import { getPosition, parseSymbolParts } from "../utils/strategy"; import type { PositionSnapshot } from "../utils/strategy"; import { computePositionPnl } from "../utils/pnl"; -import { getTopPrices, getMidOrLast } from "../utils/price"; +import { getTopPrices, getPricesAtLevel, getMidOrLast } from "../utils/price"; import { shouldStopLoss } from "../utils/risk"; import { marketClose, @@ -430,10 +430,18 @@ export class LiquidityMakerEngine { // 直接使用orderbook价格,格式化为字符串避免精度问题 const priceDecimals = this.getPriceDecimals(); + // 平仓价格始终使用买1/卖1 const closeBidPrice = formatPriceToString(finalBid, priceDecimals); const closeAskPrice = formatPriceToString(finalAsk, priceDecimals); - const rawBidPrice = finalBid - this.config.bidOffset; - const rawAskPrice = finalAsk + this.config.askOffset; + + // 开仓价格根据 entryDepthLevel 使用指定档位 + const entryLevel = this.config.entryDepthLevel ?? 1; + const { bidAtLevel: entryBid, askAtLevel: entryAsk } = getPricesAtLevel(latestDepth, entryLevel); + const entryBidBase = entryBid ?? finalBid; + const entryAskBase = entryAsk ?? finalAsk; + + const rawBidPrice = entryBidBase - this.config.bidOffset; + const rawAskPrice = entryAskBase + this.config.askOffset; const safeBid = this.ensureMakerPrice("BUY", rawBidPrice, finalBid, finalAsk); const safeAsk = this.ensureMakerPrice("SELL", rawAskPrice, finalBid, finalAsk); const bidPrice = safeBid != null ? formatPriceToString(safeBid, priceDecimals) : null; diff --git a/src/strategy/maker-engine.ts b/src/strategy/maker-engine.ts index 85179e5..ee8178b 100644 --- a/src/strategy/maker-engine.ts +++ b/src/strategy/maker-engine.ts @@ -14,7 +14,7 @@ import { isOrderActiveStatus } from "../utils/order-status"; import { getPosition } from "../utils/strategy"; import type { PositionSnapshot } from "../utils/strategy"; import { computePositionPnl } from "../utils/pnl"; -import { getTopPrices, getMidOrLast } from "../utils/price"; +import { getTopPrices, getPricesAtLevel, getMidOrLast } from "../utils/price"; import { shouldStopLoss } from "../utils/risk"; import { marketClose, @@ -305,10 +305,18 @@ export class MakerEngine { // 直接使用orderbook价格,格式化为字符串避免精度问题 const priceDecimals = this.getPriceDecimals(); + // 平仓价格始终使用买1/卖1 const closeBidPrice = formatPriceToString(topBid, priceDecimals); const closeAskPrice = formatPriceToString(topAsk, priceDecimals); - const bidPrice = formatPriceToString(topBid - this.config.bidOffset, priceDecimals); - const askPrice = formatPriceToString(topAsk + this.config.askOffset, priceDecimals); + + // 开仓价格根据 entryDepthLevel 使用指定档位 + const entryLevel = this.config.entryDepthLevel ?? 1; + const { bidAtLevel: entryBid, askAtLevel: entryAsk } = getPricesAtLevel(depth, entryLevel); + const entryBidBase = entryBid ?? topBid; + const entryAskBase = entryAsk ?? topAsk; + + const bidPrice = formatPriceToString(entryBidBase - this.config.bidOffset, priceDecimals); + const askPrice = formatPriceToString(entryAskBase + this.config.askOffset, priceDecimals); const position = getPosition(this.accountSnapshot, this.config.symbol); const absPosition = Math.abs(position.positionAmt); const desired: DesiredOrder[] = []; diff --git a/src/strategy/offset-maker-engine.ts b/src/strategy/offset-maker-engine.ts index 6bd8a1c..b92fb37 100644 --- a/src/strategy/offset-maker-engine.ts +++ b/src/strategy/offset-maker-engine.ts @@ -15,7 +15,7 @@ import { getPosition, parseSymbolParts } from "../utils/strategy"; import type { PositionSnapshot } from "../utils/strategy"; import { computeDepthStats } from "../utils/depth"; import { computePositionPnl } from "../utils/pnl"; -import { getTopPrices, getMidOrLast } from "../utils/price"; +import { getTopPrices, getPricesAtLevel, getMidOrLast } from "../utils/price"; import { shouldStopLoss } from "../utils/risk"; import { marketClose, @@ -356,10 +356,18 @@ export class OffsetMakerEngine { // 直接使用orderbook价格,格式化为字符串避免精度问题 const priceDecimals = this.getPriceDecimals(); + // 平仓价格始终使用买1/卖1 const closeBidPrice = formatPriceToString(finalBid, priceDecimals); const closeAskPrice = formatPriceToString(finalAsk, priceDecimals); - const rawBidPrice = finalBid - this.config.bidOffset; - const rawAskPrice = finalAsk + this.config.askOffset; + + // 开仓价格根据 entryDepthLevel 使用指定档位 + const entryLevel = this.config.entryDepthLevel ?? 1; + const { bidAtLevel: entryBid, askAtLevel: entryAsk } = getPricesAtLevel(latestDepth, entryLevel); + const entryBidBase = entryBid ?? finalBid; + const entryAskBase = entryAsk ?? finalAsk; + + const rawBidPrice = entryBidBase - this.config.bidOffset; + const rawAskPrice = entryAskBase + this.config.askOffset; const safeBid = this.ensureMakerPrice("BUY", rawBidPrice, finalBid, finalAsk); const safeAsk = this.ensureMakerPrice("SELL", rawAskPrice, finalBid, finalAsk); const bidPrice = safeBid != null ? formatPriceToString(safeBid, priceDecimals) : null; diff --git a/src/utils/price.ts b/src/utils/price.ts index fb5e6ba..ec02379 100644 --- a/src/utils/price.ts +++ b/src/utils/price.ts @@ -9,6 +9,46 @@ export function getTopPrices(depth?: AsterDepth | null): { topBid: number | null }; } +/** + * 获取指定档位的盘口价格 + * @param depth 深度数据 + * @param level 档位(1=买1/卖1,2=买2/卖2,以此类推) + * @returns 指定档位的买卖价格,如果该档位不存在则回退到最近的有效档位 + */ +export function getPricesAtLevel( + depth?: AsterDepth | null, + level: number = 1 +): { bidAtLevel: number | null; askAtLevel: number | null } { + const index = Math.max(0, level - 1); + + // 尝试获取指定档位,如果不存在则回退到最近的有效档位 + const bids = depth?.bids ?? []; + const asks = depth?.asks ?? []; + + let bidAtLevel: number | null = null; + let askAtLevel: number | null = null; + + // 从指定档位向前查找第一个有效的买价 + for (let i = Math.min(index, bids.length - 1); i >= 0; i--) { + const bid = Number(bids[i]?.[0]); + if (Number.isFinite(bid)) { + bidAtLevel = bid; + break; + } + } + + // 从指定档位向前查找第一个有效的卖价 + for (let i = Math.min(index, asks.length - 1); i >= 0; i--) { + const ask = Number(asks[i]?.[0]); + if (Number.isFinite(ask)) { + askAtLevel = ask; + break; + } + } + + return { bidAtLevel, askAtLevel }; +} + export function getMidOrLast(depth?: AsterDepth | null, ticker?: AsterTicker | null): number | null { const { topBid, topAsk } = getTopPrices(depth); if (topBid != null && topAsk != null) return (topBid + topAsk) / 2;