量化项目研究学习(AI Trading Agent (Nocturne) 架构分析)

AI Trading Agent (Nocturne) 架构分析

项目: Gajesh2007/ai-trading-agent(454 stars)
语言: Python 3.12+ | 包管理: Poetry | 部署: Docker / EigenCloud
分析目的: 提炼 LLM 驱动交易系统的设计模式,与当前配对交易项目做对比


1. 项目概览

Nocturne 是一个以 LLM 为核心决策引擎的 Hyperliquid 永续合约交易系统。与传统量化策略不同,它不使用硬编码规则,而是将技术指标作为上下文喂给 LLM,由 LLM 做出买/卖/持有决策。

核心特点

  • LLM 即策略引擎 — 不写交易规则,让 LLM 基于技术指标做决策
  • Tool Calling — LLM 可主动调用 TAAPI 获取额外指标,非预设管道
  • 多资产并行 — 单次 LLM 调用决策多个资产
  • 实盘部署 — 3 个实盘 Agent(GPT-5 Pro / DeepSeek R1 / Grok 4)

技术栈

组件 技术 说明
LLM 网关 OpenRouter API 支持多模型切换(Grok 4、GPT-5 Pro、DeepSeek R1)
交易所 Hyperliquid Python SDK 永续合约交易
技术指标 TAAPI.io REST API 200+ 技术指标(EMA、MACD、RSI、ATR 等)
HTTP API aiohttp 轻量观测端点(/diary、/logs)
配置 python-dotenv + 自定义 loader 类型化环境变量加载
输出格式化 rich 终端美化(声明但未深度使用)

2. 目录结构

ai-trading-agent/
├── .env.example                  # 环境变量模板
├── .gitignore
├── Dockerfile                    # 容器化部署
├── README.md
├── pyproject.toml                # Poetry 依赖管理
├── poetry.lock
├── docs/
│   └── ARCHITECTURE.md           # 架构设计文档
└── src/
    ├── __init__.py
    ├── main.py                   # 主入口 + 交易循环 + HTTP API(~600行)
    ├── config_loader.py          # 类型化环境变量加载器
    ├── agent/
    │   ├── __init__.py
    │   └── decision_maker.py     # LLM 决策引擎(Tool Calling + 结构化输出)
    ├── indicators/
    │   ├── __init__.py
    │   └── taapi_client.py       # TAAPI 技术指标客户端
    ├── trading/
    │   ├── __init__.py
    │   └── hyperliquid_api.py    # Hyperliquid 交易执行(SDK 封装)
    └── utils/
        ├── __init__.py
        ├── formatting.py         # 数值格式化工具
        └── prompt_utils.py       # JSON 序列化辅助

3. 核心架构分析

3.1 LLM 决策引擎(Decision Maker)

这是 Nocturne 最核心的设计 — LLM 不是辅助工具,而是决策主体

# src/agent/decision_maker.py
class TradingAgent:
    def __init__(self):
        self.model = CONFIG["llm_model"]           # 主决策模型(如 x-ai/grok-4)
        self.sanitize_model = "openai/gpt-5"       # 廉价修复模型
        self.taapi = TAAPIClient()                  # 指标查询客户端

    def decide_trade(self, assets, context):
        """单次 LLM 调用,为多个资产生成交易决策"""
        return self._decide(context, assets=assets)

决策流程

技术指标 + 账户状态 + 持仓信息 + 历史日记
        │
        ▼
┌─────────────────────────────────────────┐
│  System Prompt(量化交易员人设)          │
│  - 低频交易核心策略                      │
│  - 滞后效应(Hysteresis)防频繁翻转      │
│  - 冷却期机制(Cooldown)               │
│  - 杠杆策略(3x-10x)                   │
│  - TP/SL 合理性校验规则                  │
│  - Tool Calling 指引                    │
│  - JSON Schema 输出契约                 │
└──────────────┬──────────────────────────┘
               │
               ▼
┌──────────────────────────────────────┐
│  LLM 推理(最多 6 轮 Tool Loop)      │
│  ┌───────────────────────────────┐   │
│  │ 轮次 1: LLM 分析上下文         │   │
│  │   → 可能调用 fetch_taapi_     │   │
│  │     indicator 获取额外指标     │   │
│  │ 轮次 2: 工具结果回传            │   │
│  │   → LLM 继续推理或输出决策     │   │
│  │ ...                           │   │
│  │ 轮次 N: 输出结构化 JSON 决策    │   │
│  └───────────────────────────────┘   │
└──────────────┬───────────────────────┘
               │
               ▼
┌──────────────────────────────────────┐
│  输出解析 + 容错                      │
│  1. 优先使用 message.parsed          │
│  2. 降级到 JSON.parse(content)       │
│  3. 再降级到 Sanitizer LLM 修复       │
│  4. 最终降级到全 hold + parse error    │
└──────────────────────────────────────┘

vs 当前项目(纯规则驱动)

维度 Nocturne (LLM 决策) 当前项目 (规则驱动)
决策引擎 LLM 自然语言推理 协整检验 + Z-score 阈值
信号来源 TAAPI 200+ 指标 + LLM 选择 价格序列协整分析
可解释性 LLM 生成 rationale 文本 数学公式清晰
可复现性 — 相同输入可能不同输出 — 相同输入必定相同输出
回测可行性 极难 — LLM 调用不可重放 可行 — 规则确定性执行
策略迭代 改 prompt = 改策略 改代码/参数 = 改策略
成本 每次决策 $0.01-$1(视模型) 几乎零成本

借鉴价值: 中-高。不建议替换当前项目的协整信号系统,但可以引入 LLM 辅助信号确认层 — 在规则系统生成信号后,用 LLM 对信号做"第二意见"过滤。

3.2 Tool Calling 模式

Nocturne 最独特的设计之一 — LLM 在推理过程中可主动查询额外指标:

# src/agent/decision_maker.py — Tool 定义
tools = [{
    "type": "function",
    "function": {
        "name": "fetch_taapi_indicator",
        "description": "Fetch any TAAPI indicator. Available: ema, sma, rsi, macd, "
                       "bbands, stochastic, adx, atr, cci, dmi, ichimoku, supertrend, "
                       "vwap, obv, mfi, willr, roc, mom, sar, fibonacci, ...",
        "parameters": {
            "type": "object",
            "properties": {
                "indicator": {"type": "string"},
                "symbol": {"type": "string"},        # 如 "BTC/USDT"
                "interval": {"type": "string"},       # 如 "5m", "4h"
                "period": {"type": "integer"},
                "backtrack": {"type": "integer"},
                "other_params": {"type": "object"},
            },
            "required": ["indicator", "symbol", "interval"],
        },
    },
}]

Tool 执行流程(最多 6 轮):

for _ in range(6):  # 最多 6 轮 tool loop
    data = {"model": self.model, "messages": messages, "tools": tools}
    resp_json = _post(data)
    message = resp_json["choices"][0]["message"]
    messages.append(message)

    tool_calls = message.get("tool_calls") or []
    if tool_calls:
        for tc in tool_calls:
            # 执行 TAAPI 查询
            args = json.loads(tc["function"]["arguments"])
            ind_resp = requests.get(
                f"{self.taapi.base_url}{args['indicator']}",
                params={...}, timeout=30
            ).json()
            # 结果回传 LLM
            messages.append({
                "role": "tool",
                "tool_call_id": tc["id"],
                "content": json.dumps(ind_resp),
            })
        continue  # 继续下一轮让 LLM 消化工具结果

    # 无 tool call → 解析最终输出
    parsed = json.loads(message["content"])
    return parsed

设计亮点

  • LLM 不是被动接收预设指标,而是 主动选择 需要什么额外数据
  • 比如 LLM 可能看到 RSI 接近超买,主动调用 Bollinger Bands 确认
  • 这种模式让 LLM 的决策更像人类交易员的思考过程

vs 当前项目:当前项目的信号管道是预设的(协整分析 → Z-score → 信号触发),无"按需查询"能力。

借鉴价值: 高。如果引入 LLM 辅助层,Tool Calling 是最自然的方式 — 让 LLM 在确认信号时可以主动查询额外指标。

3.3 结构化输出(JSON Schema 强约束)

Nocturne 使用 OpenAI 兼容的 response_format 强制 LLM 输出类型安全的 JSON:

