1403 lines
60 KiB
Plaintext
1403 lines
60 KiB
Plaintext
{
|
|
"cells": [
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "5cd7107e",
|
|
"metadata": {},
|
|
"source": [
|
|
"# Crypto Trading System with Kraken API\n",
|
|
"\n",
|
|
"A crypto trading system that fetches data from Kraken, visualizes price charts with Simple Moving Averages (SMA), uses LLM to analyze trading signals, and executes real trades on Kraken's sandbox environment.\n",
|
|
"\n",
|
|
"## Features\n",
|
|
"- **Real-time Data**: Fetch BTC/USD and ETH/USD data from Kraken API\n",
|
|
"- **Technical Analysis**: Interactive charts with customizable SMA overlays\n",
|
|
"- **AI Trading Advisor**: LLM-powered trading recommendations based on technical indicators\n",
|
|
"- **Real Trading**: Execute actual trades on Kraken's sandbox environment\n",
|
|
"- **Multi-Model Support**: Choose from GPT-5, Claude Sonnet, or Gemini for analysis\n",
|
|
"\n",
|
|
"## Setup Requirements\n",
|
|
"1. **API Keys**: Configure OpenAI, Anthropic, Google, and Kraken API keys in `.env` file\n",
|
|
"2. **Kraken Sandbox**: Get sandbox API credentials from [Kraken Developer Portal](https://www.kraken.com/features/api)\n",
|
|
"3. **Sandbox Funding**: Kraken sandbox may require initial funding for testing trades\n",
|
|
"\n",
|
|
"## Process stes\n",
|
|
"1. **Market Data Tab**: Select crypto pair and SMA windows, fetch data\n",
|
|
"2. **Trading Analysis Tab**: Get LLM-powered trading recommendations\n",
|
|
"3. **Paper Trading Tab**: Execute real trades based on AI recommendations\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"id": "31c1f5f0",
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"# Install dependencies\n",
|
|
"!uv add ccxt"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"id": "5c58efdd",
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"# Imports and Setup\n",
|
|
"import os\n",
|
|
"import time\n",
|
|
"import pandas as pd\n",
|
|
"import numpy as np\n",
|
|
"import plotly.graph_objects as go\n",
|
|
"from plotly.subplots import make_subplots\n",
|
|
"import ccxt\n",
|
|
"from datetime import datetime\n",
|
|
"from dotenv import load_dotenv\n",
|
|
"from openai import OpenAI\n",
|
|
"import gradio as gr\n",
|
|
"import warnings\n",
|
|
"warnings.filterwarnings(\"ignore\")\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"id": "48648e9e",
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"# API Key Setup\n",
|
|
"load_dotenv(override=True)\n",
|
|
"\n",
|
|
"# LLM API Keys\n",
|
|
"openai_api_key = os.getenv('OPENAI_API_KEY')\n",
|
|
"anthropic_api_key = os.getenv('ANTHROPIC_API_KEY')\n",
|
|
"google_api_key = os.getenv('GOOGLE_API_KEY')\n",
|
|
"\n",
|
|
"# Kraken API Keys (for sandbox trading)\n",
|
|
"kraken_api_key = os.getenv('KRAKEN_API_KEY')\n",
|
|
"kraken_secret = os.getenv('KRAKEN_API_SECRET')\n",
|
|
"\n",
|
|
"# Initialize API clients\n",
|
|
"anthropic_url = \"https://api.anthropic.com/v1/\"\n",
|
|
"gemini_url = \"https://generativelanguage.googleapis.com/v1beta/openai/\"\n",
|
|
"\n",
|
|
"clients = {}\n",
|
|
"if openai_api_key:\n",
|
|
" clients['openai'] = OpenAI(api_key=openai_api_key)\n",
|
|
" print(f\"✅ OpenAI API Key loaded: {openai_api_key[:8]}...\")\n",
|
|
"else:\n",
|
|
" print(\"❌ OpenAI API Key not set\")\n",
|
|
"\n",
|
|
"if anthropic_api_key:\n",
|
|
" clients['anthropic'] = OpenAI(api_key=anthropic_api_key, base_url=anthropic_url)\n",
|
|
" print(f\"✅ Anthropic API Key loaded: {anthropic_api_key[:7]}...\")\n",
|
|
"else:\n",
|
|
" print(\"❌ Anthropic API Key not set\")\n",
|
|
"\n",
|
|
"if google_api_key:\n",
|
|
" clients['google'] = OpenAI(api_key=google_api_key, base_url=gemini_url)\n",
|
|
" print(f\"✅ Google API Key loaded: {google_api_key[:8]}...\")\n",
|
|
"else:\n",
|
|
" print(\"❌ Google API Key not set\")\n",
|
|
"\n",
|
|
"if kraken_api_key and kraken_secret:\n",
|
|
" print(f\"✅ Kraken API Keys loaded: {kraken_api_key[:8]}...\")\n",
|
|
"else:\n",
|
|
" print(\"❌ Kraken API Keys not set - paper trading will be disabled\")\n",
|
|
"\n",
|
|
"# Model configuration\n",
|
|
"MODELS = {\n",
|
|
" \"GPT-5\": {\"model_id\": \"gpt-5-mini\", \"provider\": \"openai\"},\n",
|
|
" \"Claude Sonnet\": {\"model_id\": \"claude-sonnet-4-5-20250929\", \"provider\": \"anthropic\"},\n",
|
|
" \"Gemini\": {\"model_id\": \"gemini-2.5-flash-lite\", \"provider\": \"google\"}\n",
|
|
"}\n",
|
|
"\n",
|
|
"CRYPTO_PAIRS = {\n",
|
|
" \"BTC/USD\": \"PI_XBTUSD\", # Bitcoin futures symbol\n",
|
|
" \"ETH/USD\": \"PI_ETHUSD\" # Ethereum futures symbol\n",
|
|
"}\n",
|
|
"\n",
|
|
"print(\"✅ API setup complete!\")\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"id": "59cdc0df",
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"# Kraken Data Fetcher Module\n",
|
|
"class KrakenDataFetcher:\n",
|
|
" \"\"\"Handles data fetching from Kraken API and SMA calculations\"\"\"\n",
|
|
" \n",
|
|
" def __init__(self):\n",
|
|
" self.exchange = ccxt.kraken({\n",
|
|
" 'enableRateLimit': True,\n",
|
|
" 'timeout': 30000\n",
|
|
" })\n",
|
|
" self.cached_data = {}\n",
|
|
" \n",
|
|
" def fetch_ohlcv(self, symbol, timeframe='1h', limit=720):\n",
|
|
" \"\"\"Fetch OHLCV data from Kraken\"\"\"\n",
|
|
" try:\n",
|
|
" # Convert symbol to Kraken format\n",
|
|
" # kraken_symbol = CRYPTO_PAIRS.get(symbol, symbol)\n",
|
|
" kraken_symbol = symbol\n",
|
|
" \n",
|
|
" # Check cache first\n",
|
|
" cache_key = f\"{kraken_symbol}_{timeframe}_{limit}\"\n",
|
|
" if cache_key in self.cached_data:\n",
|
|
" cache_time, data = self.cached_data[cache_key]\n",
|
|
" if time.time() - cache_time < 300: # 5 minute cache\n",
|
|
" print(f\"📊 Using cached data for {symbol}\")\n",
|
|
" return data\n",
|
|
" \n",
|
|
" print(f\"🔄 Fetching {symbol} data from Kraken...\")\n",
|
|
" ohlcv = self.exchange.fetch_ohlcv(kraken_symbol, timeframe, limit=limit)\n",
|
|
" \n",
|
|
" # Convert to DataFrame\n",
|
|
" df = pd.DataFrame(ohlcv, columns=['timestamp', 'open', 'high', 'low', 'close', 'volume'])\n",
|
|
" df['timestamp'] = pd.to_datetime(df['timestamp'], unit='ms')\n",
|
|
" df.set_index('timestamp', inplace=True)\n",
|
|
" \n",
|
|
" # Cache the data\n",
|
|
" self.cached_data[cache_key] = (time.time(), df)\n",
|
|
" \n",
|
|
" print(f\"✅ Fetched {len(df)} records for {symbol}\")\n",
|
|
" return df\n",
|
|
" \n",
|
|
" except Exception as e:\n",
|
|
" print(f\"❌ Error fetching data for {symbol}: {str(e)}\")\n",
|
|
" return pd.DataFrame()\n",
|
|
" \n",
|
|
" def calculate_sma(self, data, window):\n",
|
|
" \"\"\"Calculate Simple Moving Average\"\"\"\n",
|
|
" if len(data) < window:\n",
|
|
" return pd.Series(index=data.index, dtype=float)\n",
|
|
" return data['close'].rolling(window=window).mean()\n",
|
|
" \n",
|
|
" def detect_crossover(self, sma_short, sma_long):\n",
|
|
" \"\"\"Detect SMA crossover signals\"\"\"\n",
|
|
" if len(sma_short) < 2 or len(sma_long) < 2:\n",
|
|
" return \"No data\"\n",
|
|
" \n",
|
|
" # Get the last two values\n",
|
|
" short_current = sma_short.iloc[-1]\n",
|
|
" short_previous = sma_short.iloc[-2]\n",
|
|
" long_current = sma_long.iloc[-1]\n",
|
|
" long_previous = sma_long.iloc[-2]\n",
|
|
" \n",
|
|
" # Check for crossover\n",
|
|
" if pd.isna(short_current) or pd.isna(long_current) or pd.isna(short_previous) or pd.isna(long_previous):\n",
|
|
" return \"Insufficient data\"\n",
|
|
" \n",
|
|
" # Golden cross: short SMA crosses above long SMA\n",
|
|
" if short_previous <= long_previous and short_current > long_current:\n",
|
|
" return \"Golden Cross (BUY Signal)\"\n",
|
|
" \n",
|
|
" # Death cross: short SMA crosses below long SMA\n",
|
|
" if short_previous >= long_previous and short_current < long_current:\n",
|
|
" return \"Death Cross (SELL Signal)\"\n",
|
|
" \n",
|
|
" # No crossover\n",
|
|
" if short_current > long_current:\n",
|
|
" return \"Short SMA above Long SMA\"\n",
|
|
" else:\n",
|
|
" return \"Short SMA below Long SMA\"\n",
|
|
" \n",
|
|
" def get_market_summary(self, data, sma_short, sma_long):\n",
|
|
" \"\"\"Get market summary for LLM analysis\"\"\"\n",
|
|
" if data.empty:\n",
|
|
" return {}\n",
|
|
" \n",
|
|
" current_price = data['close'].iloc[-1]\n",
|
|
" price_change = ((current_price - data['close'].iloc[-2]) / data['close'].iloc[-2]) * 100 if len(data) > 1 else 0\n",
|
|
" \n",
|
|
" # Volume analysis\n",
|
|
" avg_volume = data['volume'].rolling(window=10).mean().iloc[-1] if len(data) >= 10 else data['volume'].mean()\n",
|
|
" current_volume = data['volume'].iloc[-1]\n",
|
|
" volume_ratio = current_volume / avg_volume if avg_volume > 0 else 1\n",
|
|
" \n",
|
|
" # Price momentum (last 5 periods)\n",
|
|
" momentum = ((data['close'].iloc[-1] - data['close'].iloc[-6]) / data['close'].iloc[-6]) * 100 if len(data) >= 6 else 0\n",
|
|
" \n",
|
|
" return {\n",
|
|
" 'current_price': current_price,\n",
|
|
" 'price_change_pct': round(price_change, 2),\n",
|
|
" 'current_volume': current_volume,\n",
|
|
" 'volume_ratio': round(volume_ratio, 2),\n",
|
|
" 'momentum_5d': round(momentum, 2),\n",
|
|
" 'sma_short': sma_short.iloc[-1] if not sma_short.empty else None,\n",
|
|
" 'sma_long': sma_long.iloc[-1] if not sma_long.empty else None,\n",
|
|
" 'crossover_status': self.detect_crossover(sma_short, sma_long)\n",
|
|
" }\n",
|
|
"\n",
|
|
"# Initialize data fetcher\n",
|
|
"data_fetcher = KrakenDataFetcher()\n",
|
|
"print(\"✅ Kraken Data Fetcher initialized!\")\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"id": "f5976c68",
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"# Chart Builder Module\n",
|
|
"class ChartBuilder:\n",
|
|
" \"\"\"Creates interactive charts with candlesticks and SMA overlays\"\"\"\n",
|
|
" \n",
|
|
" def __init__(self):\n",
|
|
" self.colors = {\n",
|
|
" 'bullish': '#00ff88',\n",
|
|
" 'bearish': '#ff4444',\n",
|
|
" 'sma_short': '#2196F3',\n",
|
|
" 'sma_long': '#FF9800',\n",
|
|
" 'volume': '#9C27B0'\n",
|
|
" }\n",
|
|
" \n",
|
|
" def create_candlestick_chart(self, data, sma_short, sma_long, symbol):\n",
|
|
" \"\"\"Create interactive candlestick chart with SMA overlays\"\"\"\n",
|
|
" if data.empty:\n",
|
|
" return go.Figure()\n",
|
|
" \n",
|
|
" try:\n",
|
|
" # Create a simple figure without subplots first\n",
|
|
" fig = go.Figure()\n",
|
|
" \n",
|
|
" # Add candlestick chart using only the data values\n",
|
|
" fig.add_trace(go.Candlestick(\n",
|
|
" x=list(range(len(data))), \n",
|
|
" open=data['open'].values,\n",
|
|
" high=data['high'].values,\n",
|
|
" low=data['low'].values,\n",
|
|
" close=data['close'].values,\n",
|
|
" name='Price',\n",
|
|
" increasing_line_color='#00ff00',\n",
|
|
" decreasing_line_color='#ff0000'\n",
|
|
" ))\n",
|
|
" \n",
|
|
" # Add SMA lines if available\n",
|
|
" if sma_short is not None and not sma_short.empty:\n",
|
|
" # Filter out NaN values and align with data\n",
|
|
" sma_short_clean = sma_short.dropna()\n",
|
|
" if not sma_short_clean.empty:\n",
|
|
" sma_x = list(range(len(sma_short_clean)))\n",
|
|
" fig.add_trace(go.Scatter(\n",
|
|
" x=sma_x,\n",
|
|
" y=sma_short_clean.values,\n",
|
|
" mode='lines',\n",
|
|
" name='SMA Short',\n",
|
|
" line=dict(color='#2196F3', width=2)\n",
|
|
" ))\n",
|
|
" \n",
|
|
" if sma_long is not None and not sma_long.empty:\n",
|
|
" # Filter out NaN values and align with data\n",
|
|
" sma_long_clean = sma_long.dropna()\n",
|
|
" if not sma_long_clean.empty:\n",
|
|
"\n",
|
|
" sma_x = list(range(len(sma_long_clean)))\n",
|
|
" fig.add_trace(go.Scatter(\n",
|
|
" x=sma_x,\n",
|
|
" y=sma_long_clean.values,\n",
|
|
" mode='lines',\n",
|
|
" name='SMA Long',\n",
|
|
" line=dict(color='#FF9800', width=2)\n",
|
|
" ))\n",
|
|
" \n",
|
|
" # Update layout\n",
|
|
" fig.update_layout(\n",
|
|
" title=f'{symbol} Trading Chart',\n",
|
|
" xaxis_title=\"Time Period\",\n",
|
|
" yaxis_title=\"Price ($)\",\n",
|
|
" height=600,\n",
|
|
" showlegend=True,\n",
|
|
" template='plotly_white'\n",
|
|
" )\n",
|
|
" \n",
|
|
" return fig\n",
|
|
" \n",
|
|
" except Exception as e:\n",
|
|
" print(f\"❌ Error in chart creation: {str(e)}\")\n",
|
|
" # Return a simple empty figure\n",
|
|
" return go.Figure()\n",
|
|
" \n",
|
|
" def _find_crossover_points(self, sma_short, sma_long, timestamps):\n",
|
|
" \"\"\"Find crossover points between SMAs\"\"\"\n",
|
|
" if len(sma_short) < 2 or len(sma_long) < 2:\n",
|
|
" return []\n",
|
|
" \n",
|
|
" crossover_points = []\n",
|
|
" \n",
|
|
" for i in range(1, len(sma_short)):\n",
|
|
" if pd.isna(sma_short.iloc[i]) or pd.isna(sma_long.iloc[i]) or \\\n",
|
|
" pd.isna(sma_short.iloc[i-1]) or pd.isna(sma_long.iloc[i-1]):\n",
|
|
" continue\n",
|
|
" \n",
|
|
" # Golden cross: short SMA crosses above long SMA\n",
|
|
" if sma_short.iloc[i-1] <= sma_long.iloc[i-1] and sma_short.iloc[i] > sma_long.iloc[i]:\n",
|
|
" crossover_points.append({\n",
|
|
" 'timestamp': timestamps[i],\n",
|
|
" 'type': 'Golden Cross'\n",
|
|
" })\n",
|
|
" \n",
|
|
" # Death cross: short SMA crosses below long SMA\n",
|
|
" elif sma_short.iloc[i-1] >= sma_long.iloc[i-1] and sma_short.iloc[i] < sma_long.iloc[i]:\n",
|
|
" crossover_points.append({\n",
|
|
" 'timestamp': timestamps[i],\n",
|
|
" 'type': 'Death Cross'\n",
|
|
" })\n",
|
|
" \n",
|
|
" return crossover_points\n",
|
|
" \n",
|
|
" def create_summary_chart(self, data, symbol):\n",
|
|
" \"\"\"Create a simple price summary chart\"\"\"\n",
|
|
" if data.empty:\n",
|
|
" return go.Figure()\n",
|
|
" \n",
|
|
" fig = go.Figure()\n",
|
|
" \n",
|
|
" # Add price line\n",
|
|
" fig.add_trace(go.Scatter(\n",
|
|
" x=data.index,\n",
|
|
" y=data['close'],\n",
|
|
" mode='lines',\n",
|
|
" name='Price',\n",
|
|
" line=dict(color='white', width=2)\n",
|
|
" ))\n",
|
|
" \n",
|
|
" # Add volume as background bars\n",
|
|
" fig.add_trace(go.Bar(\n",
|
|
" x=data.index,\n",
|
|
" y=data['volume'],\n",
|
|
" name='Volume',\n",
|
|
" opacity=0.3,\n",
|
|
" yaxis='y2'\n",
|
|
" ))\n",
|
|
" \n",
|
|
" fig.update_layout(\n",
|
|
" title=f'{symbol} Price Summary',\n",
|
|
" xaxis_title='Time',\n",
|
|
" yaxis_title='Price (USD)',\n",
|
|
" yaxis2=dict(title='Volume', overlaying='y', side='right'),\n",
|
|
" template='plotly_dark',\n",
|
|
" height=400\n",
|
|
" )\n",
|
|
" \n",
|
|
" return fig\n",
|
|
"\n",
|
|
"# Initialize chart builder\n",
|
|
"chart_builder = ChartBuilder()\n",
|
|
"print(\"✅ Chart Builder initialized!\")\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"id": "ad44be2f",
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"# LLM Trading Advisor Module\n",
|
|
"class TradingAdvisor:\n",
|
|
" \"\"\"Uses LLM to analyze market data and provide trading recommendations\"\"\"\n",
|
|
" \n",
|
|
" def __init__(self):\n",
|
|
" self.system_prompt = \"\"\"You are an expert cryptocurrency trading advisor with deep knowledge of technical analysis and market dynamics. \n",
|
|
"\n",
|
|
"Your task is to analyze cryptocurrency market data and provide clear, actionable trading recommendations.\n",
|
|
"\n",
|
|
"Key factors to consider:\n",
|
|
"1. SMA Crossover Signals: Golden Cross (short SMA > long SMA) suggests bullish momentum, Death Cross (short SMA < long SMA) suggests bearish momentum\n",
|
|
"2. Price Momentum: Recent price movements and trends\n",
|
|
"3. Volume Analysis: Volume spikes often indicate strong moves\n",
|
|
"4. Market Context: Overall market conditions and volatility\n",
|
|
"\n",
|
|
"Provide your analysis in this exact format:\n",
|
|
"DECISION: [BUY/SELL/HOLD]\n",
|
|
"CONFIDENCE: [1-10 scale]\n",
|
|
"REASONING: [2-3 sentence explanation of your decision]\n",
|
|
"RISK_LEVEL: [LOW/MEDIUM/HIGH]\n",
|
|
"\n",
|
|
"Be concise but thorough in your analysis. Focus on the most relevant technical indicators.\"\"\"\n",
|
|
"\n",
|
|
" def analyze_market(self, market_summary, model_name=\"GPT-5\", temperature=0.3):\n",
|
|
" \"\"\"Analyze market data and provide trading recommendation\"\"\"\n",
|
|
" try:\n",
|
|
" if not market_summary:\n",
|
|
" return {\n",
|
|
" 'decision': 'HOLD',\n",
|
|
" 'confidence': 1,\n",
|
|
" 'reasoning': 'Insufficient market data for analysis',\n",
|
|
" 'risk_level': 'HIGH'\n",
|
|
" }\n",
|
|
" \n",
|
|
" # Prepare context for LLM\n",
|
|
" context = self._prepare_market_context(market_summary)\n",
|
|
" \n",
|
|
" # Query the selected model\n",
|
|
" response = self._query_llm(model_name, context, temperature)\n",
|
|
" \n",
|
|
" # Parse the response\n",
|
|
" recommendation = self._parse_llm_response(response)\n",
|
|
" \n",
|
|
" return recommendation\n",
|
|
" \n",
|
|
" except Exception as e:\n",
|
|
" print(f\"❌ Error in trading analysis: {str(e)}\")\n",
|
|
" return {\n",
|
|
" 'decision': 'HOLD',\n",
|
|
" 'confidence': 1,\n",
|
|
" 'reasoning': f'Analysis error: {str(e)}',\n",
|
|
" 'risk_level': 'HIGH'\n",
|
|
" }\n",
|
|
" \n",
|
|
" def _prepare_market_context(self, market_summary):\n",
|
|
" \"\"\"Prepare market context for LLM analysis\"\"\"\n",
|
|
" # Handle SMA values safely\n",
|
|
" sma_short = market_summary.get('sma_short')\n",
|
|
" sma_long = market_summary.get('sma_long')\n",
|
|
" \n",
|
|
" sma_short_str = f\"${sma_short:.2f}\" if sma_short is not None else \"N/A\"\n",
|
|
" sma_long_str = f\"${sma_long:.2f}\" if sma_long is not None else \"N/A\"\n",
|
|
" \n",
|
|
" context = f\"\"\"\n",
|
|
"Current Market Analysis for Trading Decision:\n",
|
|
"\n",
|
|
"PRICE DATA:\n",
|
|
"- Current Price: ${market_summary.get('current_price', 0):.2f}\n",
|
|
"- Price Change: {market_summary.get('price_change_pct', 0):.2f}%\n",
|
|
"- 5-Day Momentum: {market_summary.get('momentum_5d', 0):.2f}%\n",
|
|
"\n",
|
|
"TECHNICAL INDICATORS:\n",
|
|
"- Short SMA: {sma_short_str}\n",
|
|
"- Long SMA: {sma_long_str}\n",
|
|
"- Crossover Status: {market_summary.get('crossover_status', 'Unknown')}\n",
|
|
"\n",
|
|
"VOLUME ANALYSIS:\n",
|
|
"- Current Volume: {market_summary.get('current_volume', 0):,.0f}\n",
|
|
"- Volume Ratio: {market_summary.get('volume_ratio', 1):.2f}x (vs 10-day average)\n",
|
|
"\n",
|
|
"Based on this data, provide a trading recommendation following the specified format.\n",
|
|
"\"\"\"\n",
|
|
" return context\n",
|
|
" \n",
|
|
" def _query_llm(self, model_name, context, temperature):\n",
|
|
" if model_name not in MODELS:\n",
|
|
" raise ValueError(f\"Unknown model: {model_name}\")\n",
|
|
" \n",
|
|
" model_info = MODELS[model_name]\n",
|
|
" provider = model_info['provider']\n",
|
|
" model_id = model_info['model_id']\n",
|
|
" \n",
|
|
" if provider not in clients:\n",
|
|
" raise ValueError(f\"Client not available for {provider}\")\n",
|
|
" \n",
|
|
" try:\n",
|
|
" response = clients[provider].chat.completions.create(\n",
|
|
" model=model_id,\n",
|
|
" messages=[\n",
|
|
" {\"role\": \"system\", \"content\": self.system_prompt},\n",
|
|
" {\"role\": \"user\", \"content\": context}\n",
|
|
" ],\n",
|
|
" temperature=temperature if model_id != \"gpt-5-mini\" else 1,\n",
|
|
" max_completion_tokens=500 \n",
|
|
" )\n",
|
|
" \n",
|
|
" return response.choices[0].message.content\n",
|
|
" \n",
|
|
" except Exception as e:\n",
|
|
" raise Exception(f\"LLM query failed: {str(e)}\")\n",
|
|
"\n",
|
|
" \n",
|
|
" def _parse_llm_response(self, response):\n",
|
|
" \"\"\"Parse LLM response into structured format\"\"\"\n",
|
|
" try:\n",
|
|
" lines = response.strip().split('\\n')\n",
|
|
" decision = 'HOLD'\n",
|
|
" confidence = 5\n",
|
|
" reasoning = 'No clear reasoning provided'\n",
|
|
" risk_level = 'MEDIUM'\n",
|
|
" \n",
|
|
" for line in lines:\n",
|
|
" line = line.strip()\n",
|
|
" if line.startswith('DECISION:'):\n",
|
|
" decision = line.split(':', 1)[1].strip().upper()\n",
|
|
" elif line.startswith('CONFIDENCE:'):\n",
|
|
" try:\n",
|
|
" confidence = int(line.split(':', 1)[1].strip())\n",
|
|
" confidence = max(1, min(10, confidence)) # Clamp between 1-10\n",
|
|
" except:\n",
|
|
" confidence = 5\n",
|
|
" elif line.startswith('REASONING:'):\n",
|
|
" reasoning = line.split(':', 1)[1].strip()\n",
|
|
" elif line.startswith('RISK_LEVEL:'):\n",
|
|
" risk_level = line.split(':', 1)[1].strip().upper()\n",
|
|
" \n",
|
|
" # Validate decision\n",
|
|
" if decision not in ['BUY', 'SELL', 'HOLD']:\n",
|
|
" decision = 'HOLD'\n",
|
|
" \n",
|
|
" # Validate risk level\n",
|
|
" if risk_level not in ['LOW', 'MEDIUM', 'HIGH']:\n",
|
|
" risk_level = 'MEDIUM'\n",
|
|
" \n",
|
|
" return {\n",
|
|
" 'decision': decision,\n",
|
|
" 'confidence': confidence,\n",
|
|
" 'reasoning': reasoning,\n",
|
|
" 'risk_level': risk_level,\n",
|
|
" 'raw_response': response\n",
|
|
" }\n",
|
|
" \n",
|
|
" except Exception as e:\n",
|
|
" return {\n",
|
|
" 'decision': 'HOLD',\n",
|
|
" 'confidence': 1,\n",
|
|
" 'reasoning': f'Failed to parse LLM response: {str(e)}',\n",
|
|
" 'risk_level': 'HIGH',\n",
|
|
" 'raw_response': response\n",
|
|
" }\n",
|
|
" \n",
|
|
" def get_confidence_color(self, confidence):\n",
|
|
" \"\"\"Get color based on confidence level\"\"\"\n",
|
|
" if confidence >= 8:\n",
|
|
" return '#00ff88' # Green\n",
|
|
" elif confidence >= 6:\n",
|
|
" return '#ffaa00' # Orange\n",
|
|
" else:\n",
|
|
" return '#ff4444' # Red\n",
|
|
" \n",
|
|
" def get_decision_emoji(self, decision):\n",
|
|
" \"\"\"Get emoji for decision\"\"\"\n",
|
|
" emojis = {\n",
|
|
" 'BUY': '🟢',\n",
|
|
" 'SELL': '🔴',\n",
|
|
" 'HOLD': '🟡'\n",
|
|
" }\n",
|
|
" return emojis.get(decision, '❓')\n",
|
|
"\n",
|
|
"# Initialize trading advisor\n",
|
|
"trading_advisor = TradingAdvisor()\n",
|
|
"print(\"✅ Trading Advisor initialized!\")\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"id": "ae115eb0",
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"# Paper Trader Module for Kraken Sandbox\n",
|
|
"class PaperTrader:\n",
|
|
" \"\"\"Handles real trading on Kraken sandbox environment\"\"\"\n",
|
|
" \n",
|
|
" def __init__(self):\n",
|
|
" self.exchange = None\n",
|
|
" self.connected = False\n",
|
|
" self.trade_history = []\n",
|
|
" \n",
|
|
" if kraken_api_key and kraken_secret:\n",
|
|
" self._connect_sandbox()\n",
|
|
" \n",
|
|
" def _connect_sandbox(self):\n",
|
|
" \"\"\"Connect to Kraken sandbox environment\"\"\"\n",
|
|
" try:\n",
|
|
" self.exchange = ccxt.krakenfutures({\n",
|
|
" 'apiKey': kraken_api_key, \n",
|
|
" 'secret': kraken_secret,\n",
|
|
" 'enableRateLimit': True,\n",
|
|
" })\n",
|
|
"\n",
|
|
" self.exchange.set_sandbox_mode(True)\n",
|
|
" \n",
|
|
" # Test connection\n",
|
|
" self.exchange.load_markets()\n",
|
|
" self.connected = True\n",
|
|
" print(\"✅ Connected to Kraken sandbox environment\")\n",
|
|
" \n",
|
|
" except Exception as e:\n",
|
|
" print(f\"❌ Failed to connect to Kraken sandbox: {str(e)}\")\n",
|
|
" self.connected = False\n",
|
|
" \n",
|
|
" def get_balance(self):\n",
|
|
" \"\"\"Get real balance from Kraken sandbox\"\"\"\n",
|
|
" if not self.connected:\n",
|
|
" return {'USD': 0, 'BTC': 0, 'ETH': 0}\n",
|
|
" \n",
|
|
" try:\n",
|
|
" # Fetch real balance from Kraken\n",
|
|
" balance = self.exchange.fetch_balance()\n",
|
|
" \n",
|
|
" # Extract relevant currencies\n",
|
|
" kraken_balance = {\n",
|
|
" 'USD': balance.get('USD', {}).get('free', 0),\n",
|
|
" 'BTC': balance.get('BTC', {}).get('free', 0),\n",
|
|
" 'ETH': balance.get('ETH', {}).get('free', 0)\n",
|
|
" }\n",
|
|
" \n",
|
|
" return kraken_balance\n",
|
|
" \n",
|
|
" except Exception as e:\n",
|
|
" print(f\"❌ Error fetching balance: {str(e)}\")\n",
|
|
" return {'USD': 0, 'BTC': 0, 'ETH': 0}\n",
|
|
" \n",
|
|
" def place_order(self, symbol, side, amount, price=None):\n",
|
|
" \"\"\"Place a real trade order on Kraken sandbox\"\"\"\n",
|
|
" if not self.connected:\n",
|
|
" return {\n",
|
|
" 'success': False,\n",
|
|
" 'message': \"❌ Not connected to Kraken sandbox\",\n",
|
|
" 'trade': None\n",
|
|
" }\n",
|
|
" \n",
|
|
" try:\n",
|
|
" # Convert symbol to Kraken format\n",
|
|
" kraken_symbol = CRYPTO_PAIRS.get(symbol, symbol)\n",
|
|
" \n",
|
|
" # Get current market price if not provided\n",
|
|
" ticker = self.exchange.fetch_ticker(kraken_symbol)\n",
|
|
" \n",
|
|
" price = ticker[\"ask\"] if price is None else price\n",
|
|
" \n",
|
|
" # Check balance before placing order\n",
|
|
" balance = self.get_balance()\n",
|
|
" crypto_symbol = symbol.split('/')[0]\n",
|
|
" \n",
|
|
" if side == 'buy':\n",
|
|
" required_usd = amount / price\n",
|
|
" if balance['USD'] < required_usd:\n",
|
|
" return {\n",
|
|
" 'success': False,\n",
|
|
" 'message': f\"❌ Insufficient USD balance. Required: ${required_usd:.2f}, Available: ${balance['USD']:.2f}\",\n",
|
|
" 'trade': None\n",
|
|
" }\n",
|
|
" else: # sell \n",
|
|
" if balance[crypto_symbol] < (amount / price):\n",
|
|
" return {\n",
|
|
" 'success': False,\n",
|
|
" 'message': f\"❌ Insufficient {crypto_symbol} balance. Required: {amount}, Available: {balance[crypto_symbol]}\",\n",
|
|
" 'trade': None\n",
|
|
" }\n",
|
|
" \n",
|
|
" # Place market order\n",
|
|
"\n",
|
|
" order = self.exchange.create_market_order(\n",
|
|
" symbol=kraken_symbol,\n",
|
|
" side=side,\n",
|
|
" amount=amount\n",
|
|
" )\n",
|
|
" return {\n",
|
|
" 'success': True,\n",
|
|
" 'message': f\"✅ Placed {side} order for {amount} {kraken_symbol}\",\n",
|
|
" 'trade': order\n",
|
|
" }\n",
|
|
" \n",
|
|
" except Exception as e:\n",
|
|
" return {\n",
|
|
" 'success': False,\n",
|
|
" 'message': f\"❌ Order failed: {str(e)}\",\n",
|
|
" 'trade': None\n",
|
|
" }\n",
|
|
"\n",
|
|
" def get_positions(self):\n",
|
|
" \"\"\"Get current positions from real balance\"\"\"\n",
|
|
" try:\n",
|
|
" balance = self.get_balance()\n",
|
|
" positions = []\n",
|
|
" \n",
|
|
" for crypto, amount in balance.items():\n",
|
|
" if crypto != 'USD' and amount > 0:\n",
|
|
" price = self._get_crypto_price(crypto)\n",
|
|
" positions.append({\n",
|
|
" 'symbol': crypto,\n",
|
|
" 'amount': amount,\n",
|
|
" 'value_usd': amount * price\n",
|
|
" })\n",
|
|
" \n",
|
|
" return positions\n",
|
|
" \n",
|
|
" except Exception as e:\n",
|
|
" print(f\"❌ Error getting positions: {str(e)}\")\n",
|
|
" return []\n",
|
|
" \n",
|
|
" def _get_crypto_price(self, crypto):\n",
|
|
" \"\"\"Get current price for crypto\"\"\"\n",
|
|
" try:\n",
|
|
" symbol = f\"{crypto}/USD\"\n",
|
|
" ticker = self.exchange.fetch_ticker(CRYPTO_PAIRS.get(symbol, symbol))\n",
|
|
" return ticker['last']\n",
|
|
" except:\n",
|
|
" return 0\n",
|
|
" \n",
|
|
" def get_trade_history(self, limit=50):\n",
|
|
" \"\"\"Get real trade history from Kraken\"\"\"\n",
|
|
" if not self.connected:\n",
|
|
" return []\n",
|
|
" \n",
|
|
" try:\n",
|
|
" # Fetch recent trades from Kraken\n",
|
|
" trades = self.exchange.fetch_my_trades(limit=limit)\n",
|
|
" print(\"Obtianed trades\")\n",
|
|
" print(trades)\n",
|
|
"\n",
|
|
" # Format trades for display\n",
|
|
" formatted_trades = []\n",
|
|
" for trade in trades:\n",
|
|
" formatted_trades.append({\n",
|
|
" 'timestamp': datetime.fromtimestamp(trade['timestamp'] / 1000),\n",
|
|
" 'symbol': trade['symbol'],\n",
|
|
" 'side': trade['side'],\n",
|
|
" 'amount': trade['cost'],\n",
|
|
" 'price': trade['price'],\n",
|
|
" 'order_id': trade.get('order', 'unknown'),\n",
|
|
" 'status': 'filled'\n",
|
|
" })\n",
|
|
" \n",
|
|
" return formatted_trades\n",
|
|
" \n",
|
|
" except Exception as e:\n",
|
|
" print(f\"❌ Error fetching trade history: {str(e)}\")\n",
|
|
" return self.trade_history[-limit:] if self.trade_history else []\n",
|
|
" \n",
|
|
" def get_portfolio_value(self):\n",
|
|
" \"\"\"Calculate total portfolio value in USD from real balance\"\"\"\n",
|
|
" try:\n",
|
|
" balance = self.get_balance()\n",
|
|
" total_value = balance['USD']\n",
|
|
" \n",
|
|
" for crypto, amount in balance.items():\n",
|
|
" if crypto != 'USD' and amount > 0:\n",
|
|
" price = self._get_crypto_price(crypto)\n",
|
|
" total_value += amount * price\n",
|
|
" \n",
|
|
" return total_value\n",
|
|
" \n",
|
|
" except Exception as e:\n",
|
|
" print(f\"❌ Error calculating portfolio value: {str(e)}\")\n",
|
|
" return 0\n",
|
|
" \n",
|
|
" def get_order_status(self, order_id):\n",
|
|
" \"\"\"Get status of a specific order\"\"\"\n",
|
|
" if not self.connected:\n",
|
|
" return None\n",
|
|
" \n",
|
|
" try:\n",
|
|
" order = self.exchange.fetch_order(order_id)\n",
|
|
" return order\n",
|
|
" except Exception as e:\n",
|
|
" print(f\"❌ Error fetching order status: {str(e)}\")\n",
|
|
" return None\n",
|
|
" \n",
|
|
" def cancel_order(self, order_id):\n",
|
|
" \"\"\"Cancel a pending order\"\"\"\n",
|
|
" if not self.connected:\n",
|
|
" return False\n",
|
|
" \n",
|
|
" try:\n",
|
|
" result = self.exchange.cancel_order(order_id)\n",
|
|
" return result\n",
|
|
" except Exception as e:\n",
|
|
" print(f\"❌ Error canceling order: {str(e)}\")\n",
|
|
" return False\n",
|
|
"\n",
|
|
"# Initialize paper trader\n",
|
|
"paper_trader = PaperTrader()\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"id": "d586451a",
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"# Global state variables\n",
|
|
"current_data = pd.DataFrame()\n",
|
|
"current_sma_short = pd.Series()\n",
|
|
"current_sma_long = pd.Series()\n",
|
|
"current_symbol = \"BTC/USD\"\n",
|
|
"current_market_summary = {}\n",
|
|
"current_recommendation = {}\n",
|
|
"\n",
|
|
"# Gradio UI Functions\n",
|
|
"def fetch_market_data(symbol, sma_short_window, sma_long_window):\n",
|
|
" \"\"\"Fetch market data and calculate SMAs\"\"\"\n",
|
|
" global current_data, current_sma_short, current_sma_long, current_symbol, current_market_summary\n",
|
|
" \n",
|
|
" try:\n",
|
|
" current_symbol = symbol\n",
|
|
" \n",
|
|
" # Fetch data\n",
|
|
" data = data_fetcher.fetch_ohlcv(symbol, '1h', 720) # 30 days of hourly data\n",
|
|
" \n",
|
|
" if data.empty:\n",
|
|
" return None, \"❌ Failed to fetch market data\", \"No data available\", \"No data available\"\n",
|
|
" \n",
|
|
" # Calculate SMAs\n",
|
|
" sma_short = data_fetcher.calculate_sma(data, sma_short_window)\n",
|
|
" sma_long = data_fetcher.calculate_sma(data, sma_long_window)\n",
|
|
" \n",
|
|
" # Store in global state\n",
|
|
" current_data = data\n",
|
|
" current_sma_short = sma_short\n",
|
|
" current_sma_long = sma_long\n",
|
|
" \n",
|
|
" # Get market summary\n",
|
|
" current_market_summary = data_fetcher.get_market_summary(data, sma_short, sma_long)\n",
|
|
" \n",
|
|
" # Create chart\n",
|
|
" chart = chart_builder.create_candlestick_chart(data, sma_short, sma_long, symbol)\n",
|
|
" \n",
|
|
" # Prepare status message\n",
|
|
" status = f\"✅ Fetched {len(data)} records for {symbol}\"\n",
|
|
" status += f\"\\n📊 Current Price: ${current_market_summary.get('current_price', 0):.2f}\"\n",
|
|
" status += f\"\\n📈 Price Change: {current_market_summary.get('price_change_pct', 0):.2f}%\"\n",
|
|
" status += f\"\\n🔄 Crossover: {current_market_summary.get('crossover_status', 'Unknown')}\"\n",
|
|
" \n",
|
|
" # Market summary for display\n",
|
|
" sma_short = current_market_summary.get('sma_short')\n",
|
|
" sma_long = current_market_summary.get('sma_long')\n",
|
|
" \n",
|
|
" sma_short_str = f\"${sma_short:.2f}\" if sma_short is not None else \"N/A\"\n",
|
|
" sma_long_str = f\"${sma_long:.2f}\" if sma_long is not None else \"N/A\"\n",
|
|
" \n",
|
|
" summary = f\"\"\"\n",
|
|
"**Market Summary for {symbol}**\n",
|
|
"- Current Price: ${current_market_summary.get('current_price', 0):.2f}\n",
|
|
"- Price Change: {current_market_summary.get('price_change_pct', 0):.2f}%\n",
|
|
"- 5-Day Momentum: {current_market_summary.get('momentum_5d', 0):.2f}%\n",
|
|
"- Volume Ratio: {current_market_summary.get('volume_ratio', 1):.2f}x\n",
|
|
"- Short SMA: {sma_short_str}\n",
|
|
"- Long SMA: {sma_long_str}\n",
|
|
"- Crossover Status: {current_market_summary.get('crossover_status', 'Unknown')}\n",
|
|
"\"\"\"\n",
|
|
" \n",
|
|
" # Crossover status\n",
|
|
" crossover = current_market_summary.get('crossover_status', 'Unknown')\n",
|
|
" \n",
|
|
" return chart, status, summary, crossover\n",
|
|
" \n",
|
|
" except Exception as e:\n",
|
|
" return None, f\"❌ Error: {str(e)}\", \"Error occurred\", \"Error\"\n",
|
|
"\n",
|
|
"def get_trading_recommendation(model_name, temperature):\n",
|
|
" \"\"\"Get LLM trading recommendation\"\"\"\n",
|
|
" global current_market_summary, current_recommendation\n",
|
|
" \n",
|
|
" try:\n",
|
|
" if not current_market_summary:\n",
|
|
" return \"❌ No market data available. Please fetch data first.\", \"No data\", \"No data\", \"No data\"\n",
|
|
" \n",
|
|
" # Get recommendation from LLM\n",
|
|
" recommendation = trading_advisor.analyze_market(current_market_summary, model_name, temperature)\n",
|
|
" current_recommendation = recommendation\n",
|
|
" \n",
|
|
" # Format recommendation display\n",
|
|
" decision_emoji = trading_advisor.get_decision_emoji(recommendation['decision'])\n",
|
|
" confidence_color = trading_advisor.get_confidence_color(recommendation['confidence'])\n",
|
|
" \n",
|
|
" recommendation_text = f\"\"\"\n",
|
|
"**{decision_emoji} Trading Recommendation: {recommendation['decision']}**\n",
|
|
"\n",
|
|
"**Confidence:** {recommendation['confidence']}/10\n",
|
|
"**Risk Level:** {recommendation['risk_level']}\n",
|
|
"**Reasoning:** {recommendation['reasoning']}\n",
|
|
"\n",
|
|
"**Raw Analysis:**\n",
|
|
"{recommendation.get('raw_response', 'No raw response available')}\n",
|
|
"\"\"\"\n",
|
|
" \n",
|
|
" status = f\"✅ Analysis complete using {model_name}\"\n",
|
|
" return status, recommendation_text, recommendation['decision'], recommendation['confidence'], recommendation['reasoning']\n",
|
|
" \n",
|
|
" except Exception as e:\n",
|
|
" return f\"❌ Error getting recommendation: {str(e)}\", \"ERROR\", 0, \"Error occurred\"\n",
|
|
"\n",
|
|
"def execute_trade(trade_action, trade_amount):\n",
|
|
" \"\"\"Execute paper trade\"\"\"\n",
|
|
" global current_symbol, current_recommendation\n",
|
|
" \n",
|
|
" try:\n",
|
|
" if not current_recommendation:\n",
|
|
" return \"❌ No trading recommendation available. Please get analysis first.\", \"No recommendation\"\n",
|
|
" \n",
|
|
" if trade_action == \"SKIP\":\n",
|
|
" return \"⏭️ Trade skipped as requested\", \"Skipped\"\n",
|
|
" \n",
|
|
" # Determine trade side\n",
|
|
" if trade_action == \"BUY\" and current_recommendation['decision'] == 'BUY':\n",
|
|
" side = 'buy'\n",
|
|
" elif trade_action == \"SELL\" and current_recommendation['decision'] == 'SELL':\n",
|
|
" side = 'sell'\n",
|
|
" else:\n",
|
|
" return f\"⚠️ Trade action {trade_action} doesn't match recommendation {current_recommendation['decision']}\", \"Mismatch\"\n",
|
|
" \n",
|
|
" # Execute trade\n",
|
|
" result = paper_trader.place_order(current_symbol, side, trade_amount)\n",
|
|
" \n",
|
|
" print(result)\n",
|
|
"\n",
|
|
" if result['success']:\n",
|
|
" return result['message'], \"Success\"\n",
|
|
" else:\n",
|
|
" return result['message'], \"Failed\"\n",
|
|
" \n",
|
|
" except Exception as e:\n",
|
|
" return f\"❌ Trade execution error: {str(e)}\", \"Error\"\n",
|
|
"\n",
|
|
"def get_portfolio_status():\n",
|
|
" \"\"\"Get current portfolio status\"\"\"\n",
|
|
" try:\n",
|
|
" balance = paper_trader.get_balance()\n",
|
|
" portfolio_value = paper_trader.get_portfolio_value()\n",
|
|
" positions = paper_trader.get_positions()\n",
|
|
" trade_history = paper_trader.get_trade_history(10)\n",
|
|
" \n",
|
|
" # Format balance display\n",
|
|
" balance_text = f\"\"\"\n",
|
|
"**Portfolio Balance:**\n",
|
|
"- USD: ${balance['USD']:.2f}\n",
|
|
"- BTC: {balance['BTC']:.6f}\n",
|
|
"- ETH: {balance['ETH']:.6f}\n",
|
|
"- **Total Value: ${portfolio_value:.2f}**\n",
|
|
"\"\"\"\n",
|
|
" \n",
|
|
" # Format positions\n",
|
|
" if positions:\n",
|
|
" positions_text = \"**Current Positions:**\\n\"\n",
|
|
" for pos in positions:\n",
|
|
" positions_text += f\"- {pos['symbol']}: {pos['amount']:.6f} (${pos['value_usd']:.2f})\\n\"\n",
|
|
" else:\n",
|
|
" positions_text = \"**No current positions**\"\n",
|
|
" \n",
|
|
" # Format trade history\n",
|
|
" if trade_history:\n",
|
|
" history_df = pd.DataFrame(trade_history)\n",
|
|
" history_df['timestamp'] = pd.to_datetime(history_df['timestamp']).dt.strftime('%Y-%m-%d %H:%M')\n",
|
|
" history_df = history_df[['timestamp', 'symbol', 'side', 'amount', 'price', 'status']]\n",
|
|
" else:\n",
|
|
" history_df = pd.DataFrame(columns=['timestamp', 'symbol', 'side', 'amount', 'price', 'status'])\n",
|
|
" \n",
|
|
" return balance_text, positions_text, history_df\n",
|
|
" \n",
|
|
" except Exception as e:\n",
|
|
" return f\"❌ Error getting portfolio: {str(e)}\", \"Error\", pd.DataFrame()\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"id": "3ba1f9a8",
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"# Create Gradio Interface\n",
|
|
"def create_trading_interface():\n",
|
|
" \"\"\"Create the main Gradio interface with three tabs\"\"\"\n",
|
|
" \n",
|
|
" with gr.Blocks(title=\"Crypto Trading System\", theme=gr.themes.Soft()) as interface:\n",
|
|
" \n",
|
|
" gr.Markdown(\"# 🚀 Crypto Trading System with AI Analysis\")\n",
|
|
" gr.Markdown(\"Fetch real-time crypto data, analyze with AI, and execute paper trades using Kraken API\")\n",
|
|
" \n",
|
|
" # Status bar\n",
|
|
" with gr.Row():\n",
|
|
" api_status = gr.Textbox(\n",
|
|
" label=\"API Status\",\n",
|
|
" value=\"✅ All systems ready\" if clients else \"❌ No API keys configured\",\n",
|
|
" interactive=False,\n",
|
|
" scale=1\n",
|
|
" )\n",
|
|
" portfolio_status = gr.Textbox(\n",
|
|
" label=\"Portfolio Status\",\n",
|
|
" value=\"Ready for trading\",\n",
|
|
" interactive=False,\n",
|
|
" scale=2\n",
|
|
" )\n",
|
|
" \n",
|
|
" # Tab 1: Market Data\n",
|
|
" with gr.Tab(\"📊 Market Data\"):\n",
|
|
" gr.Markdown(\"### Fetch and analyze crypto market data\")\n",
|
|
" \n",
|
|
" with gr.Row():\n",
|
|
" with gr.Column(scale=2):\n",
|
|
" symbol_input = gr.Dropdown(\n",
|
|
" choices=list(CRYPTO_PAIRS.keys()),\n",
|
|
" value=\"BTC/USD\",\n",
|
|
" label=\"Crypto Pair\"\n",
|
|
" )\n",
|
|
" \n",
|
|
" with gr.Row():\n",
|
|
" sma_short_input = gr.Number(\n",
|
|
" value=20,\n",
|
|
" minimum=5,\n",
|
|
" maximum=100,\n",
|
|
" step=1,\n",
|
|
" label=\"Short SMA Window\"\n",
|
|
" )\n",
|
|
" sma_long_input = gr.Number(\n",
|
|
" value=50,\n",
|
|
" minimum=10,\n",
|
|
" maximum=200,\n",
|
|
" step=1,\n",
|
|
" label=\"Long SMA Window\"\n",
|
|
" )\n",
|
|
" \n",
|
|
" fetch_btn = gr.Button(\"📊 Fetch Market Data\", variant=\"primary\", size=\"lg\")\n",
|
|
" \n",
|
|
" with gr.Column(scale=1):\n",
|
|
" market_status = gr.Textbox(\n",
|
|
" label=\"Fetch Status\",\n",
|
|
" lines=4,\n",
|
|
" interactive=False\n",
|
|
" )\n",
|
|
" \n",
|
|
" crossover_status = gr.Textbox(\n",
|
|
" label=\"Crossover Status\",\n",
|
|
" interactive=False\n",
|
|
" )\n",
|
|
" \n",
|
|
" # Chart display\n",
|
|
" chart_output = gr.Plot(\n",
|
|
" label=\"Price Chart with SMA Analysis\",\n",
|
|
" show_label=True\n",
|
|
" )\n",
|
|
" \n",
|
|
" # Market summary\n",
|
|
" market_summary = gr.Markdown(\n",
|
|
" label=\"Market Summary\",\n",
|
|
" value=\"No data loaded\"\n",
|
|
" )\n",
|
|
" \n",
|
|
" # Tab 2: Trading Analysis\n",
|
|
" with gr.Tab(\"🤖 AI Trading Analysis\"):\n",
|
|
" gr.Markdown(\"### Get AI-powered trading recommendations\")\n",
|
|
" \n",
|
|
" with gr.Row():\n",
|
|
" with gr.Column(scale=2):\n",
|
|
" analysis_model = gr.Dropdown(\n",
|
|
" choices=list(MODELS.keys()),\n",
|
|
" value=list(MODELS.keys())[0],\n",
|
|
" label=\"AI Model\"\n",
|
|
" )\n",
|
|
" \n",
|
|
" analysis_temperature = gr.Slider(\n",
|
|
" minimum=0.0,\n",
|
|
" maximum=1.0,\n",
|
|
" value=0.3,\n",
|
|
" step=0.1,\n",
|
|
" label=\"Temperature (Lower = More Consistent)\"\n",
|
|
" )\n",
|
|
" \n",
|
|
" analyze_btn = gr.Button(\"🤖 Get AI Recommendation\", variant=\"primary\", size=\"lg\")\n",
|
|
" \n",
|
|
" with gr.Column(scale=1):\n",
|
|
" analysis_status = gr.Textbox(\n",
|
|
" label=\"Analysis Status\",\n",
|
|
" lines=2,\n",
|
|
" interactive=False\n",
|
|
" )\n",
|
|
" \n",
|
|
" # Analysis results\n",
|
|
" recommendation_display = gr.Markdown(\n",
|
|
" label=\"Trading Recommendation\",\n",
|
|
" value=\"No analysis performed yet\"\n",
|
|
" )\n",
|
|
" \n",
|
|
" with gr.Row():\n",
|
|
" decision_output = gr.Textbox(\n",
|
|
" label=\"Decision\",\n",
|
|
" interactive=False\n",
|
|
" )\n",
|
|
" confidence_output = gr.Number(\n",
|
|
" label=\"Confidence (1-10)\",\n",
|
|
" interactive=False\n",
|
|
" )\n",
|
|
" reasoning_output = gr.Textbox(\n",
|
|
" label=\"Reasoning\",\n",
|
|
" lines=3,\n",
|
|
" interactive=False\n",
|
|
" )\n",
|
|
" \n",
|
|
" # Tab 3: Paper Trading\n",
|
|
" with gr.Tab(\"💰 Paper Trading\"):\n",
|
|
" gr.Markdown(\"### Execute paper trades based on AI recommendations\")\n",
|
|
" \n",
|
|
" with gr.Row():\n",
|
|
" with gr.Column(scale=2):\n",
|
|
" # Portfolio status\n",
|
|
" portfolio_balance = gr.Markdown(\n",
|
|
" label=\"Portfolio Balance\",\n",
|
|
" value=\"Loading portfolio...\"\n",
|
|
" )\n",
|
|
" \n",
|
|
" portfolio_positions = gr.Markdown(\n",
|
|
" label=\"Current Positions\",\n",
|
|
" value=\"No positions\"\n",
|
|
" )\n",
|
|
" \n",
|
|
" # Trade execution\n",
|
|
" trade_action = gr.Radio(\n",
|
|
" choices=[\"BUY\", \"SELL\", \"SKIP\"],\n",
|
|
" value=\"SKIP\",\n",
|
|
" label=\"Trade Action\"\n",
|
|
" )\n",
|
|
" \n",
|
|
" trade_amount = gr.Number(\n",
|
|
" value=5,\n",
|
|
" minimum=1.0,\n",
|
|
" maximum=100.0,\n",
|
|
" step=1,\n",
|
|
" label=\"Trade Amount in USD (BTC/ETH)\"\n",
|
|
" )\n",
|
|
" \n",
|
|
" execute_btn = gr.Button(\"💰 Execute Trade\", variant=\"primary\", size=\"lg\")\n",
|
|
" \n",
|
|
" trade_result = gr.Textbox(\n",
|
|
" label=\"Trade Result\",\n",
|
|
" lines=3,\n",
|
|
" interactive=False\n",
|
|
" )\n",
|
|
" trade_status = gr.Textbox(\n",
|
|
" label=\"Trade Status\",\n",
|
|
" interactive=False,\n",
|
|
" visible=False \n",
|
|
" )\n",
|
|
" \n",
|
|
" with gr.Column(scale=1):\n",
|
|
" # Trade history\n",
|
|
" trade_history = gr.Dataframe(\n",
|
|
" label=\"Recent Trades\",\n",
|
|
" interactive=False,\n",
|
|
" wrap=True\n",
|
|
" )\n",
|
|
" \n",
|
|
" refresh_portfolio_btn = gr.Button(\"🔄 Refresh Portfolio\", variant=\"secondary\")\n",
|
|
" \n",
|
|
" # Event handlers\n",
|
|
" fetch_btn.click(\n",
|
|
" fetch_market_data,\n",
|
|
" inputs=[symbol_input, sma_short_input, sma_long_input],\n",
|
|
" outputs=[chart_output, market_status, market_summary, crossover_status]\n",
|
|
" )\n",
|
|
" \n",
|
|
" analyze_btn.click(\n",
|
|
" get_trading_recommendation,\n",
|
|
" inputs=[analysis_model, analysis_temperature],\n",
|
|
" outputs=[analysis_status, recommendation_display, decision_output, confidence_output, reasoning_output]\n",
|
|
" )\n",
|
|
" \n",
|
|
" execute_btn.click(\n",
|
|
" execute_trade,\n",
|
|
" inputs=[trade_action, trade_amount],\n",
|
|
" outputs=[trade_result, trade_status]\n",
|
|
" )\n",
|
|
" \n",
|
|
" refresh_portfolio_btn.click(\n",
|
|
" get_portfolio_status,\n",
|
|
" outputs=[portfolio_balance, portfolio_positions, trade_history]\n",
|
|
" )\n",
|
|
" \n",
|
|
" # Auto-refresh portfolio on load\n",
|
|
" interface.load(\n",
|
|
" get_portfolio_status,\n",
|
|
" outputs=[portfolio_balance, portfolio_positions, trade_history]\n",
|
|
" )\n",
|
|
" \n",
|
|
" return interface\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"id": "1b040594",
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"# Launch the Trading System\n",
|
|
"interface = create_trading_interface()\n",
|
|
"\n",
|
|
"interface.launch(\n",
|
|
" server_name=\"0.0.0.0\",\n",
|
|
" server_port=7860\n",
|
|
")\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "580a4048",
|
|
"metadata": {},
|
|
"source": [
|
|
"## Testing and Validation\n",
|
|
"\n",
|
|
"### Quick Test Functions\n",
|
|
"\n",
|
|
"Run these cells to test individual components:\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"id": "2ea5bfeb",
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"# Test 1: Data Fetching\n",
|
|
"print(\"🧪 Testing Data Fetching...\")\n",
|
|
"test_data = data_fetcher.fetch_ohlcv(\"BTC/USD\", \"1h\", 24) # Last 24 hours\n",
|
|
"if not test_data.empty:\n",
|
|
" print(f\"✅ Data fetch successful: {len(test_data)} records\")\n",
|
|
" print(f\" Latest price: ${test_data['close'].iloc[-1]:.2f}\")\n",
|
|
" \n",
|
|
" # Test SMA calculation\n",
|
|
" sma_20 = data_fetcher.calculate_sma(test_data, 20)\n",
|
|
" sma_50 = data_fetcher.calculate_sma(test_data, 50)\n",
|
|
" print(f\"✅ SMA calculation successful\")\n",
|
|
" print(f\" SMA 20: ${sma_20.iloc[-1]:.2f}\" if not sma_20.empty else \" SMA 20: N/A\")\n",
|
|
" print(f\" SMA 50: ${sma_50.iloc[-1]:.2f}\" if not sma_50.empty else \" SMA 50: N/A\")\n",
|
|
" \n",
|
|
" # Test crossover detection\n",
|
|
" crossover = data_fetcher.detect_crossover(sma_20, sma_50)\n",
|
|
" print(f\"✅ Crossover detection: {crossover}\")\n",
|
|
"else:\n",
|
|
" print(\"❌ Data fetch failed\")\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"id": "eb983b26",
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"# Test 2: Chart Generation\n",
|
|
"print(\"\\n🧪 Testing Chart Generation...\")\n",
|
|
"if not test_data.empty:\n",
|
|
" try:\n",
|
|
" test_chart = chart_builder.create_candlestick_chart(test_data, sma_20, sma_50, \"BTC/USD\")\n",
|
|
" print(\"✅ Chart generation successful\")\n",
|
|
" print(f\" Chart type: {type(test_chart)}\")\n",
|
|
" print(f\" Data points: {len(test_data)}\")\n",
|
|
" except Exception as e:\n",
|
|
" print(f\"❌ Chart generation failed: {str(e)}\")\n",
|
|
"else:\n",
|
|
" print(\"❌ No data available for chart testing\")\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"id": "e58aab73",
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"# Test 3: LLM Trading Analysis\n",
|
|
"print(\"\\n🧪 Testing LLM Trading Analysis...\")\n",
|
|
"if not test_data.empty and clients:\n",
|
|
" try:\n",
|
|
" # Get market summary\n",
|
|
" market_summary = data_fetcher.get_market_summary(test_data, sma_20, sma_50)\n",
|
|
" print(f\"✅ Market summary generated: {len(market_summary)} fields\")\n",
|
|
" \n",
|
|
" # Test LLM analysis (use first available model)\n",
|
|
" available_models = [name for name, info in MODELS.items() if info['provider'] in clients]\n",
|
|
" if available_models:\n",
|
|
" model_name = available_models[0]\n",
|
|
" print(f\" Testing with model: {model_name}\")\n",
|
|
" \n",
|
|
" recommendation = trading_advisor.analyze_market(market_summary, model_name, 0.3)\n",
|
|
" print(f\"✅ LLM analysis successful\")\n",
|
|
" print(f\" Decision: {recommendation['decision']}\")\n",
|
|
" print(f\" Confidence: {recommendation['confidence']}/10\")\n",
|
|
" print(f\" Risk Level: {recommendation['risk_level']}\")\n",
|
|
" else:\n",
|
|
" print(\"❌ No LLM models available\")\n",
|
|
" except Exception as e:\n",
|
|
" print(f\"❌ LLM analysis failed: {str(e)}\")\n",
|
|
"else:\n",
|
|
" print(\"❌ No data or LLM clients available for testing\")\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"id": "71c87193",
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"# Test 4: Real Kraken Sandbox Trading\n",
|
|
"print(\"\\n🧪 Testing Real Kraken Sandbox Trading...\")\n",
|
|
"try:\n",
|
|
" # Test portfolio status\n",
|
|
" balance = paper_trader.get_balance()\n",
|
|
" portfolio_value = paper_trader.get_portfolio_value()\n",
|
|
" print(f\"✅ Portfolio status retrieved from Kraken sandbox\")\n",
|
|
" print(f\" USD Balance: ${balance['USD']:.2f}\")\n",
|
|
" print(f\" BTC Balance: {balance['BTC']:.6f}\")\n",
|
|
" print(f\" ETH Balance: {balance['ETH']:.6f}\")\n",
|
|
" print(f\" Total Value: ${portfolio_value:.2f}\")\n",
|
|
" \n",
|
|
" # Test real trade (very small amount to avoid issues)\n",
|
|
" if balance['USD'] > 10: # Only test if we have some USD balance\n",
|
|
" test_result = paper_trader.place_order(\"BTC/USD\", \"sell\", 5)\n",
|
|
" if test_result['success']:\n",
|
|
" print(f\"✅ Real trade successful: {test_result['message']}\")\n",
|
|
" \n",
|
|
" # Check updated balance\n",
|
|
" new_balance = paper_trader.get_balance()\n",
|
|
" print(f\" Updated USD: ${new_balance['USD']:.2f}\")\n",
|
|
" print(f\" Updated BTC: {new_balance['BTC']:.6f}\")\n",
|
|
" else:\n",
|
|
" print(f\"❌ Real trade failed: {test_result['message']}\")\n",
|
|
" else:\n",
|
|
" print(\"⚠️ Insufficient USD balance for testing real trades\")\n",
|
|
" print(\" Note: Kraken sandbox may require initial funding\")\n",
|
|
" \n",
|
|
" # Test trade history\n",
|
|
" trade_history = paper_trader.get_trade_history(5)\n",
|
|
" print(f\"✅ Trade history retrieved: {len(trade_history)} recent trades\")\n",
|
|
" \n",
|
|
" # Test positions\n",
|
|
" positions = paper_trader.get_positions()\n",
|
|
" print(f\"✅ Current positions: {len(positions)} active positions\")\n",
|
|
" for pos in positions:\n",
|
|
" print(f\" {pos['symbol']}: {pos['amount']:.6f} (${pos['value_usd']:.2f})\")\n",
|
|
" \n",
|
|
"except Exception as e:\n",
|
|
" print(f\"❌ Real trading test failed: {str(e)}\")\n",
|
|
"\n",
|
|
"print(\"\\n🎉 All tests completed!\")\n",
|
|
"print(\"=\" * 50)\n"
|
|
]
|
|
}
|
|
],
|
|
"metadata": {
|
|
"kernelspec": {
|
|
"display_name": ".venv",
|
|
"language": "python",
|
|
"name": "python3"
|
|
},
|
|
"language_info": {
|
|
"codemirror_mode": {
|
|
"name": "ipython",
|
|
"version": 3
|
|
},
|
|
"file_extension": ".py",
|
|
"mimetype": "text/x-python",
|
|
"name": "python",
|
|
"nbconvert_exporter": "python",
|
|
"pygments_lexer": "ipython3",
|
|
"version": "3.11.10"
|
|
}
|
|
},
|
|
"nbformat": 4,
|
|
"nbformat_minor": 5
|
|
}
|