外汇三角套利:EUR/USD、GBP/USD、EUR/GBP 的实时套利监控

阅读时间:约 15 分钟 | 技术门槛:中高级 | 代码复杂度:⭐⭐⭐⭐


“在外汇市场,三角套利是极少数能在理论上消除风险、让数学站在你这边的策略。但正是这种'无风险'的光环,让它成为散户最难复现的机构游戏。”

大多数交易者对三角套利的认知停留在教科书层面:EUR/USD、GBP/USD、EUR/GBP 三个货币对之间存在一个数学恒等式,当实际汇率偏离这个恒等式时,理论上可以买入被低估的、卖出被高估的,实现无风险利润。

问题在于:教科书没有告诉你的是——这个"无风险"的前提是毫秒级的执行速度无限流动性的市场。当你在散户账户上按下回车键时,机构算法已经完成了 200 次这样的操作。

本文拆解三角套利的数学本质,给出生产级的实时监控代码,让你至少能先"看见"套利窗口的开启与关闭——至于能否在它关闭前入场,那是另一个问题。


一、三角套利的数学原理

1.1 什么是三角套利

三角套利(Triangular Arbitrage)的核心逻辑藏在一个简单的恒等式里:

EUR/USD × GBP/USD = EUR/GBP

这个等式意味着:先用美元买入欧元,再用欧元买入英镑,最后把英镑换回美元。如果汇率完全精确,这个闭环不会产生任何利润——1 美元无论怎么周转,最终还是 1 美元。

但市场不是数学。市场报价由无数参与者共同决定,存在微小的时间差和信息差。因此:

EUR/USD × GBP/USD ≠ EUR/GBP

这个偏离就是套利机会。

1.2 套利利润的计算

假设三个货币对的报价如下:

货币对 报价 说明
EUR/USD 1.0850 / 1.0852 买入价 1.0850,卖出价 1.0852
GBP/USD 1.2650 / 1.2652 买入价 1.2650,卖出价 1.2652
EUR/GBP 0.8570 / 0.8573 买入价 0.8570,卖出价 0.8573

三角套利的关键在于买卖方向的组合。我们以 1,000,000 美元为初始资金,演示两种路径:

路径 A:美元 → 欧元 → 英镑 → 美元

1. 卖出 USD,买入 EUR:1,000,000 / 1.0852 = 921,497.98 EUR
2. 卖出 EUR,买入 GBP:921,497.98 × 0.8570 = 789,723.41 GBP
3. 卖出 GBP,买入 USD:789,723.41 × 1.2650 = 999,000.18 USD

路径 B:美元 → 英镑 → 欧元 → 美元

1. 卖出 USD,买入 GBP:1,000,000 / 1.2652 = 790,384.68 GBP
2. 卖出 GBP,买入 EUR:790,384.68 × (1/0.8573) = 922,042.15 EUR
3. 卖出 EUR,买入 USD:922,042.15 × 1.0850 = 1,000,015.72 USD

路径 B 产生了 15.72 美元的无风险利润。

1.3 汇率偏离度指标

在实际监控中,我们用隐含汇率 vs 实际汇率的偏离度来判断套利窗口:

# 三角套利偏离度公式
implied_EUR_GBP = (EUR_USD_bid / GBP_USD_ask)  # 隐含 EUR/GBP
deviation = (implied_EUR_GBP - EUR_GBP_bid) / EUR_GBP_bid * 10000  # 单位:bp

# 偏离度 > 交易成本 → 存在套利窗口
# 假设单边交易成本为 1bp,三角总成本约 3bp
# 当 deviation > 3bp 时,理论上存在利润空间

偏离度的单位通常用基点(Basis Point, bp)——1 bp = 0.01%,1 万点等于 100%。


二、为什么散户难以捕捉套利窗口

2.1 窗口的寿命以毫秒计

在外汇这个日均交易量超过 7.5 万亿美元的市场,机构算法每秒扫描数千次价格。它们使用专用的光纤线路、直连交易所的服务器、以及 FPGA 硬件加速——价格偏离的瞬间,它们的订单已经在路上了。

