量化项目研究学习(Copytrading Agent)

Copytrading Agent 架构分析

项目: Gajesh2007/copytrading-agent(42 stars)
语言: TypeScript 5.9 | 运行时: Node.js 20+ | 包管理: pnpm | 部署: Docker / EigenCloud (TEE)
分析目的: 提炼跟单交易系统的架构模式,特别是对账机制、仓位 Delta 计算和风控设计,与当前配对交易项目做对比


1. 项目概览

Copytrading Agent 是一个自动化跟单系统,实时镜像 Leader 账户的仓位到 Follower 账户,并附带可配置的风控参数。与同一作者的 ai-trading-agent (Nocturne) 相比,这个项目的架构成熟度明显更高 — 分层清晰、类型完备、关注点分离良好。

核心特点

  • 杠杆率复制 — 不复制绝对仓位大小,而是复制杠杆比率,按 Follower 权益缩放
  • 双机制状态同步 — WebSocket 实时成交 + 定时全量对账,互为补充
  • IOC 限价单 — 用 Mark Price ± 滑点的 IOC 限价单替代市价单,提供价格保护
  • Inverse 模式 — 一键切换反向交易(Long→Short, Short→Long)

技术栈

组件 技术 说明
交易所 SDK @nktkas/hyperliquid ^0.25 TypeScript 原生 Hyperliquid SDK
签名/账户 viem ^2.38 以太坊交互库(privateKeyToAccount)
WebSocket ws ^8.18 Node.js WebSocket 客户端
配置 dotenv ^16 环境变量管理
类型系统 TypeScript 5.9 strict 全量类型覆盖
部署 Docker + EigenCloud 支持 TEE 可信执行环境

2. 目录结构

copytrading-agent/
├── .env.example                    # 环境变量模板
├── .gitignore
├── .dockerignore
├── Dockerfile                      # 容器化部署
├── README.md
├── package.json                    # 依赖管理(pnpm)
├── pnpm-lock.yaml
├── tsconfig.json                   # TypeScript 配置(strict)
├── docker/                         # Docker 相关配置
├── docs/                           # 文档
├── frontend/                       # 前端面板(CSS/JS)
└── src/
    ├── index.ts                    # 主入口(启动、优雅关闭)
    ├── config/
    │   └── index.ts                # 配置加载 + 校验(类型化 env 解析)
    ├── clients/
    │   └── hyperliquid.ts          # SDK 客户端工厂(HTTP + WS + 签名)
    ├── domain/
    │   ├── types.ts                # 领域类型(PositionSnapshot, AccountMetrics)
    │   ├── traderState.ts          # 基础状态管理(增量 fill + 全量快照)
    │   ├── leaderState.ts          # Leader 状态 + 目标仓位计算
    │   └── followerState.ts        # Follower 状态 + Delta 计算
    ├── services/
    │   ├── subscriptions.ts        # WebSocket 订阅服务(Leader fills)
    │   ├── reconciler.ts           # 定时对账服务(全量状态同步)
    │   ├── tradeExecutor.ts        # 交易执行器(批量 IOC 限价单)
    │   └── marketMetadata.ts       # 市场元数据缓存(精度、杠杆上限、Mark 价)
    └── utils/
        ├── logger.ts               # 结构化日志(级别过滤)
        └── math.ts                 # 安全数学工具(toFloat、round、clamp、safeDivide)

代码量统计:~1200 行有效 TypeScript 代码,12 个源文件。


3. 核心架构分析

3.1 启动与生命周期管理

// src/index.ts — 启动流程
async function main() {
  const config = loadConfig();                    // 1. 加载配置
  const clients = createHyperliquidClients(config); // 2. 创建 SDK 客户端
  const leaderState = new LeaderState();          // 3. 初始化状态对象
  const followerState = new FollowerState();
  const metadataService = new MarketMetadataService(...);
  const tradeExecutor = new TradeExecutor(...);   // 4. 创建执行器
  const reconciler = new Reconciler(...);         // 5. 创建对账器
  const subscriptions = new SubscriptionService(...); // 6. 创建订阅服务

  await subscriptions.start();     // 7. 启动 WebSocket 订阅
  await reconciler.reconcileOnce(); // 8. 首次全量对账
  reconciler.start();              // 9. 启动定时对账循环
  void pollLoop();                 // 10. 启动轮询同步循环
}