# src/agent/decision_maker.py — JSON Schema 定义
schema = {
    "type": "object",
    "properties": {
        "reasoning": {"type": "string"},
        "trade_decisions": {
            "type": "array",
            "items": {
                "type": "object",
                "properties": {
                    "asset": {"type": "string", "enum": assets},
                    "action": {"type": "string", "enum": ["buy", "sell", "hold"]},
                    "allocation_usd": {"type": "number", "minimum": 0},
                    "tp_price": {"type": ["number", "null"]},
                    "sl_price": {"type": ["number", "null"]},
                    "exit_plan": {"type": "string"},
                    "rationale": {"type": "string"},
                },
                "required": [...],
                "additionalProperties": False,
            },
        }
    },
    "required": ["reasoning", "trade_decisions"],
    "additionalProperties": False,
}

# 应用到请求
data["response_format"] = {
    "type": "json_schema",
    "json_schema": {
        "name": "trade_decisions",
        "strict": True,          # 严格模式,不允许额外字段
        "schema": schema,
    },
}

关键设计点

  • "strict": True 强制 LLM 严格遵循 schema,不允许额外字段
  • "enum": assets 限制 asset 只能是预定义的资产列表
  • "enum": ["buy", "sell", "hold"] 限制 action 为三选一
  • tp_price/sl_price 允许 null(用 ["number", "null"] 联合类型)

借鉴价值: 高。如果在当前项目中引入 LLM,结构化输出是必须的 — 确保 LLM 的回复可以直接被代码消费。

3.4 双 LLM 容错(Sanitizer 模式)

当主 LLM 返回格式不正确时,使用廉价快速模型修复:

# src/agent/decision_maker.py
self.sanitize_model = CONFIG.get("sanitize_model") or "openai/gpt-5"

def _sanitize_output(raw_content: str, assets_list):
    """用廉价 LLM 修复格式错误的输出"""
    payload = {
        "model": self.sanitize_model,
        "messages": [
            {"role": "system", "content":
                "You are a strict JSON normalizer. Return ONLY a JSON array "
                "matching the provided JSON Schema. If input is wrapped or "
                "has prose/markdown, fix it. Do not add fields."
            },
            {"role": "user", "content": raw_content},
        ],
        "response_format": {
            "type": "json_schema",
            "json_schema": {"name": "trade_decisions", "strict": True, "schema": schema},
        },
        "temperature": 0,  # 确定性输出
    }
    resp = _post(payload)
    # ...

容错层次(4 级降级):

Level 1: message.parsed(结构化输出直接解析)
    │ 失败
    ▼
Level 2: json.loads(message.content)(手动 JSON 解析)
    │ 失败
    ▼
Level 3: _sanitize_output()(用 GPT-5 修复格式)
    │ 失败
    ▼
Level 4: 生成全 hold 决策 + "parse error" rationale

借鉴价值: 中-高。双 LLM 模式是处理 LLM 输出不确定性的实用方案。主模型做推理(贵但聪明),修复模型做格式化(便宜但可靠)。

3.5 Provider 自适应降级

Nocturne 在运行时检测 LLM 提供商的能力限制,自动降级重试:

# src/agent/decision_maker.py — 自适应降级逻辑
allow_tools = True
allow_structured = True

for _ in range(6):
    data = {"model": self.model, "messages": messages}
    if allow_structured:
        data["response_format"] = {...}
    if allow_tools:
        data["tools"] = tools

    try:
        resp_json = _post(data)
    except requests.HTTPError as e:
        err = e.response.json()
        raw = err.get("error", {}).get("metadata", {}).get("raw", "")
        provider = err.get("error", {}).get("metadata", {}).get("provider_name", "")

        # xAI 不支持 tool schema → 禁用 tools 重试
        if e.response.status_code == 422 and provider.startswith("xai"):
            allow_tools = False
            continue

        # Provider 不支持 structured outputs → 禁用 response_format 重试
        if "response_format" in json.dumps(err) or "structured" in json.dumps(err):
            allow_structured = False
            continue

设计亮点

  • 不同 LLM 提供商(xAI、OpenAI、DeepSeek)对 Tool Calling 和 Structured Output 的支持不同
  • 通过检测错误响应中的 provider_name 和错误类型,自动降级
  • 先尝试最完整的功能集,逐步降级到最基础的纯文本输出

借鉴价值: 中。当需要支持多 LLM 提供商时,这种自适应降级模式很实用。


4. 交易执行层

4.1 Hyperliquid API 封装

