🧪 Skills

myquant掘进量化

GoldMiner Quantitative Python SDK — Event-driven quantitative platform supporting backtesting and live trading for A-shares, futures, options, ETFs, converti...

v1.0.0
❤️ 0
⬇️ 22
👁 2
Share

Description


name: myquant description: GoldMiner Quantitative Python SDK — Event-driven quantitative platform supporting backtesting and live trading for A-shares, futures, options, ETFs, convertible bonds, and margin trading. version: 1.1.0 homepage: https://www.myquant.cn metadata: {"clawdbot":{"emoji":"⛏️","requires":{"bins":["python3"]}}}

GoldMiner Quantitative (MyQuant / GoldMiner)

GoldMiner Quantitative is a professional quantitative trading platform providing event-driven strategy development, backtesting, paper trading, and live trading. It supports A-shares, futures, options, ETFs, convertible bonds, and margin trading, all operated through a unified Python SDK (gm.api).

Registration at https://www.myquant.cn is required to obtain a Token for authentication. Live trading requires connecting a brokerage account through the GoldMiner terminal.

Installation

pip install gm

Also available for download from the official website: https://www.myquant.cn/docs/downloads/698

Architecture Overview

Your Python script (strategy.py)
    ↓ gm SDK (from gm.api import *)
    ├── Backtest mode  → GoldMiner backtest engine (cloud/local)
    ├── Paper trading   → GoldMiner paper trading server
    └── Live trading    → GoldMiner terminal → Broker gateway

Strategy Program Structure

All strategies follow an event-driven architecture with lifecycle functions:

from gm.api import *

def init(context):
    """Initialization function — called once when the strategy starts, used to set up subscriptions and parameters"""
    # Subscribe to 30-second bars for two stocks, keeping the most recent 5 historical bars
    subscribe(symbols='SHSE.600000,SZSE.000001', frequency='30s', count=5)
    context.my_param = 0.8  # Custom context attribute

def on_bar(context, bars):
    """Bar event — triggered at each bar close, write main trading logic here"""
    bar = bars[0]
    if bar['close'] > bar['open']:
        # Close price above open price, place a limit buy order for 1000 shares
        order_volume(symbol=bar['symbol'], volume=1000,
                     side=OrderSide_Buy, order_type=OrderType_Limit,
                     position_effect=PositionEffect_Open, price=bar['close'])

def on_tick(context, tick):
    """Tick event — triggered on each tick push (real-time mode)"""
    print(tick['symbol'], tick['price'])

def on_order_status(context, order):
    """Order status event — triggered when order status changes"""
    print(f"Order {order['cl_ord_id']}: status={order['status']}")

def on_execution_report(context, execrpt):
    """Execution report event — triggered on trade execution"""
    print(f"Executed: {execrpt['symbol']} {execrpt['filled_volume']}@{execrpt['filled_vwap']}")

if __name__ == '__main__':
    run(strategy_id='your_strategy_id',
        filename='main.py',
        mode=MODE_BACKTEST,                          # Run mode: backtest
        token='your_token',
        backtest_start_time='2024-01-01 09:30:00',   # Backtest start time
        backtest_end_time='2024-06-30 15:00:00',     # Backtest end time
        backtest_initial_cash=1000000,                # Initial capital 1,000,000
        backtest_commission_ratio=0.0003,             # Commission rate 0.03%
        backtest_slippage_ratio=0.001,                # Slippage 0.1%
        backtest_adjust=ADJUST_PREV)                  # Forward-adjusted

Basic Functions

init — Strategy Initialization

def init(context):
    # Subscribe to daily bars, keeping the most recent 20
    subscribe(symbols='SHSE.600000', frequency='1d', count=20)
    context.ratio = 0.8  # Custom parameter
  • Called once when the strategy starts
  • Used to set up subscriptions, define parameters, and initialize state
  • Placing orders in init is not allowed in backtest mode (allowed in paper/live mode)

schedule — Scheduled Tasks

def init(context):
    # Execute algo_1 function at 09:40:00 on each trading day
    schedule(schedule_func=algo_1, date_rule='1d', time_rule='09:40:00')
    # Execute algo_2 function at 14:30:00 on the 1st trading day of each month
    schedule(schedule_func=algo_2, date_rule='1m', time_rule='14:30:00')

def algo_1(context):
    # Stock selection / rebalancing logic
    pass

def algo_2(context):
    # Market order to buy 200 shares
    order_volume(symbol='SHSE.600000', volume=200,
                 side=OrderSide_Buy, order_type=OrderType_Market,
                 position_effect=PositionEffect_Open)
  • date_rule: '1d' (daily), '1w' (weekly, backtest only), '1m' (monthly, backtest only)
  • time_rule: 'HH:MM:SS' format, must be zero-padded (e.g., '09:40:00', not '9:40:0')

run — Run Strategy

run(strategy_id='strategy_1',
    filename='main.py',
    mode=MODE_BACKTEST,       # MODE_BACKTEST=2 (backtest), MODE_LIVE=1 (live/paper)
    token='your_token',
    backtest_start_time='2024-01-01 09:30:00',
    backtest_end_time='2024-06-30 15:00:00',
    backtest_initial_cash=1000000,          # Initial capital
    backtest_transaction_ratio=1,           # Transaction fill ratio
    backtest_commission_ratio=0.0003,       # Commission rate
    backtest_slippage_ratio=0.001,          # Slippage ratio
    backtest_adjust=ADJUST_PREV)  # ADJUST_NONE=0 (unadjusted), ADJUST_PREV=1 (forward-adjusted), ADJUST_POST=2 (backward-adjusted)

