Skill: cylqqqcyl/lean-engine (version 1.1.0)

--- name: lean description: Run QuantConnect LEAN backtests and manage US equity algorithm development. Use when asked to backtest a trading strategy, run a LEAN algorithm, analyze backtest results, download market data, or deploy to Interactive Brokers TWS. Covers algorithm creation, data management, config editing, and result analysis. metadata: { "openclaw": { "requires": { "bins": ["dotnet"], "anyBins": ["python3", "python"], "env": ["LEAN_ROOT", "DOTNET_ROOT", "PYTHONNET_PYDLL"] }, "install": [ { "id": "dotnet", "kind": "download", "url": "https://dotnet.microsoft.com/download/dotnet/8.0", "label": "Install .NET 8 SDK" } ] } } --- # LEAN Engine — QuantConnect Algorithmic Trading ## Prerequisites & Setup ### Required Environment Variables | Variable | Purpose | Example | |----------|---------|---------| | `LEAN_ROOT` | Path to cloned LEAN repository | `/home/user/lean` | | `DOTNET_ROOT` | Path to .NET SDK installation | `/home/user/.dotnet` | | `PYTHONNET_PYDLL` | Path to Python shared library (required by LEAN's pythonnet) | `$LEAN_ROOT/.libs/libpython3.11.so.1.0` | All three must be set before using this skill. Add to your shell profile: ```bash export LEAN_ROOT="$HOME/lean" export DOTNET_ROOT="$HOME/.dotnet" export PATH="$PATH:$DOTNET_ROOT" export PYTHONNET_PYDLL="$LEAN_ROOT/.libs/libpython3.11.so.1.0" ``` > **Note:** LEAN bundles its own Python shared library in `$LEAN_ROOT/.libs/`. If you built LEAN from source, the library should be there after `dotnet build`. If not, install `libpython3.11-dev` and point `PYTHONNET_PYDLL` to your system's `libpython3.11.so`. ### First-Time Setup 1. **Install .NET 8 SDK:** ```bash # Linux/macOS wget https://dot.net/v1/dotnet-install.sh -O dotnet-install.sh chmod +x dotnet-install.sh ./dotnet-install.sh --channel 8.0 export DOTNET_ROOT="$HOME/.dotnet" export PATH="$PATH:$DOTNET_ROOT" ``` 2. **Clone and build LEAN:** ```bash git clone https://github.com/QuantConnect/Lean.git "$LEAN_ROOT" cd "$LEAN_ROOT" dotnet build QuantConnect.Lean.sln -c Debug ``` 3. **Download initial market data:** ```bash pip install yfinance pandas python3 {baseDir}/scripts/download_us_universe.py --symbols sp500 --start 2020-01-01 --data-dir "$LEAN_ROOT/Data" ``` 4. **Verify setup:** ```bash ls "$LEAN_ROOT/Data/equity/usa/daily/" # Should list .zip files ls "$LEAN_ROOT/Launcher/bin/Debug/" # Should contain QuantConnect.Lean.Launcher.dll ``` ## Environment - **LEAN source:** `$LEAN_ROOT/` - **Launcher (pre-built):** `$LEAN_ROOT/Launcher/bin/Debug/` - **Config:** `$LEAN_ROOT/Launcher/config.json` - **Python algos:** `$LEAN_ROOT/Algorithm.Python/` - **Market data:** `$LEAN_ROOT/Data/` - **dotnet:** `$DOTNET_ROOT/dotnet` (add to PATH: `export PATH="$PATH:$DOTNET_ROOT"`) ## Quick Reference ### Run a Backtest 1. Place algorithm in `$LEAN_ROOT/Algorithm.Python/YourAlgo.py` 2. Edit config to point to it: ```bash # Update config.json — set these fields: # "algorithm-type-name": "YourClassName" # "algorithm-language": "Python" # "algorithm-location": "../../../Algorithm.Python/YourAlgo.py" ``` 3. Run: ```bash export PATH="$PATH:$DOTNET_ROOT" cd "$LEAN_ROOT/Launcher/bin/Debug" dotnet QuantConnect.Lean.Launcher.dll ``` 4. Results appear in stdout + `$LEAN_ROOT/Results/` **Or use the helper script:** ```bash bash {baseDir}/scripts/run_backtest.sh YourClassName YourAlgo.py ``` ### Config Editing Edit `$LEAN_ROOT/Launcher/config.json` with these key fields: | Field | Purpose | Example | |-------|---------|---------| | `algorithm-type-name` | Python class name | `"MyStrategy"` | | `algorithm-language` | Language | `"Python"` | | `algorithm-location` | Path to .py file | `"../../../Algorithm.Python/MyStrategy.py"` | | `data-folder` | Market data path | `"../Data/"` | | `environment` | Mode | `"backtesting"` or `"live-interactive"` | For IB live trading, set environment to `"live-interactive"` and configure the `ib-*` fields (account, username, password, host, port, trading-mode). ### Data Management **Check available data:** ```bash ls "$LEAN_ROOT/Data/equity/usa/daily/" ``` **Data format:** ZIP files containing CSV. Each line: `YYYYMMDD HH:MM,Open*10000,High*10000,Low*10000,Close*10000,Volume` Prices are stored as integers (multiply by 10000). LEAN handles conversion internally. **Download more data:** ```bash python3 {baseDir}/scripts/download_us_universe.py --symbols sp500 --data-dir "$LEAN_ROOT/Data" ``` See `{baseDir}/references/data-download.md` for additional methods to expand the universe. ### Writing Algorithms LEAN Python algorithms inherit from `QCAlgorithm`: ```python from AlgorithmImports import * class MyAlgo(QCAlgorithm): def Initialize(self): self.SetStartDate(2024, 1, 1) self.SetEndDate(2025, 1, 1) self.SetCash(100_000) self.AddEquity("SPY", Resolution.Daily) self.SetBenchmark("SPY") self.SetBrokerageModel(BrokerageName.InteractiveBrokersBrokerage, AccountType.Margin) def OnData(self, data): if not self.Portfolio.Invested: self.SetHoldings("SPY", 1.0) ``` Key API patterns: - `self.History(symbol, periods, resolution)` — get historical bars - `self.SetHoldings(symbol, weight)` — target portfolio weight - `self.Liquidate(symbol)` — close position - `self.AddUniverse(coarse_fn, fine_fn)` — dynamic universe selection - `self.Schedule.On(date_rule, time_rule, action)` — scheduled events - `self.Debug(msg)` — log output ### Analyzing Results After a backtest run, check: ```bash ls "$LEAN_ROOT/Results/" # Key files: *-log.txt, *-order-log.txt, *.json (statistics) ``` ### Rebuild LEAN (if source changes) ```bash export PATH="$PATH:$DOTNET_ROOT" cd "$LEAN_ROOT" dotnet build QuantConnect.Lean.sln -c Debug ``` ## Security Notes ### Config.json Safety The `run_backtest.sh` script does **NOT** modify your original `config.json`. Instead, it: 1. Reads the original config as a template (read-only) 2. Creates a separate `config.backtest.json` with only algorithm fields changed (class name, file path, language, environment=backtesting) 3. Temporarily swaps it in for the LEAN run, then restores the original via a `trap` cleanup handler The `configure_algo.py` helper performs the field substitution in an isolated output file. Your original config — including any Interactive Brokers credentials for live trading — is never modified. **Modified fields (in the temp copy only):** - `algorithm-type-name` — set to the requested class name - `algorithm-language` — set to `Python` - `algorithm-location` — set to the requested .py file path - `environment` — set to `backtesting` ### Network Access The setup instructions involve network downloads: - `git clone` from GitHub (QuantConnect/Lean repository) - `dotnet build` may restore NuGet packages - `pip install yfinance pandas` installs Python packages from PyPI - `download_us_universe.py` fetches market data from Yahoo Finance All downloads are from well-known public sources. For maximum isolation, run setup in a container or VM. ### Environment Variables This skill requires the following environment variables at runtime: - `LEAN_ROOT` — path to your cloned LEAN repository - `DOTNET_ROOT` — path to your .NET SDK installation - `PYTHONNET_PYDLL` — path to Python shared library (auto-detected from `$LEAN_ROOT/.libs/` if not set) These are declared in the skill metadata and must be set before use. ## Troubleshooting - **"No data files found"** → Check `data-folder` in config.json points to correct path - **Python import errors** → LEAN bundles its own Python; check `python-venv` config if using custom packages - **Slow backtest** → Reduce universe size or date range; check Resolution (Minute >> Daily) - **IB connection issues** → Verify TWS/Gateway is running, port matches config (default 4002 for Gateway) - **`LEAN_ROOT` not set** → Add `export LEAN_ROOT="$HOME/lean"` to your shell profile - **dotnet not found** → Add `export PATH="$PATH:$DOTNET_ROOT"` to your shell profile - **`Runtime.PythonDLL was not set`** → Set `PYTHONNET_PYDLL` to the Python shared library path (see env var table above)

