回测系统设计-行情数据接口

本文说明设计回测系统时,如何实现行情数据处理部分,包括接口基类、历史和实时tick 示例。

from __future__ import print_function
from abc import ABCMeta, abstractmethod 
import datetime
import threading  
import os, os.path
import numpy as np 
import pandas as pd
import akshare as ak
#from event import MarketEvent
1 数据抽象基类
# 定于数据处理的抽象基类
class DataHandler(object): 
    # ABCMeta 是 Python 的一个内置元类(metaclass),用于实现抽象基类(Abstract Base Classes, ABCs)。 
    #抽象基类是一种特殊的类,它不能被直接实例化,而是用来定义其他类应该具有的共同接口。
    __metaclass__ = ABCMeta
    
    @abstractmethod
    def get_latest_bar(self, symbol):
        """
        Returns the last bar updated.
        """
        raise NotImplementedError("Should implement get_latest_bar()")
        
    @abstractmethod
    def get_latest_bars(self, symbol, N=1):
        """
        Returns the last N bars updated.
        """
        raise NotImplementedError("Should implement get_latest_bars()")
        
    @abstractmethod
    def get_latest_bar_datetime(self, symbol):
        """
        Returns a Python datetime object for the last bar. """
        raise NotImplementedError("Should implement get_latest_bar_datetime()")
        
    @abstractmethod
    def get_latest_bar_value(self, symbol, val_type): 
        """
        Returns one of the Open, High, Low, Close, Volume or OI from the last bar.
        """
        raise NotImplementedError("Should implement get_latest_bar_value()")
        
    @abstractmethod
    def get_latest_bars_values(self, symbol, val_type, N=1):
        """
        Returns the last N bar values from the
        latest_symbol list, or N-k if less available. """
        raise NotImplementedError("Should implement get_latest_bars_values()")
        
    @abstractmethod
    def update_bars(self):
        """
         Pushes the latest bars to the bars_queue for each symbol
        in a tuple OHLCV format: (datetime, open, high, low,
        close, volume).
        """
   
        raise NotImplementedError("Should implement update_bars()")

2 从csv文件读取行情
# 继承抽象基类,从英为下载历史行情csv文件,加载到对象
# 比如平安银行:https://cn.investing.com/equities/ping-an-bank-a-historical-data
# 
class HistoricCSVDataHandler(DataHandler):
    """
    HistoricCSVDataHandler is designed to read CSV files 
    """
    def __init__(self, events, csv_dir, symbol_list):
        self.events = events
        self.csv_dir = csv_dir
        self.symbol_list = symbol_list
        self.symbol_data = {}
        self.latest_symbol_data = {}
        self.continue_backtest = True
        self._open_convert_csv_files()

    def _open_convert_csv_files(self): 
        """
        Opens the CSV files from the data directory, converting
        them into pandas DataFrames within a symbol dictionary.
        """
        comb_index = None
        for s in self.symbol_list:
            # Load the CSV file with no header information, indexed on date 
            quote_file = os.path.join(self.csv_dir, '%s.csv' %s)
            print("quote_file:", quote_file)
            
            # filepath:要读取的 CSV 文件的路径或类似文件的对象。
            # sep 或 delimiter:用于分隔字段的字符,默认为 ','。
            # header:用作列名的行号,默认为 0(第一行)。如果设置为 None,则不会使用任何行作为列名,列名将被命名为 0, 1, 2, ...。
            # index_col:用作行索引的列编号或列名。
            # usecols:要读取的列列表。
            # dtype:指定列的数据类型。
            # parse_dates:要解析为日期的列名列表。
