Covers analysis/regime.py, analysis/scanner.py, and analysis/momentum.py.
Classifies a price timeseries into one of three regimes using two statistical tests.
Decision rule:
| Condition | Regime |
|---|---|
| ADF (Augmented Dickey-Fuller) p-value < 0.05 | MEAN_REVERTING |
| ADF p-value ≥ 0.05 AND linear R² ≥ 0.25 | TRENDING |
| Otherwise | FLIP |
FLIP is the default because the bid-ask spread strategy requires no assumptions about price dynamics.
Description: Enum of possible market regimes.
Values:
Regime.MEAN_REVERTING— prices oscillate around a stable level; buy dips, sell at mean.Regime.TRENDING— prices drift persistently in one direction; momentum strategy.Regime.FLIP— no strong structure detected; capture the bid-ask spread.
Description: Output of classify_regime().
Fields:
| Field | Type | Description |
|---|---|---|
regime |
Regime |
Classified regime |
adf_pvalue |
float |
p-value from the ADF unit-root test |
trend_r2 |
float |
R² of a linear fit on mid-price |
trend_slope_pct |
float |
Per-bar slope as fraction of mean price (positive = upward drift) |
notes |
str |
Human-readable classification rationale |
Description: Classify the market regime of a single item's price timeseries.
Arguments:
ts_df(pd.DataFrame): Timeseries withavgHighPriceandavgLowPricecolumns, indexed by UTC datetime.min_bars(int): Minimum non-NaN observations required. ReturnsFLIPif too short. Default30.
Returns: RegimeResult.
Example:
from analysis.regime import classify_regime, Regime
from market.prices import get_timeseries
ts = get_timeseries(2)
result = classify_regime(ts)
print(result.regime, result.adf_pvalue, result.trend_r2)
# Regime.FLIP 0.723 0.04Bulk scorer with regime-aware strategy routing. The primary entry point for the live scan pipeline is run_scan(); individual items are scored via score_item_routed().
Description: Compute short-term mean-reversion features (rolling mean, std, z-score, net margin) from a raw timeseries DataFrame.
Arguments:
ts_df(pd.DataFrame): Raw 1h timeseries fromWikiClient.
Returns: Copy of ts_df with additional columns: mid, volume, net_margin, net_margin_pct, roll_mean, roll_std, z_score.
Example:
from analysis.scanner import compute_features_st
from market.prices import get_timeseries
ts = get_timeseries(2)
feat = compute_features_st(ts)
print(feat[["mid", "z_score"]].tail(5))Description: Walk-forward mean-reversion simulation on a pre-computed features DataFrame.
Entry when z ≤ ST_Z_ENTRY and volume is sufficient. Exit when z ≥ ST_Z_EXIT, z ≤ ST_Z_STOP (stop-loss), hold exceeds ST_MAX_HOLD_HOURS, or data ends.
Arguments:
feat(pd.DataFrame): Output ofcompute_features_st.buy_limit(int): Per-4h GE (Grand Exchange) buy limit.capital(float): Available GP (gold pieces) for position sizing.
Returns: list[dict] of trade records. Each dict has: entry_time, exit_time, entry_price, exit_price, pnl_pct, pnl_gp, hold_bars, units, exit_reason.
Description: Classify an item's regime and route it to the appropriate strategy scorer.
Routing: MEAN_REVERTING → z-score simulation; TRENDING → momentum simulation; FLIP → margin-capture. Always computes the flip score as a baseline — the directional strategy is chosen only if it beats the flip score on expected_gp_24h.
Arguments:
ts_df(pd.DataFrame): 1h timeseries DataFrame.item_id(int): OSRS item ID.item_name(str): Human-readable name.buy_limit(int): Per-4h GE buy limit.capital(float): Available GP.
Returns: Score dict | None. The dict always includes "regime", "strategy", "expected_gp_24h", "win_rate", "avg_pnl_pct", and "trades_per_24h". Returns None if no scoreable signal is found or expected GP is non-positive.
Example:
from analysis.scanner import score_item_routed
from market.prices import get_timeseries, get_mapping
ts = get_timeseries(2)
mapping = get_mapping()
score = score_item_routed(ts, 2, "Cannonball", int(mapping.at[2, "limit"]), 10_000_000)
if score:
print(score["strategy"], score["expected_gp_24h"])Description: Full 24h scan pipeline: pre-filter all items by fast score, fetch timeseries for the top candidates, classify regime, score with the appropriate strategy, and return results sorted by expected GP/day.
Arguments:
capital(float): GP available. Defaultconfig.TOTAL_CAPITAL.top_n_display(int): Number of rows to print. Default15.force_refresh(bool): Force-refresh all cached timeseries. DefaultFalse.show_cache(bool): Print cache status table before scanning. DefaultFalse.quiet(bool): Suppress all terminal output. DefaultFalse.
Returns: pd.DataFrame sorted by expected_gp_24h descending. Empty DataFrame if no items pass the filters.
Example:
python -m analysis.scanner --top 10 --capital 50000000from analysis.scanner import run_scan
df = run_scan(capital=10_000_000, quiet=True)
print(df[["item_name", "strategy", "expected_gp_24h"]].head(5))⚠ The momentum signal currently has negative Sharpe across all tested parameters. It is disabled in live trading via a sentinel threshold (MOM_SLOPE_ENTRY_THRESHOLD = 0.010). The code is retained for research and calibration purposes.
The primary signal is the rolling linear slope of mid-price, normalised by mean price. This is more robust than point-to-point momentum (φ) because it fits a line through all bars in the window rather than comparing two noisy endpoints.
Description: Compute momentum features from a raw 1h timeseries DataFrame.
Arguments:
ts_df(pd.DataFrame): Raw timeseries withavgHighPrice,avgLowPrice,highPriceVolume,lowPriceVolume.lookback(int | None): Linear fit window size in bars. Defaults toconfig.MOM_LOOKBACK_HOURS.
Returns: Copy of ts_df with additional columns:
| Column | Description |
|---|---|
mid |
(avgHighPrice + avgLowPrice) / 2 |
volume |
highPriceVolume + lowPriceVolume |
slope |
Rolling linear slope, normalised by mean price (entry/exit signal) |
Example:
from analysis.momentum import compute_momentum_features
from market.prices import get_timeseries
ts = get_timeseries(11832)
feat = compute_momentum_features(ts, lookback=24)
print(feat[["mid", "slope"]].tail(5))Description: Walk-forward momentum simulation on a pre-computed features DataFrame.
Entry when slope ≥ entry_threshold and market is liquid. Exit when slope ≤ reversal_threshold, hold exceeds MOM_MAX_HOLD_HOURS, or data ends.
Arguments:
feat(pd.DataFrame): Output ofcompute_momentum_features.buy_limit(int): Per-4h GE buy limit.capital(float): Available GP.entry_threshold(float | None): Overrideconfig.MOM_SLOPE_ENTRY_THRESHOLD.reversal_threshold(float | None): Overrideconfig.MOM_SLOPE_REVERSAL_THRESHOLD.
Returns: list[dict] of trade records with keys: entry_time, exit_time, entry_price, exit_price, pnl_pct, pnl_gp, hold_bars, units, exit_reason.