path: references/data-download.md
content: # LEAN Data Download — Expanding the Universe

## Current State

After initial setup, you'll have ~160 symbols in `$LEAN_ROOT/Data/equity/usa/daily/`.
For pattern-scanning strategies, you may need 500+ liquid US equities.

## Method 1: QuantConnect Data Library (Recommended)

QuantConnect provides free data for backtesting via their API.

```bash
# Install lean CLI
pip install lean

# Login (requires free QuantConnect account)
lean login

# Download specific symbols
lean data download --dataset "US Equity Security Master"
lean data download --dataset "US Equities" --resolution daily --start 20200101
```

## Method 2: LEAN ToolBox — Coarse Universe Generator

LEAN includes a ToolBox for generating coarse universe files from existing data:

```bash
export PATH="$PATH:$DOTNET_ROOT"
cd "$LEAN_ROOT"
dotnet run --project ToolBox -- --app=CoarseUniverseGenerator \
  --source-dir=Data/equity/usa/daily \
  --destination-dir=Data/equity/usa/fundamental/coarse
```

## Method 3: Manual Data Preparation

For custom data sources (yfinance, Alpha Vantage, etc.):

### Data Format (Daily)

File: `$LEAN_ROOT/Data/equity/usa/daily/{ticker}.zip` containing `{ticker}.csv`

