17、配置管理:配置文件设计(YAML/JSON)、环境变量管理、多环境切换、敏感信息加密

做市系统跑起来,第一件事不是写策略,而是配参数。

我见过太多团队,代码写得漂漂亮亮,结果配置文件一团糟。测试环境连生产库,生产环境打印调试日志……这种事故,说白了就是配置管理没做好。

今天咱们聊聊,怎么把配置这件事,做得像瑞士钟表一样精准。

17.1 配置文件格式:YAML vs JSON

做市系统的配置,我首选YAML。为什么?

你看,一个典型的做市策略配置,包含嵌套结构、列表、键值对。JSON写出来满眼都是花括号和引号,人眼扫过去容易疲劳。YAML用缩进表示层级,清爽多了。

我的经验法则

  • YAML:适合人工编写和阅读的配置文件
  • JSON:适合程序生成、跨语言传递的配置
  • TOML:如果你喜欢INI风格的简洁,也可以考虑

来看个实际例子。这是做市系统的核心配置:

# config.yaml
exchange:
  name: binance
  api_endpoint: https://api.binance.com
  ws_endpoint: wss://stream.binance.com:9443
  timeout_ms: 5000
  retry_count: 3

strategy:
  name: grid_market_making
  symbol: BTCUSDT
  spread_bps: 5          # 价差,单位基点
  order_size_btc: 0.1    # 每单数量
  max_position_btc: 2.0  # 最大持仓
  rebalance_interval_s: 60

risk:
  max_drawdown_pct: 5.0
  daily_loss_limit_usdt: 1000
  circuit_breaker: true

logging:
  level: INFO
  file: /var/log/market_maker.log
  rotation: daily

换成JSON,你感受一下:

{
  "exchange": {
    "name": "binance",
    "api_endpoint": "https://api.binance.com",
    "ws_endpoint": "wss://stream.binance.com:9443",
    "timeout_ms": 5000,
    "retry_count": 3
  },
  "strategy": {
    "name": "grid_market_making",
    "symbol": "BTCUSDT",
    "spread_bps": 5,
    "order_size_btc": 0.1,
    "max_position_btc": 2.0,
    "rebalance_interval_s": 60
  }
}

嗯,JSON也不是不能用。但如果你需要写注释,JSON就尴尬了——它原生不支持注释。YAML可以用#随意加注释,这对运维同学太友好了。

小技巧:我习惯在YAML里用锚点(&)和别名(*)来复用配置。比如多个币种共享相同的交易所参数,写一次就够了。

17.2 环境变量管理

配置文件里有些东西不能写死。比如API密钥、数据库密码、不同环境的IP地址。

这些我统统交给环境变量。

为什么?因为环境变量是操作系统级别的配置,不会被误提交到Git仓库。你想想看,要是把生产环境的API密钥写进配置文件,一不小心push到GitHub……那画面太美我不敢看。

我的做法是这样的:

  1. 默认值从配置文件读取:开发环境用默认值,省事
  2. 环境变量覆盖:生产环境通过环境变量传入敏感信息
  3. 优先级:环境变量 > 配置文件 > 代码默认值

代码实现很简单:

import os
import yaml

def load_config():
    # 1. 加载YAML配置文件
    with open('config.yaml', 'r') as f:
        config = yaml.safe_load(f)
    
    # 2. 环境变量覆盖
    config['exchange']['api_key'] = os.getenv(
        'EXCHANGE_API_KEY', 
        config.get('exchange', {}).get('api_key', '')
    )
    config['exchange']['api_secret'] = os.getenv(
        'EXCHANGE_API_SECRET',
        config.get('exchange', {}).get('api_secret', '')
    )
    
    # 3. 数据库连接串
    config['database']['url'] = os.getenv(
        'DATABASE_URL',
        'sqlite:///dev.db'  # 开发环境默认用SQLite
    )
    
    return config

注意:环境变量也不是绝对安全。在共享服务器上,ps aux 可能会泄露环境变量。更敏感的信息,请看后面的加密方案。

17.3 多环境切换

做市系统至少需要三个环境:

环境 用途 特点
dev 本地开发调试 模拟盘、SQLite、DEBUG日志
staging 集成测试 测试网、小资金、INFO日志
production 实盘运行 主网、大资金、WARNING日志

我习惯用APP_ENV这个环境变量来切换。代码里这样写:

import os

