# Technical Cycle Analysis

> "Implements market cycles and periodic patterns in price movement for risk management and algorithmic trading execution."

- Skill: `paulpas/technical-cycle-analysis` (Agent Skill)
- Install (CLI): `npx skillmds@latest add paulpas/technical-cycle-analysis`
- Raw SKILL.md: https://api.skillmd.com/api/skills/paulpas/technical-cycle-analysis/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- License: MIT
- Author: paulpas (https://skillmd.com/u/paulpas)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/paulpas/technical-cycle-analysis

---





**Role:** Identify and trade based on cyclical market behavior

**Philosophy:** Markets exhibit repeating cycles; understanding cycle phases enables timing advantages

## Key Principles

1. **Cycle Detection**: FFT, autocorrelation for cycle identification
2. **Phase Analysis**: Current position within cycle
3. **Cycle Amplitude**: Cycle strength and predictability
4. **Harmonic Cycles**: Multiple cycles operating simultaneously
5. **Cycle Phase Transitions**: Key turning point signals

## Implementation Guidelines

### Structure
- Core logic: technical_analysis/cycles.py
- Helper functions: technical_analysis/fourier.py
- Tests: tests/test_cycles.py

### Patterns to Follow
- Decompose price into cycle components
- Track cycle phase and amplitude
- Identify dominant cycle lengths

## Adherence Checklist
Before completing your task, verify:
- [ ] Multiple cycle lengths detected (intraday, daily, weekly, monthly)
- [ ] Cycle phase tracked in real-time
- [ ] Dominant cycle identified and monitored
- [ ] Cycle phase transitions trigger signals
- [ ] Cycle amplitude adapts to market conditions


Relative paths in this skill (e.g., scripts/, reference/) are relative to this base directory.

## Python Implementation

```python
import numpy as np
import pandas as pd
from typing import Dict, List, Tuple, Optional
from dataclasses import dataclass
from scipy import signal
from scipy.fft import fft, fftfreq
from scipy.signal import find_peaks

@dataclass
class MarketCycle:
    """Detected market cycle."""
    period: float  # In bars/units
    amplitude: float
    phase: float  # 0-2π
    strength: float  # 0-1
    frequency: float

class CycleAnalyzer:
    """Analyzes market cycles and periodic patterns."""
    
    def __init__(self, min_period: int = 10, max_period: int = 252):
        self.min_period = min_period
        self.max_period = max_period
    
    def detect_cycles_fft(
        self, prices: np.ndarray, sample_rate: float = 1.0
    ) -> List[MarketCycle]:
        """Detect cycles using Fast Fourier Transform."""
        # Detrend prices
        trend = np.polyfit(np.arange(len(prices)), prices, 1)
        detrended = prices - np.polyval(trend, np.arange(len(prices)))
        
        # Apply FFT
        n = len(detrended)
        yf = fft(detrended)
        xf = fftfreq(n, sample_rate)
        
        # Get magnitude spectrum (only positive frequencies)
        magnitude = 2.0 / n * np.abs(yf[:n // 2])
        frequencies = xf[:n // 2]
        
        # Find peaks in frequency spectrum
        peaks, _ = find_peaks(magnitude, height=np.mean(magnitude))
        
        cycles = []
        for peak in peaks:
            if self.min_period <= 1 / frequencies[peak] <= self.max_period:
                cycle = MarketCycle(
                    period=1 / frequencies[peak] if frequencies[peak] > 0 else self.max_period,
                    amplitude=magnitude[peak],
                    phase=np.angle(yf[peak]),
                    strength=magnitude[peak] / np.max(magnitude),
                    frequency=frequencies[peak]
                )
                cycles.append(cycle)
        
        return sorted(cycles, key=lambda x: x.strength, reverse=True)
    
    def detect_cycles_autocorrelation(
        self, prices: np.ndarray, max_lag: int = 200
    ) -> List[MarketCycle]:
        """Detect cycles using autocorrelation."""
        # Detrend
        trend = np.polyfit(np.arange(len(prices)), prices, 1)
        detrended = prices - np.polyval(trend, np.arange(len(prices)))
        
        # Calculate autocorrelation
        n = len(detrended)
        autocorr = np.correlate(detrended, detrended, mode='full')
        autocorr = autocorr[n-1:] / autocorr[n-1]  # Normalize
        
        # Find peaks (excluding lag 0)
        peaks, _ = find_peaks(autocorr[1:], prominence=0.1)
        
        cycles = []
        for peak in peaks:
            lag = peak + 1
            if self.min_period <= lag <= self.max_period:
                cycle = MarketCycle(
                    period=float(lag),
                    amplitude=autocorr[lag],
                    phase=0,  # Would need more analysis
                    strength=autocorr[lag],
                    frequency=1.0 / lag
                )
                cycles.append(cycle)
        
        return sorted(cycles, key=lambda x: x.strength, reverse=True)
    
    def calculate_cycle_phase(
        self, prices: np.ndarray, cycle_period: float
    ) -> float:
        """Calculate current phase of cycle (0-2π)."""
        if cycle_period <= 0:
            return 0
        
        # Create synthetic cycle
        t = np.arange(len(prices))
        synthetic = np.sin(2 * np.pi * t / cycle_period)
        
        # Cross-correlate to find phase
        correlation = np.correlate(prices, synthetic, mode='full')
        max_idx = np.argmax(np.abs(correlation))
        
        # Convert to phase
        phase_offset = max_idx - len(prices) + 1
        phase = 2 * np.pi * phase_offset / cycle_period
        
        return phase % (2 * np.pi)
    
    def identify_cycle_phase_transitions(
        self, prices: np.ndarray, cycle_period: float
    ) -> List[Dict]:
        """Identify key cycle phase transitions."""
        if cycle_period <= 0:
            return []
        
        t = np.arange(len(prices))
        synthetic = np.sin(2 * np.pi * t / cycle_period)
        
        transitions = []
        
        for i in range(1, len(prices)):
            # Detect zero crossings
            if synthetic[i-1] < 0 and synthetic[i] >= 0:
                transitions.append({
                    'type': 'bottom',
                    'phase': 'trough',
                    'index': i,
                    'price': prices[i]
                })
            elif synthetic[i-1] > 0 and synthetic[i] <= 0:
                transitions.append({
                    'type': 'top',
                    'phase': 'peak',
                    'index': i,
                    'price': prices[i]
                })
        
        return transitions
    
    def multi_cycle_analysis(
        self, prices: np.ndarray
    ) -> Dict[str, List[MarketCycle]]:
        """Analyze multiple cycle horizons simultaneously."""
        results = {
            'short': [],  # 5-50 periods
            'medium': [],  # 50-200 periods
            'long': []  # 200+ periods
        }
        
        # Short cycles
        results['short'] = self.detect_cycles_fft(prices[-100:], 1.0)
        results['short'] = [c for c in results['short'] if c.period < 50]
        
        # Medium cycles
        results['medium'] = self.detect_cycles_fft(prices[-500:], 1.0)
        results['medium'] = [c for c in results['medium'] 
                           if 50 <= c.period <= 200]
        
        # Long cycles
        results['long'] = self.detect_cycles_fft(prices[-2520:], 1.0)
        results['long'] = [c for c in results['long'] if c.period > 200]
        
        return results
``````
```
```

---

---

## Constraints

### MUST DO
- Implement indicator calculations using rolling windows with explicit lookback periods; never use full-history data for online indicators
- Validate signal generation by confirming alignment across multiple independent indicators before acting on a single signal
- Calculate all price-based indicators (SMA, EMA, RSI) on closing prices unless specifically designed for tick data
- Include proper handling of missing/NaN candles in indicator pipelines — forward-fill only within session boundaries
- Log signal generation with the full context window of indicator values that led to each signal

### MUST NOT DO
- Do not use look-ahead bias: never reference future bars or prices when calculating indicators during backtesting
- Avoid recalculating all indicators from scratch on every tick — maintain running state for efficiency
- Never combine indicators with different timeframes without explicit resampling and clear documentation of the alignment logic
- Do not generate signals based on a single indicator crossover; require confirmation from price action or volume
- Avoid hardcoding parameter values (e.g., RSI period = 14) without testing regime-specific optima


## Live References

> Authoritative documentation links for this skill's domain. The model follows markdown links at load time to resolve external references and inline content.

- [Hartmann Cycle Theory](https://www.investopedia.com/articles/trading/08/hartmann_cycle.asp)
- [Kitchin Inventory Cycles](https://en.wikipedia.org/wiki/Kitchin_inventories)
- [Juglar Fixed Investment Cycles](https://en.wikipedia.org/wiki/Juglar_cycle)
- [Kuznets Long Wave Cycles](https://en.wikipedia.org/wiki/Kuznets_cycle)
- [Economic Cycle Analysis Guide](https://www.investopedia.com/terms/e/economic-cycle.asp)

