BigQuant使用文档

分钟回测demos

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

股票分钟策略一:RSI 超买超卖策略

标的:平安银行(000001.SZ),频率:1分钟。RSI(14) < 30 超卖买入,RSI(14) > 70 超买卖出,止损 -3%,止盈 +5%,尾盘(14:55)强制平仓。

from bigquant import bigtrader
from datetime import time

def is_trading_time(dt):
    t = dt.time()
    return time(9, 30) <= t <= time(11, 30) or time(13, 0) <= t <= time(15, 0)

def calc_rsi(prices, period=14):
    if len(prices) < period + 1:
        return None
    gains, losses = [], []
    for i in range(1, len(prices)):
        delta = prices[i] - prices[i - 1]
        gains.append(max(delta, 0))
        losses.append(max(-delta, 0))
    avg_gain = sum(gains[:period]) / period
    avg_loss = sum(losses[:period]) / period
    for i in range(period, len(gains)):
        avg_gain = (avg_gain * (period - 1) + gains[i]) / period
        avg_loss = (avg_loss * (period - 1) + losses[i]) / period
    if avg_loss == 0:
        return 100.0
    return 100 - (100 / (1 + avg_gain / avg_loss))

RSI_PERIOD    = 14
RSI_OVERSOLD  = 30
RSI_OVERBOUGHT = 70
STOP_LOSS     = -0.03
TAKE_PROFIT   = 0.05
CLOSE_TIME    = time(14, 55)
INSTRUMENT    = "000001.SZ"

def initialize(context: bigtrader.IContext):
    context.set_commission(bigtrader.PerOrder(buy_cost=0.0003, sell_cost=0.0013, min_cost=5))
    context.entry_price = {}

def before_trading_start(context: bigtrader.IContext, data):
    context.subscribe_bar(context.instruments, period='1m')

def handle_data(context: bigtrader.IContext, data):
    now = data.current_dt
    if not is_trading_time(now):
        return

    # 尾盘强制平仓
    if now.time() >= CLOSE_TIME:
        positions = context.get_account_positions()
        if INSTRUMENT in positions and positions[INSTRUMENT].current_qty > 0:
            context.order_target_percent(INSTRUMENT, 0)
            context.entry_price.pop(INSTRUMENT, None)
        return

    hist = data.history(INSTRUMENT, "close", RSI_PERIOD + 20, "1m")
    if hist is None or len(hist) < RSI_PERIOD + 1:
        return

    close_prices  = list(hist)
    rsi           = calc_rsi(close_prices, RSI_PERIOD)
    if rsi is None:
        return

    current_price = close_prices[-1]
    positions     = context.get_account_positions()
    holding       = INSTRUMENT in positions and positions[INSTRUMENT].current_qty > 0

    # 止盈止损检查
    if holding and INSTRUMENT in context.entry_price:
        entry   = context.entry_price[INSTRUMENT]
        pnl_pct = (current_price - entry) / entry
        if pnl_pct <= STOP_LOSS:
            context.order_target_percent(INSTRUMENT, 0)
            context.entry_price.pop(INSTRUMENT, None)
            return
        if pnl_pct >= TAKE_PROFIT:
            context.order_target_percent(INSTRUMENT, 0)
            context.entry_price.pop(INSTRUMENT, None)
            return

    # RSI 信号
    if not holding and rsi < RSI_OVERSOLD:
        context.order_target_percent(INSTRUMENT, 0.95)
        context.entry_price[INSTRUMENT] = current_price
    elif holding and rsi > RSI_OVERBOUGHT:
        context.order_target_percent(INSTRUMENT, 0)
        context.entry_price.pop(INSTRUMENT, None)

performance = bigtrader.run(
    market=bigtrader.Market.CN_STOCK,
    frequency=bigtrader.Frequency.MINUTE,
    instruments=[INSTRUMENT],
    start_date="2026-01-01",
    end_date="2026-05-09",
    capital_base=1_000_000,
    initialize=initialize,
    before_trading_start=before_trading_start,
    handle_data=handle_data,
    order_price_field_buy="close",
    order_price_field_sell="close",
)

股票分钟策略二:均线突破策略(沪深300成分股)

每日从沪深300中选出5日动量最强的前20只股票,盘中分钟MA5上穿MA20(金叉)买入,下穿(死叉)卖出,14:55尾盘强制平仓,每只股票等权仓位 1/20。

from bigquant import bigtrader, dai
from datetime import time

def is_trading_time(dt):
    t = dt.time()
    return time(9, 30) <= t <= time(11, 30) or time(13, 0) <= t <= time(15, 0)