stop — Stop Strategy

stop()  # Gracefully stop and exit the strategy

Data Subscription

subscribe — Subscribe to Market Data

subscribe(symbols='SHSE.600000,SZSE.000001',
          frequency='60s',    # Frequency: tick, 1s~300s, 60s, 300s, 900s, 1800s, 3600s, 1d
   
          count=5,            # Number of historical bars to keep in context.data
          wait_group=True,    # Wait for all symbols in the group before triggering callback
          wait_group_timeout='5s')  # Wait timeout duration

unsubscribe — Unsubscribe

unsubscribe(symbols='SHSE.600000', frequency='60s')

Data Events

Event Trigger Condition Parameters
on_tick(context, tick) Each tick push Tick dict containing price, bid/ask, volume
on_bar(context, bars) Each bar close List of bar dicts
on_l2transaction(context, l2transaction) Level 2 tick-by-tick transactions L2Transaction dict
on_l2order(context, l2order) Level 2 tick-by-tick orders L2Order dict
on_l2order_queue(context, l2order_queue) Level 2 order queue L2OrderQueue dict

Data Query Functions

current — Real-time Snapshot

data = current(symbols='SZSE.000001')
# Returns: [{'symbol': 'SZSE.000001', 'price': 16.56, 'open': 16.20, 'high': 16.92, 'low': 16.15,
#            'quotes': [{'bid_p': 16.55, 'bid_v': 209200, 'ask_p': 16.56, 'ask_v': 296455}, ...],
#            'cum_volume': 160006232, 'cum_amount': 2654379585.66, 'created_at': ...}]
  • Backtest mode: returns only symbol, price, created_at
  • Live mode: returns full tick fields including 5-level bid/ask quotes

history — Historical Bars/Ticks

df = history(symbol='SHSE.000300', frequency='1d',
             start_time='2024-01-01', end_time='2024-06-30',
             fields='open,close,high,low,volume,eob',
             skip_suspended=True,          # Skip suspended trading days
             fill_missing=None,            # Missing value fill: None, 'NaN', 'Last'
             adjust=ADJUST_PREV,           # 0=unadjusted, 1=forward-adjusted, 2=backward-adjusted
             df=True)                      # True returns DataFrame, False returns list[dict]

history_n — Most Recent N Bars

df = history_n(symbol='SHSE.600000', frequency='1d', count=20,
               end_time='2024-06-30', fields='close,volume',
               adjust=ADJUST_PREV, df=True)

context.data — Subscription Data Cache

# Access the subscribed data buffer (set by the count parameter in subscribe)
bars = context.data(symbol='SHSE.600000', frequency='60s', count=10)

Level 2 Historical Data

get_history_l2ticks(symbol, start_time, end_time, fields, skip_suspended, fill_missing, adjust, df)
get_history_l2bars(symbol, frequency, start_time, end_time, fields, skip_suspended, fill_missing, adjust, df)
get_history_l2transactions(symbol, start_time, end_time, fields, df)
get_history_l2orders(symbol, start_time, end_time, fields, df)
get_history_l2orders_queue(symbol, start_time, end_time, fields, df)

Fundamental Data

# Retrieve financial indicator data with filtering and sorting support
df = get_fundamentals(
    table='trading_derivative_indicator',    # Data table name
    symbols='SHSE.600000,SZSE.000001',       # Stock symbols
    start_date='2024-01-01',
    end_date='2024-06-30',
    fields='TCLOSE,NEGOTIABLEMV,TOTMKTCAP,TURNRATE,PETTM',  # Field list
    limit=1000,                              # Result count limit
    df=True
)

# Retrieve the most recent N records
df = get_fundamentals_n(
    table='trading_derivative_indicator',
    symbols='SHSE.600000',
    end_date='2024-06-30',
    count=10,
    fields='TCLOSE,PETTM',
    df=True
)

Available fundamental data tables: see Financial Data Documentation

Instrument Information

# Get all A-share instruments on the Shenzhen exchange
instruments = get_instruments(exchanges='SZSE', sec_types=1, df=True)

# Get information for specific instruments
info = get_instruments(symbols='SHSE.600000,SZSE.000001', df=True)
# Fields: symbol, sec_name, exchange, listed_date, delisted_date, sec_type,
#         pre_close, upper_limit, lower_limit, adj_factor, is_st, is_suspended, ...

# Get historical instrument information
hist_info = get_history_instruments(symbols='SHSE.600000', start_date='2024-01-01', end_date='2024-06-30')

# Get basic instrument information (static)
basic_info = get_instrumentinfos(symbols='SHSE.600000', df=True)

Index Constituents

# Get current constituents
stocks = get_constituents(index='SHSE.000300')  # CSI 300

# Get historical constituents
hist = get_history_constituents(index='SHSE.000300', start_date='2024-01-01', end_date='2024-06-30')

Industry Stocks

# Get the stock list for a specified industry code
stocks = get_industry(code='J66')  # Returns stocks in industry J66 (Banking)

Dividend Data

divs = get_dividend(symbol='SHSE.600000', start_date='2020-01-01', end_date='2024-12-31', df=True)

Trading Calendar

dates = get_trading_dates(exchange='SZSE', start_date='2024-01-01', end_date='2024-06-30')
prev = get_previous_trading_date(exchange='SHSE', date='2024-03-15')   # Previous trading date
next_d = get_next_trading_date(exchange='SHSE', date='2024-03-15')     # Next trading date