优雅关闭

const shutdown = async (signal: string) => {
  logger.warn(`Received ${signal}, shutting down`);
  await subscriptions.stop();      // 1. 停止订阅(不再接收新 fill)
  reconciler.stop();               // 2. 停止对账(不再修改状态)
  await clients.wsTransport.close(); // 3. 关闭 WS 连接
  process.exit(0);
};
process.on("SIGINT", () => void shutdown("SIGINT"));
process.on("SIGTERM", () => void shutdown("SIGTERM"));

vs 当前项目:当前项目的 Python 服务应确保 signal.signal(SIGINT/SIGTERM) 时有序关闭 WS 连接、刷写 TimescaleDB 缓冲区、取消未完成订单。


3.2 杠杆率复制模型(核心创新)

这是本项目最有价值的设计 — 不复制绝对仓位大小,而是复制杠杆比率

原理

Leader 账户权益 = $100,000
Leader BTC 仓位 = 1.0 BTC @ $100,000 = $100,000 名义价值
Leader BTC 杠杆率 = $100,000 / $100,000 = 1.0x

Follower 账户权益 = $1,000
copyRatio = 0.5

Follower 目标杠杆率 = 1.0x × 0.5 = 0.5x
Follower 目标名义值 = 0.5x × $1,000 = $500
Follower 目标仓位 = $500 / $100,000 = 0.005 BTC

实现代码

// src/domain/leaderState.ts — 计算 Leader 当前杠杆率
computeTargets(metadataService: MarketMetadataService): TargetPosition[] {
  const leaderEquity = this.getMetrics().accountValueUsd;
  return Array.from(this.getPositions().values()).map((position) => {
    // 关键:用当前 Mark Price 而非 Entry Price 计算杠杆
    const markPrice = metadataService.getMarkPrice(position.coin) ?? position.entryPrice;
    const notionalUsd = Math.abs(position.size) * markPrice;
    const leaderLeverage = safeDivide(notionalUsd, leaderEquity, 0);
    return { coin: position.coin, leaderSize: position.size, leaderLeverage, markPrice };
  });
}
// src/domain/followerState.ts — 按比率缩放 + 风控约束
computeDeltas(targets: TargetPosition[], risk: RiskConfig): PositionDelta[] {
  const followerEquity = this.getMetrics().accountValueUsd;
  for (const target of targets) {
    const targetLeverage = target.leaderLeverage * risk.copyRatio;  // 按比率缩放
    const cappedLeverage = Math.min(targetLeverage, risk.maxLeverage); // 杠杆硬上限
    const targetNotional = cappedLeverage * followerEquity;           // 按权益计算名义值
    const allowedNotional = Math.min(targetNotional, risk.maxNotionalUsd); // 名义值硬上限
    const direction = Math.sign(target.leaderSize) * (risk.inverse ? -1 : 1);
    const allowedSize = direction * safeDivide(allowedNotional, price, 0);
    const deltaSize = allowedSize - (current?.size ?? 0);            // 计算差值
    // ...
  }
}

三层风控约束

  1. copyRatio — 杠杆缩放比例(第一道)
  2. maxLeverage — 杠杆绝对上限(第二道)
  3. maxNotionalUsd — 名义值绝对上限(第三道)

vs 当前项目:配对交易的两腿仓位大小分配可以参考这种"按权益比例 + 分层硬上限"模式,替代固定金额。


3.3 仓位 Delta 计算模型

干净的三步流水线:

LeaderState.computeTargets()          → TargetPosition[]
    ↓
FollowerState.computeDeltas(targets)  → PositionDelta[]
    ↓
TradeExecutor.syncWithLeader()        → 批量下单

每个 PositionDelta 统一描述五种操作:

场景 current targetSize deltaSize
开新仓 undefined +0.5 +0.5(买入)
加仓 +0.3 +0.5 +0.2(追加买入)
减仓 +0.5 +0.2 -0.3(部分卖出)
平仓 +0.5 0 -0.5(全部卖出)
翻转 +0.5 -0.3 -0.8(平仓 + 反向开仓)

