• 从零开始实现一个量化回测系统(一)


    持续行动1期 41/100,“AI技术应用于量化投资研资”之可转债投资。

    今天是中秋节和教师节,祝大家中秋快乐,教师节快乐!

    今天开始一个主题——“从零实现一个量化回测系统”。

    暂时不用考虑接入实盘,所以我会把订单、交易所,执行器等很多模拟交易的细节省掉,连事件驱动都省略,直接按bar来做循环。

    这样代码看起来更加简洁,直奔核心而去,出了问题定位也相对容易。

    一句话,该简化的简化,而且我们会兼容qlib的数据格式(其实等同于pandas的dataframe)。

    01 账本account

    我们就以自己做投资的视角来看这件回测,假设我们有一个账本叫Account。

    Account有:

    初始资金init_cash=10万。

    维护当前还有多少现金余额:curr_cash。

    当前持仓curr_holding = {"茅台": 20000.0} ,注意是 股票代码:市值。

    cache_dates=['2010-01-01','2010-01-02'...]就是走过的bar的日期。

    cache_porfilio_mv=[100000,,,,]每天的总市值(含现金)

    class Account:
        def __init__(self, init_cash=100000.0):
            self.init_cash = init_cash
            self.curr_cash = self.init_cash
            self.curr_holding = defaultdict(float)  # 当前持仓{symbol:mv}
            self.cache_dates = []
            self.cache_portfolio_mv = []

    总共这5个变量就够了

    Account每天要做的事情:

    一、更新持仓 curr_holding的市值,按持仓更新每支股票的市值,这个好理解,市值mv*涨跌幅

    二、把总市值和日期cache起来。

    # 要更新一次df_bar及市值,再进行交易。
    def update_bar(self, date, df_bar):
    
        # 所有有持仓的,按收益率更新mv
        total_mv = 0.0
        # 当前已经持仓中标的,使用收盘后的收益率更新
        for s, mv in self.curr_holding.items():
            rate = 0.0
            # 这里不同市场,比如海外市场,可能不存在的,不存在变化率就是0.0, 即不变
            if s in df_bar.index:
                symbol_bar = df_bar.loc[s]
                rate = symbol_bar['rate']
    
            new_mv = mv * (1 + rate)
            self.curr_holding[s] = new_mv
            total_mv += new_mv
    
        self.cache_portfolio_mv.append(total_mv + self.curr_cash)
        self.cache_dates.append(date)

    02 引擎engine

    engine除了init_cash之外,需要一个datafeed。

    datafeed里可以得到所有股票池的df数据,然后取出排重后的交易日期列表,对列表进行逐个bar的循环:

    class Engine:
        def __init__(self, init_cash, datafeed):
            self.acc = Account(init_cash=init_cash)
            # 从feed得到df,并从index取出日期列表
            self.df = datafeed.get_all_df()
            self.dates = self.df.index.unique()
    
        def run(self):
            for index, date in enumerate(self.dates):
                self.step(index, date)
    
        def step(self, index, date):
            df_bar = self.df.loc[date]
            if type(df_bar) is pd.Series:
                df_bar = df_bar.to_frame().T
    
            df_bar.index = df_bar['code']
            df_bar.sort_index(ascending=True, inplace=True)
            self.acc.update_bar(date, df_bar)

    03 支持交易:

    想想,最简单的一个策略叫“买入并持有”,只有一个买入动作。

    买入可以按“比例”买,也可以按“市值”买。从真实交易的角度,就是要买多少钱。

    比例也可以换算成市值。

    def order_buy_mv(self, instrument, mv):
        logger.debug('买入{},mv:{}'.format(instrument, mv))
        if mv <= 0.0:
            logger.warning('mv需要>0')
            return
        if mv > self.curr_cash:
            logger.warning('资金不够,只能买:{}'.format(self.curr_cash))
            mv = self.curr_cash
        self.curr_holding[instrument] += mv
        self.curr_cash -= mv

    要买的时候,需要检查现金够不够,如果不够,有多少就买多少,有一些系统是如果不行,完全不买,这个看策略。

    然后更新curr_holding持仓市值,现金减少相应的市值。

    卖出的逻辑类似:

    def order_sell_mv(self, instrument, mv):
        logger.debug('卖出{},mv:{}'.format(instrument, mv))
        if mv <= 0.0:
            logger.warning('mv需要>0')
            return
        mv_holding = self.curr_holding.get(instrument, 0.0)
        if mv_holding == 0:
            logger.warning('当前未持仓,无法卖出')
            return
        else:
            if mv_holding < mv:
                logger.warning('实际持仓:{},不够:{}'.format(mv_holding, mv))
                mv = mv_holding
            self.curr_holding[instrument] -= mv
            self.curr_cash += mv

    卖出的市值必须为正,同时如果未持仓,无法卖出。

    如果仓位不够,只卖出当前持有的(也是清仓了)

    然后更新curr_holding和curr_cash即可。

    04 投资的可行性及意义

    要把一件事当成“长期”事业来做,我们要问两个问题,

    一是这件事行不行,如果逻辑上走通的概率低,但不值得当长线的事情来做;

    二是这件事的意义,能不能给我们带来满足感,人还是讲自我实现的,尤其是当遇到困难的时候。

    先说可行性,投资能不能当长线事情做。

    也许你会说,有很多人把投资当成事业或者职业啊。比如职业股评师,基金经理,私募基金管理人等等,甚至很多失业的大龄中年都“沦”为职业股民。

    但这么说我们心里还是没谱的,毕竟投资是一个概率游戏,西蒙斯花了十年时间才实现盈利,普通人能撑多久呢?

    真正让我相信这条路可以当作B计划,因为它可以同时走三条路,一是互联网产品平台模式,比如集思录,理杏仁,乌龟量化等;二是IP模式,如螺丝钉,力哥,甚至做可转债的阿秋;三才是投资本身。

    后者与前两者,如同淘金和送水。淘金具备不确定性,但送水具备稳定性。送水的同时自己同时可以研究淘金,一举多得。

    三者叠加起来,让投资这条路的方向变得清晰,不用再担心打井有没有水的问题。

    另外一个问题,是关于意义。

    尽管前途光明,但道路是曲折的。

    当道理曲折时,能不能坚持下去,除了内心相信能不能赢的必胜信心之外,还有就是对于这件事的认同

    金融有没有意义,做投资如果纯粹是为了赚钱,客观讲这条路并不好走,冒的险并不少。

    也许我们会说金融是为了资源跨时空的优化配置,投机有利于市场价格发现,这此理由高大上但不接地气。

    西蒙斯从知名数学家进入资本市场,他的信念就是像解决数学难题一样去看待资本市场的“预测”,之于他证明定理无异。都是为了发现这个世界未知的规律,“顺便”实现了财富自由,带给自己更多的精神自由。

    这一点给我不少启发。

    其实,我们不是刻意做投资,我们是为了认知这个世界,社会,你如何得到相应的反馈,投资和资本市场是一个好的角度。否则你就像出租车司机般,大谈特谈对这个世界的偏见,除了夸夸其谈,并不能带来任何效果。

    以投资的视角看待历史,现实,未来,去探索这个世界,“顺便”把投资做好。

    这就是一个“系统”,所以,不必以短期投资效果作为衡量标准,而是持续改进我们的认知系统。

    小结:

    你仔细看看,一个回测引擎的核心其实就干这几件事,你就以你交易账本的视角来看就好,别管什么order, event。

    就是买什么,买多少,卖什么,卖多少,每天根据市场涨跌更新一下市值。

    只是后续由“量化策略”来决定 买或卖以及仓位的动作。

    然后还有一个部分就是回测完成之后,做分析,比如风险收益特性,各种比率以及可视化等等。

    后面的篇幅再展开。

    投资是为了认知世界,投资可以做为一项长线的事业来积累和经营。

    pyalgotrade量化回测框架简单试用

    持续行动,做成一件件小事

    飞狐,科技公司CTO,用AI技术做量化投资;以投资视角观历史,解时事;专注个人成长与财富自由。

     

  • 相关阅读:
    vim指令
    c# 同步异步锁
    创建并启动华为HarmonyOS本地与远程模拟器及远程真机
    活久见:都 2203 年了,你还在使用 word 调试 API
    uniapp 放大中间图标
    springboot2.7.10升级到3.0.8版本
    Xshell7试用期过了,打开就显示评估期已过,想继续或者不能删除怎么办?详细说明解决步骤
    Maven 中引用其他项目jar包出现BOOT-INF问题
    Flutter release打包安卓闪退,但是ios正常,debug两者都正常
    python 服务器数据转发数据 跳板机?
  • 原文地址:https://blog.csdn.net/weixin_38175458/article/details/126795902