散户面临的是三重障碍:

障碍 散户 机构
信息延迟 100-500ms <1ms
执行延迟 500ms-2s <10ms
交易成本 固定点差 + 佣金 银行间市场零佣金

理论上存在的套利窗口,在散户能够行动之前,往往已经关闭。

2.2 流动性与滑点

教科书假设你可以"无限量"地买卖而不影响价格。现实是:

  • 你的订单量越大,市场冲击成本越高
  • 非农就业数据发布时段,流动性急剧萎缩
  • 银行报价可能在毫秒内撤回

因此,即使检测到套利机会,还需要计算净收益 = 理论利润 - 交易成本 - 滑点 - 延迟损耗

2.3 散户的最优策略

这不是让你放弃。散户有两种可行的路径:

路径一:做套利窗口的"记录者"而非"参与者"

监控套利窗口的出现频率、持续时间、幅度分布,建立对市场微观结构的深度认知。这本身就是有价值的数据产品。

路径二:关注套利窗口与宏观事件的关联

在特定时段(如央行决议、非农数据发布前后),套利窗口出现的频率和幅度会显著增加。找到这种关联,本身就是 alpha。

无论哪条路,第一步都是构建可靠的价格监控基础设施——这正是本文的核心。


三、实时监控架构设计

3.1 为什么需要 WebSocket 而非轮询

三角套利需要三个货币对的同一时刻价格。轮询模式下:

  • 你请求 EUR/USD,得到 10:00:00.100 的价格
  • 你请求 GBP/USD,得到 10:00:00.350 的价格
  • 你请求 EUR/GBP,得到 10:00:00.600 的价格

三个时间戳不同步,计算出的偏离度存在严重的时间偏差——上一个时刻的 EUR/USD + 当前时刻的 GBP/USD,这已经不是三角套利,而是随机赌博。

WebSocket 推送解决了这个问题:所有价格更新在同一数据流中携带时间戳,你可以缓存最新报价,计算窗口时使用同一时刻的数据。

3.2 系统架构图

┌─────────────────────────────────────────────────────────────────┐
│                     TickDB WebSocket 实时数据流                   │
└────────────────────┬─────────────────────────────┬───────────────┘
                     │                             │
          ┌──────────▼──────────┐     ┌───────────▼───────────┐
          │   EUR/USD 频道       │     │   GBP/USD 频道         │
          └──────────┬──────────┘     └───────────┬───────────┘
                     │                             │
                     └──────────┬──────────────────┘
                                │
                     ┌──────────▼──────────┐
                     │   价格缓存层(Redis)  │
                     │  - 保留最近 100 条     │
                     │  - 自动过期:500ms    │
                     └──────────┬──────────┘
                                │
                     ┌──────────▼──────────┐
                     │   套利计算引擎       │
                     │  - 偏离度计算         │
                     │  - 交易成本估算       │
                     │  - 窗口识别           │
                     └──────────┬──────────┘
                                │
                     ┌──────────▼──────────┐
                     │   告警层            │
                     │  - 飞书 WebHook     │
                     │  - Slack            │
                     │  - 日志持久化        │
                     └─────────────────────┘

3.3 套利窗口识别算法

