Files
ritmex-bot/src/utils/price.ts
T
2026-01-21 11:11:58 +08:00

114 lines
3.6 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import type { AsterDepth, AsterTicker } from "../exchanges/types";
export function getTopPrices(depth?: AsterDepth | null): { topBid: number | null; topAsk: number | null } {
const bid = Number(depth?.bids?.[0]?.[0]);
const ask = Number(depth?.asks?.[0]?.[0]);
return {
topBid: Number.isFinite(bid) ? bid : null,
topAsk: Number.isFinite(ask) ? ask : null,
};
}
/**
* 获取指定档位的盘口价格
* @param depth 深度数据
* @param level 档位(1=买1/卖12=买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;
const last = Number(ticker?.lastPrice);
return Number.isFinite(last) ? last : null;
}
/**
* 计算从盘口一档到目标价格之间的挂单总量
* @param depth 深度数据
* @param side 挂单方向: BUY 检查 bids, SELL 检查 asks
* @param targetPrice 目标挂单价格
* @returns 从一档到目标价格之间的挂单总量 (不包含目标价格本身)
*/
export function getDepthBetweenPrices(
depth: AsterDepth | null | undefined,
side: "BUY" | "SELL",
targetPrice: number
): number {
if (!depth) return 0;
if (!Number.isFinite(targetPrice) || targetPrice <= 0) return 0;
let total = 0;
if (side === "BUY") {
// BUY 订单挂在 bid 侧,检查从 bid1 到目标价格之间的所有 bids
// bids 按价格从高到低排序,目标价格 < bid1
const bids = depth.bids ?? [];
for (const level of bids) {
const price = Number(level[0]);
const qty = Number(level[1]);
if (!Number.isFinite(price) || !Number.isFinite(qty)) continue;
// 只计算价格 > 目标价格的档位 (目标价格以上的挂单)
if (price > targetPrice) {
total += qty;
} else {
// bids 是从高到低排序,一旦 price <= targetPrice 就停止
break;
}
}
} else {
// SELL 订单挂在 ask 侧,检查从 ask1 到目标价格之间的所有 asks
// asks 按价格从低到高排序,目标价格 > ask1
const asks = depth.asks ?? [];
for (const level of asks) {
const price = Number(level[0]);
const qty = Number(level[1]);
if (!Number.isFinite(price) || !Number.isFinite(qty)) continue;
// 只计算价格 < 目标价格的档位 (目标价格以下的挂单)
if (price < targetPrice) {
total += qty;
} else {
// asks 是从低到高排序,一旦 price >= targetPrice 就停止
break;
}
}
}
return total;
}