金融量化--事件驱动的回测框架

矢量化方法回测相对于事件驱动回测优点在于直观与计算效率高, 但是对于复杂策略或者模拟真实交易情形时, 矢量方法复原能力较弱. 引入适当的回测框架可以实现代码在不同策略下的重复利用, 这也体现出了模块化编程的思想. 这里将回测框架大致区分为: 数据处理, 头寸管理, 交易指令, 策略四个部分, 用事件驱动产生交易信息. 

1. 结构化数据

class TickData:
    def __init__(self, date, close):
        self.date = date
        self.close = close

TickData对象, 用于记录日期(date), 收盘价(close). 这里将使用均值回归策略(MeanRevertingStrategy)进行回测, 仅需要记录日期和收盘价信息. 在使用更复杂策略回测时, 可以相应地改变数据结构. 

2. 数据获取, 清洗与模拟

class MarketDataSource:
    def __init__(self, symbol, start, end):
        self.symbol = symbol
        self.start = start
        self.end = end
        self.data = None



    def download_data(self):
        self.data = ts.get_k_data(self.symbol, self.start, self.end)

    def clean_data(self):
        self.data.index = pd.to_datetime(self.data.date)
        self.data = self.data.sort_index()

    def start_simulation(self, strategy, position):
        for date, row in self.data.iterrows():
            tick_data = TickData(date, row['close'])
            strategy.event_tick(tick_data)
            strategy.event_position(position)
            strategy.event_order()
            if strategy.order is not None:
                position.operate(strategy.order.date, strategy.order.is_buy, strategy.order.price, strategy.order.qty)

MarketDataSource对象记录了代码(symbol), 开始与结束时间(start, end). 在MarketDataSource中, 定义download_data和clean_data方法用于下载和清洗数据, 定义start_simulation方法对数据(DataFrame对象)进行逐行迭代, 产生价格信息(tick_data). 将价格信息传递给策略(strategy), 判断是否产生交易信号以及相应地改变头寸. 

3. 头寸管理

class Position:
    def __init__(self):
        self.buys = 0
        self.sells = 0
        self.net = 0
        self.cash = 0
        self.net_value = 0



    def operate(self, date, is_buy, price, qty):
        if is_buy:
            self.buys += qty
            print('Date: {}, Trade: Buy, Price: {}, Qty: {}'.format(date.strftime('%Y-%m-%d'), price, qty))
        else:
            self.sells += qty
            print('Date: {}, Trade: Sell, Price: {}, Qty: {}'.format(date.strftime('%Y-%m-%d'), price, qty))
        self.net = self.buys - self.sells
        self.cash += price * qty * (-1 if is_buy else 1)
        self.net_value = self.cash + self.net * price

    def display(self):
        print('Position Info: Cash: {}, Net: {}, Net Value: {}'. format(self.cash, self.net, self.net_value))

Position对象记录头寸相关信息. operate方法用于计算每次交易时现金(cash), 证券资产(net), 策略净值(net_value)变化情况, display方法用于打印出头寸中现金, 证券资产和净值信息. 

4. 交易指令

class Order:
    def __init__(self, date, is_buy, price, qty):
        self.date = date
        self.is_buy = is_buy
        self.price = price
        self.qty = qty

5. 均值回归策略

class MeanRevertingStrategy:
    def __init__(self, cycle=20, buy_threshold=-1.5, sell_threshold=1.3):
        self.cycle = cycle
        self.buy_threshold = buy_threshold
        self.sell_threshold = sell_threshold
        self.prices = pd.DataFrame()
        self.is_long = False
        self.is_short = False
        self.order = None



    def event_tick(self, tick_data):
        self.prices.loc[tick_data.date, 'close'] = tick_data.close

    def event_position(self, position):
        if position.net > 0:
            self.is_long, self.is_short = True, False
        elif position.net < 0:
            self.is_long, self.is_short = False, True
        else:
            self.is_long = self.is_short = False

    def event_order(self):
        if len(self.prices) < self.cycle:
            self.order = None
        else:
            z = self.calculate_z_score()
            if self.is_long is False and z < self.buy_threshold:
                self.order = Order(self.prices.index[-1], is_buy=True, price=self.prices.close[-1], qty=100)
            elif self.is_short is False and z > self.sell_threshold:
                self.order = Order(self.prices.index[-1], is_buy=False, price=self.prices.close[-1], qty=100)
            else:
                self.order = None



    def calculate_z_score(self):
        window = self.prices[-self.cycle:]
        ret = window['close'].pct_change().dropna()
        z = ((ret[-1]) - ret.mean()) / ret.std()
        return z

初始化MeanRevertingStrategy需要给定计算均值周期(cycle), 买卖信号产生的阈值(buy_threshold, sell_threshold). event_tick, event_position方法分别用于接收MarketDataSource.start_simulation迭代产生的价格数据和头寸信息, event_order方法判断是否产生交易指令(order), 并将order记录为策略的属性值. 

6. 回测对象

class BackTester:
    def __init__(self, symbol, start, end):
        self.symbol = symbol
        self.start = start
        self.end = end
        self.position = None
        self.strategy = None



    def check_position(self):
        if self.position is None:
            self.position = Position()
        else:
            return None



    def start_back_test(self):
        market_data_source = MarketDataSource(self.symbol, self.start, self.end)
        market_data_source.download_data()
        market_data_source.clean_data()
        print('Start Simulation: ')
        market_data_source.start_simulation(self.strategy, self.position)
        self.position.display()
        print('Completed. ')

建立回测对象的实例需要初始化代码, 起止时间. check_position方法用于检测当前回测对象的实例是否已经存在头寸. start_back_test方法整合了MarketDataSource中的方法, 产生回测信息. 

7. 回测

back_tester = BackTester('600000', '2019-01-01', '2020-01-01')
back_tester.check_position()
back_tester.strategy = MeanRevertingStrategy()
back_tester.start_back_test()

下面是回测结果. 打印出了交易时间, 买卖方向, 价格和数量等信息. 

尾记: 面对对象的编程相对于矢量化编程更加抽象, 因为在实例化之前要先赋予类属性和方法, 要求对需要实现的功能, 特征具有全面的理解. 在参照了他人写的非常优秀, 功能强大(花里胡哨)的回测框架后, 拆拆补补写出了一个简单的回测, 这有种拆了法拉利的车轮安在了夏利车上的感觉. 当然好的回测框架应该具有强大的可再用性. 从最基础的框架出发, 下面的任务就是怎么一步步去实现更多的功能. 最后不管怎么样, 小破车也要上路了. 

拓展阅读:

1.一个量化策略师的自白(好文强烈推荐)

2.市面上经典的量化交易策略都在这里了!(源码)

3.期货/股票数据大全查询(历史/实时/Tick/财务等)

4.干货| 量化金融经典理论、重要模型、发展简史大全

5.从量化到高频交易,不可不读的五本书

6.高频交易四大派系大揭秘

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值