#             self.symbol_data[s] = pd.read_csv(quote_file,
#                 header=0, index_col=0, parse_dates=True, skiprows = 1,
#                 names=[
#                   'datetime', 'close', 'open',
#                 'high', 'low', 'volume'])
            self.symbol_data[s] = pd.read_csv(quote_file,header = 0,index_col=0, skiprows = 1,
                                              names=['close', 'open','high', 'low', 'volume','rise'])
            #print(">>>>>>")
            
            # Combine the index to pad forward values
            if comb_index is None:
                comb_index = self.symbol_data[s].index
            else: 
                comb_index.union(self.symbol_data[s].index)
                # Set the latest symbol_data to None
            #print(self.symbol_data[s])
            self.latest_symbol_data[s] = pd.DataFrame()
        # Reindex the dataframes
       
        for s in self.symbol_list:
            # reindex是一个Pandas方法,用于调整DataFrame或Series的索引。
            # index=comb_index表示将DataFrame或Series的索引重新设置为comb_index。
            # method='pad'是一个填充方法,表示使用前一个有效值来填充缺失值。
            # iterrows 迭代处理调整后的每一行数据
            # [::-1] 反转索引
            self.symbol_data[s] = self.symbol_data[s].reindex(index=comb_index[::-1])
            #print(self.symbol_data[s])

        
    def _get_new_bar(self, symbol): 
        """
        Returns the latest bar from the data feed. 
        """
        if len(self.symbol_data[symbol]) > 0:
            return self.symbol_data[symbol]
        return None
            
    def get_latest_bar(self, symbol): 
        """
            Returns the last bar from the latest_symbol list.
        """
        try:
            bars_list = self.latest_symbol_data[symbol]
        except KeyError:
            print("That symbol is not available in the historical data set.") 
            raise
        else:
            #print(type(bars_list))
            #print(bars_list)
            return bars_list[-1:]
    
    def get_latest_bars(self, symbol, N=1): 
        """
        Returns the last N bars from the latest_symbol list,
        or N-k if less available. 
        """
        try:
            bars_list = self.latest_symbol_data[symbol] 
        except KeyError:
            print("That symbol is not available in the historical data set.")
            raise 
        else:
            return bars_list[-N:]

    def get_latest_bar_datetime(self, symbol): 
        """
        Returns a Python datetime object for the last bar. 
        """
        try:
            bars_list = self.latest_symbol_data[symbol].index
        except KeyError:
            print("That symbol is not available in the historical data set.") 
            raise
        else:
            return bars_list[-1]
        
    def get_latest_bar_value(self, symbol, val_type): 
        """
        Returns one of the Open, High, Low, Close, Volume or OI
        values from the pandas Bar series object.
        """
        try:
            bars_list = self.latest_symbol_data[symbol]
        except KeyError:
            print("That symbol is not available in the historical data set.")
            raise 
        else:
            return (bars_list.iloc[-1][val_type])
    
    def get_latest_bars_values(self, symbol, val_type, N=1): 
            """
            Returns the last N bar values from the latest_symbol list, or N-k if less available. 
            """
            try:
                bars_list = self.latest_symbol_data[symbol] 
            except KeyError:
                print("That symbol is not available in the historical data set.")
                raise 
            else:
                return bars_list.iloc[-N:][val_type].to_numpy()

    def update_bars(self): 
        """
            Pushes the latest bar to the latest_symbol_data structure for all symbols in the symbol list.
        """
        for s in self.symbol_list: 
            try:
                bar = self._get_new_bar(s)
            except StopIteration:
                self.continue_backtest = False 
            else:
                if bar is not None: 
                    # 添加新增行情到行情数据尾部
                    self.latest_symbol_data[s] = pd.concat([self.latest_symbol_data[s], bar], axis=0)
        # 发送行情通知事件
        #self.events.put(MarketEvent())
        

        
hisData = HistoricCSVDataHandler(None, "", ["000001","0700"])
hisData.update_bars()
print("获取最新的一条数据--->") 
print(hisData.get_latest_bar("0700"))
print("获取最新的3条数据--->") 
print(hisData.get_latest_bars("0700", 3))
print("获取最新日期--->") 
print(hisData.get_latest_bar_datetime("0700"))  # out : 2024-2-23

print("获取最新的收盘价--->") 
print(hisData.get_latest_bar_value("0700", "close")) # out : 290.8
 
