# Changelog of Technical Stock Screener & Backtester (US / TW) (`yzy001/technical-stock-screener`) Actor

- **URL**: https://apify.com/yzy001/technical-stock-screener/changelog.md
- **Full Actor documentation**: https://apify.com/yzy001/technical-stock-screener.md

## Changelog — stock-screener-actor

### 2026-10-13 — new conditions, taiwan long-history data layer, local long backtest engine

Backups before the change (verified file count + per-file sha256): ~/apify/backups/{stock-screener,tw-kd-screener,breakout,actors-lib}\_2026-10-13_pre-upgrade (each has BACKUP_NOTE.md). This directory was never tracked by git, so there is no tag.

#### Added — rule conditions (src/lib/indicators.py, rulespec.py, btengine.py)

- ind prior_range (HH/LL/HC/LC/AMP over the previous n bars, excluding today), ma_cluster (CL, lag), turnover (TO/TOP, traded value), high_n (H52/L52/DTH52/FRL52), kd_runs (K/D run lengths, KD blunting 鈍化), macd_live (weekly/monthly MACD including the unfinished week, no look-ahead).
- Condition keys: gte / lte comparators, lag (read the previous bar), px (raw / adjusted price), params.min_bars (warm-up).
- All new indicators are strict: not enough history -> value missing -> condition false.
- btengine.build_series accepts amount / raw prices / min_bars. data.py and main.py now pass traded value (7th tuple element) so turnover works in the Actor TW path.
- Fixed: rulespec.validate_rule used to reject synthetic b-dict conditions yielded by iter_conds (latent bug); twdata.limit_up/limit_dn float rounding.

#### Added — local data layer (src/lib/twfetch.py, twdata.py) — NOT used by the Apify Actor run

- twfetch.py: resumable, rate-limited, official-source-only fetcher (TWSE MI_INDEX, TPEx afterTrading/otc, TWT49U / exDailyQ ex-dividend tables, TAIEX price + total-return index). Failure is never recorded as holiday; 8 consecutive failures stop the run.
- twdata.py: raw files -> store.sqlite (incremental), back-adjusted (還原) price loader using the official ex-rights table (reference price after dividend / pre-ex close), limit-up/down from official columns, unmarked-break detection (capital reduction, ETF split, par change), delisting visible (no survivorship filter), quality_report().

#### Added — local long-history backtest engine (src/lib/longbt.py)

- Entry at the next market day's open; skip limit-up open; limit-down locked exits are deferred; halts deferred; delisted names closed at last close with a configurable haircut; cost 0.585% round trip (fee 0.1425% x2 + tax 0.3%); back-adjusted or raw basis.
- Equal-weight portfolio, max drawdown, 0050 / TAIEX total-return / whole-market benchmarks, by-year and bull/bear slices, n<30 warning, signal-day clustering (Kish effective sample size), 95% CI (naive and signal-day cluster bootstrap).
- Rules and exits are evaluated by the same rulespec/btengine as the Actor.

#### Added — examples and tests

- examples/rules_breakout_A.json (10 conditions), rules_breakout_B.json (A + MA cluster + liquidity).
- test_longbt.py (48 tests), tests/regress_compare.py (old lib vs new lib, same rules and data: entries, trades, matches_now).
- Shared lib files synced to ~/apify/actors/lib via tools/sync_lib.sh (FILES list extended: indicators rulespec btengine twfetch twdata longbt).

#### Compatibility

- Existing rules give bit-identical entries and trades (regress_compare.py: 9 rules x 1976 stocks on the old 117-day cache; 9 rules x 120 random stocks on the long data, 2020-01..2026-10, raw prices: zero differences).
- Behaviour differences of longbt vs the previous half-year script (documented, not bugs): positions still open at the end of data are marked open and excluded from closed stats (the old script closed them at max_hold on the last bar); signals whose own bar is a data break are excluded.

#### Known limits

See README "Data layer" and "Local long-history backtest".

### 2026-10-14 — weekly/monthly conditions evaluated as of each day; README cleanup

- **Changed (backtest semantics):** `tf:"W"`/`"M"` conditions now see the period in progress up to the evaluated day
  (btengine.build_series computes per-day values from completed periods + the partial period; rulespec reads them
  for the current period). Before, mid-period days used the last completed period, so backtest and screen disagreed.
  Checked: 200 random rules, full-history vs cut-at-day evaluation identical (79 differed before); daily-only rules
  (61, entry and exit) bit-identical to the published engine; on the last day of each period weekly/monthly
  decisions identical to before (49,751 mid-period days changed). Tools: tools/engine_equivalence.py,
  tools/engine_lookahead.py. Cost: 1,260 daily bars with 3 weekly/monthly conditions ≈ 0.14 s per symbol.
- **Fixed (published 0.1.9 bug, by the 2026-10-13 upgrade):** rules whose `b` is another series spec
  (MA5 crossing MA60) were refused as malformed.
- README: local-only data layer and long-backtest sections moved to NOTES.md (they described tools the Actor
  does not run and paths on the developer's machine); macd_live row and No look-ahead row updated.
- **Fixed (published 0.1.9 bug):** a rule the data cannot support (px:"raw", turnover on US data, an unknown
  indicator, volume on data without volume) returned `{"error": ...}` with zero rows, i.e. a SUCCEEDED run with an
  empty dataset. It is now refused (RuleCannotEvaluate, input level: no rows, no per-row charge, status message).
