Skip to content

oajm79/trading

Repository files navigation

Crypto Trading Bot

Autonomous trading system with technical analysis, market regime detection, AI validation, and self-optimization. Designed to run 24/7 on Raspberry Pi 4.

Python Status AI Telegram

Table of Contents

  1. Project Philosophy
  2. Overview
  3. System Architecture
  4. Strategy Engine
  5. Risk Management
  6. AI Layers
  7. Scheduler and Timing
  8. Market Hours by Asset
  9. Telegram Notifications
  10. State and Data Persistence
  11. Installation
  12. Configuration
  13. Operation
  14. Monitoring and SQL Queries
  15. Project Structure
  16. Tech Stack

Project Philosophy

Generate real returns from financial markets with the minimum available resources — and never lose a cent of what has already been earned.

This project was born with concrete constraints: modest hardware (Raspberry Pi 4), a limited investment budget, and free or low-cost APIs. Those constraints are part of the design, not a temporary obstacle. Every technical decision must optimize what we have, not assume resources that don't exist.

Principles That Guide Every Decision

1. Minimum profitability is enough. The goal is not the perfect trade or maximum alpha. A consistent, small gain sustained over time meets the objective. The bot doesn't need to be the most aggressive — it needs to be the most reliable.

2. Gains are protected above all else. Trailing stop, automatic break-even, P17 (exit monitor), P15 (multi-timeframe confirmation): every layer exists to prevent a gain from turning into a loss. Protecting capital is as important as generating it.

3. Demo phase = learning, not paralysis. We're in paper trading to learn. Demo losses don't hurt financially, but they do teach. More trades executed = more data = better strategy calibration. Being too strict with entry filters doesn't protect — it blinds the bot to real opportunities.

4. Technical resources as strategic assets. Every tool (Claude, Gemini, eToro API, SQLite, Telegram) was chosen for its cost-benefit ratio. Before adding a new technology or dependency, we evaluate whether the value it provides justifies the operational cost.

5. Code without technical debt. No Frankenstein code. No duplication. No features that don't add measurable value. Every change goes through the question: does this improve the system or just complicate it? Simplicity is a competitive advantage when resources are limited.


Overview

A single bot with capital synchronized to an eToro demo account operating in demo mode. Each cycle (every 4 hours, aligned to candle closes) detects the market regime per symbol and applies the most appropriate technical strategy. An autonomous agent (Advisor) reviews historical performance weekly or after losing streaks, and adjusts parameters conservatively.

Supports crypto assets (24/7) and ETFs/stocks with restricted market hours (e.g. QQQ, 9:30–16:00 ET).

Historical evolution: The system started as 3 parallel bots with fixed strategies (BOT-A SMA / BOT-B Combined / BOT-C Aggressive). It was consolidated into a single bot with dynamic strategy selection based on market regime.


System Architecture

Main Loop Flow (run_forever)

+---------------------------------------------------------------------------+
|  while running:                                                           |
|                                                                           |
|  1. run_cycle()          <- technical analysis + execution per symbol    |
|      |                                                                    |
|  2. sleep(10 min)        <- post-cycle window                             |
|      |                                                                    |
|  3. advisor check?       <- weekly or 3 consecutive losses                |
|      |   +-- _run_advisor() -> background thread (non-blocking)          |
|      |                                                                    |
|  4. get_next_cycle_time() <- calculates time to next aligned slot        |
|      |                                                                    |
|  5. sleep(remaining)     <- sleeps until next cycle                       |
|      |   +-- intercept 22:00 -> _send_daily_summary()                    |
|                                                                           |
+---------------------------------------------------------------------------+

run_cycle() Flow

run_cycle()
  +-- check_daily_loss_limit()
  |
  +-- for symbol in symbols:
        process_symbol(symbol)
          +-- is_market_open(symbol)                <- skip if market closed
          +-- data_provider.update_prices()         <- eToro API -> cache
          +-- get_recent_candles(symbol, "4h", 100) <- last 100 candles
          +-- get_recent_candles(symbol, "1d", 60)  <- HTF (Higher Timeframe)
          +-- strategy_engine.analyze()
          |     +-- detect_market_regime()           <- ATR% + HTF -> regime
          |     +-- apply strategy                   <- RSI / SMA / Combined
          |     +-- dynamic SL/TP (ATR)              <- entry ± ATR × multiplier
          |     +-- ai_validator.validate()          <- Gemini 2.0 Flash + F&G
          +-- [if confidence >= min_confidence]
          |     +-- check spread < max_spread_pct    <- spread filter
          |     +-- calc size (risk-based)           <- risk management
          |     +-- BUY  -> open_position()  -> exchange.create_order()
          |     +-- SELL -> close_position() -> exchange.close_position()
          +-- check_position_stops(live_price)       <- SL/TP in real time

Strategy Engine

Market Regime Detection

StrategyEngine.detect_market_regime(data_4h, data_1d) calculates each cycle:

Condition Regime Strategy
ATR% < 1.5% RANGING RSI mean reversion
htf_strength > 0.5 and HTF directional TRENDING SMA crossover
Otherwise MIXED Combined (voting)

When the regime changes, an immediate Telegram alert is sent with the new regime and ATR%.

Implemented Strategies

SMA Crossover (sma_cross) — fast=10, slow=30. BUY on golden cross, SELL on death cross. Volume filter > 80% of 20-candle average.

RSI Mean Reversion (rsi_reversal) — period 14. BUY if RSI < 30, SELL if RSI > 70.

Combined (combined) — weighted voting of SMA and RSI. Threshold 0.7.

Auto (auto) — calls detect_market_regime() per cycle and symbol, selects the optimal strategy automatically.

Bot Signals (Market States)

Each cycle, the strategy engine emits a signal per symbol. The bot's action depends on the emitted state:

State Emoji Meaning Bot Action
BUY 🟢 Entry conditions met (bullish indicators aligned) Opens long position
SELL 🔴 Exit conditions (bearish indicators or RSI overbought) Closes existing position
HOLD 🟡 Neutral market — no clear trend or RSI extreme No action. Open position continues with trailing stop
HOLD_BULLISH 🟡 Ongoing bullish trend, but missing confirmation (RSI, volume or spread) Does not open position directly. P18 BuyMonitor uses it as favorable context to look for a 1H entry
HOLD_BEARISH 🟠 Ongoing bearish trend without an explicit death cross Does not close directly. P17 ExitMonitor uses it as an early exit signal in momentum_1h

When Each Signal Is Generated per Strategy

Strategy BUY SELL HOLD_BULLISH HOLD_BEARISH HOLD
sma_cross SMA10 crosses above SMA30 (golden cross) SMA10 crosses below SMA30 (death cross) SMA10 > SMA30 × 1.005 without a new cross SMA10 < SMA30 × 0.995 No clear differential
rsi_reversal RSI < 30 (oversold) RSI > 70 (overbought) RSI between 30–70
combined Weighted SMA+RSI vote ≥ 0.7 Vote ≤ −0.7 SMA vote is HOLD_BULLISH SMA vote is HOLD_BEARISH Inconclusive vote
momentum_1h EMA9 > EMA21 + RSI 40–65 + volume ≥ 1× average EMAs aligned but RSI or volume insufficient EMA9 < EMA21 (bearish on 1H) Neutral

momentum_1h is the P18 (BuyMonitor) strategy for detecting entries on 1H candles. It does not generate SELL — closing is the responsibility of the trailing stop and P17 ExitMonitor.


Shared ATR (_calculate_atr)

StrategyEngine._calculate_atr(data, period=14) is the central volatility calculation method. It is called by both detect_market_regime() and all 3 strategies to calculate dynamic SL/TP, eliminating code duplication.

Unified RSI (_compute_rsi)

StrategyEngine._compute_rsi(closes, period=14) implements Wilder's RSI and is the single source of truth for this indicator in the project. All modules that need RSI (P15, P18, rsi_mean_reversion) call this static method — there are no alternative implementations.

AI Validation (Gemini 2.0 Flash)

technical signal -> GeminiTradeValidator.validate()
                    + Fear & Greed Index (alternative.me)
                      +-- CONFIRM     -> confidence *= boost_factor
                      +-- REJECT      -> signal discarded
                      +-- REDUCE_SIZE -> position_value *= 0.5
                      +-- NEUTRAL     -> no modification (signal continues)
                      +-- WAIT        -> converted to HOLD

Risk Management

Position Sizing (Risk-Based)

Position size is calculated dynamically to risk a fixed percentage of capital (default 1%) if the Stop Loss is hit.

risk_amount = capital * risk_per_trade_pct  # e.g. 1%
sl_distance = abs(entry - stop_loss)
size = risk_amount / sl_distance
  • Safety cap: Size will never exceed max_position_pct (default 25% of capital).
  • AI adjustment: If the AI validator returns REDUCE_SIZE, the calculated size is cut in half.

Dynamic Stop Loss and Take Profit (ATR)

SL and TP levels are calculated based on the asset's real ATR:

Strategy Stop Loss Take Profit
sma_cross entry - ATR × 2.0 entry + ATR × 3.0
rsi_reversal entry - ATR × 1.5 entry + ATR × 2.5
combined entry - ATR × 2.0 entry + ATR × 3.5

Fallback to fixed percentages (−5%/+10%) if data is insufficient to calculate ATR.

Entry Monitors (Smart Entry)

The bot doesn't always enter immediately at the 4H candle close. It uses parallel threads to optimize the entry price:

  1. MTF Monitor (Multi-Timeframe - P15): If the 4H signal is BUY, defers entry to a monitor that analyzes 1H/15M candles. Seeks confirmation (RSI not overbought) to enter at a better intra-cycle moment.
  2. Entry Monitor (P14): If price has spiked, queues the order and waits for a specific pullback before executing.

Correlation Protection (P13)

To avoid overexposure to systemic crypto market risk:

  • Calculates Pearson Correlation (30-cycle window) between the candidate asset and existing open positions.
  • If correlation exceeds max_correlation (default 0.75), the entry is blocked.

Tiered Trailing Stop (5 levels):

  • Break-even (gain ≥ profit_lock_pct 0.5%): SL → entry price. Guaranteed minimum protection.
  • Phase 2 — Immediate trail (≥ 0.5×ATR): SL = peak − 0.5×ATR. No dead zone: captures gain from the first tick above the trigger.
  • Phase 3 — Continuous trail (≥ 2.0×ATR): SL = peak − 0.5×ATR. Same formula, reflects larger gains.
  • Phase 4 — Maximum lock-in (≥ 3.5×ATR): SL = peak − 0.25×ATR — maximum accumulated profit protection.

Each escalation generates a Telegram alert. trailing_phase (0–4) is persisted in the state.

