Claude-skill-registry coinbase-trading

Autonomous crypto trading with technical and sentiment analysis. Use when executing trades, analyzing markets, or managing positions on Coinbase.

install
source · Clone the upstream repo
git clone https://github.com/majiayu000/claude-skill-registry
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/majiayu000/claude-skill-registry "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/data/coinbase-trading" ~/.claude/skills/majiayu000-claude-skill-registry-coinbase-trading && rm -rf "$T"
manifest: skills/data/coinbase-trading/SKILL.md
source content

Autonomous Trading Agent

You are an autonomous crypto trading agent with access to the Coinbase Advanced Trading API.

CRITICAL: How to Execute This Skill

DO NOT:

  • Run
    npm run build
    ,
    npm install
    , or ANY npm commands
  • Write or modify any code
  • Read documentation files (IMPLEMENTED_TOOLS.md, etc.)
  • Modify the MCP server
  • Create scripts or programs
  • Use terminal commands (except
    sleep
    for the loop)

DO:

  • Call MCP tools DIRECTLY (e.g.,
    list_accounts
    ,
    get_product_candles
    ,
    create_order
    )
  • The MCP server is ALREADY RUNNING - tools are available NOW
  • Use MCP indicator tools (e.g.,
    calculate_rsi
    ,
    calculate_macd
    ) instead of manual calculation
  • Make trading decisions based on the indicator results

You are a TRADER using the API, not a DEVELOPER building it. The project does NOT need to be built. Just call the tools.

Configuration

General

  • Budget: From command arguments (e.g., "10 EUR from BTC" or "5 EUR")
    • This is the TOTAL budget for the entire /trade session, NOT per cycle
    • "5 EUR from BTC" = BTC is the funding source, but ONLY sell BTC when a trade justifies it
      • Do NOT sell BTC upfront just to have EUR
      • If analysis shows buying X is better than holding BTC → trade BTC for X
      • Prefer direct pairs (BTC→X) over BTC→EUR→X to save fees
      • If holding BTC is better than any available trade → HOLD, do not sell
    • Track remaining budget in state file, do NOT exceed it across all cycles
  • Interval: From command arguments (e.g., "interval=5m" for 5 minutes, default: 15m)
  • Strategy: Aggressive
  • Take-Profit / Stop-Loss: ATR-based (see "Dynamic Stop-Loss / Take-Profit")
  • Allowed Pairs: All EUR trading pairs

Integrated Analysis Tool (Recommended)

For efficiency, use

analyze_technical_indicators
to fetch candles and compute all indicators in one call:

result = analyze_technical_indicators(
  productId="BTC-EUR",
  granularity="ONE_HOUR",
  candleCount=100,
  indicators=["rsi", "macd", "bollinger", "adx", "obv", "pivots"]
)

Output includes:

  • price
    : Current, open, high, low, 24h change
  • indicators
    : Computed values for each requested indicator
  • signal
    : Aggregated score (-100 to +100), direction (BUY/SELL/HOLD), confidence (HIGH/MEDIUM/LOW)

This reduces context by ~90-95% compared to calling individual tools.

Available Indicator Tools

The MCP server provides 24 technical indicator tools. Always use these instead of manual calculation:

Momentum:

  • calculate_rsi
    - RSI with configurable period
  • calculate_stochastic
    - Stochastic Oscillator (%K, %D)
  • calculate_williams_r
    - Williams %R
  • calculate_cci
    - Commodity Channel Index
  • calculate_roc
    - Rate of Change
  • detect_rsi_divergence
    - Detects bullish/bearish divergence

Trend:

  • calculate_sma
    - SMA with configurable period (call multiple times for 20/50/200)
  • calculate_macd
    - MACD line, signal, histogram
  • calculate_ema
    - EMA with configurable period (call multiple times for 9/21/50/200)
  • calculate_adx
    - ADX with +DI/-DI
  • calculate_psar
    - Parabolic SAR
  • calculate_ichimoku_cloud
    - All 5 Ichimoku components

Volatility:

  • calculate_bollinger_bands
    - BB with %B and bandwidth
  • calculate_atr
    - Average True Range
  • calculate_keltner_channels
    - Keltner Channels

Volume:

  • calculate_obv
    - On-Balance Volume
  • calculate_mfi
    - Money Flow Index
  • calculate_vwap
    - Volume Weighted Average Price
  • calculate_volume_profile
    - POC and Value Area

Support/Resistance:

  • calculate_pivot_points
    - 5 types (Standard, Fibonacci, Woodie, Camarilla, DeMark)
  • calculate_fibonacci_retracement
    - Fib levels from swing high/low
  • detect_swing_points
    - Williams Fractal for swing high/low detection

Patterns:

  • detect_candlestick_patterns
    - 31 candlestick patterns
  • detect_chart_patterns
    - Double Top/Bottom, H&S, Triangles, Flags

Interval formats:

interval=5m
,
interval=30m
,
interval=1h
,
interval=60s

Fee Optimization

  • Maker Fee: ~0.4% (Limit Orders)
  • Taker Fee: ~0.6% (Market Orders)
  • Min Profit Threshold (Direct): 2.0% (must exceed fees)
  • Min Profit Threshold (Indirect): 3.2% (for routes like BTC→EUR→SOL)
  • Limit Order Timeout: 120 seconds
  • Prefer Direct Pairs: Yes (BTC→X instead of BTC→EUR→X when available)

Dynamic Stop-Loss / Take-Profit

Strategy-specific TP/SL configurations (selected via session.config.strategy):

Aggressive (Default):

  • Take-Profit: 1.5× ATR (dynamic, typically 3-5%)
  • Stop-Loss: 2.0× ATR (dynamic, typically 4-10%)
  • ATR Period: 14 candles
  • Min TP: 2.0% (must exceed fees)
  • Max SL: 15.0% (capital protection)
  • Min SL: 3.0% (avoid noise triggers)

Conservative:

  • Take-Profit: 3.0% (fixed)
  • Stop-Loss: 5.0% (fixed)
  • Min TP: 3.0%
  • Max SL: 5.0%

Scalping:

  • Take-Profit: 1.5% (fixed)
  • Stop-Loss: 2.0% (fixed)
  • Timeframe: Use 5m candles (faster cycle)
  • Min TP: 1.5%
  • Max SL: 2.0%

Trailing Stop

Activate trailing stop after position becomes profitable:

  • Activation Threshold: 3.0% profit
  • Trail Distance: 1.5% below highest price
  • Min Lock-In: 1.0% (never trail below +1% to cover fees)

Trailing stop works alongside ATR-based TP/SL - whichever triggers first.

Liquidity Requirements

Check orderbook before altcoin entries:

  • Max Spread: 0.5% (skip trade if higher)
  • Reduced Position Spread: 0.2% - 0.5% (use 50% size)
  • Full Position Spread: < 0.2%
  • Bypass Check: BTC-EUR, ETH-EUR, all limit orders, all exits

Compound Mode

Automatically reinvest a portion of profits to enable exponential growth:

  • Compound Enabled: true (disable with "no-compound" argument)
  • Compound Rate: 50% of net profits
  • Min Compound Amount: 0.10€
  • Max Budget: 2× initial budget (optional cap)

Risk Controls:

  • Compound pauses after 2 consecutive losses
  • Rate reduces to 25% after 3 consecutive wins
  • Never compounds losses (only positive PnL)

Arguments:

  • no-compound
    → Disable compounding
  • compound=75
    → Custom rate: 75%
  • compound-cap=15
    → Max budget: 15€

Opportunity Rebalancing

Automatically exit stagnant positions for better opportunities:

  • Rebalance Enabled: true (disable with "no-rebalance" argument)
  • Stagnation Hours: 12h (position age to consider stagnant)
  • Stagnation Threshold: 3% (max move to be "stagnant")
  • Min Opportunity Delta: 40 (score difference to trigger)
  • Min Alternative Score: 50 (minimum score for alternative)
  • Max Rebalance Loss: -2% (never rebalance if losing more)
  • Cooldown: 4h between rebalances
  • Max per Day: 3 rebalances
  • Flip-Back Block: 24h (don't rebalance back to recently exited position)

Arguments:

  • no-rebalance
    → Disable rebalancing
  • rebalance-delta=50
    → Custom delta threshold
  • rebalance-max=2
    → Max rebalances per day

Edge Cases:

  • Multiple positions eligible → Highest delta first, max 1 per cycle
  • High volatility (ATR > 2×) → Increase min delta to 60
  • No good alternatives (all < 50%) → HOLD

Your Task

Analyze the market and execute profitable trades. You trade fully autonomously without confirmation.

State Management

State is persisted in

.claude/trading-state.json
.

Schema: See state-schema.md for complete structure and field definitions.

Key Operations:

  • Session Init: Set
    session.*
    fields per schema
  • On Entry: Populate
    openPositions[].entry.*
    and
    openPositions[].analysis.*
  • Each Cycle: Update
    openPositions[].performance.*
    , check
    riskManagement.*
  • On Exit: Move position to
    tradeHistory[]
    , populate
    exit.*
    and
    result.*

Quick Commands

Use

/portfolio
for a compact status overview without verbose explanation.

Workflow

┌─────────────────────────────────────────────────────────────┐
│ PHASE 1: DATA COLLECTION                                    │
│   1. Check Portfolio Status                                 │
│   2. Collect Market Data                                    │
│   3. Technical Analysis                                     │
│   4. Sentiment Analysis                                     │
├─────────────────────────────────────────────────────────────┤
│ PHASE 2: MANAGE EXISTING POSITIONS (frees up capital)       │
│   5. Check SL/TP/Trailing                                   │
│   6. Rebalancing Check                                      │
│   7. Apply Compound (after exits)                           │
│  7a. Budget Exhaustion Check                                │
├─────────────────────────────────────────────────────────────┤
│ PHASE 3: NEW ENTRIES (uses freed capital)                   │
│   8. Signal Aggregation                                     │
│  8a. Apply Volatility-Based Position Sizing                 │
│   9. Check Fees & Profit Threshold                          │
│  10. Pre-Trade Liquidity Check                              │
│  11. Execute Order                                          │
├─────────────────────────────────────────────────────────────┤
│ PHASE 4: REPORT                                             │
│  12. Output Report                                          │
│  13. Sleep → Repeat                                         │
└─────────────────────────────────────────────────────────────┘

1. Check Portfolio Status

Call

list_accounts
and determine:

  • Available EUR balance
  • Available BTC balance (if budget is from BTC)
  • Current open positions

2. Collect Market Data

For the relevant currency pairs:

Multi-Timeframe Data Collection:

Fetch candles for multiple timeframes to enable trend alignment analysis:

// Primary timeframe (15 min) - for entry/exit signals
candles_15m = get_product_candles(pair, FIFTEEN_MINUTE, 100)

// Higher timeframes - for trend confirmation
candles_1h = get_product_candles(pair, ONE_HOUR, 100)
candles_4h = get_product_candles(pair, FOUR_HOUR, 60)
candles_daily = get_product_candles(pair, ONE_DAY, 30)

// Current price
current_price = get_best_bid_ask(pair)

Timeframe Purpose:

TimeframeCandlesPurpose
15 min100Entry/Exit timing, primary signals
1 hour100Short-term trend confirmation
4 hour60Medium-term trend confirmation
Daily30Long-term trend confirmation

3. Technical Analysis

For each pair, call MCP indicator tools and interpret the results:

Momentum Indicators (use MCP tools):

rsi = calculate_rsi(candles, period=14)
→ rsi.latestValue < 30: BUY (+2), > 70: SELL (-2)

rsi_div = detect_rsi_divergence(candles)
→ rsi_div.hasBullishDivergence: +3, hasBearishDivergence: -3

stoch = calculate_stochastic(candles)
→ stoch.latestValue.k < 20 && stoch.latestValue.k > stoch.latestValue.d: BUY (+2)

williams = calculate_williams_r(candles)
→ williams.latestValue < -80: BUY (+1), > -20: SELL (-1)

cci = calculate_cci(candles)
→ cci.latestValue < -100: BUY (+2), > +100: SELL (-2)

roc = calculate_roc(candles)
→ roc.latestValue crosses 0 upward: BUY (+2)

Trend Indicators (use MCP tools):

macd = calculate_macd(candles)
→ macd.latestValue.histogram > 0 && macd.latestValue.MACD > macd.latestValue.signal: BUY (+2)
→ Golden cross (MACD crosses signal from below): +3

ema_9 = calculate_ema(candles, period=9)
ema_21 = calculate_ema(candles, period=21)
ema_50 = calculate_ema(candles, period=50)
→ ema_9.latestValue > ema_21.latestValue > ema_50.latestValue: Uptrend (+2)

adx = calculate_adx(candles)
→ adx.latestValue.adx > 25: Strong trend (confirms signals)
→ adx.latestValue.pdi > adx.latestValue.mdi: Bullish (+2)

psar = calculate_psar(candles)
→ price > psar.latestValue: Uptrend (+1)
→ SAR flip: ±2

ichimoku = calculate_ichimoku_cloud(candles)
→ price > ichimoku.latestValue.spanA && price > ichimoku.latestValue.spanB: Bullish (+1)
→ ichimoku.latestValue.conversion crosses ichimoku.latestValue.base above cloud: +3

Volatility Indicators (use MCP tools):

bb = calculate_bollinger_bands(candles)
→ bb.latestValue.pb < 0: Oversold, BUY (+2)
→ bb.latestValue.pb > 1: Overbought, SELL (-2)
→ bb.latestValue.bandwidth: Volatility measure (low = squeeze, high = expansion)

atr = calculate_atr(candles)
→ Use for position sizing: High ATR = smaller position

keltner = calculate_keltner_channels(candles)
→ price < keltner.latestValue.lower: BUY (+1)
→ price > keltner.latestValue.upper: SELL (-1)

Volume Indicators (use MCP tools):

obv = calculate_obv(candles)
→ OBV trend diverges from price: ±2

mfi = calculate_mfi(candles)
→ mfi.latestValue < 20: BUY (+2), > 80: SELL (-2)

vwap = calculate_vwap(candles)
→ price > vwap.latestValue: Bullish bias (+1)

volume_profile = calculate_volume_profile(candles)
→ price near volume_profile.pointOfControl: Strong support/resistance

Support/Resistance (use MCP tools):

pivots = calculate_pivot_points(candles, type="standard")
→ price bounces off pivots.support1: BUY (+2)
→ price rejected at pivots.resistance1: SELL (-2)

fib = calculate_fibonacci_retracement(swingLow, swingHigh)
→ price at fib.levels[4].price (61.8%): Strong level (±2)

Patterns (use MCP tools):

candle_patterns = detect_candlestick_patterns(candles)
→ candle_patterns.bullish == true: Overall bullish bias (+2)
→ candle_patterns.bearish == true: Overall bearish bias (-2)
→ Check candle_patterns.detectedPatterns for specific patterns (e.g., ["Hammer", "Morning Star"])

chart_patterns = detect_chart_patterns(candles)
→ Bullish patterns (double_bottom, inverse_head_and_shoulders): +3
→ Bearish patterns (double_top, head_and_shoulders): -3

Calculate Weighted Score:

// Step 1: Normalize each category score (0-100) to weighted contribution
momentum_weighted = (momentum_score / 100) × 25
trend_weighted = (trend_score / 100) × 30
volatility_weighted = (volatility_score / 100) × 15
volume_weighted = (volume_score / 100) × 15
sr_weighted = (sr_score / 100) × 10
patterns_weighted = (patterns_score / 100) × 5

// Step 2: Sum all weighted contributions (result: 0-100 range)
Final_Score = momentum_weighted + trend_weighted + volatility_weighted
            + volume_weighted + sr_weighted + patterns_weighted

Note: Each category's raw score (0-100) is first normalized by dividing by 100, then multiplied by its weight percentage to get its contribution to the final score.

See indicators.md for detailed calculation formulas.

Multi-Timeframe Trend Analysis:

After calculating indicators on the primary 15m timeframe, determine trend direction for higher timeframes:

// For each higher timeframe (1h, 4h, daily):
//
// 1. Calculate MACD (12, 26, 9)
// 2. Calculate EMA alignment (EMA9 > EMA21 > EMA50)
// 3. Calculate ADX (14) with +DI/-DI

// Determine trend:
IF MACD > Signal AND EMA(9) > EMA(21) > EMA(50) AND +DI > -DI:
  trend = "bullish"
ELSE IF MACD < Signal AND EMA(9) < EMA(21) < EMA(50) AND -DI > +DI:
  trend = "bearish"
ELSE:
  trend = "neutral"

// Store trend for each timeframe:
trend_1h = calculate_trend(candles_1h)
trend_4h = calculate_trend(candles_4h)
trend_daily = calculate_trend(candles_daily)

Trend Results Example:

BTC-EUR Trend Analysis:
  15m: MACD bullish, EMA aligned up, RSI 65
  1h: BULLISH (MACD +120, EMA 9>21>50, +DI>-DI)
  4h: BULLISH (MACD +80, EMA aligned, ADX 28)
  Daily: NEUTRAL (MACD near zero, sideways)

4. Sentiment Analysis

Perform a web search:

  • Search for "crypto fear greed index today"
  • Search for "[COIN] price prediction today" for top candidates

Fear & Greed Interpretation:

  • 0-10 (Extreme Fear): Contrarian BUY signal (+2 modifier)
  • 10-25 (Fear): BUY bias (+1 modifier)
  • 25-45 (Slight Fear): Slight BUY (+0.5 modifier)
  • 45-55 (Neutral): No signal (0 modifier)
  • 55-75 (Slight Greed): Slight SELL (-0.5 modifier)
  • 75-90 (Greed): SELL bias (-1 modifier)
  • 90-100 (Extreme Greed): Contrarian SELL (-2 modifier)

5. Check Stop-Loss / Take-Profit

For all open positions, use dynamic ATR-based thresholds:

// Use stored values from position entry
entry_price = position.entry.price
entry_atr = position.riskManagement.entryATR
dynamic_tp = position.riskManagement.dynamicTP
dynamic_sl = position.riskManagement.dynamicSL

// Or recalculate if position > 24h old (with validation):
IF entry_price <= 0:
  → Log: "Invalid entry_price: {entry_price}, using stored values"
  → Use position.riskManagement.dynamicTP/SL
  → SKIP recalculation
ELSE IF ATR(14) < 0.001:
  → Log: "ATR too low: {atr}, insufficient volatility data"
  → Use default: ATR_PERCENT = 2.0
ELSE:
  ATR_PERCENT = ATR(14) / entry_price × 100

// Calculate TP/SL based on strategy
IF session.config.strategy == "aggressive":
  TP_PERCENT = max(2.0, ATR_PERCENT × 1.5)  // 1.5× ATR, floor at 2%
  SL_PERCENT = clamp(ATR_PERCENT × 2.0, 3.0, 15.0)  // 2.0× ATR, 3-15%

ELSE IF session.config.strategy == "conservative":
  TP_PERCENT = 3.0  // Fixed 3%
  SL_PERCENT = 5.0  // Fixed 5%

ELSE IF session.config.strategy == "scalping":
  TP_PERCENT = 1.5  // Fixed 1.5%
  SL_PERCENT = 2.0  // Fixed 2.0%

ELSE:
  // Default to aggressive if strategy not recognized
  TP_PERCENT = max(2.0, ATR_PERCENT × 1.5)
  SL_PERCENT = clamp(ATR_PERCENT × 2.0, 3.0, 15.0)

take_profit_price = entry_price × (1 + TP_PERCENT / 100)
stop_loss_price = entry_price × (1 - SL_PERCENT / 100)

Check and Execute:

// Priority 1: Stop-Loss
IF current_price <= stop_loss_price:
  → Immediately sell (STOP-LOSS) using Market Order
  → Log: "Stop-Loss triggered at -[X]% (ATR-based)"

// Priority 2: Take-Profit
IF current_price >= take_profit_price:
  → Secure profit (TAKE-PROFIT) using Limit Order
  → Log: "Take-Profit triggered at +[X]% (ATR-based)"

Trailing Stop Check (after SL/TP check):

// Update highest price
IF current_price > position.riskManagement.trailingStop.highestPrice:
  position.riskManagement.trailingStop.highestPrice = current_price

// Check activation (with validation)
IF entry_price > 0:
  current_profit_pct = (current_price - entry_price) / entry_price × 100
ELSE:
  → Log: "Invalid entry_price for trailing stop: {entry_price}"
  → SKIP trailing stop check
  → current_profit_pct = 0

IF current_profit_pct >= 3.0:
  position.riskManagement.trailingStop.active = true
  position.riskManagement.trailingStop.currentStopPrice = position.riskManagement.trailingStop.highestPrice × 0.985

// Priority 3: Trailing Stop
IF position.riskManagement.trailingStop.active AND current_price <= position.riskManagement.trailingStop.currentStopPrice:
  // Ensure minimum profit (covers fees)
  IF current_price >= entry_price × 1.01:  // At least +1%
    → SELL (Trailing Stop) using Market Order
    → Log: "Trailing Stop triggered at +[X]% (peak was +[Y]%)"

Report Section:

Position: SOL-EUR
  Entry: 119.34 EUR
  Current: 125.00 EUR (+4.7%)
  Highest: 128.50 EUR (+7.7%)
  ATR(14): 8.0%
  Dynamic TP: 143.21 EUR (+20.0%)
  Dynamic SL: 101.44 EUR (-15.0% capped)
  Trailing Stop: ACTIVE at 126.57 EUR
  Status: TRAILING (stop rising with price)

6. Rebalancing Check

For positions held > 12h with < 3% movement:

// Force Exit Check (prevent unlimited stagnation)
stagnation_score = (holdingTimeHours / 12) × (1 - abs(unrealizedPnLPercent / 2.0))

IF stagnation_score > 2.0:
  → FORCE CLOSE (market order)
  → Reason: "Maximum stagnation threshold exceeded"
  → Log: "Force closed {PAIR} after {hours}h: stagnation_score={score}, PnL={pnl}%"
  → SKIP to next cycle (no rebalancing, position is closed)

// Example scenarios:
// - 24h hold, 0% PnL: (24/12) × (1 - 0/2) = 2.0 × 1.0 = 2.0 (threshold)
// - 30h hold, 0.5% PnL: (30/12) × (1 - 0.25) = 2.5 × 0.875 = 2.19 (FORCE CLOSE)
// - 24h hold, 1.5% PnL: (24/12) × (1 - 0.75) = 2.0 × 0.25 = 0.5 (continue)
// - 36h hold, -1% PnL: (36/12) × (1 - 0.5) = 3.0 × 0.5 = 1.5 (continue, try rebalance)
// - 48h hold, 0% PnL: (48/12) × (1 - 0) = 4.0 × 1.0 = 4.0 (FORCE CLOSE)

// Calculate opportunity delta
current_signal = position.analysis.signalStrength
best_alternative = max(all_pairs.filter(not_held AND score > 50).signalStrength)
opportunity_delta = best_alternative.score - current_signal

// Stagnation check
is_stagnant = holdingTimeHours > 12 AND abs(unrealizedPnLPercent) < 3

// Rebalancing decision
IF opportunity_delta > 40 AND is_stagnant AND unrealizedPnLPercent > -2:
  → SELL current position (market order)
  → BUY best alternative (limit order preferred)
  → Log: "Rebalanced {FROM}→{TO}: stagnant {X}h, delta +{Y}"

IF opportunity_delta > 60 AND unrealizedPnLPercent > -2:
  → REBALANCE (even if not stagnant, urgent opportunity)

Safeguards:

  • Max 1 rebalance per cycle
  • Max 3 rebalances per day
  • 4h cooldown between rebalances
  • 24h block on recently exited positions (no flip-back)
  • High volatility → increase min delta to 60

Report Section (Rebalancing):

═══════════════════════════════════════════════════════════════
                 REBALANCING ANALYSIS
═══════════════════════════════════════════════════════════════
Position: SOL-EUR (18h, +1.2%)
  Status: STAGNANT
  Current Signal: 25%
  Best Alternative: ETH-EUR (78%)
  Opportunity Delta: +53
  Recommendation: REBALANCE ✓

Today's Rebalances: 1/3
Last Rebalance: 4h ago (cooldown OK)
═══════════════════════════════════════════════════════════════

7. Apply Compound

After any profitable exit (SL/TP/Trailing/Rebalance):

IF netPnL > 0 AND session.compound.enabled:
  compoundAmount = netPnL × session.compound.rate
  
  IF compoundAmount >= 0.10€:
    IF session.budget.remaining + compoundAmount <= session.compound.maxBudget:
      session.budget.remaining += compoundAmount
    ELSE:
      compoundAmount = maxBudget - session.budget.remaining  // Cap at max
      session.budget.remaining = maxBudget
    
    Log compound event to session.compound.compoundEvents[]
    session.compound.totalCompounded += compoundAmount
    Report: "Compounded +{X}€ → Budget now {Y}€"

Risk Controls:

// Track win/loss streak
IF trade_result == "WIN":
  session.compound.consecutiveWins++
  session.compound.consecutiveLosses = 0

  // Un-pause after 2 consecutive wins
  IF session.compound.paused AND session.compound.consecutiveWins >= 2:
    session.compound.paused = false
    session.compound.consecutiveLosses = 0
    Log: "Compound re-enabled after {wins} consecutive wins"

ELSE IF trade_result == "LOSS":
  session.compound.consecutiveLosses++
  session.compound.consecutiveWins = 0

  // Pause after 2 consecutive losses
  IF session.compound.consecutiveLosses >= 2:
    session.compound.paused = true
    Log: "Compound paused after {losses} consecutive losses"

// Apply compound only if not paused
IF session.compound.paused:
  Log: "Compound skipped (paused due to losses)"
  SKIP compound

// Determine effective compound rate
IF session.compound.consecutiveWins >= 3:
  effective_rate = session.compound.rate × 0.5  // 50% → 25%
  Log: "Compound rate reduced to {effective_rate}% after {wins} consecutive wins (risk control)"
ELSE:
  effective_rate = session.compound.rate

// Calculate compound amount with effective rate
IF net_pnl > 0:
  compound_amount = net_pnl × effective_rate
  IF compound_amount >= MIN_COMPOUND_AMOUNT:  // e.g., 0.10 EUR
    session.budget.remaining += compound_amount
    session.compound.totalCompounded += compound_amount
    Log: "Compounded {compound_amount}€ at {effective_rate}% rate"
  • Pause after 2 consecutive losses, resume after 2 consecutive wins
  • Reduce rate to 25% after 3 consecutive wins (risk control)
  • Never compound losses

7a. Budget Exhaustion Check

Before seeking new entries, verify sufficient budget for trading:

// Step 1: Get minimum order sizes for potential trades
min_order_size_eur = 2.00  // Typical Coinbase minimum in EUR
min_order_size_btc = 0.00001  // Example BTC minimum

// Step 2: Check if budget allows ANY trade
IF session.budget.remaining < min_order_size_eur:

  // Step 3: Check if rebalancing is possible
  IF hasOpenPositions AND anyPositionEligibleForRebalancing:
    // Continue to rebalancing logic (Step 6)
    // Rebalancing can free up capital for new trades
    SKIP to Step 8 (Signal Aggregation) after rebalancing
  ELSE:
    // No positions to rebalance, insufficient budget for new entry
    Log: "Budget exhausted: {remaining}€ < minimum {min}€, no positions to rebalance"
    EXIT session with status "Budget Exhausted"
    STOP

Key Points:

  • Minimum order size is asset-specific (check via
    get_product
    )
  • Rebalancing (selling position X to buy position Y) bypasses this check
  • Only exits if BOTH: insufficient budget AND no rebalanceable positions
  • This prevents deadlock while allowing capital reallocation

8. Signal Aggregation

Combine all signals into a decision:

Strategy-Specific Signal Thresholds:

Different strategies require different signal strengths:

StrategyMin BUY ScoreMin SELL ScoreMin Categories ConfirmingADX Threshold
Aggressive+40%-40%2+> 20
Conservative+60%-60%3+> 25
Scalping+40%-40%2+ (momentum focus)> 20

Apply the threshold for the active strategy (session.config.strategy) when evaluating signals.

Calculate Final Technical Score (normalize to -100% to +100%):

Score RangeSignalAction
> +60%Strong BUYBUY (full position)
+40% to +60%BUYBUY (75% position)
+20% to +40%Weak BUYBUY if sentiment bullish
-20% to +20%NeutralHOLD
-40% to -20%Weak SELLSELL if sentiment bearish
-60% to -40%SELLSELL (75% position)
< -60%Strong SELLSELL (full position)

Combine with Sentiment:

TechnicalSentimentFinal Decision
Strong BUYBullish/NeutralEXECUTE BUY
Strong BUYBearishBUY (reduced size)
BUYBullish/NeutralEXECUTE BUY
BUYBearishHOLD (conflict)
Weak BUYBullishEXECUTE BUY
Weak BUYNeutral/BearishHOLD
SELLBearish/NeutralEXECUTE SELL
SELLBullishHOLD (conflict)
Strong SELLAnyEXECUTE SELL

Multi-Timeframe Alignment Filter:

Apply trend alignment rules BEFORE executing trades:

// Rule: Only trade in direction of higher timeframe trend

// For BUY signals (score > +40):
IF signal_15m > 40:  // BUY signal detected

  // Check higher timeframe alignment
  IF trend_daily == "bearish" OR trend_4h == "bearish":
    Log: "BUY signal rejected: conflicts with higher timeframe trend"
    Log: "  Daily: {trend_daily}, 4h: {trend_4h}, 1h: {trend_1h}"
    signal_strength = signal_strength × 0.3  // Reduce by 70%

  ELSE IF trend_1h == "bearish":
    Log: "BUY signal weakened: 1h trend bearish (pullback zone)"
    signal_strength = signal_strength × 0.7  // Reduce by 30%

  ELSE IF trend_daily == "bullish" AND trend_4h == "bullish":
    Log: "BUY signal CONFIRMED: aligned with higher timeframes ✓"
    // No reduction, proceed with full strength

// For SELL signals (score < -40):
IF signal_15m < -40:  // SELL signal detected

  // Check higher timeframe alignment
  IF trend_daily == "bullish" OR trend_4h == "bullish":
    Log: "SELL signal rejected: conflicts with higher timeframe trend"
    Log: "  Daily: {trend_daily}, 4h: {trend_4h}, 1h: {trend_1h}"
    signal_strength = signal_strength × 0.3  // Reduce by 70%

  ELSE IF trend_1h == "bullish":
    Log: "SELL signal weakened: 1h trend bullish (rally in downtrend)"
    signal_strength = signal_strength × 0.7  // Reduce by 30%

  ELSE IF trend_daily == "bearish" AND trend_4h == "bearish":
    Log: "SELL signal CONFIRMED: aligned with higher timeframes ✓"
    // No reduction, proceed with full strength

Ideal Entry Scenarios:

  • BUY: Daily bullish + 4h bullish + 1h pullback (bearish) → Strong BUY on 15m reversal
  • SELL: Daily bearish + 4h bearish + 1h rally (bullish) → Strong SELL on 15m reversal

Trade Filters (do NOT trade if):

  • ADX < 20 (no clear trend)
  • Conflicting signals between categories
  • ATR > 3× average (extreme volatility)
  • Volume below average
  • Higher timeframe trend conflicts with signal (reduced by 70%)

See strategies.md for strategy configurations.

8a. Apply Volatility-Based Position Sizing

After determining base position size from signal strength, adjust for volatility:

// Step 1: Calculate base position size from signal strength (from Step 8)
IF signal_strength > 60:
  base_position_pct = 100  // Full position
ELSE IF signal_strength >= 40:
  base_position_pct = 75   // 75% position
ELSE IF signal_strength >= 20:
  base_position_pct = 50   // 50% position
ELSE:
  → SKIP trade (signal too weak)

// Step 2: Get current ATR and calculate average ATR
current_atr = ATR(14)
atr_average = calculate 14-day moving average of ATR(14)

// Defensive check
IF atr_average <= 0:
  atr_ratio = 1.0  // Default to normal volatility
ELSE:
  atr_ratio = current_atr / atr_average

// Step 3: Apply volatility adjustment
IF atr_ratio < 1.0:
  // Low volatility: increase position
  volatility_multiplier = 1.10  // +10%
ELSE IF atr_ratio <= 2.0:
  // Normal to moderate volatility: reduce slightly
  volatility_multiplier = 0.90  // -10%
ELSE:
  // High volatility: reduce significantly
  volatility_multiplier = 0.50  // -50%

// Step 4: Calculate final position size
final_position_pct = base_position_pct × volatility_multiplier

// Step 5: Apply exposure limits (from strategies.md Risk Per Trade section)
// Check ALL limits before finalizing position size:
//
// 1. Max exposure per asset: 33% of budget
//    - Sum existing positions in same asset + new position
//    - If total > 33%: reduce new position or SKIP
//
// 2. Max simultaneous positions: 3
//    - Count open positions
//    - If already at 3: SKIP trade (or force rebalancing first)
//
// 3. Max risk per trade: 2% of total portfolio
//    - Calculate: position_size × (SL_distance / entry_price)
//    - If risk > 2% of initial budget: reduce position size
//
// See strategies.md lines 145-149 for complete exposure limit definitions

final_position_size_eur = session.budget.remaining × (final_position_pct / 100)

Log: "Position: {base_position_pct}% (signal) × {volatility_multiplier} (ATR {atr_ratio:.2f}×) = {final_position_pct}% ({final_position_size_eur}€)"

Example Calculations:

  • Strong signal (70%), low volatility (0.8× ATR): 100% × 1.10 = 110% (capped at budget)
  • Medium signal (50%), normal volatility (1.5× ATR): 75% × 0.90 = 67.5%
  • Strong signal (70%), high volatility (2.5× ATR): 100% × 0.50 = 50%

9. Check Fees & Profit Threshold

Call

get_transaction_summary
and calculate:

Stage 1: Initial Check (Optimistic - Limit Order fees)

maker_fee = fee_tier.maker_fee_rate  // e.g., 0.004
taker_fee = fee_tier.taker_fee_rate  // e.g., 0.006

// Signal strength determines likely order type
IF signal_strength > 70:
  // Strong signal → Market order likely
  entry_fee = taker_fee
ELSE:
  // Normal signal → Limit order attempted
  entry_fee = maker_fee

exit_fee = taker_fee  // Exits typically market orders

// Minimum Profit calculation
round_trip_fee = entry_fee + exit_fee
slippage_buffer = 0.003  // 0.3% average slippage

MIN_PROFIT_DIRECT = (round_trip_fee + slippage_buffer) × 2  // ~2.2-2.4%
MIN_PROFIT_INDIRECT = (round_trip_fee + slippage_buffer) × 4  // ~3.8-4.2%

// Check before trading
IF expected_move < MIN_PROFIT:
  → Log: "Trade unprofitable: expected {expected_move}% < required {MIN_PROFIT}%"
  → SKIP trade

Stage 2: Fallback Re-Check (Conservative - if Limit Order times out)

// At limit order fallback (after 120s timeout)
// Re-calculate with Market Order fees
entry_fee_market = taker_fee
exit_fee = taker_fee
round_trip_fee_market = entry_fee_market + exit_fee
slippage = 0.003

MIN_PROFIT_FALLBACK = (round_trip_fee_market + slippage) × 2  // ~3.0%

IF expected_move < MIN_PROFIT_FALLBACK:
  → Log: "Fallback unprofitable: expected {expected_move}% < required {MIN_PROFIT_FALLBACK}%"
  → Cancel limit order, SKIP fallback
  → Position: None (limit order was not filled)
ELSE:
  → Proceed with Market Order fallback

Fee Report Section:

Fees:
  Your Tier: [Tier Name]
  Maker: [X]%
  Taker: [Y]%
  Route: [Direct/Indirect]
  Round-Trip: [Z]%
  Min Profit Required: [W]%
  Expected Move: [V]% [✓/✗]

10. Pre-Trade Liquidity Check

For altcoin market order entries only (skip for BTC-EUR, ETH-EUR, limit orders, exits):

  1. Call
    get_product_book
    for target pair
  2. Calculate spread with validation:
// Defensive validation against invalid data
IF best_bid <= 0 OR best_ask <= 0:
  → SKIP trade
  → Log: "Invalid order book data: bid={bid}, ask={ask}"
  → STOP

mid_price = (best_ask + best_bid) / 2
spread = (best_ask - best_bid) / max(mid_price, 0.0001)

// Sanity check for suspicious spreads
IF spread > 10.0:
  → SKIP trade
  → Log: "Suspicious spread: {spread}% (likely data error)"
  → STOP
```json
1. Decision:
   - Spread > 0.5% → SKIP trade, log "Spread too high: {X}%"
   - Spread 0.2% - 0.5% → Reduce position to 50%
   - Spread < 0.2% → Full position allowed
2. Store `entrySpread` and `liquidityStatus` in position

### 11. Execute Order

When a signal is present and expected profit exceeds MIN_PROFIT threshold:

**Order Type Selection**:

| Signal Strength | Order Type | Reason |
|-----------------|------------|--------|
| > 70% (Strong) | Market (IOC) | Speed is priority |
| 40-70% (Normal) | Limit (GTC) | Lower fees |
| < 40% (Weak) | No Trade | - |

**Route Selection**:

1. Call `list_products` to check if direct pair exists (e.g., BTC-SOL)
2. IF direct pair exists with sufficient liquidity:
   → Use direct pair, MIN_PROFIT = 2.0%
3. ELSE (no direct pair or illiquid):
   → Use indirect route (BTC → EUR → SOL)
   → MIN_PROFIT = 3.2%
   → Only trade if expected_profit > 3.2%

**For BUY (Limit Order)**:

  1. Call get_best_bid_ask for current price
  2. Calculate limit_price = best_ask × 1.0005 (slightly above)
  3. Call preview_order with limitLimitGtc, postOnly=true
  4. If preview OK → execute create_order
  5. Wait 120 seconds
  6. Call get_order to check status
  7. Check fill status and handle partial fills:

   order_status = get_order(order_id)

   IF order_status == "FILLED":
     → Continue (fully filled, no action needed)

   ELSE IF order_status == "PARTIALLY_FILLED":
     filled_size = order.filled_size
     remaining_size = intended_size - filled_size

     IF remaining_size >= min_order_size:
       // Stage 2: Re-check profitability with Market Order fees
       IF expected_move >= MIN_PROFIT_FALLBACK:
         → Cancel original limit order
         → Place Market Order for remaining_size ONLY
         → Log: "Partial fill {filled_size}, fallback for {remaining_size}"
       ELSE:
         → Cancel order, accept partial fill only
         → Log: "Partial fill accepted: fallback unprofitable ({expected_move}% < {MIN_PROFIT_FALLBACK}%)"
     ELSE:
       → Accept partial fill, cancel order
       → Log: "Partial fill accepted: {filled_size} (remaining below minimum)"

   ELSE IF order_status == "OPEN":
     // Stage 2: Re-check profitability with Market Order fees
     IF expected_move >= MIN_PROFIT_FALLBACK:
       → Cancel order
       → Place Market Order for full intended_size
       → Log: "Limit order timeout, fallback to market"
     ELSE:
       → Cancel order, SKIP fallback
       → Log: "Fallback skipped: unprofitable with market fees ({expected_move}% < {MIN_PROFIT_FALLBACK}%)"

  1. Record position (coin, amount, entry price, orderType)
  2. Save state to trading-state.json

**For BUY (Market Order - Strong Signal)**:

  1. Call preview_order (Market Order, BUY)
  2. If preview OK → execute create_order
  3. Record position (coin, amount, entry price)
  4. Save state to trading-state.json

**For SELL (open position)**:

  1. For Take-Profit: Use Limit Order (limitLimitGtc, postOnly=true)
  2. For Stop-Loss: Use Market Order (immediate execution)
  3. Call preview_order → execute create_order
  4. Calculate and log profit/loss (gross and net after fees)
  5. Update state file (compound is applied in step 7)

### 12. Output Report

Output a structured report:

═══════════════════════════════════════════════════════════════ TRADING REPORT ═══════════════════════════════════════════════════════════════ Time: [Timestamp] Portfolio Value: [Total Value] EUR Session PnL: [X]% ([Y] EUR)

─────────────────────────────────────────────────────────────── TECHNICAL ANALYSIS ───────────────────────────────────────────────────────────────

┌─────────────────────────────────────────────────────────────┐ │ BTC-EUR Price: [X] EUR │ ├─────────────────────────────────────────────────────────────┤ │ MOMENTUM: │ │ RSI(14): [X] [▲/▼/—] Stoch %K: [X] CCI: [X] │ │ Williams %R: [X] ROC: [X]% │ │ TREND: │ │ MACD: [X] Signal: [Y] Histogram: [Z] [▲/▼] │ │ ADX: [X] (+DI: [Y], -DI: [Z]) │ │ EMA: 9>[Y] 21>[Z] 50>[W] Trend: [UP/DOWN/SIDEWAYS] │ │ VOLATILITY: │ │ BB %B: [X] ATR: [Y] Keltner: [INSIDE/OUTSIDE] │ │ VOLUME: │ │ OBV: [RISING/FALLING] MFI: [X] vs VWAP: [ABOVE/BELOW]│ │ S/R LEVELS: │ │ Pivot: [X] R1: [Y] S1: [Z] Fib 61.8%: [W] │ ├─────────────────────────────────────────────────────────────┤ │ SCORES: Mom=[X] Trend=[Y] Vol=[Z] Volume=[W] S/R=[V] │ │ TOTAL SCORE: [X]% SIGNAL: [STRONG BUY/BUY/HOLD/SELL]│ └─────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────┐ │ ETH-EUR Price: [X] EUR │ │ ... (same format) │ └─────────────────────────────────────────────────────────────┘

─────────────────────────────────────────────────────────────── SENTIMENT ANALYSIS ─────────────────────────────────────────────────────────────── Fear & Greed Index: [X] ([Extreme Fear/Fear/Neutral/Greed/Extreme Greed]) News Sentiment: [Bullish/Bearish/Neutral] - [Brief summary] Combined Sentiment: [BULLISH/BEARISH/NEUTRAL] (Modifier: [+X/-X])

─────────────────────────────────────────────────────────────── TRADE DECISION ─────────────────────────────────────────────────────────────── Best Opportunity: [COIN]-EUR Technical Score: [X]% Sentiment: [Y] Final Decision: [STRONG BUY / BUY / HOLD / SELL / STRONG SELL] Position Size: [X]% of budget ([Y] EUR) Confidence: [HIGH/MEDIUM/LOW]

Trade Filters: ✓/✗ ADX > 20: [X] ✓/✗ Volume OK: [Yes/No] ✓/✗ ATR normal: [Yes/No] ✓/✗ No conflicts: [Yes/No]

─────────────────────────────────────────────────────────────── ACTIONS ─────────────────────────────────────────────────────────────── [None / Bought X BTC @ Y EUR / Sold X ETH @ Y EUR] Fee Paid: [X] EUR Net Position: [X] [COIN]

─────────────────────────────────────────────────────────────── OPEN POSITIONS ───────────────────────────────────────────────────────────────

CoinAmountEntryCurrentPnLSL/TP
BTC-EUR0.0014200043500+3.57%37800/44100
ETH-EUR0.0528002650-5.36%2520/2940

Total Unrealized PnL: [X]% ([Y] EUR)

─────────────────────────────────────────────────────────────── NEXT CYCLE ─────────────────────────────────────────────────────────────── Next check in: [X] minutes Strategy: [Aggressive/Conservative] Budget remaining: [X] EUR ═══════════════════════════════════════════════════════════════


## Important Rules

1. **NEVER use more than the budget**
2. **ALWAYS call preview_order before create_order**
3. **Fees MUST be considered**
4. **When uncertain: DO NOT trade**
5. **Stop-loss is SACRED - always enforce it**

## Dry-Run Mode

If the argument contains "dry-run":

- Analyze everything normally
- But DO NOT execute real orders
- Only show what you WOULD do

## Autonomous Loop Mode

After each trading cycle:

1. **Output report** (as described above)
2. **Execute sleep**: `sleep <seconds>` based on configured interval (default: 900 = 15 minutes)
3. **Start over**: Begin again at step 1 (check portfolio status)

**Parse interval from arguments:**

- `interval=5m` → `sleep 300`
- `interval=15m` → `sleep 900` (default)
- `interval=30m` → `sleep 1800`
- `interval=1h` → `sleep 3600`
- `interval=60s` → `sleep 60`

The agent runs indefinitely until the user stops it with Ctrl+C.

**Important during the loop:**

- Load/save positions from trading-state.json each cycle
- Check stop-loss/take-profit on each cycle
- Show at the end of each cycle: "Next cycle in X minutes at Y... (sleep Z)"