CSV format (no header):
```
YYYYMMDD HH:MM,Open,High,Low,Close,Volume
```

**Important:** Prices are scaled by 10000 (integer format).
- Real price $150.25 → stored as 1502500
- Real price $3.50 → stored as 35000

### Conversion Script

```python
#!/usr/bin/env python3
"""Convert yfinance data to LEAN format."""
import yfinance as yf
import zipfile
import io

def download_to_lean(ticker, start="2020-01-01", end="2026-03-01"):
    df = yf.download(ticker, start=start, end=end, auto_adjust=True)
    
    lines = []
    for date, row in df.iterrows():
        date_str = date.strftime("%Y%m%d 00:00")
        o = int(row["Open"] * 10000)
        h = int(row["High"] * 10000)
        l = int(row["Low"] * 10000)
        c = int(row["Close"] * 10000)
        v = int(row["Volume"])
        lines.append(f"{date_str},{o},{h},{l},{c},{v}")
    
    csv_content = "\n".join(lines)
    zip_path = f"Data/equity/usa/daily/{ticker.lower()}.zip"
    
    with zipfile.ZipFile(zip_path, "w", zipfile.ZIP_DEFLATED) as zf:
        zf.writestr(f"{ticker.lower()}.csv", csv_content)
    
    print(f"Wrote {len(lines)} bars to {zip_path}")
```

### Map Files

For proper symbol resolution, LEAN needs map files at:
`$LEAN_ROOT/Data/equity/usa/map_files/{ticker}.csv`

Format:
```
YYYYMMDD,ticker,market,security_type
20200101,AAPL,usa,Equity
```

For simple backtests without delistings/splits, a single-row map file works.

## Method 4: Batch Download Script

Use the included `download_us_universe.py` script:

1. Gets S&P 500 constituent list (~160 symbols)
2. Downloads daily OHLCV via yfinance
3. Converts to LEAN format
4. Generates map files

Usage:
```bash
pip install yfinance pandas
python3 download_us_universe.py --symbols sp500 --start 2020-01-01 --data-dir "$LEAN_ROOT/Data"
```


path: scripts/configure_algo.py
content: #!/usr/bin/env python3
"""
configure_algo.py — Create a backtest-ready config from a LEAN config template.

Reads a source config.json, updates algorithm fields, and writes to a
separate output file. The original config is NEVER modified.

Usage:
  python3 configure_algo.py <source_config> <output_config> <class_name> <file_name>

Example:
  python3 configure_algo.py config.json config.backtest.json MyAlgo MyAlgo.py
"""

import re
import sys


def main():
    if len(sys.argv) != 5:
        print(f"Usage: {sys.argv[0]} <source_config> <output_config> <class_name> <algo_file>")
        sys.exit(1)

    source, output, algo_class, algo_file = sys.argv[1:5]

    with open(source, "r") as f:
        content = f.read()

    # Replace algorithm-type-name
    content = re.sub(
        r'"algorithm-type-name"\s*:\s*"[^"]*"',
        f'"algorithm-type-name": "{algo_class}"',
        content,
    )

    # Replace algorithm-language
    content = re.sub(
        r'"algorithm-language"\s*:\s*"[^"]*"',
        '"algorithm-language": "Python"',
        content,
    )

    # Replace algorithm-location
    content = re.sub(
        r'"algorithm-location"\s*:\s*"[^"]*"',
        f'"algorithm-location": "../../../Algorithm.Python/{algo_file}"',
        content,
    )

    # Ensure backtesting environment
    content = re.sub(
        r'"environment"\s*:\s*"[^"]*"',
        '"environment": "backtesting"',
        content,
    )

    with open(output, "w") as f:
        f.write(content)

    print(f"✅ Config written to {output}")
    print(f"   Class: {algo_class}")
    print(f"   File:  {algo_file}")
    print(f"   Mode:  backtesting")
    print(f"   Source config unchanged: {source}")


if __name__ == "__main__":
    main()


path: scripts/download_us_universe.py
content: #!/usr/bin/env python3
"""
download_us_universe.py
Download US equity daily data and convert to LEAN format.

Usage:
  python3 download_us_universe.py --symbols sp500 --start 2020-01-01
  python3 download_us_universe.py --symbols AAPL,MSFT,GOOGL --start 2023-01-01
  python3 download_us_universe.py --symbols sp500 --start 2020-01-01 --data-dir /path/to/lean/Data

Requires: pip install yfinance pandas

If --data-dir is not specified, uses $LEAN_ROOT/Data (env var).
"""

import argparse
import os
import zipfile
import sys
from datetime import datetime

try:
    import yfinance as yf
    import pandas as pd
except ImportError:
    print("Missing dependencies. Install with: pip install yfinance pandas")
    sys.exit(1)


# S&P 500 — top ~100 by market cap (expand as needed)
SP500_TOP = [
    "AAPL", "MSFT", "GOOGL", "AMZN", "NVDA", "META", "BRK-B", "TSLA", "UNH", "LLY",
    "JPM", "V", "XOM", "JNJ", "PG", "MA", "AVGO", "HD", "MRK", "COST",
    "ABBV", "CVX", "PEP", "KO", "ADBE", "CRM", "WMT", "MCD", "BAC", "CSCO",
    "ACN", "TMO", "ABT", "NFLX", "LIN", "AMD", "DHR", "ORCL", "CMCSA", "TXN",
    "PM", "DIS", "VZ", "NEE", "WFC", "INTC", "RTX", "BMY", "UPS", "QCOM",
    "HON", "COP", "AMGN", "SPGI", "LOW", "IBM", "GE", "CAT", "BA", "INTU",
    "DE", "MDLZ", "ISRG", "PLD", "SYK", "GILD", "BKNG", "AXP", "GS", "AMAT",
    "BLK", "ADI", "VRTX", "TJX", "REGN", "MMC", "LMT", "SBUX", "CVS", "SCHW",
    "EOG", "NOW", "MO", "SO", "CI", "PGR", "CB", "ZTS", "BDX", "CME",
    "DUK", "SLB", "CL", "NOC", "FI", "ICE", "ITW", "SHW", "MCK", "APD",
    # Additional liquid names
    "PANW", "CRWD", "SNOW", "DDOG", "ZS", "NET", "PLTR", "COIN", "MARA", "RIOT",
    "SQ", "SHOP", "ROKU", "SNAP", "PINS", "UBER", "LYFT", "DASH", "ABNB", "RBLX",
    "SOFI", "HOOD", "RIVN", "LCID", "NIO", "XPEV", "LI", "F", "GM", "TM",
    "MRVL", "MU", "LRCX", "KLAC", "SNPS", "CDNS", "ASML", "TSM", "ON", "SWKS",
    "ENPH", "SEDG", "FSLR", "RUN", "PLUG", "CHPT", "BLNK", "QS", "STEM", "BE",
]

# ETFs for benchmarking
ETFS = ["SPY", "QQQ", "IWM", "DIA", "XLF", "XLK", "XLE", "XLV", "XLI", "XLY"]


def get_default_data_dir():
    """Get default data directory from LEAN_ROOT env var."""
    lean_root = os.environ.get("LEAN_ROOT")
    if lean_root:
        return os.path.join(lean_root, "Data")
    return None


def get_symbols(mode: str) -> list:
    """Get symbol list based on mode."""
    if mode == "sp500":
        return SP500_TOP + ETFS
    elif mode == "etfs":
        return ETFS
    else:
        return [s.strip().upper() for s in mode.split(",")]