Leader 已平仓但 Follower 仍持有的处理

// 遍历 Follower 持有但 Leader 不再持有的仓位,生成平仓 delta
for (const [coin, position] of this.getPositions()) {
  if (targetCoins.has(coin)) continue;  // Leader 仍持有,跳过
  if (Math.abs(position.size) < 1e-9) continue; // 忽略 dust
  deltas.push({
    coin, current: position, targetSize: 0,
    deltaSize: -position.size,  // 全部平掉
    maxNotionalUsd: 0,
  });
}

vs 当前项目:配对交易的两腿可以用类似的 delta 模型 — 先算出两腿的目标仓位(基于 Z-score 信号强度和账户权益),再统一计算差值,一次性批量下单。


4. 状态管理

4.1 TraderStateStore(增量/全量双模式)

// src/domain/traderState.ts — 基类
export class TraderStateStore {
  private readonly positions = new Map<string, PositionSnapshot>();
  private metrics: AccountMetrics;

  // 增量模式:处理 WebSocket fill 事件
  handleFillEvent(event: UserFillsEvent) {
    for (const fill of event.fills) {
      this.applyFill(fill as Fill);
    }
  }

  // 全量模式:从交易所 API 拉取完整状态(权威来源)
  applyClearinghouseState(state: ClearinghouseStateResponse) {
    this.positions.clear();  // 清空全部
    // 重建所有仓位...
  }
}

4.2 增量 Fill 处理的边界情况

applyFill() 方法覆盖了所有仓位变更场景:

private applyFill(fill: Fill) {
  const oldSize = existing?.size ?? toFloat(fill.startPosition);
  const signedFillSize = fill.side === "B" ? fillSize : -fillSize;
  const newSize = round(oldSize + signedFillSize, 9);

  // 完全平仓 → 删除仓位
  if (Math.abs(newSize) < EPSILON) {
    this.positions.delete(fill.coin);
    return;
  }

  if (!existing) {
    // 新开仓:入场价 = fill 价格
    newEntryPrice = fillPrice;
  } else {
    const sameDirection = Math.sign(oldSize) === Math.sign(newSize);
    if (sameDirection) {
      // 加仓:加权平均入场价
      newEntryPrice = safeDivide(
        oldNotional + fillNotional, Math.abs(newSize), fillPrice
      );
    } else {
      const remainingFill = Math.abs(fillSize) - closingSize;
      if (remainingFill > EPSILON) {
        // 翻转:新入场价 = fill 价格
        newEntryPrice = fillPrice;
      } else {
        // 减仓:保持原入场价
        newEntryPrice = existing.entryPrice;
      }
    }
  }
}

关键设计选择

  • 使用 EPSILON = 1e-9 过滤浮点 dust,避免因精度问题保留已平仓位
  • 翻转时重置入场价(符合交易所的 FIFO 结算逻辑)
  • 减仓时保持原入场价(未实现部分的成本不变)

vs 当前项目:当前项目用 TimescaleDB 持久化仓位状态,不依赖内存。但增量 fill 的加权均价计算逻辑值得参考 — 特别是配对交易加仓时需要正确计算两腿的平均入场价。


5. WebSocket 订阅 + 定时对账双机制

5.1 实时订阅(低延迟)

// src/services/subscriptions.ts
export class SubscriptionService {
  async start() {
    this.subscription = await this.subscriptionClient.userFills(
      { user: this.config.leaderAddress as `0x${string}` },
      (event) => {
        this.log.info("Received leader fills", { count: event.fills.length });
        this.leaderState.handleFillEvent(event);  // 增量更新
        this.onLeaderFill?.();                     // 触发同步
      },
      this.config.websocketAggregateFills,
    );
  }
}

5.2 定时对账(漂移修正)

