量化交易回測系統---RQalpha、qstrade學習筆記


一、RQalpha

github 地址  https://github.com/ricequant/rqalpha

1、運行test.py文件,顯示 No module named 'logbook.base'。

解決先卸載再安裝: pip uninstall logbook   pip install logbook

2、出現:RuntimeError: 請設置賬戶及初始資金。

解決:

二、Zipline

github地址  https://github.com/quantopian/zipline     Zipline學習資料      http://www.zipline.io/

zipline代碼比較多,不好復制

三、qstrade

github地址  https://github.com/mhallsmoore/qstrader

聽從朋友建議,暫時學習qstrade。代碼少,上手快。

測試:

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)

 

學習問題匯總:

1、運行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

1、self.tickers_data 是什么內容?-->self.csv_dir=csv_dir,csv_dir在哪里?-->

2、出錯位置在: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' ,所有讀取不到文件,可以如下修改:

一、可以在buy_and_hold_backtest.py 文件中加入: import os  os.chdir('E:\\qstrader-master')

二、在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%

 

2、后面debug過程中,循環的流程為:

trading_session.py  只有一個類:TradingSession(object)  

注意:在 __init__時,運行了self._config_session()

方法有4個:1、 _config_session(self):   2、_continue_loop_condition(self) 3、_run_session(self) 4、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

運行到方法3、_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 ,包含三個類:

1、AbstractPriceHandler(object) 類   2、AbstractTickPriceHandler(AbstractPriceHandler) 類    3、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")

1、Event(object): 2、TickEvent(Event) 3、BarEvent(Event) 4、SignalEvent(Event) 5、OrderEvent(Event) 6、FillEvent(Event) 7、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)   # 

保存交易記錄的

 

 3、debug查看運行流程

一、交易之前創建投資過清單。

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

 

 4、思考問題,復雜的看不太懂地方

一、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)

 

二、如何復制一個簡易版本回測系統?

 去除掉不需要部分,哪些是不需要的,需要看懂整體運行流程和各個部分功能。

1、去掉risk_manager   完成

2、去掉  ORDER事件,直接從SIGNAL事件到FILL事件

ORDER事件干了什么事情,1、order_event---->fill_event 2、self.compliance.record_trade(fill_event)

 3、修改trading_session.py中的self.position_sizer文件,(1)、trading_session.py中self.position_sizer有點重復;(2)、產生信號的時候直接生成需要的order參數,tick、action、quantity、

現在問題是什么呢?是帶入SIGNAL中的問題,1、有信號要交易,立即生成交易需要參數(代碼量比較大,可以寫入策略模板)

這樣有個問題是在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修改數量,

 

4、先分析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

 

5、修改掉乘以1000萬部分,看着好奇怪。

 

三、問題是: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實例化過程中的參數。

 

 

 

 四、修改

1、重寫position.py transact_shares函數

2、position中整除修改為除,在price_parser.py中保留兩位小數  44行 round(x,2)

將ib_simulated.py中commission設置為0,print發現quantity非常大,中間有bug

解決:使用vnpy中數據調整函數,如下所示,復制到price_parser.py文件中,再修改一個地方:一、compliance文件中,record_trade方法下display,刪去參數4即可。

 

3、

 

 

 

 

 

 

    


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM