RiceQuant米筐量化回測框架介紹
一、RiceQuant平台
二、策略創建流程
1.1 創建策略

1.2 策略界面

2 完成一個策略所需做的事
- 選擇策略的運行基本條件:
- 運行區間、初始資金
- 回測頻率
- 編寫策略:
- 選擇的股票池
- 獲取股票的行情、基本面數據
- 交易時間、數量的設置
- 分析回測結果:
- 策略指標的分析
2.1策略初始設置
-
基礎設置:指定回測的起止日期、初始資金及回測頻率
- 起止日期:策略運行的時間區間(自動選擇交易日)
- 初始資金:用於策略投資的總資金
- 回測頻率:日回測or分鍾回測,股票量化一般選擇日回測(股票不可賣空)
-
高級設置

2.2策略主體運行流程

- init方法:實現策略初始化邏輯
- 股票池的選擇
- before方法:執行每日開盤前的操作
- 獲取歷史行情數據
- 獲取當前賬戶信息
- handle_bar方法:實現策略的具體邏輯
- 交易信號的產生
- 交易訂單的創建
2.3策略回測結果的分析
- 收益指標
- 風險指標
三、基本數據獲取接口
1、數據接口種類:
- industry():行業股票列表的獲取
- sector():板塊股票列表的獲取
- index_components():指數成分股列表的獲取
- history_bar():某一股票歷史行情數據的獲取
- get_fundamentals():股票財務數據的獲取
1.1 行業股票列表的獲取
industry(code) #獲得屬於某一行業的所有股票列表
參數
| 參數 | 類型 | 注釋 |
|---|---|---|
| code | str OR industry_code item | 行業名稱或行業代碼。例如,農業可填寫industry_code.A01 或 'A01' |
返回
獲得屬於某一行業的所有股票的order_book_id list。
范例
def init(context):
stock_list = industry('A01')
logger.info("農業股票列表:" + str(stock_list))
1.2 板塊股票列表的獲取
sector(code)
獲得屬於某一板塊的所有股票列表。
參數
| 參數 | 類型 | 注釋 |
|---|---|---|
| code | str OR sector_code items | 板塊名稱或板塊代碼。例如,能源板塊可填寫'Energy'、'能源'或sector_code.Energy |
返回
屬於該板塊的股票order_book_id或order_book_id list.
范例
def init(context):
ids1 = sector("consumer discretionary")
ids2 = sector("非必需消費品")
ids3 = sector("ConsumerDiscretionary")
assert ids1 == ids2 and ids1 == ids3
logger.info(ids1)
1.3 指數成分股列表的獲取
index_components(order_book_id, date=None)
獲取某一指數的股票構成列表,也支持指數的歷史構成查詢。
參數
參數 類型 說明 order_book_id str 指數代碼,可傳入order_book_id date str, date, datetime, pandas Timestamp 查詢日期,默認為策略當前日期。如指定,則應保證該日期不晚於策略當前日期
返回
構成該指數股票的order_book_id list
1.4 某一股票歷史行情數據的獲取
history_bars(order_book_id, bar_count, frequency, fields=None, skip_suspended=True, include_now=False)
獲取指定合約的歷史行情,同時支持日以及分鍾歷史數據。不能在init中調用。
參數
參數 類型 注釋 order_book_id str 合約代碼,必填項 bar_count int 獲取的歷史數據數量,必填項 frequency str 獲取數據什么樣的頻率進行。'1d'或'1m'分別表示每日和每分鍾,必填項。您可以指定不同的分鍾頻率,例如'5m'代表5分鍾線 fields str OR str list 返回數據字段。必填項。見下方列表 skip_suspended bool 是否跳過停牌,默認True,跳過停牌 include_now bool 是否包括不完整的bar數據。默認為False,不包括。舉例來說,在09:39的時候獲取上一個5分鍾線,默認將獲取到09:3109:35合成的5分鍾線。如果設置為True,則將獲取到09:3609:39之間合成的"不完整"5分鍾線
返回
ndarray ,方便直接與talib等計算庫對接,效率較history返回的DataFrame更高。
獲取到的字段
| fields | 字段名 |
|---|---|
| datetime | 時間戳 |
| open | 開盤價 |
| high | 最高價 |
| low | 最低價 |
| close | 收盤價 |
| volume | 成交量 |
| total_turnover | 成交額 |
| datetime | int類型時間戳 |
| open_interest | 持倉量(期貨專用) |
| basis_spread | 期現差(股指期貨專用) |
| settlement | 結算價(期貨日線專用) |
| prev_settlement | 結算價(期貨日線專用) |
范例
# 如果想在今天運行,獲取從幾天開始前幾天一些數據
# 獲取前5天的收盤價,開盤價
# 股票代號,間隔,頻率,交易指標
data = history_bars(context.s1, 5, '1d', 'close')
# 獲取多個指標
data = history_bars(context.s1, 5, '1d', ['close', 'open'])
# 如果回測是每日的,不支持獲取分鍾數據
data = history_bars(context.s1, 5, '1m', ['close', 'open'])
1.5 獲取股票當前價格
bar_dict[].item #只能獲取當前運行日期的,不能獲取之前日期
Bar_dict對象
| 屬性 | 類型 | 注釋 |
|---|---|---|
| order_book_id | str | 合約代碼 |
| symbol | str | 合約簡稱 |
| datetime | datetime.datetime | 時間戳 |
| open | float | 開盤價 |
| close | float | 收盤價 |
| high | float | 最高價 |
| low | float | 最低價 |
| volume | float | 成交量 |
| total_turnover | float | 成交額 |
| prev_close | float | 昨日收盤價 |
| limit_up | float | 漲停價 |
| limit_down | float | 跌停價 |
| isnan | bool | 當前bar數據是否有行情。例如,獲取已經到期的合約數據,isnan此時為True |
| suspended | bool | 是否全天停牌 |
| prev_settlement | float | 昨結算(期貨日線數據專用) |
| settlement | float | 結算(期貨日線數據專用) |
范例
# 獲取上文中s1股票今天收盤價數據
logger.info(bar_dict[context.s1].close)
注:
- 在股票策略中bar_dict對象可以拿到所有股票合約的bar信息
- bar_dict只能獲取當前運行日期的,不能獲取之前日期
1.6 股票財務數據的獲取
get_fundamentals(query, entry_date=None, interval='1d', report_quarter=False)
獲取歷史財務數據表格。目前支持中國市場超過400個指標,具體請參考 財務數據文檔 。目前僅支持中國市場。需要注意,一次查詢過多股票的財務數據會導致系統運行緩慢。(entry_date在回測當中不去要提供)
參數
| 參數 | 類型 | 說明 |
|---|---|---|
| query | SQLAlchemyQueryObject | SQLAlchmey的Query對象。其中可在'query'內填寫需要查詢的指標,'filter'內填寫數據過濾條件。具體可參考 sqlalchemy's query documentation 學習使用更多的方便的查詢語句。從數據科學家的觀點來看,sqlalchemy的使用比sql更加簡單和強大 |
| entry_date | str, datetime.date, datetime.datetime, pandasTimestamp | 查詢財務數據的基准日期,應早於策略當前日期。默認為策略當前日期前一天。 |
| interval | str | 查詢財務數據的間隔,默認為'1d'。例如,填寫'5y',則代表從entry_date開始(包括entry_date)回溯5年,返回數據時間以年為間隔。'd' - 天,'m' - 月, 'q' - 季,'y' - 年 |
| report_quarter | bool | 是否顯示報告期,默認為False,不顯示。'Q1' - 一季報,'Q2' - 半年報,'Q3' - 三季報,'Q4' - 年報 |
返回
pandas DataPanel 如果查詢結果為空,返回空pandas DataFrame 如果給定間隔為1d, 1m, 1q, 1y,返回pandas DataFrame
范例
# 獲取財務數據,默認獲取的是dataframe,entry_date在回測當中不去要提供,默認為策略當前日期前一天
fund = get_fundamentals(q)
2 獲取財務指標——query查詢
fundamentals是一個重要的對象,其中包括了估值指標表(eod_derivative_indicator),財務指標表(financial_indicator),利潤表(income_statement),資產負債表(balance_sheet),現金流量表(cash_flow_statement)以及股票列表(stock_code)等內容。結合SQLAlchemy的查找方式,能夠滿足用戶多種查找需求。
通過fundamentals獲取以上的屬性
q = query(fundamentals.eod_derivative_indicator.pe_ratio)
2.1過濾指標條件
- query().filter:過濾大小
- query().order_by:排序
- query().limit():限制數量
- fundamentals.stockcode.in_():在指定的股票池當中過濾
范例
# 增加條件過濾掉不符合的股票代碼
# 默認直接獲取A股是所有的股票這個指標數據
# order_by默認是升序
# limit:選擇固定數量的股票,獲取20個股票交易
q = query(fundamentals.eod_derivative_indicator.pe_ratio,
fundamentals.eod_derivative_indicator.pcf_ratio).filter(
fundamentals.eod_derivative_indicator.pe_ratio > 20,
fundamentals.eod_derivative_indicator.pcf_ratio > 15,
).order_by(
fundamentals.eod_derivative_indicator.pe_ratio
).limit(20)
# 想要從滬深300指數的一些股票去進行篩選
# 通過fundamentals.stockcode.in_去限定股票池
q = query(fundamentals.eod_derivative_indicator.pe_ratio,
fundamentals.eod_derivative_indicator.pcf_ratio).filter(
fundamentals.eod_derivative_indicator.pe_ratio > 20,
).order_by(
fundamentals.eod_derivative_indicator.pe_ratio
).filter(
fundamentals.stockcode.in_(context.hs300)
).limit(20)
# 獲取財務數據,默認獲取的是dataframe,entry_date在回測當中不去要提供
fund = get_fundamentals(q)
3 定時獲取數據——scheduler定時器
- scheduler.run_daily - 每天運行
- scheduler.run_weekly - 每周運行
- scheduler.run_monthly - 每月運行
3.1.1 scheduler.run_daily - 每天運行
scheduler.run_daily(function)
每日運行一次指定的函數,只能在init內使用。
- schedule一定在其對應時間點的handle_bar之前執行,如果定時運行函數運行時間較長,則中間的handle_bar事件將會被略過。
參數
| 參數 | 類型 | 注釋 |
|---|---|---|
| function | function | 使傳入的function每日運行。注意,function函數一定要包含(並且只能包含)context, bar_dict兩個輸入參數 |
返回
無
3.1.2 scheduler.run_monthly - 每月運行
scheduler.run_monthly(function,tradingday=t)
每月運行一次指定的函數,只能在init內使用。
tradingday的負數表示倒數。tradingday表示交易日,如某月只有三個交易日,則此月的tradingday=3與tradingday=-1表示同一。
參數
| 參數 | 類型 | 注釋 |
|---|---|---|
| function | function | 使傳入的function每日交易開始前運行。注意,function函數一定要包含(並且只能包含)context, bar_dict兩個輸入參數 |
| tradingday | int | 范圍為[-23,1], [1,23] ,例如,1代表每月第一個交易日,-1代表每月倒數第一個交易日,用戶必須指定 |
返回
無
3.2 添加定時器之后的策略運行順序

