Skip to content

Releases: markudevelop/msts-trader

v0.25.2

23 Jun 18:58

Choose a tag to compare

Fixed

  • CHANGELOG version headings weren't clickable (links stopped at 0.16.2). The Keep-a-Changelog link-reference footer was maintained by hand and silently drifted — every version from 0.17.0 through 0.25.1 had no [x.y.z]: definition, so those headings rendered as plain text instead of linking to their compare diff. Regenerated the full footer from the headings (each version → compare from its nearest tagged predecessor; the never-tagged 0.21.0 spans v0.20.1...v0.22.0), and added a test (test_every_version_heading_has_a_link_definition) that fails if any heading lacks a link definition, so it can't drift again.

Full Changelog: v0.25.1...v0.25.2

v0.25.1

23 Jun 17:49

Choose a tag to compare

Fixed

  • Self-heal could double a position when positions() lags the fill. Post-trade verify rebuilt the diff purely from broker.positions() after a 2s settle; on an eventually-consistent broker (e.g. Tastytrade's REST, which can lag the fill by several seconds) a just-filled market order still read as residual, so self-heal re-executed it as a second market order that filled immediately — doubling the position. This is the same bug class as the MOC self-heal double-fire (0.25.0) but it bites normal market orders on any lagging broker. Self-heal now never re-trades a leg that was already cleanly sent in the same run (a market order isn't idempotent; a residual there is almost certainly a position-read lag, not a real miss). Genuinely-failed legs (rejected/skipped/resting) were never recorded as clean sends, so they still heal; a true miss on a just-sent leg reconciles on the next run.
  • Release notes were always the generic fallback. The release workflow extracted the CHANGELOG section with an inline awk that fed ## [x.y.z] to a regex match — the brackets were read as a character class, so it never matched and every GitHub release showed "See CHANGELOG.md for details." instead of the real notes. Extraction moved to an import-tested helper (msts_trader/changelog.py, literal-heading match) and the release now also runs --generate-notes, so each release shows the changelog plus clickable commit/PR links and a "Full Changelog" compare link.

