想”以交易为生“,基础设施很重要。

想”以交易为生“,基础设施很重要。

现在开源框架比较多,我几乎读过市面上所有开源框架的代码,了解其设计理念与使用细节。

从选型上讲,兼容实盘与回测,尤其是策略可以”一键切换到实盘“,这个很重要。另外一点也很关键,就是开发策略效率要高

传统从实盘起家的框架,往往暴露很多实盘细节,让新手不知所措。而很多量化框架,只关心回测,忽略了上实盘的诉求。

兼容二者而言,目前个人偏向的选型是backtrader。

当然,我会整合qlib的因子表达式,以及它的topK因子轮动算法。

这样就兼顾了传统规则量化、机器学习多因子智能量化,回测效率与实盘

我们来看下如何封装:

首先,继承bt.strategy——这是一个常规的封装,notify_order, notify_trade这样的”模板“代码。后续传统的backtrader策略,直接继承StrategyBase即可。

class StrategyBase(bt.Strategy):
def log(self, txt, dt=None):
dt = dt or self.datas[0].datetime.date(0)
logger.info('%s, %s' % (dt.isoformat(), txt))

# 取当前的日期 def get_current_dt(self): # print(self.datas[0].datetime) dt = self.datas[0].datetime.date(0).strftime('%Y-%m-%d') # print(dt) return dt # 取当前持仓的data列表 def get_current_holding_datas(self): holdings = [] for data in self.datas: if self.getposition(data).size > 0: holdings.append(data) return holdings # 打印订单日志 def notify_order(self, order): order_status = ['Created', 'Submitted', 'Accepted', 'Partial', 'Completed', 'Canceled', 'Expired', 'Margin', 'Rejected'] # 未被处理的订单 if order.status in [order.Submitted, order.Accepted]: return self.log('未处理订单:订单号:%.0f, 标的: %s, 状态状态: %s' % (order.ref, order.data._name, order_status[order.status])) return # 已经处理的订单 if order.status in [order.Partial, order.Completed]: if order.isbuy(): self.log( 'BUY EXECUTED, 状态: %s, 订单号:%.0f, 标的: %s, 数量: %.2f, 价格: %.2f, 成本: %.2f, 手续费 %.2f' % (order_status[order.status], # 订单状态 order.ref, # 订单编号 order.data._name, # 股票名称 order.executed.size, # 成交量 order.executed.price, # 成交价 order.executed.value, # 成交额 order.executed.comm)) # 佣金 else: # Sell self.log( 'SELL EXECUTED, status: %s, ref:%.0f, name: %s, Size: %.2f, Price: %.2f, Cost: %.2f, Comm %.2f' % (order_status[order.status], order.ref, order.data._name, order.executed.size, order.executed.price, order.executed.value, order.executed.comm)) elif order.status in [order.Canceled, order.Margin, order.Rejected, order.Expired]: # order.Margin资金不足,订单无法成交 # 订单未完成 self.log('未完成订单,订单号:%.0f, 标的 : %s, 订单状态: %s' % ( order.ref, order.data._name, order_status[order.status])) self.order = None def notify_trade(self, trade):

如果要实现一个”买入并持有“的策略:

class StrategyBuyAndHold(StrategyBase):
    def __init__(self):
        pass

    def next(self):
        if self.position:
            return
        print('全仓买入')
        self.order_target_percent(self.data, 0.99)

但这是一支股票的情况,如果是多支,就需要 for data in self.datas,然后,循环判断,position为空,然后计算各自权重,再order_target_percent,这样进行调仓——代码繁琐,且容易出错!

我们来实现一个通用的”算子“策略:

class StrategyAlgo(StrategyBase):
    def __init__(self, algo_list, engine):
        self.now = None
        self.df_bar = None
        self.algos = algo_list
        self.df_data = engine.df_data
        self.temp = {}
        self.perm = {}
        self.index = -1
        self.dates = list(self.df_data.index.unique())

    def next(self):
        self.index += 1
        self.now = self.dates[self.index]

        self.df_bar = self.df_data.loc[self.now]
        if type(self.df_bar) is pd.Series:
            self.df_bar = self.df_bar.to_frame().T
        self.df_bar.set_index('symbol', inplace=True)

        for algo in self.algos:
            if algo(self) is False:  # 如果algo返回False,直接不运行
                return

其实就是实现了bt.Strategy的next函数,这里会将当前的一些变量,比如index, now ,df_bar(类似很多线上平台的onbar函数传入的当前的bar,这里不需要一个个取因子,我们都预计算好),然后调用”算子“列表,完成策略。

这些代码在如下位置:

图片

同样买入并持有策略——定义algo_list即可:

RunOnce——只运行一次;

