Skip to main content

first week 2026 - january momentum algo kicking off

new year. new momentum.

first real trading week of 2026 in the books.

january effect algo activated.

the january effect
#

some people think it’s BS.

historical data says otherwise:

  • small caps outperform first two weeks
  • winners from december continue
  • tax-loss harvesting reverses
  • institutional money returns from holiday

January Effect Returns

6 years of january returns. avg +1.5% in first two weeks. not huge but consistent.

the algo
#

been running a january-specific momentum strategy since 2024.

core logic:

import numpy as np
import pandas as pd
from dataclasses import dataclass
from typing import List, Optional, Tuple
from datetime import datetime
from enum import Enum

class JanuarySignal(Enum):
    STRONG_BUY = "strong_momentum"
    BUY = "momentum"
    NEUTRAL = "flat"
    AVOID = "reversal_risk"

@dataclass
class JanuaryMomentumConfig:
    lookback_days: int = 10  # last 10 trading days of december
    min_momentum_score: float = 0.6
    position_size_pct: float = 0.03  # 3% per position
    max_positions: int = 15
    stop_loss_pct: float = 0.04  # 4% stop
    take_profit_pct: float = 0.08  # 8% target

class JanuaryMomentumAlgo:
    def __init__(self, config: JanuaryMomentumConfig):
        self.config = config
        self.active_positions = {}
        self.signals_generated = []

    def calculate_december_momentum(self,
                                     prices: pd.DataFrame,
                                     volume: pd.DataFrame) -> pd.DataFrame:
        """
        Calculate momentum score based on december performance
        Higher score = stronger january continuation expected
        """
        results = []

        for symbol in prices.columns:
            try:
                # Get last N days of december
                dec_prices = prices[symbol].iloc[-self.config.lookback_days:]
                dec_volume = volume[symbol].iloc[-self.config.lookback_days:]

                # Price momentum (weighted recent more)
                weights = np.linspace(0.5, 1.5, len(dec_prices))
                price_return = (dec_prices.iloc[-1] / dec_prices.iloc[0]) - 1
                weighted_return = price_return * np.average(weights)

                # Volume confirmation
                avg_vol = dec_volume.mean()
                recent_vol = dec_volume.iloc[-3:].mean()
                vol_ratio = recent_vol / avg_vol if avg_vol > 0 else 1.0

                # Momentum score (0-1 scale)
                momentum_score = self._normalize_score(
                    weighted_return * 0.6 + (vol_ratio - 1) * 0.4
                )

                results.append({
                    'symbol': symbol,
                    'dec_return': price_return,
                    'vol_ratio': vol_ratio,
                    'momentum_score': momentum_score,
                    'signal': self._generate_signal(momentum_score)
                })

            except Exception as e:
                continue

        return pd.DataFrame(results).sort_values(
            'momentum_score', ascending=False
        )

    def _normalize_score(self, raw_score: float) -> float:
        """Normalize to 0-1 scale using sigmoid"""
        return 1 / (1 + np.exp(-raw_score * 10))

    def _generate_signal(self, score: float) -> JanuarySignal:
        if score >= 0.75:
            return JanuarySignal.STRONG_BUY
        elif score >= self.config.min_momentum_score:
            return JanuarySignal.BUY
        elif score >= 0.4:
            return JanuarySignal.NEUTRAL
        else:
            return JanuarySignal.AVOID

    def generate_january_portfolio(self,
                                    momentum_df: pd.DataFrame,
                                    account_value: float) -> List[dict]:
        """
        Build portfolio from top momentum stocks
        """
        # Filter to actionable signals
        buys = momentum_df[
            momentum_df['signal'].isin([
                JanuarySignal.STRONG_BUY,
                JanuarySignal.BUY
            ])
        ].head(self.config.max_positions)

        positions = []
        position_value = account_value * self.config.position_size_pct

        for _, row in buys.iterrows():
            positions.append({
                'symbol': row['symbol'],
                'signal': row['signal'].value,
                'momentum_score': row['momentum_score'],
                'allocation_usd': position_value,
                'stop_loss': self.config.stop_loss_pct,
                'take_profit': self.config.take_profit_pct,
                'entry_date': datetime.now().strftime('%Y-%m-%d')
            })

        return positions

    def backtest_january(self,
                          prices: pd.DataFrame,
                          years: List[int]) -> dict:
        """
        Backtest january strategy across multiple years
        Returns performance metrics
        """
        results = {
            'year': [],
            'jan_return': [],
            'spy_return': [],
            'alpha': [],
            'win_rate': [],
            'max_dd': []
        }

        for year in years:
            # Would implement full backtest here
            # Simplified for blog post
            pass

        return results

week 1 results
#

first 5 trading days done (markets closed jan 1).

Week 1 Performance

cumulative +1.12% so far. beat SPY by 0.4%. not crushing it but positive.

breakdown:

  • monday (1/2): +0.42% - new year momentum kicked in
  • tuesday (1/3): +0.18% - continuation
  • wednesday (1/4): -0.31% - pullback, expected
  • thursday (1/5): +0.55% - jobs report bounce
  • friday (1/6): +0.12% - flat close

positions active
#

currently holding:

  • tech momentum (NVDA, META, AMZN) - 40% weight
  • small cap momentum (via IWM options) - 25%
  • crypto continuation (BTC, ETH) - 20%
  • cash buffer - 15%

the edge
#

january effect isn’t magic.

it’s behavioral:

  • tax-loss sellers from december become buyers
  • new year = new capital allocations
  • pension/401k contributions hit first two weeks
  • “new year resolution” retail buying

algo just positions ahead of the flow.

next steps
#

watching week 2 closely.

historically week 2 is strongest for continuation.

if momentum holds through jan 15, will increase exposure.

if it fades, algo cuts to 50% and waits.

been discussing momentum strategies with the NexusFi algo trading community - some good perspectives on january seasonality patterns.


2:47am thursday. first week 2026 complete. january momentum algo +1.12% so far. beat SPY by 0.4%. nothing crazy but consistent with historical patterns. week 2 historically strongest. watching closely.

-AK

Related

year-end portfolio rebalancing algo - detecting institutional flows
december means institutional rebalancing. pension funds, endowments, mutual funds all adjusting. built an algo to detect and trade the flows. the concept # year-end rebalancing patterns:
vix term structure algo - contango/backwardation trading
been researching VIX term structure trades. contango vs backwardation. predictable patterns. finally got an algo working. the concept # contango: front month VIX < back month VIX
sector rotation algo - implementation with relative strength scoring
been working on a sector rotation algo. concept: own the strongest sectors, short the weakest. simple in theory. complex in implementation. the core idea # sectors rotate in predictable cycles.
earnings volatility filter - implementation and early results
been running the earnings volatility filter for a week now. early results are promising. the problem # earnings = binary events. stock moves 5-10% or nothing.
crypto momentum algo - btc breakout strategy implementation
BTC broke out of 3-month range today. my momentum algo caught it. time to document the implementation. the context # BTC been consolidating between $25,000 and $28,000 since june.
regime detection filter - why it failed march, python implementation fix
march disaster taught lesson. regime detection lagged. cost $6,690 before pausing. fixing implementation. what went wrong # my current filter: