欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

用Python徒手擼一個股票回測框架搭建【推薦】

 更新時間:2019年08月05日 13:07:42   作者:youerning  
回測框架就是提供這樣的一個平臺讓交易策略在歷史數(shù)據(jù)中不斷交易,最終生成最終結(jié)果,通過查看結(jié)果的策略收益,年化收益,最大回測等用以評估交易策略的可行性。這篇文章主要介紹了用Python徒手擼一個股票回測框架,需要的朋友可以參考下

通過純Python完成股票回測框架的搭建。

什么是回測框架?

無論是傳統(tǒng)股票交易還是量化交易,無法避免的一個問題是我們需要檢驗自己的交易策略是否可行,而最簡單的方式就是利用歷史數(shù)據(jù)檢驗交易策略,而回測框架就是提供這樣的一個平臺讓交易策略在歷史數(shù)據(jù)中不斷交易,最終生成最終結(jié)果,通過查看結(jié)果的策略收益,年化收益,最大回測等用以評估交易策略的可行性。

代碼地址在最后。

本項目并不是一個已完善的項目, 還在不斷的完善。

回測框架

回測框架應(yīng)該至少包含兩個部分, 回測類, 交易類.

回測類提供各種鉤子函數(shù),用于放置自己的交易邏輯,交易類用于模擬市場的交易平臺,這個類提供買入,賣出的方法。

代碼架構(gòu)

以自己的回測框架為例。主要包含下面兩個文件

backtest/
  backtest.py
  broker.py

backtest.py主要提供BackTest這個類用于提供回測框架,暴露以下鉤子函數(shù).

def initialize(self):
  """在回測開始前的初始化"""
  pass
 def before_on_tick(self, tick):
  pass
 def after_on_tick(self, tick):
  pass
 def before_trade(self, order):
  """在交易之前會調(diào)用此函數(shù)
  可以在此放置資金管理及風(fēng)險管理的代碼
  如果返回True就允許交易,否則放棄交易
  """
  return True
 def on_order_ok(self, order):
  """當訂單執(zhí)行成功后調(diào)用"""
  pass
 def on_order_timeout(self, order):
  """當訂單超時后調(diào)用"""
  pass
 def finish(self):
  """在回測結(jié)束后調(diào)用"""
  pass
 @abstractmethod
 def on_tick(self, bar):
  """
  回測實例必須實現(xiàn)的方法,并編寫自己的交易邏輯
  """
  pass

玩過量化平臺的回測框架或者開源框架應(yīng)該對這些鉤子函數(shù)不陌生,只是名字不一樣而已,大多數(shù)功能是一致的,除了on_tick.

之所以是on_tick而不是on_bar, 是因為我希望交易邏輯是一個一個時間點的參與交易,在這個時間點我可以獲取所有當前時間的所有股票以及之前的股票數(shù)據(jù),用于判斷是否交易,而不是一個時間點的一個一個股票參與交易邏輯。

而broker.py主要提供buy,sell兩個方法用于交易。

