BigQuant使用文档

分钟回测demos

由qxiao创建,最终由qxiao 被浏览 1 用户

期货策略一:唐奇安通道突破(螺纹钢)

CTA 趋势跟踪策略,基于 N 根分钟 K 线的最高价/最低价构建唐奇安通道。当前收盘价 >= 上轨时开多/平空,当前收盘价 <= 下轨时开空/平多。

from datetime import datetime
import pandas as pd
from bigquant import bigtrader, dai
from bigtrader.constant import Direction, OrderType

# ── 策略参数 ──────────────────────────────────────────────────────────────────
INSTRUMENT = "rb8888.SHF"   # 螺纹钢主连合约
DC_PERIOD  = 20             # 唐奇安通道周期(分钟 K 线根数)
LOTS       = 1              # 每次开仓手数
CAPITAL    = 500000         # 初始资金(元)

START_DATE = "2026-04-30"
END_DATE   = "2026-05-08"

def initialize(context: bigtrader.IContext):
    context.logger.info("初始化:加载分钟行情,计算唐奇安通道...")

    sql = """
    SELECT
        cn_future_bar1m.date,
        cn_future_bar1m.instrument,
        cn_future_bar1m.date::datetime::date as trading_day,
        dominant,
        close,
        high,
        low,
        m_max(high, {period}, pb:=cn_future_bar1m.instrument, ob:=cn_future_bar1m.date) AS dc_upper,
        m_min(low,  {period}, pb:=cn_future_bar1m.instrument, ob:=cn_future_bar1m.date) AS dc_lower,
        CASE
            WHEN close >= dc_upper THEN  1
            WHEN close <= dc_lower THEN -1
            ELSE 0
        END AS signal
    FROM cn_future_bar1m
    LEFT JOIN cn_future_dominant on cn_future_bar1m.date::datetime::date=cn_future_dominant.date
    """.format(period=DC_PERIOD)

    df = dai.query(sql, filters={
        "date": [
            context.add_trading_days(context.start_date, -5),
            context.end_date,
        ],
        "instrument": [INSTRUMENT]
    }).df()
    df['trading_day'] = pd.to_datetime(df['trading_day'])

    context.data = df
    context.lots = LOTS
    context.logger.info(f"数据加载完成,共 {len(df)} 条分钟记录")

def before_trading_start(context: bigtrader.IContext, data: bigtrader.IBarData):
    today = pd.to_datetime(data.current_dt.strftime("%Y-%m-%d"))
    df_today = context.data[context.data["trading_day"] == today]
    if df_today.empty:
        return
    dominant_symbol = df_today["dominant"].iloc[0]
    if dominant_symbol and str(dominant_symbol) != "nan":
        context.subscribe_bar([dominant_symbol])
        context.logger.info(f"订阅分钟行情:{dominant_symbol}")

def handle_data(context: bigtrader.IContext, data: bigtrader.IBarData):
    current_dt = data.current_dt
    df_bar = context.data[context.data["date"] == current_dt]
    if df_bar.empty:
        return

    signal          = int(df_bar["signal"].iloc[0])
    price           = float(df_bar["close"].iloc[0])
    dominant_symbol = df_bar["dominant"].iloc[0]

    if not dominant_symbol or str(dominant_symbol) == "nan":
        return

    positions       = context.get_account_positions()
    position_symbol = list(positions.keys())[0] if positions else dominant_symbol

    long_qty  = context.get_account_position(position_symbol, direction=Direction.LONG).avail_qty
    short_qty = context.get_account_position(position_symbol, direction=Direction.SHORT).avail_qty

    # ── 移仓换月 ──────────────────────────────────────────────────────────────
    if dominant_symbol != position_symbol:
        context.logger.info(f"移仓换月:{position_symbol} → {dominant_symbol}")
        if long_qty > 0:
            context.sell_close(position_symbol, long_qty, price, order_type=OrderType.MARKET)
            context.buy_open(dominant_symbol, long_qty, price, order_type=OrderType.MARKET)
        elif short_qty > 0:
            context.buy_close(position_symbol, short_qty, price, order_type=OrderType.MARKET)
            context.sell_open(dominant_symbol, short_qty, price, order_type=OrderType.MARKET)
        return

    # ── 信号交易逻辑 ──────────────────────────────────────────────────────────
    if signal == 1:
        if short_qty > 0:
            context.buy_close(position_symbol, short_qty, price, order_type=OrderType.MARKET)
        if long_qty == 0:
            context.buy_open(dominant_symbol, context.lots, price, order_type=OrderType.MARKET)
    elif signal == -1:
        if long_qty > 0:
            context.sell_close(position_symbol, long_qty, price, order_type=OrderType.MARKET)
        if short_qty == 0:
            context.sell_open(dominant_symbol, context.lots, price, order_type=OrderType.MARKET)

performance = bigtrader.run(
    market=bigtrader.Market.CN_FUTURE,
    frequency=bigtrader.Frequency.MINUTE,
    start_date=START_DATE,
    end_date=END_DATE,
    capital_base=CAPITAL,
    initialize=initialize,
    before_trading_start=before_trading_start,
    handle_data=handle_data,
    order_price_field_buy="open",
    order_price_field_sell="open",
)

期货策略二:EMA 双均线交叉 + ATR 动态止损(铜)

基于分钟 K 线的 EMA 快慢线交叉策略,结合 ATR 动态止损。EMA_fast(12周期)上穿 EMA_slow(26周期)时金叉开多/平空,下穿时死叉开空/平多。止损距离 = 2 × ATR。

from datetime import datetime
import pandas as pd
from bigquant import bigtrader, dai
from bigtrader.constant import Direction, OrderType

# ── 策略参数 ──────────────────────────────────────────────────────────────────
INSTRUMENT = "cu8888.SHF"   # 铜主连合约
EMA_FAST   = 12             # 快线 EMA 周期
EMA_SLOW   = 26             # 慢线 EMA 周期
ATR_PERIOD = 14             # ATR 周期
ATR_MULT   = 2.0            # 止损距离 = ATR_MULT × ATR
LOTS       = 1
CAPITAL    = 500000

START_DATE = "2026-04-30"
END_DATE   = "2026-05-08"

def initialize(context: bigtrader.IContext):
    context.logger.info("初始化:加载分钟行情,计算 EMA 双均线 + ATR 止损...")

    sql = """
    SELECT
        cn_future_bar1m.date,
        cn_future_bar1m.instrument,
        cn_future_bar1m.date::datetime::date AS trading_day,
        dominant,
        close,
        m_ta_ema(close, {fast}, pb:=cn_future_bar1m.instrument, ob:=cn_future_bar1m.date) AS ema_fast,
        m_ta_ema(close, {slow}, pb:=cn_future_bar1m.instrument, ob:=cn_future_bar1m.date) AS ema_slow,
        m_lag(m_ta_ema(close, {fast}, pb:=cn_future_bar1m.instrument, ob:=cn_future_bar1m.date), 1,
              pb:=cn_future_bar1m.instrument, ob:=cn_future_bar1m.date) AS ema_fast_prev,
        m_lag(m_ta_ema(close, {slow}, pb:=cn_future_bar1m.instrument, ob:=cn_future_bar1m.date), 1,
              pb:=cn_future_bar1m.instrument, ob:=cn_future_bar1m.date) AS ema_slow_prev,
        m_ta_atr(high, low, close, {atr}, pb:=cn_future_bar1m.instrument, ob:=cn_future_bar1m.date) AS atr,
        CASE
            WHEN ema_fast > ema_slow AND ema_fast_prev <= ema_slow_prev THEN 1
            WHEN ema_fast < ema_slow AND ema_fast_prev >= ema_slow_prev THEN -1
            ELSE 0
        END AS signal
    FROM cn_future_bar1m
    LEFT JOIN cn_future_dominant ON
        cn_future_bar1m.date::datetime::date = cn_future_dominant.date
        AND cn_future_bar1m.instrument = cn_future_dominant.instrument
    """.format(fast=EMA_FAST, slow=EMA_SLOW, atr=ATR_PERIOD)

    df = dai.query(sql, filters={
        "date": [
            context.add_trading_days(context.start_date, -10),
            context.end_date,
        ],
        "instrument": [INSTRUMENT]
    }).df()
    df["trading_day"] = pd.to_datetime(df["trading_day"])

    context.data        = df
    context.lots        = LOTS
    context.stop_prices = {}
    context.logger.info(f"数据加载完成,共 {len(df)} 条分钟记录")

