DEV Community

Paarthurnax
Paarthurnax

Posted on

Crypto Regime Detection with a Local AI Agent: Bull vs Bear vs Sideways

Crypto Regime Detection with a Local AI Agent: Bull vs Bear vs Sideways

The biggest mistake most crypto traders make isn't picking bad coins — it's using a bull market strategy in a bear market. Or holding through a sideways chop when they should be flat. The market has distinct regimes, and your strategy needs to know which one it's in.

Regime detection used to require expensive quant tools or years of statistics experience. With a local AI agent and a few Python libraries, you can do it yourself — for free, running on your own machine, in real-time.

Not financial advice. Paper trading only.


What Is a Market Regime?

A market regime is the dominant behavior pattern of an asset over a period of time. For crypto, there are three that matter:

Bull Regime — Sustained upward trend. Higher highs, higher lows. Breakouts hold. Dips are bought. Momentum strategies work.

Bear Regime — Sustained downward trend. Lower highs, lower lows. Bounces get sold. Mean reversion is dangerous. Defensive positioning is key.

Sideways/Ranging Regime — No clear trend. Price oscillates between support and resistance. Mean reversion strategies outperform. Trend-following fails.

Each regime rewards different strategies. Using the wrong strategy in the wrong regime is like wearing snow boots at the beach — technically functional, completely wrong for the context.


Why AI Helps With Regime Detection

Classic approaches to regime detection use:

  • Moving average crossovers (simple but lag-heavy)
  • ADX (Average Directional Index) for trend strength
  • Volatility regimes (low vol = trending, high vol = chaotic)
  • Hidden Markov Models (powerful but complex)

A local LLM adds a layer that pure indicators miss: contextual reasoning. You can feed your agent a set of indicators and ask it to synthesize them the way an experienced analyst would.

More importantly: you can do this without sending your data to any external API. Everything runs locally.


Building the Regime Detection System

Step 1: Fetch Multi-Timeframe Data

Regime detection works better with multiple timeframes. A coin can be in a bull regime on the daily but ranging on the hourly.

import ccxt
import pandas as pd
import ta

def fetch_ohlcv(symbol, timeframe, limit=200):
    exchange = ccxt.binance({"enableRateLimit": True})
    data = exchange.fetch_ohlcv(symbol, timeframe, limit=limit)
    df = pd.DataFrame(data, columns=["ts","open","high","low","close","volume"])
    df["datetime"] = pd.to_datetime(df["ts"], unit="ms")
    df.set_index("datetime", inplace=True)
    return df

# Pull 4H and Daily
df_4h = fetch_ohlcv("BTC/USDT", "4h", 200)
df_1d = fetch_ohlcv("BTC/USDT", "1d", 100)
Enter fullscreen mode Exit fullscreen mode

Step 2: Calculate Regime Indicators

def add_regime_indicators(df):
    # Trend direction
    df["ema_21"] = ta.trend.EMAIndicator(df["close"], 21).ema_indicator()
    df["ema_55"] = ta.trend.EMAIndicator(df["close"], 55).ema_indicator()
    df["ema_200"] = ta.trend.EMAIndicator(df["close"], 200).ema_indicator()

    # Trend strength
    adx_obj = ta.trend.ADXIndicator(df["high"], df["low"], df["close"], 14)
    df["adx"] = adx_obj.adx()
    df["adx_pos"] = adx_obj.adx_pos()  # +DI
    df["adx_neg"] = adx_obj.adx_neg()  # -DI

    # Momentum
    df["rsi"] = ta.momentum.RSIIndicator(df["close"], 14).rsi()

    # Volatility
    bb = ta.volatility.BollingerBands(df["close"], 20, 2)
    df["bb_width"] = (bb.bollinger_hband() - bb.bollinger_lband()) / bb.bollinger_mavg()

    # Volume trend
    df["vol_ema"] = ta.trend.EMAIndicator(df["volume"], 20).ema_indicator()
    df["vol_ratio"] = df["volume"] / df["vol_ema"]

    return df.dropna()
Enter fullscreen mode Exit fullscreen mode

Step 3: Rule-Based Regime Classifier

Before involving the LLM, build a solid rule-based baseline:

def classify_regime_rules(df):
    """Fast, deterministic regime classification using rules."""
    latest = df.iloc[-1]

    price = latest["close"]
    ema21 = latest["ema_21"]
    ema55 = latest["ema_55"]
    ema200 = latest["ema_200"]
    adx = latest["adx"]
    adx_pos = latest["adx_pos"]
    adx_neg = latest["adx_neg"]
    rsi = latest["rsi"]
    bb_width = latest["bb_width"]

    # Strong bull conditions
    if (price > ema21 > ema55 > ema200 and 
        adx > 25 and adx_pos > adx_neg and 
        rsi > 50):
        return "BULL", "Strong uptrend: price above all EMAs, ADX trending, RSI bullish"

    # Strong bear conditions
    if (price < ema21 < ema55 < ema200 and 
        adx > 25 and adx_neg > adx_pos and 
        rsi < 50):
        return "BEAR", "Strong downtrend: price below all EMAs, ADX trending, RSI bearish"

    # Ranging/sideways
    if adx < 20 or bb_width < 0.05:
        return "SIDEWAYS", f"Low trend strength: ADX={adx:.1f}, BB width={bb_width:.3f}"

    # Transitional states
    if price > ema55 and ema21 > ema55:
        return "BULL_WEAK", "Emerging bull: above medium-term EMAs but not confirmed"

    if price < ema55 and ema21 < ema55:
        return "BEAR_WEAK", "Emerging bear: below medium-term EMAs but not confirmed"

    return "UNCERTAIN", "Mixed signals — no clear regime"