Continuous Contracts (Futures)

contracts = get_continuous_contracts(csymbol='CFFEX.IF', start_date='2024-01-01', end_date='2024-06-30')

IPO Subscription Functions

quota = ipo_get_quota()                  # Query IPO subscription quota
instruments = ipo_get_instruments()      # Query today's IPO list
match_nums = ipo_get_match_number()      # Query allotment numbers
lot_info = ipo_get_lot_info()            # Query winning lot information

Trading Functions

Order by Volume

order = order_volume(
    symbol='SHSE.600000',
    volume=10000,                               # Order quantity
    side=OrderSide_Buy,                         # Side: OrderSide_Buy=1 (buy), OrderSide_Sell=2 (sell)
    order_type=OrderType_Limit,                 # Order type: OrderType_Limit=1 (limit), OrderType_Market=2 (market)
    position_effect=PositionEffect_Open,        # Position effect: Open=1 (open), Close=2 (close), CloseToday=3 (close today), CloseYesterday=4 (close yesterday)
    price=11.0,                                 # Order price
    account=''                                  # Optional: specify account for multi-account setups
)

Order by Value

order = order_value(
    symbol='SHSE.600000',
    value=100000,                   # Target order value (CNY)
    side=OrderSide_Buy,
    order_type=OrderType_Limit,
    position_effect=PositionEffect_Open,
    price=11.0
)
# Actual quantity = value / price, truncated to valid lot size

Order by Percentage

order = order_percent(
    symbol='SHSE.600000',
    percent=0.1,                    # 10% of total assets
    side=OrderSide_Buy,
    order_type=OrderType_Limit,
    position_effect=PositionEffect_Open,
    price=11.0
)

Target Position Functions

# Adjust to target volume
order_target_volume(symbol='SHSE.600000', volume=10000,
                    position_side=PositionSide_Long,
                    order_type=OrderType_Limit, price=13.0)

# Adjust to target value
order_target_value(symbol='SHSE.600000', value=130000,
                   position_side=PositionSide_Long,
                   order_type=OrderType_Limit, price=13.0)

# Adjust to target percentage (percentage of total assets)
order_target_percent(symbol='SHSE.600000', percent=0.1,
                     position_side=PositionSide_Long,
                     order_type=OrderType_Limit, price=13.0)

Batch Orders

orders = [
    {'symbol': 'SHSE.600000', 'volume': 1000, 'side': OrderSide_Buy,
     'order_type': OrderType_Limit, 'position_effect': PositionEffect_Open, 'price': 11.0},
    {'symbol': 'SZSE.000001', 'volume': 2000, 'side': OrderSide_Buy,
     'order_type': OrderType_Limit, 'position_effect': PositionEffect_Open, 'price': 15.0},
]
results = order_batch(orders)  # Submit orders in batch

Cancel Orders & Close All Positions

# Cancel a specific order
order_cancel(wait_cancel_orders=[
    {'cl_ord_id': order1['cl_ord_id'], 'account_id': order1['account_id']}
])
order_cancel_all()        # Cancel all unfilled orders
order_close_all()         # Close all positions (limit orders)

Query Orders

unfinished = get_unfinished_orders()    # Query unfinished orders (pending/partially filled)
all_orders = get_orders()               # Query all orders today
executions = get_execution_reports()    # Query all execution reports today

Special Trading

ipo_buy(symbol)                                       # IPO subscription
fund_etf_buy(symbol, volume, price)                   # ETF purchase
fund_etf_redemption(symbol, volume, price)             # ETF redemption
fund_subscribing(symbol, volume, price)                # Fund initial subscription
fund_buy(symbol, volume, price)                        # Fund purchase
fund_redemption(symbol, volume, price)                 # Fund redemption
bond_reverse_repurchase_agreement(symbol, volume, price) # Treasury reverse repo

Account & Position Queries

# Query cash information
cash = context.account().cash
# Attributes: nav (net asset value), fpnl (floating P&L), pnl (realized P&L), available (available funds),
#             order_frozen (order frozen), balance (balance), market_value (position market value), cum_trade (cumulative trade amount), ...

# Query all positions
positions = context.account().positions()
# Each position contains: symbol, side, volume, available_now, cost, vwap, market_value, fpnl, ...

# Query a specific position
pos = context.account().position(symbol='SHSE.600000', side=PositionSide_Long)

Margin Trading

# Margin buy
credit_buying_on_margin(symbol, volume, price, side=OrderSide_Buy, order_type=OrderType_Limit)

# Short sell
credit_short_selling(symbol, volume, price, side=OrderSide_Sell, order_type=OrderType_Limit)

# Repayment
credit_repay_cash_directly(amount)              # Direct cash repayment
credit_repay_share_directly(symbol, volume)      # Direct share repayment
credit_repay_share_by_buying_share(symbol, volume, price)  # Buy shares to repay
credit_repay_cash_by_selling_share(symbol, volume, price)  # Sell shares to repay cash

# Collateral operations
credit_buying_on_collateral(symbol, volume, price)   # Buy collateral
credit_selling_on_collateral(symbol, volume, price)   # Sell collateral
credit_collateral_in(symbol, volume)                  # Transfer collateral in
credit_collateral_out(symbol, volume)                 # Transfer collateral out