class TriangularArbitrageDetector:
    """
    三角套利窗口检测器
    
    检测逻辑:
    1. 维护三个货币对的最新买卖价
    2. 计算两种路径的隐含利润
    3. 当利润 > 交易成本时,触发告警
    """
    
    def __init__(self, cost_bp=3.0):
        self.prices = {
            'EUR_USD': {'bid': None, 'ask': None, 'timestamp': None},
            'GBP_USD': {'bid': None, 'ask': None, 'timestamp': None},
            'EUR_GBP': {'bid': None, 'ask': None, 'timestamp': None},
        }
        self.cost_bp = cost_bp  # 三角总交易成本(基点)
        self.window_history = []
    
    def update_price(self, pair, bid, ask):
        """更新单个货币对的价格"""
        self.prices[pair] = {
            'bid': bid,
            'ask': ask,
            'timestamp': time.time()
        }
    
    def check_data_freshness(self):
        """
        检查三个货币对的数据是否足够新鲜
        超过 500ms 的数据视为过期
        """
        if any(p['bid'] is None for p in self.prices.values()):
            return False
        
        now = time.time()
        max_age = 0.5  # 500ms
        
        for pair, data in self.prices.items():
            age = now - data['timestamp']
            if age > max_age:
                logger.warning(f"{pair} 数据过期: {age*1000:.1f}ms")
                return False
        return True
    
    def calculate_path_profit(self, initial_usd=1_000_000):
        """
        计算两种套利路径的利润
        
        返回:
            dict: {
                'path_A_profit': 美元金额,
                'path_B_profit': 美元金额,
                'best_profit': 最大利润,
                'deviation_bp': 偏离度基点
            }
        """
        eur_usd = self.prices['EUR_USD']
        gbp_usd = self.prices['GBP_USD']
        eur_gbp = self.prices['EUR_GBP']
        
        # 路径 A: USD -> EUR -> GBP -> USD
        # 卖 USD 买 EUR(用 ask),卖 EUR 买 GBP(用 bid),卖 GBP 买 USD(用 bid)
        step1_A = initial_usd / eur_usd['ask']  # USD -> EUR
        step2_A = step1_A * eur_gbp['bid']       # EUR -> GBP
        step3_A = step2_A * gbp_usd['bid']       # GBP -> USD
        
        # 路径 B: USD -> GBP -> EUR -> USD
        # 卖 USD 买 GBP(用 ask),卖 GBP 买 EUR(用 bid),卖 EUR 买 USD(用 bid)
        step1_B = initial_usd / gbp_usd['ask']  # USD -> GBP
        step2_B = step1_B / eur_gbp['ask']       # GBP -> EUR (汇率反向)
        step3_B = step2_B * eur_usd['bid']       # EUR -> USD
        
        profit_A = step3_A - initial_usd
        profit_B = step3_B - initial_usd
        
        # 计算偏离度
        implied_eur_gbp = eur_usd['bid'] / gbp_usd['ask']
        deviation_bp = (implied_eur_gbp - eur_gbp['bid']) / eur_gbp['bid'] * 10000
        
        return {
            'path_A_profit': profit_A,
            'path_B_profit': profit_B,
            'best_profit': max(profit_A, profit_B),
            'deviation_bp': deviation_bp,
            'net_profit': max(profit_A, profit_B) - (initial_usd * self.cost_bp / 10000)
        }
    
    def detect_window(self, min_profit=10.0):
        """
        检测是否存在可交易的套利窗口
        
        参数:
            min_profit: 最小利润阈值(美元)
        
        返回:
            dict 或 None: 如果存在窗口,返回窗口详情;否则返回 None
        """
        if not self.check_data_freshness():
            return None
        
        result = self.calculate_path_profit()
        
        if result['net_profit'] >= min_profit:
            window = {
                'timestamp': time.time(),
                'profit': result['best_profit'],
                'net_profit': result['net_profit'],
                'deviation_bp': result['deviation_bp'],
                'prices': {k: {'bid': v['bid'], 'ask': v['ask']} 
                          for k, v in self.prices.items()}
            }
            self.window_history.append(window)
            return window
        
        return None

四、生产级 WebSocket 实时监控代码

以下代码实现了完整的三角套利监控系统,包含:

  • 三个货币对的并行 WebSocket 连接
  • 心跳保活机制
  • 指数退避重连
  • 限频自适应处理
  • 价格缓存与偏离度计算
  • 飞书告警集成
#!/usr/bin/env python3
"""
外汇三角套利实时监控工具
支持: EUR/USD, GBP/USD, EUR/GBP

数据源: TickDB WebSocket
告警: 飞书 WebHook

⚠️ 本代码仅用于监控和学习,不构成投资建议
"""

import os
import json
import time
import logging
import asyncio
import websockets
import requests
from datetime import datetime
from typing import Optional, Dict, Any

# ============================================================
# 配置区
# ============================================================

# TickDB API 配置
TICKDB_API_KEY = os.environ.get("TICKDB_API_KEY")
TICKDB_WS_URL = "wss://stream.tickdb.ai/v1/ws"