def buy(self, code, price, shares, ttl=-1):
  """
  限價提交買入訂單
  ---------
  Parameters:
   code:str
    股票代碼
   price:float or None
    最高可買入的價格, 如果為None則按市價買入
   shares:int
    買入股票數(shù)量
   ttl:int
    訂單允許存在的最大時間,默認為-1,永不超時
  ---------
  return:
   dict
    {
    "type": 訂單類型, "buy",
    "code": 股票代碼,
    "date": 提交日期,
    "ttl": 存活時間, 當ttl等于0時則超時,往后不會在執(zhí)行
    "shares": 目標股份數(shù)量,
    "price": 目標價格,
    "deal_lst": 交易成功的歷史數(shù)據(jù),如
     [{"price": 成交價格,
      "date": 成交時間,
      "commission": 交易手續(xù)費,
      "shares": 成交份額
     }]
    ""
   }
  """
  if price is None:
   stock_info = self.ctx.tick_data[code]
   price = stock_info[self.deal_price]
  order = {
   "type": "buy",
   "code": code,
   "date": self.ctx.now,
   "ttl": ttl,
   "shares": shares,
   "price": price,
   "deal_lst": []
  }
  self.submit(order)
  return order
 def sell(self, code, price, shares, ttl=-1):
  """
  限價提交賣出訂單
  ---------
  Parameters:
   code:str
    股票代碼
   price:float or None
    最低可賣出的價格, 如果為None則按市價賣出
   shares:int
    賣出股票數(shù)量
   ttl:int
    訂單允許存在的最大時間,默認為-1,永不超時
  ---------
  return:
   dict
    {
    "type": 訂單類型, "sell",
    "code": 股票代碼,
    "date": 提交日期,
    "ttl": 存活時間, 當ttl等于0時則超時,往后不會在執(zhí)行
    "shares": 目標股份數(shù)量,
    "price": 目標價格,
    "deal_lst": 交易成功的歷史數(shù)據(jù),如
     [{"open_price": 開倉價格,
      "close_price": 成交價格,
      "close_date": 成交時間,
      "open_date": 持倉時間,
      "commission": 交易手續(xù)費,
      "shares": 成交份額,
      "profit": 交易收益}]
    ""
   }
  """
  if code not in self.position:
   return
  if price is None:
   stock_info = self.ctx.tick_data[code]
   price = stock_info[self.deal_price]
  order = {
   "type": "sell",
   "code": code,
   "date": self.ctx.now,
   "ttl": ttl,
   "shares": shares,
   "price": price,
   "deal_lst": []
  }
  self.submit(order)
  return order

由于我很討厭抽象出太多類,抽象出太多類及方法,我怕我自己都忘記了,所以對于對象的選擇都是盡可能的使用常用的數(shù)據(jù)結(jié)構(gòu),如list, dict.

這里用一個dict代表一個訂單。

上面的這些方法保證了一個回測框架的基本交易邏輯,而回測的運行還需要一個調(diào)度器不斷的驅(qū)動這些方法,這里的調(diào)度器如下。

class Scheduler(object):
 """

    整個回測過程中的調(diào)度中心, 通過一個個時間刻度(tick)來驅(qū)動回測邏輯

    所有被調(diào)度的對象都會綁定一個叫做ctx的Context對象,由于共享整個回測過程中的所有關(guān)鍵數(shù)據(jù),
    可用變量包括:

        ctx.feed: {code1: pd.DataFrame, code2: pd.DataFrame}對象
        ctx.now: 循環(huán)所處時間
        ctx.tick_data: 循環(huán)所處時間的所有有報價的股票報價
        ctx.trade_cal: 交易日歷
        ctx.broker: Broker對象
        ctx.bt/ctx.backtest: Backtest對象

    可用方法:    

 ctx.get_hist
 """
 def __init__(self):
  """"""
  self.ctx = Context()
  self._pre_hook_lst = []
  self._post_hook_lst = []
  self._runner_lst = []
 def run(self):
  # runner指存在可調(diào)用的initialize, finish, run(tick)的對象
  runner_lst = list(chain(self._pre_hook_lst, self._runner_lst, self._post_hook_lst))
  # 循環(huán)開始前為broker, backtest, hook等實例綁定ctx對象及調(diào)用其initialize方法
  for runner in runner_lst:
   runner.ctx = self.ctx
   runner.initialize()
  # 創(chuàng)建交易日歷
  if "trade_cal" not in self.ctx:
   df = list(self.ctx.feed.values())[0]
   self.ctx["trade_cal"] = df.index
  # 通過遍歷交易日歷的時間依次調(diào)用runner
  # 首先調(diào)用所有pre-hook的run方法
  # 然后調(diào)用broker,backtest的run方法
  # 最后調(diào)用post-hook的run方法
  for tick in self.ctx.trade_cal:
   self.ctx.set_currnet_time(tick)
   for runner in runner_lst:
    runner.run(tick)
  # 循環(huán)結(jié)束后調(diào)用所有runner對象的finish方法
  for runner in runner_lst:
   runner.finish()

在Backtest類實例化的時候就會自動創(chuàng)建一個調(diào)度器對象,然后通過Backtest實例的start方法就能啟動調(diào)度器,而調(diào)度器會根據(jù)歷史數(shù)據(jù)的一個一個時間戳不斷驅(qū)動Backtest, Broker實例被調(diào)用。

