Real-time cycle detection in financial time series with concept drift handling, extending the FSC CycleScanner methodology (detrending → cycle detection → validation → ranking) with adaptive techniques.
Financial markets are non-stationary: cycles can change length, phase, and amplitude due to regime shifts, sentiment changes, or external shocks. This module provides an adaptive, online scanner that:
- Adaptive detrending – Kalman filter (local linear trend) to separate trend and cycle
- Online cycle detection – Recursive Goertzel filter bank with exponential forgetting over candidate cycle lengths
- Dynamic validation – Rolling-window Bartels test for cycle genuineness; change-point detection on dominant length
- Adaptive ranking – Strength = (amplitude / length) × recency weight
- Regime detection – Optional Bayesian online change-point detection (Adams & MacKay 2007) on dominant cycle length
pip install -r requirements.txt# Run the Streamlit GUI
streamlit run app.py-
Data source
- Upload CSV: Choose a file with at least one numeric price column (e.g.
close,price). The app will try to detect the price column or let you select it. - Demo: Load a synthetic series (stationary cycle, sudden regime shift, or gradual frequency drift) with known ground truth.
- Upload CSV: Choose a file with at least one numeric price column (e.g.
-
Parameters (sidebar)
- Min/Max cycle length – Candidate cycle range (e.g. 5–200 bars).
- Forgetting factor – Exponential forgetting (e.g. 0.98); higher = more memory.
- Bartels window – Window size for the rolling Bartels test (e.g. 200).
- Genuineness threshold – Cycle is “genuine” if genuineness % exceeds this (e.g. 49%).
- Enable regime detection – Turn on Bayesian change-point detection on the dominant length.
-
Run
Click Run scanner to process the series. Results include:- Main chart: price with overlaid dominant cycle (sinusoidal projection).
- Dominant cycle length over time (colored by genuineness %).
- Optional phase and amplitude in an expander.
- Download results (CSV): time index, dominant length, phase, amplitude, strength, genuineness %, regime_change flag.
-
Demo data
Use “Demo: synthetic data” to try predefined scenarios (e.g. sudden shift or gradual drift) and see how the scanner adapts.
| Parameter | Typical range | Description |
|---|---|---|
min_cycle / max_cycle |
5–200 | Candidate cycle lengths (bars). |
forgetting_factor |
0.90–0.999 | Goertzel state decay; higher = more weight on history. |
bartels_window |
100–300 | Rolling window for Bartels genuineness. |
genuineness_threshold |
40–60% | Minimum genuineness to treat cycle as genuine. |
change_point_threshold |
0.5–0.9 | Bayesian P(change) above this triggers regime shift. |
Kalman process_noise_level / process_noise_slope |
1e-6–1e-3 | Trend adaptability. |
Kalman observation_noise |
0.1–10 | Observation noise variance. |
from adaptive_cyclescanner import AdaptiveCycleScanner
import numpy as np
scanner = AdaptiveCycleScanner(
min_cycle=5,
max_cycle=200,
forgetting_factor=0.98,
bartels_window=200,
genuineness_threshold=49.0,
use_regime_detection=True,
)
# Online: one bar at a time
out = scanner.update(100.5)
print(out["dominant_length"], out["dominant_phase"], out["dominant_amplitude"])
# Batch (e.g. backtest)
prices = np.random.randn(1000).cumsum() + 100
results = scanner.fit(prices)
# results["dominant_lengths"], ["dominant_phases"], ["dominant_amplitudes"], ["regime_change_flags"], etc.from adaptive_cyclescanner import AdaptiveCycleScanner
from benchmark import Benchmark
from synthetic_data import scenario_stationary_cycle, scenario_sudden_regime_shift
series, ground_truth = scenario_sudden_regime_shift(n_points=800, shift_time=400, seed=42)
scanner = AdaptiveCycleScanner(min_cycle=5, max_cycle=200)
bench = Benchmark(scanner, series, ground_truth)
bench.run()
metrics = bench.compute_metrics()
# length_mae, length_rmse, phase_mae, amplitude_mae, regime_detection_*, adaptation_lag, overall_score
bench.plot_results(show=True, save_path="benchmark_plot.png")python tune.py
python tune.py --quick # smaller grid
python tune.py --no-plot # no heatmaptune.py runs a grid over e.g. forgetting_factor, bartels_window, genuineness_threshold on a suite of synthetic scenarios and prints a summary table. Optionally it saves a heatmap to tune_heatmap.png.
from synthetic_data import generate_synthetic_series, scenario_stationary_cycle, scenario_sudden_regime_shift, scenario_gradual_frequency_drift
# Generic
series, gt = generate_synthetic_series(
n_points=1000,
trend_type="linear", # or 'random_walk', 'step'
cycle_specs=[{"length": 20, "amplitude": 2.0, "phase": 0.0, "start": 0, "end": 999}],
drift_events=[{"time": 400, "type": "sudden", "new_length": 50}],
noise_std=0.3,
seed=42,
)
# gt: true_lengths, true_phases, true_amplitudes, regimes
# Predefined scenarios
series, gt = scenario_stationary_cycle(1000, length=20, seed=42)
series, gt = scenario_sudden_regime_shift(1000, first_length=20, second_length=50, shift_time=400, seed=42)
series, gt = scenario_gradual_frequency_drift(1000, start_length=15, end_length=60, drift_start=300, drift_duration=400, seed=42)pytest test_adaptive_cyclescanner.py -vpython demo.pyRuns the scanner on stationary and sudden-shift synthetic data, prints metrics, and shows a short online update example.
We acknowledge Lars von Thienen and the Foundation for the Study of Cycles for the original CycleScanner methodology (see References below) on which this adaptive extension is based. We acknowledge with gratitude Donald E. Knuth for his lasting influence on computer science and for his recent, open embrace of AI-assisted research. In his paper Claude Cycles [1], Knuth accepts and documents the method of “vibe-researching”—iterative, conversational collaboration with large language models as part of the scholarly process. His example encourages us to treat human–AI collaboration as a legitimate and documentable part of modern research. We also thank the unique ideological and intellectual space that formed the special "Württemberg spirit" (der württembergische Geist), and the tradition of the Evangelisches Stift Tübingen and the broader Württemberg and Tübingen intellectual milieu [2], which continue to inspire rigorous and open-ended inquiry.
- [1] Donald E. Knuth, Claude Cycles. Stanford University.
https://www-cs-faculty.stanford.edu/~knuth/papers/claude-cycles.pdf - [2] Württemberg / Tübingen tradition. Evangelisches Stift Tübingen, Geschichte und Tradition: https://www.evstift.de/evangelisches-stift/geschichte-und-tradition. Landeskunde Baden-Württemberg, Tübingen und das schwäbische Geistesleben: https://www.landeskunde-baden-wuerttemberg.de/tuebingen
The following papers by the author are included in this repository (see papers/) and inform the broader research context:
-
Samokhvalov, E. The Fox on the Throne of Heaven: From Biblical Dispute to Quantum Trail (Vulpes Cybeles: Ousia Diakritikē). A comparative study of the trickster archetype in world traditions. From the cycle "The Fox Path: Archaeology of the Future," 2026.
- Local copies: papers/fox_throne_en_v2.pdf, papers/fox_throne_en_v2.html, papers/fox_throne_ru.pdf
- Quote: «The fox in world traditions functions not merely as a character or symbol of cunning, but as ousia diakritikē—a "distinguishing essence," a principle that makes possible transitions between states, worlds, and levels of reality.»
- Quote: «What was tested was the system's readiness for paradox. Shibi accepted impossible conditions […] and transformed himself into the solution. This is vulpina sapientia—fox wisdom.»
-
Samokhvalov, E. The Fox as Archetypal Middleware: A Systems Analysis. Applying the "Operating Systems of Civilization" framework to the trickster archetype. 2026.
- Local copy: papers/fox_as_middleware_en.pdf
- Quote: «These networks functioned as the "middleware" between civilizational operating systems… They were the internet before the internet.»
- Quote: «Is the fox archetype the deep prototype of what historically manifests as trans-territorial networks?»
- Evangelisches Stift Tübingen / Landeskunde Baden-Württemberg – The "Württemberg spirit" (der württembergische Geist) and Tübingen intellectual tradition; see links in Acknowledgments above.
- von Thienen, L. (FSC) – The Cycle Scanner Algorithm (FSC whitepaper, 2020): original methodology (detrending, Goertzel-based cycle detection, Bartels test for validation, ranking). Available: https://cycles.org/wp-content/uploads/2020/03/CycleScanner_Whitepaper_FSC.pdf
- Adams & MacKay (2007) – Bayesian online change-point detection.
- Kalman filter – Local linear trend model for adaptive detrending.
- Goertzel algorithm – Recursive computation of a single DFT bin (here, one bin per candidate cycle length).
- Bartels test – Rank test for randomness; used here as a rolling measure of cycle consistency (genuineness).
- Samokhvalov, E. (2026) – The Fox on the Throne of Heaven and The Fox as Archetypal Middleware; see Related publications above and
papers/.
adaptive_cyclescanner.py– Core scanner (Kalman, Goertzel, Bartels, ranking, regime detection).synthetic_data.py– Synthetic series and ground truth generation.benchmark.py– Benchmark class and metrics.tune.py– Hyperparameter grid search and optional plots.test_adaptive_cyclescanner.py– Unit tests.app.py– Streamlit GUI.demo.py– Example usage.requirements.txt– Dependencies.tribute_knuth.tex– Tribute and regards to Donald E. Knuth (LaTeX; compile withpdflatex tribute_knuth.tex).papers/– Author’s related papers (Fox on the Throne, Fox as Middleware): PDF and HTML copies.README.md– This file.