# 监控的货币对
SYMBOLS = {
    "EUR/USD": "EUR_USD.FX",      # 注意:需要确认 TickDB 的符号格式
    "GBP/USD": "GBP_USD.FX",
    "EUR/GBP": "EUR_GBP.FX",
}

# 告警配置
FEISHU_WEBHOOK_URL = os.environ.get("FEISHU_WEBHOOK_URL")
ALERT_THRESHOLD_BP = 2.0  # 偏离度阈值(基点)
MIN_PROFIT_USD = 1.0      # 最小利润阈值(美元)

# 重连配置
MAX_RETRIES = 10
BASE_DELAY = 1
MAX_DELAY = 60

# 日志配置
logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s [%(levelname)s] %(message)s',
    datefmt='%Y-%m-%d %H:%M:%S'
)
logger = logging.getLogger(__name__)


# ============================================================
# 工具函数
# ============================================================

def calculate_triangular_arb(profits: Dict[str, Dict]) -> Dict[str, float]:
    """
    计算三角套利利润
    
    参数:
        profits: 三个货币对的最新报价 {'symbol': {'bid': float, 'ask': float}}
    
    返回:
        包含各路径利润和偏离度的字典
    """
    eur_usd = profits.get('EUR/USD', {})
    gbp_usd = profits.get('GBP/USD', {})
    eur_gbp = profits.get('EUR/GBP', {})
    
    if not all([eur_usd.get('bid'), gbp_usd.get('bid'), eur_gbp.get('bid')]):
        return {'valid': False}
    
    initial_usd = 1_000_000
    
    # 路径 A: USD -> EUR -> GBP -> USD
    # 买 EUR 用 ask,卖 EUR 用 bid;买 GBP 用 ask,卖 GBP 用 bid
    step1_A = initial_usd / eur_usd['ask']      # USD → EUR (用 ask 价格买入)
    step2_A = step1_A * eur_gbp['bid']           # EUR → GBP (用 bid 价格卖出)
    step3_A = step2_A * gbp_usd['bid']           # GBP → USD (用 bid 价格卖出)
    profit_A = step3_A - initial_usd
    
    # 路径 B: USD -> GBP -> EUR -> USD
    step1_B = initial_usd / gbp_usd['ask']       # USD → GBP
    step2_B = step1_B / eur_gbp['ask']           # GBP → EUR (除以 ask,等效于买入 EUR)
    step3_B = step2_B * eur_usd['bid']           # EUR → USD
    profit_B = step3_B - initial_usd
    
    # 计算偏离度
    # 隐含 EUR/GBP = EUR/USD_bid / GBP/USD_ask
    # 如果隐含值 > 实际 EUR/GBP_bid,则存在套利机会
    implied_eur_gbp = eur_usd['bid'] / gbp_usd['ask']
    deviation_bp = (implied_eur_gbp - eur_gbp['bid']) / eur_gbp['bid'] * 10000
    
    return {
        'valid': True,
        'path_A_profit': profit_A,
        'path_B_profit': profit_B,
        'best_profit': max(profit_A, profit_B),
        'deviation_bp': deviation_bp,
        'best_path': 'A' if profit_A >= profit_B else 'B'
    }


