github 地址 https://github.com/ricequant/rqalphapython
一、運行test.py文件,顯示 No module named 'logbook.base'。git
解決先卸載再安裝: pip uninstall logbook pip install logbookgithub
二、出現:RuntimeError: 請設置帳戶及初始資金。session
解決:數據結構
github地址 https://github.com/quantopian/zipline Zipline學習資料 http://www.zipline.io/ide
zipline代碼比較多,很差複製函數
github地址 https://github.com/mhallsmoore/qstraderoop
遵從朋友建議,暫時學習qstrade。代碼少,上手快。post
測試:學習
Could not subscribe ticker SPY as no data CSV found for pricing.
Could not subscribe ticker AGG as no data CSV found for pricing.
Traceback (most recent call last):
File "E:/qstrader-master/examples/monthly_liquidate_rebalance_backtest.py", line 108, in <module>
run(config, testing, tickers, filename)
File "E:/qstrader-master/examples/monthly_liquidate_rebalance_backtest.py", line 94, in run
在策略代碼後面添加以下便可:
import os from munch import munchify os.chdir('E:\\qstrader-master') config = munchify({"CSV_DATA_DIR": "data", "OUTPUT_DIR": "out", 'testing': True})
保存圖片須要修改:
將trading_session.py文件最後部分,倒數第二行self.statistics.plot_results()-------->self.statistics.save()
將tearsheet.py文件最後部分,self.plot_results()---------->self.plot_results(filename)
學習問題彙總:
一、運行buy_and_hold_backtest.py文件時出錯。
File "E:\qstrader-master\qstrader\price_handler\yahoo_daily_csv_bar.py", line 68, in _merge_sort_ticker_data
df = pd.concat(self.tickers_data.values()).sort_index()
File "D:\Anaconda3\lib\site-packages\pandas\core\reshape\concat.py", line 206, in concat
copy=copy)
File "D:\Anaconda3\lib\site-packages\pandas\core\reshape\concat.py", line 239, in __init__
raise ValueError('No objects to concatenate')
ValueError: No objects to concatenate
一、self.tickers_data 是什麼內容?-->self.csv_dir=csv_dir,csv_dir在哪裏?-->
二、出錯位置在:backtest=TradingSession(config,strategy,tickers,initial_equity,start_date,end_date,events_queue,title=title) 爲實例化,
參數以下:
config --> TEST==munchify({"CSV_DATA_DIR":"data","OUTPUT_DIR":"out"})
strategy -->實例化,BuyAndHoldStrategy(tickers[0],events_queue)
tickers -->['SPY']
initial_equity -->10000
start_date -->datetime.datetime(2000,1,1)
end_date -->datetime.datetime(2014,1,1)
events_queue -->queue.Queue()
title -->['Buy and Hold Example on SPY']
進一步查看,錯誤地點在 qstrader\trading_session.py ,交易階段。爲何/如何 進入這一階段?
仍是要細心看代碼,在__init__下面有self._config_session(),啓動了該方法。功能是初始化回測期間必須類。
--> def _config_session(self): -->self.price_handler=YahooDailyCsvBarPriceHandler(self.config.CSV_DATA_DIR,self.events_queue,self.tickers,
start_date=self.start_date,end_date=self.end_date)
「」「設計去讀取csv文件、開-高-低-收-交易量(OHLCV),for each requested financial instrument and stream those to the provided events queue as BarEvents"""
-->self.subscribe_ticker(ticker) 訂閱ticker -->訂閱以前 self._open_ticker_price_csv(ticker)
-->self._merge_sort_ticker_data() 合併ticker
debug後,發現代碼錯誤位於讀取SPY.csv文件部分。代碼以下:
ticker_path=os.path.join(self.csv_dir,'%s.csv"%ticker)
self.tickers_data[ticker]=pd.io.parser.read_csv(ticker_path,....) ,好奇怪,這裏debug下一步,ticker_path沒有了。
進一步發現是路徑的問題,buy_and_hold_backtest.py是在examples路徑下面,即:
import os os.getcwd() Out[7]: 'E:\\qstrader-master\\examples'
而文件在路徑爲:'E:\qstrader-master\data',ticker_path='data\\SPY.csv'。
即文件讀取位置爲:'E:\qstrader-master\examples\data\SPY.csv' ,全部讀取不到文件,能夠以下修改:
1、能夠在buy_and_hold_backtest.py 文件中加入: import os os.chdir('E:\\qstrader-master')
2、在python console中運行buy_and_hold_backtest.py文件, run examples\buy_and_hold_backtest.py
run examples\buy_and_hold_backtest.py Backend Qt5Agg is interactive backend. Turning interactive mode on. ticker: SPY self.tickers: {} ticker_path: data\SPY.csv Running Backtest... --------------------------------- Backtest complete. Sharpe Ratio: 0.25 Max Drawdown: 79.59%
二、後面debug過程當中,循環的流程爲:
trading_session.py 只有一個類:TradingSession(object)
注意:在 __init__時,運行了self._config_session()
方法有4個:一、 _config_session(self): 二、_continue_loop_condition(self) 三、_run_session(self) 四、start_trading(self,testing=False)
在buy_and_hold_backtest.py中先TradingSession實例化,再運行start_trading方法,流程以下
self._config_session()---->
self.start_trading()----->self._run_session()---->self._continue_loop_condition
運行到方法三、_run_session(self)中
while self._continue_loop_condition():
try:
event=self.events_queue.get(False)
except queue.Empty:
self.price_handler.stream_next()
else:
if event is not None:
if (event.type==EventType.TICK or event.type==EventType.BAR): 。。。。
#dir(event) -->有6個屬性 action print_order quantity ticker type typename --->event.type=EventType.ORDER,不知足條件進入elif
elif event.type==EventType.ORDER:
self.execution_handler.execute_order(event)
---->
def _continue_loop_condition(self):
if self.session_type=='backtest':
return self.price_handler.continue_backtest
else:
return datetime.now()<self.end_session_time
查看可知:self.price_handler來自:qstrader.price_handler.yahoo_daily_csv_bar.YahooDailyCsvBarPriceHandler object
debug self.price_handler.continue_backtest 爲True,繼續,
-->
self.execution_handler.execute_order(event) 來自於:qstrader/execution_handler/ib_simulated.py
def execute_order(self,event):
if event.type==EventType.ORDER:
#obtain values from the OrderEvent
timestamp=self.price_handler.get_last_timestamp(event.ticker)
ticker=event.ticker
action=event.action
quantity=event.quantity
#obtain the fill price
if self.price_handler.istick():
bid,ask=self.price_handler.get_best_bid_ask(ticker)
fill_price=ask if event.action=="BOT" elst bid
else:
close_price=self.price_handler.get_last_close(ticker)
fill_price=close_price
#set a dummy exchange and calculate trade commission dummy:模擬交易
exchange='ARCA'
commission=self.calculate_ib_commission(quantity,fill_price)
#create the FillEvent and place on the events queue
fill_event=FillEvent(timestamp,ticker,action,quantity,exchange,fill_price,commission) #實例化FillEvent類
self.events_queue.put(fill_event)
if self.compliance is not None:
self.compliance.record_trade(fill_event)
---->
self.price_handler 來自於:qstrader/price_handler/base.py ,包含三個類:
一、AbstractPriceHandler(object) 類 二、AbstractTickPriceHandler(AbstractPriceHandler) 類 三、AbstractBarPriceHandler(AbstractPriceHandler)類
self.price_handler.get_last_timestamp()爲AbstractPriceHandler(object)類中方法,
def get_last_timestamp(self,ticker):
if ticker in self.tickers:
timestamp=self.tickers[ticker]["timestamp"]
return timestamp
self.price_handler.istick() 類:AbstractBarPriceHandler(AbstractPriceHandler) 方法:istick
def istick(self):
return False
def isbar(self):
return True
self.price_handler.get_last_close(self,ticker) 爲 AbstractBarPriceHandler(AbstractPriceHandler)類中的方法
def get_last_clost(self,ticker): #ticker:‘SPY’
if ticker in self.tickers: #self.tickers爲:{'SPY': {'close': 1454375000, 'adj_close': 1058253320, 'timestamp': Timestamp('2000-01-03 00:00:00')}}
close_price=self.tickers[ticker]['close'] #self.tickers[ticker]爲字典
return close_price #在ib_similated.py中獲得close_price
----->
ticker=event.ticker action=event.action event來自於: qstrader.event.OrderEvent 位置 qstrader/event.py
event.py內容以下:總共有7個類,一個基礎類Event。
from enum import Enum
EventType=Enum("EventType","TICK BAR SIGNAL ORDER FILL SENTIMENT")
一、Event(object): 二、TickEvent(Event) 三、BarEvent(Event) 四、SignalEvent(Event) 五、OrderEvent(Event) 六、FillEvent(Event) 七、SentimentEvent(Event)
fill_event=FillEvent(timestamp,ticker,action,quantity,exchange,fill_price,commission)
乾的活很少,就只是進入類,帶入屬性值
---->
self.calculate_ib_commission(quantity,fill_price) 來自於:execution_handler/ib_simulated.py
包含一個類:IBSimulatedExecutionHandler(AbstractExecutionHandler)
def __init__(self,events_queue,price_handler,compliance=None):
def calculate_ib_commission(self,quantity,fill_price):
commission=min(0.5*fill_price*quantity,max(1.0,0.005*quantity))
return PriceParser.parse(commission)
----->
self.compliance.record_trade(fill_event) 來自於:qstrader/compliance/example.py文件,只有一個類
from .base import AbstractCompliance 來自於:qstrader/compliance/base.py 沒啥內容
類:ExampleCompliance(AbstractCompliance) #
保存交易記錄的
三、debug查看運行流程
1、交易以前建立投資過清單。
run(config,testing,tickers,filename)---->
title/initial_equity/start_date/end_date/events_queue/strategy,加載策略類(MonthlyLiquidateRebalanceStrategy(tickers,events_queue)----->
tickers_invested:{'SPY': False, 'AGG': False},初始化投資過的股票代碼,----->
ticker_weights/position_sizer,加載頭寸數量類(LiquidateRebalancePositionSizer(ticker_weights))------->
qstrader/position_sizer/rebalance.py-----position_sizer-----獲得initial_order----------->
創建回測backtest=TradingSession(config,strategy,tickers,initial_equity,start_date,end_date,events_queue,position_sizer=position_sizer,title,
benchmark=tickers[0])--------->
開始回測 results=backtest.start_trading(testing=testing)------------->
前面工做都幹完了,如今開始回測,backtest是實例化,start_trading調用實例化的方法。------->
self._config_session(),初始化回測期間必須類,self.price_handler=YahooDailyCsvBarPriceHandler(self.config.CSV_DATA_DIR,self.events_queue,
self.tickers,start_date=self.start_date,end_date=self.end_date)----->
event=self.events_queue.get(False)得到隊列中的事件,每一個bar事件--------->
self.strategy.calculate_signals(event),加載策略,計算信號----->
self.portfolio_handler.update_portfolio_value(),更新組合價值,------>
self.statistics.update(event.time,self.portfolio_handler),統計更新------->
返回結果 return results
四、思考問題,複雜的看不太懂地方
1、event.py中EventType,EventType=Enum('EventTye','TICK BAR SIGNAL FILL')
TICK BAR 分別對應TICK BAR數據事件,SIGNAL FILL做用是什麼?各個事件運行的流程是怎樣的?
以buy_and_hold_backtest.py debug來看,事件流程以下:
---->
buy_and_hold_backtest.py 功能:calculate_signals(self,event)
signal=SignalEvent(self.ticker,'BOT',suggested_quantity=self.base_quantity)
self.events_queue.put(signal) -----------------------> trading_session.py self.portfolio_handler.on_signal(event)
portfolio_handler.py 功能:on_signal(self,signal_event)
#從單個信號事件中建立初始訂單列表
initial_order=self._create_order_from_signal(signal_event)
#從初始的訂單中設置買賣數量
sized_order=self.position_sizer.size_order(self.portfolio,initial_order)
#從總體風控角度從新修改或者消除訂單
order_events=self.risk_manager.refine_orders(self.portfolio,sized_order)
#把訂單放入事件隊列
self._place_orders_onto_queue(order_events)
2、如何複製一個簡易版本回測系統?
去除掉不須要部分,哪些是不須要的,須要看懂總體運行流程和各個部分功能。
一、去掉risk_manager 完成
二、去掉 ORDER事件,直接從SIGNAL事件到FILL事件
ORDER事件幹了什麼事情,一、order_event---->fill_event 二、self.compliance.record_trade(fill_event)
三、修改trading_session.py中的self.position_sizer文件,(1)、trading_session.py中self.position_sizer有點重複;(2)、產生信號的時候直接生成須要的order參數,tick、action、quantity、
如今問題是什麼呢?是帶入SIGNAL中的問題,一、有信號要交易,當即生成交易須要參數(代碼量比較大,能夠寫入策略模板)
這樣有個問題是在portfolio_handler.py中,在生成order_events時加載了一下self.position_sizer,已經被我刪除了。寫法以下:
order_events=self.position_sizer.size_order(self.portfolio,initial_order)
能夠在portfolio_handler.py 中on_signal中修改,帶入須要參數。
如今問題是,主策略中quantity數量爲0,又沒有加載position_sizer修改數量,
四、先分析portfolio.py、postition.py、tearsheet.py文件,瞭解各個文件功能。
portfolio.py要傳入兩個參數,price_handler和cash;price_handler也是portfolio_handler.py、trading_session.py參數,在trading_session.py中入參。
self.price_handler=YahooDailyCsvBarPriceHandler(self.config.CSV_DATA_DIR,self.events_quue,self.tickers,start_date=self.start_date,end_date=self.end_date)
參數傳遞過程:buy_and_hold_backtest.py initial_equity----> trading_session self.equity=PriceParser.parse(equity)---->portfolio_handler.py self.initial_cash=initial_cash ---->Portfolio.py self.init_cash=cash
portfolio.py 參數設置:
self.price_handler=price_handler self.init_cash=cash self.equity=cash self.cur_cash=cash self.position={} self.closed_positions=[] self.realised_pnl=0
五、修改掉乘以1000萬部分,看着好奇怪。
3、問題是:TradingSession類中的屬性(self.price_handler self.suggested_order self.position_size self.portfolio_handler self.compliance self.statistics)定義爲另外一個類的實例化對象,該屬性在實例化的過程當中,該如何輸入參數?爲何要這樣寫,有沒有別的寫法呢?
解答:本質上類時數據結構,實例化後的對象也是一個參數,因此是能夠的。它的主要做用是:在父級對象內直接使用子級對象的功能和參數。
子級對象實例化過程當中傳參能夠直接使用父級全局變量參數,子級對象實例化傳入的參數可爲另外一子級實例化的屬性,以下圖所示:self.portfolio_handler爲類PortfolioHandler實例化對象,爲trading_session的屬性之一,self.portfolio_handler又爲TearsheetStatistics實例化過程當中的參數。
4、修改
一、重寫position.py transact_shares函數
二、position中整除修改成除,在price_parser.py中保留兩位小數 44行 round(x,2)
將ib_simulated.py中commission設置爲0,print發現quantity很是大,中間有bug
解決:使用vnpy中數據調整函數,以下所示,複製到price_parser.py文件中,再修改一個地方:1、compliance文件中,record_trade方法下display,刪去參數4便可。
三、