print("获取最新的收盘价集合--->") 
close_np = hisData.get_latest_bars_values("0700", "close" , 3) # out : [288.8 291.4 290.8]
print(type(close_np))
print(close_np)
#print(hisData.get_latest_bar("0700"))
quote_file: ./000001.csv
quote_file: ./0700.csv
获取最新的一条数据--->
           close   open   high    low  volume    rise
2024-2-23  290.8  290.0  296.4  287.0  13.23M  -0.21%
获取最新的3条数据--->
           close   open   high    low  volume    rise
2024-2-21  288.8  280.8  294.6  280.8  23.95M   1.62%
2024-2-22  291.4  288.2  291.4  286.8  12.44M   0.90%
2024-2-23  290.8  290.0  296.4  287.0  13.23M  -0.21%
获取最新日期--->
2024-2-23
获取最新的收盘价--->
290.8
获取最新的收盘价集合--->
<class 'numpy.ndarray'>
[288.8 291.4 290.8]
3 从akshare 获取历史数据
# 继承抽象基类
# 
class HistoricCSVDataHandler(DataHandler):
    """
    HistoricCSVDataHandler is designed to read CSV files 
    """
    def __init__(self, events, market, symbol_list):
        self.events = events
        self.market = market
        self.symbol_list = symbol_list
        self.symbol_data = {}
        self.latest_symbol_data = {}
        self.continue_backtest = True
        self._get_akshare_data()
        
        
    def _get_akshare_data(self): 
        """
        Get data from akshare, converting
        them into pandas DataFrames within a symbol dictionary.
        """
        comb_index = None
        for s in self.symbol_list:
            self.symbol_data[s] = ak.futures_zh_daily_sina(s)           
            #print("----")
            #print(self.symbol_data[s])
            self.latest_symbol_data[s] = pd.DataFrame()
        # Reindex the dataframes
       
        for s in self.symbol_list:
            comb_index = self.symbol_data[s]['date']
            self.symbol_data[s] = self.symbol_data[s].set_index(comb_index)
            #print(self.symbol_data[s])

        
    def _get_new_bar(self, symbol): 
        """
        Returns the latest bar from the data feed. 
        """
        if len(self.symbol_data[symbol]) > 0:
            return self.symbol_data[symbol]
        return None
        
    def get_latest_bar(self, symbol): 
        """
            Returns the last bar from the latest_symbol list.
        """
        try:
            bars_list = self.latest_symbol_data[symbol]
        except KeyError:
            print("That symbol is not available in the historical data set.") 
            raise
        else:
            #print(type(bars_list))
            #print(bars_list)
            return bars_list[-1:]
    
    def get_latest_bars(self, symbol, N=1): 
        """
        Returns the last N bars from the latest_symbol list,
        or N-k if less available. 
        """
        try:
            bars_list = self.latest_symbol_data[symbol] 
        except KeyError:
            print("That symbol is not available in the historical data set.")
            raise 
        else:
            return bars_list[-N:]

    def get_latest_bar_datetime(self, symbol): 
        """
        Returns a Python datetime object for the last bar. 
        """
        try:
            bars_list = self.latest_symbol_data[symbol].index
        except KeyError:
            print("That symbol is not available in the historical data set.") 
            raise
        else:
            return bars_list[-1]
        
    def get_latest_bar_value(self, symbol, val_type): 
        """
        Returns one of the Open, High, Low, Close, Volume or OI
        values from the pandas Bar series object.
        """
        try:
            bars_list = self.latest_symbol_data[symbol]
        except KeyError:
            print("That symbol is not available in the historical data set.")
            raise 
        else:
            return (bars_list.iloc[-1][val_type])
    
    def get_latest_bars_values(self, symbol, val_type, N=1): 
            """
            Returns the last N bar values from the latest_symbol list, or N-k if less available. 
            """
            try:
                bars_list = self.latest_symbol_data[symbol] 
            except KeyError:
                print("That symbol is not available in the historical data set.")
                raise 
            else:
                return bars_list.iloc[-N:][val_type].to_numpy()

    def update_bars(self): 
        """
            Pushes the latest bar to the latest_symbol_data structure for all symbols in the symbol list.
        """
        for s in self.symbol_list: 
            try:
                bar = self._get_new_bar(s)
            except StopIteration:
                self.continue_backtest = False 
            else:
                if bar is not None: 
                    # 添加新增行情到行情数据尾部
                    self.latest_symbol_data[s] = pd.concat([self.latest_symbol_data[s], bar], axis=0)
        # 发送行情通知事件
        #self.events.put(MarketEvent())
        

        