def send_feishu_alert(window: Dict[str, Any], profits: Dict[str, Dict]):
    """
    发送飞书告警
    
    ⚠️ 告警不保证送达,存在网络延迟,不应作为交易依据
    """
    if not FEISHU_WEBHOOK_URL:
        return
    
    timestamp = datetime.fromtimestamp(window['timestamp']).strftime('%H:%M:%S.%f')[:-3]
    
    message = {
        "msg_type": "interactive",
        "card": {
            "header": {
                "title": {"tag": "plain_text", "content": f"🚨 三角套利窗口预警 {timestamp}"},
                "template": "red"
            },
            "elements": [
                {
                    "tag": "div",
                    "text": {
                        "tag": "lark_md",
                        "content": (
                            f"**偏离度**: {window['deviation_bp']:.2f} bp\n"
                            f"**理论利润**: ${window['best_profit']:.2f}\n"
                            f"**最优路径**: 路径 {window['best_path']}"
                        )
                    }
                },
                {"tag": "hr"},
                {
                    "tag": "div",
                    "text": {
                        "tag": "lark_md",
                        "content": (
                            f"**EUR/USD**: {profits['EUR/USD']['bid']:.5f} / {profits['EUR/USD']['ask']:.5f}\n"
                            f"**GBP/USD**: {profits['GBP/USD']['bid']:.5f} / {profits['GBP/USD']['ask']:.5f}\n"
                            f"**EUR/GBP**: {profits['EUR/GBP']['bid']:.5f} / {profits['EUR/GBP']['ask']:.5f}"
                        )
                    }
                },
                {"tag": "hr"},
                {
                    "tag": "note",
                    "elements": [{"tag": "plain_text", "content": "⚠️ 本告警仅供监控研究,不构成交易建议"}]
                }
            ]
        }
    }
    
    try:
        response = requests.post(
            FEISHU_WEBHOOK_URL,
            headers={"Content-Type": "application/json"},
            data=json.dumps(message),
            timeout=5
        )
        if response.status_code != 200:
            logger.warning(f"飞书告警发送失败: {response.status_code}")
    except requests.RequestException as e:
        logger.warning(f"飞书告警发送异常: {e}")


# ============================================================
# WebSocket 客户端
# ============================================================

async def subscribe_symbol(websocket, symbol: str):
    """
    订阅单个货币对的实时报价
    
    订阅消息格式参考 TickDB WebSocket 文档
    """
    subscribe_msg = {
        "cmd": "subscribe",
        "params": {
            "symbol": symbol,
            "channels": ["ticker"]  # ticker 频道包含最新买卖价
        }
    }
    await websocket.send(json.dumps(subscribe_msg))
    logger.info(f"已订阅: {symbol}")


async def keepalive(websocket, interval=30):
    """
    WebSocket 心跳保活
    
    防止连接因空闲被服务器断开
    """
    while True:
        try:
            await asyncio.sleep(interval)
            await websocket.send(json.dumps({"cmd": "ping"}))
            logger.debug("心跳保活已发送")
        except asyncio.CancelledError:
            break
        except Exception as e:
            logger.error(f"心跳异常: {e}")
            break


async def receive_messages(websocket, profits: Dict, last_update: Dict):
    """
    接收并处理 WebSocket 消息
    """
    async for message in websocket:
        try:
            data = json.loads(message)
            
            # 处理心跳响应
            if data.get('type') == 'pong':
                logger.debug("心跳响应正常")
                continue
            
            # 处理 ticker 数据
            if 'data' in data:
                for item in data['data']:
                    symbol = item.get('symbol', '')
                    # 格式化货币对名称
                    if 'EUR' in symbol and 'USD' in symbol and 'GBP' not in symbol:
                        pair_name = 'EUR/USD'
                    elif 'GBP' in symbol and 'USD' in symbol:
                        pair_name = 'GBP/USD'
                    elif 'EUR' in symbol and 'GBP' in symbol:
                        pair_name = 'EUR/GBP'
                    else:
                        continue
                    
                    # 更新价格缓存
                    profits[pair_name] = {
                        'bid': float(item.get('bid', 0)),
                        'ask': float(item.get('ask', 0)),
                    }
                    last_update[pair_name] = time.time()
                    
                    logger.debug(f"更新 {pair_name}: {item.get('bid')} / {item.get('ask')}")
                    
        except json.JSONDecodeError:
            logger.warning(f"收到无效 JSON: {message[:100]}")
        except Exception as e:
            logger.error(f"消息处理异常: {e}")