# Queries
credit_get_collateral_instruments()          # Query collateral instruments
credit_get_borrowable_instruments()          # Query borrowable instruments
credit_get_borrowable_instruments_positions() # Query broker's lending positions
credit_get_contracts()                       # Query margin trading contracts
credit_get_cash()                            # Query margin trading funds

Algorithmic Trading

# Submit an algorithmic order (e.g., TWAP, VWAP)
algo_order(symbol, volume, side, order_type, position_effect, price, algo_name, algo_param)

# Cancel an algorithmic order
algo_order_cancel(cl_ord_id, account_id)

# Query algorithmic orders
get_algo_orders()

# Pause/resume an algorithmic order
algo_order_pause(cl_ord_id, account_id, status)  # AlgoOrderStatus_Pause / AlgoOrderStatus_Resume

# Query child orders of an algorithmic order
get_algo_child_orders(cl_ord_id, account_id)

# Algorithmic order status event
def on_algo_order_status(context, order):
    print(f"Algo order {order['cl_ord_id']}: status={order['status']}")

Trading Events

Event Trigger Condition Key Fields
on_order_status(context, order) Order status change cl_ord_id, symbol, status, filled_volume, filled_vwap
on_execution_report(context, execrpt) Execution report cl_ord_id, symbol, filled_volume, filled_vwap, price
on_account_status(context, account) Account status change account_id, status

Dynamic Parameters

def init(context):
    # Add a parameter that can be dynamically modified in the terminal UI
    add_parameter(key='threshold', value=0.05, min=0.01, max=0.2, name='买入阈值', intro='触发买入的比例')

def on_parameter(context, parameter):
    """Triggered when a parameter is modified in the terminal UI"""
    print(f"Parameter changed: {parameter['key']} = {parameter['value']}")

# Modify parameter at runtime
set_parameter(key='threshold', value=0.08)

# Read all parameters
params = context.parameters

Connection Events

Event Trigger Condition
on_backtest_finished(context, indicator) Backtest completed, receives performance statistics
on_error(context, code, info) Error occurred
on_market_data_connected(context) Market data connection established
on_trade_data_connected(context) Trade connection established
on_market_data_disconnected(context) Market data connection lost
on_trade_data_disconnected(context) Trade connection lost

Symbol Format

Format: ExchangeCode.SecurityCode

Exchange Code Description Example
SHSE Shanghai Stock Exchange SHSE.600000 (SPD Bank)
SZSE Shenzhen Stock Exchange SZSE.000001 (Ping An Bank)
CFFEX China Financial Futures Exchange CFFEX.IF2401 (CSI 300 Futures)
SHFE Shanghai Futures Exchange SHFE.ag2407 (Silver Futures)
DCE Dalian Commodity Exchange DCE.m2405 (Soybean Meal Futures)
CZCE Zhengzhou Commodity Exchange CZCE.CF405 (Cotton Futures)
INE Shanghai International Energy Exchange INE.sc2407 (Crude Oil Futures)
GFEX Guangzhou Futures Exchange GFEX.si2407 (Industrial Silicon Futures)

Enum Constants Reference

OrderSide — Order Side

Constant Value Description
OrderSide_Buy 1 Buy
OrderSide_Sell 2 Sell

OrderType — Order Type

Constant Value Description
OrderType_Limit 1 Limit order
OrderType_Market 2 Market order

PositionEffect — Position Effect

Constant Value Description
PositionEffect_Open 1 Open position
PositionEffect_Close 2 Close position
PositionEffect_CloseToday 3 Close today's position
PositionEffect_CloseYesterday 4 Close yesterday's position

PositionSide — Position Side

Constant Value Description
PositionSide_Long 1 Long
PositionSide_Short 2 Short

OrderStatus — Order Status

Constant Value Description
OrderStatus_New 1 Submitted
OrderStatus_PartiallyFilled 2 Partially filled
OrderStatus_Filled 3 Filled
OrderStatus_Canceled 5 Canceled
OrderStatus_PendingCancel 6 Pending cancel
OrderStatus_Rejected 8 Rejected
OrderStatus_PendingNew 10 Pending submission
OrderStatus_Expired 12 Expired

ADJUST — Price Adjustment

Constant Value Description
ADJUST_NONE 0 Unadjusted
ADJUST_PREV 1 Forward-adjusted
ADJUST_POST 2 Backward-adjusted

Full Example — Dual Moving Average Backtest Strategy

from gm.api import *

def init(context):
    # Subscribe to daily bars, keeping the most recent 21
    subscribe(symbols='SHSE.600000', frequency='1d', count=21)
    context.symbol = 'SHSE.600000'

def on_bar(context, bars):
    # Get the most recent 20 daily bars
    hist = context.data(symbol=context.symbol, frequency='1d', count=20)
    closes = [bar['close'] for bar in hist]

    if len(closes) < 20:
        return  # Insufficient data, skip

    # Calculate 5-day and 20-day moving averages
    ma5 = sum(closes[-5:]) / 5
    ma20 = sum(closes) / 20
    price = bars[0]['close']

    # Query current position
    pos = context.account().position(symbol=context.symbol, side=PositionSide_Long)

    # Golden cross signal: 5-day MA > 20-day MA and no position, buy
    if ma5 > ma20 and (pos is None or pos['volume'] == 0):
        order_percent(symbol=context.symbol, percent=0.9,
                      side=OrderSide_Buy, order_type=OrderType_Limit,
                      position_effect=PositionEffect_Open, price=price)
    # Death cross signal: 5-day MA < 20-day MA and has position, close all
    elif ma5 < ma20 and pos is not None and pos['volume'] > 0:
        order_close_all()

