mirror of
https://github.com/discountry/ritmex-bot.git
synced 2026-09-10 16:58:08 +00:00
更新 MakerEngine、OffsetMakerEngine 和 TrendEngine,重构订单同步逻辑,优化撤单和止损判断,新增订单计划生成和安全撤单功能,提升代码可读性和维护性。
This commit is contained in:
@@ -0,0 +1,42 @@
|
||||
import type { AsterDepth } from "../exchanges/types";
|
||||
|
||||
export type DepthImbalance = "balanced" | "buy_dominant" | "sell_dominant";
|
||||
|
||||
export function computeDepthStats(
|
||||
depth: AsterDepth,
|
||||
levels = 10,
|
||||
ratio = 3
|
||||
): {
|
||||
buySum: number;
|
||||
sellSum: number;
|
||||
skipBuySide: boolean;
|
||||
skipSellSide: boolean;
|
||||
imbalance: DepthImbalance;
|
||||
} {
|
||||
const topBids = (depth.bids ?? []).slice(0, levels);
|
||||
const topAsks = (depth.asks ?? []).slice(0, levels);
|
||||
|
||||
const buySum = topBids.reduce((total, level) => {
|
||||
const qty = Number(level?.[1]);
|
||||
return Number.isFinite(qty) ? total + qty : total;
|
||||
}, 0);
|
||||
|
||||
const sellSum = topAsks.reduce((total, level) => {
|
||||
const qty = Number(level?.[1]);
|
||||
return Number.isFinite(qty) ? total + qty : total;
|
||||
}, 0);
|
||||
|
||||
const skipSellSide = sellSum === 0 || sellSum * ratio < buySum;
|
||||
const skipBuySide = buySum === 0 || buySum * ratio < sellSum;
|
||||
|
||||
let imbalance: DepthImbalance = "balanced";
|
||||
if (buySum > sellSum * ratio) {
|
||||
imbalance = "buy_dominant";
|
||||
} else if (sellSum > buySum * ratio) {
|
||||
imbalance = "sell_dominant";
|
||||
}
|
||||
|
||||
return { buySum, sellSum, skipBuySide, skipSellSide, imbalance };
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
import type { PositionSnapshot } from "./strategy";
|
||||
|
||||
export function computePositionPnl(
|
||||
position: PositionSnapshot,
|
||||
bestBid?: number | null,
|
||||
bestAsk?: number | null
|
||||
): number {
|
||||
const priceForPnl = position.positionAmt > 0 ? bestBid : bestAsk;
|
||||
if (!Number.isFinite(priceForPnl as number)) return 0;
|
||||
const absAmt = Math.abs(position.positionAmt);
|
||||
return position.positionAmt > 0
|
||||
? ((priceForPnl as number) - position.entryPrice) * absAmt
|
||||
: (position.entryPrice - (priceForPnl as number)) * absAmt;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
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,
|
||||
};
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
import type { PositionSnapshot } from "./strategy";
|
||||
|
||||
export function shouldStopLoss(
|
||||
position: PositionSnapshot,
|
||||
bestBid: number,
|
||||
bestAsk: number,
|
||||
lossLimit: number
|
||||
): boolean {
|
||||
const absPosition = Math.abs(position.positionAmt);
|
||||
if (absPosition < 1e-5) return false;
|
||||
|
||||
const pnl = position.positionAmt > 0
|
||||
? (bestBid - position.entryPrice) * absPosition
|
||||
: (position.entryPrice - bestAsk) * absPosition;
|
||||
|
||||
const unrealized = Number.isFinite(position.unrealizedProfit)
|
||||
? (position.unrealizedProfit as number)
|
||||
: null;
|
||||
|
||||
const derivedLoss = pnl < -lossLimit;
|
||||
const snapshotLoss = Boolean(unrealized != null && unrealized < -lossLimit && pnl <= 0);
|
||||
|
||||
return derivedLoss || snapshotLoss;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user