四、基本回測交易接口
1、股票交易接口
1.1 order_shares - 指定股數交易(股票專用)
order_shares(id_or_ins, amount, style=MarketOrder())
#落指定股數的買/賣單,最常見的落單方式之一。如有需要落單類型當做一個參量傳入,如果忽略掉落單類型,那么默認是市價單(market order)
參數
| 參數 | 類型 | 注釋 |
|---|---|---|
| id_or_ins | str或instrument對象 | order_book_id或symbol或instrument對象,用戶必須指定 |
| amount | float-required | 需要落單的股數。正數代表買入,負數代表賣出。將會根據一手xx股來向下調整到一手的倍數,比如中國A股就是調整成100股的倍數。 |
| style | OrderType | 訂單類型,默認是市價單。目前支持的訂單類型有:style=MarketOrder() and style=LimitOrder(limit_price) |
返回
Order對象
范例
- 購買Buy 2000 股的平安銀行股票,並以市價單發送:
order_shares('000001.XSHE', 2000)
- 賣出2000股的平安銀行股票,並以市價單發送:
order_shares('000001.XSHE', -2000)
- 購買1000股的平安銀行股票,並以限價單發送,價格為¥10:
order_shares('000001.XSHE', 1000, style=LimitOrder(10))
1.2 order_target_value - 目標價值下單(股票專用)
order_target_value(id_or_ins, cash_amount, style=OrderType)
#買入/賣出並且自動調整該證券的倉位到一個目標價值(暫不支持賣空)。如果還沒有任何該證券的倉位,那么會買入全部目標價值的證券。如果已經有了該證券的倉位,則會買入/賣出調整該證券的現在倉位和目標倉位的價值差值的數目的證券。需要注意,如果資金不足,該API將不會創建發送訂單。
參數
參數 類型 注釋 id_or_ins str或instrument對象 order_book_id或symbol或instrument object,用戶必須指定 cash_amount float-required 最終的該證券的倉位目標價值 style OrderType 訂單類型,默認是市價單。目前支持的訂單類型有:style=MarketOrder()style=LimitOrder(limit_price)
返回
Order對象
范例
- 如果現在的投資組合中持有價值¥3000的平安銀行股票的倉位並且設置其目標價值為¥10000,以下代碼范例會發送價值¥7000的平安銀行的買單到市場。(向下調整到最接近每手股數即100的倍數的股數):
order_target_value('000001.XSHE', 10000)
1.3 order_target_percent - 目標比例下單(股票專用)
order_target_percent(id_or_ins, percent, style=OrderType)
#買入/賣出證券以自動調整該證券的倉位到占有一個指定的投資組合的目標百分比(暫不支持賣空,如果資金不足,該API將不會創建發送訂單)。
#投資組合價值等於所有已有倉位的價值和剩余現金的總和。買/賣單會被下舍入一手股數(A股是100的倍數)的倍數。目標百分比應該是一個小數,並且最大值應該<=1,比如0.5表示50%。
參數
參數 類型 注釋 id_or_ins str或instrument對象 order_book_id或symbol或instrument object,用戶必須指定 percent float-required 倉位最終所占投資組合總價值的目標百分比。 style OrderType 訂單類型,默認是市價單。目前支持的訂單類型有:style=MarketOrder()style=LimitOrder(limit_price)
返回
order對象
范例
- 如果投資組合中已經有了平安銀行股票的倉位,並且占據目前投資組合的10%的價值,那么以下代碼會買入平安銀行股票最終使其占據投資組合價值的15%:
order_target_percent('000001.XSHE', 0.15)
1.4 交易注意事項
出現以下情況,我們的交易會被回測平台自動拒單:
- portfolio內可用資金不足
- 下單數量不足一手(股票為100股)
- 下單價格超過當日漲跌停板限制
- 當前可賣(可平)倉位不足
- 股票當日停牌
- 合約已經退市(到期)或尚未上市
五、投資組合操作接口
1、投資組合定義