def on_order_status(context, order):
    if order['status'] == OrderStatus_Filled:
        print(f"Executed: {order['symbol']} volume={order['filled_volume']} avg_price={order['filled_vwap']}")

def on_backtest_finished(context, indicator):
    print(f"Backtest completed: return={indicator['pnl_ratio']:.2%}, "
          f"Sharpe ratio={indicator['sharpe_ratio']:.2f}, "
          f"max drawdown={indicator['max_drawdown']:.2%}")

if __name__ == '__main__':
    run(strategy_id='ma_cross',
        filename='main.py',
        mode=MODE_BACKTEST,
        token='your_token',
        backtest_start_time='2023-01-01 09:30:00',
        backtest_end_time='2024-06-30 15:00:00',
        backtest_initial_cash=1000000,
        backtest_commission_ratio=0.0003,
        backtest_slippage_ratio=0.001,
        backtest_adjust=ADJUST_PREV)

Full Example — Scheduled Rebalancing Strategy

from gm.api import *

def init(context):
    # Rebalance at 09:35 on each trading day
    schedule(schedule_func=rebalance, date_rule='1d', time_rule='09:35:00')
    context.target_stocks = ['SHSE.600000', 'SZSE.000001', 'SHSE.601318']

def rebalance(context):
    # Equal-weight allocation, each stock gets 1/N of total assets
    target_pct = 1.0 / len(context.target_stocks)
    for sym in context.target_stocks:
        order_target_percent(symbol=sym, percent=target_pct * 0.95,
                             position_side=PositionSide_Long,
                             order_type=OrderType_Market)

if __name__ == '__main__':
    run(strategy_id='equal_weight',
        filename='main.py',
        mode=MODE_BACKTEST,
        token='your_token',
        backtest_start_time='2024-01-01 09:30:00',
        backtest_end_time='2024-06-30 15:00:00',
        backtest_initial_cash=1000000,
        backtest_adjust=ADJUST_PREV)

Full Example — Pure Data Research (No Trading)

from gm.api import *

# Set Token (can query data without starting a strategy)
set_token('your_token')

# Get historical bar data
df = history(symbol='SHSE.000300', frequency='1d',
             start_time='2024-01-01', end_time='2024-06-30',
             fields='open,close,high,low,volume',
             adjust=ADJUST_PREV, df=True)
print(df.head())

# Get financial indicator data
fund = get_fundamentals(table='trading_derivative_indicator',
                        symbols='SHSE.600000',
                        start_date='2024-01-01',
                        end_date='2024-06-30',
                        fields='TCLOSE,PETTM,TURNRATE',
                        df=True)
print(fund)

# Get CSI 300 constituents
constituents = get_constituents(index='SHSE.000300')
print(f'CSI 300 has {len(constituents)} constituent stocks')

# Get banking industry stocks
bank_stocks = get_industry(code='J66')
print(f'Banking industry has {len(bank_stocks)} stocks')

Supported Data Frequencies

Frequency Description Mode
tick Tick-level data Real-time & Backtest
1s ~ 300s N-second bars Real-time & Backtest
60s 1-minute bars Real-time & Backtest
300s 5-minute bars Real-time & Backtest
900s 15-minute bars Real-time & Backtest
1800s 30-minute bars Real-time & Backtest
3600s 60-minute bars Real-time & Backtest
1d Daily bars Real-time & Backtest

Run Modes

Mode Constant Description
Backtest MODE_BACKTEST (2) Historical data simulation with configurable parameters
Live/Paper MODE_LIVE (1) Real-time paper trading or live trading via GoldMiner terminal

Usage Tips

  • Token required: Register at https://www.myquant.cn to obtain one. Use set_token() or pass it in run().
  • Event-driven: All strategy logic is triggered by events (on_bar, on_tick, on_order_status), no polling needed.
  • Subscribe before trading: Use subscribe() in init() to ensure data is ready before on_bar/on_tick fires.
  • No orders in init during backtest: init() only allows subscriptions and parameter setup in backtest mode.
  • Use schedule for periodic rebalancing: Use schedule() instead of on_bar for periodic rebalancing strategies.
  • df=True: Most data query functions support df=True to return a pandas DataFrame for easy analysis.
  • Multi-account: Pass the account='' parameter in order functions to specify the account.
  • Futures close positions: SHFE requires PositionEffect_CloseToday / PositionEffect_CloseYesterday to distinguish closing today's vs yesterday's positions.
  • Backtest defaults: Initial capital=1,000,000, commission=0, slippage=0. Set reasonable values.
  • Documentation: https://www.myquant.cn/docs/python/41

Advanced Examples

Multi-Factor Stock Selection Strategy

from gm.api import *
import numpy as np

def init(context):
    # Execute stock selection and rebalancing at 10:00 on the 1st trading day of each month
    schedule(schedule_func=multi_factor_rebalance, date_rule='1m', time_rule='10:00:00')
    context.hold_num = 10          # Number of stocks to hold
    context.target_index = 'SHSE.000300'  # Selection universe: CSI 300 constituents