SelectAll——选择所有股票;

就是把df_bar的index——当前有bar,也就是有数据的symbols都包含进来。

class SelectAll(Algo):
    def __call__(self, target):
        target.temp["selected"] = list(target.df_bar.index)
        return True

WeightEqually——等权分配;

按Selected数组,平均分配权重:

class WeightEqually(Algo):
    def __init__(self):
        super(WeightEqually, self).__init__()

    def __call__(self, target):
        selected = target.temp["selected"]
        n = len(selected)

        if n == 0:
            target.temp["weights"] = {}
        else:
            w = 1.0 / n
            target.temp["weights"] = {x: w for x in selected}
        return True

Rebalance——调仓。

class ReBalance(Algo):

    def __init__(self):
        super(ReBalance, self).__init__()

    def __call__(self, target):
        if "weights" not in target.temp:
            return True

        targets = target.temp["weights"]
        if type(targets) is pd.Series:
            targets = targets.to_dict()

        for data, w in targets.items():
            target.order_target_percent(data, w*0.99)

        return True

拆来来看,Algo都非常简单:

engine.run_algo_strategy(algo_list=[
    RunOnce(),
    SelectAll(),
    WeightEqually(),
    Rebalance()
])

如果想开发因子轮动策略呢?加一个Algo即可,而且代码是完全可复用的:

SelectTopK:

class SelectTopK(Algo):
    def __init__(self, factor_name='order_by', K=1, drop_top_n=0, b_ascending=False):
        self.K = K
        self.drop_top_n = drop_top_n  # 这算是一个魔改,就是把最强的N个弃掉,尤其动量指标,过尤不及。
        self.factor_name = factor_name
        self.b_ascending = b_ascending

    def __call__(self, target):

        selected = None
        key = 'selected'
        if key in target.temp.keys():
            selected = target['temp'][key]

        df_bar = target.bar_df
        factor_sorted = df_bar.sort_values(by=self.factor_name, ascending=self.b_ascending)

        symbols = factor_sorted.index
        if not selected:
            start = 0
            if self.drop_top_n <= len(symbols):
                start = self.drop_top_n
                ordered = symbols[start: start + self.K]
            else:
                ordered = []
        else:
            ordered = []
            count = 0
            for s in symbols:  # 一定是当天有记录的
                if s in selected:  # 已经选择的symbols
                    count += 1
                    if count >= self.drop_top_n:
                        ordered.append(s)

                    if len(ordered) >= self.K:
                        break

        target.temp[key] = ordered

        return True

代码解读:

按order_by指定列,默认从大到小排序。如果前面的Algo有设置Selected,那么在Selected的基本上选择,否则在df_bar.index里选择,drop掉前N个,取之后的K个,这就是因子轮动的逻辑。

这里的因子可以由线性模型来合成,也可以机器学习来预测,总之会生成一个最终的排序列。

吾日三省吾身

最近刷的几本投资类的书。

埃尔德的《以交易为生》系列,还有欧尼斯特.陈的《算法交易》系列。

这两套书很有代表性。

埃尔德是传统主观技术派操盘手的代表,但它的理念还是比较好的,没有把技术分析说成玄学,而是秉持客观、科学、系统的角度讲。

埃尔德认为:心理,技术分析,资金管理,风险管理以及交易记录是非常重要的。——其实多数理念,在量化交易里同时适用。

只是量化里,心理因素可能不需要提到如此高的位置。

埃尔德提出的“三重滤网”,“动量系统”,也许现在未心能直接赚钱,但其背后的理念是值得学习和借鉴的。

还有他的2%的单笔最大损失风控,以及每月6%和最大损失风控,都给新手很好的指导理念。

当然埃尔德对于技术,或者说AI了解有限,因此,他只提及了“黑盒”,“灰盒”和“白盒”。白盒就是技术工具箱,这个是肯定需要的,灰盒可以交给用户自己配置及参数优化;完全黑盒肯定不可取,交易没有一劳永逸的事情。

反观欧尼斯特.陈。本身就物理学博士,有自己的量化资管公司。因此,他对于量化系统设计,多因子模型,时间序列分析,机器学习等做了系统的阐述。

不过,最令人印象深刻的还是最后一章——算法交易有助手身心健康。

交易是一个集数学、计算机科学和经济学的交叉学科,给我们可以一生做创造性研究的机会。——而且不需要太多人的协作或许可。

——你可能不需要上司,或者同事,不需要看别人脸色,不需要处理人情世故。

——专心做好你的创造性研究,然后行走江湖,忘情山水。

想”以交易为生“,基础设施很重要。

现在开源框架比较多,我几乎读过市面上所有开源框架的代码,了解其设计理念与使用细节。

从选型上讲,兼容实盘与回测,尤其是策略可以”一键切换到实盘“,这个很重要。另外一点也很关键,就是开发策略效率要高

传统从实盘起家的框架,往往暴露很多实盘细节,让新手不知所措。而很多量化框架,只关心回测,忽略了上实盘的诉求。

兼容二者而言,目前个人偏向的选型是backtrader。

当然,我会整合qlib的因子表达式,以及它的topK因子轮动算法。

这样就兼顾了传统规则量化、机器学习多因子智能量化,回测效率与实盘

我们来看下如何封装:

首先,继承bt.strategy——这是一个常规的封装,notify_order, notify_trade这样的”模板“代码。后续传统的backtrader策略,直接继承StrategyBase即可。

class StrategyBase(bt.Strategy):
def log(self, txt, dt=None):
dt = dt or self.datas[0].datetime.date(0)
logger.info('%s, %s' % (dt.isoformat(), txt))

# 取当前的日期 def get_current_dt(self): # print(self.datas[0].datetime) dt = self.datas[0].datetime.date(0).strftime('%Y-%m-%d') # print(dt) return dt # 取当前持仓的data列表 def get_current_holding_datas(self): holdings = [] for data in self.datas: if self.getposition(data).size > 0: holdings.append(data) return holdings # 打印订单日志 def notify_order(self, order): order_status = ['Created', 'Submitted', 'Accepted', 'Partial', 'Completed', 'Canceled', 'Expired', 'Margin', 'Rejected'] # 未被处理的订单 if order.status in [order.Submitted, order.Accepted]: return self.log('未处理订单:订单号:%.0f, 标的: %s, 状态状态: %s' % (order.ref, order.data._name, order_status[order.status])) return # 已经处理的订单 if order.status in [order.Partial, order.Completed]: if order.isbuy(): self.log( 'BUY EXECUTED, 状态: %s, 订单号:%.0f, 标的: %s, 数量: %.2f, 价格: %.2f, 成本: %.2f, 手续费 %.2f' % (order_status[order.status], # 订单状态 order.ref, # 订单编号 order.data._name, # 股票名称 order.executed.size, # 成交量 order.executed.price, # 成交价 order.executed.value, # 成交额 order.executed.comm)) # 佣金 else: # Sell self.log( 'SELL EXECUTED, status: %s, ref:%.0f, name: %s, Size: %.2f, Price: %.2f, Cost: %.2f, Comm %.2f' % (order_status[order.status], order.ref, order.data._name, order.executed.size, order.executed.price, order.executed.value, order.executed.comm)) elif order.status in [order.Canceled, order.Margin, order.Rejected, order.Expired]: # order.Margin资金不足,订单无法成交 # 订单未完成 self.log('未完成订单,订单号:%.0f, 标的 : %s, 订单状态: %s' % ( order.ref, order.data._name, order_status[order.status])) self.order = None def notify_trade(self, trade):

如果要实现一个”买入并持有“的策略:

class StrategyBuyAndHold(StrategyBase):
    def __init__(self):
        pass

    def next(self):
        if self.position:
            return
        print('全仓买入')
        self.order_target_percent(self.data, 0.99)

但这是一支股票的情况,如果是多支,就需要 for data in self.datas,然后,循环判断,position为空,然后计算各自权重,再order_target_percent,这样进行调仓——代码繁琐,且容易出错!

我们来实现一个通用的”算子“策略:

class StrategyAlgo(StrategyBase):
    def __init__(self, algo_list, engine):
        self.now = None
        self.df_bar = None
        self.algos = algo_list
        self.df_data = engine.df_data
        self.temp = {}
        self.perm = {}
        self.index = -1
        self.dates = list(self.df_data.index.unique())

    def next(self):
        self.index += 1
        self.now = self.dates[self.index]

        self.df_bar = self.df_data.loc[self.now]
        if type(self.df_bar) is pd.Series:
            self.df_bar = self.df_bar.to_frame().T
        self.df_bar.set_index('symbol', inplace=True)

        for algo in self.algos:
            if algo(self) is False:  # 如果algo返回False,直接不运行
                return

其实就是实现了bt.Strategy的next函数,这里会将当前的一些变量,比如index, now ,df_bar(类似很多线上平台的onbar函数传入的当前的bar,这里不需要一个个取因子,我们都预计算好),然后调用”算子“列表,完成策略。

这些代码在如下位置:

图片

同样买入并持有策略——定义algo_list即可:

RunOnce——只运行一次;

SelectAll——选择所有股票;

就是把df_bar的index——当前有bar,也就是有数据的symbols都包含进来。

class SelectAll(Algo):
    def __call__(self, target):
        target.temp["selected"] = list(target.df_bar.index)
        return True

WeightEqually——等权分配;

按Selected数组,平均分配权重:

class WeightEqually(Algo):
    def __init__(self):
        super(WeightEqually, self).__init__()

    def __call__(self, target):
        selected = target.temp["selected"]
        n = len(selected)

        if n == 0:
            target.temp["weights"] = {}
        else:
            w = 1.0 / n
            target.temp["weights"] = {x: w for x in selected}
        return True

Rebalance——调仓。

class ReBalance(Algo):

    def __init__(self):
        super(ReBalance, self).__init__()

    def __call__(self, target):
        if "weights" not in target.temp:
            return True

        targets = target.temp["weights"]
        if type(targets) is pd.Series:
            targets = targets.to_dict()

        for data, w in targets.items():
            target.order_target_percent(data, w*0.99)

        return True

拆来来看,Algo都非常简单:

engine.run_algo_strategy(algo_list=[
    RunOnce(),
    SelectAll(),
    WeightEqually(),
    Rebalance()
])

如果想开发因子轮动策略呢?加一个Algo即可,而且代码是完全可复用的:

SelectTopK:

class SelectTopK(Algo):
    def __init__(self, factor_name='order_by', K=1, drop_top_n=0, b_ascending=False):
        self.K = K
        self.drop_top_n = drop_top_n  # 这算是一个魔改,就是把最强的N个弃掉,尤其动量指标,过尤不及。
        self.factor_name = factor_name
        self.b_ascending = b_ascending

    def __call__(self, target):

        selected = None
        key = 'selected'
        if key in target.temp.keys():
            selected = target['temp'][key]

        df_bar = target.bar_df
        factor_sorted = df_bar.sort_values(by=self.factor_name, ascending=self.b_ascending)

        symbols = factor_sorted.index
        if not selected:
            start = 0
            if self.drop_top_n <= len(symbols):
                start = self.drop_top_n
                ordered = symbols[start: start + self.K]
            else:
                ordered = []
        else:
            ordered = []
            count = 0
            for s in symbols:  # 一定是当天有记录的
                if s in selected:  # 已经选择的symbols
                    count += 1
                    if count >= self.drop_top_n:
                        ordered.append(s)

                    if len(ordered) >= self.K:
                        break

        target.temp[key] = ordered

        return True

代码解读:

按order_by指定列,默认从大到小排序。如果前面的Algo有设置Selected,那么在Selected的基本上选择,否则在df_bar.index里选择,drop掉前N个,取之后的K个,这就是因子轮动的逻辑。

这里的因子可以由线性模型来合成,也可以机器学习来预测,总之会生成一个最终的排序列。

吾日三省吾身

最近刷的几本投资类的书。

埃尔德的《以交易为生》系列,还有欧尼斯特.陈的《算法交易》系列。

这两套书很有代表性。

埃尔德是传统主观技术派操盘手的代表,但它的理念还是比较好的,没有把技术分析说成玄学,而是秉持客观、科学、系统的角度讲。

埃尔德认为:心理,技术分析,资金管理,风险管理以及交易记录是非常重要的。——其实多数理念,在量化交易里同时适用。

只是量化里,心理因素可能不需要提到如此高的位置。

埃尔德提出的“三重滤网”,“动量系统”,也许现在未心能直接赚钱,但其背后的理念是值得学习和借鉴的。

还有他的2%的单笔最大损失风控,以及每月6%和最大损失风控,都给新手很好的指导理念。

当然埃尔德对于技术,或者说AI了解有限,因此,他只提及了“黑盒”,“灰盒”和“白盒”。白盒就是技术工具箱,这个是肯定需要的,灰盒可以交给用户自己配置及参数优化;完全黑盒肯定不可取,交易没有一劳永逸的事情。

反观欧尼斯特.陈。本身就物理学博士,有自己的量化资管公司。因此,他对于量化系统设计,多因子模型,时间序列分析,机器学习等做了系统的阐述。

不过,最令人印象深刻的还是最后一章——算法交易有助手身心健康。

交易是一个集数学、计算机科学和经济学的交叉学科,给我们可以一生做创造性研究的机会。——而且不需要太多人的协作或许可。

——你可能不需要上司,或者同事,不需要看别人脸色,不需要处理人情世故。

——专心做好你的创造性研究,然后行走江湖,忘情山水。

发布者:股市刺客,转载请注明出处:https://www.95sca.cn/archives/103191
站内所有文章皆来自网络转载或读者投稿,请勿用于商业用途。如有侵权、不妥之处,请联系站长并出示版权证明以便删除。敬请谅解!

(0)
股市刺客的头像股市刺客
上一篇 2024 年 7 月 26 日
下一篇 2024 年 7 月 26 日

相关推荐

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注