def ma(prices, n):
    return sum(prices[-n:]) / n

MA_SHORT   = 5
MA_LONG    = 20
STOCK_NUM  = 20
CLOSE_TIME = time(14, 55)

def initialize(context: bigtrader.IContext):
    context.set_commission(bigtrader.PerOrder(buy_cost=0.0003, sell_cost=0.0013, min_cost=5))

    sql = """
    SELECT date, instrument, close / m_lag(close, 5) - 1 AS momentum_5d
    FROM cn_stock_prefactors
    WHERE is_hs300 = 1 AND st_status = 0 AND suspended = 0 AND list_days > 365
    ORDER BY date, momentum_5d DESC
    """
    df = dai.query(sql, filters={"date": [
        context.add_trading_days(context.start_date, -10),
        context.end_date,
    ]}).df()

    context.daily_selected = (
        df.groupby("date").head(STOCK_NUM)
        .groupby("date")["instrument"].apply(list).to_dict()
    )
    context.daily_selected = {str(k)[:10]: v for k, v in context.daily_selected.items()}

def before_trading_start(context: bigtrader.IContext, data):
    today = data.current_dt.strftime("%Y-%m-%d")
    context.selected = context.daily_selected.get(today, [])
    context.subscribe_bar(context.selected, period="1m")

def handle_data(context: bigtrader.IContext, data):
    now = data.current_dt
    if not is_trading_time(now):
        return

    today = now.strftime("%Y-%m-%d")
    context.selected = context.daily_selected.get(today, [])

    if now.time() >= CLOSE_TIME:
        for ins, pos in context.get_account_positions().items():
            if pos.current_qty > 0:
                context.order_target_percent(ins, 0)
        return

    positions   = context.get_account_positions()
    holding_set = {ins for ins, pos in positions.items() if pos.current_qty > 0}

    for ins in list(holding_set):
        if ins not in context.selected:
            context.order_target_percent(ins, 0)
            holding_set.discard(ins)

    for instrument in context.selected:
        hist = data.history(instrument, "close", MA_LONG + 1, "1m")
        if hist is None or len(hist) < MA_LONG + 1:
            continue
        prices  = list(hist)
        ma_s    = ma(prices, MA_SHORT)
        ma_l    = ma(prices, MA_LONG)
        holding = instrument in holding_set

        if not holding and ma_s > ma_l:
            context.order_target_percent(instrument, 1.0 / STOCK_NUM)
            holding_set.add(instrument)
        elif holding and ma_s < ma_l:
            context.order_target_percent(instrument, 0)
            holding_set.discard(instrument)

performance = bigtrader.run(
    market=bigtrader.Market.CN_STOCK,
    frequency=bigtrader.Frequency.MINUTE,
    start_date="2026-01-01",
    end_date="2026-01-20",
    capital_base=1_000_000,
    initialize=initialize,
    before_trading_start=before_trading_start,
    handle_data=handle_data,
    order_price_field_buy="close",
    order_price_field_sell="close",
    benchmark="000300.SH",
)

ETF 分钟策略一:单 ETF 均线趋势策略

标的:沪深300ETF(510300.SH),MA5 > MA20 多头趋势持有,MA5 < MA20 空仓,止损 -2%,止盈 +3%,尾盘强制平仓。

from bigquant import bigtrader
from datetime import time

def is_trading_time(dt):
    t = dt.time()
    return time(9, 30) <= t <= time(11, 30) or time(13, 0) <= t <= time(15, 0)

def ma(prices, n):
    return sum(prices[-n:]) / n

MA_SHORT    = 5
MA_LONG     = 20
STOP_LOSS   = -0.02
TAKE_PROFIT = 0.03
CLOSE_TIME  = time(14, 55)
INSTRUMENT  = "510300.SH"

def initialize(context: bigtrader.IContext):
    context.set_commission(bigtrader.PerOrder(buy_cost=0.0003, sell_cost=0.0013, min_cost=5))
    context.entry_price = None

def before_trading_start(context: bigtrader.IContext, data):
    context.subscribe_bar(context.instruments, period="1m")
    context.entry_price = None

