- Change file mode from 644 to 755 for all project files - Add chain_id parameter to get_balance RPC endpoint with default "ait-devnet" - Rename Miner.extra_meta_data to extra_metadata for consistency
325 lines
9.8 KiB
Python
Executable File
325 lines
9.8 KiB
Python
Executable File
"""
|
|
Production Exchange API Integration Service
|
|
Handles real exchange connections and trading operations
|
|
"""
|
|
|
|
import asyncio
|
|
import json
|
|
import logging
|
|
from datetime import datetime
|
|
from pathlib import Path
|
|
from typing import Dict, Any, List, Optional
|
|
import aiohttp
|
|
from fastapi import FastAPI, HTTPException
|
|
from pydantic import BaseModel
|
|
|
|
# Configure logging
|
|
logging.basicConfig(level=logging.INFO)
|
|
logger = logging.getLogger(__name__)
|
|
|
|
app = FastAPI(
|
|
title="AITBC Exchange Integration Service",
|
|
description="Production exchange API integration for AITBC trading",
|
|
version="1.0.0"
|
|
)
|
|
|
|
# Data models
|
|
class ExchangeRegistration(BaseModel):
|
|
name: str
|
|
api_key: str
|
|
sandbox: bool = True
|
|
description: Optional[str] = None
|
|
|
|
class TradingPair(BaseModel):
|
|
symbol: str
|
|
base_asset: str
|
|
quote_asset: str
|
|
min_order_size: float
|
|
price_precision: int
|
|
quantity_precision: int
|
|
|
|
class OrderRequest(BaseModel):
|
|
symbol: str
|
|
side: str # buy/sell
|
|
type: str # market/limit
|
|
quantity: float
|
|
price: Optional[float] = None
|
|
|
|
# In-memory storage (in production, use database)
|
|
exchanges: Dict[str, Dict] = {}
|
|
trading_pairs: Dict[str, Dict] = {}
|
|
orders: Dict[str, Dict] = {}
|
|
|
|
@app.get("/")
|
|
async def root():
|
|
return {
|
|
"service": "AITBC Exchange Integration",
|
|
"status": "running",
|
|
"timestamp": datetime.utcnow().isoformat(),
|
|
"version": "1.0.0"
|
|
}
|
|
|
|
@app.get("/health")
|
|
async def health_check():
|
|
return {
|
|
"status": "healthy",
|
|
"exchanges_connected": len([e for e in exchanges.values() if e.get("connected")]),
|
|
"active_pairs": len(trading_pairs),
|
|
"total_orders": len(orders)
|
|
}
|
|
|
|
@app.post("/api/v1/exchanges/register")
|
|
async def register_exchange(registration: ExchangeRegistration):
|
|
"""Register a new exchange connection"""
|
|
exchange_id = registration.name.lower()
|
|
|
|
if exchange_id in exchanges:
|
|
raise HTTPException(status_code=400, detail="Exchange already registered")
|
|
|
|
# Create exchange configuration
|
|
exchange_config = {
|
|
"exchange_id": exchange_id,
|
|
"name": registration.name,
|
|
"api_key": registration.api_key,
|
|
"sandbox": registration.sandbox,
|
|
"description": registration.description,
|
|
"connected": False,
|
|
"created_at": datetime.utcnow().isoformat(),
|
|
"last_sync": None,
|
|
"trading_pairs": []
|
|
}
|
|
|
|
exchanges[exchange_id] = exchange_config
|
|
|
|
logger.info(f"Exchange registered: {registration.name}")
|
|
|
|
return {
|
|
"exchange_id": exchange_id,
|
|
"status": "registered",
|
|
"name": registration.name,
|
|
"sandbox": registration.sandbox,
|
|
"created_at": exchange_config["created_at"]
|
|
}
|
|
|
|
@app.post("/api/v1/exchanges/{exchange_id}/connect")
|
|
async def connect_exchange(exchange_id: str):
|
|
"""Connect to a registered exchange"""
|
|
if exchange_id not in exchanges:
|
|
raise HTTPException(status_code=404, detail="Exchange not found")
|
|
|
|
exchange = exchanges[exchange_id]
|
|
|
|
if exchange["connected"]:
|
|
return {"status": "already_connected", "exchange_id": exchange_id}
|
|
|
|
# Simulate exchange connection
|
|
# In production, this would make actual API calls to the exchange
|
|
await asyncio.sleep(1) # Simulate connection delay
|
|
|
|
exchange["connected"] = True
|
|
exchange["last_sync"] = datetime.utcnow().isoformat()
|
|
|
|
logger.info(f"Exchange connected: {exchange_id}")
|
|
|
|
return {
|
|
"exchange_id": exchange_id,
|
|
"status": "connected",
|
|
"connected_at": exchange["last_sync"]
|
|
}
|
|
|
|
@app.post("/api/v1/pairs/create")
|
|
async def create_trading_pair(pair: TradingPair):
|
|
"""Create a new trading pair"""
|
|
pair_id = f"{pair.symbol.lower()}"
|
|
|
|
if pair_id in trading_pairs:
|
|
raise HTTPException(status_code=400, detail="Trading pair already exists")
|
|
|
|
# Create trading pair configuration
|
|
pair_config = {
|
|
"pair_id": pair_id,
|
|
"symbol": pair.symbol,
|
|
"base_asset": pair.base_asset,
|
|
"quote_asset": pair.quote_asset,
|
|
"min_order_size": pair.min_order_size,
|
|
"price_precision": pair.price_precision,
|
|
"quantity_precision": pair.quantity_precision,
|
|
"status": "active",
|
|
"created_at": datetime.utcnow().isoformat(),
|
|
"current_price": None,
|
|
"volume_24h": 0.0,
|
|
"orders": []
|
|
}
|
|
|
|
trading_pairs[pair_id] = pair_config
|
|
|
|
logger.info(f"Trading pair created: {pair.symbol}")
|
|
|
|
return {
|
|
"pair_id": pair_id,
|
|
"symbol": pair.symbol,
|
|
"status": "created",
|
|
"created_at": pair_config["created_at"]
|
|
}
|
|
|
|
@app.get("/api/v1/pairs")
|
|
async def list_trading_pairs():
|
|
"""List all trading pairs"""
|
|
return {
|
|
"pairs": list(trading_pairs.values()),
|
|
"total_pairs": len(trading_pairs)
|
|
}
|
|
|
|
@app.get("/api/v1/pairs/{pair_id}")
|
|
async def get_trading_pair(pair_id: str):
|
|
"""Get specific trading pair information"""
|
|
if pair_id not in trading_pairs:
|
|
raise HTTPException(status_code=404, detail="Trading pair not found")
|
|
|
|
return trading_pairs[pair_id]
|
|
|
|
@app.post("/api/v1/orders")
|
|
async def create_order(order: OrderRequest):
|
|
"""Create a new trading order"""
|
|
pair_id = order.symbol.lower()
|
|
|
|
if pair_id not in trading_pairs:
|
|
raise HTTPException(status_code=404, detail="Trading pair not found")
|
|
|
|
# Generate order ID
|
|
order_id = f"order_{int(datetime.utcnow().timestamp())}"
|
|
|
|
# Create order
|
|
order_data = {
|
|
"order_id": order_id,
|
|
"symbol": order.symbol,
|
|
"side": order.side,
|
|
"type": order.type,
|
|
"quantity": order.quantity,
|
|
"price": order.price,
|
|
"status": "submitted",
|
|
"created_at": datetime.utcnow().isoformat(),
|
|
"filled_quantity": 0.0,
|
|
"remaining_quantity": order.quantity,
|
|
"average_price": None
|
|
}
|
|
|
|
orders[order_id] = order_data
|
|
|
|
# Add to trading pair
|
|
trading_pairs[pair_id]["orders"].append(order_id)
|
|
|
|
# Simulate order processing
|
|
await asyncio.sleep(0.5) # Simulate processing delay
|
|
|
|
# Mark as filled (for demo)
|
|
order_data["status"] = "filled"
|
|
order_data["filled_quantity"] = order.quantity
|
|
order_data["remaining_quantity"] = 0.0
|
|
order_data["average_price"] = order.price or 0.00001 # Default price for demo
|
|
order_data["filled_at"] = datetime.utcnow().isoformat()
|
|
|
|
logger.info(f"Order created and filled: {order_id}")
|
|
|
|
return order_data
|
|
|
|
@app.get("/api/v1/orders")
|
|
async def list_orders():
|
|
"""List all orders"""
|
|
return {
|
|
"orders": list(orders.values()),
|
|
"total_orders": len(orders)
|
|
}
|
|
|
|
@app.get("/api/v1/orders/{order_id}")
|
|
async def get_order(order_id: str):
|
|
"""Get specific order information"""
|
|
if order_id not in orders:
|
|
raise HTTPException(status_code=404, detail="Order not found")
|
|
|
|
return orders[order_id]
|
|
|
|
@app.get("/api/v1/exchanges")
|
|
async def list_exchanges():
|
|
"""List all registered exchanges"""
|
|
return {
|
|
"exchanges": list(exchanges.values()),
|
|
"total_exchanges": len(exchanges)
|
|
}
|
|
|
|
@app.get("/api/v1/exchanges/{exchange_id}")
|
|
async def get_exchange(exchange_id: str):
|
|
"""Get specific exchange information"""
|
|
if exchange_id not in exchanges:
|
|
raise HTTPException(status_code=404, detail="Exchange not found")
|
|
|
|
return exchanges[exchange_id]
|
|
|
|
@app.post("/api/v1/market-data/{pair_id}/price")
|
|
async def update_market_price(pair_id: str, price_data: Dict[str, Any]):
|
|
"""Update market price for a trading pair"""
|
|
if pair_id not in trading_pairs:
|
|
raise HTTPException(status_code=404, detail="Trading pair not found")
|
|
|
|
pair = trading_pairs[pair_id]
|
|
pair["current_price"] = price_data.get("price")
|
|
pair["volume_24h"] = price_data.get("volume", pair["volume_24h"])
|
|
pair["last_price_update"] = datetime.utcnow().isoformat()
|
|
|
|
return {
|
|
"pair_id": pair_id,
|
|
"current_price": pair["current_price"],
|
|
"updated_at": pair["last_price_update"]
|
|
}
|
|
|
|
@app.get("/api/v1/market-data")
|
|
async def get_market_data():
|
|
"""Get market data for all pairs"""
|
|
market_data = {}
|
|
for pair_id, pair in trading_pairs.items():
|
|
market_data[pair_id] = {
|
|
"symbol": pair["symbol"],
|
|
"current_price": pair.get("current_price"),
|
|
"volume_24h": pair.get("volume_24h"),
|
|
"last_update": pair.get("last_price_update")
|
|
}
|
|
|
|
return {
|
|
"market_data": market_data,
|
|
"total_pairs": len(market_data),
|
|
"generated_at": datetime.utcnow().isoformat()
|
|
}
|
|
|
|
# Background task for simulating market data
|
|
async def simulate_market_data():
|
|
"""Background task to simulate market data updates"""
|
|
while True:
|
|
await asyncio.sleep(30) # Update every 30 seconds
|
|
|
|
for pair_id, pair in trading_pairs.items():
|
|
if pair["status"] == "active":
|
|
# Simulate price changes
|
|
import random
|
|
base_price = 0.00001 # Base price for AITBC
|
|
variation = random.uniform(-0.02, 0.02) # ±2% variation
|
|
new_price = round(base_price * (1 + variation), 8)
|
|
|
|
pair["current_price"] = new_price
|
|
pair["volume_24h"] += random.uniform(100, 1000)
|
|
pair["last_price_update"] = datetime.utcnow().isoformat()
|
|
|
|
# Start background task on startup
|
|
@app.on_event("startup")
|
|
async def startup_event():
|
|
logger.info("Starting AITBC Exchange Integration Service")
|
|
# Start background market data simulation
|
|
asyncio.create_task(simulate_market_data())
|
|
|
|
@app.on_event("shutdown")
|
|
async def shutdown_event():
|
|
logger.info("Shutting down AITBC Exchange Integration Service")
|
|
|
|
if __name__ == "__main__":
|
|
import uvicorn
|
|
uvicorn.run(app, host="0.0.0.0", port=8010, log_level="info")
|