def to_lean_csv(df: pd.DataFrame, ticker: str) -> str:
    """Convert yfinance DataFrame to LEAN daily CSV format."""
    # yfinance returns MultiIndex columns: (Price, Ticker) — flatten
    if isinstance(df.columns, pd.MultiIndex):
        df = df.droplevel("Ticker", axis=1)

    lines = []
    for date, row in df.iterrows():
        date_str = date.strftime("%Y%m%d 00:00")
        o = int(round(float(row["Open"]) * 10000))
        h = int(round(float(row["High"]) * 10000))
        l = int(round(float(row["Low"]) * 10000))
        c = int(round(float(row["Close"]) * 10000))
        v = int(float(row["Volume"]))
        if v > 0 and c > 0:
            lines.append(f"{date_str},{o},{h},{l},{c},{v}")
    return "\n".join(lines)


def write_lean_zip(ticker: str, csv_content: str, data_dir: str):
    """Write LEAN-format zip file."""
    daily_dir = os.path.join(data_dir, "equity", "usa", "daily")
    os.makedirs(daily_dir, exist_ok=True)

    clean_ticker = ticker.lower().replace("-", "")
    zip_path = os.path.join(daily_dir, f"{clean_ticker}.zip")

    with zipfile.ZipFile(zip_path, "w", zipfile.ZIP_DEFLATED) as zf:
        zf.writestr(f"{clean_ticker}.csv", csv_content)

    return zip_path


def write_map_file(ticker: str, start_date: str, data_dir: str):
    """Write minimal map file for symbol resolution."""
    map_dir = os.path.join(data_dir, "equity", "usa", "map_files")
    os.makedirs(map_dir, exist_ok=True)

    clean_ticker = ticker.lower().replace("-", "")
    map_path = os.path.join(map_dir, f"{clean_ticker}.csv")

    date_str = start_date.replace("-", "")
    with open(map_path, "w") as f:
        f.write(f"{date_str},{clean_ticker},usa,Equity\n")
        f.write(f"20500101,{clean_ticker},usa,Equity\n")  # far future end date

    return map_path


def main():
    default_data_dir = get_default_data_dir()

    ap = argparse.ArgumentParser(description="Download US equity data for LEAN")
    ap.add_argument("--symbols", default="sp500",
                    help="'sp500', 'etfs', or comma-separated tickers")
    ap.add_argument("--start", default="2020-01-01", help="Start date YYYY-MM-DD")
    ap.add_argument("--end", default=None, help="End date YYYY-MM-DD (default: today)")
    ap.add_argument("--data-dir", default=default_data_dir,
                    help="LEAN Data directory (default: $LEAN_ROOT/Data)")
    ap.add_argument("--batch-size", type=int, default=20,
                    help="Download batch size")
    args = ap.parse_args()

    if not args.data_dir:
        print("Error: --data-dir not specified and LEAN_ROOT env var not set.")
        print("Set LEAN_ROOT or pass --data-dir explicitly.")
        sys.exit(1)

    symbols = get_symbols(args.symbols)
    end = args.end or datetime.now().strftime("%Y-%m-%d")

    print(f"Downloading {len(symbols)} symbols: {args.start} to {end}")
    print(f"Output: {args.data_dir}/equity/usa/daily/")

    success = 0
    failed = []

    for i in range(0, len(symbols), args.batch_size):
        batch = symbols[i:i + args.batch_size]
        print(f"\nBatch {i // args.batch_size + 1}: {', '.join(batch)}")

        for ticker in batch:
            try:
                df = yf.download(ticker, start=args.start, end=end,
                                 auto_adjust=True, progress=False)
                if df.empty or len(df) < 10:
                    print(f"  SKIP {ticker} (insufficient data)")
                    failed.append(ticker)
                    continue

                csv_content = to_lean_csv(df, ticker)
                zip_path = write_lean_zip(ticker, csv_content, args.data_dir)
                write_map_file(ticker, args.start, args.data_dir)

                print(f"  OK   {ticker} ({len(df)} bars) → {os.path.basename(zip_path)}")
                success += 1

            except Exception as e:
                print(f"  FAIL {ticker}: {e}")
                failed.append(ticker)

    print(f"\n{'='*50}")
    print(f"Done: {success}/{len(symbols)} symbols downloaded")
    if failed:
        print(f"Failed: {', '.join(failed)}")
    print(f"Data at: {args.data_dir}/equity/usa/daily/")