def handle_data(context: bigtrader.IContext, data):
    now = data.current_dt
    if not is_trading_time(now):
        return

    if now.time() >= CLOSE_TIME:
        positions = context.get_account_positions()
        if INSTRUMENT in positions and positions[INSTRUMENT].current_qty > 0:
            context.order_target_percent(INSTRUMENT, 0)
            context.entry_price = None
        return

    hist = data.history(INSTRUMENT, "close", MA_LONG + 1, "1m")
    if hist is None or len(hist) < MA_LONG + 1:
        return

    prices        = list(hist)
    ma_s          = ma(prices, MA_SHORT)
    ma_l          = ma(prices, MA_LONG)
    current_price = prices[-1]

    positions = context.get_account_positions()
    holding   = INSTRUMENT in positions and positions[INSTRUMENT].current_qty > 0

    if holding and context.entry_price is not None:
        pnl_pct = (current_price - context.entry_price) / context.entry_price
        if pnl_pct <= STOP_LOSS:
            context.order_target_percent(INSTRUMENT, 0)
            context.entry_price = None
            return
        if pnl_pct >= TAKE_PROFIT:
            context.order_target_percent(INSTRUMENT, 0)
            context.entry_price = None
            return

    if not holding and ma_s > ma_l:
        context.order_target_percent(INSTRUMENT, 0.95)
        context.entry_price = current_price
    elif holding and ma_s < ma_l:
        context.order_target_percent(INSTRUMENT, 0)
        context.entry_price = None

performance = bigtrader.run(
    market=bigtrader.Market.CN_FUND,
    frequency=bigtrader.Frequency.MINUTE,
    instruments=[INSTRUMENT],
    start_date="2026-04-25",
    end_date="2026-05-09",
    capital_base=1_000_000,
    initialize=initialize,
    before_trading_start=before_trading_start,
    handle_data=handle_data,
    order_price_field_buy="close",
    order_price_field_sell="close",
    benchmark="000300.SH",
)

ETF 分钟策略二:多 ETF 动量轮动

标的池:沪深300ETF / 纳指ETF / 黄金ETF / 国债ETF / 科创50ETF。每日开盘前用前一日日线数据计算各ETF的5日动量,选出动量最强的前3只;盘中分钟MA5 > MA20持有,MA5 < MA20空仓,每只ETF等权仓位 1/3,尾盘强制平仓。

from bigquant import bigtrader, dai
from datetime import time

def is_trading_time(dt):
    t = dt.time()
    return time(9, 30) <= t <= time(11, 30) or time(13, 0) <= t <= time(15, 0)

def ma(prices, n):
    return sum(prices[-n:]) / n

MA_SHORT   = 5
MA_LONG    = 20
ETF_NUM    = 3
CLOSE_TIME = time(14, 55)
ETF_POOL   = ["510300.SH", "513100.SH", "518880.SH", "511010.SH", "588000.SH", "159941.SZ", "513520.SH"]

def initialize(context: bigtrader.IContext):
    context.set_commission(bigtrader.PerOrder(buy_cost=0.0003, sell_cost=0.0013, min_cost=5))

    sql = """
    SELECT date, instrument, close / m_lag(close, 5) - 1 AS momentum_5d
    FROM cn_fund_bar1d
    WHERE instrument IN $etf_pool
    ORDER BY date, momentum_5d DESC
    """
    df = dai.query(sql, filters={"date": [
        context.add_trading_days(context.start_date, -10),
        context.end_date,
    ]}, params={"etf_pool": ETF_POOL}).df()

    context.daily_selected = (
        df.dropna(subset=["momentum_5d"])
        .groupby("date").head(ETF_NUM)
        .groupby("date")["instrument"].apply(list).to_dict()
    )
    context.daily_selected = {str(k)[:10]: v for k, v in context.daily_selected.items()}

def before_trading_start(context: bigtrader.IContext, data):
    today = data.current_dt.strftime("%Y-%m-%d")
    context.selected = context.daily_selected.get(today, [])
    if context.selected:
        context.subscribe_bar(context.selected, period="1m")

def handle_data(context: bigtrader.IContext, data):
    now      = data.current_dt
    selected = context.selected
    if not is_trading_time(now):
        return

    if now.time() >= CLOSE_TIME:
        for ins, pos in context.get_account_positions().items():
            if pos.current_qty > 0:
                context.order_target_percent(ins, 0)
        return

    positions   = context.get_account_positions()
    holding_set = {ins for ins, pos in positions.items() if pos.current_qty > 0}

    for ins in list(holding_set):
        if ins not in selected:
            context.order_target_percent(ins, 0)
            holding_set.discard(ins)

    for instrument in selected:
        hist = data.history(instrument, "close", MA_LONG + 1, "1m")
        if hist is None or len(hist) < MA_LONG + 1:
            continue
        prices  = list(hist)
        ma_s    = ma(prices, MA_SHORT)
        ma_l    = ma(prices, MA_LONG)
        holding = instrument in holding_set

        if not holding and ma_s > ma_l:
            context.order_target_percent(instrument, 1.0 / ETF_NUM)
            holding_set.add(instrument)
        elif holding and ma_s < ma_l:
            context.order_target_percent(instrument, 0)
            holding_set.discard(instrument)