為了處理不同實例之間的數(shù)據(jù)訪問隔離,所以通過一個將一個Context對象綁定到Backtest, Broker實例上,通過self.ctx訪問共享的數(shù)據(jù),共享的數(shù)據(jù)主要包括feed對象,即歷史數(shù)據(jù),一個數(shù)據(jù)結(jié)構(gòu)如下的字典對象。

{code1: pd.DataFrame, code2: pd.DataFrame}

而這個Context對象也綁定了Broker, Backtest的實例, 這就可以使得數(shù)據(jù)訪問接口統(tǒng)一,但是可能導(dǎo)致數(shù)據(jù)訪問混亂,這就要看策略者的使用了,這樣的一個好處就是減少了一堆代理方法,通過添加方法去訪問其他的對象的方法,真不嫌麻煩,那些人。

綁定及Context對象代碼如下:

class Context(UserDict):
 def __getattr__(self, key):
  # 讓調(diào)用這可以通過索引或者屬性引用皆可
  return self[key]
 def set_currnet_time(self, tick):
  self["now"] = tick
  tick_data = {}
  # 獲取當前所有有報價的股票報價
  for code, hist in self["feed"].items():
   df = hist[hist.index == tick]
   if len(df) == 1:
    tick_data[code] = df.iloc[-1]
  self["tick_data"] = tick_data
 def get_hist(self, code=None):
  """如果不指定code, 獲取截至到當前時間的所有股票的歷史數(shù)據(jù)"""
  if code is None:
   hist = {}
   for code, hist in self["feed"].items():
    hist[code] = hist[hist.index <= self.now]
  elif code in self.feed:
   return {code: self.feed[code]}
  return hist
class Scheduler(object):
 """

    整個回測過程中的調(diào)度中心, 通過一個個時間刻度(tick)來驅(qū)動回測邏輯

    所有被調(diào)度的對象都會綁定一個叫做ctx的Context對象,由于共享整個回測過程中的所有關(guān)鍵數(shù)據(jù),

    可用變量包括:

  ctx.feed: {code1: pd.DataFrame, code2: pd.DataFrame}對象
  ctx.now: 循環(huán)所處時間
  ctx.tick_data: 循環(huán)所處時間的所有有報價的股票報價
  ctx.trade_cal: 交易日歷
  ctx.broker: Broker對象
  ctx.bt/ctx.backtest: Backtest對象

    可用方法:

   

  ctx.get_hist
 """
 def __init__(self):
  """"""
  self.ctx = Context()
  self._pre_hook_lst = []
  self._post_hook_lst = []
  self._runner_lst = []
 def add_feed(self, feed):
  self.ctx["feed"] = feed
 def add_hook(self, hook, typ="post"):
  if typ == "post" and hook not in self._post_hook_lst:
   self._post_hook_lst.append(hook)
  elif typ == "pre" and hook not in self._pre_hook_lst:
   self._pre_hook_lst.append(hook)
 def add_broker(self, broker):
  self.ctx["broker"] = broker
 def add_backtest(self, backtest):
  self.ctx["backtest"] = backtest
  # 簡寫
  self.ctx["bt"] = backtest
 def add_runner(self, runner):
  if runner in self._runner_lst:
   return
  self._runner_lst.append(runner)

為了使得整個框架可擴展,回測框架中框架中抽象了一個Hook類,這個類可以在在每次回測框架調(diào)用前或者調(diào)用后被調(diào)用,這樣就可以加入一些處理邏輯,比如統(tǒng)計資產(chǎn)變化等。

這里創(chuàng)建了一個Stat的Hook對象,用于統(tǒng)計資產(chǎn)變化。