// src/services/reconciler.ts
export class Reconciler {
  async reconcileOnce() {
    // 并行拉取 Leader 和 Follower 的完整状态
    const [leader, follower] = await Promise.all([
      this.infoClient.clearinghouseState({ user: this.config.leaderAddress }),
      this.infoClient.clearinghouseState({ user: this.followerAddress }),
    ]);
    this.leaderState.applyClearinghouseState(leader);   // 全量覆盖
    this.followerState.applyClearinghouseState(follower); // 全量覆盖
  }

  start() {
    void tick();  // 立即执行一次
    this.intervalHandle = setInterval(tick, this.config.reconciliationIntervalMs); // 定时循环
  }
}

为什么需要双机制

  • WebSocket 可能丢消息(网络抖动、重连期间)
  • 内存状态可能因浮点累积误差漂移
  • 交易所可能有外部操作(手动交易、清算)改变仓位
  • 定时对账用交易所状态作为权威来源,定期修正

vs 当前项目:当前项目的 realtime_kline_service 用 WebSocket 做 K 线推送,但交易执行层缺少对账机制。如果 WS 丢消息导致 position_manager 的内存状态与交易所不一致,可能触发错误的开平仓决策。建议增加定时 reconciliation


6. 交易执行

6.1 IOC 限价单 + 滑点保护

// src/services/tradeExecutor.ts(核心逻辑)
buildOrder(delta: PositionDelta, markPrice: number): OrderRequest {
  const isBuy = delta.deltaSize > 0;
  const slippageMultiplier = 1 + this.risk.maxSlippageBps / 10_000;

  // 买入:mark * (1 + slippage),卖出:mark * (1 - slippage)
  const limitPrice = isBuy
    ? markPrice * slippageMultiplier
    : markPrice / slippageMultiplier;

  return {
    coin: delta.coin,
    isBuy,
    sz: roundedSize,
    limitPx: roundToMarkPricePrecision(limitPrice, markPrice),
    orderType: { limit: { tif: "Ioc" } },   // IOC = Immediate-Or-Cancel
    reduceOnly: isReduceOnly,                 // 减仓时设置 reduce-only
    cloid: crypto.randomUUID(),               // 客户端订单 ID
  };
}

IOC 限价单的优势(vs 市价单):

  • 价格保护 — 成交价不会超过 mark ± slippage(默认 25bps = 0.25%)
  • 无挂单风险 — 未成交部分立即取消,不会挂在订单簿上
  • 滑点可控 — 适合流动性差的品种(如 PURR)

6.2 批量下单

// syncWithLeader() 中
const orders = deltas.map(d => this.buildOrder(d, ...));
// 一次性提交所有订单到交易所
const result = await this.exchangeClient.order({ orders, grouping: "na" });

vs 当前项目:当前项目的 executor.py 如果逐笔下单,在配对交易场景下两腿间会有时间差,可能导致单腿暴露。建议改为批量下单或至少尽量缩小两腿下单间隔。


7. 风控体系

7.1 RiskConfig 值对象

// src/config/index.ts
export interface RiskConfig {
  copyRatio: number;       // 杠杆缩放比例(0.5 = 用 Leader 一半杠杆)
  maxLeverage: number;     // 杠杆绝对上限
  maxNotionalUsd: number;  // 单仓名义值上限
  maxSlippageBps: number;  // 滑点上限(基点)
  inverse: boolean;        // 反向模式
}

7.2 三层仓位约束

Layer 1: copyRatio → 目标杠杆 = Leader杠杆 × copyRatio
Layer 2: maxLeverage → 目标杠杆 = min(目标杠杆, maxLeverage)
Layer 3: maxNotionalUsd → 目标名义值 = min(目标名义值, maxNotionalUsd)

7.3 最小交易过滤

// tradeExecutor.ts — 过滤 dust 交易
const MIN_DELTA = 1e-6;
const MIN_ORDER_NOTIONAL_USD = 10;

if (Math.abs(delta.deltaSize) < MIN_DELTA) continue;       // 仓位变化太小
if (Math.abs(delta.deltaSize) * markPrice < MIN_ORDER_NOTIONAL_USD) continue; // 名义值太小

7.4 Reduce-Only 标志

// 当前有仓位 且 deltaSize 方向与现有仓位相反 → reduce-only
const isReduceOnly = !!delta.current && Math.sign(delta.deltaSize) !== Math.sign(delta.current.size);

