import time
import datetime
import json
import pandas as pd
import numpy as np
import requests
import hashlib
import hmac
from urllib.parse import urlencode
from threading import Thread, Lock
from queue import Queue, Empty
import logging
from typing import Optional, Dict, List, Any
# Configure logging
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
handlers=[
logging.FileHandler('delta_bot.log'),
logging.StreamHandler()
]
)
logger = logging.getLogger(__name__)
class DeltaExchangeAPI:
"""Enhanced Delta Exchange API client with all required methods"""
def __init__(self, api_key: str, secret_key: str, base_url: str =
"https://api.delta.exchange"):
self.base_url = base_url
self.api_key = api_key
self.secret_key = secret_key
self.last_request_time = 0
self.min_request_interval = 0.5
self.session = requests.Session()
self.session.headers.update({
'User-Agent': 'DeltaTradingBot/1.0',
'api-key': self.api_key
})
self.lock = Lock()
def _rate_limit(self) -> None:
"""Enforce rate limiting between API calls"""
with self.lock:
elapsed = time.time() - self.last_request_time
if elapsed < self.min_request_interval:
time.sleep(self.min_request_interval - elapsed)
self.last_request_time = time.time()
def _signed_request(self, method: str, path: str, params: Optional[Dict] =
None) -> Optional[Dict]:
"""Make signed requests with comprehensive error handling"""
self._rate_limit()
params = params or {}
headers = {'Content-Type': 'application/json'}
try:
# Add authentication for private endpoints
if path.startswith('/v2/wallet') or path.startswith('/v2/orders'):
params['timestamp'] = int(time.time() * 1000)
query_string = urlencode(sorted(params.items())) if params else ''
signature_payload = f"{method}\n{path}\
n{query_string}".encode('utf-8')
signature = hmac.new(
self.secret_key.encode('utf-8'),
signature_payload,
hashlib.sha256
).hexdigest()
headers.update({
'signature': signature
})
url = f"{self.base_url}{path}"
if method == "GET" and params:
url = f"{url}?{urlencode(params)}"
response = self.session.request(
method,
url,
headers=headers,
json=params if method != "GET" else None,
timeout=15
)
response.raise_for_status()
return response.json()
except requests.exceptions.HTTPError as e:
if e.response.status_code == 401:
error_detail = e.response.json().get('error', {}).get('message',
'') if e.response.content else ''
logger.error(f"Authentication failed (401). Details:
{error_detail}")
logger.error(f"HTTP error for {method} {path}: {str(e)}")
if e.response.status_code == 429:
retry_after = int(e.response.headers.get('Retry-After', 5))
logger.warning(f"Rate limited. Waiting {retry_after} seconds")
time.sleep(retry_after)
return None
except requests.exceptions.RequestException as e:
logger.error(f"Request failed for {method} {path}: {str(e)}")
return None
except Exception as e:
logger.error(f"Unexpected error in _signed_request: {str(e)}")
return None
def get_products(self, page_size: int = 100) -> Optional[Dict]:
"""Get list of available trading products"""
params = {
'page_size': page_size,
'contract_types': 'perpetual_futures',
'is_active': 'true'
}
return self._signed_request("GET", "/v2/products", params)
def get_market_data(self, symbol: str) -> Optional[Dict]:
"""Get market data for a specific symbol"""
return self._signed_request("GET", f"/v2/tickers/{symbol}")
def get_account_balance(self) -> Optional[Dict]:
"""Get current account balance with improved error handling"""
response = self._signed_request("GET", "/v2/wallet/balances")
if response is None:
logger.error("No response received from balance API")
return None
if isinstance(response, str):
try:
response = json.loads(response)
except json.JSONDecodeError:
logger.error("Failed to parse balance response")
return None
if not isinstance(response, dict):
logger.error("Unexpected balance response format")
return None
return response
def place_order(self, product_id: int, side: str, size: float,
order_type: str = "limit", price: Optional[float] = None,
reduce_only: bool = False) -> Optional[Dict]:
"""Place an order"""
params = {
"product_id": int(product_id),
"size": str(size),
"side": side.lower(),
"order_type": order_type.lower(),
"reduce_only": reduce_only
}
if price is not None:
params["limit_price"] = str(price)
return self._signed_request("POST", "/v2/orders", params)
def get_historical_data(self, symbol: str, resolution: int, limit: int = 100) -
> Optional[Dict]:
"""Get historical candle data"""
params = {
'symbol': symbol,
'resolution': resolution,
'limit': min(limit, 1000)
}
return self._signed_request("GET", "/v2/history/candles", params)
if not result or 'result' not in result:
logger.error("Failed to get account balance")
return None
return result
class Config:
"""Enhanced configuration with validation"""
# Risk Management
MAX_RISK_PER_TRADE = 0.01 # 1% of account per trade
DAILY_PROFIT_TARGET = 0.05 # 5% daily target
MAX_TRADE_DURATION = 3600 # 1 hour max trade duration
MAX_DRAWDOWN = 0.10 # 10% max daily drawdown
# Indicators
RSI_PERIOD = 14
ATR_PERIOD = 14
EMA_PERIODS = [9, 21, 50]
SUPER_TREND_PERIOD = 10
SUPER_TREND_MULTIPLIER = 3
MACD_FAST = 12
MACD_SLOW = 26
MACD_SIGNAL = 9
# Trading Criteria
TARGET_SYMBOLS = ['BTC-PERP', 'ETH-PERP', 'SOL-PERP', 'BNB-PERP', 'XRP-PERP']
MIN_24H_VOLUME = 1000000 # $1M minimum daily volume
MIN_VOLATILITY = 0.015 # 1.5% minimum daily volatility
MAX_SPREAD_PCT = 0.002 # 0.2% maximum spread
# System
INITIAL_DELAY = 5 # seconds for startup
PRODUCT_REFRESH_INTERVAL = 1800 # 30 minutes
REQUEST_TIMEOUT = 15 # seconds
MAX_RETRIES = 5 # max retries for API calls
HEARTBEAT_INTERVAL = 300 # 5 minutes for system health checks
class TradingBot:
"""Enhanced trading bot with robust error handling and position management"""
def __init__(self, api: DeltaExchangeAPI):
self.api = api
self.active_positions = []
self.trade_history = []
self.products = []
self.order_queue = Queue()
self.lock = Lock()
self.running = True
self.start_time = time.time()
self.last_balance_check = 0
self.account_value = self._get_initial_account_value()
self.daily_profit_target = self.account_value * (1 +
Config.DAILY_PROFIT_TARGET)
self.max_drawdown = self.account_value * (1 - Config.MAX_DRAWDOWN)
# Start background threads
Thread(target=self._process_orders, daemon=True).start()
Thread(target=self._monitor_positions, daemon=True).start()
Thread(target=self._system_monitor, daemon=True).start()
logger.info(f"Trading bot initialized with account value: $
{self.account_value:,.2f}")
def _get_initial_account_value(self) -> float:
"""Get initial account value from available balance"""
balance = self.api.get_account_balance()
if balance and 'result' in balance:
for asset in balance['result']:
if asset['asset_symbol'] == 'USD':
return float(asset['available_balance'])
logger.warning("Failed to get account balance. Using default $10,000")
return 10000 # Fallback to $10,000 if balance check fails
def _process_orders(self) -> None:
"""Process orders from the queue with enhanced error handling"""
while self.running:
try:
try:
order = self.order_queue.get(timeout=1)
if not order:
continue
logger.info(f"Processing order: {order}")
try:
if order['action'] == 'buy':
result = self.api.place_order(
product_id=order['product_id'],
side='buy',
size=order['size'],
order_type=order.get('order_type', 'limit'),
price=order.get('price'),
reduce_only=order.get('reduce_only', False)
)
elif order['action'] == 'sell':
result = self.api.place_order(
product_id=order['product_id'],
side='sell',
size=order['size'],
order_type=order.get('order_type', 'market'),
price=order.get('price'),
reduce_only=order.get('reduce_only', False)
)
else:
logger.error(f"Invalid order action:
{order['action']}")
continue
if result and 'result' in result:
self._log_trade(order, result['result'])
self._update_account_value(order, result['result'])
else:
logger.error(f"Order failed: {order}")
except Exception as e:
logger.error(f"Order processing error: {str(e)}",
exc_info=True)
except Empty:
continue # Empty queue is normal, just continue
except Exception as e:
if self.running: # Only log if we're not shutting down
logger.error(f"Order queue processing error: {str(e)}",
exc_info=True)
def _monitor_positions(self) -> None:
"""Monitor and manage positions with enhanced risk controls"""
while self.running:
try:
self._check_daily_limits()
self._manage_positions()
time.sleep(10)
except Exception as e:
logger.error(f"Position monitoring error: {str(e)}", exc_info=True)
time.sleep(30)
def _system_monitor(self) -> None:
"""Monitor system health and performance"""
while self.running:
try:
# Check account balance periodically
if time.time() - self.last_balance_check > 3600: # 1 hour
self._update_account_balance()
self.last_balance_check = time.time()
# Check for system anomalies
if len(self.trade_history) > 100:
logger.warning("Trade history growing large, consider
archiving")
time.sleep(Config.HEARTBEAT_INTERVAL)
except Exception as e:
logger.error(f"System monitor error: {str(e)}", exc_info=True)
time.sleep(60)
def _update_account_balance(self) -> None:
"""Update account balance from API"""
balance = self.api.get_account_balance()
if balance and 'result' in balance:
for asset in balance['result']:
if asset['asset_symbol'] == 'USD':
new_balance = float(asset['available_balance'])
if abs(new_balance - self.account_value) > 100: # Significant
change
logger.info(f"Account balance updated from
{self.account_value:,.2f} to {new_balance:,.2f}")
self.account_value = new_balance
return
def _check_daily_limits(self) -> None:
"""Check daily profit target and max drawdown"""
if self.account_value >= self.daily_profit_target:
logger.info(f"Daily profit target reached! Current value: $
{self.account_value:,.2f}")
self._close_all_positions("Profit Target Reached")
self.running = False
if self.account_value <= self.max_drawdown:
logger.error(f"Max drawdown reached! Current value: $
{self.account_value:,.2f}")
self._close_all_positions("Max Drawdown Reached")
self.running = False
def _close_all_positions(self, reason: str) -> None:
"""Close all open positions"""
with self.lock:
for pos in list(self.active_positions):
self._close_position(pos, reason)
def _load_products(self) -> bool:
"""Load and filter trading products with enhanced validation"""
logger.info("Loading products...")
products_data = self.api.get_products(page_size=100)
if not products_data or 'result' not in products_data:
logger.error("No products data received")
return False
qualified = []
for p in products_data['result']:
try:
# Initial filtering
if not (p['symbol'] in Config.TARGET_SYMBOLS and p['is_active']):
continue
# Get market data
market_data = self.api.get_market_data(p['symbol'])
if not market_data or 'result' not in market_data:
continue
md = market_data['result']
# Calculate metrics
try:
mark_price = float(md['mark_price'])
best_ask = float(md['best_ask'])
best_bid = float(md['best_bid'])
spread = (best_ask - best_bid) / mark_price
volume = float(md.get('volume_24h', 0))
volatility = (float(md.get('high_24h', 0)) -
float(md.get('low_24h', 0))) / mark_price
# Apply filters
if (volume >= Config.MIN_24H_VOLUME and
volatility >= Config.MIN_VOLATILITY and
spread <= Config.MAX_SPREAD_PCT):
qualified.append({
'symbol': p['symbol'],
'product_id': p['id'],
'mark_price': mark_price,
'volume': volume,
'volatility': volatility,
'spread': spread,
'liquidity': volume * volatility # Simple liquidity
score
})
except (KeyError, ValueError, TypeError) as e:
logger.warning(f"Error processing market data for
{p['symbol']}: {str(e)}")
continue
except Exception as e:
logger.error(f"Error processing product {p.get('symbol')}:
{str(e)}")
continue
if qualified:
# Sort by liquidity score (volume * volatility)
qualified.sort(key=lambda x: -x['liquidity'])
self.products = qualified
logger.info(f"Loaded {len(qualified)} qualified products")
return True
logger.error("No qualified products found")
return False
def _calculate_indicators(self, symbol: str) -> Optional[pd.DataFrame]:
"""Calculate technical indicators with robust error handling"""
try:
data = self.api.get_historical_data(symbol, resolution=60, limit=100)
if not data or 'result' not in data or not data['result']:
logger.warning(f"No historical data for {symbol}")
return None
df = pd.DataFrame(data['result'])
# Convert and validate columns
required_cols = ['time', 'open', 'high', 'low', 'close', 'volume']
if not all(col in df.columns for col in required_cols):
logger.warning(f"Missing required columns for {symbol}")
return None
try:
df['time'] = pd.to_datetime(df['time'], unit='s')
numeric_cols = ['open', 'high', 'low', 'close', 'volume']
df[numeric_cols] = df[numeric_cols].apply(pd.to_numeric,
errors='coerce')
df.dropna(inplace=True)
if len(df) < max(Config.RSI_PERIOD, Config.ATR_PERIOD,
Config.MACD_SLOW):
logger.warning(f"Insufficient data points for {symbol}")
return None
except Exception as e:
logger.error(f"Data conversion error for {symbol}: {str(e)}")
return None
# Calculate indicators
try:
df = self._calculate_rsi(df)
df = self._calculate_macd(df)
df = self._calculate_supertrend(df)
df = self._calculate_emas(df)
df = self._calculate_atr(df)
return df.iloc[-100:] # Return most recent 100 data points
except Exception as e:
logger.error(f"Indicator calculation error for {symbol}: {str(e)}")
return None
except Exception as e:
logger.error(f"Unexpected error in _calculate_indicators: {str(e)}")
return None
def _calculate_rsi(self, df: pd.DataFrame) -> pd.DataFrame:
"""Calculate RSI with validation"""
delta = df['close'].diff()
gain = delta.where(delta > 0, 0)
loss = -delta.where(delta < 0, 0)
avg_gain = gain.rolling(Config.RSI_PERIOD,
min_periods=Config.RSI_PERIOD).mean()
avg_loss = loss.rolling(Config.RSI_PERIOD,
min_periods=Config.RSI_PERIOD).mean()
rs = avg_gain / avg_loss
df['rsi'] = 100 - (100 / (1 + rs))
return df
def _calculate_macd(self, df: pd.DataFrame) -> pd.DataFrame:
"""Calculate MACD with validation"""
df['ema_fast'] = df['close'].ewm(span=Config.MACD_FAST,
min_periods=Config.MACD_FAST, adjust=False).mean()
df['ema_slow'] = df['close'].ewm(span=Config.MACD_SLOW,
min_periods=Config.MACD_SLOW, adjust=False).mean()
df['macd'] = df['ema_fast'] - df['ema_slow']
df['macd_signal'] = df['macd'].ewm(span=Config.MACD_SIGNAL,
min_periods=Config.MACD_SIGNAL, adjust=False).mean()
return df
def _calculate_supertrend(self, df: pd.DataFrame) -> pd.DataFrame:
"""Calculate SuperTrend with validation"""
hl2 = (df['high'] + df['low']) / 2
atr = (df['high'] - df['low']).rolling(Config.SUPER_TREND_PERIOD,
min_periods=Config.SUPER_TREND_PERIOD).mean()
upper = hl2 + (Config.SUPER_TREND_MULTIPLIER * atr)
lower = hl2 - (Config.SUPER_TREND_MULTIPLIER * atr)
supertrend = [lower.iloc[0]]
direction = ['up']
for i in range(1, len(df)):
if df['close'].iloc[i] > upper.iloc[i-1]:
supertrend.append(lower.iloc[i])
direction.append('up')
elif df['close'].iloc[i] < lower.iloc[i-1]:
supertrend.append(upper.iloc[i])
direction.append('down')
else:
supertrend.append(supertrend[-1])
direction.append(direction[-1])
if direction[-1] == 'up' and df['close'].iloc[i] < supertrend[-1]:
supertrend[-1] = upper.iloc[i]
direction[-1] = 'down'
elif direction[-1] == 'down' and df['close'].iloc[i] > supertrend[-
1]:
supertrend[-1] = lower.iloc[i]
direction[-1] = 'up'
df['supertrend'] = supertrend
df['supertrend_dir'] = direction
return df
def _calculate_emas(self, df: pd.DataFrame) -> pd.DataFrame:
"""Calculate EMAs with validation"""
for period in Config.EMA_PERIODS:
df[f'ema_{period}'] = df['close'].ewm(span=period, min_periods=period,
adjust=False).mean()
return df
def _calculate_atr(self, df: pd.DataFrame) -> pd.DataFrame:
"""Calculate ATR with validation"""
tr = pd.concat([
df['high'] - df['low'],
(df['high'] - df['close'].shift()).abs(),
(df['low'] - df['close'].shift()).abs()
], axis=1).max(axis=1)
df['atr'] = tr.rolling(Config.ATR_PERIOD,
min_periods=Config.ATR_PERIOD).mean()
return df
def _determine_trend(self, df: pd.DataFrame) -> str:
"""Determine market trend with weighted scoring"""
try:
last = df.iloc[-1]
score = 0
# SuperTrend direction (weight: 2)
if last['supertrend_dir'] == 'up':
score += 2
else:
score -= 2
# EMA alignment (weight: 1 per condition)
if last['ema_9'] > last['ema_21']:
score += 1
else:
score -= 1
if last['ema_21'] > last['ema_50']:
score += 1
else:
score -= 1
# MACD (weight: 1)
if last['macd'] > last['macd_signal']:
score += 1
else:
score -= 1
# RSI (weight: 0.5)
if last['rsi'] > 50:
score += 0.5
else:
score -= 0.5
# Determine trend based on total score
if score >= 3:
return 'bullish'
elif score <= -3:
return 'bearish'
return 'neutral'
except Exception as e:
logger.error(f"Trend determination error: {str(e)}")
return 'neutral'
def _manage_positions(self) -> None:
"""Manage open positions with enhanced risk controls"""
if not self.active_positions:
return
for pos in list(self.active_positions):
try:
# Check max duration
if (datetime.datetime.now() - pos['entry_time']).total_seconds() >
Config.MAX_TRADE_DURATION:
self._close_position(pos, 'Time Exit')
continue
# Get current market data
market = self.api.get_market_data(pos['symbol'])
if not market or 'result' not in market:
logger.warning(f"Couldn't get market data for {pos['symbol']}")
continue
current_price = float(market['result']['mark_price'])
pnl_pct = (current_price - pos['entry_price']) / pos['entry_price']
if pos['side'] == 'sell':
pnl_pct *= -1
# Check profit target
if pnl_pct >= Config.DAILY_PROFIT_TARGET:
self._close_position(pos, 'Profit Target')
continue
# Dynamic stop loss based on ATR and volatility
atr_multiplier = 1.5
if 'atr' not in pos:
logger.warning(f"No ATR data for position {pos['symbol']}")
continue
if pos['side'] == 'buy':
stop_price = pos['entry_price'] - (pos['atr'] * atr_multiplier)
if current_price <= stop_price:
self._close_position(pos, 'Stop Loss')
continue
else: # sell/short
stop_price = pos['entry_price'] + (pos['atr'] * atr_multiplier)
if current_price >= stop_price:
self._close_position(pos, 'Stop Loss')
continue
# Check for trend reversal
df = self._calculate_indicators(pos['symbol'])
if df is not None:
current_trend = self._determine_trend(df)
if ((pos['side'] == 'buy' and current_trend == 'bearish') or
(pos['side'] == 'sell' and current_trend ==
'bullish')):
self._close_position(pos, 'Trend Reversal')
continue
except Exception as e:
logger.error(f"Position management error for {pos['symbol']}:
{str(e)}")
continue
def _close_position(self, position: Dict, reason: str) -> None:
"""Close a position with proper logging"""
try:
logger.info(f"Closing position: {position['symbol']} {position['side']}
{position['size']} ({reason})")
self.order_queue.put({
'action': 'sell' if position['side'] == 'buy' else 'buy',
'product_id': position['product_id'],
'symbol': position['symbol'],
'size': position['size'],
'order_type': 'market',
'reason': reason
})
with self.lock:
if position in self.active_positions:
self.active_positions.remove(position)
except Exception as e:
logger.error(f"Error closing position: {str(e)}")
def _log_trade(self, order: Dict, result: Dict) -> None:
"""Log trade details with comprehensive information"""
try:
trade = {
'time': datetime.datetime.now(),
'symbol': order.get('symbol', ''),
'side': order['action'].upper(),
'size': float(result['size']),
'price': float(result.get('fill_price', order.get('price', 0))),
'type': result['order_type'],
'status': result['status'],
'reason': order.get('reason', 'entry'),
'order_id': result.get('id', '')
}
with self.lock:
self.trade_history.append(trade)
logger.info(f"Trade executed: {trade}")
except Exception as e:
logger.error(f"Error logging trade: {str(e)}")
def _update_account_value(self, order: Dict, result: Dict) -> None:
"""Update account value with P&L calculation"""
try:
if order['action'] == 'sell' and self.trade_history:
# Find matching entry trade
entry_trade = next(
(t for t in reversed(self.trade_history)
if t['symbol'] == order.get('symbol') and t['side'] == 'BUY'
and t['status'] == 'filled'),
None
)
if entry_trade:
exit_price = float(result.get('fill_price', 0))
entry_price = entry_trade['price']
size = float(result['size'])
if order.get('side', '').lower() == 'sell':
pnl = (entry_price - exit_price) * size
else:
pnl = (exit_price - entry_price) * size
with self.lock:
self.account_value += pnl
logger.info(f"Position closed. P&L: ${pnl:,.2f} | Account
Value: ${self.account_value:,.2f}")
except Exception as e:
logger.error(f"Error updating account value: {str(e)}")
def _get_position_size(self, symbol: str, price: float) -> float:
"""Calculate position size based on risk parameters"""
try:
# Get current price and volatility
market = self.api.get_market_data(symbol)
if not market or 'result' not in market:
return 0
mark_price = float(market['result']['mark_price'])
atr = self._calculate_atr(pd.DataFrame([{
'high': float(market['result'].get('high_24h', mark_price * 1.01)),
'low': float(market['result'].get('low_24h', mark_price * 0.99)),
'close': mark_price
}]))['atr'].iloc[0]
# Calculate size based on risk and volatility
risk_amount = self.account_value * Config.MAX_RISK_PER_TRADE
size = risk_amount / (atr * 2) # Risk 1% per ATR*2 move
# Apply minimum and maximum size constraints
min_size = 0.001
max_size = risk_amount / (mark_price * 0.01) # Don't risk more than 1%
of price
size = max(min(size, max_size), min_size)
return round(size, 4)
except Exception as e:
logger.error(f"Error calculating position size: {str(e)}")
return 0
def run(self) -> None:
"""Main trading loop with comprehensive error handling"""
logger.info("Starting Delta Exchange Trading Bot")
logger.info(f"Initial account value: ${self.account_value:,.2f}")
logger.info(f"Daily profit target: ${self.daily_profit_target:,.2f}")
logger.info(f"Max drawdown: ${self.max_drawdown:,.2f}")
# Initial delay for system stability
time.sleep(Config.INITIAL_DELAY)
# Initial product load
if not self._load_products():
logger.error("Critical error: Failed to load products. Exiting.")
return
last_product_refresh = time.time()
last_heartbeat = time.time()
while self.running:
try:
current_time = time.time()
# System heartbeat
if current_time - last_heartbeat > 300: # 5 minutes
logger.info("System heartbeat - Bot is running normally")
last_heartbeat = current_time
# Refresh products periodically
if current_time - last_product_refresh >
Config.PRODUCT_REFRESH_INTERVAL:
if self._load_products():
last_product_refresh = current_time
else:
logger.warning("Product refresh failed. Will retry next
cycle")
# Skip if we have active positions
if self.active_positions:
time.sleep(10)
continue
# Get best product
if not self.products:
logger.warning("No products available. Refreshing...")
if not self._load_products():
time.sleep(60)
continue
product = self.products[0]
logger.info(f"Analyzing {product['symbol']}...")
# Calculate indicators
df = self._calculate_indicators(product['symbol'])
if df is None or df.empty:
logger.warning(f"Could not calculate indicators for
{product['symbol']}")
time.sleep(10)
continue
# Determine trend
trend = self._determine_trend(df)
if trend == 'neutral':
logger.info(f"No clear trend for {product['symbol']}.
Waiting...")
time.sleep(10)
continue
# Get current market data
market = self.api.get_market_data(product['symbol'])
if not market or 'result' not in market:
logger.warning(f"Could not get market data for
{product['symbol']}")
time.sleep(10)
continue
price = float(market['result']['mark_price'])
size = self._get_position_size(product['symbol'], price)
# Validate position size
if size <= 0:
logger.warning(f"Invalid size {size} for {product['symbol']}")
time.sleep(10)
continue
# Place order
order = {
'action': 'buy' if trend == 'bullish' else 'sell',
'product_id': product['product_id'],
'symbol': product['symbol'],
'size': size,
'price': price,
'order_type': 'limit'
}
self.order_queue.put(order)
# Track position
with self.lock:
self.active_positions.append({
'product_id': product['product_id'],
'symbol': product['symbol'],
'side': 'buy' if trend == 'bullish' else 'sell',
'size': size,
'entry_price': price,
'entry_time': datetime.datetime.now(),
'atr': df['atr'].iloc[-1]
})
logger.info(f"New {trend} position: {product['symbol']} {size} @
{price}")
time.sleep(10)
except KeyboardInterrupt:
logger.info("Shutdown signal received. Closing all positions...")
self._close_all_positions("Shutdown")
self.running = False
break
except Exception as e:
logger.error(f"Main loop error: {str(e)}", exc_info=True)
time.sleep(30)
if __name__ == "__main__":
try:
# Get API keys securely
print("Delta Exchange Trading Bot Setup")
print("-------------------------------")
while True:
api_key = input("Enter your Delta Exchange API key: ").strip()
if api_key:
break
print("API key cannot be empty. Please try again.")
while True:
secret_key = input("Enter your Delta Exchange API secret: ").strip()
if secret_key:
break
print("API secret cannot be empty. Please try again.")
# Initialize API
api = DeltaExchangeAPI(api_key=api_key, secret_key=secret_key)
# Enhanced connection test
logger.info("Testing public API connection...")
try:
test_response = api.get_products(page_size=1)
if not test_response:
raise ConnectionError("Received empty response from API")
if not isinstance(test_response, dict):
raise ConnectionError(f"Unexpected response type:
{type(test_response)}")
if 'result' not in test_response:
if 'error' in test_response:
error_msg = test_response.get('error', {}).get('message',
'Unknown error')
raise ConnectionError(f"API error: {error_msg}")
raise ConnectionError("Malformed API response - missing 'result'
key")
if not test_response.get('result'):
raise ConnectionError("No products returned - check account
permissions")
logger.info(f"Public API connection successful. First product:
{test_response['result'][0]['symbol']}")
# Test private endpoint
logger.info("Testing private API authentication...")
balance_data = api.get_account_balance()
if not balance_data:
raise ConnectionError("Failed to get balance data - check API
keys")
if not isinstance(balance_data, dict):
raise ConnectionError(f"Unexpected balance response type:
{type(balance_data)}")
if 'result' not in balance_data:
raise ConnectionError("Invalid balance response format - missing
'result' key")
usd_balance = None
for asset in balance_data['result']:
if isinstance(asset, dict) and asset.get('asset_symbol') == 'USD':
try:
usd_balance = float(asset.get('available_balance', 0))
break
except (ValueError, TypeError):
continue
if usd_balance is None:
raise ConnectionError("No USD balance found in response")
logger.info(f"Authentication successful. Available balance: $
{usd_balance:,.2f}")
# Start the bot if tests pass
bot = TradingBot(api)
bot.run()
except ConnectionError as e:
logger.error(f"Connection test failed: {str(e)}")
logger.info("Please check your internet connection and try again.")
except Exception as e:
logger.error(f"Unexpected error during connection test: {str(e)}")
logger.info("Please check your API keys and try again.")
except Exception as e:
logger.error(f"Fatal error: {str(e)}")
logger.info("The bot has encountered a critical error and will exit.")