diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7fba991..924b930 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -35,7 +35,8 @@ jobs: run: bundle exec rspec - name: Upload coverage reports to Codecov - uses: codecov/codecov-action@v4.0.1 + uses: codecov/codecov-action@v5 with: token: ${{ secrets.CODECOV_TOKEN }} slug: mwlang/quantitative + continue-on-error: true diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..8a6b0b8 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,50 @@ +# Changelog + +All notable changes to `quantitative` will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +--- + +## [0.5.0] — 2026-05-22 + +### Changed (breaking) + +- **`Quant::AssetClass::CLASSES` renamed `:treasury_note` → `:treasury`.** The single `:treasury` symbol now covers the full US-government-debt family (bills, notes, bonds, TIPS). Maturity becomes listing metadata, not an AssetClass distinction. Breaking for consumers that exhaustively switch on the `CLASSES` set or pattern-match on `:treasury_note`. + + Migration: `s/:treasury_note/:treasury/g` across consumer code. Where maturity-aware behavior is needed (e.g., yield-curve segmentation), consult the listing's metadata rather than the AssetClass label. + +### Added + +- `Quant::AssetClass::CLASSES` gains six new entries: `:adr, :cash, :etn, :forward, :swap, :warrant`. Final catalog is 19 entries: `adr, bond, cash, commodity, cryptocurrency, etf, etn, forex, forward, future, mbs, mutual_fund, option, preferred_stock, reit, stock, swap, treasury, warrant`. +- Per-class spec coverage in `spec/lib/quant/asset_class_spec.rb` — each catalog entry is `valid?`, the 19-count invariant is asserted, and `:treasury_note` rejection is a regression guard against re-introducing the retired symbol. + +--- + +## [0.4.1] — 2026-05 + +### Fixed + +- **`Quant::Ticks::OHLC#initialize` and `Quant::Ticks::Spot#initialize` now preserve fractional volumes as `Float`.** A prior regression in both classes coerced `@base_volume` and `@target_volume` via `.to_i`, silently truncating fractional values to integers — a serious correctness issue for crypto markets (and any other domain where fractional base/target volumes are normal, such as fractional-share trading and many futures markets). For example, a kline with `base_volume: 0.12345` was previously stored as `0`. Existing spec assertions used `eq(2.0)` which masked the regression because Ruby's `==` coerces `2 == 2.0 → true`; the values *appeared* correct in tests while being silently truncated in production. Both initializers now coerce via `.to_f`. Trade count (`@trades`) continues to use `.to_i` as it is genuinely an integer. + + Affected files: + - `lib/quant/ticks/ohlc.rb` (lines 51–52) + - `lib/quant/ticks/spot.rb` (lines 46–47) + + Backwards compatibility: this is a behavior change for callers reading `tick.base_volume` / `tick.target_volume`. Code that did `volume == 2` continues to work (Ruby coerces). Code that did `volume.is_a?(Integer)` will see `false` where it previously saw `true`; such code was implicitly relying on the bug and should be updated to `is_a?(Numeric)` or `is_a?(Float)`. Code that did `volume + 0.5` continues to work, but the result is now precise instead of truncating to integer-then-promoting. + +### Added + +- Regression-guard specs in `spec/lib/quant/ticks/ohlc_spec.rb` and `spec/lib/quant/ticks/spot_spec.rb` that explicitly assert `Float` class membership and use fractional values (`0.12345`, `5_678.901_234`) so a future `.to_i` regression would fail loudly rather than silently coerce. +- This CHANGELOG. + +--- + +## [0.4.0] — prior baseline + +Prior baseline before this CHANGELOG was introduced. Subsequent entries above will track changes from 0.4.1 forward. + +[0.5.0]: https://github.com/mwlang/quantitative/compare/v0.4.1...v0.5.0 +[0.4.1]: https://github.com/mwlang/quantitative/compare/v0.4.0...v0.4.1 +[0.4.0]: https://github.com/mwlang/quantitative/releases/tag/v0.4.0 diff --git a/Gemfile.lock b/Gemfile.lock index ccc6381..bf2bf2a 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,7 +1,7 @@ PATH remote: . specs: - quantitative (0.4.1) + quantitative (0.5.0) csv oj (~> 3.10) zeitwerk (~> 2.6) diff --git a/lib/quant/asset_class.rb b/lib/quant/asset_class.rb index f327343..a5036ef 100644 --- a/lib/quant/asset_class.rb +++ b/lib/quant/asset_class.rb @@ -42,11 +42,15 @@ module Quant # profit from changes in exchange rates. class AssetClass CLASSES = %i( + adr bond + cash commodity cryptocurrency etf + etn forex + forward future mbs mutual_fund @@ -54,7 +58,9 @@ class AssetClass preferred_stock reit stock - treasury_note + swap + treasury + warrant ).freeze attr_reader :asset_class diff --git a/lib/quant/version.rb b/lib/quant/version.rb index 8f90815..0e9aabe 100644 --- a/lib/quant/version.rb +++ b/lib/quant/version.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true module Quant - VERSION = "0.4.1" + VERSION = "0.5.0" end diff --git a/spec/lib/quant/asset_class_spec.rb b/spec/lib/quant/asset_class_spec.rb index f19269e..4480912 100644 --- a/spec/lib/quant/asset_class_spec.rb +++ b/spec/lib/quant/asset_class_spec.rb @@ -57,4 +57,23 @@ expect { subject }.to raise_error Quant::Errors::AssetClassError, 'Unknown asset class: "bogus"' end end + + describe "CLASSES catalog" do + it "contains 19 entries" do + expect(described_class::CLASSES.size).to eq 19 + end + + %i[adr bond cash commodity cryptocurrency etf etn forex forward future mbs + mutual_fund option preferred_stock reit stock swap treasury warrant].each do |name| + it "accepts :#{name}" do + expect { described_class.new(name) }.not_to raise_error + expect(described_class.new(name).asset_class).to eq name + end + end + + it "rejects the removed :treasury_note" do + expect { described_class.new(:treasury_note) } + .to raise_error Quant::Errors::AssetClassError, "Unknown asset class: :treasury_note" + end + end end