class Stat(Base):
  def __init__(self):
    self._date_hist = []
    self._cash_hist = []
    self._stk_val_hist = []
    self._ast_val_hist = []
    self._returns_hist = []

  def run(self, tick):
    self._date_hist.append(tick)
    self._cash_hist.append(self.ctx.broker.cash)
    self._stk_val_hist.append(self.ctx.broker.stock_value)
    self._ast_val_hist.append(self.ctx.broker.assets_value)

  @property
  def data(self):
    df = pd.DataFrame({"cash": self._cash_hist,
              "stock_value": self._stk_val_hist,
              "assets_value": self._ast_val_hist}, index=self._date_hist)
    df.index.name = "date"
    return df

而通過這些統(tǒng)計的數(shù)據(jù)就可以計算最大回撤年化率等。

def get_dropdown(self):
    high_val = -1
    low_val = None
    high_index = 0
    low_index = 0
    dropdown_lst = []
    dropdown_index_lst = []
    for idx, val in enumerate(self._ast_val_hist):
      if val >= high_val:
        if high_val == low_val or high_index >= low_index:
          high_val = low_val = val
          high_index = low_index = idx
          continue
        dropdown = (high_val - low_val) / high_val
        dropdown_lst.append(dropdown)
        dropdown_index_lst.append((high_index, low_index))
        high_val = low_val = val
        high_index = low_index = idx
      if low_val is None:
        low_val = val
        low_index = idx
      if val < low_val:
        low_val = val
        low_index = idx
    if low_index > high_index:
      dropdown = (high_val - low_val) / high_val
      dropdown_lst.append(dropdown)
      dropdown_index_lst.append((high_index, low_index))
    return dropdown_lst, dropdown_index_lst
  @property
  def max_dropdown(self):
    """最大回車率"""
    dropdown_lst, dropdown_index_lst = self.get_dropdown()
    if len(dropdown_lst) > 0:
      return max(dropdown_lst)
    else:
      return 0
  @property
  def annual_return(self):
    """
    年化收益率
    y = (v/c)^(D/T) - 1
    v: 最終價值
    c: 初始價值
    D: 有效投資時間(365)

        注: 雖然投資股票只有250天,但是持有股票后的非交易日也沒辦法投資到其他地方,所以這里我取365

        參考: https://wiki.mbalib.com/zh-tw/%E5%B9%B4%E5%8C%96%E6%94%B6%E7%9B%8A%E7%8E%87

    """
    D = 365
    c = self._ast_val_hist[0]
    v = self._ast_val_hist[-1]
    days = (self._date_hist[-1] - self._date_hist[0]).days
    ret = (v / c) ** (D / days) - 1
    return ret

至此一個筆者需要的回測框架形成了。

交易歷史數(shù)據(jù)

在回測框架中我并沒有集成各種獲取數(shù)據(jù)的方法,因為這并不是回測框架必須集成的部分,規(guī)定數(shù)據(jù)結(jié)構(gòu)就可以了,數(shù)據(jù)的獲取通過查看數(shù)據(jù)篇,

回測報告

回測報告我也放在了回測框架之外,這里寫了一個Plottter的對象用于繪制一些回測指標等。結(jié)果如下:


回測示例

下面是一個回測示例。

import json
from backtest import BackTest
from reporter import Plotter
class MyBackTest(BackTest):
  def initialize(self):
    self.info("initialize")
  def finish(self):
    self.info("finish")
  def on_tick(self, tick):
    tick_data = self.ctx["tick_data"]
    for code, hist in tick_data.items():
      if hist["ma10"] > 1.05 * hist["ma20"]:
        self.ctx.broker.buy(code, hist.close, 500, ttl=5)
      if hist["ma10"] < hist["ma20"] and code in self.ctx.broker.position:
        self.ctx.broker.sell(code, hist.close, 200, ttl=1)
if __name__ == '__main__':
  from utils import load_hist
  feed = {}
  for code, hist in load_hist("000002.SZ"):
    # hist = hist.iloc[:100]
    hist["ma10"] = hist.close.rolling(10).mean()
    hist["ma20"] = hist.close.rolling(20).mean()
    feed[code] = hist
  mytest = MyBackTest(feed)
  mytest.start()
  order_lst = mytest.ctx.broker.order_hist_lst
  with open("report/order_hist.json", "w") as wf:
    json.dump(order_lst, wf, indent=4, default=str)
  stats = mytest.stat
  stats.data.to_csv("report/stat.csv")
  print("策略收益: {:.3f}%".format(stats.total_returns * 100))
  print("最大回徹率: {:.3f}% ".format(stats.max_dropdown * 100))
  print("年化收益: {:.3f}% ".format(stats.annual_return * 100))
  print("夏普比率: {:.3f} ".format(stats.sharpe))
  plotter = Plotter(feed, stats, order_lst)
  plotter.report("report/report.png")