def multi_factor_rebalance(context):
    """Multi-factor stock selection + equal-weight rebalancing"""
    # Get CSI 300 constituents
    constituents = get_constituents(index=context.target_index)
    symbols = [c['symbol'] for c in constituents]

    # Get financial indicator data (PE, PB, ROE, turnover rate)
    fund_data = get_fundamentals(
        table='trading_derivative_indicator',
        symbols=','.join(symbols),
        end_date=context.now.strftime('%Y-%m-%d'),
        count=1,
        fields='PETTM,PB,TURNRATE',
        df=True
    )

    if fund_data is None or len(fund_data) == 0:
        return

    # Data cleaning: remove missing values and outliers
    fund_data = fund_data.dropna(subset=['PETTM', 'PB'])
    fund_data = fund_data[(fund_data['PETTM'] > 0) & (fund_data['PETTM'] < 100)]
    fund_data = fund_data[(fund_data['PB'] > 0) & (fund_data['PB'] < 20)]

    # Factor scoring: lower PE is better, lower PB is better (higher rank = higher score)
    fund_data['PE_rank'] = fund_data['PETTM'].rank(ascending=True)
    fund_data['PB_rank'] = fund_data['PB'].rank(ascending=True)
    # Composite score = PE rank * 0.5 + PB rank * 0.5
    fund_data['score'] = fund_data['PE_rank'] * 0.5 + fund_data['PB_rank'] * 0.5
    # Sort by composite score, select top N
    selected = fund_data.nsmallest(context.hold_num, 'score')
    target_symbols = selected['symbol'].tolist()

    print(f"Selected {len(target_symbols)} stocks this period: {target_symbols}")

    # First close positions not in the target list
    positions = context.account().positions()
    for pos in positions:
        if pos['symbol'] not in target_symbols and pos['volume'] > 0:
            order_target_volume(symbol=pos['symbol'], volume=0,
                                position_side=PositionSide_Long,
                                order_type=OrderType_Market)
            print(f"  Closing: {pos['symbol']}")

    # Equal-weight buy target stocks
    target_pct = 0.95 / len(target_symbols)  # Keep 5% cash
    for sym in target_symbols:
        order_target_percent(symbol=sym, percent=target_pct,
                             position_side=PositionSide_Long,
                             order_type=OrderType_Market)
        print(f"  Rebalancing: {sym} -> {target_pct*100:.1f}%")

if __name__ == '__main__':
    run(strategy_id='multi_factor',
        filename='main.py',
        mode=MODE_BACKTEST,
        token='your_token',
        backtest_start_time='2023-01-01 09:30:00',
        backtest_end_time='2024-06-30 15:00:00',
        backtest_initial_cash=1000000,
        backtest_commission_ratio=0.0003,
        backtest_slippage_ratio=0.001,
        backtest_adjust=ADJUST_PREV)

Pairs Trading Strategy (Statistical Arbitrage)

from gm.api import *
import numpy as np

def init(context):
    # Pair: China Merchants Bank vs Industrial Bank (same industry, high correlation)
    context.stock_a = 'SHSE.600036'
    context.stock_b = 'SHSE.601166'
    context.lookback = 60          # Lookback window (trading days)
    context.entry_z = 2.0          # Entry Z-score threshold
    context.exit_z = 0.5           # Exit Z-score threshold
    context.position_pct = 0.4     # Single-side position ratio

    # Subscribe to daily bars for both stocks
    subscribe(symbols=f'{context.stock_a},{context.stock_b}', frequency='1d', count=context.lookback + 1)

def on_bar(context, bars):
    # Get historical close prices
    hist_a = context.data(symbol=context.stock_a, frequency='1d', count=context.lookback)
    hist_b = context.data(symbol=context.stock_b, frequency='1d', count=context.lookback)

    if len(hist_a) < context.lookback or len(hist_b) < context.lookback:
        return

    closes_a = np.array([bar['close'] for bar in hist_a])
    closes_b = np.array([bar['close'] for bar in hist_b])

    # Calculate price ratio (spread)
    ratio = closes_a / closes_b
    ratio_mean = np.mean(ratio)
    ratio_std = np.std(ratio)

    if ratio_std == 0:
        return

    # Calculate current Z-score
    current_ratio = bars[0]['close'] / bars[1]['close'] if len(bars) >= 2 else ratio[-1]
    z_score = (current_ratio - ratio_mean) / ratio_std

    # Query current positions
    pos_a = context.account().position(symbol=context.stock_a, side=PositionSide_Long)
    pos_b = context.account().position(symbol=context.stock_b, side=PositionSide_Long)
    has_pos_a = pos_a is not None and pos_a['volume'] > 0
    has_pos_b = pos_b is not None and pos_b['volume'] > 0

    print(f"Z-score={z_score:.2f}, ratio={current_ratio:.4f}, mean={ratio_mean:.4f}")

    if z_score > context.entry_z and not has_pos_b:
        # Z-score too high: A overvalued relative to B → sell A, buy B
        if has_pos_a:
            order_close_all()  # Close reverse positions first
        order_percent(symbol=context.stock_b, percent=context.position_pct,
                      side=OrderSide_Buy, order_type=OrderType_Market,
                      position_effect=PositionEffect_Open)
        print(f"  Open position: buy {context.stock_b}")

    elif z_score < -context.entry_z and not has_pos_a:
        # Z-score too low: A undervalued relative to B → buy A, sell B
        if has_pos_b:
            order_close_all()
        order_percent(symbol=context.stock_a, percent=context.position_pct,
                      side=OrderSide_Buy, order_type=OrderType_Market,
                      position_effect=PositionEffect_Open)
        print(f"  Open position: buy {context.stock_a}")

    elif abs(z_score) < context.exit_z and (has_pos_a or has_pos_b):
        # Z-score reverts to mean → close positions
        order_close_all()
        print(f"  Close positions: Z-score reverted")