Levels are checked against the real-time live ticker price every cycle.

Spread Filter (Slippage Protection)

Before opening a trade, the spread ((ask - bid) / bid) is verified. If it exceeds max_spread_pct (default 0.2%), the entry is cancelled to avoid excessive execution costs.

Fee Management

P&L is calculated net of estimated commissions (fee_rate_pct, default 0.1% per leg). Net PnL = (Exit - Entry) * Size - Fees

Daily Loss Limit

If daily_pnl / daily_start_capital exceeds max_daily_loss_pct (default 5%), the cycle aborts without processing signals. Resets at midnight.

Consecutive Losses

Three consecutive losses trigger the emergency Advisor.

Static Market Trigger (6-HOLD)

If all symbols accumulate 6 consecutive cycles without executing any trade (only HOLDs), the Advisor activates early to evaluate pair rotation. The consecutive_hold_cycles counter is persisted in the state JSON between restarts.


AI Layers

Layer 1: AI Validator (per cycle)

Model: Gemini 2.0 Flash — validates technical signals before each position opening. The prompt includes the crypto market Fear & Greed Index (src/utils/fear_greed.py) for sentiment context.

Layer 2: Trading Advisor (weekly / emergency)

Models: Claude Opus 4.6 → Gemini 2.0 Flash (automatic fallback on billing/quota errors).

The active backend is controlled with the AI_PRIMARY variable in .env (claude by default). If AI_PRIMARY=gemini, the Advisor uses Gemini directly without attempting Claude.

When it activates:

  • 7 days since last session (routine review)
  • 3 consecutive losses (emergency intervention)
  • 6 consecutive cycles without any trade (dead market)
  • Manually: python3 run_advisor.py "reason"

Available tools:

Tool Description
read_config Reads config/config_auto.json
read_performance SQLite: win rate, P&L, drawdown
read_logs Last N lines of logs/bot.log
update_config Writes changes (whitelist of parameters only)
restart_bots Executes ./bot.sh restart
scan_market Scans 15 pairs: ATR%, volume, trend
Backtest context Automatically reads data/backtest_results.json (HybridBacktest) and data/pipeline_backtest_results.json (PipelineBacktest) when building the prompt

Pre-rotation signals: the Advisor does NOT rotate a symbol if it detects signs of imminent movement:

  • atr_pct < 1.0% → Bollinger Squeeze (imminent breakout, wait)
  • vol_trend_pct > 20% with flat trend → volume accumulation (movement incoming)

Fear & Greed Index: injected into the user message before each Advisor session for macro market context.

Cross-session memory: each parameter change is recorded in the advisor_changes table (SQLite). On startup, the Advisor automatically receives the history of the last 3 days to avoid re-applying redundant adjustments.

Operator context notes: advisor_notes table in the DB. Allows injecting persistent context (e.g. cause of a specific loss, infrastructure changes) that the Advisor reads before making decisions. Add with:

INSERT INTO advisor_notes (created_at, note, active) VALUES (date('now'), 'your note here', 1);

Safety guardrails:

Parameter Allowed range Max change/session
min_confidence 0.45 – 0.80 10%
max_position_pct 0.20 – 0.95 10%
max_daily_loss_pct 2 – 10% 10%
update_interval_hours 2 – 8h (integer) 10%
risk_per_trade_pct 0.5 – 2.5% 10%
max_spread_pct 0.1 – 0.5% 10%
ai_validation.min_ai_confidence 0.50 – 0.80 10%
exit_monitor_min_confidence 0.45 – 0.75 10%
buy_monitor_min_confidence 0.55 – 0.80 10%
profit_lock_pct 0.3 – 2.0% 10%
mtf_rsi_max 50 – 70 10%
momentum_1h_rsi_min 30.0 – 50.0 10%
momentum_1h_vol_ratio_min 0.7 – 1.5 10%
buy_monitor_cooldown_hours 0.0 – 8.0h 10%

Scheduler and Timing

get_next_cycle_time(interval_hours=4) aligns cycles to round UTC hours: [0, 4, 8, 12, 16, 20].

Safety check: if the next slot is less than 2 minutes away, it skips to the next one.

Daily summary: the bot intercepts the sleep of the cycle that crosses 22:00 local time and sends the summary before continuing.


Market Hours by Asset

Crypto assets trade 24/7. ETFs and stocks are only analyzed and traded during market hours. Outside those hours the symbol is automatically skipped with a log message.

Assets with restricted hours (NYSE/NASDAQ): 9:30–16:00 ET, Monday to Friday.

Symbol Hours
QQQ, SPY 09:30–16:00 ET (Mon-Fri)
GLD, SLV 09:30–16:00 ET (Mon-Fri)
AAPL, MSFT, TSLA, NVDA 09:30–16:00 ET (Mon-Fri)

To add a new asset with restricted hours, edit RESTRICTED_ASSETS in src/utils/market_hours.py.


Telegram Notifications

Event When
Position opened Immediately
Position closed Immediately (includes P&L)
Stop Loss / Take Profit When triggered
Strategy change (auto) When regime changes (with ATR%)
Critical error System exceptions
Daily summary Every day at 22:00
Advisor change When parameter is modified (with reasoning)

Cycles with no activity (only HOLDs) do not generate notifications — controlled by "cycle_summary": false.

Interactive Commands (send to Telegram bot)

Command Action
/status General status: cycle, strategy, symbols, daily P&L
/balance Cash, position values, unrealized and total P&L
/positions Open positions with entry, current price, P&L, SL/TP
/signals Signals generated in the last cycle (action, confidence, regime)
/next Time until the next analysis cycle (available from the first second of startup)
/pi Hardware health: CPU, temperature, RAM, disk, uptime
/ask [question] Opens a persistent chat session with the AI (Claude Haiku 4.5 or Gemini 2.0 Flash based on AI_PRIMARY). Maintains conversation history (max 20 exchanges). Auto-closes on inactivity (warning at ~30s, close at 60s). Any / command closes the session. Without parameter: starts empty chat
/setai [claude|gemini] Change primary AI at runtime (persists to .env). Without parameter: guided flow
/analyze [symbol] Full technical analysis + AI verdict on demand (crypto, stocks, ETFs). Without parameter: guided flow
/pause Block BUY/SELL execution (analysis continues)
/resume Resume normal trading
/add [symbol] Add symbol to the next cycle (validates on exchange, persists config). Without parameter: guided flow
/remove [symbol] Remove active symbol (warns if position is open). Without parameter: guided flow
/advisor Launch the Advisor manually from Telegram
/reload Reload config_auto.json without restarting the bot
/scout Proactive asset rotation scan via Advisor
/rotate [current] [new] Manually rotate symbol (validates cooldown and universe). Without parameters: guided 2-step flow
/weekly Generate weekly report on demand (trades, win rate, PnL, best strategy)
/backtest [symbol] Run a quick historical backtest with Binance data
/verbose Toggle verbose mode: HOLD signals generate a Telegram alert with full analysis (strategy, regime, RSI, ATR%, SL/TP, reasoning)
/issue [description] Create a GitHub issue directly from Telegram with technical bot context (capital, positions, active symbol)
/cancelar Cancel ongoing guided flow
/help List of all available commands (22 total)

The listener uses long polling (20s server-side timeout) — instant response, ~3 req/min. 22 commands available. The BotFather menu updates automatically on each startup via setMyCommands — no manual intervention required.

Registering Commands in BotFather

The bot calls setMyCommands automatically on startup — the menu syncs without manual steps. If you need to register them manually (e.g. in BotFather for another bot), send to @BotFather/setcommands and paste:

status - General bot status
balance - Capital, positions and total P&L
positions - Open positions with SL/TP
signals - Signals from the last cycle
next - Time until next cycle
pi - CPU, RAM, disk and Pi temperature
ask - Ask the AI about the bot
add - Add active symbol (e.g: add BTC/USDT)
remove - Remove active symbol
pause - Pause BUY/SELL execution
resume - Resume trading
advisor - Launch the Advisor manually
setai - Change primary AI (claude or gemini)
analyze - Technical analysis + AI on demand
reload - Reload config without restarting
scout - Proactive asset rotation scan
rotate - Manually rotate symbol (e.g: rotate BTC ETH)
weekly - Generate weekly performance report
backtest - Quick historical test (e.g: backtest BTC/USDT)
verbose - Toggle detailed HOLD alerts
issue - Create GitHub issue from Telegram
cancelar - Cancel ongoing operation
help - List of available commands

State and Data Persistence

State file (data/state_AUTO_BOT.json)

Persists capital, open positions, active strategies per symbol, daily P&L, and the static cycle counter. Automatically restored on restart.

{
    "capital": 54772.08,
    "daily_pnl": 0,
    "daily_start_capital": 54772.08,
    "last_reset_date": "2026-02-21",
    "positions": {},
    "strategy_per_symbol": {
        "ARB/USDT": "sma_cross",
        "POL/USDT": "sma_cross"
    },
    "consecutive_hold_cycles": 0,
    "pending_entries": {},
    "mtf_pending": {},
    "saved_at": "2026-02-21T19:45:43"
}

SQLite Database (data/trading.db)

Three tables, all in WAL mode for greater integrity against power cuts:

Table Content
trades One record per closed position: P&L, strategy, AI fields (ai_recommendation, ai_confidence, ai_sentiment, ai_reasoning)
signals Every signal generated per cycle (BUY/SELL/HOLD), with symbol, action, confidence, strategy, regime, and ATR%
validator_decisions Every AI Validator decision on BUY/SELL signals: recommendation, AI confidence, risk level, reasoning

Logs (logs/)

File Content
bot.log Main log: cycles, signals, trades, scheduler
error.log Errors and exceptions

Installation

Prerequisites

Requirement Minimum version Notes
Python 3.11+ Raspberry Pi OS Bookworm includes 3.11. To install 3.13: sudo apt install python3.13
git any sudo apt install git
RAM 2 GB Pi 4 / Pi 5

Steps

# 1. Clone the repository
git clone <repo-url>
cd trading

# 2. Create virtual environment
python3 -m venv venv
source venv/bin/activate

# 3. Install dependencies
pip install -r requirements.txt

# 4. Create directory structure (if it doesn't exist)
mkdir -p data config logs

# 5. Configure credentials
cp .env.example .env   # if it exists, or create manually (see Environment Variables section)
nano .env

# 6. Copy bot configuration
cp config/config_example.json config/config_auto.json  # if an example exists
# Or copy from another server: scp user@old-pi:projects/trading/config/config_auto.json config/

# 7. Verify installation (must complete without errors)
python3 -c "import ccxt, pandas, numpy, anthropic, google.generativeai, matplotlib, yfinance, psutil, dotenv; print('OK')"