# src/trading/hyperliquid_api.py
class HyperliquidAPI:
    def __init__(self):
        # 支持私钥或助记词
        if CONFIG["hyperliquid_private_key"]:
            self.wallet = Account.from_key(CONFIG["hyperliquid_private_key"])
        elif CONFIG["mnemonic"]:
            Account.enable_unaudited_hdwallet_features()
            self.wallet = Account.from_mnemonic(CONFIG["mnemonic"])

        # 网络选择(mainnet / testnet)
        network = CONFIG.get("hyperliquid_network", "mainnet")
        self.info = Info(base_url)
        self.exchange = Exchange(self.wallet, base_url)

4.2 异步重试机制

async def _retry(self, fn, *args, max_attempts=3, backoff_base=0.5,
                 reset_on_fail=True, to_thread=True, **kwargs):
    """指数退避重试 + 可选客户端重建"""
    for attempt in range(max_attempts):
        try:
            if to_thread:
                return await asyncio.to_thread(fn, *args, **kwargs)
            return await fn(*args, **kwargs)
        except (WebSocketConnectionClosedException, aiohttp.ClientError,
                ConnectionError, TimeoutError) as e:
            if reset_on_fail:
                self._reset_clients()       # 重建 SDK 客户端
            await asyncio.sleep(backoff_base * (2 ** attempt))

设计亮点

  • asyncio.to_thread() 将同步 SDK 调用桥接到异步事件循环
  • 连接失败时自动重建 ExchangeInfo 客户端实例
  • 区分可恢复错误(网络问题,3 次重试)和不可恢复错误(逻辑错误,1 次重试后放弃)

vs 当前项目:当前项目使用 CircuitBreaker + RateLimiter 模式,更系统化。Nocturne 的重试更轻量但缺乏熔断保护。

4.3 订单精度处理

def round_size(self, asset, amount):
    """根据交易所元数据精确控制下单精度"""
    meta = self._meta_cache[0]
    universe = meta.get("universe", [])
    asset_info = next((u for u in universe if u["name"] == asset), None)
    if asset_info:
        decimals = asset_info.get("szDecimals", 8)
        return round(amount, decimals)
    return round(amount, 8)

4.4 TP/SL 触发单

async def place_take_profit(self, asset, is_buy, amount, tp_price):
    amount = self.round_size(asset, amount)
    order_type = {
        "trigger": {
            "triggerPx": tp_price,
            "isMarket": True,       # 触发后市价执行
            "tpsl": "tp"
        }
    }
    return await self._retry(lambda: self.exchange.order(
        asset, not is_buy, amount, tp_price, order_type, True  # reduce_only=True
    ))

借鉴价值: 中。当前项目的止损/止盈逻辑在应用层实现,可以考虑使用交易所原生触发单来减少监控压力。


5. 交易所状态对账机制(Reconciliation)

Nocturne 的核心风控设计之一 — 交易所状态永远是权威来源

# src/main.py — 每轮循环中的状态对账
try:
    # 从交易所获取当前持仓资产
    assets_with_positions = set()
    for pos in state['positions']:
        if abs(float(pos.get('szi') or 0)) > 0:
            assets_with_positions.add(pos.get('coin'))

    # 获取有挂单的资产
    assets_with_orders = {o.get('coin') for o in open_orders if o.get('coin')}

    # 清理过时的本地 active_trades
    for tr in active_trades[:]:
        asset = tr.get('asset')
        if asset not in assets_with_positions and asset not in assets_with_orders:
            # 交易所没有持仓也没有挂单 → 本地记录过时
            active_trades.remove(tr)
            # 记录到 diary
            diary_entry = {
                "timestamp": datetime.now(timezone.utc).isoformat(),
                "asset": asset,
                "action": "reconcile_close",
                "reason": "no_position_no_orders",
            }
            f.write(json.dumps(diary_entry) + "\n")
except Exception:
    pass

设计原则

  • 本地 active_trades 可能因 TP/SL 被交易所自动执行而过时
  • 每轮循环检查:如果交易所既没有持仓也没有挂单,就清理本地记录
  • 对账结果写入 diary.jsonl,可追溯

vs 当前项目:当前项目通过 TimescaleDB 持久化状态 + 崩溃恢复实现更强的一致性保证。但 Nocturne 的"交易所即权威"原则值得在当前项目中也显式实现 — 定期与交易所持仓对账,确保本地状态不偏离。

借鉴价值: 高。即使有数据库持久化,也应该定期与交易所做 reconciliation。


6. Exit Plan as State — LLM 决策连续性

Nocturne 最创新的设计之一 — LLM 写的退出计划作为下一轮的上下文回传

# LLM 输出的 exit_plan 示例:
# "close if 4h close above EMA50; cooldown_bars:3 until 2025-10-19T15:55Z"

# 存储到 active_trades
active_trades.append({
    "asset": asset,
    "is_long": is_buy,
    "entry_price": current_price,
    "exit_plan": output["exit_plan"],  # LLM 自定义的退出条件
    "opened_at": datetime.now().isoformat()
})

# 下一轮循环中,active_trades 作为 dashboard 的一部分回传给 LLM
dashboard = {
    "active_trades": [
        {
            "asset": tr.get('asset'),
            "exit_plan": tr.get('exit_plan'),  # ← 上一轮 LLM 自己写的
            "opened_at": tr.get('opened_at')
        }
        for tr in active_trades
    ],
}

设计精妙之处

  • 传统系统:退出条件是硬编码的规则(如"Z-score 回归到 0 时平仓")
  • Nocturne:LLM 自己定义退出条件,下一轮 LLM 自己检查这些条件
  • 实现了 LLM 跨轮次的"记忆"和"承诺"
  • System prompt 中的 Core policy (1) 要求 LLM 尊重先前的 exit_plan

局限性

  • LLM 可能"遗忘"或无视自己之前写的 exit_plan
  • 没有代码级强制执行(虽然有 check_exit_condition 函数,但仅支持有限模式匹配)
  • 完全依赖 prompt 中的软约束

借鉴价值: 中。这个设计本身很创新,但在严肃的交易系统中,退出条件应该有代码级硬约束兜底。可以考虑"LLM 建议 + 规则执行"的混合模式。


7. 配置管理

7.1 类型化辅助函数(config_loader.py)

Nocturne 的配置加载器提供了一组类型安全的辅助函数:

# src/config_loader.py
def _get_env(name: str, default: str | None = None, required: bool = False) -> str | None:
    """带必填校验的环境变量获取"""
    value = os.getenv(name, default)
    if required and (value is None or value == ""):
        raise RuntimeError(f"Missing required environment variable: {name}")
    return value

def _get_bool(name: str, default: bool = False) -> bool:
    """布尔型环境变量(支持 1/true/yes/on)"""
    raw = os.getenv(name)
    return raw.strip().lower() in {"1", "true", "yes", "on"} if raw else default

def _get_int(name: str, default: int | None = None) -> int | None:
    """整型环境变量,带错误提示"""

def _get_json(name: str, default: dict | None = None) -> dict | None:
    """JSON 对象型环境变量"""

def _get_list(name: str, default: list[str] | None = None) -> list[str] | None:
    """列表型环境变量(支持 JSON 数组或逗号分隔)"""

# 模块级字典,启动时即加载
CONFIG = {
    "taapi_api_key": _get_env("TAAPI_API_KEY", required=True),
    "llm_model": _get_env("LLM_MODEL", "x-ai/grok-4"),
    "reasoning_enabled": _get_bool("REASONING_ENABLED", False),
    "api_port": _get_env("APP_PORT") or _get_env("API_PORT") or "3000",
    "provider_config": _get_json("PROVIDER_CONFIG"),
    "provider_quantizations": _get_list("PROVIDER_QUANTIZATIONS"),
    # ...
}

设计亮点

  • 比裸 os.getenv() 更安全 — 类型转换失败时有明确错误信息
  • _get_list 支持 JSON 数组和逗号分隔两种格式
  • _get_json 支持直接在环境变量中传递 JSON 对象
  • required=True 在启动时就校验,不会运行时才崩溃

vs 当前项目:当前项目使用 dataclass + os.getenv(),Nocturne 的类型化辅助函数虽然没有 Pydantic 那么强大,但零依赖且实用。

借鉴价值: 中。如果不想引入 Pydantic,这组辅助函数是轻量替代方案。


8. 可观测性

8.1 HTTP API 端点

# src/main.py — aiohttp 轻量 API
async def start_api(app):
    app.router.add_get('/diary', handle_diary)   # 交易日记
    app.router.add_get('/logs', handle_logs)     # 日志查看

async def main_async():
    app = web.Application()
    await start_api(app)
    runner = web.AppRunner(app)
    await runner.setup()
    site = web.TCPSite(runner, CFG.get("api_host"), int(CFG.get("api_port")))
    await site.start()
    await run_loop()  # 交易循环

可用端点

端点 参数 功能
GET /diary limit=200 JSON 格式的最近交易日记
GET /diary?raw=1 原始 JSONL 文本
GET /diary?download=1 下载 diary.jsonl 文件
GET /logs path=llm_requests.log 查看 LLM 请求日志
GET /logs?limit=all 获取完整日志
GET /logs?download=1 下载日志文件

8.2 交易日记(diary.jsonl)

每笔交易和决策都记录到 diary.jsonl

{
    "timestamp": "2025-10-19T12:34:56+00:00",
    "asset": "BTC",
    "action": "buy",
    "allocation_usd": 500.0,
    "amount": 0.0052,
    "entry_price": 96153.85,
    "tp_price": 98000.0,
    "sl_price": 95000.0,
    "exit_plan": "close if 4h RSI > 70 and MACD bearish cross",
    "rationale": "Structure bullish on 4h with EMA20 > EMA50...",
    "filled": true
}

8.3 LLM 请求日志(llm_requests.log)

完整记录每次 LLM 调用的请求和响应:

with open("llm_requests.log", "a") as f:
    f.write(f"\n\n=== {datetime.now()} ===\n")
    f.write(f"Model: {payload.get('model')}\n")
    f.write(f"Payload:\n{json.dumps(payload, indent=2)}\n")

借鉴价值: 高。轻量级 HTTP API 暴露内部状态非常实用,比 SSH 到服务器查日志方便很多。当前项目可以用类似方式暴露策略状态、持仓信息、信号历史。


9. 独特设计模式详解

模式 1: LLM 辅助信号解读

核心思想: 不替换量化规则,而是让 LLM 对规则产出的信号做"第二意见"。

在当前项目中的应用方式

协整分析 → Z-score 信号 → LLM 确认/过滤 → 执行

LLM 可以回答:"当前 Z-score 2.1 触发了做多信号,但 BTC 大盘正在暴跌,基本面不支持,建议跳过"。

模式 2: Tool Calling 按需指标查询

核心思想: LLM 在决策过程中主动选择需要的额外数据。

在当前项目中的应用方式: 定义工具函数如 get_funding_rateget_order_book_depthget_correlation_with_btc,让 LLM 在确认信号时按需调用。

模式 3: 结构化输出强约束

核心思想: 使用 JSON Schema 的 strict: true 模式,确保 LLM 输出可直接被代码消费。

模式 4: 双 LLM 容错

核心思想: 主模型做推理(贵但聪明),修复模型做格式化(便宜但可靠)。

模式 5: Exit Plan as State

核心思想: LLM 写退出条件 → 存储 → 下一轮作为上下文回传 → LLM 自我检查。

模式 6: HTTP API 可观测性

核心思想: 用 aiohttp 暴露 2-3 个只读端点,零前端依赖,远程可查。


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

10.1 架构维度对比

维度 Nocturne (LLM 驱动) 当前项目 (规则驱动) 评价
决策引擎 LLM 自然语言推理 协整检验 + Z-score 各有适用场景
并发模型 asyncio + to_thread threading (~37 线程) Nocturne 更轻量
信号来源 TAAPI 200+ 指标 + LLM 选择 价格序列协整分析 当前项目更专精
交易类型 单腿方向性交易 双腿配对交易 + 对冲 当前项目更安全
输出格式 JSON Schema 强约束 不涉及(规则直接执行) Nocturne 创新
可复现性 低(LLM 非确定性) 高(数学规则确定) 当前项目胜出
数据持久化 无(JSONL 仅日记) TimescaleDB 当前项目胜出
崩溃恢复 无(重启丢 active_trades) 数据库恢复 当前项目胜出
安全防护 杠杆仅 prompt 软约束 KillSwitch + RateLimiter + CircuitBreaker 当前项目胜出
状态对账 每轮与交易所对账 依赖数据库状态 Nocturne 思路值得借鉴
可观测性 HTTP API + diary.jsonl 日志 + 飞书通知 Nocturne 更可查询
远程监控 HTTP 端点随时查 SSH + 飞书推送 Nocturne 更方便
回测能力 极难(LLM 不可重放) 可行(规则确定性) 当前项目胜出
API 成本 高(每次决策 LLM 调用) 几乎零 当前项目胜出
测试覆盖 都需要改进
代码组织 main.py ~600 行膨胀 模板方法 + 职责分离 当前项目胜出

10.2 当前项目独有优势(不需要改动)

  1. TimescaleDB 持久化 + 崩溃恢复 — Nocturne 重启后丢失所有 active_trades,仅有 diary.jsonl 不足以恢复运行
  2. 三层安全防护 — KillSwitch(文件触发紧急停止)+ RateLimiter + CircuitBreaker,Nocturne 无任何代码级安全防护
  3. 双腿配对交易 + 对冲回滚 — 统计套利模型自带对冲属性,Nocturne 是单腿方向性交易
  4. 多周期协整分析 — 5m/1h/4h 三周期 Z-score + 双重确认,数学上可回测
  5. 线程安全状态管理 — Lock/RLock 保护共享状态,Nocturne 使用全局变量无保护
  6. 日损限额 + 最大回撤监控 — 企业级风控,Nocturne 杠杆约束仅靠 prompt 软约束
  7. 批量写入优化 — TimescaleDB COPY 方法 >40K records/sec

11. 可借鉴改进建议

P0 - 高优先级(高价值低成本)

11.1 LLM 辅助信号过滤层

现状: 纯规则驱动信号,可能在极端市况下产生假信号
改进: 在规则信号触发后,增加 LLM 确认层

# 概念方案
class LLMSignalFilter:
    def __init__(self, model="openai/gpt-4o-mini"):
        self.model = model

    def confirm_signal(self, signal: dict, market_context: dict) -> dict:
        """LLM 对规则信号做第二意见过滤"""
        context = {
            "signal": signal,  # Z-score, direction, pair
            "market": market_context,  # 大盘状态, funding, OI
        }
        # 结构化输出: {confirm: bool, confidence: float, reason: str}
        return self._call_llm(context)

工作量: ~1-2 天 | 风险: 低(LLM 仅做过滤,不直接控制交易) | 收益: 减少假信号

11.2 交易所状态对账(Reconciliation)

现状: 依赖数据库状态,可能与交易所实际状态偏离
改进: 定期与交易所持仓/挂单状态对账

async def reconcile_positions(self):
    """与交易所对账,确保本地状态准确"""
    exchange_positions = await self.api.get_positions()
    local_positions = self.state_manager.get_active_positions()

    for local in local_positions:
        if local.symbol not in exchange_positions:
            self.state_manager.mark_closed(local, reason="reconcile")
            self.notify(f"Reconciled stale position: {local.symbol}")

工作量: ~3-4 小时 | 风险: 低 | 收益: 防止状态偏离导致风控失效

11.3 HTTP API 可观测端点

现状: 只能通过 SSH + 日志或飞书推送了解系统状态
改进: 增加轻量 HTTP API 暴露内部状态

# 概念方案(可用 aiohttp 或 FastAPI)
# GET /status    → 系统状态、运行时长、活跃持仓
# GET /signals   → 最近信号历史
# GET /positions → 当前持仓详情
# GET /metrics   → Z-score、协整强度、PnL 等关键指标

工作量: ~4-6 小时 | 风险: 低 | 收益: 远程监控能力

P1 - 中优先级

11.4 结构化 LLM 输出(如果引入 LLM)

前提: 如果实施 11.1 的 LLM 信号过滤
方案: 使用 OpenAI 兼容的 JSON Schema 强约束

response_format = {
    "type": "json_schema",
    "json_schema": {
        "name": "signal_confirmation",
        "strict": True,
        "schema": {
            "type": "object",
            "properties": {
                "confirm": {"type": "boolean"},
                "confidence": {"type": "number", "minimum": 0, "maximum": 1},
                "reason": {"type": "string"},
                "suggested_adjustment": {
                    "type": ["object", "null"],
                    "properties": {
                        "position_scale": {"type": "number"},
                        "tighter_stop": {"type": "boolean"},
                    }
                }
            },
            "required": ["confirm", "confidence", "reason"],
        }
    }
}

11.5 交易日记持久化

现状: 飞书通知是即发即忘
改进: 借鉴 Nocturne 的 diary.jsonl 模式,每笔决策都结构化记录