vs 当前项目:当前项目的风控体系(KillSwitch + CircuitBreaker + 日损限额)在安全性上更完善。但这个项目的分层仓位约束滑点保护可以补充到当前体系中。


8. 市场元数据服务

8.1 懒加载 + 缓存

// src/services/marketMetadata.ts
export class MarketMetadataService {
  private loaded = false;
  private readonly coinToMeta = new Map<string, AssetMetadata>();
  private readonly coinToMarkPx = new Map<string, number>();

  // 安全多次调用(幂等)
  async ensureLoaded(signal?: AbortSignal) {
    if (this.loaded) return;
    const [meta, contexts] = await this.infoClient.metaAndAssetCtxs(undefined, signal);
    // 解析 szDecimals、maxLeverage、markPx...
    this.loaded = true;
  }

  // 仅刷新价格,不重载元数据
  async refreshMarkPrices(signal?: AbortSignal) {
    if (!this.loaded) { await this.ensureLoaded(signal); return; }
    // 只更新 markPx...
  }
}

AssetMetadata 接口

export interface AssetMetadata {
  assetId: number;       // 交易所内部 ID
  coin: string;          // 币种符号
  maxLeverage: number;   // 最大杠杆
  sizeDecimals: number;  // 下单精度(szDecimals)
  marginTableId: number; // 保证金梯度表 ID
}

设计亮点

  • ensureLoaded() 幂等调用,避免重复加载
  • 元数据(不变)和价格(频繁变化)分开刷新,减少 API 调用
  • AbortSignal 支持,允许超时取消

9. SDK 客户端工厂

9.1 Node.js WebSocket 适配器

// src/clients/hyperliquid.ts — 桥接 ws 库到 DOM WebSocket API
class NodeWebSocketWrapper extends WebSocket {
  constructor(url: string | URL, protocols?: string | string[]) {
    super(typeof url === "string" ? url : url.toString(), protocols);
    this.binaryType = "arraybuffer"; // DOM 默认值(ws 默认是 "nodebuffer")
  }

  dispatchEvent(event: Event): boolean {
    // 实现 DOM 风格的事件分发
    const handler = (this as any)[`on${event.type}`];
    if (typeof handler === "function") handler.call(this, event);
    return super.emit(event.type, event);
  }
}

9.2 客户端初始化

export function createHyperliquidClients(config): HyperliquidClients {
  const httpTransport = new hl.HttpTransport({
    isTestnet: isTestnet(config.environment),
    timeout: 10_000,                          // 10 秒超时
  });

  const wsTransport = new hl.WebSocketTransport({
    isTestnet: isTestnet(config.environment),
    reconnect: {
      WebSocket: NodeWebSocketWrapper,
      maxRetries: Number.POSITIVE_INFINITY,    // 无限重连
    },
  });

  const exchangeClient = new hl.ExchangeClient({
    transport: httpTransport,
    wallet: followerAccount,
    ...(config.followerVaultAddress
      ? { defaultVaultAddress: config.followerVaultAddress }  // Vault 模式
      : {}),
    signatureChainId: async () =>
      isTestnet(config.environment) ? "0x66eee" : "0x1",     // EIP-712 chain ID
  });

  return { infoClient, exchangeClient, subscriptionClient, ... };
}

亮点

  • 无限重连策略(maxRetries: Infinity),适合 7×24 运行的交易系统
  • Vault 模式可选(通过 defaultVaultAddress 配置代签交易)
  • Testnet/Mainnet 通过单一 environment 参数切换

10. 配置管理

10.1 类型化环境变量加载

// src/config/index.ts
function requireEnv(key: string): string {
  const value = process.env[key];
  if (!value) throw new Error(`Missing required environment variable: ${key}`);
  return value;
}

function optionalNumberEnv(key: string, fallback: number): number {
  const raw = process.env[key];
  if (!raw) return fallback;
  const parsed = toFloat(raw);
  if (Number.isNaN(parsed)) throw new Error(`Invalid numeric value for ${key}: ${raw}`);
  return parsed;
}

