【Python量化实盘工程 #02】仓位与风控不能靠感觉:5 条硬红线代码化
策略有了,撮合引擎也有了,但还差最关键的一环:在信号变成订单之前,先过一遍风控。
很多个人量化系统的事故,不是因为策略失效,而是因为:
- 满仓一只票,结果黑天鹅一来直接腰斩;
- 没有止损,浮亏 30% 还安慰自己「会回来的」;
- 单日亏损超过心理承受线,导致后面的交易全部变形;
- 行情波动突然放大,仓位却没降下来。
本文把 5 条最常见的硬红线写成代码,让你的策略从「我觉得没问题」变成「系统不让我冒险」。
一、为什么风控必须代码化?
口头风控的问题在于:临场决策会被情绪绑架。
代码化风控的核心思想是:在策略产生信号之后、发单之前,先跑一遍 RiskManager.check()。如果任何一条红线被触发,就把信号拦住或减仓。风控不是建议,是门禁。
二、本文你将得到什么
- 一个可复用的 RiskManager 类;
- 5 条硬红线的代码实现:
- 最大持仓市值上限
- 单笔止损
- 单笔止盈
- 单日亏损上限
- 波动率降仓
- 用历史行情逐日跑风控信号的完整示例;
- 可直接运行的 Python 代码。
三、5 条硬红线设计
红线 触发条件 典型动作
最大持仓 单票持仓市值 > 账户上限比例 禁止再加仓
单笔止损 浮亏 ≤ -5% 强制平仓
单笔止盈 浮盈 ≥ +10% 获利了结或移动止盈
单日亏损 当日账户回撤 ≤ -3% 当日停止开新仓
波动率降仓 20 日年化波动率 > 25% 按比例降低仓位
这些阈值不是唯一标准,你可以根据自己的资金规模和策略波动率调整。关键是:先把检查写进代码,再讨论阈值。
四、完整代码实战
import math
from dataclasses import dataclass
from typing import List, Tuple
from mairui import Client
DEMO_LICENCE = "LICENCE-66D8-9F96-0C7F0FBCD073"
INITIAL_CASH = 100_000.0
MAX_POSITION_VALUE = 60_000.0
STOP_LOSS_PCT = -0.05
TAKE_PROFIT_PCT = 0.10
DAILY_LOSS_LIMIT_PCT = -0.03
VOLATILITY_LOOKBACK = 20
VOLATILITY_REDUCTION_THRESHOLD = 0.25
def annualized_volatility(returns: List[float]) -> float:
if len(returns) < 2:
return 0.0
mean = sum(returns) / len(returns)
variance = sum((r - mean) ** 2 for r in returns) / (len(returns) - 1)
return math.sqrt(variance * 252)
class RiskManager:
def check(
self,
date: str,
cash: float,
position_qty: int,
avg_cost: float,
market_price: float,
account_value: float,
daily_return: float,
) -> List[Tuple[str, str]]:
signals = []
position_value = position_qty * market_price
红线1:最大持仓
if position_value > MAX_POSITION_VALUE:
signals.append(("MAX_POSITION",
f"持仓市值 {position_value:.2f} 元超过上限 {MAX_POSITION_VALUE:.2f} 元"))
红线2/3:止损止盈
if avg_cost > 0 and position_qty > 0:
unrealized_pct = (market_price - avg_cost) / avg_cost
if unrealized_pct <= STOP_LOSS_PCT:
signals.append(("STOP_LOSS",
f"浮亏 {unrealized_pct100:.2f}% 触及止损线 {STOP_LOSS_PCT100:.2f}%"))
elif unrealized_pct >= TAKE_PROFIT_PCT:
signals.append(("TAKE_PROFIT",
f"浮盈 {unrealized_pct100:.2f}% 触及止盈线 {TAKE_PROFIT_PCT100:.2f}%"))
红线4:单日亏损上限
if daily_return <= DAILY_LOSS_LIMIT_PCT:
signals.append(("DAILY_LOSS_LIMIT",
f"当日账户回撤 {daily_return100:.2f}% 触及上限 {DAILY_LOSS_LIMIT_PCT100:.2f}%"))
return signals
def volatility_signal(self, vol: float):
if vol > VOLATILITY_REDUCTION_THRESHOLD:
return ("VOLATILITY_REDUCTION",
f"年化波动率 {vol100:.2f}% 超过阈值 {VOLATILITY_REDUCTION_THRESHOLD100:.2f}%,建议降仓")
return None
def fetch_bars(code: str):
client = Client(DEMO_LICENCE)
return client.stock_history(code=code, period="d", dividend="f")
def main():
bars = fetch_bars("600519")
risk_mgr = RiskManager()
cash = INITIAL_CASH
position_qty = 0
avg_cost = 0.0
account_value_prev = INITIAL_CASH
初始建仓
first_price = float(bars[0]["c"])
target_qty = int(MAX_POSITION_VALUE / first_price / 100) * 100
cost = target_qty * first_price
cash -= cost
position_qty = target_qty
avg_cost = first_price
print(f"初始建仓:{bars[0]['t']} 买入 {target_qty} 股 @ {first_price:.2f}\n")
for i, bar in enumerate(bars[1:], start=1):
date = bar["t"]
price = float(bar["c"])
account_value = cash + position_qty * price
daily_return = (account_value - account_value_prev) / account_value_prev
recent_returns = []
if i >= VOLATILITY_LOOKBACK:
closes = [float(b["c"]) for b in bars[i - VOLATILITY_LOOKBACK:i + 1]]
recent_returns = [closes[j] / closes[j - 1] - 1 for j in range(1, len(closes))]
vol = annualized_volatility(recent_returns)
signals = risk_mgr.check(date, cash, position_qty, avg_cost,
price, account_value, daily_return)
vol_signal = risk_mgr.volatility_signal(vol)
if vol_signal:
signals.append(vol_signal)
if signals:
print(f"{date} 收盘={price:.2f} 总值={account_value:.2f} 当日={daily_return100:.2f}% 波动={vol100:.2f}%")
for name, desc in signals:
print(f" [触发] {name}: {desc}")
account_value_prev = account_value
if name == "main":
main()
五、运行结果(演示环境)
初始建仓:2025-09-15 买入 5300 股 @ 11.14
2025-09-16 收盘=11.54 总值=102120.00 当日=2.12% 波动=0.00%
[触发] MAX_POSITION: 持仓市值 61162.00 元超过上限 60000.00 元
2025-09-19 收盘=10.43 总值=96237.00 当日=-1.78% 波动=0.00%
[触发] STOP_LOSS: 浮亏 -6.37% 触及止损线 -5.00%
2025-10-24 收盘=10.96 总值=99046.00 当日=-3.61% 波动=47.42%
[触发] DAILY_LOSS_LIMIT: 当日账户回撤 -3.61% 触及上限 -3.00%
[触发] VOLATILITY_REDUCTION: 年化波动率 47.42% 超过阈值 25.00%,建议降仓
2025-11-05 收盘=11.63 总值=102597.00 当日=3.97% 波动=55.37%
[触发] MAX_POSITION: 持仓市值 61639.00 元超过上限 60000.00 元
[触发] VOLATILITY_REDUCTION: 年化波动率 55.37% 超过阈值 25.00%,建议降仓
汇总:共触发风控信号 34 次
最终账户总值:99258.00 元,总收益 -0.74%
六、常见坑与注意
- 阈值不要照搬:止损 -5% / 止盈 10% 是示例。真实策略要根据历史回测的最大回撤和波动率来设定,最好先做压力测试。
- 单日亏损上限要锁定:一旦触发,建议当天停止开新仓,而不是只提示。否则很容易「再赌一把」。
- 波动率要用滚动窗口:不要用全历史波动率,否则最近的风险变化会被早期数据稀释。
- 风控信号 ≠ 自动平仓:本文示例只输出信号,真实系统要把它接到下单模块。建议用事件总线(Event Bus)解耦策略、风控、执行三层。
- 持仓均价要算对:多次加减仓时,avg_cost 应该用加权平均;如果做 T0,还要区分可卖数量和总数量。
七、小结 + 下篇预告
本文把 5 条最常见的风控红线写成了可执行的 Python 代码。核心结论是:
风控不是交易结束后的复盘,而是交易开始前的门禁。
下篇我们将在撮合引擎和风控之上,搭建一个模拟盘(Paper Trading)——用真实行情、虚拟资金、完整订单落库,把回测和实盘之间的鸿沟再缩小一步。
免责声明:本文仅供技术学习与交流,不构成任何投资建议。市场有风险,决策需谨慎。

浙公网安备 33010602011771号