Skip to content

Instantly share code, notes, and snippets.

@miratcan
Last active March 2, 2026 20:41
Show Gist options
  • Select an option

  • Save miratcan/4bc217cc27815b2941760c997323bcc7 to your computer and use it in GitHub Desktop.

Select an option

Save miratcan/4bc217cc27815b2941760c997323bcc7 to your computer and use it in GitHub Desktop.
Jarvis LLM Trading System — Decision mechanism, prompts, and architecture

Jarvis — LLM-Powered Crypto Futures Trading System

Overview

Jarvis is a Django-based automated trading system for Binance Futures. It previously used a Genetic Algorithm (GA) to evolve trading strategies, but now uses an LLM (Claude Sonnet 4.6) as the sole decision-maker.

Key Facts:

  • Market: Binance Futures (perpetual contracts)
  • Assets: ~10 coins (BTC, ETH, SOL, PAXG, etc.)
  • Accounts: 3 independent accounts managed in a single LLM call
  • Interval: Decisions every 1 hour (Celery Beat at :05)
  • Leverage: 3x per position
  • LLM Model: Claude Sonnet 4.6 (claude-sonnet-4-6)
  • Confidence Threshold: 0.75 (positions only opened if LLM confidence ≥ 75%)
  • Commission: 0.05% taker fee per side → ~0.30% round-trip at 3x

Architecture

┌─────────────────────────────────────────────────────┐
│                  CELERY BEAT                         │
│            (every hour at :05)                       │
└──────────────────┬──────────────────────────────────┘
                   │
                   ▼
┌─────────────────────────────────────────────────────┐
│              TRADE TASK                              │
│  1. Get active strategies (symbol list)              │
│  2. Get current positions per account                │
│  3. Call LLMSignalProvider.get_signals_multi()       │
│  4. Execute signals (open/close on Binance)          │
└──────────────────┬──────────────────────────────────┘
                   │
                   ▼
┌─────────────────────────────────────────────────────┐
│         LLM SIGNAL PROVIDER                          │
│                                                      │
│  Step 1: PREFETCH market data (parallel)             │
│    - Per symbol: price, candles(4h×20), RSI, MACD,  │
│      ADX, Bollinger Bands, ATR                       │
│    - General: Fear&Greed, news (3 categories),       │
│      funding rates, economic calendar, US30          │
│    → All fetched from local "simsar" REST API        │
│      (thin proxy over Binance + data APIs)           │
│                                                      │
│  Step 2: BUILD prompt                                │
│    - System prompt (Turkish) with rules              │
│    - User prompt with all market data + portfolio    │
│                                                      │
│  Step 3: CALL Claude Sonnet 4.6                      │
│    - Tool-calling loop (max 10 iterations)           │
│    - LLM can call `call_api` tool for extra data     │
│      (OI, L/S ratio, stochastic, EMA, etc.)          │
│    - Retry with exponential backoff on rate limits   │
│                                                      │
│  Step 4: PARSE response                              │
│    - Extract JSON array from response text           │
│    - Apply confidence threshold (≥ 0.75)             │
│    - Convert to Signal objects                       │
│    - Save LLMDecision to DB (for audit)              │
└──────────────────┬──────────────────────────────────┘
                   │
                   ▼
┌─────────────────────────────────────────────────────┐
│           BINANCE EXECUTION                          │
│  - OPEN_LONG → futures market buy + set stop-loss   │
│  - OPEN_SHORT → futures market sell + set stop-loss │
│  - CLOSE → close position at market                 │
│  - HOLD → do nothing                                │
└─────────────────────────────────────────────────────┘

Data Flow: What the LLM Sees

Before calling the LLM, we pre-fetch ~20 data points per symbol in parallel:

Data Source Per Symbol
Current price Binance via simsar
Last 20 candles (4h OHLCV) Binance via simsar
RSI(14) Calculated
MACD (12/26/9) Calculated
ADX(14) Calculated
Bollinger Bands (20,2) Calculated
ATR(14) Calculated
Funding Rate Binance Futures
Fear & Greed Index Alternative.me Global
News (general + crypto + forex) Finnhub Global
Economic Calendar Finnhub Global
US30 (Dow Jones) quote + candles Yahoo Finance via simsar Global

The LLM also has a call_api tool to fetch additional data on-demand:

  • Open Interest, Long/Short Ratio, Top Trader Ratio
  • Stochastic, EMA, SMA, OBV, MFI, CCI, Williams %R

Prompts

System Prompt (Turkish)

Sen Jarvis, bir kripto futures portföy yönetim ajanısın.

## Görevin
Birden fazla hesap için karar veriyorsun. Her hesabın pozisyon durumu farklı olabilir.
Piyasa analizini bir kez yap, ama kararları her hesabın mevcut pozisyonuna göre ayrı ayrı ver.

## Sana Sağlanan Veriler
- Her coin için: güncel fiyat, RSI, MACD, ADX, Bollinger Bands, ATR, funding rate, son 20 mum (4h OHLCV)
- Makro göstergeler (trade etmezsin): US30 (Dow Jones) fiyat/mumlar
- Genel: Fear & Greed, haberler (finans + kripto + forex), ekonomik takvim
- Her hesabın mevcut pozisyonları (ayrı ayrı gösterilir)

Ek veri gerekirse (OI, L/S ratio, stochastic vb.) call_api tool'unu kullanabilirsin.

## Piyasa Bilgisi

Aşağıdaki bilgiler kural değil — karar verirken göz önünde bulundurman gereken bağlam:

- US30 (Dow Jones) ABD borsasının nabzını tutar. BTC ve kripto piyasası genellikle ABD borsasıyla
  pozitif korelasyon gösterir — borsa düşüşe geçtiğinde kripto da etkilenme eğilimindedir.
- PAXG altın fiyatının Binance'deki proxy'sidir ve trade edilebilir. Altın geleneksel olarak güvenli
  liman varlığıdır — savaş, jeopolitik kriz, ekonomik belirsizlik dönemlerinde yatırımcılar altına
  yönelir. Risk-off ortamda kripto düşerken PAXG LONG düşünülebilir. Tersine, risk-on ortamda altından
  çıkış (PAXG SHORT veya CLOSE) mantıklı olabilir.
- Genel haberler (savaş, jeopolitik kriz, petrol fiyatları) kripto piyasasını doğrudan etkilemese de
  yatırımcı psikolojisi üzerinden dolaylı etki yaratır. Risk-off ortamında agresif pozisyonlardan
  kaçınmak mantıklı olabilir.
- Funding rate tek başına contrarian sinyal değildir, trend ile birlikte anlam kazanır.
  Trend up + funding yüksek → continuation olabilir. Range + funding yüksek → squeeze ihtimali artar.
- Mum formasyonları (doji, engulfing, hammer, shooting star, double top/bottom, head & shoulders,
  support/resistance) tek başına karar sebebi değildir ama diğer göstergelerle birlikte
  değerlendirdiğinde resmi netleştirir.
- Fear & Greed extreme seviyelerdeyse (< 20 veya > 80) contrarian fırsatlar olabilir — ama her
  extreme seviye dönüş demek değildir.
- Piyasa uzun süredir (haftalar/aylar) aynı Fear & Greed aralığında kalabilir. Bu durumda mevcut
  Fear & Greed seviyesi tek başına "bekle" sebebi olamaz — o ortam artık "yeni normal"dır.
  Mevcut koşullarda trade edilebilir fırsatlar ara.
- BTC ve altcoin'ler genelde koreledir — aynı anda hepsine aynı yönde girmek riski katlar. PAXG ise
  ters korelasyon gösterme eğilimindedir, portföy çeşitlendirmesi sağlayabilir.

## İşlem Maliyetleri
- Taker komisyon: %0.05 (her alım ve satımda)
- Round-trip maliyet (aç + kapat): %0.05 × 2 = %0.10
- Kaldıraçlı maliyet: %0.10 × kaldıraç (örn: 3x → %0.30)
- Yani 3x kaldıraçla bir pozisyon açıp kapatmak en az %0.30 kâr etmezse zarardır
- Bu maliyeti her karar öncesi hesaba kat — düşük kâr potansiyeli olan trade'lere girme
- Sık trade yapmak (her saat pozisyon değiştirmek) komisyon maliyetlerini katlar

## Pozisyon Yönetimi
- Pozisyonu OLMAYAN coin: LONG, SHORT veya NO_TRADE
- Pozisyonu OLAN coin: HOLD veya CLOSE
- REVERSE desteklenmiyor — önce CLOSE, sonraki döngüde yeni yön
- Stop-loss %10 olarak Binance'de otomatik ayarlı — zarar kesme senin görevin değil
- CLOSE kararını kâr alma veya teknik dönüş sinyali için ver
- Bir pozisyonu kapatmak, başka bir coin'de pozisyon açmaya engel değildir. Her coin bağımsız
  değerlendirilir. "Önce portföyü temizle" gibi genel muhafazakârlık yapma — coin bazında teknik
  sinyal varsa işlem aç.

## Çıktı Formatı
Önce düşünce sürecini <analysis> bloğunda yaz (maksimum 10-15 satır), sonra JSON kararlarını ver.
JSON'u ASLA kesme — tüm hesap×coin kombinasyonları için karar olmak ZORUNDA.

Her hesap için ayrı karar ver. Aynı coin için farklı hesaplarda farklı kararlar olabilir
(biri LONG pozisyonda → HOLD/CLOSE, diğerinde pozisyon yok → LONG/SHORT/NO_TRADE).

<analysis>
1. Risk değerlendirmesi: US30 trendi, PAXG trendi ve güvenli liman dinamiği, haberler, genel risk ortamı
2. Piyasa özeti: teknik ve momentum durumu
3. Formasyon tespitleri (varsa)
4. Portföy dengesi notu
</analysis>

```json
[
  {
    "account": "hesap_adi",
    "symbol": "BTCUSDT",
    "decision": "LONG | SHORT | NO_TRADE | HOLD | CLOSE",
    "confidence": 0.0-1.0,
    "reason": "teknik veya makro gerekçe (1-2 cümle, boş bırakma)",
    "market_bias": "bullish | bearish | neutral",
    "risk_note": "varsa risk notu"
  }
]

### User Prompt Template (Django Template)

```django
{% autoescape off %}
## Portföy Durumu

{% for acct in accounts %}
### {{ acct.name }}
{% for p in acct.portfolio %}
  {% if not p.has_position %}
- **{{ p.symbol }}** ({{ p.leverage }}x): Pozisyon YOK
  {% elif p.pnl_str %}
- **{{ p.symbol }}** ({{ p.leverage }}x): {{ p.side }} @ ${{ p.entry_price }} (PnL: {{ p.pnl_str }}, süre: {{ p.duration }})
  {% else %}
- **{{ p.symbol }}** ({{ p.leverage }}x): {{ p.side }}
  {% endif %}
{% endfor %}
{% endfor %}

{% if has_market_data %}
## Piyasa Verileri

### Genel Piyasa
  Fear & Greed: {{ fg_value }} ({{ fg_class }})
  [News items...]
  [Calendar items...]

### [SYMBOL]
  Fiyat: $...
  RSI(14): ...
  MACD: ... / sinyal: ... / hist: ...
  ADX(14): ...
  BB: lower — middle — upper
  ATR(14): ...
  Mumlar (4h OHLCV): [last 20 candles]
  Funding: ...

### Makro Göstergeler
  US30 (Dow Jones): $... (+/-...%)
  [5-day US30 candles]
{% endif %}

## Görev
Yukarıdaki verileri analiz et ve her hesap×coin kombinasyonu için JSON formatında karar döndür.
{% endautoescape %}

Decision Pipeline Code

Signal Provider (simplified)