if __name__ == '__main__':
    run(strategy_id='pair_trading',
        filename='main.py',
        mode=MODE_BACKTEST,
        token='your_token',
        backtest_start_time='2023-01-01 09:30:00',
        backtest_end_time='2024-06-30 15:00:00',
        backtest_initial_cash=1000000,
        backtest_commission_ratio=0.0003,
        backtest_adjust=ADJUST_PREV)

Futures CTA Trend Following Strategy (Turtle Trading)

from gm.api import *
import numpy as np

def init(context):
    context.symbol = 'CFFEX.IF2401'   # CSI 300 index futures
    context.entry_period = 20          # Entry channel period (20-day high/low)
    context.exit_period = 10           # Exit channel period (10-day high/low)
    context.atr_period = 20            # ATR calculation period
    context.risk_ratio = 0.01          # Per-trade risk ratio (1% of total assets)

    subscribe(symbols=context.symbol, frequency='1d', count=context.entry_period + 1)

def on_bar(context, bars):
    hist = context.data(symbol=context.symbol, frequency='1d', count=context.entry_period)
    if len(hist) < context.entry_period:
        return

    highs = np.array([bar['high'] for bar in hist])
    lows = np.array([bar['low'] for bar in hist])
    closes = np.array([bar['close'] for bar in hist])

    # Calculate Donchian Channel
    entry_high = np.max(highs[-context.entry_period:])    # 20-day high (breakout long)
    entry_low = np.min(lows[-context.entry_period:])      # 20-day low (breakout short)
    exit_high = np.max(highs[-context.exit_period:])      # 10-day high (short stop-loss)
    exit_low = np.min(lows[-context.exit_period:])        # 10-day low (long stop-loss)

    # Calculate ATR (Average True Range)
    tr_list = []
    for i in range(1, len(highs)):
        tr = max(highs[i] - lows[i],
                 abs(highs[i] - closes[i-1]),
                 abs(lows[i] - closes[i-1]))
        tr_list.append(tr)
    atr = np.mean(tr_list[-context.atr_period:]) if len(tr_list) >= context.atr_period else 0

    current_price = bars[0]['close']
    pos_long = context.account().position(symbol=context.symbol, side=PositionSide_Long)
    pos_short = context.account().position(symbol=context.symbol, side=PositionSide_Short)
    has_long = pos_long is not None and pos_long['volume'] > 0
    has_short = pos_short is not None and pos_short['volume'] > 0

    # Calculate position size (ATR-based risk management)
    if atr > 0:
        nav = context.account().cash['nav']
        unit_size = int(nav * context.risk_ratio / (atr * 300))  # IF contract multiplier 300
        unit_size = max(unit_size, 1)
    else:
        unit_size = 1

    # Entry signals
    if current_price > entry_high and not has_long:
        if has_short:
            # Close short position first
            order_volume(symbol=context.symbol, volume=pos_short['volume'],
                         side=OrderSide_Buy, order_type=OrderType_Market,
                         position_effect=PositionEffect_Close)
        # Open long position
        order_volume(symbol=context.symbol, volume=unit_size,
                     side=OrderSide_Buy, order_type=OrderType_Market,
                     position_effect=PositionEffect_Open)
        print(f"Breakout long: price={current_price}, upper channel={entry_high}, lots={unit_size}")

    elif current_price < entry_low and not has_short:
        if has_long:
            order_volume(symbol=context.symbol, volume=pos_long['volume'],
                         side=OrderSide_Sell, order_type=OrderType_Market,
                         position_effect=PositionEffect_Close)
        # Open short position
        order_volume(symbol=context.symbol, volume=unit_size,
                     side=OrderSide_Sell, order_type=OrderType_Market,
                     position_effect=PositionEffect_Open)
        print(f"Breakout short: price={current_price}, lower channel={entry_low}, lots={unit_size}")

    # Exit signals
    elif has_long and current_price < exit_low:
        order_volume(symbol=context.symbol, volume=pos_long['volume'],
                     side=OrderSide_Sell, order_type=OrderType_Market,
                     position_effect=PositionEffect_Close)
        print(f"Long stop-loss: price={current_price}, exit lower channel={exit_low}")

    elif has_short and current_price > exit_high:
        order_volume(symbol=context.symbol, volume=pos_short['volume'],
                     side=OrderSide_Buy, order_type=OrderType_Market,
                     position_effect=PositionEffect_Close)
        print(f"Short stop-loss: price={current_price}, exit upper channel={exit_high}")

def on_backtest_finished(context, indicator):
    print(f"\nBacktest results:")
    print(f"  Return: {indicator['pnl_ratio']:.2%}")
    print(f"  Annualized return: {indicator['pnl_ratio_annual']:.2%}")
    print(f"  Sharpe ratio: {indicator['sharpe_ratio']:.2f}")
    print(f"  Max drawdown: {indicator['max_drawdown']:.2%}")
    print(f"  Win rate: {indicator['win_ratio']:.2%}")

if __name__ == '__main__':
    run(strategy_id='turtle_cta',
        filename='main.py',
        mode=MODE_BACKTEST,
        token='your_token',
        backtest_start_time='2023-01-01 09:30:00',
        backtest_end_time='2024-06-30 15:00:00',
        backtest_initial_cash=2000000,
        backtest_commission_ratio=0.000023,
        backtest_slippage_ratio=0.0005,
        backtest_adjust=ADJUST_PREV)