Enter fullscreen mode Exit fullscreen mode

Step 4: LLM Synthesis Layer

Now bring in Ollama for contextual reasoning:

import urllib.request
import json

def classify_regime_llm(df, symbol="BTC/USDT"):
    """Use local LLM to synthesize regime from indicators."""
    latest = df.iloc[-1]
    prev_week = df.iloc[-42]  # ~1 week ago on 4H

    price_change_7d = (latest["close"] - prev_week["close"]) / prev_week["close"] * 100

    prompt = f"""You are a crypto market analyst. Classify the current market regime for {symbol}.

CURRENT INDICATORS:
- Price: ${latest['close']:,.2f} ({price_change_7d:+.1f}% vs 7 days ago)
- EMA 21: ${latest['ema_21']:,.2f}
- EMA 55: ${latest['ema_55']:,.2f}  
- EMA 200: ${latest['ema_200']:,.2f}
- ADX: {latest['adx']:.1f} (>25 = trending, <20 = ranging)
- +DI: {latest['adx_pos']:.1f}, -DI: {latest['adx_neg']:.1f}
- RSI(14): {latest['rsi']:.1f}
- Bollinger Band Width: {latest['bb_width']:.3f}
- Volume vs 20-period avg: {latest['vol_ratio']:.2f}x

Price vs EMAs: {'ABOVE' if latest['close'] > latest['ema_21'] else 'BELOW'} EMA21, \
{'ABOVE' if latest['close'] > latest['ema_55'] else 'BELOW'} EMA55, \
{'ABOVE' if latest['close'] > latest['ema_200'] else 'BELOW'} EMA200

Respond with exactly:
REGIME: [BULL/BEAR/SIDEWAYS]
CONFIDENCE: [HIGH/MEDIUM/LOW]
REASON: [One sentence explanation]
STRATEGY: [What type of strategy works in this regime]
"""

    payload = {
        "model": "mistral",
        "prompt": prompt,
        "stream": False,
        "options": {"temperature": 0.1}  # Low temp for consistency
    }

    req = urllib.request.Request(
        "http://localhost:11434/api/generate",
        data=json.dumps(payload).encode(),
        headers={"Content-Type": "application/json"}
    )

    with urllib.request.urlopen(req, timeout=30) as resp:
        result = json.loads(resp.read())
        return result["response"].strip()
Enter fullscreen mode Exit fullscreen mode

Putting It All Together

def detect_regime(symbol="BTC/USDT"):
    """Full regime detection pipeline."""
    print(f"\nAnalyzing regime for {symbol}...")

    # Get data
    df_4h = fetch_ohlcv(symbol, "4h", 250)
    df_4h = add_regime_indicators(df_4h)

    # Rule-based check (fast)
    regime, reason = classify_regime_rules(df_4h)
    print(f"Rule-based: {regime}{reason}")

    # LLM synthesis (deeper)
    llm_analysis = classify_regime_llm(df_4h, symbol)
    print(f"\nLLM Analysis:\n{llm_analysis}")

    return regime, llm_analysis

# Run it
detect_regime("BTC/USDT")
detect_regime("ETH/USDT")
detect_regime("SOL/USDT")
Enter fullscreen mode Exit fullscreen mode

Adapting Your Strategy to the Regime

Once you know the regime, you can dynamically switch strategies:

STRATEGY_MAP = {
    "BULL": {
        "approach": "trend-following",
        "entries": "buy dips to EMA21",
        "exits": "trail stop below EMA55",
        "leverage": "1-2x max",
    },
    "BEAR": {
        "approach": "defensive/short",
        "entries": "sell bounces to EMA21",
        "exits": "cover at oversold RSI",
        "leverage": "avoid longs",
    },
    "SIDEWAYS": {
        "approach": "mean-reversion",
        "entries": "buy at lower BB, sell at upper BB",
        "exits": "take profit at opposite band",
        "leverage": "none",
    },
    "UNCERTAIN": {
        "approach": "flat",
        "entries": "none",
        "exits": "reduce exposure",
        "leverage": "none",
    },
}
Enter fullscreen mode Exit fullscreen mode

Scheduling Regime Checks with OpenClaw

Run regime detection automatically every 4 hours via OpenClaw's heartbeat system:

# In your OpenClaw skill file
def heartbeat():
    regime, analysis = detect_regime("BTC/USDT")
    send_telegram(f"**BTC Regime Update**\n{analysis}")
Enter fullscreen mode Exit fullscreen mode

Set the heartbeat interval to 4 hours and you'll never miss a regime shift.


What Regime Detection Won't Do

It won't predict tops and bottoms. It won't tell you when a regime will end. It operates on historical data with a lag — the faster your timeframe, the noisier the signal.

The value is in avoiding catastrophic mismatches: running a trend-following bot in a ranging market, or buying dips in a bear trend. Regime detection keeps you in the right mode.


Ready to Automate Your Analysis?

The full regime detection system — including multi-timeframe analysis, Telegram alerts, and integration with paper trading — is in the OpenClaw kit:

👉 OpenClaw Home AI Agent Kit — Full Setup Guide

Know the regime. Trade accordingly.



🛠️ Also check out CryptoClaw Skills Hub — browse and install crypto skills for your OpenClaw agent: https://paarthurnax970-debug.github.io/cryptoclawskills/

Not financial advice. Paper trading only. All strategies should be backtested before any live deployment.

Top comments (0)