hisData = HistoricCSVDataHandler(None, "", ["V2403", "P2403", "B2403", "M2403"])
hisData.update_bars()
print("获取最新的一条数据--->") 
print(hisData.get_latest_bar("V2403"))
print("获取最新的3条数据--->") 
print(hisData.get_latest_bars("V2403", 3))
print("获取最新日期--->") 
print(hisData.get_latest_bar_datetime("V2403"))  # out : 2024-2-23

print("获取最新的收盘价--->") 
print(hisData.get_latest_bar_value("V2403", "close")) # out : 290.8
 
print("获取最新的收盘价集合--->") 
close_np = hisData.get_latest_bars_values("V2403", "close" , 3) # out : [288.8 291.4 290.8]
print(type(close_np))
print(close_np)
#print(hisData.get_latest_bar("0700"))
获取最新的一条数据--->
                  date    open    high     low   close  volume   hold  settle
date                                                                         
2024-02-29  2024-02-29  5569.0  5606.0  5541.0  5600.0    5094  42171  5580.0
获取最新的3条数据--->
                  date    open    high     low   close  volume   hold  settle
date                                                                         
2024-02-27  2024-02-27  5537.0  5576.0  5528.0  5573.0    5525  42932  5557.0
2024-02-28  2024-02-28  5578.0  5610.0  5544.0  5560.0    5622  42949  5565.0
2024-02-29  2024-02-29  5569.0  5606.0  5541.0  5600.0    5094  42171  5580.0
获取最新日期--->
2024-02-29
获取最新的收盘价--->
5600.0
获取最新的收盘价集合--->
<class 'numpy.ndarray'>
[5573. 5560. 5600.]
4 模拟从akshare 实时获取TICK
# 继承抽象基类
# 
class TickAKShareDataHandler(DataHandler):
    """
    HistoricCSVDataHandler is designed to read CSV files 
    """
    def __init__(self, events, market, symbol_list):
        self.events = events
        self.market = market
        self.symbol_list = symbol_list
        self.symbol_data = {}
        self.latest_symbol_data = {}
        self.continue_backtest = True
        self._get_akshare_data()
        # 事件处理线程
        self._task_thread = threading.Thread(target = self._run)
        # 模拟tick 3秒请求一次
        self._timerSleep= 3 
        self._stop = True
        
        

    def _run(self):
        
        while not self._stop :
            time.sleep(self.timerSleep_)
            _get_akshare_data(self)
            update_bars(self)
        
    def _get_akshare_data(self): 
        """
        Get data from akshare, converting
        them into pandas DataFrames within a symbol dictionary.
        """
        comb_index = None
        for s in self.symbol_list:
            self.symbol_data[s] = ak.futures_zh_spot(s)           
            #print("----")
            #print(self.symbol_data[s])
            self.latest_symbol_data[s] = pd.DataFrame()
        # Reindex the dataframes
       
        for s in self.symbol_list:
            comb_index = self.symbol_data[s]['time']
            self.symbol_data[s] = self.symbol_data[s].set_index(comb_index)
            #print(self.symbol_data[s])

        
    def _get_new_bar(self, symbol): 
        """
        Returns the latest bar from the data feed. 
        """
        if len(self.symbol_data[symbol]) > 0:
            return self.symbol_data[symbol]
        return None
    
    def start(self):
        self._stop = False
        self._task_thread.start()
        
    def get_latest_bar(self, symbol): 
        """
            Returns the last bar from the latest_symbol list.
        """
        try:
            bars_list = self.latest_symbol_data[symbol]
        except KeyError:
            print("That symbol is not available in the historical data set.") 
            raise
        else:
            #print(type(bars_list))
            #print(bars_list)
            return bars_list[-1:]
    
    def get_latest_bars(self, symbol, N=1): 
        """
        Returns the last N bars from the latest_symbol list,
        or N-k if less available. 
        """
        try:
            bars_list = self.latest_symbol_data[symbol] 
        except KeyError:
            print("That symbol is not available in the historical data set.")
            raise 
        else:
            return bars_list[-N:]

    def get_latest_bar_datetime(self, symbol): 
        """
        Returns a Python datetime object for the last bar. 
        """
        try:
            bars_list = self.latest_symbol_data[symbol].index
        except KeyError:
            print("That symbol is not available in the historical data set.") 
            raise
        else:
            return bars_list[-1]
        
    def get_latest_bar_value(self, symbol, val_type): 
        """
        Returns one of the Open, High, Low, Close, Volume or OI
        values from the pandas Bar series object.
        """
        try:
            bars_list = self.latest_symbol_data[symbol]
        except KeyError:
            print("That symbol is not available in the historical data set.")
            raise 
        else:
            return (bars_list.iloc[-1][val_type])
    
    def get_latest_bars_values(self, symbol, val_type, N=1): 
            """
            Returns the last N bar values from the latest_symbol list, or N-k if less available. 
            """
            try:
                bars_list = self.latest_symbol_data[symbol] 
            except KeyError:
                print("That symbol is not available in the historical data set.")
                raise 
            else:
                return bars_list.iloc[-N:][val_type].to_numpy()

    def update_bars(self): 
        """
            Pushes the latest bar to the latest_symbol_data structure for all symbols in the symbol list.
        """
        for s in self.symbol_list: 
            try:
                bar = self._get_new_bar(s)
            except StopIteration:
                self.continue_backtest = False 
            else:
                if bar is not None: 
                    # 添加新增行情到行情数据尾部
                    self.latest_symbol_data[s] = pd.concat([self.latest_symbol_data[s], bar], axis=0)
        # 发送行情通知事件
        #self.events.put(MarketEvent())
        

        