項目地址

https://github.com/youerning/stock_playground

總結(jié)

以上所述是小編給大家介紹的用Python徒手擼一個股票回測框架,希望對大家有所幫助,如果大家有任何疑問請給我留言,小編會及時回復(fù)大家的。在此也非常感謝大家對腳本之家網(wǎng)站的支持!
如果你覺得本文對你有幫助,歡迎轉(zhuǎn)載,煩請注明出處,謝謝!

相關(guān)文章

  • python中的腳本性能分析

    python中的腳本性能分析

    這篇文章主要介紹了python中的腳本性能分析,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2022-11-11
  • python實現(xiàn)在內(nèi)存中讀寫str和二進制數(shù)據(jù)代碼

    python實現(xiàn)在內(nèi)存中讀寫str和二進制數(shù)據(jù)代碼

    這篇文章主要介紹了python實現(xiàn)在內(nèi)存中讀寫str和二進制數(shù)據(jù)代碼,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2020-04-04
  • python利用dir函數(shù)查看類中所有成員函數(shù)示例代碼

    python利用dir函數(shù)查看類中所有成員函數(shù)示例代碼

    這篇文章主要給大家介紹了關(guān)于python如何利用dir函數(shù)查看類中所有成員函數(shù)的相關(guān)資料,文中通過示例代碼介紹的非常詳細,對大家學(xué)習(xí)或者使用python具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)下吧。
    2017-09-09
  • 使用PyCharm調(diào)試程序?qū)崿F(xiàn)過程

    使用PyCharm調(diào)試程序?qū)崿F(xiàn)過程

    這篇文章主要介紹了使用PyCharm調(diào)試程序?qū)崿F(xiàn)過程,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2022-11-11
  • Python生成器的使用方法和示例代碼

    Python生成器的使用方法和示例代碼

    今天小編就為大家分享一篇關(guān)于Python生成器的使用方法和示例代碼,小編覺得內(nèi)容挺不錯的,現(xiàn)在分享給大家,具有很好的參考價值,需要的朋友一起跟隨小編來看看吧
    2019-03-03
  • 最好的Python DateTime 庫之 Pendulum 長篇解析

    最好的Python DateTime 庫之 Pendulum 長篇解析

    datetime 模塊是 Python 中最重要的內(nèi)置模塊之一,它為實際編程問題提供許多開箱即用的解決方案,非常靈活和強大。例如,timedelta 是我最喜歡的工具之一
    2021-11-11
  • python3.6+selenium實現(xiàn)操作Frame中的頁面元素

    python3.6+selenium實現(xiàn)操作Frame中的頁面元素

    這篇文章主要為大家詳細介紹了python3.6+selenium實現(xiàn)操作Frame中的頁面元素,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2019-07-07
  • Python selenium 加載并保存QQ群成員,去除其群主、管理員信息的示例代碼

    Python selenium 加載并保存QQ群成員,去除其群主、管理員信息的示例代碼

    這篇文章主要介紹了Python selenium 加載并保存QQ群成員 去除其群主、管理員信息的示例代碼,本文通過實例代碼給大家介紹的非常詳細,對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友參考下吧
    2020-05-05
  • 基于Django框架的權(quán)限組件rbac實例講解

    基于Django框架的權(quán)限組件rbac實例講解

    今天小編就為大家分享一篇基于Django框架的權(quán)限組件rbac實例講解,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2019-08-08
  • python 命名規(guī)范知識點匯總

    python 命名規(guī)范知識點匯總

    這里給大家分享的是在python開發(fā)過程中需要注意的命名的規(guī)范的知識匯總,有需要的小伙伴可以查看下
    2020-02-02

最新評論