mirror of
https://github.com/discountry/ritmex-bot.git
synced 2026-09-09 08:18:07 +00:00
Compare commits
4
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
37476ea4bc | ||
|
|
ffd62fd481 | ||
|
|
511cf8669d | ||
|
|
968b585688 |
+16
-45
@@ -116,25 +116,17 @@ export interface BasisArbConfig {
|
||||
arbAmount: number; // base asset amount to arb (e.g., ASTER amount when ASTERUSDT)
|
||||
}
|
||||
|
||||
export type GridDirection = "both" | "long" | "short";
|
||||
|
||||
export interface GridConfig {
|
||||
symbol: string;
|
||||
lowerPrice: number;
|
||||
upperPrice: number;
|
||||
gridLevels: number;
|
||||
orderSize: number;
|
||||
maxPositionSize: number;
|
||||
tradeAmount: number;
|
||||
refreshIntervalMs: number;
|
||||
maxLogEntries: number;
|
||||
priceTick: number;
|
||||
qtyStep: number;
|
||||
direction: GridDirection;
|
||||
stopLossPct: number;
|
||||
restartTriggerPct: number;
|
||||
autoRestart: boolean;
|
||||
gridMode: "geometric";
|
||||
maxCloseSlippagePct: number;
|
||||
levelsPerSide: number;
|
||||
spacingPct: number;
|
||||
stopLossBufferPct: number;
|
||||
maxLogEntries: number;
|
||||
maxPositionSize: number;
|
||||
}
|
||||
|
||||
const resolveBasisSymbol = (envKeys: string[], fallback: string): string => {
|
||||
@@ -162,17 +154,8 @@ export const basisConfig: BasisArbConfig = {
|
||||
arbAmount: parseNumber(process.env.ARB_AMOUNT, parseNumber(process.env.TRADE_AMOUNT, 0)),
|
||||
};
|
||||
|
||||
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 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;
|
||||
@@ -180,30 +163,18 @@ const resolveGridMaxPosition = (orderSize: number, levels: number): number => {
|
||||
|
||||
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),
|
||||
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),
|
||||
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",
|
||||
maxCloseSlippagePct: Math.max(
|
||||
0,
|
||||
parseNumber(
|
||||
process.env.GRID_MAX_CLOSE_SLIPPAGE_PCT ?? process.env.MAX_CLOSE_SLIPPAGE_PCT,
|
||||
0.05
|
||||
)
|
||||
),
|
||||
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.orderSize, gridConfig.gridLevels);
|
||||
gridConfig.maxPositionSize = resolveGridMaxPosition(gridConfig.tradeAmount);
|
||||
|
||||
export function isBasisStrategyEnabled(): boolean {
|
||||
const raw = process.env.ENABLE_BASIS_STRATEGY;
|
||||
|
||||
@@ -1201,6 +1201,7 @@ function toApiOrderPayload(order: GrvtSignedOrder): IOrder {
|
||||
const metadata = order.metadata ? toApiOrderMetadata(order.metadata) : undefined;
|
||||
return {
|
||||
...order,
|
||||
client_order_id: order.metadata?.client_order_id,
|
||||
time_in_force: toApiTimeInForce(order.time_in_force),
|
||||
metadata,
|
||||
};
|
||||
@@ -1300,7 +1301,7 @@ function buildUnsignedOrder(params: {
|
||||
|
||||
const trigger = buildTriggerMetadata(orderParams);
|
||||
const metadata = {
|
||||
client_order_id: generateClientOrderId(),
|
||||
client_order_id: orderParams.clientOrderId ?? generateClientOrderId(),
|
||||
...(trigger ? { trigger } : {}),
|
||||
};
|
||||
|
||||
|
||||
@@ -22,6 +22,9 @@ function applyCommonFields(params: CreateOrderParams, intent: BaseOrderIntent):
|
||||
if (intent.closePosition !== undefined) {
|
||||
params.closePosition = toStringBoolean(intent.closePosition);
|
||||
}
|
||||
if (intent.clientOrderId) {
|
||||
params.clientOrderId = intent.clientOrderId;
|
||||
}
|
||||
return params;
|
||||
}
|
||||
|
||||
@@ -34,6 +37,7 @@ export async function createLimitOrder(intent: LimitOrderIntent): Promise<AsterO
|
||||
quantity: intent.quantity,
|
||||
price: intent.price,
|
||||
timeInForce: intent.timeInForce ?? "GTX",
|
||||
clientOrderId: intent.clientOrderId,
|
||||
},
|
||||
intent
|
||||
);
|
||||
@@ -89,4 +93,3 @@ export async function createClosePositionOrder(intent: ClosePositionIntent): Pro
|
||||
);
|
||||
return intent.adapter.createOrder(params);
|
||||
}
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ export interface BaseOrderIntent {
|
||||
reduceOnly?: boolean;
|
||||
closePosition?: boolean;
|
||||
timeInForce?: TimeInForce | "GTX";
|
||||
clientOrderId?: string;
|
||||
}
|
||||
|
||||
export interface LimitOrderIntent extends BaseOrderIntent {
|
||||
@@ -39,4 +40,3 @@ export function toStringBoolean(value: boolean | undefined): "true" | "false" |
|
||||
if (value === undefined) return undefined;
|
||||
return value ? "true" : "false";
|
||||
}
|
||||
|
||||
|
||||
@@ -599,8 +599,8 @@ export class ParadexGateway {
|
||||
|
||||
try {
|
||||
const isClosePosition = (extraParams as any).closePosition === true;
|
||||
// Only omit amount for MARKET close-position orders; STOP requires explicit size
|
||||
const shouldOmitAmount = isClosePosition && type === "market";
|
||||
// Paradex requires explicit size for close-position MARKET orders; always send amount
|
||||
const shouldOmitAmount = false;
|
||||
const amountArg: any = shouldOmitAmount ? undefined : amount;
|
||||
if (!shouldOmitAmount && amountArg != null && extraParams.size === undefined) {
|
||||
extraParams.size = amountArg.toString();
|
||||
|
||||
@@ -25,6 +25,7 @@ export interface CreateOrderParams {
|
||||
reduceOnly?: StringBoolean;
|
||||
closePosition?: StringBoolean;
|
||||
triggerType?: "UNSPECIFIED" | "TAKE_PROFIT" | "STOP_LOSS";
|
||||
clientOrderId?: string;
|
||||
}
|
||||
|
||||
export interface AsterAccountPosition {
|
||||
|
||||
@@ -1,79 +0,0 @@
|
||||
import { promises as fs } from "fs";
|
||||
import path from "path";
|
||||
import type { GridDirection } from "../../config";
|
||||
|
||||
const DATA_DIR = process.env.GRID_DATA_DIR?.trim() || path.resolve("data");
|
||||
const GRID_FILE = path.resolve(DATA_DIR, "grid-record.json");
|
||||
|
||||
export interface StoredGridState {
|
||||
symbol: string;
|
||||
lowerPrice: number;
|
||||
upperPrice: number;
|
||||
gridLevels: number;
|
||||
orderSize: number;
|
||||
maxPositionSize: number;
|
||||
direction: GridDirection;
|
||||
longExposure: Record<string, number>;
|
||||
shortExposure: Record<string, number>;
|
||||
updatedAt: number;
|
||||
}
|
||||
|
||||
type GridStateMap = Record<string, StoredGridState>;
|
||||
|
||||
async function ensureDataDir(): Promise<void> {
|
||||
try {
|
||||
await fs.mkdir(DATA_DIR, { recursive: true });
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
|
||||
async function readStateFile(): Promise<GridStateMap> {
|
||||
try {
|
||||
const content = await fs.readFile(GRID_FILE, "utf8");
|
||||
const parsed = JSON.parse(content);
|
||||
if (parsed && typeof parsed === "object") {
|
||||
return parsed as GridStateMap;
|
||||
}
|
||||
return {};
|
||||
} catch (error: any) {
|
||||
if (error && (error.code === "ENOENT" || error.code === "ENOTDIR")) {
|
||||
return {};
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
export async function loadGridState(symbol: string): Promise<StoredGridState | null> {
|
||||
const map = await readStateFile();
|
||||
const snapshot = map[symbol];
|
||||
return snapshot ?? null;
|
||||
}
|
||||
|
||||
export async function saveGridState(snapshot: StoredGridState): Promise<void> {
|
||||
await ensureDataDir();
|
||||
const map = await readStateFile();
|
||||
map[snapshot.symbol] = snapshot;
|
||||
await fs.writeFile(GRID_FILE, JSON.stringify(map, null, 2), "utf8");
|
||||
}
|
||||
|
||||
export async function clearGridState(symbol: string): Promise<void> {
|
||||
const map = await readStateFile();
|
||||
if (!Object.prototype.hasOwnProperty.call(map, symbol)) {
|
||||
return;
|
||||
}
|
||||
delete map[symbol];
|
||||
const entries = Object.keys(map);
|
||||
if (!entries.length) {
|
||||
try {
|
||||
await fs.unlink(GRID_FILE);
|
||||
} catch (error: any) {
|
||||
if (!error || (error.code !== "ENOENT" && error.code !== "ENOTDIR")) {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
await ensureDataDir();
|
||||
await fs.writeFile(GRID_FILE, JSON.stringify(map, null, 2), "utf8");
|
||||
}
|
||||
+484
-1106
File diff suppressed because it is too large
Load Diff
+4
-4
@@ -118,10 +118,10 @@ export function GridApp({ onExit }: GridAppProps) {
|
||||
<Box flexDirection="column" marginBottom={1}>
|
||||
<Text color="cyanBright">Grid Strategy Dashboard</Text>
|
||||
<Text>
|
||||
交易所: {exchangeName} | 交易对: {snapshot.symbol} | 状态: {snapshot.running ? "运行中" : "暂停"} | 方向: {snapshot.direction}
|
||||
交易所: {exchangeName} | 交易对: {snapshot.symbol} | 状态: {snapshot.running ? "运行中" : "暂停"}
|
||||
</Text>
|
||||
<Text>
|
||||
实时价格: {formatNumber(snapshot.lastPrice, 4)} | 下界: {formatNumber(snapshot.lowerPrice, 4)} | 上界: {formatNumber(snapshot.upperPrice, 4)} | 网格数量: {snapshot.gridLines.length}
|
||||
实时价格: {formatNumber(snapshot.lastPrice, 4)} | 中心价: {formatNumber(snapshot.centerPrice, 4)} | 下界: {formatNumber(snapshot.lowerPrice, 4)} | 上界: {formatNumber(snapshot.upperPrice, 4)} | 网格数量: {snapshot.gridLines.length}
|
||||
</Text>
|
||||
<Text color="gray">数据状态:
|
||||
{feedEntries.map((entry, index) => (
|
||||
@@ -139,10 +139,10 @@ export function GridApp({ onExit }: GridAppProps) {
|
||||
<Box flexDirection="column" marginRight={4}>
|
||||
<Text color="greenBright">网格配置</Text>
|
||||
<Text>
|
||||
单笔数量: {formatNumber(gridConfig.orderSize, 6)} | 最大仓位: {formatNumber(gridConfig.maxPositionSize, 6)}
|
||||
单笔数量: {formatNumber(gridConfig.tradeAmount, 6)} | 每侧格子数: {gridConfig.levelsPerSide} | 最大仓位: {formatNumber(gridConfig.maxPositionSize, 6)}
|
||||
</Text>
|
||||
<Text>
|
||||
止损阈值: {(gridConfig.stopLossPct * 100).toFixed(2)}% | 重启阈值: {(gridConfig.restartTriggerPct * 100).toFixed(2)}% | 自动重启: {gridConfig.autoRestart ? "启用" : "关闭"}
|
||||
网格步长: {(gridConfig.spacingPct * 100).toFixed(3)}% | 止损缓冲: {(gridConfig.stopLossBufferPct * 100).toFixed(3)}%
|
||||
</Text>
|
||||
<Text>
|
||||
刷新间隔: {gridConfig.refreshIntervalMs} ms
|
||||
|
||||
Reference in New Issue
Block a user