diary_entry = {
    "timestamp": datetime.utcnow().isoformat(),
    "pair": "HYPE/PURR",
    "action": "open_long",
    "z_score": 2.15,
    "signal_source": "cointegration",
    "llm_confirmed": True,   # 如果有 LLM 层
    "entry_prices": {"HYPE": 25.3, "PURR": 0.45},
    "position_size_usd": 500,
    "rationale": "Z-score > 2.0, 三周期确认, funding 有利"
}

P2 - 低优先级(长期改进)

11.6 LLM Tool Calling 按需指标查询

前提: 更深度的 LLM 集成
方案: 定义一组 Tool Function,让 LLM 在信号确认时按需查询

tools = [
    {"name": "get_funding_rate", "params": {"asset": "string"}},
    {"name": "get_order_book_imbalance", "params": {"asset": "string", "depth": "int"}},
    {"name": "get_correlation", "params": {"asset1": "string", "asset2": "string"}},
    {"name": "get_market_sentiment", "params": {"asset": "string"}},
]

11.7 Provider 自适应降级

前提: 使用 LLM 且需要支持多提供商
方案: 借鉴 Nocturne 的运行时能力检测 + 自动降级


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

以下是 Nocturne 的设计缺陷,当前项目应避免这些问题:

  1. main.py 膨胀(~600 行) — 混合了 CLI 参数解析、HTTP 路由、交易循环、状态管理、Sharpe 计算。当前项目的模板方法模式 + 职责分离明显更优

  2. 同步 HTTP 阻塞异步事件循环decision_maker.py 使用 requests.post() 发送 LLM 请求,这是同步调用,会阻塞 asyncio 事件循环。应该使用 aiohttphttpx

    # 问题代码
    resp = requests.post(self.base_url, headers=headers, json=payload, timeout=60)
    # 这个调用在异步上下文中会阻塞整个事件循环 60 秒
    
  3. 无持久化恢复active_trades 是内存列表,重启后完全丢失。diary.jsonl 记录了历史但没有恢复逻辑

  4. 无测试代码 — 零测试文件,项目没有 tests/ 目录

  5. LLM 杠杆约束仅靠 prompt 软约束 — System prompt 说 "KEEP IT WITHIN 10X" 但代码中没有任何硬限制。LLM 完全可能输出 50x 杠杆的 allocation_usd

    # 无任何代码级杠杆校验
    alloc_usd = float(output.get("allocation_usd", 0.0))
    amount = alloc_usd / current_price
    order = await hyperliquid.place_buy_order(asset, amount)  # 直接执行,无上限检查
    
  6. Sharpe 比率计算中 pnl 字段从未填充trade_log 中的 entry 没有 pnl 字段,导致 calculate_sharpe 永远返回 0

    # trade_log.append 时没有 pnl 字段
    trade_log.append({"type": action, "price": current_price, "amount": amount, ...})
    
    # 但 calculate_sharpe 读取 pnl
    vals = [r.get('pnl', 0) for r in returns]  # 永远是 0
    
  7. 全局变量无保护active_tradesinvocation_count 等全局变量在异步上下文中无锁保护

  8. TAAPI 调用未做频率限制 — 每轮循环为每个资产串行调用多次 TAAPI API(EMA、MACD、RSI 等),无 rate limiting


13. 总结

Nocturne 的核心价值

设计模式 借鉴价值 实施难度 说明
LLM 辅助信号过滤 不替换规则,仅做信号确认
Tool Calling 按需查询 LLM 主动选择需要的数据
结构化输出 JSON Schema 确保 LLM 输出可编程消费
双 LLM 容错(Sanitizer) 中-高 廉价模型修复格式错误
Exit Plan as State LLM 跨轮次决策连续性
HTTP API 可观测性 轻量远程监控
交易所状态对账 防止本地状态偏离
Provider 自适应降级 多 LLM 提供商兼容

行动清单

  • [ ] P0 实现交易所状态对账(Reconciliation)
  • [ ] P0 增加 HTTP API 可观测端点
  • [ ] P0 评估 LLM 信号过滤层可行性
  • [ ] P1 实现结构化交易日记(diary 模式)
  • [ ] P1 如引入 LLM,使用 JSON Schema 强约束输出
  • [ ] P2 LLM Tool Calling 按需指标查询
  • [ ] P2 多 LLM Provider 自适应降级

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