0% found this document useful (0 votes)
55 views17 pages

Deltacode Ajay 2025

The document outlines the implementation of a trading bot using the Delta Exchange API, featuring robust error handling, rate limiting, and position management. It includes classes for API interaction, configuration settings for risk management and trading criteria, and methods for processing orders, monitoring positions, and calculating technical indicators. The bot is designed to operate efficiently with logging for tracking performance and issues.

Uploaded by

ajayrao
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as TXT, PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
55 views17 pages

Deltacode Ajay 2025

The document outlines the implementation of a trading bot using the Delta Exchange API, featuring robust error handling, rate limiting, and position management. It includes classes for API interaction, configuration settings for risk management and trading criteria, and methods for processing orders, monitoring positions, and calculating technical indicators. The bot is designed to operate efficiently with logging for tracking performance and issues.

Uploaded by

ajayrao
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as TXT, PDF, TXT or read online on Scribd
You are on page 1/ 17

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.")

You might also like