async def monitor_loop(profits: Dict, last_update: Dict, alert_cooldown: float = 60):
    """
    主监控循环:定时检查套利窗口
    
    参数:
        alert_cooldown: 告警冷却时间(秒),防止告警轰炸
    """
    last_alert_time = 0
    
    while True:
        await asyncio.sleep(0.5)  # 每 500ms 检查一次
        
        # 检查数据新鲜度
        now = time.time()
        stale_pairs = []
        for pair, timestamp in last_update.items():
            if now - timestamp > 2.0:  # 超过 2 秒未更新视为过期
                stale_pairs.append(pair)
        
        if stale_pairs:
            logger.debug(f"数据过期: {stale_pairs}")
            continue
        
        # 检查是否所有货币对都有数据
        if len(profits) < 3:
            continue
        
        # 计算三角套利
        result = calculate_triangular_arb(profits)
        
        if not result['valid']:
            continue
        
        # 检查是否触发告警条件
        if result['deviation_bp'] >= ALERT_THRESHOLD_BP or result['best_profit'] >= MIN_PROFIT_USD:
            # 检查告警冷却
            if now - last_alert_time < alert_cooldown:
                continue
            
            window = {
                'timestamp': now,
                'deviation_bp': result['deviation_bp'],
                'best_profit': result['best_profit'],
                'best_path': result['best_path']
            }
            
            logger.warning(
                f"套利窗口! 偏离度={result['deviation_bp']:.2f}bp, "
                f"利润=${result['best_profit']:.2f}, 路径={result['best_path']}"
            )
            
            # 发送告警
            send_feishu_alert(window, profits)
            last_alert_time = now


async def connect_with_retry(symbols: list):
    """
    WebSocket 连接,支持指数退避重连
    
    重连策略:
    - 初始等待 1 秒
    - 每次失败后等待时间翻倍
    - 最大等待时间 60 秒
    - 添加随机抖动避免惊群效应
    """
    if not TICKDB_API_KEY:
        raise ValueError("请设置环境变量 TICKDB_API_KEY")
    
    uri = f"{TICKDB_WS_URL}?api_key={TICKDB_API_KEY}"
    
    profits = {}
    last_update = {}
    retry_count = 0
    
    while retry_count < MAX_RETRIES:
        try:
            logger.info(f"尝试连接 WebSocket (第 {retry_count + 1} 次)")
            
            async with websockets.connect(
                uri,
                ping_interval=30,
                ping_timeout=10,
                close_timeout=10
            ) as websocket:
                logger.info("WebSocket 连接成功")
                retry_count = 0  # 连接成功后重置计数
                
                # 订阅所有货币对
                for symbol in symbols:
                    await subscribe_symbol(websocket, symbol)
                
                # 并行运行心跳、接收消息、监控循环
                await asyncio.gather(
                    keepalive(websocket),
                    receive_messages(websocket, profits, last_update),
                    monitor_loop(profits, last_update),
                    return_exceptions=True
                )
                
        except websockets.exceptions.ConnectionClosed as e:
            logger.warning(f"连接关闭: {e}")
        except asyncio.CancelledError:
            logger.info("任务取消,退出连接")
            break
        except Exception as e:
            logger.error(f"连接异常: {e}")
        
        # 计算退避时间
        retry_count += 1
        delay = min(BASE_DELAY * (2 ** (retry_count - 1)), MAX_DELAY)
        jitter = delay * 0.1 * (0.5 - (retry_count % 2))  # 添加抖动
        total_delay = delay + jitter
        
        logger.info(f"{total_delay:.1f} 秒后重连...")
        await asyncio.sleep(total_delay)
    
    logger.error("达到最大重试次数,退出程序")


async def main():
    """主函数"""
    logger.info("=" * 50)
    logger.info("外汇三角套利实时监控系统启动")
    logger.info(f"监控货币对: {list(SYMBOLS.keys())}")
    logger.info(f"告警阈值: {ALERT_THRESHOLD_BP} bp / ${MIN_PROFIT_USD}")
    logger.info("=" * 50)
    
    # ⚠️ 注意: 实际符号需要根据 TickDB 的数据格式调整
    # 以下为示意,请登录 tickdb.ai 确认具体符号格式
    symbols = list(SYMBOLS.values())
    
    try:
        await connect_with_retry(symbols)
    except KeyboardInterrupt:
        logger.info("收到中断信号,正在关闭...")


if __name__ == "__main__":
    # ⚠️ 生产环境高频场景建议使用 aiohttp/asyncio
    # 本代码使用 websockets 库,适合中小规模监控
    
    asyncio.run(main())