# 8. Start the bot
bash bot.sh start

The SQLite database (data/trading.db) and all tables are created automatically on the first run. No manual initialization step is required.

Environment Variables (.env)

# eToro API (demo)
ETORO_PUBLIC_KEY=your_public_key
ETORO_PRIVATE_KEY=your_private_key
ETORO_ENV=demo

# Telegram
TELEGRAM_BOT_TOKEN=your_token
TELEGRAM_CHAT_ID=your_chat_id

# Per-cycle AI Validation (required)
GOOGLE_API_KEY=your_google_key

# Trading Advisor (optional — without key, uses Gemini as primary)
ANTHROPIC_API_KEY=your_anthropic_key

# Primary AI: 'claude' (Opus + Gemini fallback) or 'gemini' (Gemini only)
AI_PRIMARY=claude

Configuration

config/config_auto.json

{
    "_bot_name": "AUTO BOT",
    "strategy": "auto",
    "symbols": ["ARB/USDT", "POL/USDT"],
    "capital": 54772.08,
    "min_confidence": 0.60,
    "max_position_pct": 0.05,
    "max_daily_loss_pct": 5,
    "update_interval_hours": 4,
    "exchange": { "name": "etoro", "testnet": true },
    "ai_validation": {
        "enabled": true,
        "min_ai_confidence": 0.6,
        "override_on_reject": false,
        "boost_on_confirm": true
    },
    "telegram": {
        "enabled": true,
        "notifications": {
            "startup": true,
            "cycle_summary": false,
            "trades": true,
            "daily_summary": true
        }
    }
}

Advisor-Adjustable Parameters

Parameter Effect
min_confidence Threshold to execute signals (higher = more selective)
max_position_pct Fraction of capital per position (currently 5%)
max_daily_loss_pct Daily circuit breaker
update_interval_hours Cycle frequency
symbols Pair rotation (requires Advisor justification with scan data)

Migration from Previous Pi

Steps to migrate to a new Pi without losing history or active positions.

1. Stop the bot on the old Pi

ssh your-pi "cd projects/trading && bash bot.sh stop"

2. Copy critical files to the new Pi

# From the local machine (adjust IPs/hostnames to your network)
PI_OLD=your-pi
PI_NEW=oajm-pi5        # adjust to the Pi 5 hostname/IP

# Full database (trade history, signals, Advisor changes)
scp ${PI_OLD}:projects/trading/data/trading.db       ${PI_NEW}:projects/trading/data/

# Bot state (capital, open positions, cooldowns, counters)
scp ${PI_OLD}:projects/trading/data/state_auto.json  ${PI_NEW}:projects/trading/data/

# Active configuration
scp ${PI_OLD}:projects/trading/config/config_auto.json ${PI_NEW}:projects/trading/config/

# Credentials
scp ${PI_OLD}:projects/trading/.env                  ${PI_NEW}:projects/trading/

# Backtest results (context for Advisor — optional but recommended)
scp ${PI_OLD}:projects/trading/data/backtest_results.json          ${PI_NEW}:projects/trading/data/ 2>/dev/null
scp ${PI_OLD}:projects/trading/data/pipeline_backtest_results.json ${PI_NEW}:projects/trading/data/ 2>/dev/null

# Historical logs (optional)
scp ${PI_OLD}:projects/trading/logs/bot.log          ${PI_NEW}:projects/trading/logs/

3. Verify the migration on the new Pi

ssh ${PI_NEW} "cd projects/trading && source venv/bin/activate && \
  python3 -c \"
import sqlite3, json, os
db = sqlite3.connect('data/trading.db')
tables = db.execute(\\\"SELECT name FROM sqlite_master WHERE type='table'\\\").fetchall()
trades = db.execute('SELECT count(*) FROM trades').fetchone()[0]
db.close()
state = json.load(open('data/state_auto.json'))
print('Tables:', [t[0] for t in tables])
print('Historical trades:', trades)
print('Capital in state:', state.get('capital'))
print('Open positions:', list(state.get('positions', {}).keys()))
print('OK')
\""

4. Start on the new Pi

ssh ${PI_NEW} "cd projects/trading && bash bot.sh start"

If there are open positions during migration: the bot detects them automatically from state_auto.json and resumes tracking (trailing stop, P14/P15/P17 monitors). The SL registered in eToro remains active throughout the migration — there is no risk of unprotected positions.

Files That Do NOT Need to Be Migrated

File Reason
data/cache/ OHLCV cache — regenerates automatically
venv/ Rebuild with pip install -r requirements.txt
logs/error.log Errors from the old Pi are not relevant
data/*.tmp Atomic write temporary files

Operation

# Start / stop / restart
./bot.sh start
./bot.sh stop
./bot.sh restart

# Process status
./bot.sh status

# Real-time logs
./bot.sh follow

# Last N lines
./bot.sh logs 200

# Trade statistics
./bot.sh stats

# Trigger the Advisor manually
python3 run_advisor.py "Reason for analysis"

# Run tests
source venv/bin/activate && python3 -m pytest tests/ -v

Note: BOT_CONFIG is already configured internally in bot.sh — it does not need to be passed as an environment variable.

Restarts: the bot automatically restores capital, open positions, active strategies per symbol, daily P&L, and consecutive_hold_cycles. It does not generate false strategy change alerts.


Monitoring and SQL Queries

# Recent trades with P&L
sqlite3 data/trading.db "
  SELECT symbol, strategy,
         round(entry_price,2), round(exit_price,2),
         round(pnl,2) as pnl, round(pnl_pct,2) as pct,
         ai_recommendation, created_at
  FROM trades ORDER BY created_at DESC LIMIT 10;"

# Performance by strategy
sqlite3 data/trading.db "
  SELECT strategy,
         count(*) as trades,
         round(100.0*sum(case when pnl>0 then 1 else 0 end)/count(*),1) as win_rate,
         round(sum(pnl),2) as total_pnl
  FROM trades GROUP BY strategy;"

# AI Validator impact (from trades table)
sqlite3 data/trading.db "
  SELECT ai_recommendation,
         count(*) as total,
         round(100.0*sum(case when pnl>0 then 1 else 0 end)/count(*),1) as win_rate,
         round(avg(pnl),2) as avg_pnl
  FROM trades WHERE ai_recommendation != 'N/A'
  GROUP BY ai_recommendation;"

# Validator decisions (all, including signals that never became trades)
sqlite3 data/trading.db "
  SELECT recommendation, count(*) as total,
         round(avg(ai_confidence),2) as avg_conf,
         round(100.0*sum(should_execute)/count(*),1) as execute_pct
  FROM validator_decisions
  GROUP BY recommendation ORDER BY count(*) DESC;"

# Signal distribution by action and symbol
sqlite3 data/trading.db "
  SELECT symbol, action, count(*) as total,
         round(avg(confidence),2) as avg_conf
  FROM signals
  GROUP BY symbol, action ORDER BY symbol, count(*) DESC;"

Project Structure

trading/
├── trading_bot.py              # Main bot (TradingBot class)
├── run_advisor.py              # Manual Advisor trigger
├── bot.sh                      # Bot manager (start/stop/restart/status/logs/stats)
├── diagnose.sh                 # System diagnostics
├── .env                        # Credentials (never in git)
├── requirements.txt
│
├── src/
│   ├── exchanges/
│   │   └── etoro.py            # eToro API wrapper (CCXT-compatible interface)
│   ├── core/
│   │   ├── claude_advisor.py   # Autonomous Advisor (Claude Opus 4.6 / Gemini fallback)
│   │   ├── ai_validator.py     # Per-cycle validation with Gemini 2.0 Flash + Fear & Greed
│   │   └── telegram_notifier.py
│   ├── strategies/
│   │   └── strategies.py       # StrategyEngine: SMA, RSI, Combined, Auto + dynamic ATR + HTF
│   ├── analysis/
│   │   └── backtest.py         # HybridBacktest (4H) + PipelineBacktest (1H+4H, full flow)
│   ├── data/
│   │   └── data_provider.py    # eToro (primary) + Yahoo Finance (fallback) + OHLCV cache + SQLite gateway
│   └── utils/
│       ├── market_hours.py     # Market hours by asset (ETFs/stocks vs crypto)
│       ├── fear_greed.py       # Fear & Greed Index (alternative.me API)
│       ├── scheduler.py        # Cycles aligned to 4h UTC candle closes
│       └── logger.py
│
├── config/
│   └── config_auto.json        # Active configuration
│
├── data/
│   ├── trading.db              # SQLite (WAL mode): trade history
│   ├── cache/                  # OHLCV cache (parquet, 4h validity)
│   └── state_AUTO_BOT.json     # State: capital, positions, strategies, hold_cycles
│
├── tests/
│   ├── __init__.py
│   ├── conftest.py             # Synthetic OHLCV fixtures
│   └── test_strategies.py      # 19 tests: SMA, RSI, Combined, _calculate_atr
│
└── logs/
    ├── bot.log                 # Main log
    └── error.log               # Errors and exceptions

Tech Stack

Component Technology
Exchange eToro Public API (demo mode)
OHLCV Data eToro API (primary) + Yahoo Finance (fallback)
Per-cycle AI Gemini 2.0 Flash
AI Advisor Claude Opus 4.6 → Gemini 2.0 Flash (fallback)
Fear & Greed alternative.me API (free)
Notifications Telegram Bot API
Database SQLite WAL mode (data/trading.db)
OHLCV Cache Parquet (4h validity)
Scheduler Custom, aligned to 4h UTC candle closes
Tests pytest + pytest-cov (19 tests)
Target hardware Raspberry Pi 4 (4 GB RAM) / Raspberry Pi 5 (8–16 GB RAM)

Known Bugs

/market-data/instruments/rates does not support multiple IDs

Detected: February 2026

Format Result
instrumentIds=100000 (1 ID) ✅ 200 OK
instrumentIds=100000,100001 (comma) ❌ 500 Internal Server Error
instrumentIds=100000&instrumentIds=100001 (repeated) ⚠️ 200 OK, returns only the last

Workaround: fetch_ticker() makes an individual call per symbol. Minimal impact.

State Corruption Risk (Atomic Write)

Implemented in v3.19 (P8): StateManager.save() now writes to a temporary file (.tmp) and renames it with os.replace(), an atomic operation on Unix. If the Pi shuts down during the write, load() recovers the state from .tmp.

eToro ID Hardcoding

Detected: February 2026 Instrument numeric IDs are hardcoded in etoro.py. If eToro changes these IDs, the bot will fail for those symbols.

'Wait' Logic in AI Validator

Detected: February 2026 The WAIT recommendation from the validator is converted to HOLD. If the original signal was an exit (SELL), this could block a necessary sale.


Security

  • Credentials only in .env (included in .gitignore)
  • The Advisor only modifies parameters from a strict whitelist with a 10% change limit per session
  • All modifications notified via Telegram with model reasoning
  • Paper trading by default (eToro demo, no real funds)

Roadmap

See ROADMAP.md for the full details of implemented and planned features.

Implemented in v3.7

  • Centralized _calculate_atr() — elimination of duplicate ATR calculation
  • Dynamic ATR-based SL/TP in all 3 strategies (with fallback to fixed %)
  • Fear & Greed Index in AI Validator and Advisor
  • SQLite WAL mode in all DB accesses
  • Pre-rotation signals in Advisor SYSTEM_PROMPT (Bollinger Squeeze, volume accumulation)
  • pytest test suite (19/19 passing)
  • signals table — persists all generated signals (BUY/SELL/HOLD) per cycle
  • validator_decisions table — persists each AI Validator decision for performance analysis
  • AI_PRIMARY variable in .env — controls AI backend across the entire project without touching code
  • /setai command — changes primary AI from Telegram with immediate effect and persistence
  • /analyze command — technical analysis + AI validation on demand for any asset
  • Advisor _read_performance() enriched: context for new bot + validator stats

v3.8: Thread Safety (commit 8bbf54a)

  • _restart_bots(): Popen(start_new_session=True) — bot survives Advisor restart
  • self._lock = threading.RLock() in TradingBot — shared reentrant lock
  • Snapshots in run_cycle, save_state, /add, /remove, /balance, /positions

v3.9: Trailing Stop — P2 (commit b4e6bde)

  • check_position_stops(): phase 1 break-even (gain >= 1.5xATR, SL to entry) + phase 2 trail (SL follows price at 2xATR)
  • Telegram alert when break-even activates

v3.10: Risk-based sizing — P1 (commit 500b312)

  • size = (capital x risk_per_trade_pct%) / abs(entry - SL), capped at max_position_pct (25%)
  • risk_usd saved in position; detailed log with sizing method

v3.11: Spread filter — P3 (commit 9f7e769)

  • open_position() cancels entry if (ask - bid) / bid > max_spread_pct (0.2%) + Telegram alert

v3.12: Net P&L with fees — P4 (commit 3b94b81)

  • fees = (entry_notional + exit_notional) x fee_rate_pct%; capital and daily_pnl use net value
  • DB: fees and pnl_gross columns (idempotent migration)

v3.13: StateManager refactor — P5 (commit 314ff3e)

  • New src/core/state_manager.py with own RLock for atomic I/O
  • TradingBot delegates save_state() / load_state() to StateManager; removed _state_file()

v3.16: Stability and bug fixes (commits f6a1472, 6c79780, a1274fd — 2026-02-23)

  • Complete Thread Safety: close_position uses atomic pop() under lock; run_cycle writes current_price/atr under lock; check_position_stops reads position under lock; capital/daily_pnl under lock
  • AI Validation fix: ai_min_confidence was used before initialization → AI silently disabled even when config said enabled: true
  • Telegram startup retry: test_connection() retries 3× with 3s backoff
  • Exchange startup retry: setup_exchange() retries 3× with 5s backoff
  • consecutive_hold_cycles persists between restarts: was being overwritten with = 0 after load_state()
  • Telegram listener backoff: sleeps 30s after 3 consecutive failed polls (network down)
  • _get_symbol_performance() implemented: Advisor was calling it but it never existed → AttributeError on every run
  • daily_summary_sent timing: flag was set before sending; if it failed, the summary was lost
  • Unrealized P&L None guard: (pos.get('current_price') or entry) avoids TypeError
  • /rotate whitelist fail-safe: rejects rotation if MARKET_UNIVERSE doesn't load
  • SQLite connection leak: _read_performance now with try/finally and guaranteed close
  • advisor_changes/advisor_notes tables in _init_db(): clean install no longer fails
  • ZeroDivisionError in _calculate_atr: guard if closes[-1] == 0: return None, None
  • Duplicate self.last_signals: second initialization in __init__ removed

v3.17: Telegram UX — Guided prompts + fix /next (commits 5a896be, b3aa81c — 2026-02-23)

  • Guided prompts: commands that require parameters (/analyze, /ask, /add, /remove, /setai, /rotate) now work correctly from the Telegram menu. When they arrive without arguments, the bot responds with a contextual prompt and waits for the user's reply. /rotate uses a 2-step flow (symbol to replace → new symbol). Writing cancelar or /cancelar aborts the flow at any point
  • /next from startup: next_cycle_time is now calculated before entering the main loop, eliminating the initial period where /next showed "No information yet"
  • telegram_notifier.py: removed text.startswith('/') filter in check_commands() — plain text responses from the guided flow now reach the handler (security maintained by chat_id filter)

v3.18: DailyAuditor — Proactive bug detection (2026-02-24)

  • New DailyAuditor class with 5 levels: instances, filesystem, data integrity, code consistency, and AI analysis. Runs as a daemon thread at 22:00.
  • Level 0: detects multiple bot instances via /proc/<pid>/cmdline
  • Full Telegram report with critical issues, warnings, and AI diagnostics

v3.19: Tiered trailing stop + Atomic State Save (commits 07dcfe7, 22e82e6 — 2026-02-24)

  • P7: check_position_stops() rewritten with 4 progressive phases (break-even → 0.5×ATR); trailing_phase saved in position; distinct Telegram alert per escalation
  • P8: StateManager.save() uses atomic write via .tmp + os.replace(); load() recovers from .tmp on shutdown during write

v3.21: Sub-hour + P14 Entry Monitor + P15 MTF (commits be4afea, 28d5128, 5d5ee08 — 2026-02-25)

  • Sub-hour: run_forever() supports update_interval_hours < 1; proportional post-cycle sleep (30s for sub-hour); dynamic candle timeframe based on update_interval_hours
  • P14 — Entry Monitor: pending_entries dict with pending BUY signals; daemon thread checks price every N minutes; executes entry when price ≤ entry_target or spread is low; discards signal at the start of the next cycle
  • P15 — MTF (Multi-Timeframe): intra-cycle monitor analyzes 1H/15M candles when 4H signal is BUY; seeks RSI confirmation (not overbought) on smaller TF before executing; mtf_pending dict persists between restarts; mtf_enabled and mtf_tf in config_auto.json

v3.22: Extended DailyAuditor + MTF/P14 Persistence + Chat sessions (commits 745c0b7, e99b394, 069a23d, 2e6e45f — 2026-02-25/26)

  • DailyAuditor — P13/P14/P15 integrity: alerts if pending_entries > 3 (possible leak); alerts if mtf_pending exceeds the number of configured symbols; verifies presence of P13/P14/P15 config keys; validates mtf_tf against supported timeframes
  • DailyAuditor — Telegram stability: chunked AI analysis (Telegram character limit); HTML escaping in AI analysis text to avoid parse errors
  • P14/P15 persistence: pending_entries and mtf_pending included in save_state() and load_state() — survive bot restarts
  • Chat sessions in /ask: persistent chat mode with multi-turn history; _chat_timeout_monitor_loop thread sends inactivity warning and closes session automatically; any / command closes the session

v3.23: P16 Universe Pulse Scanner + pinned_symbols (commits 59579de3f… — 2026-02-28)

  • P16 — Universe Pulse Scanner: scan of the full MARKET_UNIVERSE (15 pairs) at the end of each 4H cycle; detects movements > ±5% or ATR% > 3% in pairs outside the active config; automatic rotation proposal (best alert ↔ worst asset); Telegram inline buttons [✅ Rotate X→Y] / [❌ Ignore] executable with one tap; 8h cooldown per symbol to avoid spam; fresh data forced via update_prices() before analyzing
  • pinned_symbols: new field in config_auto.json; the Advisor can never rotate out a pinned symbol; BTC/USDT configured as pinned by default
  • Telegram inline keyboards: send_message() accepts reply_markup; get_updates() listens for callback_query; new answer_callback_query(); _handle_callback() in TradingBot dispatches actions from buttons

v3.27: PipelineBacktest + P18 Cooldown + Trailing fix #21 (commits d49eeef, 2e2159e — 2026-03-08)

  • PipelineBacktest: new class in src/analysis/backtest.py that replicates the full bot flow in 1H+4H: BuyMonitor (momentum_1h), P15 confirmation, ATR phase trailing stop, P17 exit signals, post-close cooldown. Runs weekly alongside HybridBacktest and saves data/pipeline_backtest_results.json.
  • Advisor PipelineBacktest: _read_pipeline_backtest_results() reads the JSON and adds actionable context to the Advisor prompt (WR, return, exit types, P15/P17 stats per symbol). The Advisor now receives real data from the flow that drives its decisions.
  • BuyMonitor cooldown: buy_monitor_cooldown_hours (0–8h) in ADJUSTABLE_PARAMS. _bm_last_close records close epoch in close_position(); the P18 loop filters candidates within the cooldown window and logs how many minutes they have left. Designed for the NEAR/USDT March 2–4 pattern: 7 consecutive re-entries in a ranging market.
  • Trailing Phase 2 (fix #21): multiplier 0.75×ATR → 0.5×ATR. Eliminates the dead zone where the SL was at break-even but gains weren't being captured. Now: any tick above the trigger (0.5×ATR) moves the SL above entry. Same change in PipelineBacktest._TRAIL_PHASES.

v3.26: Architectural refactoring + Telegram UX (2026-02-28)

  • verbose_alerts: detailed alert mode for HOLD signals — toggle with /verbose
  • /issue: create GitHub issues with technical context directly from Telegram
  • Auto BotFather registration: setMyCommands on startup — menu syncs automatically
  • Dead code: show_status() removed (empty wrapper over log_cycle_end())
  • P18 filter 4: now uses entry_target_pct from config instead of the hardcoded 1.015%
  • /reload safety: warns if symbols with open positions were removed
  • Centralized SQL: 4 new methods in DataProviderTradingBot no longer accesses SQLite directly
  • Unified RSI: StrategyEngine._compute_rsi() as the single source of truth — removed duplicate implementation in TradingBot

Pending

  • P6: Switch to live production (eToro live) — manual decision required
  • P9: Economic Calendar — free APIs do not include this endpoint; future solution

Disclaimer

Software for educational purposes. Trading carries substantial risk of loss. The authors are not responsible for financial losses. Always start with paper trading.


Version: 3.27 | Status: Active Paper Trading (eToro Demo) | Updated: March 8, 2026

About

Crypto trading bot for eToro using Telegram commands, AI analysis and SQLite - runs on Raspberry Pi 5

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors