chore: enhance .gitignore and remove obsolete documentation files - Reorganize .gitignore with categorized sections for better maintainability - Add comprehensive ignore patterns for Python, Node.js, databases, logs, and build artifacts - Add project-specific ignore rules for coordinator, explorer, and deployment files - Remove outdated documentation: BITCOIN-WALLET-SETUP.md, LOCAL_ASSETS_SUMMARY.md, README-CONTAINER-DEPLOYMENT.md, README-DOMAIN-DEPLOYMENT.md ```
230 lines
6.4 KiB
Python
230 lines
6.4 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
FastAPI backend for the AITBC Trade Exchange
|
|
"""
|
|
|
|
from datetime import datetime, timedelta
|
|
from typing import List, Optional
|
|
from fastapi import FastAPI, Depends, HTTPException, status
|
|
from fastapi.middleware.cors import CORSMiddleware
|
|
from pydantic import BaseModel
|
|
from sqlalchemy import desc, func, and_
|
|
from sqlalchemy.orm import Session
|
|
|
|
from database import init_db, get_db_session
|
|
from models import User, Order, Trade, Balance
|
|
|
|
# Initialize FastAPI app
|
|
app = FastAPI(title="AITBC Trade Exchange API", version="1.0.0")
|
|
|
|
# Add CORS middleware
|
|
app.add_middleware(
|
|
CORSMiddleware,
|
|
allow_origins=["*"],
|
|
allow_credentials=True,
|
|
allow_methods=["*"],
|
|
allow_headers=["*"],
|
|
)
|
|
|
|
# Pydantic models
|
|
class OrderCreate(BaseModel):
|
|
order_type: str # 'BUY' or 'SELL'
|
|
amount: float
|
|
price: float
|
|
|
|
class OrderResponse(BaseModel):
|
|
id: int
|
|
order_type: str
|
|
amount: float
|
|
price: float
|
|
total: float
|
|
filled: float
|
|
remaining: float
|
|
status: str
|
|
created_at: datetime
|
|
|
|
class Config:
|
|
from_attributes = True
|
|
|
|
class TradeResponse(BaseModel):
|
|
id: int
|
|
amount: float
|
|
price: float
|
|
total: float
|
|
created_at: datetime
|
|
|
|
class Config:
|
|
from_attributes = True
|
|
|
|
class OrderBookResponse(BaseModel):
|
|
buys: List[OrderResponse]
|
|
sells: List[OrderResponse]
|
|
|
|
# Initialize database on startup
|
|
@app.on_event("startup")
|
|
async def startup_event():
|
|
init_db()
|
|
|
|
# Create mock data if database is empty
|
|
db = get_db_session()
|
|
try:
|
|
# Check if we have any trades
|
|
if db.query(Trade).count() == 0:
|
|
create_mock_trades(db)
|
|
finally:
|
|
db.close()
|
|
|
|
def create_mock_trades(db: Session):
|
|
"""Create some mock trades for demonstration"""
|
|
import random
|
|
|
|
# Create mock trades over the last hour
|
|
now = datetime.utcnow()
|
|
trades = []
|
|
|
|
for i in range(20):
|
|
# Generate random trade data
|
|
amount = random.uniform(10, 500)
|
|
price = random.uniform(0.000009, 0.000012)
|
|
total = amount * price
|
|
|
|
trade = Trade(
|
|
buyer_id=1, # Mock user ID
|
|
seller_id=2, # Mock user ID
|
|
order_id=1, # Mock order ID
|
|
amount=amount,
|
|
price=price,
|
|
total=total,
|
|
trade_hash=f"mock_tx_{i:04d}",
|
|
created_at=now - timedelta(minutes=random.randint(0, 60))
|
|
)
|
|
trades.append(trade)
|
|
|
|
db.add_all(trades)
|
|
db.commit()
|
|
print(f"Created {len(trades)} mock trades")
|
|
|
|
@app.get("/api/trades/recent", response_model=List[TradeResponse])
|
|
def get_recent_trades(limit: int = 20, db: Session = Depends(get_db_session)):
|
|
"""Get recent trades"""
|
|
trades = db.query(Trade).order_by(desc(Trade.created_at)).limit(limit).all()
|
|
return trades
|
|
|
|
@app.get("/api/orders/orderbook", response_model=OrderBookResponse)
|
|
def get_orderbook(db: Session = Depends(get_db_session)):
|
|
"""Get current order book"""
|
|
|
|
# Get open buy orders (sorted by price descending)
|
|
buys = db.query(Order).filter(
|
|
and_(Order.order_type == 'BUY', Order.status == 'OPEN')
|
|
).order_by(desc(Order.price)).limit(20).all()
|
|
|
|
# Get open sell orders (sorted by price ascending)
|
|
sells = db.query(Order).filter(
|
|
and_(Order.order_type == 'SELL', Order.status == 'OPEN')
|
|
).order_by(Order.price).limit(20).all()
|
|
|
|
return OrderBookResponse(buys=buys, sells=sells)
|
|
|
|
@app.post("/api/orders", response_model=OrderResponse)
|
|
def create_order(order: OrderCreate, db: Session = Depends(get_db_session)):
|
|
"""Create a new order"""
|
|
|
|
# Validate order type
|
|
if order.order_type not in ['BUY', 'SELL']:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_400_BAD_REQUEST,
|
|
detail="Order type must be 'BUY' or 'SELL'"
|
|
)
|
|
|
|
# Create order
|
|
total = order.amount * order.price
|
|
db_order = Order(
|
|
user_id=1, # TODO: Get from authentication
|
|
order_type=order.order_type,
|
|
amount=order.amount,
|
|
price=order.price,
|
|
total=total,
|
|
remaining=order.amount
|
|
)
|
|
|
|
db.add(db_order)
|
|
db.commit()
|
|
db.refresh(db_order)
|
|
|
|
# Try to match the order
|
|
try_match_order(db_order, db)
|
|
|
|
return db_order
|
|
|
|
def try_match_order(order: Order, db: Session):
|
|
"""Try to match an order with existing orders"""
|
|
|
|
if order.order_type == 'BUY':
|
|
# Match with sell orders at same or lower price
|
|
matching_orders = db.query(Order).filter(
|
|
and_(
|
|
Order.order_type == 'SELL',
|
|
Order.status == 'OPEN',
|
|
Order.price <= order.price
|
|
)
|
|
).order_by(Order.price).all()
|
|
else:
|
|
# Match with buy orders at same or higher price
|
|
matching_orders = db.query(Order).filter(
|
|
and_(
|
|
Order.order_type == 'BUY',
|
|
Order.status == 'OPEN',
|
|
Order.price >= order.price
|
|
)
|
|
).order_by(desc(Order.price)).all()
|
|
|
|
for match in matching_orders:
|
|
if order.remaining <= 0:
|
|
break
|
|
|
|
# Calculate trade amount
|
|
trade_amount = min(order.remaining, match.remaining)
|
|
trade_total = trade_amount * match.price
|
|
|
|
# Create trade record
|
|
trade = Trade(
|
|
buyer_id=order.user_id if order.order_type == 'BUY' else match.user_id,
|
|
seller_id=match.user_id if order.order_type == 'BUY' else order.user_id,
|
|
order_id=order.id,
|
|
amount=trade_amount,
|
|
price=match.price,
|
|
total=trade_total,
|
|
trade_hash=f"trade_{datetime.utcnow().timestamp()}"
|
|
)
|
|
|
|
db.add(trade)
|
|
|
|
# Update orders
|
|
order.filled += trade_amount
|
|
order.remaining -= trade_amount
|
|
match.filled += trade_amount
|
|
match.remaining -= trade_amount
|
|
|
|
# Update order statuses
|
|
if order.remaining <= 0:
|
|
order.status = 'FILLED'
|
|
else:
|
|
order.status = 'PARTIALLY_FILLED'
|
|
|
|
if match.remaining <= 0:
|
|
match.status = 'FILLED'
|
|
else:
|
|
match.status = 'PARTIALLY_FILLED'
|
|
|
|
db.commit()
|
|
|
|
@app.get("/api/health")
|
|
def health_check():
|
|
"""Health check endpoint"""
|
|
return {"status": "ok", "timestamp": datetime.utcnow()}
|
|
|
|
if __name__ == "__main__":
|
|
import uvicorn
|
|
uvicorn.run(app, host="0.0.0.0", port=3003)
|