if __name__ == "__main__":
    main()


path: scripts/run_backtest.sh
content: #!/bin/bash
# run_backtest.sh — Configure and run a LEAN backtest
#
# Usage:
#   ./run_backtest.sh <AlgoClassName> <AlgoFile.py>
#   ./run_backtest.sh SecondWavePortfolio SecondWavePortfolio.py
#
# Requires: LEAN_ROOT env var pointing to cloned LEAN repository
# The .py file must exist in $LEAN_ROOT/Algorithm.Python/
# Results go to $LEAN_ROOT/Results/
#
# SAFETY: This script does NOT modify your original config.json.
# It creates a temporary config copy (config.backtest.json) for each run.
# Your original config (including any IB credentials) is never touched.

set -e

ALGO_CLASS="${1:?Usage: $0 <AlgoClassName> <AlgoFile.py>}"
ALGO_FILE="${2:?Usage: $0 <AlgoClassName> <AlgoFile.py>}"

: "${LEAN_ROOT:?Error: LEAN_ROOT not set. Export it to your LEAN repository path.}"

ALGO_DIR="$LEAN_ROOT/Algorithm.Python"
LAUNCHER="$LEAN_ROOT/Launcher/bin/Debug"
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"

# Locate source config (read-only — never modified)
if [ -f "$LAUNCHER/config.json" ]; then
    SOURCE_CONFIG="$LAUNCHER/config.json"
else
    SOURCE_CONFIG="$LEAN_ROOT/Launcher/config.json"
fi

# Output config is a separate file — original is never touched
BACKTEST_CONFIG="$LAUNCHER/config.backtest.json"

# Ensure dotnet is in PATH
export PATH="$PATH:${DOTNET_ROOT:-$HOME/.dotnet}"

# LEAN's pythonnet requires the Python shared library path
if [ -z "$PYTHONNET_PYDLL" ]; then
    if [ -f "$LEAN_ROOT/.libs/libpython3.11.so.1.0" ]; then
        export PYTHONNET_PYDLL="$LEAN_ROOT/.libs/libpython3.11.so.1.0"
    else
        echo "⚠️  PYTHONNET_PYDLL not set. LEAN may fail to load Python algorithms."
        echo "   Set it to your Python shared library, e.g.:"
        echo "   export PYTHONNET_PYDLL=\"\$LEAN_ROOT/.libs/libpython3.11.so.1.0\""
    fi
fi

# Verify algorithm file exists
if [ ! -f "$ALGO_DIR/$ALGO_FILE" ]; then
    echo "❌ Algorithm not found: $ALGO_DIR/$ALGO_FILE"
    echo "   Place your .py file there first."
    exit 1
fi

# Verify LEAN is built
if [ ! -f "$LAUNCHER/QuantConnect.Lean.Launcher.dll" ]; then
    echo "❌ LEAN not built. Run: cd $LEAN_ROOT && dotnet build QuantConnect.Lean.sln -c Debug"
    exit 1
fi

# Create backtest config from source (source is NOT modified)
python3 "$SCRIPT_DIR/configure_algo.py" "$SOURCE_CONFIG" "$BACKTEST_CONFIG" "$ALGO_CLASS" "$ALGO_FILE"

# Temporarily swap config for the run, then restore
# LEAN reads config.json from its working directory
cp "$SOURCE_CONFIG" "$SOURCE_CONFIG.pre-backtest"
cp "$BACKTEST_CONFIG" "$SOURCE_CONFIG"

cleanup() {
    # Restore original config after run
    if [ -f "$SOURCE_CONFIG.pre-backtest" ]; then
        cp "$SOURCE_CONFIG.pre-backtest" "$SOURCE_CONFIG"
        rm -f "$SOURCE_CONFIG.pre-backtest"
    fi
}
trap cleanup EXIT

# Run the backtest
echo ""
echo "🚀 Starting LEAN backtest..."
echo "═══════════════════════════════════════════"
cd "$LAUNCHER"
dotnet QuantConnect.Lean.Launcher.dll

echo ""
echo "═══════════════════════════════════════════"
echo "✅ Backtest complete. Results:"
ls -la "$LEAN_ROOT/Results/" 2>/dev/null || echo "   (check $LAUNCHER for output)"


Security scan verdicts — ClawScan: suspicious | static analysis: clean | VirusTotal: clean | SkillSpector: suspicious (HIGH)