Tests / robustness

  • tests/test_self_heal.py: self-heal skips a just-traded leg when positions lag (no double-trade) and still heals a leg that was not traded this run.
  • tests/test_changelog.py: release-notes extraction (bracketed heading, tag form, missing/blank version, last-section-to-EOF, and a guard that the repo's own CHANGELOG yields non-empty notes for the current version).
  • tests/test_cli.py: MOC + limit-chase mutual-exclusion is refused; a live MOC run inside the ~15:50 ET cutoff is refused. (Companion to the MOC self-heal fix in 0.25.0 — locks the guards so they can't regress.)
  • msts_trader/changelog CLI writes UTF-8 bytes so the em-dashes/arrows in the changelog can't crash it on a legacy Windows console.

Full Changelog: v0.25.0...v0.25.1

v0.25.0

23 Jun 16:46

Choose a tag to compare

Added

  • liquidate command — flatten an account to cash via the patient limit-chase. Sells every long / buys back every short, largest position first, working each line as a LIMIT pegged to the mid (chased over rungs) with a MARKET mop-up for the unfilled / fractional remainder — captures the spread instead of crossing it. RTH-only for live execution; --dry-run previews any time. Knobs: --only / --exclude, --aggression (negative = more passive/better price), --retries, --interval, --pace (delay between names), --no-fallback, --json. Live execution requires confirmation or --yes. Resting protective stops on names being sold are cancelled first. New module msts_trader/liquidate.py (planner + runner) reuses the existing chase engine and broker adapter primitives.

Fixed

  • MOC orders no longer double-execute (IBKR / any MOC broker). A market-on-close order fills only in the closing auction, but the post-trade verify ran with self-heal enabled and re-checked convergence ~2s after submission. Seeing the book still "unconverged" (the MOC hadn't filled), self-heal re-executed the residual as a plain MARKET order that filled immediately — so each position got one immediate market fill plus the resting MOC. Self-heal is now disabled for MOC runs (convergence is only meaningful after the auction); a one-line note explains why. Affected IBKR, Alpaca, Schwab, and paper MOC runs.

Tests

  • tests/test_liquidate.py: planner (longs→SELL, shorts→BUY, largest-first, only/exclude, flat/fractional handling), runner fills, dry-run sends nothing, fractional remainder routes to the market mop-up, and config aggression/fallback wiring.
  • tests/test_self_heal.py::test_moc_run_never_self_heals: a MOC run with self_heal=True reports only and never re-executes.

Full Changelog: v0.24.4...v0.25.0

v0.24.4

22 Jun 08:07

Choose a tag to compare

Fixed

  • paper-reset now honors configured starting cash. The command always reset to the hardcoded $100k default even when login (or PAPER_STARTING_CASH) stored a different amount — contradicting the existing-book login hint. Reset now resolves cash from env/creds-file, then keychain, then the default.

Improved

  • Schwab headless creds accept optional SCHWAB_ACCOUNT_HASH (or account_hash in a creds file) so automated runs can skip re-discovering the account hash.

Tests

  • 2028 NYSE holiday + early-close calendar coverage.
  • Paper login existing-book UX, paper-reset keychain cash, and Paper.reset() explicit/default starting cash.
  • Schwab SCHWAB_ACCOUNT_HASH env + creds-file alias.

Full Changelog: v0.24.3...v0.24.4

v0.24.3

21 Jun 16:24

Choose a tag to compare

Fixed

  • Schwab adapter: login no longer reaches into private _account_hash. The class now exposes a stable public account_hash attribute (used for token-file reuse). Login flow + test mocks updated.
  • Paper broker: removed dead raise BrokerError signalling code in reset() (never reached by the CLI and would incorrectly raise on some explicit-starting-cash calls). reset() now cleanly succeeds.
  • Paper login UX: when running msts-trader login --broker paper against an existing book, the entered starting cash is now clearly noted as applying only to brand-new state files (with hint to use paper-reset).

Improved

  • Market hours: holiday + early-close calendars extended through 2028 using official NYSE dates. New Year 2028 (falls on Saturday) correctly has no observed trading holiday. Comment + README updated.
  • Development: added ruff format example + [tool.ruff.format] config. Main source package (msts_trader/) is now consistently formatted.
  • Ruff format run on the library source (24 files) as part of release hygiene.

What's Changed

  • fix(stops): reconcile protective stops on no-trade and duplicate days by @markudevelop in #8
  • fix: NYSE half-day early closes + atomic/locked runstate writes by @markudevelop in #9

Full Changelog: v0.24.2...v0.24.3

v0.24.2

20 Jun 11:03
f458616

Choose a tag to compare

Fixed

  • Backfilled protective stops now anchor on the live quote, not the position's
    price.
    Some adapters (Tradier) report a position's price as average cost,
    not market; a stop re-placed for a held-but-untraded name was therefore set at
    cost × (1 − stop_pct) — the wrong distance (too loose on a name that ran up,
    possibly above market on one underwater). The backfill now prefers a fresh quote
    and only falls back to the position price if no quote is available. Fresh-fill
    anchoring (a name bought this run) is unchanged.
  • A Hyperliquid market order that only RESTS (unfilled remainder on a thin book)
    is no longer counted as a clean fill.
    It's now treated as not-done, so the run
    isn't marked complete (idempotency stays open for a re-run) and the post-trade
    verify / self-heal chases the unfilled remainder. Other adapters are unaffected
    (they never return a resting status).

What's Changed

  • fix(adapters): live-quote stop anchor + resting-order not a clean fill (0.24.2) by @markudevelop in #7

Full Changelog: v0.24.1...v0.24.2

v0.24.1

20 Jun 10:53
3ff0d60

Choose a tag to compare

Fixed

  • Idempotency fingerprint now includes the execution params (allocation,
    rebalance-scope, sweep, threshold, threshold-mode, whole-shares, min-weight),
    not just broker/account/targets. Before, a deliberate second same-day run that
    changed how the book is executed (e.g. --rebalance-scope per-ticker,
    --no-sweep, a different --allocation) hashed identically to the first and
    was silently skipped as a duplicate. Such a run is a different plan and now
    executes. Same-plan re-runs still dedupe; --force still overrides.
  • Post-trade verify is now buying-power-aware under --margin-aware. A fully
    invested / margin-limited book no longer reports "🔴 NOT converged" for a
    residual buy it cannot fund, and self-heal no longer re-submits unaffordable
    buys
    every pass. A residual buy that can't be funded from buying power (+ any
    residual sell proceeds) is treated as converged (the book is as-deployed-as
    -possible) and removed so self-heal skips it; an affordable residual buy still
    flags non-convergence, and a failed close (residual SELL) is never excused.

Audit

  • Production-readiness sweep of the execution path found no critical
    sign/rounding/double-execution bugs; the core sizing math is sound. Remaining
    open items (broker-adapter stop round-trip specifics for Schwab/Tradier/
    Hyperliquid) need live-API confirmation and are tracked separately.

What's Changed

  • fix: production-readiness audit fixes — idempotency fingerprint + buying-power-aware verify (0.24.1) by @markudevelop in #6

Full Changelog: v0.24.0...v0.24.1

v0.24.0

19 Jun 21:21
a040c3b

Choose a tag to compare

Added

  • rebalance --no-sweep (or sweep = false in the config / per [[account]]):
    touch only the tickers listed in the CSV and leave every other held
    position untouched — for running a strategy sleeve inside a mixed account.
    • Default (--sweep) is unchanged: the CSV is the complete book, so any held
      ticker not in it is liquidated (the account-wide sweep).
    • Under --no-sweep, close a rotated-out name by listing it explicitly with
      weight 0 (handled by the existing weight-0 exit path). Held-but-unlisted
      positions are surfaced in the preview as kept — not in targets (--no-sweep)
      with no order, and never trigger a whole-book snap.
    • Threaded through rebalance, multi, and the post-trade verify/self-heal path.

Note

  • This addresses a long-standing footgun: pointing the rebalance at an account
    that holds positions outside the target CSV would liquidate them. --no-sweep
    makes that opt-out explicit. Companion change in msts-live: the published
    weights feed can emit weight=0 rows for rotated-out names (the removed set
    it already computes) so liquidations are explicit rather than sweep-implicit.

What's Changed

  • feat(rebalance): --no-sweep — leave unlisted positions untouched (0.24.0) by @markudevelop in #5

Full Changelog: v0.23.0...v0.24.0

v0.23.0

19 Jun 20:51
241ebb6

Choose a tag to compare

Added

  • rebalance --rebalance-scope whole-book|per-ticker (or rebalance_scope
    in the config / per [[account]]): choose how the drift threshold is applied.
    • whole-book (NEW DEFAULT): the threshold is a trigger — if any line
      breaches it, the entire book is snapped to target. Mirrors msts-live's
      all-or-nothing dead zone. On a full-period backtest of the PNL Unified book
      (2015–2026) this delivered higher gross and net CAGR (+~9pp net) than
      per-ticker, at ~1.3× the turnover.
    • per-ticker: trade only the breaching lines, leave the rest at their
      drifted value (the prior behavior). Lower turnover, better Sharpe/drawdown.
    • Orthogonal to --threshold-mode (which only sets the drift denominator).

Changed

  • BREAKING (default behavior): the rebalance now defaults to whole-book
    scope. Existing books that relied on the old per-line partial behavior should
    set rebalance_scope = "per-ticker" (config) or pass --rebalance-scope per-ticker. A pleasant side effect of the new default: small sleeves below the
    drift gate are no longer frozen on a fresh account — once any line breaches,
    the whole book (including sub-threshold sleeves above the $1 dust floor) is
    established in one pass.
  • The post-trade verify / self-heal path honors the same rebalance_scope, and
    the multi command now threads both rebalance_scope and threshold_mode
    (top-level or per [[account]]).

What's Changed

  • feat(rebalance): selectable execution scope — whole-book (default) vs per-ticker (0.23.0) by @markudevelop in #4

Full Changelog: v0.22.1...v0.23.0

v0.22.1

19 Jun 12:12
272d846

Choose a tag to compare

Docs

  • README now documents post-trade verify, self-heal, and their flags (--no-verify,
    --no-self-heal, --heal-passes).
  • Clarified that protective stops are OPT-IN. The 0.20.0 --stop-pct note read as if stops are
    always applied; they are not. With no per-row stop_pct and no --stop-pct, no stops are
    placed
    , and a rebalance never strips an existing stop off a still-held position (only orphan
    stops with no position are cancelled). --stop-pct is a blanket default you opt into for feeds
    that omit stops but want every leg protected; a mixed book (e.g. some held-forever positions)
    should rely on per-row stop_pct instead.

Tests

  • Added opt-in coverage: no stop_pct anywhere → zero stops placed; a held position's existing
    stop survives a no-stop_pct rebalance.

What's Changed

  • docs+test(stops): document verify/self-heal; clarify stops are opt-in (0.22.1) by @markudevelop in #3

Full Changelog: v0.22.0...v0.22.1