全文1.8w字,先帶大家認識量化交易和量化回測框架,最後帶你手把手實現第一個股票策略。
一、量化交易簡介
量化交易(Quantitative trading)
1.1 定義
量化交易(量化投資)是指藉助現代統計學和數學(機器學習)的方法,利用計算機技術來進行交易的證券投資方式。
量化交易從龐大的歷史數據中海選能帶來超額收益的多種「大概率」事件以制定策略,用數量模型驗證及固化這些規律和策略,然後嚴格執行已固化的策略來指導投資,以求獲得可以持續的、穩定且高於平均收益的超額回報。
1.2 掌握技能
量化交易分類
1、分類
關於這三種分類,不用記憶它們的定義。我們課程主要的量化投資方法是市場中性的策略。
2、 三種分類特點以及要求
- 趨勢性交易
- 適合一些主觀交易的高手,用技術指標作為輔助工具在市場中如魚得水的,但如果只用各種技術指標或指標組合作為核心算法構建模型,從未見過能長期盈利的。
- 一般也會做一些量化分析操作,使用編程如python/matlab 。
- 市場中性
- 在任何市場環境下風險更低,收益穩定性更高,資金容量更大。適合一些量化交易者,發現市場中的alpha因子賺取額外收益,例如股票與股指期貨的對沖策略等。
- 會做一些量化分析操作,使用編程如python/matlab。
- 高頻交易
- 在極短的時間內頻繁買進賣出,完成多次大量的交易,此類交易方式對硬體系統以及市場環境的要求極高,所以只有在成熟市場中的專業機構才會得到應用
- 適合一些算法高手,使用C/C++程式語言,去進行算法交易,對軟硬體條件要求比較高。
1、金融專業出生,對金融市場環境非深入了解(交易員、基金經理)
2、基本了解金融基礎、投資知識,對數據挖掘、機器學習方法擅長,挖掘股票等的價值 (quanter)
3、非常擅長算法,C/C++ ,編寫程序化的一些交易方法 (程序化交易員)
3、 金融產品以及衍生品的常用投資技術
- 投資策略
註:比特幣不屬於衍生品,通常使用的也是趨勢策略,少部分使用高頻策略
4、 量化交易的優勢
- 嚴格的紀律性
- 完備的系統性
- 完備的系統性具體表現為「三多」。
- 首先表現在多層次,包括在大類資產配置、行業選擇、精選個股三個層次上我們都有模型;
- 其次是多角度,量化交易的核心投資思想包括宏觀周期、市場結構、估值、成長、盈利質量、分析師盈利預測、市場情緒等多個角度;
- 再者就是多數據,就是海量數據的處理。人腦處理信息的能力是有限的,當一個資本市場只有100隻股票,這對定性投資基金經理是有優勢的,他可以深刻分析這100家公司。但在一個很大的資本市場,比如有成千上萬隻股票的時候,強大的定量化交易的信息處理能力能反映它的優勢,能捕捉更多的投資機會,拓展更大的投資機會。
- 靠數學模型取勝
- 股票實際操作過程中,運用概率分析,提高買賣成功的概率
量化交易歷史
1、量化交易全球的發展歷史
- 量化投資的產生(60年代)
- 1969年,愛德華·索普利用他發明的「科學股票市場系統」(實際上是一種股票權證定價模型),成立了第一個量化投資基金。索普也被稱之為量化投資的鼻祖
- 量化投資的興起(70~80年代)
- 1988年,詹姆斯·西蒙斯成立了大獎章基金,從事高頻交易和多策略交易。基金成立20多年來收益率達到了年化70%左右,除去報酬後達到40%以上。西蒙斯也因此被稱為"量化對沖之王"。
- 量化交易的繁榮(90年代)
- 1991年,彼得·穆勒發明了alpha系統策略等,開始用計算機+金融數據來設計模型,構建組合
2、 國內量化交易的發展歷史
我們通過一張圖來對比國內國外的發展歷史
2012年到2016年量化對沖策略管理的資金規模增長了20倍,管理期貨策略更是增長了30倍,增長的速度是所有策略中最快的。相比美國量化基金髮展歷程,中國現在基本處於美國90年代至21世紀之間的階段。
- 量化投資元年
- 2010年,滬深300股指期貨上市,此時的量化基金終於具備了可行的對沖工具,各種量化投資策略如alpha策略、股指期貨套利策略才真正有了大展拳腳的空間,可以說2010年是中國量化投資元年。
- 量化投資高速發展、多元化發展
- 2013-2015年股指新政之前可以說是國內量化基金有史以來最風光的一段時期。國內量化投資機構成批湧現,國內量化投資高速發展。
量化交易項目做什麼?
1、 量化交易研究流程
量化回測框架提供完整的數據,以及回測機制進行策略評估研究,並能夠實時進行模擬交易。為實盤交易提供選擇。我們的研究一般在回測平台當中做
1.1 分析結果
我們最終想要的結果就是在回測當中表現得較好的分析方法和策略。比如:
- 演示結果
1.2 什麼是策略
量化策略是指使用計算機作為工具,通過一套固定的邏輯來分析、判斷和決策。 量化策略既可以自動執行,也可以人工執行。其實策略也可以理解為,分析數據之後,決策買什麼以及交易時間。
1.3 流程包含的內容
- 獲取數據:
- 公司財務、新聞數據
- 基本行情數據
- 數據分析挖掘:
- 傳統分析方法、機器學習,數據挖掘方法
- 數據處理,標準化,去極值,中性化分組回測,行業分布
- 構建策略:
- 獲取歷史行情,歷史持倉信息,調倉記錄等
- 止盈止損單,限價單,市價單
- 回測:
- 股票漲跌停、停復牌處理
- 市場衝擊,交易滑點,手續費
- 策略分析:
- 訂單分析,成交分析,持倉分析
- 模擬交易:
- 接入實時行情,實時獲取成交回報
- 實時監控,實時歸因分析
- 實盤交易:
- 接入真實券商帳戶
2、 量化開發和研究崗位要求
- 基於交易市場數據,研究、開發交易策略,進行基礎建模
- 負責對交易策略進行回測、跟蹤、分析、優化
- 定期對交易策略的運行結果進行總結,給出分析報告,評估市場適用度
- 負責數據挖掘、處理,數據統計分析,從數據中發現規律,為量化分析提供支持,開發量化模型策略
- 與基金經理合作跟蹤優化股票市場量化策略在實盤的表現
二、量化回測框架介紹
回測框架介紹
1、 基礎回測框架
Zipline本身只支持美國的證券,無法更好的使用數據,本地運行速度慢
2、雲端的框架
- 提供部分滿足需求的數據(但是平台數據質量不行,指標不完整)
- 策略運行在遠端伺服器
這些線上平台提供了本地專業版,但是需要收費
3、不去實現一個回測框架的原因
- 1、沒有完整的股票行情和基本面數據
- 2、回測平台是載體,重點在於快速驗證策略
- 3、證券投資機構各自使用回測框架不同,沒有通用的框架
4、RiceQuant回測平台介紹
網址:https://www.ricequant.com/
- 註冊帳號
策略創建運行流程
1、體驗創建策略、運行策略流程
1.1 創建策略
1.2 策略界面
2、 策略界面功能、運行介紹
2.1 一個完整的策略需要做的事情
- 選擇策略的運行信息:
- 選擇運行區間和初始資金
- 選擇回測頻率
- 選擇股票池
- 編寫策略的邏輯
- 獲取股票行情、基本面數據
- 選擇哪些股票、以及交易時間
- 分析結果
- 策略指標分析
2.2 策略初始設置介紹
- 基礎設置:指定回測的起止日期、初始資金以及回測頻率
- 起止日期:策略運行的時間區間
- 初始資金:用於投資的總資金
- 回測的頻率:有兩種選擇,日回測/分鐘回測。做股票量化選擇日回測即可
- 高級設置:
關於高級的設置其他部分,在介紹交易函數時介紹
2.3 策略主體運行流程分析
- 在init方法中實現策略初始化邏輯
- 策略的股票池:在那些股票中進行交易判斷(例如:HS300)
- 可以選擇在before_trading進行一些每日開盤之前的操作,比如獲取歷史行情做一些數據預處理,獲取當前帳戶資金等。
- 在handle_bar方法中實現策略具體邏輯,包括交易信號的產生、訂單的創建。handle_bar內的邏輯會在每次bar數據更新的時候被觸發。
調用的順序為:
- 1、init
- 2、before_trading
- 3、handle_bar
2.4 策略結果分析
回測完成後,在'回測結果'頁面會展示回測的倉位、盈虧、交易、風險等信息
數據獲取接口
1、 數據接口種類
- 獲取指定行業、板塊股票列表
- history_bars - 指定股票合約歷史數據
- get_fundamentals - 查詢財務數據
2、 獲取行業、板塊以及概念股票列表
2.1 關於股票代碼以及代碼補齊
RiceQuant上的股票代碼標記
股票自動搜索及補全
- Windows 用戶 : 輸入ctrl + i
- Mac 用戶 :輸入command + i
- Linux 用戶 :輸入ctrl + i
當您輸入了這個組合鍵之後,Ricequant在線IDE就會進入股票代碼搜索和自動完成模式,接著您可以輸入任何一種進行搜索和自動補全:
- 股票數字代碼 - 自動補全為股票數字代碼,比如"000024.XSHE":
- 股票中文全稱 - 自動補全為股票中文全稱,比如"招商地產"
- 股票拼音縮寫 - 這裡比較特殊,自動補全為股票中文全稱,因為股票拼音縮寫並不是獨一無二的,比如ZSDC補全為"招商地產"
2.2 獲取行業
industry - 行業股票列表
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))
2.3 獲取板塊
sector - 板塊股票列表
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)
支持的行業獲取如下,想要了解全球行業劃分標準參考全球行業標準分類:
板塊代碼 |
中文板塊名稱 |
英文板塊名稱 |
Energy |
能源 |
energy |
Materials |
原材料 |
materials |
ConsumerDiscretionary |
非必需消費品 |
consumer discretionary |
ConsumerStaples |
必需消費品 |
consumer staples |
HealthCare |
醫療保健 |
health care |
Financials |
金融 |
financials |
InformationTechnology |
信息技術 |
information technology |
TelecommunicationServices |
電信服務 |
telecommunication services |
Utilities |
公共服務 |
utilities |
Industrials |
工業 |
industrials |
2.4 獲取概念
參考:https://www.ricequant.com/api/python/chn#data-methods-concept
2.5 獲取指數成分股
index_components - 指數成分股
index_components(order_book_id, date=None)
獲取某一指數的股票構成列表,也支持指數的歷史構成查詢。
參數 |
類型 |
說明 |
order_book_id |
str |
指數代碼,可傳入order_book_id |
date |
str, date, datetime, pandas Timestamp |
查詢日期,默認為策略當前日期。如指定,則應保證該日期不晚於策略當前日期 |
返回
構成該指數股票的order_book_id list
常見的指數獲取代碼為
2.6 自定義股票池,提供給handle_bar使用
我們可以通過context的參數,相當於提供一個全局變量來獲取
def init(context):
# 在context中保存全局變量
context.s1 = "000001.XSHE"
# context.s2 = "601390.XSHG"
# 獲取行業
# context.stock_list = industry("C39")
# 獲取指數成分股
context.hs300 = index_components("000300.XSHG")
def before_trading(context):
logger.info(context.hs300)
logger.info("before_trading")
3、獲取股票合約數據
3.1 history_bars - 某一合約歷史數據
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 |
strOR str list |
返回數據欄位。必填項。見下方列表 |
skip_suspended |
bool |
是否跳過停牌,默認True,跳過停牌 |
include_now |
bool |
是否包括不完整的bar數據。默認為False,不包括。舉例來說,在09:39的時候獲取上一個5分鐘線,默認將獲取到09:31~09:35合成的5分鐘線。如果設置為True,則將獲取到09:36~09:39之間合成的"不完整"5分鐘線 |
返回
ndarray ,方便直接與talib等計算庫對接,效率較history返回的DataFrame更高。
獲取的欄位內容如下
fields |
欄位名 |
datetime |
時間戳 |
open |
開盤價 |
high |
最高價 |
low |
最低價 |
close |
收盤價 |
volume |
成交量 |
total_turnover |
成交額 |
datetime |
int類型時間戳 |
open_interest |
持倉量(期貨專用) |
basis_spread |
期現差(股指期貨專用) |
settlement |
結算價(期貨日線專用) |
prev_settlement |
結算價(期貨日線專用) |
3.2 代碼以及注意的問題
- 因為撮合邏輯是當前bar收盤或者下一個bar開盤,所以history_bars()可以獲取到包含當前bar及之前所有的bar數據
- 獲取當天的數據
- 獲取前十天的數據
- 獲取每天的每分鐘分鐘的數據?獲取每分鐘之前的幾分鐘數據?
# 如果想在今天運行,獲取從幾天開始前幾天一些數據
# 獲取前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'])
問題:這裡的頻率跟回測的頻率區別?
3.3 其它-通過bar_dict獲取
獲取合約當前價格的bar_dict,
Bar對象
屬性 |
類型 |
注釋 |
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 |
結算(期貨日線數據專用) |
注意,在股票策略中bar對象可以拿到所有股票合約的bar信息
# 只能獲取當前的交易信息
logger.info(bar_dict[context.s1].close)
註:只能獲取當前運行日期的,不能獲取之前日期
4、獲取財務數據
4.1 get_fundamentals - 查詢財務數據
get_fundamentals(query, entry_date=None, interval='1d', report_quarter=False)
獲取歷史財務數據表格。目前支持中國市場超過400個指標,具體請參考 財務數據文檔 。目前僅支持中國市場。需要注意,一次查詢過多股票的財務數據會導致系統運行緩慢。(entry_date在回測當中不去要提供)
注意這裡的數據指標類別雖然有400多種,但是RQ平台的這些指標數據質量不高,很多指標沒有經過運算處理成需要的指標,跟我們在講金融數據處理的時候列出來的那些財務指標差別比較大。
參數
參數 |
類型 |
說明 |
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
4.2 如何獲取指標-query查詢
通過fundamentals獲取以上的屬性
q = query(fundamentals.eod_derivative_indicator.pe_ratio)
4.3 過濾指標條件
- 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去限定股票池
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)
# 注釋:每個表都有一個stockcode在用來方便通過股票代碼來過濾掉查詢的數據
問題:一般選擇一些滿足財務數據的股票時間並不是每天去獲取,而是間隔一周、一個月去獲取一次?怎麼取獲取呢?
4、scheduler定時器定時數據獲取
- scheduler.run_daily - 每天運行
- scheduler.run_weekly - 每周運行
- scheduler.run_monthly - 每月運行
4.1 API介紹
4.1.1 scheduler.run_daily - 每天運行
scheduler.run_daily(function)
每日運行一次指定的函數,只能在init內使用。
注意,schedule一定在其對應時間點的handle_bar之前執行,如果定時運行函數運行時間較長,則中間的handle_bar事件將會被略過。
參數
參數 |
類型 |
注釋 |
function |
function |
使傳入的function每日運行。注意,function函數一定要包含(並且只能包含)context, bar_dict兩個輸入參數 |
返回
無
4.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代表每月倒數第一個交易日,用戶必須指定 |
返回
無
4.2 添加定時器之後的策略運行順序
比如我們添加了這樣一段代碼:
def init(context):
# 定義一個每天運行一個定時器
scheduler.run_daily(get_data)
# 每個一個月去獲取財務數據,每隔一周去獲取財務數據
scheduler.run_monthly(get_data, tradingday=1)
4.3 代碼
def get_data(context, bar_dict):
# logger.info("-------")
# 進行每月的第一天去調整要買賣的股票
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)
# 獲取財務數據
data = get_fundamentals(q)
logger.info("這個月更新的股票池")
logger.info(data.T)
回測交易接口
1、用於股票的交易函數
- order_shares - 指定股數交易(股票專用)
- order_lots - 指定手數交易(股票專用)
- order_value - 指定價值交易(股票專用)
- order_percent - 一定比例下單(股票專用)
- order_target_value - 目標價值下單(股票專用)
更多詳細內容參考:https://www.ricequant.com/api/python/chn#methods-implement-after-trading
1.1 交易函數API
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.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.1.3 order_target_percent - 目標比例下單(股票專用)
order_target_percent(id_or_ins, percent, style=OrderType)
買入/賣出證券以自動調整該證券的倉位到占有一個指定的投資組合的目標百分比(暫不支持賣空)。
其實我們需要計算一個position_to_adjust (即應該調整的倉位)
position_to_adjust = target_position - current_position
投資組合價值等於所有已有倉位的價值和剩餘現金的總和。買/賣單會被下捨入一手股數(A股是100的倍數)的倍數。目標百分比應該是一個小數,並且最大值應該<=1,比如0.5表示50%。
如果position_to_adjust 計算之後是正的,那麼會買入該證券,否則會賣出該證券。 需要注意,如果資金不足,該API將不會創建發送訂單。
參數
參數 |
類型 |
注釋 |
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.2 交易注意事項
出現以下情況,我們的交易會被回測平台自動拒單
portfolio內可用資金不足
下單數量不足一手(股票為100股)
下單價格超過當日漲跌停板限制
當前可賣(可平)倉位不足
股票當日停牌
合約已經退市(到期)或尚未上市
1.3 何為市價單和限價單
撮合機制
我們加入了允許用戶自定義撮合機制的功能。您可以在策略編輯頁面"更多"選項下選擇不同的撮合機制。目前提供的撮合方式有以下兩種:
- 1.當前收盤價。即當前bar發單,以當前bar收盤價作為參考價撮合。
- 2.下一開盤價。即當前bar發單,以下一bar開盤價作為參考價撮合
市價單與限價單
1、限價單(LimitOrder)如果買單價格>=參考價,或賣單價格<=參考價,以參考價加入滑點影響成交(買得更高,賣得更低)。市價單(MarketOrder)直接以以參考價加入滑點影響成交。
2、成交數量都不超過當前bar成交量的25%。某一分鐘成交量10000股,那麼回測的時候我們做限制成交不能超過2500股 。一旦超過,市價單會在部分成交之後被自動撤單;限價單會一直在訂單隊列中等待下一個bar數據撮合成交,直到當日收盤。當日收盤後,所有未成交限價單都將被系統自動撤單。
拓展:需要注意,在當前的分鐘回測撮合模式下,用戶在回測中無法通過在scheduler調用的函數中一次性實現 賣出 -> 資金釋放 -> 買入 這種先賣後買的邏輯的。因為在分鐘回測中,賣出並不能立刻成交。
- 分鐘回測及實盤模擬:在一個handle_bar內下單,在該handle_bar結束時統一撮合成交(成交價取決於撮合機制以及滑點設置)。
- 日回測:在一個handelbar內下單,下單時立刻撮合成交(成交價取決於撮合機制以及滑點設置)。
- 舉例來說,策略A設置每周一開盤進行調倉操作,先賣後買。那麼,以下這種方式在分鐘回測中無法實現賣出資金立刻釋放的(在開啟驗資的風控情況下,可能導致後面的買入操作因資金不足而拒單):
#scheduler調用的函數需要包括context, bar_dict兩個參數 def rebalance(context, bar_dict): order_shares('000001.XSHE', -100) order_shares('601998.XSHG', 100) def init(context): scheduler.run_weekly(rebalance, weekday=1)
總結
為了更好模擬實際交易中訂單對市場的衝擊,我們引入滑點的設置。您可以在策略編輯頁面"更多"選項下進行滑點設置,允許設置的範圍是[0, 1)。該設置將在一定程度上使最後的成交價"惡化",也就是買得更貴,賣得更便宜。我們的滑點方式是按照最後成交價的一定比例進行惡化。例如,設置滑點為0.1,那麼如果原本買入交易的成交價為10元,則設置之後成交價將變成11元,即買得更貴。
註:真是交易不需要
1.4 交易的費用
相關介紹參考:https://www.ricequant.com/api/python/chn#backtests-margin
一旦發生了交易,投資組合的資金就會發生變化!那麼接下來要介紹的投資組合是什麼?
2、投資組合
我們看一張圖
2.1 定義
投資組合是由投資人或金融機構所持有的股票、債券、金融衍生產品等組成的集合,目的是分散風險。
2.2 如何查看投資組合的信息
還記得我們之前提到的一個叫context的參數嗎,這個參數當中就包含了投資組合的信息
context屬性
- now - 當前時間
context.now
使用以上的方式就可以在handle_bar中拿到當前的bar的時間,比如day bar的話就是那天的時間,minute bar的話就是這一分鐘的時間點。
返回數據類型為datetime.datetime
- portfolio - 投資組合信息
context.portfolio
該投資組合在單一股票或期貨策略中分別為股票投資組合和期貨投資組合。在股票+期貨的混合策略中代表匯總之後的總投資組合。
- stock_account - 股票資金帳戶信息
context.stock_account
獲取股票資金帳戶信息。
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的更多的信息可以在下面的部分找到。 |
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 |
平均建倉成本 |
2.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)
2.4 查看交易情況界面
策略評價指標
1、策略評價指標
- 收益指標
- 風險指標
2、 收益指標
2.1 回測收益率
策略在期限內的收益率。
2.2 年化收益率
採用了複利累積以及Actual/365 Fixed的年化方式計算得到的年化收益。
我們更加注重年化收益率,對於股票來講,年化達到15~30%已經算是比較好的策略。年化收益率越高越好
2.3 基準收益率
相同條件下,一個簡單的買入並持有基準合約策略的收益率(默認基準合約為滬深300指數,這裡假設指數可交易,最小交易單位為1)。
基準收益率拿來對比我們的策略收益率,策略的表現期望超過基準收益率才獲得了比較好的收益
3、 風險指標
風險指標指的是在獲得收益的時候,承擔一些風險值
3.1 最大回撤
最大回撤越小越好,最大回撤最好保持10~30%之間
4、單位風險收益指標
4.1 夏普比率
舉例而言,假如國債的回報是4%,而您的投資組合預期回報是16%,您的投資組合的標準偏差是5%,那麼用16%-4%,可以得出12%(代表您超出無風險投資的回報),再用12%÷5%=2.4,代表投資者風險每增長1%,換來的是2.4%的多餘收益。夏普比率越大,說明單位風險所獲得的風險回報越高。
最終夏普比率越高越好,達到1.5以上已經是很好的結果
實現第一個股票策略
1、 選股簡單介紹
不管是技術分析還是基本面分析,我們在進行投資的時候都會選擇某些表現較好的股票來作為一個股票池,從中進行交易的判斷(技術分析)或者直接購買。
2、 需求
- 選股:獲得市盈率大於50且小於65,營業總收入前10的股票
- 調倉:每日調倉,將所有資金平攤到這10個股票的購買策略,賣出一次性賣出所有不符合條件的
3、 代碼
# 可以自己import我們平台支持的第三方python模塊,比如pandas、numpy等。
# 每日選股:獲得市盈率大於50且小於65,營業總收入前10的股票
# 買賣:買入每天選出來的10隻,賣出不符合條件
# 調倉按照月調倉,投資對象HS300
# 在這個方法中編寫任何的初始化邏輯。context對象將會在你的算法策略的任何方法之間做傳遞。
def init(context):
# 在context中保存全局變量
# context.s1 = "000001.XSHE"
# 實時列印日誌
# logger.info("RunInfo: {}".format(context.run_info))
# 定義一個選股的範圍
context.hs300 = index_components("000300.XSHG")
scheduler.run_monthly(get_data, tradingday=1)
def get_data(context, bar_dict):
# 刪掉兩個條件
# .filter(
# fundamentals.eod_derivative_indicator.pe_ratio > 50
# ).filter(
# fundamentals.eod_derivative_indicator.pe_ratio < 65
# )
# 選股
q = query(fundamentals.eod_derivative_indicator.pe_ratio,
fundamentals.income_statement.revenue
).order_by(
fundamentals.income_statement.revenue.desc()
).filter(
fundamentals.stockcode.in_(context.hs300)
).limit(10)
fund = get_fundamentals(q)
# 行列內容以及索引一起進行轉置
# print(fund.T)
context.stock_list = fund.T.index
# before_trading此函數會在每天策略交易開始前被調用,當天只會被調用一次
def before_trading(context):
pass
# 你選擇的證券的數據更新將會觸發此段邏輯,例如日或分鐘歷史數據切片或者是實時數據切片更新
def handle_bar(context, bar_dict):
# 開始編寫你的主要的算法邏輯
# bar_dict[order_book_id] 可以拿到某個證券的bar信息
# context.portfolio 可以拿到現在的投資組合信息
# 使用order_shares(id_or_ins, amount)方法進行落單
# TODO: 開始編寫你的算法吧!
# order_shares(context.s1, 1000)
# 在這裡才能進行交易
# 先判斷倉位是否有股票,如果有,賣出(判斷在不在新的股票池當中)
if len(context.portfolio.positions.keys()) != 0:
for stock in context.portfolio.positions.keys():
# 如果舊的持有的股票不在新的股票池當中,賣出
if stock not in context.stock_list:
order_target_percent(stock, 0)
# 買入最新的每日更新的股票池當中的股票
# 等比例資金買入,投資組合總價值的百分比平分10份
weight = 1.0 / len(context.stock_list)
for stock in context.stock_list:
order_target_percent(stock, weight)
# after_trading函數會在每天交易結束後被調用,當天只會被調用一次
def after_trading(context):
pass