function optionalBooleanEnv(key: string, fallback: boolean): boolean {
  const raw = process.env[key];
  if (!raw) return fallback;
  return ["1", "true", "yes", "on"].includes(raw.toLowerCase());
}

配置项一览

配置项 类型 默认值 说明
LEADER_ADDRESS string 必填 跟单目标地址
FOLLOWER_PRIVATE_KEY hex 必填 Follower 私钥
FOLLOWER_VAULT_ADDRESS hex? 可选 Vault 地址
COPY_RATIO number 1.0 杠杆缩放比例
MAX_LEVERAGE number 10 杠杆上限
MAX_NOTIONAL_USD number 250,000 单仓名义值上限
MAX_SLIPPAGE_BPS number 25 滑点上限(基点)
INVERSE boolean false 反向模式
RECONCILIATION_INTERVAL_MS number 60,000 对账间隔
REFRESH_ACCOUNT_INTERVAL_MS number 5,000 账户刷新间隔
AGGREGATE_FILLS boolean true WS 聚合 fills
LOG_LEVEL string "info" 日志级别

vs 当前项目的 config_loader:模式相似(都是类型化 env 解析),但这个项目的 boolean 解析支持 1/true/yes/on 四种格式,更鲁棒。


11. 工具函数

11.1 安全数学

// src/utils/math.ts
export function toFloat(value: string | number | bigint | undefined | null): number {
  if (value === null || value === undefined) return 0;
  if (typeof value === "number") return value;
  if (typeof value === "bigint") return Number(value);
  const parsed = Number(value);
  if (Number.isNaN(parsed)) throw new Error(`Unable to parse numeric value: ${value}`);
  return parsed;
}

export function safeDivide(numerator: number, denominator: number, fallback = 0): number {
  if (Math.abs(denominator) < Number.EPSILON) return fallback;
  return numerator / denominator;
}

export function round(value: number, decimals = 6): number {
  const factor = 10 ** decimals;
  return Math.round(value * factor) / factor;
}

export function clamp(value: number, min: number, max: number): number {
  return Math.min(Math.max(value, min), max);
}

11.2 结构化日志

// src/utils/logger.ts — 单例 logger,支持依赖注入
export const logger = {
  debug: (message: string, meta?: Record<string, unknown>) => log("debug", message, meta),
  info:  (message: string, meta?: Record<string, unknown>) => log("info", message, meta),
  warn:  (message: string, meta?: Record<string, unknown>) => log("warn", message, meta),
  error: (message: string, meta?: Record<string, unknown>) => log("error", message, meta),
};
export type Logger = typeof logger; // 导出类型供 DI 使用

设计亮点:Logger 作为 type 导出,所有 Service/Domain 类通过构造函数注入,便于测试时 mock。


12. 与当前项目的对比矩阵

维度 copytrading-agent 当前配对交易项目 评价
语言 TypeScript (strict) Python 3.x 各有优势
架构 分层(domain/services/clients) 分层(trading/services/utils) 相当
类型安全 TS strict + 完整接口定义 Python 动态类型 TS 更强
状态管理 内存 Map + 定时对账 TimescaleDB + 崩溃恢复 当前更强
持久化 无(重启丢状态) TimescaleDB 全量持久化 当前更强
风控体系 3 层约束(ratio/leverage/notional) KillSwitch + CircuitBreaker + 日损限额 当前更全
对账机制 ✅ 60s 定时全量对账 ❌ 缺少 应借鉴
订单类型 ✅ IOC 限价 + 滑点保护 市价单 应借鉴
仓位计算 ✅ Delta 模型(统一 5 种场景) 分散在各模块 应借鉴
批量下单 ✅ 一次提交所有订单 逐笔下单 应借鉴
信号系统 无(纯跟单) 协整 + Z-score + 多周期确认 当前更强
WebSocket SDK 原生支持 + 无限重连 自建 WS Manager + 去重 各有特色
优雅关闭 ✅ SIGINT/SIGTERM 有序清理 待确认 应借鉴
Vault 支持 ✅ 可选 Vault 代签 可选功能
测试 Node test runner 测试脚本 相当
通知 飞书机器人 当前更强
部署 Docker + EigenCloud (TEE) Docker 相当

13. 独特设计模式详解

模式 1:杠杆率复制(Leverage Ratio Copying)

问题:直接复制绝对仓位大小在不同规模账户间不合理($100 账户复制 $100K 账户的仓位会爆仓)。

解法:复制杠杆比率而非仓位大小,然后按 Follower 权益缩放。

可借鉴场景:配对交易中,两腿仓位大小应该基于账户权益动态计算,而不是写死固定金额。当账户权益因盈亏变化时,仓位大小自动调整。

模式 2:增量+全量双模式状态管理

问题:纯 WebSocket 推送可能丢消息;纯 API 轮询延迟高。

解法

  • 常态下用 WebSocket fills 做增量更新(毫秒级延迟)
  • 定时用 clearinghouseState API 做全量覆盖(秒级延迟,但权威)
  • 增量优先,全量纠偏

可借鉴场景:当前项目的仓位状态应增加定时对账,防止 WS 丢消息导致的状态偏移。

模式 3:IOC 限价单替代市价单

问题:市价单在低流动性品种上滑点不可控。

解法:用 mark price ± slippage 的 IOC 限价单 — 立即成交的部分立刻执行,超出滑点的部分自动取消。

可借鉴场景:PURR 等低流动性配对交易品种,IOC 限价单可以避免极端滑点。

模式 4:Inverse 模式

问题:跟单系统通常只支持同向复制。

解法risk.inverse 标志翻转方向,一行代码实现反向交易。

可借鉴场景:配对交易天然包含一正一反两腿,可以用类似的 inverse 标志统一处理 long leg 和 short leg 的下单逻辑。

模式 5:Logger 类型导出用于依赖注入

问题:直接 import logger 单例导致测试困难。

解法:导出 type Logger = typeof logger,所有类通过构造函数注入 logger,测试时可以传入 mock。

可借鉴场景:Python 项目中也应通过构造函数或 setter 注入 logger,而非模块级 import logging


14. 架构缺陷警示(应避免)

  1. 无持久化 — 重启丢失所有状态。虽然有对账机制可以恢复仓位快照,但历史交易记录、PnL 统计全部丢失。当前项目的 TimescaleDB 方案更优。

  2. 无交易日志持久化 — 没有 diary/trade log 文件,无法事后审计。建议所有交易操作都应持久化记录。

  3. 无 Kill Switch — 没有紧急停止机制。当 Leader 疯狂交易或市场极端波动时,系统会持续跟单。当前项目的 KillSwitch + CircuitBreaker 更安全。

  4. 无日损限额maxNotionalUsd 限制的是单仓名义值,没有日级/累计亏损限额。如果 Leader 频繁止损,Follower 可能在一天内被小额亏损累积消耗。

  5. 无 Rate Limiting — 对交易所 API 调用没有频率限制。在高频同步场景下可能触发交易所限流。

  6. WebSocket 重连期间的状态空白 — 虽然设置了无限重连,但重连期间的 fills 会丢失,只能等下次对账修正。重连后应立刻触发一次 reconcileOnce()。

  7. Error Handling 不够精细tradeExecutor.syncWithLeader() 的错误只区分 margin 不足和其他错误,没有对网络超时、交易所拒绝等场景做针对性处理。


15. 总结

copytrading-agent 的核心价值

设计模式 借鉴价值 实施难度 说明
定时对账机制(Reconciliation) 60s 全量对账,防止状态偏移
IOC 限价单 + 滑点保护 替代市价单,价格可控
仓位 Delta 计算模型 统一 5 种场景的 delta 计算
杠杆率复制 / 权益比例分配 按账户权益动态计算仓位大小
Inverse 标志统一正反方向 配对交易两腿可复用同一逻辑
优雅关闭(Graceful Shutdown) 有序清理连接和状态
Logger DI 类型导出 便于测试和 mock
批量下单 中-高 减少两腿间时间差