hisData = TickAKShareDataHandler(None, "", ["V2403", "P2403", "B2403", "M2403"])
hisData.update_bars()
print("获取最新的一条数据--->") 
print(hisData.get_latest_bar("V2403"))
print("获取最新的3条数据--->") 
print(hisData.get_latest_bars("V2403", 3))
print("获取最新日期--->") 
print(hisData.get_latest_bar_datetime("V2403"))  # out : 2024-2-23

print("获取最新的收盘价--->") 
print(hisData.get_latest_bar_value("V2403", "last_close")) # out : 290.8
 
print("获取最新的收盘价集合--->") 
close_np = hisData.get_latest_bars_values("V2403", "last_close" , 3) # out : [288.8 291.4 290.8]
print(type(close_np))
print(close_np)
#print(hisData.get_latest_bar("0700"))
获取最新的一条数据--->
         symbol    time    open    high     low  current_price  bid_price  \
time                                                                        
150230  PVC2403  150230  5569.0  5606.0  5541.0         5600.0     5582.0   

        ask_price  buy_vol  sell_vol     hold  volume  avg_price  last_close  \
time                                                                           
150230     5606.0       10         2  42171.0    5094     5580.0      5600.0   

        last_settle_price  
time                       
150230             5565.0  
获取最新的3条数据--->
         symbol    time    open    high     low  current_price  bid_price  \
time                                                                        
150230  PVC2403  150230  5569.0  5606.0  5541.0         5600.0     5582.0   

        ask_price  buy_vol  sell_vol     hold  volume  avg_price  last_close  \
time                                                                           
150230     5606.0       10         2  42171.0    5094     5580.0      5600.0   

        last_settle_price  
time                       
150230             5565.0  
获取最新日期--->
150230
获取最新的收盘价--->
5600.0
获取最新的收盘价集合--->
<class 'numpy.ndarray'>
[5600.]
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容