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 / 抖音
Comments (0)
No comments yet. Be the first to share your thoughts!