Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,25 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

---

## [0.6.0] — 2026-06-13

### Added

- **`Quant::Indicators::DominantCycles::SuppliedPeriod`** — generalization of the prior `HalfPeriod` slot. The class doc names four canonical supply modes (fixed / manual / configured / external) that consumers compose freely. The `compute` method stays no-op; whoever sets `point.period` controls the period that downstream indicators reading `adaptive_period` see. The substrate-driven external mode is the load-bearing pattern for tick-derived adaptive lookbacks (e.g., Fishy peak-count cycle period) where another process writes the period to a runtime store and the integration site reads + populates the point.
- **`:supplied_period`** symbol registered alongside the other DC kinds; it is the new default for `Settings::Indicators#dominant_cycle_kind`.

### Deprecated (removed in 0.9.0)

- **`Quant::Indicators::DominantCycles::HalfPeriod`** → use `SuppliedPeriod`. The old constant remains as an alias and is marked via `Module#deprecate_constant`; each access emits a Ruby deprecation warning. The two are equivalent at runtime; the rename clarifies the underlying capability.
- **`Quant::Indicators::DominantCycles::HalfPeriodPoint`** → use `SuppliedPeriodPoint`. Same alias + `deprecate_constant` treatment.
- **`:half_period` dominant_cycle_kind symbol** → use `:supplied_period`. `Settings::Indicators#dominant_cycle_indicator_class` resolves the deprecated symbol to `SuppliedPeriod` and emits a one-time-per-instance warning via `warn`.

The unrelated `Settings::Indicators#half_period` method (returns `(max_period + min_period) / 2`) and the point default symbol `:half_period` (which references that method, not the deprecated class) are NOT deprecated and continue to work unchanged.

See `Quant::Indicators::DominantCycles::SuppliedPeriod` class doc §Migration for the upgrade path. Existing consumers see identical runtime behavior; only the names change.

---

## [0.5.0] — 2026-05-22

### Changed (breaking)
Expand Down
2 changes: 1 addition & 1 deletion Gemfile.lock
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
PATH
remote: .
specs:
quantitative (0.5.0)
quantitative (0.6.0)
csv
oj (~> 3.10)
zeitwerk (~> 2.6)
Expand Down
5 changes: 3 additions & 2 deletions lib/quant/dominant_cycles_source.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,9 @@ module Quant
#
# Quant.configure_indicators(min_period: 8, max_period: 32)
#
# The default dominant cycle kind is the `half_period` filter. This can be adjusted by setting
# the `dominant_cycle_kind` configuration value in {Quant::Config}.
# The default dominant cycle kind is the `supplied_period` slot (formerly `half_period`; the old name
# is deprecated since 0.6.0 and removed in 0.9.0). This can be adjusted by setting the
# `dominant_cycle_kind` configuration value in {Quant::Config}.
#
# Quant.configure_indicators(dominant_cycle_kind: :band_pass)
#
Expand Down
23 changes: 0 additions & 23 deletions lib/quant/indicators/dominant_cycles/half_period.rb

This file was deleted.

171 changes: 171 additions & 0 deletions lib/quant/indicators/dominant_cycles/supplied_period.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
# frozen_string_literal: true

