@@ -629,6 +629,72 @@ def _rolling_ivol(x):
629629
630630 return result
631631
632+
633+
634+ # ---------------------------------------------------------------------------
635+ # Multi-timeframe resonance factor (inspired by a-share-signal)
636+ # ---------------------------------------------------------------------------
637+
638+
639+ class MultiTimeframeResonanceFactor (BaseFactor ):
640+ """Multi-timeframe resonance: trend alignment across daily/weekly/monthly.
641+
642+ Checks whether price trends across 3 timeframes are aligned.
643+ High resonance = daily, weekly, and monthly trends all point same direction.
644+ Low resonance = timeframes conflict, choppy price action expected.
645+
646+ Score [0, 1]:
647+ 1.0 = perfect alignment (all 3 timeframes bullish or bearish)
648+ 0.0 = complete conflict (daily and monthly opposite)
649+ """
650+ category = FactorCategory .TECHNICAL
651+
652+ def __init__ (self , name : str = "mtf_resonance" ):
653+ super ().__init__ ()
654+ self ._name = name
655+
656+ @property
657+ def name (self ) -> str :
658+ return self ._name
659+
660+ def compute (self , prices , ** kwargs ):
661+ # Daily trend: 20-day MA slope
662+ daily_ma = prices .rolling (20 ).mean ()
663+ daily_slope = (daily_ma - daily_ma .shift (5 )) / daily_ma .shift (5 ).replace (0 , float ("nan" ))
664+
665+ # Weekly trend: approximate via 60-day MA slope
666+ weekly_ma = prices .rolling (60 ).mean ()
667+ weekly_slope = (weekly_ma - weekly_ma .shift (15 )) / weekly_ma .shift (15 ).replace (0 , float ("nan" ))
668+
669+ # Monthly trend: approximate via 120-day MA slope
670+ monthly_ma = prices .rolling (120 ).mean ()
671+ monthly_slope = (monthly_ma - monthly_ma .shift (30 )) / monthly_ma .shift (30 ).replace (0 , float ("nan" ))
672+
673+ # Direction: 1 = bullish, -1 = bearish, 0 = flat
674+ def _direction (slope : pd .DataFrame ) -> pd .DataFrame :
675+ d = pd .DataFrame (0.0 , index = slope .index , columns = slope .columns )
676+ d [slope > 0.001 ] = 1.0
677+ d [slope < - 0.001 ] = - 1.0
678+ return d
679+
680+ d_dir = _direction (daily_slope )
681+ w_dir = _direction (weekly_slope )
682+ m_dir = _direction (monthly_slope )
683+
684+ # Resonance: how many timeframes agree
685+ agreement = d_dir + w_dir + m_dir
686+
687+ # Map to [0, 1] score
688+ # 3 or -3 = all agree = 1.0
689+ # 1 or -1 = 2 agree, 1 flat = 0.7
690+ # 0 = conflict = 0.0
691+ score = pd .DataFrame (0.0 , index = prices .index , columns = prices .columns )
692+ score [agreement .abs () >= 3 ] = 1.0
693+ score [agreement .abs () == 2 ] = 0.7
694+ score [agreement .abs () == 1 ] = 0.3
695+
696+ return score
697+
632698def register_all ():
633699 registry = get_registry ()
634700 for cls in [
@@ -643,5 +709,6 @@ def register_all():
643709 MAConvergenceFactor ,
644710 BreakoutProximityFactor ,
645711 PureVolatilityFactor ,
712+ MultiTimeframeResonanceFactor ,
646713 ]:
647714 registry .register (cls )
0 commit comments