4.1 环境配置与依赖

# 安装依赖
pip install websockets requests

# 设置环境变量
export TICKDB_API_KEY="your_api_key_here"
export FEISHU_WEBHOOK_URL="https://open.feishu.cn/open-apis/bot/v2/hook/xxxxxx"

# 运行监控
python forex_triangular_arb.py

4.2 代码核心设计说明

设计要素 实现方式 原因
心跳保活 keepalive() 每 30 秒发送 ping 防止空闲连接被服务端断开
指数退避 delay = min(BASE_DELAY * 2^retry, MAX_DELAY) 避免频繁重连对服务造成压力
随机抖动 jitter = delay * 0.1 * (0.5 - retry%2) 多个客户端同时重连时分散请求
数据新鲜度检查 超过 2 秒未更新的货币对不参与计算 避免用过期价格计算套利
告警冷却 60 秒内不重复告警 防止套利窗口持续期间收到轰炸
异步架构 asyncio + websockets 高并发、低资源占用的 IO 密集型任务

4.3 关于 TickDB 数据格式的说明

重要提示:本文代码中使用的货币对符号(如 EUR_USD.FX)为示意。TickDB 的实际符号格式需要登录后在控制台确认。获取可用符号列表的 API 调用示例:

import os
import requests

headers = {"X-API-Key": os.environ.get("TICKDB_API_KEY")}

# 获取所有可用交易品种
response = requests.get(
    "https://api.tickdb.ai/v1/symbols/available",
    headers=headers,
    timeout=(3.05, 10)
)

data = response.json()
# 筛选外汇相关品种(符号中包含 FX 或货币代码)
fx_symbols = [s for s in data.get('data', []) if 'FX' in s or 'EUR' in s or 'GBP' in s]
print("可用外汇品种:", fx_symbols)

五、监控数据的深度分析

5.1 偏离度的统计特征

仅靠单个套利窗口的信息无法形成有效策略。以下是你应该在积累足够数据后分析的指标:

指标 含义 应用
偏离度均值 正常市场下,三个货币对的平均偏离度 判断是否存在系统性偏差
偏离度标准差 偏离度的波动程度 设置动态阈值
窗口持续时间 从出现到消失的平均时长 判断是否有执行窗口
窗口频率 每小时/每天出现的次数 评估策略容量

5.2 偏离度分布图(示意)

偏离度分布(假设数据)
        
频率
  200 |▐▌
  150 |▐▐▌
  100 |▐▐▐▌
   50 |▐▐▐▐▌
    0 +---+---+---+---+---+---
      -5  -2.5  0   2.5   5   偏离度 (bp)
      
      ↑          ↑           ↑
    负偏离      正常范围    正偏离
   (套利空间)   (无机会)    (套利空间)

在实际数据中,大多数偏离度会集中在 ±1bp 的范围内,这部分被交易成本消耗殆尽。真正的机会出现在尾部区域。

5.3 套利窗口与宏观事件的关联

根据历史经验,以下时段套利窗口出现的频率和幅度会显著增加:

时段 原因 频率增幅
央行利率决议前后 30 分钟 货币政策不确定性导致报价分歧 3-5x
非农就业数据发布 美元短期剧烈波动 5-10x
英国退欧公投、美国大选 地缘政治风险 10x+
美股开盘/收盘 流动性结构变化 2-3x

注意:这些时段虽然机会更多,但流动性风险也更高。限价单可能无法成交,市价单的滑点可能吞噬理论利润。


六、TickDB 在三角套利监控中的定位

6.1 为什么选择 TickDB

外汇三角套利监控的核心需求是:多货币对并行接入 + 实时价格推送 + 低延迟

能力维度 传统方案(如 Tushare) TickDB
数据推送方式 轮询(最小间隔 1 秒) WebSocket 推送
多品种并行 需要多个请求,串行获取 单连接订阅多个品种
数据新鲜度 上一秒的历史快照 实时tick
延迟 1-5 秒 <100ms
连接稳定性 无自动重连 心跳 + 指数退避重连