performance = bigtrader.run(
    market=bigtrader.Market.CN_FUND,
    frequency=bigtrader.Frequency.MINUTE,
    start_date="2026-04-25",
    end_date="2026-05-09",
    capital_base=1_000_000,
    initialize=initialize,
    before_trading_start=before_trading_start,
    handle_data=handle_data,
    order_price_field_buy="close",
    order_price_field_sell="close",
    benchmark="000300.SH",
)

可转债分钟策略一:均线趋势策略

每日开盘前,从可转债池中按双低因子(低价格+低溢价率)选出前5只;盘中分钟MA5 > MA20持有,MA5 < MA20空仓,每只等权仓位 1/5,尾盘强制平仓。

from bigquant import bigtrader, dai
from datetime import time

def is_trading_time(dt):
    t = dt.time()
    return time(9, 30) <= t <= time(11, 30) or time(13, 0) <= t <= time(15, 0)

def ma(prices, n):
    return sum(prices[-n:]) / n

MA_SHORT   = 5
MA_LONG    = 20
CB_NUM     = 5
CLOSE_TIME = time(14, 55)

def initialize(context: bigtrader.IContext):
    context.set_commission(bigtrader.PerOrder(buy_cost=0.0003, sell_cost=0.0013, min_cost=5))

    sql = """
    SELECT date, instrument, close + conversion_premium_rate AS double_low
    FROM cn_cbond_bar1d
    INNER JOIN cn_cbond_analyze_metric USING (date, instrument)
    WHERE bond_balance > 1 AND remaining_days > 120
      AND conversion_premium_rate > -10 AND conversion_premium_rate < 50
      AND close > 80 AND close < 150
    ORDER BY date, double_low ASC
    """
    df = dai.query(sql, filters={"date": [
        context.add_trading_days(context.start_date, -5),
        context.end_date,
    ]}).df()

    context.daily_selected = (
        df.dropna(subset=["double_low"])
        .groupby("date").head(CB_NUM)
        .groupby("date")["instrument"].apply(list).to_dict()
    )
    context.daily_selected = {str(k)[:10]: v for k, v in context.daily_selected.items()}

def before_trading_start(context: bigtrader.IContext, data):
    today = data.current_dt.strftime("%Y-%m-%d")
    context.selected = context.daily_selected.get(today, [])
    if context.selected:
        context.subscribe_bar(context.selected, period="1m")

def handle_data(context: bigtrader.IContext, data):
    now      = data.current_dt
    selected = context.selected
    if not is_trading_time(now):
        return

    if now.time() >= CLOSE_TIME:
        for ins, pos in context.get_account_positions().items():
            if pos.current_qty > 0:
                context.order_target_percent(ins, 0)
        return

    positions   = context.get_account_positions()
    holding_set = {ins for ins, pos in positions.items() if pos.current_qty > 0}

    for ins in list(holding_set):
        if ins not in selected:
            context.order_target_percent(ins, 0)
            holding_set.discard(ins)

    for instrument in selected:
        hist = data.history(instrument, "close", MA_LONG + 1, "1m")
        if hist is None or len(hist) < MA_LONG + 1:
            continue
        prices  = list(hist)
        ma_s    = ma(prices, MA_SHORT)
        ma_l    = ma(prices, MA_LONG)
        holding = instrument in holding_set

        if not holding and ma_s > ma_l:
            context.order_target_percent(instrument, 1.0 / CB_NUM)
            holding_set.add(instrument)
        elif holding and ma_s < ma_l:
            context.order_target_percent(instrument, 0)
            holding_set.discard(instrument)

performance = bigtrader.run(
    market=bigtrader.Market.CN_CBOND,
    frequency=bigtrader.Frequency.MINUTE,
    start_date="2026-04-25",
    end_date="2026-05-09",
    capital_base=500_000,
    initialize=initialize,
    before_trading_start=before_trading_start,
    handle_data=handle_data,
    order_price_field_buy="close",
    order_price_field_sell="close",
    benchmark="000300.SH",
)

可转债分钟策略二:RSI 超买超卖策略

每日开盘前按双低因子选出前5只转债,盘中 RSI(14) < 30 超卖买入,RSI > 70 超买卖出,止损 -3%,止盈 +5%,尾盘强制平仓。

from bigquant import bigtrader, dai
from datetime import time

def is_trading_time(dt):
    t = dt.time()
    return time(9, 30) <= t <= time(11, 30) or time(13, 0) <= t <= time(15, 0)

