Initial commit: Gold Trading Simulator with AI-powered analysis
This commit is contained in:
@@ -0,0 +1,133 @@
|
||||
from fastapi import APIRouter, HTTPException
|
||||
from typing import Dict
|
||||
from datetime import datetime, timezone
|
||||
|
||||
from app.services.risk import validate_order
|
||||
|
||||
router = APIRouter(prefix="/trading", tags=["Trading"])
|
||||
|
||||
|
||||
# In-memory simulation state (for MVP - will use DB in future)
|
||||
simulation_state = {
|
||||
"cash": 100000.0,
|
||||
"initial_capital": 100000.0,
|
||||
"position": None,
|
||||
"trades": [],
|
||||
"equity_history": [], # list of {time: epoch_sec, equity: float}
|
||||
}
|
||||
|
||||
|
||||
def _compute_equity_at_price(price: float) -> float:
|
||||
pos = simulation_state.get("position")
|
||||
qty = pos["quantity"] if pos else 0.0
|
||||
return float(simulation_state.get("cash", 0.0) + qty * price)
|
||||
|
||||
|
||||
@router.post("/execute")
|
||||
async def execute_trade(trade: Dict):
|
||||
"""
|
||||
Execute a trade in the simulation.
|
||||
|
||||
- Validates simple risk rules (position cap, anti-stacking)
|
||||
- Updates cash/position
|
||||
- Records trade with timestamp
|
||||
- Appends equity snapshot after execution
|
||||
"""
|
||||
try:
|
||||
action = trade.get("action")
|
||||
quantity = float(trade.get("quantity")) if trade.get("quantity") is not None else None
|
||||
price = float(trade.get("price")) if trade.get("price") is not None else None
|
||||
|
||||
if not all([action, quantity is not None, price is not None]):
|
||||
raise HTTPException(status_code=400, detail="Missing required fields")
|
||||
|
||||
# Risk validation prior to execution
|
||||
try:
|
||||
validate_order(simulation_state, action, quantity, price)
|
||||
except ValueError as ve:
|
||||
raise HTTPException(status_code=400, detail=str(ve))
|
||||
|
||||
total = quantity * price
|
||||
|
||||
if action == "BUY":
|
||||
if total > simulation_state["cash"]:
|
||||
raise HTTPException(status_code=400, detail="Insufficient funds")
|
||||
|
||||
simulation_state["cash"] -= total
|
||||
|
||||
if simulation_state["position"] is None:
|
||||
simulation_state["position"] = {
|
||||
"symbol": "XAU/USD",
|
||||
"quantity": quantity,
|
||||
"avg_price": price,
|
||||
}
|
||||
else:
|
||||
# Update average price for additional buy
|
||||
pos = simulation_state["position"]
|
||||
new_qty = pos["quantity"] + quantity
|
||||
new_avg = (
|
||||
pos["avg_price"] * pos["quantity"] + price * quantity
|
||||
) / new_qty
|
||||
pos["quantity"] = new_qty
|
||||
pos["avg_price"] = new_avg
|
||||
|
||||
elif action == "SELL":
|
||||
if (
|
||||
simulation_state["position"] is None
|
||||
or quantity > simulation_state["position"]["quantity"]
|
||||
):
|
||||
raise HTTPException(status_code=400, detail="Insufficient position")
|
||||
|
||||
simulation_state["cash"] += total
|
||||
pnl = (price - simulation_state["position"]["avg_price"]) * quantity
|
||||
|
||||
simulation_state["position"]["quantity"] -= quantity
|
||||
|
||||
if simulation_state["position"]["quantity"] == 0:
|
||||
simulation_state["position"] = None
|
||||
|
||||
trade["pnl"] = pnl
|
||||
else:
|
||||
raise HTTPException(status_code=400, detail="Unsupported action")
|
||||
|
||||
now_ts = int(datetime.now(timezone.utc).timestamp())
|
||||
trade["id"] = len(simulation_state["trades"]) + 1
|
||||
trade["timestamp"] = now_ts
|
||||
simulation_state["trades"].append(trade)
|
||||
|
||||
# Append equity snapshot post trade using trade price
|
||||
equity = _compute_equity_at_price(price)
|
||||
simulation_state["equity_history"].append({"time": now_ts, "equity": equity})
|
||||
|
||||
return trade
|
||||
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
||||
|
||||
@router.get("/portfolio")
|
||||
async def get_portfolio():
|
||||
"""Get current portfolio state"""
|
||||
return simulation_state
|
||||
|
||||
|
||||
@router.post("/reset")
|
||||
async def reset_simulation():
|
||||
"""Reset simulation to initial state"""
|
||||
global simulation_state
|
||||
simulation_state = {
|
||||
"cash": 100000.0,
|
||||
"initial_capital": 100000.0,
|
||||
"position": None,
|
||||
"trades": [],
|
||||
"equity_history": [],
|
||||
}
|
||||
return {"message": "Simulation reset successfully"}
|
||||
|
||||
|
||||
@router.get("/history")
|
||||
async def get_trade_history():
|
||||
"""Get trade history"""
|
||||
return simulation_state["trades"]
|
||||
Reference in New Issue
Block a user