module Quant
module Indicators
module DominantCycles
# `SuppliedPeriod` — a dominant-cycle slot whose period is supplied
# by the caller rather than computed from price data. The `compute`
# method is intentionally a no-op; whatever sets `point.period` is
# the period that downstream indicators reading `adaptive_period`
# will see.
#
# Use this when you want a downstream indicator's lookback window
# to come from somewhere other than the indicator's own cycle
# detection — a fixed constant, an operator setting, a config
# value, or a separate runtime process. Four canonical modes,
# distinguished only by *who* sets the period and *when*:
#
# | Mode | Period source | When set | Example use case |
# |------------|----------------------------------------|------------------|-------------------------------------------|
# | Fixed | Literal value at construction | Once | RSI(14), SMA(20) — classical lookbacks |
# | Manual | Operator-mutable runtime setting | On every read | Settings page, Pool config tuning |
# | Configured | `Quant.config.indicators` symbol | On every read | Project-wide default lookback |
# | External | Substrate written by another process | Per-tick by collector | Tick-derived adaptive period (Fishy peak-count) |
#
# ## Mode 1: Fixed
#
# Period is a literal value, set once at construction, never
# changes. The classical "RSI(14)" / "SMA(20)" shape.
#
# Quant.config.indicators.dominant_cycle_kind = :supplied_period
# indicator = MyIndicator.new(series: series)
# indicator.dominant_cycle.p0.period = 14 # fixed lookback
#
# ## Mode 2: Manual (operator-tunable at runtime)
#
# Period is set from a value the operator changes via UI, config
# file reload, settings page, etc. The slot reads whatever value
# is current when the indicator runs.
#
# point.period = pool.config.fetch(:lookback_window)
#
# When the operator updates `pool.config[:lookback_window]`, the
# next tick sees the new value. No re-instantiation needed.
#
# ## Mode 3: Configured (Quant.config-resolved default)
#
# Period defaults to a symbol (`:half_period`) that resolves
# through `Quant.config.indicators` at indicator construction
# time. This was the original `HalfPeriod` usage — the parent
# `Indicator#half_period` resolves to
# `(min_period + max_period) / 2` from the active config.
#
# The default symbol mechanism resolves once per indicator
# instance; if the operator mutates `Quant.config.indicators`
# after construction, existing indicator instances continue
# using the resolved-at-construction value. Construct a fresh
# indicator to pick up the new config (or use Mode 2 / 4 if
# you need true runtime tracking).
#
# class SuppliedPeriodPoint < IndicatorPoint
# attribute :period, default: :half_period
# end
#
# # consumer site — no explicit set; default symbol resolves:
# indicator.dominant_cycle.p0.period # => 18 (resolved from config)
#
# ## Mode 4: External (runtime-supplied by a separate process)
#
# Period is supplied per-tick by a separate producer writing to
# substrate. The integration site (collector, service, anywhere
# downstream of the producer) reads the substrate and sets
# `point.period` before consumers read `adaptive_period`.
#
# Example pattern: a producer computes a tick-stream peak-to-peak
# period and writes it to substrate. A consuming indicator's
# collector instance reads the substrate value and populates the
# supplied-period slot:
#
# period_seconds = substrate.read(platform, listing).period_seconds
# period_bars = period_seconds / series.interval.to_seconds
# indicator.dominant_cycle.p0.period = period_bars
#
# The substrate-driven external mode is the load-bearing pattern
# for tick-derived adaptive lookbacks — but the slot itself is
# mode-agnostic; consumers compose any of the four modes per
# their need.
#
# ## Why no-op `compute`?
#
# The `DominantCycle` framework's contract is that
# `Indicator#dominant_cycle_indicator_class.compute` runs once per
# tick, then consumers read `point.period`. `SuppliedPeriod`
# short-circuits that — `compute` runs (no-op), and `point.period`
# carries whatever the caller put there. This lets the polymorphic
# DC dispatch (`Indicator#dominant_cycle`) treat externally-driven
# period sources identically to algorithmically-computed ones.
#
# ## Default fallback
#
# When no caller sets `point.period`, the attribute default
# (`:half_period`) provides safe behavior: downstream indicators
# see the midpoint-of-min-max-period config value, which is what
# they got pre-rename with `HalfPeriod`. Backward-compatible.
#
# Note: the default symbol `:half_period` refers to the
# `Settings::Indicators#half_period` method (the `(max+min)/2`
# attribute), NOT the deprecated `HalfPeriod` class. The method
# is not deprecated and is unaffected by this rename.
#
# ## Migration from HalfPeriod (deprecated; removed in 0.9.0)
#
# Prior to 0.6.0 this class was named `HalfPeriod`. The old name
# was descriptive of one resolved value (the midpoint of min/max
# period in config) rather than the underlying capability (a slot
# whose period is supplied externally). The rename clarifies that
# the slot supports four equivalent modes — see table above.
#
# The old names continue to work through 0.8.x but emit Ruby
# deprecation warnings via `Module#deprecate_constant`:
#
# HalfPeriod = SuppliedPeriod # deprecated; warns at access
# HalfPeriodPoint = SuppliedPeriodPoint # deprecated; warns at access
#
# The registry symbol `:half_period` continues to resolve to
# `SuppliedPeriod` for `Quant.config.indicators.dominant_cycle_kind`
# through 0.8.x; the resolver emits a one-time deprecation warning
# per `Quant::Settings::Indicators` instance when `:half_period`
# is the lookup key.
#
# **To migrate** (any 0.6.x or later):
#
# # before:
# Quant.config.indicators.dominant_cycle_kind = :half_period
# point = Quant::Indicators::DominantCycles::HalfPeriodPoint.new(...)
#
# # after:
# Quant.config.indicators.dominant_cycle_kind = :supplied_period
# point = Quant::Indicators::DominantCycles::SuppliedPeriodPoint.new(...)
#
# The point's `period` attribute default stays `:half_period` —
# that's the resolved-via-config symbol (Mode 3) and a sensible
# safe default; it is NOT the deprecated class name and is not
# affected by the rename. Existing consumers see identical
# runtime behavior.
#
# **Removal**: `HalfPeriod` / `HalfPeriodPoint` constants and the
# `:half_period` registry alias are scheduled for removal in
# quantitative 0.9.0. Migrate any time before then.
class SuppliedPeriodPoint < Quant::Indicators::IndicatorPoint
attribute :period, default: :half_period
end