def before_trading_start(context: bigtrader.IContext, data: bigtrader.IBarData):
    today = pd.to_datetime(data.current_dt.strftime("%Y-%m-%d"))
    df_today = context.data[context.data["trading_day"] == today]
    if df_today.empty:
        return
    dominant_symbol = df_today["dominant"].iloc[0]
    if dominant_symbol and str(dominant_symbol) != "nan":
        context.subscribe_bar([dominant_symbol])

def handle_data(context: bigtrader.IContext, data: bigtrader.IBarData):
    current_dt = data.current_dt
    df_bar = context.data[context.data["date"] == current_dt]
    if df_bar.empty:
        return

    row             = df_bar.iloc[0]
    signal          = int(row["signal"])
    price           = float(row["close"])
    atr             = row["atr"]
    dominant_symbol = row["dominant"]

    if not dominant_symbol or str(dominant_symbol) == "nan":
        return
    if pd.isna(atr) or atr <= 0:
        return

    positions       = context.get_account_positions()
    position_symbol = list(positions.keys())[0] if positions else dominant_symbol

    long_qty  = context.get_account_position(position_symbol, direction=Direction.LONG).avail_qty
    short_qty = context.get_account_position(position_symbol, direction=Direction.SHORT).avail_qty

    # ── 移仓换月 ──────────────────────────────────────────────────────────────
    if dominant_symbol != position_symbol:
        context.logger.info(f"移仓换月:{position_symbol} → {dominant_symbol}")
        if long_qty > 0:
            context.sell_close(position_symbol, long_qty, price, order_type=OrderType.MARKET)
            context.buy_open(dominant_symbol, long_qty, price, order_type=OrderType.MARKET)
            context.stop_prices[dominant_symbol] = price - ATR_MULT * atr
        elif short_qty > 0:
            context.buy_close(position_symbol, short_qty, price, order_type=OrderType.MARKET)
            context.sell_open(dominant_symbol, short_qty, price, order_type=OrderType.MARKET)
            context.stop_prices[dominant_symbol] = price + ATR_MULT * atr
        context.stop_prices.pop(position_symbol, None)
        return

    # ── ATR 动态止损检查 ──────────────────────────────────────────────────────
    stop_price = context.stop_prices.get(dominant_symbol)
    if stop_price is not None:
        if long_qty > 0 and price <= stop_price:
            context.logger.info(f"多头止损触发:price={price:.0f} <= stop={stop_price:.0f}")
            context.sell_close(dominant_symbol, long_qty, price, order_type=OrderType.MARKET)
            context.stop_prices.pop(dominant_symbol, None)
            return
        if short_qty > 0 and price >= stop_price:
            context.logger.info(f"空头止损触发:price={price:.0f} >= stop={stop_price:.0f}")
            context.buy_close(dominant_symbol, short_qty, price, order_type=OrderType.MARKET)
            context.stop_prices.pop(dominant_symbol, None)
            return

    # ── EMA 双均线交叉信号 ────────────────────────────────────────────────────
    if signal == 1:
        if short_qty > 0:
            context.buy_close(dominant_symbol, short_qty, price, order_type=OrderType.MARKET)
            context.stop_prices.pop(dominant_symbol, None)
        if long_qty == 0:
            context.buy_open(dominant_symbol, context.lots, price, order_type=OrderType.MARKET)
            context.stop_prices[dominant_symbol] = price - ATR_MULT * atr
            context.logger.info(f"金叉开多:price={price:.0f},止损={context.stop_prices[dominant_symbol]:.0f}")
    elif signal == -1:
        if long_qty > 0:
            context.sell_close(dominant_symbol, long_qty, price, order_type=OrderType.MARKET)
            context.stop_prices.pop(dominant_symbol, None)
        if short_qty == 0:
            context.sell_open(dominant_symbol, context.lots, price, order_type=OrderType.MARKET)
            context.stop_prices[dominant_symbol] = price + ATR_MULT * atr
            context.logger.info(f"死叉开空:price={price:.0f},止损={context.stop_prices[dominant_symbol]:.0f}")

performance = bigtrader.run(
    market=bigtrader.Market.CN_FUTURE,
    frequency=bigtrader.Frequency.MINUTE,
    start_date=START_DATE,
    end_date=END_DATE,
    capital_base=CAPITAL,
    initialize=initialize,
    before_trading_start=before_trading_start,
    handle_data=handle_data,
    order_price_field_buy="open",
    order_price_field_sell="open",
)

期权分钟回测一:买入并持有科创板50ETF期权

"""买入并持有华夏上证科创板50ETF期权2605认购期权"""
import bigtrader
import math

