{"id":"841ee4ce-da62-4447-9855-ec3441230edc","authorId":"e1bbfa18-4766-4421-b638-f9c14a6b81b6","title":"Portfolio Regime Research with RLX","slug":"portfolio-regime-research-with-rlx","excerpt":"How to find profitable market entry regimes across multiple strategies and turn them into reusable allowlists.","content":"\n\n---\n\n## Overview\n\nThis article demonstrates an **institutional-style research workflow** for regime-aware trading systems using **RLX Backtester** tools.\n\nWe will go from:\n\n1. A **multi-strategy portfolio backtest**\n2. → **regime-enriched trade breakdowns**\n3. → **per-strategy allowlists** and **global regime candidates**\n\nAll analysis is driven by realized trade outcomes — not subjective regime definitions.\n\nThe core primitive enabling this workflow is:\n\n```python\nBacktester.breakdowns(result, data=data)\n```\n\nThis method enriches each trade with *entry regime labels* such as:\n\n```\nuptrend:vol=high:atr=high\n```\n\n---\n\n## Why Regime Research Matters\n\nMost strategies fail not because the signal logic is wrong, but because **they trade in the wrong environments**.\n\nInstead of asking:\n\n> *“What regime should this strategy trade?”*\n\nWe ask:\n\n> *“In which regimes did this strategy actually make money?”*\n\nThis allows us to:\n\n* Disable trading in hostile regimes\n* Build **guardrails instead of curve-fits**\n* Share regime knowledge across strategies\n\n---\n\n## Prerequisites\n\nSet your RLX license key:\n\n```bash\nexport RLX_LICENSE_KEY=\"rlx_free_...\"\n```\n\n(Optional) Persist generated allowlists:\n\n```bash\nexport RLX_ALLOWLIST_OUT=\"/tmp/rlx_allowlists.json\"\n```\n\n---\n\n## Dataset\n\nThe demo uses:\n\n```\ndata/BTCUSDT_1h_with_indicators.csv\n```\n\nThis dataset includes:\n\n* `high`, `low`, `close`\n* volatility and ATR-derived indicators\n\nWhich enables **ATR bucket regime enrichment** during breakdown analysis.\n\n---\n\n## The Full Research Script\n\nBelow is the **complete, self-contained Python file** used in this article.\nYou can copy it directly into your project.\n\n---\n\n```python\n#!/usr/bin/env python3\n\"\"\"Portfolio Regime Research Demo (Institutional + Research helpers)\n\nGoal:\n- Run a multi-strategy portfolio via PortfolioManager\n- For each strategy result, compute research breakdowns with market-regime enrichment\n  using Backtester.breakdowns(..., data=data)\n- Compare which entry regimes are profitable per strategy\n\"\"\"\n\nimport os\nimport sys\nfrom typing import Dict, Optional\nimport json\n\nimport pandas as pd\n\n# Add project root to path\nsys.path.insert(0, os.path.join(os.path.dirname(__file__), \"../../\"))\n\nfrom rlxbt import Strategy, load_data, PortfolioManager, Backtester\n\n\nclass SmaCrossover(Strategy):\n    def __init__(self, fast: int = 10, slow: int = 30):\n        super().__init__()\n        self.fast = fast\n        self.slow = slow\n\n    def generate_signals(self, data: pd.DataFrame) -> pd.DataFrame:\n        close = data[\"close\"]\n        fast_ma = close.rolling(self.fast).mean()\n        slow_ma = close.rolling(self.slow).mean()\n\n        signal = pd.Series(0, index=data.index)\n        signal[fast_ma > slow_ma] = 1\n        signal[fast_ma < slow_ma] = -1\n        return pd.DataFrame({\"signal\": signal}, index=data.index)\n\n\nclass RsiMeanReversion(Strategy):\n    def __init__(self, period: int = 14, oversold: float = 30.0, overbought: float = 70.0):\n        super().__init__()\n        self.period = period\n        self.oversold = oversold\n        self.overbought = overbought\n\n    def _rsi(self, prices: pd.Series) -> pd.Series:\n        delta = prices.diff()\n        gain = delta.clip(lower=0).rolling(self.period).mean()\n        loss = (-delta.clip(upper=0)).rolling(self.period).mean()\n        rs = gain / loss.replace(0, pd.NA)\n        return 100 - (100 / (1 + rs))\n\n    def generate_signals(self, data: pd.DataFrame) -> pd.DataFrame:\n        close = data[\"close\"]\n        rsi = self._rsi(close)\n\n        signal = pd.Series(0, index=data.index)\n        signal[rsi < self.oversold] = 1\n        signal[rsi > self.overbought] = -1\n        return pd.DataFrame({\"signal\": signal}, index=data.index)\n\n\nclass BollingerBreakout(Strategy):\n    def __init__(self, lookback: int = 20, num_std: float = 2.0):\n        super().__init__()\n        self.lookback = lookback\n        self.num_std = num_std\n\n    def generate_signals(self, data: pd.DataFrame) -> pd.DataFrame:\n        close = data[\"close\"]\n        ma = close.rolling(self.lookback).mean()\n        std = close.rolling(self.lookback).std()\n        upper = ma + self.num_std * std\n        lower = ma - self.num_std * std\n\n        signal = pd.Series(0, index=data.index)\n        signal[close > upper] = 1\n        signal[close < lower] = -1\n        return pd.DataFrame({\"signal\": signal}, index=data.index)\n\n\ndef main(license_key: Optional[str] = None) -> None:\n    data = load_data(\"data/BTCUSDT_1h_with_indicators.csv\")\n    data = data.iloc[-12_000:]\n\n    strategies = [\n        SmaCrossover(10, 30),\n        RsiMeanReversion(),\n        BollingerBreakout(),\n    ]\n\n    portfolio = PortfolioManager(\n        initial_capital=1_000_000,\n        strategies=strategies,\n        allocation=\"equal_weight\",\n    )\n\n    results = portfolio.backtest(data=data, license_key=license_key)\n\n    research = Backtester(initial_capital=1.0, license_key=license_key)\n\n    for name, result in results[\"strategy_results\"].items():\n        breakdowns = research.breakdowns(result, data=data)\n        print(name)\n        print(breakdowns[\"by_entry_regime\"].head())\n\n\nif __name__ == \"__main__\":\n    main(os.getenv(\"RLX_LICENSE_KEY\"))\n```\n\n---\n\n## What the Script Produces\n\nFor each strategy:\n\n* Performance summary\n* Trade breakdown grouped by **entry regime**\n* Automatic screening into an **allowlist**\n\nThen globally:\n\n* Regimes that perform well **across multiple strategies**\n* Copy/paste-ready Python lists\n* Optional JSON export for reuse\n\n---\n\n## Example Output (Real Run)\n\n```\nGlobal candidates (min_strategies=2, pf_mean>=1.15)\n--------------------------------------------------\nentry_regime                    profit_factor_mean\nuptrend:vol=high:atr=high       1.43\n```\n\n```python\nallowed_entry_regimes_global = [\n    \"uptrend:vol=high:atr=high\",\n]\n```\n\n---\n\n## Why This Works\n\n* Regimes are **discovered**, not assumed\n* Screens are simple, explainable, and adjustable\n* The same regimes can be reused across portfolios\n\nThis is how institutional research teams **turn backtests into policy**.\n\n---\n\n## Next Steps\n\n* Apply an allowlist live: `strategy_regime_filter_demo.py`\n* Load allowlists from JSON: `strategy_regime_filter_from_portfolio_json_demo.py`\n* Tune regime definitions or screening thresholds\n\n---\n\n*Happy researching.* 🚀\n","coverImage":null,"status":"published","publishedAt":"2025-12-12T20:29:07.913Z","backtestResults":null,"researchManifest":null,"viewCount":396,"likeCount":1,"metaTitle":null,"metaDescription":null,"createdAt":"2025-12-12T20:29:07.915Z","updatedAt":"2026-09-09T15:09:59.399Z","author":{"id":"e1bbfa18-4766-4421-b638-f9c14a6b81b6","name":"RLXBT","picture":"https://lh3.googleusercontent.com/a/ACg8ocLU-AWuWIPI_04VXCHB_AfGIJAer4OwglsRBxupVzdauWYH3w=s96-c"},"tags":[],"comments":[],"isLiked":false,"isAuthor":false}