class SuppliedPeriod < DominantCycle
register name: :supplied_period

def compute
# No-Op — `period` is supplied by the caller (see class doc
# for the four canonical supply modes).
end
end

# Deprecated aliases — removed in 0.9.0. See `SuppliedPeriod`
# class doc §Migration for the upgrade path.
HalfPeriodPoint = SuppliedPeriodPoint
HalfPeriod = SuppliedPeriod

deprecate_constant :HalfPeriod
deprecate_constant :HalfPeriodPoint
end
end
end
11 changes: 10 additions & 1 deletion lib/quant/settings.rb
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,23 @@ module Settings
).freeze

DOMINANT_CYCLE_KINDS = %i(
half_period
supplied_period
band_pass
auto_correlation_reversal
homodyne
differential
phase_accumulator
half_period
).freeze

# Deprecated `dominant_cycle_kind` symbols that resolve to current
# symbols via `Settings::Indicators#dominant_cycle_indicator_class`.
# Each access emits a one-time-per-instance deprecation warning.
# Removed in 0.9.0.
DEPRECATED_DOMINANT_CYCLE_KIND_ALIASES = {
half_period: :supplied_period
}.freeze

# ---- Risk Management Ratio Settings ----
# Risk Reward Breakeven Win Rate %
# 50 1 98%
Expand Down
24 changes: 19 additions & 5 deletions lib/quant/settings/indicators.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,19 @@ module Settings
# The micro period comes from Ehler's writings on Swami charts and auto-correlation computations, which
# is a period of 3 bars. It is useful enough in various indicators to be its own setting.
#
# The dominant cycle kind is the kind of dominant cycle to use in the indicator. The default is +:settings+
# which means the dominant cycle is whatever the +max_period+ is set to. It is not adaptive when configured
# this way. The other kinds are adaptive and are computed from the series data. The choices are:
# * +:half_period+ - the half_period is the dominant cycle and is not adaptive
# The dominant cycle kind is the kind of dominant cycle to use in the indicator. The default is +:supplied_period+
# which means the period is supplied by the caller (defaulting to the half_period config value). It is not
# adaptive when configured this way. The other kinds are adaptive and are computed from the series data.
# The choices are:
# * +:supplied_period+ - The period is supplied externally by the caller; not adaptive (see SuppliedPeriod doc
# for the four canonical supply modes: fixed, manual, configured, external). Default.
# * +:band_pass+ - The zero crossings of the band pass filter are used to compute the dominant cycle
# * +:auto_correlation_reversal+ - The dominant cycle is computed from the auto-correlation of the series.
# * +:homodyne+ - The dominant cycle is computed from the homodyne discriminator.
# * +:differential+ - The dominant cycle is computed from the differential discriminator.
# * +:phase_accumulator+ - The dominant cycle is computed from the phase accumulator.
# * +:half_period+ - **Deprecated** alias for +:supplied_period+; removed in 0.9.0. Resolver emits a
# one-time warning per Settings::Indicators instance when this symbol is used.
#
# All of the above are adaptive and are computed from the series data and are described in John Ehlers' books
# and published papers.
Expand Down Expand Up @@ -89,7 +93,17 @@ def compute_half_period
def dominant_cycle_indicator_class
return @dominant_cycle_indicator_class if @dominant_cycle_indicator_class

