Spaces:
Runtime error
Runtime error
| use axum::{ | |
| routing::{get, post}, | |
| Router, Json, extract::State, | |
| }; | |
| use serde::{Deserialize, Serialize}; | |
| use std::sync::Arc; | |
| use tokio::sync::Mutex; | |
| use chrono::{DateTime, Utc}; | |
| mod trading; | |
| mod binance; | |
| pub use trading::*; | |
| pub use binance::*; | |
| pub struct AppState { | |
| pub config: TradingConfig, | |
| pub positions: Arc<Mutex<Vec<Position>>>, | |
| pub orders: Arc<Mutex<Vec<Order>>>, | |
| } | |
| pub struct TradingConfig { | |
| pub api_key: String, | |
| pub api_secret: String, | |
| pub testnet: bool, | |
| pub max_position_size: f64, | |
| pub stop_loss_percent: f64, | |
| pub take_profit_percent: f64, | |
| } | |
| pub struct Position { | |
| pub id: String, | |
| pub symbol: String, | |
| pub side: String, | |
| pub quantity: f64, | |
| pub entry_price: f64, | |
| pub current_price: f64, | |
| pub pnl: f64, | |
| pub opened_at: DateTime<Utc>, | |
| } | |
| pub struct Order { | |
| pub id: String, | |
| pub symbol: String, | |
| pub side: String, | |
| pub order_type: String, | |
| pub quantity: f64, | |
| pub price: Option<f64>, | |
| pub status: String, | |
| pub created_at: DateTime<Utc>, | |
| } | |
| pub struct CreateOrderRequest { | |
| pub symbol: String, | |
| pub side: String, | |
| pub quantity: f64, | |
| pub price: Option<f64>, | |
| } | |
| pub struct ClosePositionRequest { | |
| pub position_id: String, | |
| } | |
| pub struct ApiResponse<T> { | |
| pub success: bool, | |
| pub data: Option<T>, | |
| pub error: Option<String>, | |
| } | |
| async fn health() -> Json<ApiResponse<String>> { | |
| Json(ApiResponse { | |
| success: true, | |
| data: Some("RapidLiveClient API running".to_string()), | |
| error: None, | |
| }) | |
| } | |
| async fn get_balance(State(state): State<AppState>) -> Json<ApiResponse<f64>> { | |
| let balance = state.config.testnet as i32 as f64 * 10000.0; | |
| Json(ApiResponse { | |
| success: true, | |
| data: Some(balance), | |
| error: None, | |
| }) | |
| } | |
| async fn get_positions(State(state): State<AppState>) -> Json<ApiResponse<Vec<Position>>> { | |
| let positions = state.positions.lock().await.clone(); | |
| Json(ApiResponse { | |
| success: true, | |
| data: Some(positions), | |
| error: None, | |
| }) | |
| } | |
| async fn get_orders(State(state): State<AppState>) -> Json<ApiResponse<Vec<Order>>> { | |
| let orders = state.orders.lock().await.clone(); | |
| Json(ApiResponse { | |
| success: true, | |
| data: Some(orders), | |
| error: None, | |
| }) | |
| } | |
| async fn create_order( | |
| State(state): State<AppState>, | |
| Json(req): Json<CreateOrderRequest>, | |
| ) -> Json<ApiResponse<Order>> { | |
| let order = Order { | |
| id: format!("ORD-{}", chrono::Utc::now().timestamp_millis()), | |
| symbol: req.symbol.clone(), | |
| side: req.side.clone(), | |
| order_type: if req.price.is_some() { "LIMIT".to_string() } else { "MARKET".to_string() }, | |
| quantity: req.quantity, | |
| price: req.price, | |
| status: "pending".to_string(), | |
| created_at: Utc::now(), | |
| }; | |
| state.orders.lock().await.push(order.clone()); | |
| Json(ApiResponse { | |
| success: true, | |
| data: Some(order), | |
| error: None, | |
| }) | |
| } | |
| async fn close_position( | |
| State(state): State<AppState>, | |
| Json(req): Json<ClosePositionRequest>, | |
| ) -> Json<ApiResponse<Position>> { | |
| let mut positions = state.positions.lock().await; | |
| if let Some(pos) = positions.iter_mut().find(|p| p.id == req.position_id) { | |
| let current_price = pos.current_price; | |
| let pnl = if pos.side == "BUY" { | |
| (current_price - pos.entry_price) * pos.quantity | |
| } else { | |
| (pos.entry_price - current_price) * pos.quantity | |
| }; | |
| pos.pnl = pnl; | |
| Json(ApiResponse { | |
| success: true, | |
| data: Some(pos.clone()), | |
| error: None, | |
| }) | |
| } else { | |
| Json(ApiResponse { | |
| success: false, | |
| data: None, | |
| error: Some("Position not found".to_string()), | |
| }) | |
| } | |
| } | |
| async fn analyze_market( | |
| State(state): State<AppState>, | |
| Json(req): Json<serde_json::Value>, | |
| ) -> Json<ApiResponse<serde_json::Value>> { | |
| let symbol = req.get("symbol") | |
| .and_then(|s| s.as_str()) | |
| .unwrap_or("BTCUSDT"); | |
| let analysis = serde_json::json!({ | |
| "symbol": symbol, | |
| "recommendation": "BUY", | |
| "confidence": 0.75, | |
| "reason": "RSI oversold, trend bullish", | |
| "entry_price": 50000.0, | |
| "stop_loss": 47500.0, | |
| "take_profit": 55000.0, | |
| "risk_level": "MEDIUM" | |
| }); | |
| Json(ApiResponse { | |
| success: true, | |
| data: Some(analysis), | |
| error: None, | |
| }) | |
| } | |
| async fn main() { | |
| tracing_subscriber::fmt::init(); | |
| let config = TradingConfig { | |
| api_key: std::env::var("BINANCE_API_KEY").unwrap_or_default(), | |
| api_secret: std::env::var("BINANCE_API_SECRET").unwrap_or_default(), | |
| testnet: true, | |
| max_position_size: 1000.0, | |
| stop_loss_percent: 5.0, | |
| take_profit_percent: 10.0, | |
| }; | |
| let state = AppState { | |
| config, | |
| positions: Arc::new(Mutex::new(Vec::new())), | |
| orders: Arc::new(Mutex::new(Vec::new())), | |
| }; | |
| let app = Router::new() | |
| .route("/", get(health)) | |
| .route("/health", get(health)) | |
| .route("/api/balance", get(get_balance)) | |
| .route("/api/positions", get(get_positions)) | |
| .route("/api/orders", get(get_orders)) | |
| .route("/api/orders", post(create_order)) | |
| .route("/api/positions/close", post(close_position)) | |
| .route("/api/analyze", post(analyze_market)) | |
| .with_state(state); | |
| let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap(); | |
| tracing::info!("RapidLiveClient API running on http://0.0.0.0:3000"); | |
| axum::serve(listener, app).await.unwrap(); | |
| } | |