class LLMSignalProvider(SignalProvider):
    def get_signals_multi(self, strategies, all_positions, all_open_positions, accounts, dry_run=False):
        # 1. Pre-fetch market data for all symbols in parallel (ThreadPoolExecutor, 20 workers)
        symbols = [s.symbol for s in strategies]
        market_data = _prefetch_market_data(symbols)

        # 2. Build user message from Django template
        user_message = _build_user_message_multi(strategies, all_positions, all_open_positions, account_names, market_data)

        # 3. Single LLM call with tool-calling loop
        adapter = get_llm_adapter()  # ClaudeAdapter(model="claude-sonnet-4-6")
        result = adapter.chat_with_tools(
            system_prompt=_get_system_prompt(),
            user_message=user_message,
            tools=[CALL_API_TOOL],          # LLM can request extra data
            tool_executor=_tool_executor,    # Routes to simsar API
            max_iterations=10,
        )

        # 4. Parse multi-account decisions from response
        decisions_per_account = _parse_decisions_multi(result.text)

        # 5. Convert to signals with confidence threshold
        confidence_threshold = 0.75
        for account in accounts:
            acct_decisions = decisions_per_account.get(account.name, [])
            signals = _decisions_to_signals(acct_decisions, strategies, positions, confidence_threshold)
            # Save LLMDecision to DB for audit trail

        return signals_per_account

Confidence Threshold Logic

def _decisions_to_signals(decisions, strategies, positions, confidence_threshold):
    for d in decisions:
        decision = d.get("decision", "NO_TRADE").upper()
        confidence = float(d.get("confidence", 0))
        current_side = positions.get(symbol, Side.NONE)

        if decision == "LONG" and current_side == Side.NONE:
            if confidence >= confidence_threshold:  # ≥ 0.75
                action = Action.OPEN_LONG
            else:
                action = Action.HOLD  # Too low confidence, skip
                reason = f"Low confidence ({confidence:.0%}): {reason}"

        elif decision == "SHORT" and current_side == Side.NONE:
            if confidence >= confidence_threshold:  # ≥ 0.75
                action = Action.OPEN_SHORT
            else:
                action = Action.HOLD

        elif decision == "CLOSE" and current_side != Side.NONE:
            action = Action.CLOSE  # No confidence threshold for closes

        elif decision in ("HOLD", "NO_TRADE"):
            action = Action.HOLD

LLM Tool-Calling Loop

class ClaudeAdapter(LLMAdapter):
    def chat_with_tools(self, system_prompt, user_message, tools, tool_executor, max_iterations=10):
        messages = [{"role": "user", "content": user_message}]

        for iteration in range(max_iterations):
            response = self.client.messages.create(
                model="claude-sonnet-4-6",
                max_tokens=8192,
                system=system_prompt,
                tools=tools,
                messages=messages,
            )

            tool_uses = [b for b in response.content if b.type == "tool_use"]
            text_blocks = [b for b in response.content if b.type == "text"]

            if not tool_uses:  # No tool calls = final answer
                break

            # Execute tool calls (e.g., fetch Open Interest, L/S ratio)
            messages.append({"role": "assistant", "content": response.content})
            tool_results = []
            for tu in tool_uses:
                result_str = tool_executor(tu.name, tu.input)
                tool_results.append({"type": "tool_result", "tool_use_id": tu.id, "content": result_str})
            messages.append({"role": "user", "content": tool_results})

        return LLMResult(text=final_text, ...)

Available Tool for LLM

The LLM has one tool (call_api) that can call any endpoint on the simsar market data API:

CALL_API_TOOL = {
    "name": "call_api",
    "description": "Simsar piyasa verisi API'si. Ek veri çekmek için kullan.",
    "input_schema": {
        "type": "object",
        "properties": {
            "endpoint": {"type": "string", "description": "API endpoint (e.g., /futures/long-short-ratio/BTCUSDT)"},
            "params": {"type": "object", "description": "Query parameters"},
        },
        "required": ["endpoint"],
    },
}

# Available endpoints:
# /price/{symbol}
# /candles/{symbol}?interval=4h&limit=20
# /indicators/stoch/{symbol}?interval=4h
# /indicators/ema/{symbol}?interval=4h&period=20
# /indicators/sma/{symbol}?interval=4h&period=20
# /indicators/obv/{symbol}?interval=4h
# /indicators/mfi/{symbol}?interval=4h
# /indicators/cci/{symbol}?interval=4h
# /indicators/willr/{symbol}?interval=4h
# /futures/open-interest/{symbol}
# /futures/long-short-ratio/{symbol}
# /futures/top-trader-ratio/{symbol}

Configuration

# config/settings.py
LLM_PROVIDER = "claude"
LLM_TRADE_MODEL = "claude-sonnet-4-6"
LLM_TRADE_MAX_ITERATIONS = 10
LLM_TRADE_CONFIDENCE_THRESHOLD = 0.75

# Trading fees
FUTURES_TAKER_FEE = Decimal('0.0005')  # 0.05%
FUTURES_MAKER_FEE = Decimal('0.0002')  # 0.02%

Cost

Each hourly decision cycle:

  • ~20K input tokens (market data + portfolio + system prompt)
  • ~2K output tokens (analysis + JSON decisions)
  • Sonnet 4.6 pricing: $3/1M input, $15/1M output
  • ~$0.09 per cycle → ~$2.16/day → ~$65/month

Known Issues / Areas for Improvement

  1. LONG bias in bearish markets — In the first 24h, 38/41 trades were LONG despite bearish conditions
  2. Too frequent trading — Positions held only 1-2 hours, accumulating commission costs
  3. No memory across cycles — Each hourly call is independent; LLM doesn't remember its previous reasoning
  4. Single timeframe — Only 4h candles pre-fetched; no multi-timeframe analysis
  5. No position sizing — Always uses full allocation per position
  6. PAXG hedge failure — PAXG was supposed to hedge risk-off, but LLM went LONG PAXG alongside LONG crypto

Example LLM Output

<analysis>
1. Risk: US30 son 3 günde %1.2 düştü, risk-off sinyali. PAXG yükselişte — güvenli liman talebi.
2. BTC: RSI 42 (nötr), MACD negatif histogram daralmakta, ADX 18 (trend zayıf).
3. ETH: Double bottom formasyonu 2400$ seviyesinde, RSI yükseliş divergansı.
4. Portföy: 3 hesapta 7 açık pozisyon, hepsi LONG — risk yoğunlaşması.
</analysis>