base_class_name = dominant_cycle_kind.to_s.split("_").map(&:capitalize).join
resolved_kind = Settings::DEPRECATED_DOMINANT_CYCLE_KIND_ALIASES[dominant_cycle_kind] || dominant_cycle_kind
if resolved_kind != dominant_cycle_kind
@dc_kind_deprecation_warned ||= begin
warn "[DEPRECATION] dominant_cycle_kind: :#{dominant_cycle_kind} is renamed to " \
":#{resolved_kind} (scheduled for removal in quantitative 0.9.0). " \
"See Quant::Indicators::DominantCycles::SuppliedPeriod doc §Migration."
true
end
end

base_class_name = resolved_kind.to_s.split("_").map(&:capitalize).join
class_name = "Quant::Indicators::DominantCycles::#{base_class_name}"

@dominant_cycle_indicator_class = Object.const_get(class_name)
Expand Down
2 changes: 1 addition & 1 deletion lib/quant/version.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# frozen_string_literal: true

module Quant
VERSION = "0.5.0"
VERSION = "0.6.0"
end
4 changes: 2 additions & 2 deletions spec/lib/quant/config_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
it { expect(subject.half_period).to eq(7) }
it { expect(subject.micro_period).to eq(2) }
it { expect(subject.pivot_kind).to eq(:fibbonacci) }
it { expect(subject.dominant_cycle_kind).to eq(:half_period) }
it { expect(subject.dominant_cycle_kind).to eq(:supplied_period) }
end

context "by block" do
Expand All @@ -47,7 +47,7 @@
it { expect(subject.min_period).to eq(2) }
it { expect(subject.half_period).to eq(3) }
it { expect(subject.micro_period).to eq(4) }
it { expect(subject.dominant_cycle_kind).to eq(:half_period) }
it { expect(subject.dominant_cycle_kind).to eq(:supplied_period) }
it { expect(subject.pivot_kind).to eq(:bollinger) }

it "configures twice" do
Expand Down
2 changes: 1 addition & 1 deletion spec/lib/quant/dominant_cycles_source_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -23,5 +23,5 @@

it { expect(subject.differential.values[-1].period).to eq(41) }
it { expect(subject.phase_accumulator.values[-1].period).to eq(41) }
it { expect(subject.half_period.values[-1].period).to eq(29) }
it { expect(subject.supplied_period.values[-1].period).to eq(29) }
end
42 changes: 0 additions & 42 deletions spec/lib/quant/indicators/dominant_cycles/half_period_spec.rb

This file was deleted.

Loading
Loading