Risk Management Module — Position Control & Drawdown Monitoring

from gm.api import *

def init(context):
    subscribe(symbols='SHSE.600000,SZSE.000001,SHSE.601318', frequency='1d', count=21)
    context.max_drawdown_limit = 0.10   # Max drawdown limit 10%
    context.max_single_position = 0.30  # Max single stock position 30%
    context.max_total_position = 0.90   # Max total position 90%
    context.peak_nav = 0                # Historical peak NAV
    context.is_stopped = False          # Whether risk control has stopped trading

def on_bar(context, bars):
    # Update historical peak NAV
    nav = context.account().cash['nav']
    if nav > context.peak_nav:
        context.peak_nav = nav

    # Calculate current drawdown
    current_drawdown = (context.peak_nav - nav) / context.peak_nav if context.peak_nav > 0 else 0

    # Check if max drawdown limit is triggered
    if current_drawdown >= context.max_drawdown_limit:
        if not context.is_stopped:
            print(f"⚠️ Max drawdown limit triggered: drawdown={current_drawdown:.2%}, closing all positions!")
            order_close_all()
            context.is_stopped = True
        return

    context.is_stopped = False

    # Check if any single stock position exceeds the limit
    positions = context.account().positions()
    total_position_value = sum(p['market_value'] for p in positions)
    total_asset = context.account().cash['nav']

    for pos in positions:
        single_pct = pos['market_value'] / total_asset if total_asset > 0 else 0
        if single_pct > context.max_single_position:
            # Single stock position exceeds limit, reduce to the limit
            target_value = total_asset * context.max_single_position
            order_target_value(symbol=pos['symbol'], value=target_value,
                               position_side=PositionSide_Long,
                               order_type=OrderType_Market)
            print(f"  Reducing: {pos['symbol']} position {single_pct:.1%} -> {context.max_single_position:.1%}")

    # Output risk control status
    total_pct = total_position_value / total_asset if total_asset > 0 else 0
    print(f"NAV={nav:.2f}, drawdown={current_drawdown:.2%}, total position={total_pct:.1%}")

if __name__ == '__main__':
    run(strategy_id='risk_mgmt',
        filename='main.py',
        mode=MODE_BACKTEST,
        token='your_token',
        backtest_start_time='2023-01-01 09:30:00',
        backtest_end_time='2024-06-30 15:00:00',
        backtest_initial_cash=1000000,
        backtest_adjust=ADJUST_PREV)

Data Research — Industry Rotation Analysis

from gm.api import *
import pandas as pd

# Set Token (pure data research, no strategy needed)
set_token('your_token')

# Define industry ETF list (using ETFs instead of industry indices for easier data access)
industry_etfs = {
    'SHSE.510050': '上证50',
    'SHSE.510300': '沪深300',
    'SHSE.510500': '中证500',
    'SZSE.159915': '创业板',
    'SHSE.512010': '医药ETF',
    'SHSE.512880': '证券ETF',
    'SHSE.512800': '银行ETF',
    'SHSE.515030': '新能源车ETF',
    'SZSE.159995': '芯片ETF',
    'SHSE.512690': '白酒ETF',
}

# Get daily bar data for each industry ETF over the past year
results = []
for symbol, name in industry_etfs.items():
    df = history(symbol=symbol, frequency='1d',
                 start_time='2024-01-01', end_time='2024-12-31',
                 fields='close,volume,eob',
                 adjust=ADJUST_PREV, df=True)
    if df is not None and len(df) > 20:
        # Calculate returns over various periods
        ret_5d = (df['close'].iloc[-1] / df['close'].iloc[-6] - 1) * 100    # 5-day return
        ret_20d = (df['close'].iloc[-1] / df['close'].iloc[-21] - 1) * 100  # 20-day return
        ret_60d = (df['close'].iloc[-1] / df['close'].iloc[-61] - 1) * 100 if len(df) > 60 else None
        avg_volume = df['volume'].tail(20).mean()  # 20-day average volume

        results.append({
            '行业': name,
            '代码': symbol,
            '近5日收益(%)': round(ret_5d, 2),
            '近20日收益(%)': round(ret_20d, 2),
            '近60日收益(%)': round(ret_60d, 2) if ret_60d else None,
            '20日均量': int(avg_volume),
        })

# Sort by 20-day return
df_result = pd.DataFrame(results).sort_values('近20日收益(%)', ascending=False)
print("\nIndustry rotation analysis (sorted by 20-day return):")
print(df_result.to_string(index=False))

# Find industries with strongest momentum (ranked top 3 in both 5-day and 20-day returns)
top5d = set(df_result.nlargest(3, '近5日收益(%)')['行业'].tolist())
top20d = set(df_result.nlargest(3, '近20日收益(%)')['行业'].tolist())
momentum_leaders = top5d & top20d
if momentum_leaders:
    print(f"\nMomentum leaders (strong in both short-term and mid-term): {momentum_leaders}")

社区与支持

大佬量化 (Boss Quant) 维护 — 量化交易教学与策略研发团队。

微信客服: bossquant1 · Bilibili · 搜索 大佬量化 on 微信公众号 / Bilibili / 抖音

Reviews (0)

Sign in to write a review.

No reviews yet. Be the first to review!

Comments (0)

Sign in to join the discussion.

No comments yet. Be the first to share your thoughts!

Compatible Platforms

Pricing

Free

Related Configs