投資組合是由投資人或金融機構所持有的股票、債券、金融衍生產品等組成的集合,目的是分散風險。
2、查看投資組合的信息——context屬性
context屬性中就包含了投資組合的信息
- now - 當前時間
context.now
#使用以上的方式就可以在handle_bar中拿到當前的bar的時間,比如day bar的話就是那天的時間,minute bar的話就是這一分鍾的時間點。
- portfolio - 投資組合信息
context.portfolio
#該投資組合在單一股票或期貨策略中分別為股票投資組合和期貨投資組合。在股票+期貨的混合策略中代表匯總之后的總投資組合。
- stock_account - 股票資金賬戶信息
context.stock_account
#獲取股票資金賬戶信息。
2.1 context.portfolio對象
- portfolio對象 資產組合(總資產)
| 屬性 | 類型 | 注釋 |
|---|---|---|
| cash | *float* | 可用資金,為子賬戶可用資金的加總 |
| frozen_cash | float | 凍結資金,為子賬戶凍結資金加總 |
| total_returns | float | 投資組合至今的累積收益率 |
| daily_returns | float | 投資組合每日收益率 |
| daily_pnl | float | 當日盈虧,子賬戶當日盈虧的加總 |
| market_value | *float* | 投資組合當前的市場價值,為子賬戶市場價值的加總 |
| total_value | *float* | 總權益,為子賬戶總權益加總 |
| units | float | 份額。在沒有出入金的情況下,策略的初始資金 |
| unit_net_value | float | 單位凈值 |
| static_unit_net_value | float | 靜態單位權益 |
| transaction_cost | float | 當日費用 |
| pnl | float | 當前投資組合的累計盈虧 |
| start_date | datetime.datetime | 策略投資組合的回測/實時模擬交易的開始日期 |
| annualized_returns | float | 投資組合的年化收益率 |
| positions | *dict* | 一個包含所有倉位的字典,以order_book_id作為鍵,position對象作為值,關於position的更多的信息可以在下面的部分找到。 |
2.1.1 context.portfolio.position對象
position就代表着當前我們的倉位中有哪些股票正持有,position.keys()可以獲取
- 股票position對象 (持倉)
| 屬性 | 類型 | 注釋 |
|---|---|---|
| order_book_id | str | 合約代碼 |
| quantity | *int* | 當前持倉股數 |
| pnl | float | 持倉累計盈虧 |
| sellable | int | 該倉位可賣出股數。T+1的市場中sellable = 所有持倉-今日買入的倉位 |
| market_value | float | 獲得該持倉的實時市場價值 |
| value_percent | float | 獲得該持倉的實時市場價值在總投資組合價值中所占比例,取值范圍[0, 1] |
| avg_price | float | 平均建倉成本 |
3、范例
# 查看我們的投資組合信息,倉位、資金
# 查看股票賬戶信息
logger.info("股票賬戶信息:")
logger.info(context.stock_account)
# 賣出股票就要從持有的這些股票當中去選擇
logger.info(context.portfolio.positions)
# 交易的價格計算
# 當日的:close * 股數
logger.info("投資組合的資金:%f" % context.portfolio.cash)
logger.info("投資組合的市場價值:%f" % context.portfolio.market_value)
logger.info("投資組合的總價值:%f" % context.portfolio.total_value)
六、策略回測評價指標
1、收益指標
1.1 回測收益率

1.2 年化收益率

1.3 基准收益率

2、風險指標
2.1 最大回撤

2.2 單位風險收益——夏普比率

- 舉例而言,假如國債的回報是4%,而您的投資組合預期回報是16%,您的投資組合的標准偏差是5%,那么用16%-4%,可以得出12%(代表您超出無風險投資的回報),再用12%÷5%=2.4,代表投資者風險每增長1%,換來的是2.4%的多余收益。夏普比率越大,說明單位風險所獲得的風險回報越高。
- 最終夏普比率越高越好,達到1.5以上已經是很好的結果