ENV = os.getenv('APP_ENV', 'dev')

# 根据环境加载不同配置文件
config_file = f'config.{ENV}.yaml'
with open(config_file, 'r') as f:
    config = yaml.safe_load(f)

目录结构大概是:

config/
├── config.dev.yaml          # 开发环境
├── config.staging.yaml      # 测试环境
├── config.production.yaml   # 生产环境
└── config.schema.yaml       # 配置模板(带注释)

我曾经犯过一个错:三个环境的配置文件内容差异很大,导致切换环境时总有些参数忘记改。后来我学乖了——用config.schema.yaml作为模板,每个环境只覆盖差异部分。

推荐做法:用deep_merge函数,把环境配置和基础配置合并。基础配置放公共参数,环境配置只放差异项。

17.4 敏感信息加密

API密钥、私钥、数据库密码……这些是做市系统的命根子。

明文存储?绝对不行。环境变量?比明文好,但还不够。

我推荐的做法是:加密存储 + 运行时解密

方案一:使用Python的cryptography库

from cryptography.fernet import Fernet

# 生成密钥(仅一次)
key = Fernet.generate_key()
cipher = Fernet(key)

# 加密
encrypted = cipher.encrypt(b"my_api_secret_123")
print(encrypted)  # 保存这个加密后的字符串

# 解密
decrypted = cipher.decrypt(encrypted)
print(decrypted.decode())  # 输出: my_api_secret_123

密钥本身存在哪里?我通常放在环境变量CONFIG_ENCRYPTION_KEY里。或者用KMS(密钥管理服务),比如AWS KMS、HashiCorp Vault。

方案二:使用Hashicorp Vault

如果你的团队规模比较大,直接上Vault吧。它提供:

  • 动态密钥(密钥定期轮换)
  • 访问审计(谁什么时候读取了哪个密钥)
  • 租约机制(密钥过期自动失效)

代码集成也很简单:

import hvac

client = hvac.Client(url='https://vault.example.com', token=os.getenv('VAULT_TOKEN'))
secret = client.secrets.kv.v2.read_secret_version(path='market-maker/api-key')
api_key = secret['data']['data']['api_key']

避坑指南:我曾经把加密密钥和加密后的密文放在同一个配置文件里……这相当于把钥匙和锁放在一起。密钥必须独立存储,最好由不同的人管理。

17.5 配置校验

配置加载进来,不能直接用。万一有人把spread_bps写成了负数呢?

我习惯用Pydantic做配置校验:

from pydantic import BaseModel, Field, validator
from typing import Optional

class ExchangeConfig(BaseModel):
    name: str
    api_endpoint: str
    timeout_ms: int = Field(ge=100, le=30000)  # 100ms ~ 30s
    retry_count: int = Field(ge=0, le=10)

class StrategyConfig(BaseModel):
    name: str
    symbol: str
    spread_bps: int = Field(ge=1, le=100)  # 1~100基点
    order_size_btc: float = Field(gt=0)
    max_position_btc: float = Field(gt=0)

class AppConfig(BaseModel):
    exchange: ExchangeConfig
    strategy: StrategyConfig
    logging: dict

# 使用
raw_config = load_config()
validated_config = AppConfig(**raw_config)
print("配置校验通过!")

这样,任何非法配置在启动时就会报错,而不是等到运行时炸了才发现。

17.6 知识体系总览

下面这张图,概括了配置管理的核心逻辑:

配置管理体系 YAML/JSON配置文件 环境变量 加密存储(Vault) 配置合并(优先级:环境变量 > 文件) 多环境切换(dev/staging/prod) 配置校验(Pydantic Schema) 做市系统启动运行 配置文件 环境变量 加密存储

配置管理这件事,说难不难,说简单也不简单。核心就三点:分层(不同环境不同配置)、加密(敏感信息不裸奔)、校验(错误尽早暴露)。

把这三点做好了,你的做市系统至少不会因为配置问题在半夜炸掉。嗯,我当年可是被这种问题折腾过好几回,现在想想都心疼那几杯咖啡钱。

总结一下今天的要点

  • YAML适合人工编写,JSON适合程序处理
  • 环境变量覆盖配置文件,优先级明确
  • 多环境用APP_ENV切换,配置模板化
  • 敏感信息加密存储,密钥独立管理
  • 配置加载后必须校验,用Pydantic最省心
交易系统化学习资料 微信Strategy888888