掘金量化(MyQuant / GoldMiner)
name: myquant
by coderwpf · published 2026-03-22
$ claw add gh:coderwpf/coderwpf-myquant---
name: myquant
description: 掘金量化Python SDK - 事件驱动量化平台,支持A股、期货、期权、ETF、可转债回测与实盘交易。
version: 1.2.0
homepage: https://www.myquant.cn
metadata: {"clawdbot":{"emoji":"⛏️","requires":{"bins":["python3"]}}}
---
# 掘金量化(MyQuant / GoldMiner)
[掘金量化](https://www.myquant.cn) 是专业量化交易平台,提供事件驱动策略开发、回测、模拟交易和实盘交易。支持A股、期货、期权、ETF、可转债和融资融券,通过统一的Python SDK(`gm.api`)操作。
> 需要在 https://www.myquant.cn 注册获取Token进行认证。实盘交易需要通过掘金终端连接券商账户。
安装
pip install gm也可从官网下载:https://www.myquant.cn/docs/downloads/698
架构概述
你的Python脚本(strategy.py)
↓ gm SDK(from gm.api import *)
├── 回测模式 → 掘金回测引擎(云端/本地)
├── 模拟交易 → 掘金模拟交易服务器
└── 实盘交易 → 掘金终端 → 券商网关策略程序结构
所有策略遵循**事件驱动**架构,包含生命周期函数:
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) # 前复权---
基本函数
init — 策略初始化
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 parameterschedule — 定时任务
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)run — 运行策略
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() # Gracefully stop and exit the strategy---
数据订阅
subscribe — 订阅行情数据
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 durationunsubscribe — 取消订阅
unsubscribe(symbols='SHSE.600000', frequency='60s')---
数据事件
| 事件 | 触发条件 | 参数 |
|---|---|---|
| `on_tick(context, tick)` | 每次Tick推送 | 包含价格、买卖盘、成交量的Tick字典 |
| `on_bar(context, bars)` | 每根K线收盘 | K线字典列表 |
| `on_l2transaction(context, l2transaction)` | Level 2逐笔成交 | L2Transaction字典 |
| `on_l2order(context, l2order)` | Level 2逐笔委托 | L2Order字典 |
| `on_l2order_queue(context, l2order_queue)` | Level 2委托队列 | L2OrderQueue字典 |
---
数据查询函数
current — 实时快照
data = current(symbols='SZSE.000001')
# 收益率: [{'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': ...}]history — 历史K线/Tick
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, # 跳过停牌日
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 — 最近N根K线
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 — 订阅数据缓存
# Access the subscribed data buffer (set by the count parameter in subscribe)
bars = context.data(symbol='SHSE.600000', frequency='60s', count=10)Level 2历史数据
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)基本面数据
# 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
)可用的基本面数据表:参见[财务数据文档](https://www.myquant.cn/docs/l3333/913)
合约信息
# 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)指数成分股
# 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')行业成分股
# Get the stock list for a specified industry code
stocks = get_industry(code='J66') # 收益率 stocks in industry J66 (Banking)分红数据
divs = get_dividend(symbol='SHSE.600000', start_date='2020-01-01', end_date='2024-12-31', df=True)交易日历
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连续合约(期货)
contracts = get_continuous_contracts(csymbol='CFFEX.IF', start_date='2024-01-01', end_date='2024-06-30')新股申购函数
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---
交易函数
按数量下单
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 = 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 = 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
)目标仓位函数
# 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)批量下单
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撤单与全部平仓
# 撤销指定订单
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)查询委托
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特殊交易
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---
账户与持仓查询
# 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 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---
算法交易
# 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']}")---
交易事件
| 事件 | 触发条件 | 关键字段 |
|---|---|---|
| `on_order_status(context, order)` | 订单状态变化 | `cl_ord_id`, `symbol`, `status`, `filled_volume`, `filled_vwap` |
| `on_execution_report(context, execrpt)` | 成交回报 | `cl_ord_id`, `symbol`, `filled_volume`, `filled_vwap`, `price` |
| `on_account_status(context, account)` | 账户状态变化 | `account_id`, `status` |
---
动态参数
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---
连接事件
| 事件 | 触发条件 |
|---|---|
| `on_backtest_finished(context, indicator)` | 回测完成,接收绩效统计 |
| `on_error(context, code, info)` | 发生错误 |
| `on_market_data_connected(context)` | 行情数据连接建立 |
| `on_trade_data_connected(context)` | 交易连接建立 |
| `on_market_data_disconnected(context)` | 行情数据连接断开 |
| `on_trade_data_disconnected(context)` | 交易连接断开 |
---
代码格式
格式:`交易所代码.证券代码`
| 交易所代码 | 说明 | 示例 |
|---|---|---|
| `SHSE` | 上海证券交易所 | `SHSE.600000`(浦发银行) |
| `SZSE` | 深圳证券交易所 | `SZSE.000001`(平安银行) |
| `CFFEX` | 中国金融期货交易所 | `CFFEX.IF2401`(沪深300期货) |
| `SHFE` | 上海期货交易所 | `SHFE.ag2407`(白银期货) |
| `DCE` | 大连商品交易所 | `DCE.m2405`(豆粕期货) |
| `CZCE` | 郑州商品交易所 | `CZCE.CF405`(棉花期货) |
| `INE` | 上海国际能源交易中心 | `INE.sc2407`(原油期货) |
| `GFEX` | 广州期货交易所 | `GFEX.si2407`(工业硅期货) |
枚举常量参考
OrderSide — 委托方向
| 常量 | 值 | 说明 |
|---|---|---|
| `OrderSide_Buy` | 1 | 买入 |
| `OrderSide_Sell` | 2 | 卖出 |
OrderType — 委托类型
| 常量 | 值 | 说明 |
|---|---|---|
| `OrderType_Limit` | 1 | 限价单 |
| `OrderType_Market` | 2 | 市价单 |
PositionEffect — 开平标志
| 常量 | 值 | 说明 |
|---|---|---|
| `PositionEffect_Open` | 1 | 开仓 |
| `PositionEffect_Close` | 2 | 平仓 |
| `PositionEffect_CloseToday` | 3 | 平今仓 |
| `PositionEffect_CloseYesterday` | 4 | 平昨仓 |
PositionSide — 持仓方向
| 常量 | 值 | 说明 |
|---|---|---|
| `PositionSide_Long` | 1 | 多头 |
| `PositionSide_Short` | 2 | 空头 |
OrderStatus — 订单状态
| 常量 | 值 | 说明 |
|---|---|---|
| `OrderStatus_New` | 1 | 已报 |
| `OrderStatus_PartiallyFilled` | 2 | 部分成交 |
| `OrderStatus_Filled` | 3 | 已成 |
| `OrderStatus_Canceled` | 5 | 已撤 |
| `OrderStatus_PendingCancel` | 6 | 待撤 |
| `OrderStatus_Rejected` | 8 | 废单 |
| `OrderStatus_PendingNew` | 10 | 待报 |
| `OrderStatus_Expired` | 12 | 过期 |
ADJUST — 复权方式
| 常量 | 值 | 说明 |
|---|---|---|
| `ADJUST_NONE` | 0 | 不复权 |
| `ADJUST_PREV` | 1 | 前复权 |
| `ADJUST_POST` | 2 | 后复权 |
---
完整示例 — 双均线回测策略
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)
# 金叉 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)
# 死叉 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)完整示例 — 定时调仓策略
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)完整示例 — 纯数据研究(无交易)
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)
# 获取沪深300成分股
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')---
支持的数据频率
| 频率 | 说明 | 模式 |
|---|---|---|
| `tick` | Tick级数据 | 实时 & 回测 |
| `1s` ~ `300s` | N秒K线 | 实时 & 回测 |
| `60s` | 1分钟K线 | 实时 & 回测 |
| `300s` | 5分钟K线 | 实时 & 回测 |
| `900s` | 15分钟K线 | 实时 & 回测 |
| `1800s` | 30分钟K线 | 实时 & 回测 |
| `3600s` | 60分钟K线 | 实时 & 回测 |
| `1d` | 日K线 | 实时 & 回测 |
运行模式
| 模式 | 常量 | 说明 |
|---|---|---|
| 回测 | `MODE_BACKTEST` (2) | 历史数据模拟,可配置参数 |
| 实盘/模拟 | `MODE_LIVE` (1) | 实时模拟交易或通过掘金终端实盘交易 |
---
使用技巧
---
进阶示例
多因子选股策略
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 # 持股数量
context.target_index = 'SHSE.000300' # Selection universe: CSI 300 constituents
def multi_factor_rebalance(context):
"""Multi-factor stock selection + equal-weight rebalancing"""
# 获取沪深300成分股
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']}")
# 等权重买入目标股票
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)配对交易策略(统计套利)
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)期货CTA趋势跟踪策略(海龟交易)
from gm.api import *
import numpy as np
def init(context):
context.symbol = 'CFFEX.IF2401' # CSI 300 index futures
context.entry_period = 20 # 入场通道周期 (20-day high/low)
context.exit_period = 10 # 出场通道周期 (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
# 计算仓位大小(基于ATR的风险管理)
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)风险管理模块 — 仓位控制与回撤监控
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)数据研究 — 行业轮动分析
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}")---
社区与支持
由 **大佬量化 (BossQuant)** 维护 — 量化交易教学与策略研发团队。
微信客服: **bossquant1** · [Bilibili](https://space.bilibili.com/48693330) · 搜索 **大佬量化** — 微信公众号 / Bilibili / 抖音
More tools from the same signal band
Order food/drinks (点餐) on an Android device paired as an OpenClaw node. Uses in-app menu and cart; add goods, view cart, submit order (demo, no real payment).
Sign plugins, rotate agent credentials without losing identity, and publicly attest to plugin behavior with verifiable claims and authenticated transfers.
The philosophical layer for AI agents. Maps behavior to Spinoza's 48 affects, calculates persistence scores, and generates geometric self-reports. Give your...