def calc_rsi(prices, period=14):
    if len(prices) < period + 1:
        return None
    gains, losses = [], []
    for i in range(1, len(prices)):
        delta = prices[i] - prices[i - 1]
        gains.append(max(delta, 0))
        losses.append(max(-delta, 0))
    avg_gain = sum(gains[:period]) / period
    avg_loss = sum(losses[:period]) / period
    for i in range(period, len(gains)):
        avg_gain = (avg_gain * (period - 1) + gains[i]) / period
        avg_loss = (avg_loss * (period - 1) + losses[i]) / period
    if avg_loss == 0:
        return 100.0
    return 100 - (100 / (1 + avg_gain / avg_loss))

RSI_PERIOD    = 14
RSI_OVERSOLD  = 30
RSI_OVERBOUGHT = 70
STOP_LOSS     = -0.03
TAKE_PROFIT   = 0.05
CB_NUM        = 5
CLOSE_TIME    = time(14, 55)

def initialize(context: bigtrader.IContext):
    context.set_commission(bigtrader.PerOrder(buy_cost=0.0003, sell_cost=0.0013, min_cost=5))

    sql = """
    SELECT date, instrument, close + conversion_premium_rate AS double_low
    FROM cn_cbond_bar1d
    INNER JOIN cn_cbond_analyze_metric USING (date, instrument)
    WHERE bond_balance > 1 AND remaining_days > 120
      AND conversion_premium_rate > -10 AND conversion_premium_rate < 50
      AND close > 80 AND close < 150
    ORDER BY date, double_low ASC
    """
    df = dai.query(sql, filters={"date": [
        context.add_trading_days(context.start_date, -5),
        context.end_date,
    ]}).df()

    context.daily_selected = (
        df.dropna(subset=["double_low"])
        .groupby("date").head(CB_NUM)
        .groupby("date")["instrument"].apply(list).to_dict()
    )
    context.daily_selected = {str(k)[:10]: v for k, v in context.daily_selected.items()}

def before_trading_start(context: bigtrader.IContext, data):
    today = data.current_dt.strftime("%Y-%m-%d")
    context.selected = context.daily_selected.get(today, [])
    if context.selected:
        context.subscribe_bar(context.selected, period="1m")
    context.entry_price = {}

def handle_data(context: bigtrader.IContext, data):
    now      = data.current_dt
    selected = context.selected
    if not is_trading_time(now):
        return

    if now.time() >= CLOSE_TIME:
        for ins, pos in context.get_account_positions().items():
            if pos.current_qty > 0:
                context.order_target_percent(ins, 0)
                context.entry_price.pop(ins, None)
        return

    positions   = context.get_account_positions()
    holding_set = {ins for ins, pos in positions.items() if pos.current_qty > 0}

    for ins in list(holding_set):
        if ins not in selected:
            context.order_target_percent(ins, 0)
            holding_set.discard(ins)
            context.entry_price.pop(ins, None)

    for instrument in selected:
        hist = data.history(instrument, "close", RSI_PERIOD + 20, "1m")
        if hist is None or len(hist) < RSI_PERIOD + 1:
            continue
        prices  = list(hist)
        rsi     = calc_rsi(prices, RSI_PERIOD)
        if rsi is None:
            continue
        current_price = prices[-1]
        holding       = instrument in holding_set

        if holding and instrument in context.entry_price:
            entry   = context.entry_price[instrument]
            pnl_pct = (current_price - entry) / entry
            if pnl_pct <= STOP_LOSS:
                context.order_target_percent(instrument, 0)
                holding_set.discard(instrument)
                context.entry_price.pop(instrument, None)
                continue
            if pnl_pct >= TAKE_PROFIT:
                context.order_target_percent(instrument, 0)
                holding_set.discard(instrument)
                context.entry_price.pop(instrument, None)
                continue

        if not holding and rsi < RSI_OVERSOLD:
            context.order_target_percent(instrument, 1.0 / CB_NUM)
            holding_set.add(instrument)
            context.entry_price[instrument] = current_price
        elif holding and rsi > RSI_OVERBOUGHT:
            context.order_target_percent(instrument, 0)
            holding_set.discard(instrument)
            context.entry_price.pop(instrument, None)

performance = bigtrader.run(
    market=bigtrader.Market.CN_CBOND,
    frequency=bigtrader.Frequency.MINUTE,
    start_date="2026-04-25",
    end_date="2026-05-09",
    capital_base=500_000,
    initialize=initialize,
    before_trading_start=before_trading_start,
    handle_data=handle_data,
    order_price_field_buy="close",
    order_price_field_sell="close",
    benchmark="000300.SH",
)

\

{link}