def initialize(context):
    context.bought = False

def before_trading_start(context, data):
    context.subscribe_bar(["10011413.SHO"], '1m')

def is_none_or_nan(x):
    return x is None or math.isnan(x)

def handle_data(context, data):
    dt_str = context.current_dt.strftime('%Y-%m-%d %H:%M:%S')
    if not is_none_or_nan(data.current("10011413.SHO", 'close')) and not context.bought:
        context.logger.info(f'{dt_str}下单500张期权标的')
        ret = context.buy_open("10011413.SHO", 500)
        if ret >= 0:
            context.bought = True

if __name__ == "__main__":
    performance = bigtrader.run(
        market=bigtrader.Market.CN_STOCK_OPTION,
        frequency=bigtrader.Frequency.MINUTE,
        start_date="2026-05-01",
        end_date="2026-05-14",
        capital_base=500000,
        instruments=["588000.SH"],
        benchmark=None,
        initialize=initialize,
        before_trading_start=before_trading_start,
        handle_data=handle_data,
    )

期权分钟回测二:日内期权偏度策略(IV 择时 + Delta 对冲)

中证1000指数期权(MO)+ 中证1000股指期货(IM)Delta 对冲策略。等距行权价(ATM±100),9:32 开仓前实时计算双腿 IV,仅当 Put IV / Call IV > 偏度阈值时开仓(风险逆转),同时做空 IM 期货抵消 Delta,实现方向中性。盘中每 30 分钟 re-hedge,收盘 14:56 强制平仓。

# ── Black-Scholes IV 反推 ────────────────────────────────────
def bs_iv(option_price, S, K, T, r=0.02, option_type='call'):
    import math, numpy as np
    from scipy.stats import norm
    from scipy.optimize import brentq

    if any(math.isnan(x) for x in [option_price, S, K, T]):
        return None
    if T <= 0 or S <= 0 or K <= 0 or option_price <= 0:
        return None
    intrinsic = max(0.0, S - K) if option_type == 'call' else max(0.0, K - S)
    if option_price <= intrinsic + 1e-6:
        return None

    def bs_price(sigma):
        d1 = (np.log(S / K) + (r + 0.5 * sigma**2) * T) / (sigma * np.sqrt(T))
        d2 = d1 - sigma * np.sqrt(T)
        if option_type == 'call':
            return S * norm.cdf(d1) - K * np.exp(-r * T) * norm.cdf(d2)
        else:
            return K * np.exp(-r * T) * norm.cdf(-d2) - S * norm.cdf(-d1)

    try:
        return brentq(lambda sigma: bs_price(sigma) - option_price, 1e-6, 10.0, maxiter=100)
    except Exception:
        return None

def bs_delta(S, K, T, r=0.02, sigma=0.20, option_type='call'):
    import numpy as np
    from scipy.stats import norm
    if T <= 0 or S <= 0 or K <= 0 or sigma <= 0:
        return None
    try:
        d1 = (np.log(S / K) + (r + 0.5 * sigma**2) * T) / (sigma * np.sqrt(T))
        return float(norm.cdf(d1)) if option_type == 'call' else float(norm.cdf(d1) - 1)
    except Exception:
        return None

# 盘中 re-hedge 时间点(每 30 分钟一次)
REHEDGE_TIMES = frozenset([
    (10, 0), (10, 30), (11, 0), (11, 30),
    (13, 0), (13, 30), (14, 0), (14, 30),
])

def initialize(context):
    from bigmodule import M
    context.enter_hour   = 9
    context.enter_minute = 32
    context.exit_hour    = 14
    context.exit_minute  = 56
    context.strike_offset             = 100
    context.skew_threshold            = 1.10
    context.portfolio_stop_loss_ratio = 0.35
    context.risk_free_rate            = 0.02
    context.num_contracts     = 2
    context.option_multiplier = 100
    context.im_multiplier     = 200
    # ... (完整代码见原始文档)

import bigtrader
Q = bigtrader.run(
    market=bigtrader.Market.CN_FUTURE_OPTION,
    frequency=bigtrader.Frequency.MINUTE,
    instruments=["000852.SH"],
    benchmark="000852.SH",
    start_date="2024-01-01",
    end_date="2024-06-17",
    capital_base=500000,
    initialize=initialize,
    before_trading_start=before_trading_start,
    handle_data=handle_data,
)

\

{link}