行动清单

  • [ ] P0position_manager.py 增加定时 reconciliation(每 60s 从交易所拉取全量持仓对账)
  • [ ] P0executor.py 将市价单改为 IOC 限价单 + 可配置滑点上限
  • [ ] P1 实现统一的 compute_deltas() 方法,计算两腿目标仓位差值
  • [ ] P1 仓位大小改为按账户权益比例 + 分层硬上限计算
  • [ ] P1 确保 Python 服务的 SIGINT/SIGTERM 处理有序关闭 WS、刷写 DB
  • [ ] P2 为配对交易两腿引入 inverse 标志,统一下单逻辑
  • [ ] P2 尝试批量下单(一次提交两腿订单),减少单腿暴露时间

Read more

跑步的技巧(滚动落地)

“滚动落地(rolling contact / rolling foot strike)”不是一种教条式的“脚法”,而是一种 让冲击沿着整只脚、整条后链逐级传递的落地机制。 它的核心不是“你先用哪儿着地”,而是: 你的脚落地之后,冲击是不是像轮子一样滚过去,而不是像锤子一样砸下去。 这就是滚动落地的本质。 一、什么叫“滚动落地”? 你可以把它理解成两种完全不同的落地方式: 1. 砸地(撞击式) 脚像锤子一样拍到地上: * 要么后跟先砸 * 要么前掌先戳 * 冲击集中在一个点 * 一个结构瞬间吃掉大部分载荷 结果就是: * 后跟砸 → 膝盖难受 * 前掌戳 → 前脚掌磨烂 * 都不是长跑友好模式 这叫 撞击式着地(impact strike)。 2. 滚地(滚动式) 脚像轮胎一样“滚”过地面: * 不是某一点硬砸 * 而是外侧中足先轻触 * 再向前滚到前掌 * 最后从大脚趾蹬离

By SHI XIAOLONG

AMI的优越性

世界模型(World Models)的具体例子 如下,我按类型分类,便于理解。每类都附带实际实现、演示效果和应用场景。 1. Yann LeCun / Meta 的 JEPA 系列(最直接对应“世界模型”概念) 这些是 LeCun 主张的非生成式抽象预测世界模型代表。 * I-JEPA(Image JEPA,2023) 输入一张图像,模型把不同区域(context 和 target)编码成抽象表示,然后预测 target 的表示(不在像素级别重建)。 例子:给定一张遮挡了部分物体的图片,模型能预测“被遮挡物体的大致位置和属性”,构建对物体持久性和空间关系的理解。 这是一个“原始世界模型”,能学习物理常识(如物体不会凭空消失)。 * V-JEPA / V-JEPA 2(Video JEPA,

By SHI XIAOLONG

什么是:“世界模型(World Models)”

世界模型(World Models) 是人工智能领域的一个核心概念,尤其在 Yann LeCun 等研究者推动的下一代 AI 架构中占据中心位置。它指的是 AI 系统在内部构建的对现实世界的抽象模拟或内部表示,让机器能够像人类或动物一样“理解”物理世界、预测未来、规划行动。 简单比喻 想象你闭上眼睛也能“看到”房间里的物体会如何移动、碰撞或掉落——这就是你大脑里的世界模型。AI 的世界模型就是类似的“数字孪生”(digital twin)或“内部模拟器”:它不是简单记住数据,而是学习世界的动态、因果关系和物理直觉(如重力、物体持久性、遮挡、因果等)。 为什么需要世界模型? 当前主流的大型语言模型(LLM) 擅长处理文本(统计模式预测),但存在根本局限: * 缺乏对物理世界的真正理解 → 容易“幻觉”、无法可靠规划。 * 样本效率低 → 人类/

By SHI XIAOLONG

K线周期可配置化设计方案

K线周期可配置化设计方案 1. 背景与目标 当前 Beta 套利策略的 K 线周期硬编码为 "1h",分散在多个文件中。需要: 1. 将 K 线周期从 1h 改为 2h 2. 提取为环境变量 BETA_ARB_KLINE_INTERVAL,使其可在 .env 中配置 2. 影响范围分析 2.1 需要修改的文件(共 6 个) 文件 硬编码位置 修改内容 src/trading/config.py BetaArbConfig dataclass 新增 kline_interval 字段,

By SHI XIAOLONG