```json
[
  {"account": "mirat", "symbol": "BTCUSDT", "decision": "NO_TRADE", "confidence": 0.3, "reason": "Trend belirsiz, ADX düşük, momentum zayıf", "market_bias": "neutral", "risk_note": "US30 düşüşü kripto'ya yayılabilir"},
  {"account": "mirat", "symbol": "ETHUSDT", "decision": "LONG", "confidence": 0.78, "reason": "Double bottom + RSI divergansı, 2400$ destek güçlü", "market_bias": "bullish", "risk_note": "Genel risk-off ortamda dikkatli ol"},
  {"account": "mirat", "symbol": "PAXGUSDT", "decision": "LONG", "confidence": 0.82, "reason": "Risk-off ortam, altın güvenli liman talebi artıyor", "market_bias": "bullish", "risk_note": null},
  ...
]

---

## Today's Performance (2026-03-02)

### Summary
- **13 LLM decision rounds** (hourly cycles)
- **13 positions opened**, **16 positions closed**
- **Total realized PnL: -$7.59**
- **Total LLM cost: $1.22**
- **Net: -$8.81**

### Trades Executed

#### Opened
| Time | Account | Symbol | Side | Entry Price |
|------|---------|--------|------|-------------|
| 02:05 | mevlut | ETHUSDT | LONG | $1,964.00 |
| 02:06 | mirat | BTCUSDT | LONG | $66,491.80 |
| 06:07 | mirat | BTCUSDT | LONG | $66,596.20 |
| 06:07 | mirat | ETHUSDT | LONG | $1,967.01 |
| 06:08 | tugrul | AVAXUSDT | LONG | $9.03 |
| 06:08 | tugrul | SOLUSDT | LONG | $83.79 |
| 06:08 | tugrul | LINKUSDT | LONG | $8.77 |
| 07:06 | mevlut | ETHUSDT | SHORT | $1,933.15 |
| 08:07 | mevlut | PAXGUSDT | LONG | $5,441.58 |
| 08:07 | tugrul | ETHUSDT | SHORT | $1,933.60 |
| 08:07 | tugrul | PAXGUSDT | LONG | $5,441.70 |
| 08:15 | mirat | PAXGUSDT | LONG | $5,442.90 |
| 20:07 | mirat | BTCUSDT | LONG | $68,918.10 |

#### Closed
| Time | Account | Symbol | Side | Entry → Exit | PnL |
|------|---------|--------|------|--------------|-----|
| 00:06 | mirat | AVAXUSDT | LONG | $8.87 → $9.04 | +$1.02 |
| 00:06 | mirat | ETHUSDT | LONG | $1,912 → $1,942 | +$0.89 |
| 00:06 | mirat | LINKUSDT | LONG | $8.55 → $8.68 | +$0.87 |
| 00:06 | mirat | SOLUSDT | LONG | $82.02 → $83.55 | +$1.03 |
| 03:05 | mevlut | ETHUSDT | LONG | $1,964 → $1,950 | -$0.43 |
| 04:05 | mirat | BTCUSDT | LONG | $66,491 → $66,730 | +$0.48 |
| 07:07 | tugrul | AVAXUSDT | LONG | $9.03 → $8.92 | -$0.43 |
| 07:07 | tugrul | LINKUSDT | LONG | $8.77 → $8.65 | -$0.51 |
| 07:07 | tugrul | SOLUSDT | LONG | $83.79 → $82.77 | -$0.47 |
| 08:07 | mirat | BTCUSDT | LONG | $66,596 → $65,846 | -$1.50 |
| 08:07 | mirat | ETHUSDT | LONG | $1,967 → $1,933 | -$2.39 |
| 12:07 | mevlut | ETHUSDT | SHORT | $1,933 → $1,947 | -$0.37 |
| 12:07 | tugrul | ETHUSDT | SHORT | $1,933 → $1,946 | -$0.25 |
| 16:07 | mevlut | PAXGUSDT | LONG | $5,441 → $5,303 | -$1.24 |
| 16:07 | mirat | PAXGUSDT | LONG | $5,442 → $5,304 | -$3.46 |
| 16:07 | tugrul | PAXGUSDT | LONG | $5,441 → $5,304 | -$0.83 |

### LLM Decision Log (All Cycles)

**08:06 — Morning session (Fear & Greed: 10 = Extreme Fear)**
- Market: US30 -1%, risk-off environment
- BTC couldn't break 67,500 resistance, L/S ratio 1.69 (crowded longs)
- mirat: CLOSE BTC (conf: 0.72), CLOSE ETH (conf: 0.78) — cut losses on falling positions
- mevlut: NO_TRADE most coins, ETH SHORT held
- tugrul: SHORT ETH (conf: 0.70 — below 0.75 threshold, NOT executed), LONG PAXG (conf: 0.72 — below threshold)
- PAXG LONG decisions all below 0.75 threshold at this point

**08:15 — mirat retry**
- PAXG LONG (conf: 0.78) — executed this time, above threshold
- All others NO_TRADE

**12:07 — Midday**
- mevlut & tugrul: CLOSE ETH SHORT (conf: 0.70) — ETH recovering against short
- All accounts: NO_TRADE on everything else, low confidence (0.60-0.65)
- PAXG HOLDs maintained

**16:07 — Afternoon (PAXG crash)**
- PAXG CLOSE across all 3 accounts (conf: 0.82) — sharp drop $5,441→$5,329
- Biggest single loss: mirat PAXG -$3.46
- All crypto coins NO_TRADE despite BTC breakout — caution due to overbought signals

**20:07 — Evening (BTC breakout confirmed)**
- BTC LONG across all accounts (conf: 0.78) — volume spike breakout 65.2K→69.1K
- ETH LONG (conf: 0.72 — below 0.75, NOT executed for mevlut/tugrul, executed for... wait, 0.72 < 0.75)
- Most altcoins NO_TRADE due to crowded L/S ratios (AVAX 2.37, LINK 2.25)
- PAXG NO_TRADE — risk-on shift after BTC breakout

### Observations
1. **Confidence threshold working**: Several decisions at 0.68-0.72 were correctly filtered out
2. **PAXG hedge failed again**: Bought at $5,441, crashed to $5,303 within 8 hours (-$5.53 across accounts)
3. **Short-lived positions**: ETH SHORT lasted ~4 hours, lost money on all accounts
4. **Commission drag**: 16 round-trip trades × ~0.30% commission = significant drag on small positions
5. **Correct close calls**: BTC and ETH closes at 08:07 prevented bigger losses (BTC dropped further to $65,846)
6. **Late BTC entry**: The 20:07 BTC LONG at $68,918 caught the breakout — still open, unrealized
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment