继续讲因子挖掘:目前有三个方向:
一、“传统的” 以gplearn为代表的遗传算法;
二、强化学习驱动的深度学习框架;
三是gpt驱动的LLM生成因子框架。
这三类咱们都写过代码,不过接下来要做的事情,需要把三者整合到一个统一的框架下,并且与Quantlab回测引擎打通。因子可以直接形成策略。
表达式由token组成,比如+,-,*,/这样的运算符,
还有自定义函数,比如ts_rank, ts_argmin,我们在wordquant101里实现了
很多这样的函数
强化学习每次生成一个token,如果遇到结束符号就中止。
形成一个表达式。
我们定义一系列的Token,包含开始,结束符,然后是函数集,运算符以及常数等。
from enum import IntEnum from typing import Type class FeatureType(IntEnum): OPEN = 0 CLOSE = 1 HIGH = 2 LOW = 3 VOLUME = 4 VWAP = 5 class SequenceIndicatorType(IntEnum): BEG = 0 SEP = 1 class Token: def __repr__(self): return str(self) class ConstantToken(Token): def __init__(self, constant: float) -> None: self.constant = constant def __str__(self): return str(self.constant) class DeltaTimeToken(Token): def __init__(self, delta_time: int) -> None: self.delta_time = delta_time def __str__(self): return str(self.delta_time) class FeatureToken(Token): def __init__(self, feature: FeatureType) -> None: self.feature = feature def __str__(self): return self.feature.name.lower() class OperatorToken(Token): def __init__(self, operator) -> None: self.operator = operator # 直接返回函数名(这里的operator就是函数名), 在函灵敏集的基础上,需要加上Add, Sub, Mul, Div加减乘出。 def __str__(self): return self.operator.__name__ class UnaryOperator(OperatorToken): def __init__(self, operator): super(UnaryOperator, self).__init__(operator) pass @classmethod def n_args(cls) -> int: return 1 class UnaryRollingOperator(OperatorToken): def __init__(self, operator): super(UnaryRollingOperator, self).__init__(operator) @classmethod def n_args(cls) -> int: return 2 class BinaryOperator(OperatorToken): def __init__(self, operator): super(BinaryOperator, self).__init__(operator) @classmethod def n_args(cls) -> int: return 2 class BinaryRollingOperator(OperatorToken): def __init__(self, operator): super(BinaryRollingOperator, self).__init__(operator) @classmethod def n_args(cls) -> int: return 3 class DeltaTime(Token): def __init__(self, delta: int): self.delta = delta class SequenceIndicatorToken(Token): def __init__(self, indicator: SequenceIndicatorType) -> None: self.indicator = indicator def __str__(self): return self.indicator.name BEG_TOKEN = SequenceIndicatorToken(SequenceIndicatorType.BEG) SEP_TOKEN = SequenceIndicatorToken(SequenceIndicatorType.SEP)
一棵逻辑树:(一棵“逆波兰”token构成的表达式树),对于构建有意义的表达式。
#from alphagen.data.expression import * from typing import List from datafeed.mining.tokens import * class ExpressionBuilder: stack: List[Token] def __init__(self): self.stack = [] def get_tree(self): if len(self.stack) == 1: return self.stack[0] else: raise InvalidExpressionException(f"Expected only one tree, got {len(self.stack)}") def add_token(self, token: Token): if not self.validate(token): raise InvalidExpressionException(f"Token {token} not allowed here, stack: {self.stack}.") if isinstance(token, OperatorToken): n_args: int = token.n_args() children = [] for _ in range(n_args): children.append(self.stack.pop()) self.stack.append(token(*reversed(children))) # type: ignore elif isinstance(token, ConstantToken): self.stack.append(ConstantToken(token.constant)) elif isinstance(token, DeltaTimeToken): self.stack.append(DeltaTime(token.delta_time)) elif isinstance(token, FeatureToken): self.stack.append(FeatureToken(token.feature)) else: assert False def is_valid(self) -> bool: return len(self.stack) == 1 and self.stack[0].is_featured def validate(self, token: Token) -> bool: if isinstance(token, OperatorToken): return self.validate_op(token) elif isinstance(token, DeltaTimeToken): return self.validate_dt() elif isinstance(token, ConstantToken): return self.validate_const() elif isinstance(token, FeatureToken): return self.validate_feature() else: assert False def validate_op(self, op) -> bool: if len(self.stack) < op.n_args(): return False #print(isinstance(op, UnaryOperator)) if isinstance(op, UnaryOperator): if not isinstance(self.stack[-1], FeatureToken): return False elif isinstance(op, BinaryOperator): if not self.stack[-1].is_featured and not self.stack[-2].is_featured: return False if (isinstance(self.stack[-1], DeltaTime) or isinstance(self.stack[-2], DeltaTime)): return False elif isinstance(op, UnaryRollingOperator): if not isinstance(self.stack[-1], DeltaTime): return False if not self.stack[-2].is_featured: return False elif isinstance(op, BinaryRollingOperator): if not isinstance(self.stack[-1], DeltaTime): return False if not self.stack[-2].is_featured or not self.stack[-3].is_featured: return False else: assert False return True def validate_dt(self) -> bool: return len(self.stack) > 0 and self.stack[-1].is_featured def validate_const(self) -> bool: return len(self.stack) == 0 or self.stack[-1].is_featured def validate_feature(self) -> bool: return not (len(self.stack) >= 1 and isinstance(self.stack[-1], DeltaTime)) class InvalidExpressionException(ValueError): pass if __name__ == '__main__': from datafeed.expr_functions import * tokens = [ FeatureToken(FeatureType.LOW), UnaryOperator(sign), DeltaTimeToken(-10), #OperatorToken(Ref), FeatureToken(FeatureType.HIGH), FeatureToken(FeatureType.CLOSE), OperatorToken(Div), OperatorToken(Add), ] builder = ExpressionBuilder() for token in tokens: print(token) builder.add_token(token) print(f'res: {str(builder.get_tree())}') print(f'ref: Add(Ref(Abs($low),-10),Div($high,$close))')
代码在如下位置:
这个表达式树就可以应用于强化学习生成一个个token,然后形成一个有效的表达式,计算ic值等。
大模型落地场景之AI量化投资
大横型如火如荼,与AI量化的关系也非常紧密,除了咱们之前聊天AlphaGPT之外,Quantlab3.8源码发布:整合AlphaGPT大模型自动因子挖掘以及zvt股票数据框架,这种智能问答在量化投研上应用也非常广。
AI智能投顾,第一性原理与kensho,这个被高盛和标普500收购的智能引擎,在现在大模型时代看来,就是个小儿科了。
我在星球同步了一篇最新的论文(后续会提供代码):
FinReport: 结合新闻语义信息的多因子模型显著提升预测准确性。
大模型相当于“大脑”。
很多时候,并不需要再训练一个“金融大脑”。因为它的知识储备已经非常充充分。我们需要的是场景,流程分解,prompts指令,提出精确的问题。
吾日三省吾身 之 “一人企业”与“超级个体”
十年前,我们聊“大众创业”,现在更多人聊“超级个体”。
现在大家都劝人吃饱饭,别创业,以稳为主。把炒股和创业列为高风险行当。
其实,都是脸谱化的误解。
从本质上看,超级个体与创业的逻辑一致。
创业并不是大家理解中的“All in”。大家对创业的刻板印象是,自己辞职,找钱,找人,租一个办公室,然后开会,讨论战略,开发产品,提供服务,建立商业模式。
这些只是形式罢了。
创业的本质就是给市场提供一种更多的产品或服务。以什么样的方式来组织生产,公司只是一种形式。
而超级个体是在当前技术,市场条件下,把自己“活成”一家公司。
你是自己的CEO,有战略部;你又是研发,有研发部;有市场部,运营部。
你说精力上顾不过来怎么办?
你拆解一下:一种是基础设施,已经不需要自己构建了。比如云服务,大模型,支付服务,流量平台,方便接入且成本不高。二是一些开发产品必须的技能,比如工程师——这种硬技能没有办法,如果你恰好是栈工程师,这就再好不过了,这种可以外包给另外的超级个体——以松散的方式组织生产。
完成比完美更加重要,方向比努力重要。
选择做对的事,有限的的事,专注而持续,先完成,形成反馈闭环。
简言之:作为超级个体,你专注你的专长,把以前需要以公司化来组织的事情,交给其的组织或超级个体。
这会带来什么来的好处呢?
灵活。——“一人企业”,就是以成本小到无法倒闭的方式来组织生产,创造价值。——本质还是“创业”。
不需要风投,不需要各种沟通,汇报,会议,管理。
前两天的文章,我们开始构建WorldQuant101因子集:
WorldQuant101因子集整合Quantlab表达式引擎
在Quantlab里复现WorldQuant101因子表达式引擎(代码+数据)
WorldQuant101因子库
有前两天的函数积累,我们直接添加因子即可:
大家仔细看会发现,WorldQuant101的因子,思路上“雷同”的居多。
就是价量关系,排序后取相关性,也就是“价量背离”。
names.append('alpha012')
features.append('(sign(delta(volume, 1)) * (-1 * delta(close, 1)))')
names.append('alpha013')
features.append('(-1 * rank(covariance(rank(close), rank(volume), 5))) ')
names.append('alpha014')
features.append('((-1 * rank(delta(returns, 3))) * correlation(open, volume, 10))')
names.append('alpha015')
features.append('(-1 * sum(rank(correlation(rank(high), rank(volume), 3)), 3))')
比如,29和30号因子,这么长:
Alpha#29: (min(product(rank(rank(scale(log(sum(ts_min(rank(rank((-1 * rank(delta((close - 1), 5))))), 2), 1))))), 1), 5) + ts_rank(delay((-1 * returns), 6), 5)) Alpha#30: (((1.0 - rank(((sign((close - delay(close, 1))) + sign((delay(close, 1) - delay(close, 2)))) + sign((delay(close, 2) - delay(close, 3)))))) * sum(volume, 5)) / sum(volume, 20))
咱们的表达式引擎计算起来也毫无压力。
第31号因子,涉及到一个新函数 decay_linear,我们需要实现即可:
Alpha#31: ((rank(rank(rank(decay_linear((-1 * rank(rank(delta(close, 10)))), 10)))) + rank((-1 * delta(close, 3)))) + sign(scale(correlation(adv20, low, 12))))
把WorldQuant 101刷了一轮,补充了所有的运算函数:
from datafeed.expr_functions import * class AlphaBase: pass # https://www.joinquant.com/data/dict/alpha101 class WorldQuant101(AlphaBase): def get_names_features(self): names = [] features = [] # names.append('alpha001') # features.append('(rank(ts_argmax(signed_power((stddev(returns, 20) if (returns < 0) else close), 2.), ' # '5)) - 0.5)') names.append('alpha002') features.append('(-1 * correlation(rank(delta(log(volume), 2)), rank(((close - open) / open)), 6))') names.append('alpha003') features.append('(-1 * correlation(rank(open), rank(volume), 10))') names.append('alpha004') features.append('(-1 * ts_rank(rank(low), 9))') ''' Alpha#5: (rank((open - (sum(vwap, 10) / 10))) * (-1 * abs(rank((close - vwap))))) Alpha#6: (-1 * correlation(open, volume, 10)) ''' names.append('alpha006') features.append('(-1 * correlation(open, volume, 10))') ''' Alpha#7: ((adv20 < volume) ? ((-1 * ts_rank(abs(delta(close, 7)), 60)) * sign(delta(close, 7))) : (-1 * 1)) Alpha#8: (-1 * rank(((sum(open, 5) * sum(returns, 5)) - delay((sum(open, 5) * sum(returns, 5)), 10)))) ''' names.append('alpha008') features.append( '(-1 * rank(((sum(open, 5) * sum(returns, 5)) - delay((sum(open, 5) * sum(returns, 5)), 10))))') ''' Alpha#11: ((rank(ts_max((vwap - close), 3)) + rank(ts_min((vwap - close), 3))) * rank(delta(volume, 3))) Alpha#12: (sign(delta(volume, 1)) * (-1 * delta(close, 1))) Alpha#13: (-1 * rank(covariance(rank(close), rank(volume), 5))) Alpha#14: ((-1 * rank(delta(returns, 3))) * correlation(open, volume, 10)) Alpha#15: (-1 * sum(rank(correlation(rank(high), rank(volume), 3)), 3)) ''' names.append('alpha012') features.append('(sign(delta(volume, 1)) * (-1 * delta(close, 1)))') names.append('alpha013') features.append('(-1 * rank(covariance(rank(close), rank(volume), 5))) ') names.append('alpha014') features.append('((-1 * rank(delta(returns, 3))) * correlation(open, volume, 10))') names.append('alpha015') features.append('(-1 * sum(rank(correlation(rank(high), rank(volume), 3)), 3))') ''' Alpha#18: (-1 * rank(((stddev(abs((close - open)), 5) + (close - open)) + correlation(close, open, 10)))) Alpha#19: ((-1 * sign(((close - delay(close, 7)) + delta(close, 7)))) * (1 + rank((1 + sum(returns, 250))))) Alpha#20: (((-1 * rank((open - delay(high, 1)))) * rank((open - delay(close, 1)))) * rank((open - delay(low, 1)))) ''' names.append('alpha018') features.append('(-1 * rank(((stddev(abs((close - open)), 5) + (close - open)) + correlation(close, open, 10))))') names.append('alpha019') features.append( '((-1 * sign(((close - delay(close, 7)) + delta(close, 7)))) * (1 + rank((1 + sum(returns, 250)))))') names.append('alpha020') features.append( '(((-1 * rank((open - delay(high, 1)))) * rank((open - delay(close, 1)))) * rank((open - delay(low, 1))))') ''' Alpha#22: (-1 * (delta(correlation(high, volume, 5), 5) * rank(stddev(close, 20)))) Alpha#26: (-1 * ts_max(correlation(ts_rank(volume, 5), ts_rank(high, 5), 5), 3)) Alpha#28: scale(((correlation(adv20, low, 5) + ((high + low) / 2)) - close)) Alpha#29: (min(product(rank(rank(scale(log(sum(ts_min(rank(rank((-1 * rank(delta((close - 1), 5))))), 2), 1))))), 1), 5) + ts_rank(delay((-1 * returns), 6), 5)) Alpha#30: (((1.0 - rank(((sign((close - delay(close, 1))) + sign((delay(close, 1) - delay(close, 2)))) + sign((delay(close, 2) - delay(close, 3)))))) * sum(volume, 5)) / sum(volume, 20)) ''' names.append('alpha022') features.append( '(-1 * (delta(correlation(high, volume, 5), 5) * rank(stddev(close, 20))))') names.append('alpha026') features.append( '(-1 * ts_max(correlation(ts_rank(volume, 5), ts_rank(high, 5), 5), 3))') #names.append('alpha028') #features.append( # 'scale(((correlation(adv20, low, 5) + ((high + low) / 2)) - close))') names.append('alpha029') features.append( '(min(product(rank(rank(scale(log(sum(ts_min(rank(rank((-1 * rank(delta((close - 1), 5))))), 2), 1))))), 1), 5) + ts_rank(delay((-1 * returns), 6), 5))') names.append('alpha030') features.append('(((1.0 - rank(((sign((close - delay(close, 1))) + sign((delay(close, 1) - delay(close, 2)))) + sign((delay(close, 2) - delay(close, 3)))))) * sum(volume, 5)) / sum(volume, 20))') ''' Alpha#31: ((rank(rank(rank(decay_linear((-1 * rank(rank(delta(close, 10)))), 10)))) + rank((-1 * delta(close, 3)))) + sign(scale(correlation(adv20, low, 12)))) Alpha#32: (scale(((sum(close, 7) / 7) - close)) + (20 * scale(correlation(vwap, delay(close, 5), 230)))) Alpha#33: rank((-1 * ((1 - (open / close))^1))) Alpha#34: rank(((1 - rank((stddev(returns, 2) / stddev(returns, 5)))) + (1 - rank(delta(close, 1))))) Alpha#35: ((Ts_Rank(volume, 32) * (1 - Ts_Rank(((close + high) - low), 16))) * (1 - Ts_Rank(returns, 32))) ''' names.append('alpha034') features.append( 'rank(((1 - rank((stddev(returns, 2) / stddev(returns, 5)))) + (1 - rank(delta(close, 1)))))') names.append('alpha035') features.append( '((ts_rank(volume, 32) * (1 - ts_rank(((close + high) - low), 16))) * (1 - ts_rank(returns, 32)))') ''' Alpha#36: (((((2.21 * rank(correlation((close - open), delay(volume, 1), 15))) + (0.7 * rank((open - close)))) + (0.73 * rank(Ts_Rank(delay((-1 * returns), 6), 5)))) + rank(abs(correlation(vwap, adv20, 6)))) + (0.6 * rank((((sum(close, 200) / 200) - open) * (close - open))))) Alpha#37: (rank(correlation(delay((open - close), 1), close, 200)) + rank((open - close))) Alpha#38: ((-1 * rank(Ts_Rank(close, 10))) * rank((close / open))) Alpha#39: ((-1 * rank((delta(close, 7) * (1 - rank(decay_linear((volume / adv20), 9)))))) * (1 + rank(sum(returns, 250)))) Alpha#40: ((-1 * rank(stddev(high, 10))) * correlation(high, volume, 10)) ''' names.append('alpha037') features.append( '(rank(correlation(delay((open - close), 1), close, 200)) + rank((open - close)))') names.append('alpha038') features.append( '((-1 * rank(ts_rank(close, 10))) * rank((close / open)))') names.append('alpha040') features.append( '((-1 * rank(stddev(high, 10))) * correlation(high, volume, 10))') ''' Alpha#44: (-1 * correlation(high, rank(volume), 5)) Alpha#45: (-1 * ((rank((sum(delay(close, 5), 20) / 20)) * correlation(close, volume, 2)) * rank(correlation(sum(close, 5), sum(close, 20), 2)))) ''' names.append('alpha044') features.append( '(-1 * correlation(high, rank(volume), 5))') names.append('alpha045') features.append( '(-1 * ((rank((sum(delay(close, 5), 20) / 20)) * correlation(close, volume, 2)) * rank(correlation(sum(close, 5), sum(close, 20), 2))))') ''' Alpha#52: ((((-1 * ts_min(low, 5)) + delay(ts_min(low, 5), 5)) * rank(((sum(returns, 240) - sum(returns, 20)) / 220))) * ts_rank(volume, 5)) Alpha#53: (-1 * delta((((close - low) - (high - close)) / (close - low)), 9)) Alpha#54: ((-1 * ((low - close) * (open^5))) / ((low - high) * (close^5))) Alpha#55: (-1 * correlation(rank(((close - ts_min(low, 12)) / (ts_max(high, 12) - ts_min(low, 12)))), rank(volume), 6)) Alpha#60: (0 - (1 * ((2 * scale(rank(((((close - low) - (high - close)) / (high - low)) * volume)))) - scale(rank(ts_argmax(close, 10)))))) ''' names.append('alpha052') features.append( '((((-1 * ts_min(low, 5)) + delay(ts_min(low, 5), 5)) * rank(((sum(returns, 240) - sum(returns, 20)) / 220))) * ts_rank(volume, 5))') names.append('alpha053') features.append( '(-1 * delta((((close - low) - (high - close)) / (close - low)), 9))') names.append('alpha055') features.append( '(-1 * correlation(rank(((close - ts_min(low, 12)) / (ts_max(high, 12) - ts_min(low, 12)))), rank(volume), 6))') names.append('alpha060') features.append('(0 - (1 * ((2 * scale(rank(((((close - low) - (high - close)) / (high - low)) * volume)))) - scale(rank(ts_argmax(close, 10))))))') names.append('alpha101') features.append('((close - open) / ((high - low) + .001))') return names, features
几点收获:
一、积累运算函数库,后续我们机器挖掘可以复用,丢给gplearn和强化学习。
二、因子构造的思路,其实这个我之前让GPT做过:
Quantlab3.9代码:内置大模型LLM因子挖掘,全A股数据源以及自带GUI界面
今天文章涉及的代码在如下位置:
量化数据中心建设
昨天有同学问数据的事情,咱们公众号对应的文章,使用到的数据,都会随代码一起打包在星球发布。
但为了更方便使用,咱们可以考虑建设一个会自动更新的数据中台。
这也印证了咱们是想把AI量化当作事业持续做下去的。
大模型LLM
最近大模型领域,重磅消息是Lalla3开源。
目前看来,llama3-8B的版本,很多小团队有几张卡就可以玩,这是个令人振奋的消息。
可以做的事情有三件: 增量预训练(比如加入更多的中文数据集或者领域数据集)_如果只是小数据集,在增量预训练中如何起作用?指令微调(让它更加能听懂人话),SFT(监督微调)。
需要自己构建数据集,指令集,领域数据集等,同时还需要有评测手段,以评估训练的结果。
另外,就是基于大模型的api,使用指令(prompts)与大模型交互。这个优点是自己要做的事情比较少,调API就好了,缺点是模型不符合预期时,没法干预,还是token还是比较贵的。
吾日三省吾身
最近老看到一些职场里,竞业限制引发的争端,甚至很多人赔了不少钱。
以前这种事情可能只会发生于大公司,高管圈之中。而且多数这些钱对他们来讲,小case。更多是为了专利保护,技术保密之类的诉求。
现在似乎情况发生了变化。
有些人就是普通的开发,运营。
以前的互联网,人员流动比较快。甚至很多公司在做类似项目时,就高价到对方公司挖人。
以前的互联网,开除人也比较快。现在随着新劳动法的出台,基本上没有N+1是结束不了。
企业无法高效激发活力,这是一种负担。人才不能有效流动。这也是一种负担。
不过从成熟的,契约精神的角度,平衡工作与生活的角度,也许未必是错的。
毕竟,工作都是为了更好地生活嘛。
历史文章:
发布者:股市刺客,转载请注明出处:https://www.95sca.cn/archives/103408
站内所有文章皆来自网络转载或读者投稿,请勿用于商业用途。如有侵权、不妥之处,请联系站长并出示版权证明以便删除。敬请谅解!