6.2 功能限制说明

外汇市场在 TickDB 中的定位是高流动性现货市场,适合实时监控和趋势分析。需要注意:

  • TickDB 不提供订单簿深度数据(depth),这是外汇市场的技术限制
  • 外汇交易存在隔夜利息(swap),本文的三角套利策略假设为日内操作
  • 不同经纪商的报价存在差异,实际套利需要考虑经纪商间的报价差

6.3 与其他数据源的协同

如果你的策略需要更高的数据频率或更深的订单簿信息,可以将 TickDB 作为实时监控层,结合其他数据源:

数据需求 推荐数据源 TickDB 的角色
高频订单流 L2 经纪商 API 价格监控
历史回测 TickDB / Broker API 提供清洗过的历史数据
事件标记 财经日历 API 事件时间同步

七、常见问题与避坑指南

7.1 为什么我看到的机会成交不了?

原因 1:报价延迟

你看到的报价可能是 500ms 前的价格。机构算法用直连线路,可能在 5ms 内完成交易,你看到机会时它已经消失了。

原因 2:流动性枯竭

大单进入市场后,报价会向不利方向移动。模拟盘显示的利润在实盘中会被滑点吞噬。

原因 3:经纪商限制

某些经纪商对套利行为有限制条款,可能冻结账户或拒绝成交。

7.2 如何验证代码逻辑正确性?

步骤 1:用历史数据回测

用 TickDB 的历史 K 线数据验证公式计算是否正确。将历史数据代入 calculate_triangular_arb() 函数,输出每日的理论套利机会,与理论值对比。

步骤 2:用模拟盘验证

先用模拟账户(小资金、限价单)测试 2 周以上,统计"看到的信号"与"实际成交的信号"之间的比例。

步骤 3:对比多个数据源

同时接入两个数据源(如 TickDB + 另一个经纪商 API),对比同一时刻的价格差异。如果差异持续存在,可能是数据源本身的问题。

7.3 代码有哪些工程风险点?

风险点 表现 处理方式
网络抖动导致数据丢失 某货币对数据突然中断 数据新鲜度检查 + 缓存过期
WebSocket 假连接 服务端已断开但客户端未感知 心跳 + 超时检测
告警轰炸 套利窗口持续存在时每秒告警 冷却时间机制
数据竞争 多线程同时写入价格缓存 asyncio 单线程协程,无锁

八、结语

三角套利是外汇市场中最接近"数学确定"的策略。但正因为它的"无风险"属性,它也是最难被散户复现的机构游戏——不是策略本身有问题,而是执行基础设施的天壤之别。

本文提供的监控代码,能让你至少看见这些窗口的存在。看见,是理解的前提;理解,是改进的开始。

如果你对三角套利的数学细节还有疑问,建议从一手数据开始:用 TickDB 积累 1 周的三个货币对实时价格,用 Python 分析偏离度的统计分布。你的第一手发现,比任何教科书都更有价值。


下一步行动

如果你是外汇新手,想了解货币对间的数学关系
先从 EUR/USD 的价格形成机制开始,理解"什么是点、什么是手、什么是点差"。

如果你是量化开发者,想验证本文的监控逻辑

  1. 访问 tickdb.ai 注册(免费,无需信用卡)
  2. 在控制台生成 API Key
  3. 设置环境变量 TICKDB_API_KEY,复制本文代码即可运行
  4. 观察偏离度的统计分布,验证策略可行性

如果你对订单簿数据有需求
TickDB 当前支持港股和加密货币的深度数据(depth)。如果你同时关注港股 ETF 的三角套利(如 02800.HK、02801.HK、02828.HK),可以使用 depth 频道获取更完整的流动性视图。联系 [email protected] 获取详情。

如果你想用 AI 辅助开发
在 Claude、Cursor 等 AI 助手中,搜索安装 tickdb-market-data SKILL,可以用自然语言查询 TickDB 的数据能力。


风险提示:本文不构成任何投资建议。三角套利策略存在执行延迟、流动性风险、交易成本等现实约束,历史机会不代表未来可复制。市场有风险,投资需谨慎。