Edoruin's picture
first commit
8294aff
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::*;
#[derive(Clone)]
pub struct AppState {
pub config: TradingConfig,
pub positions: Arc<Mutex<Vec<Position>>>,
pub orders: Arc<Mutex<Vec<Order>>>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
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,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
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>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
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>,
}
#[derive(Debug, Deserialize)]
pub struct CreateOrderRequest {
pub symbol: String,
pub side: String,
pub quantity: f64,
pub price: Option<f64>,
}
#[derive(Debug, Deserialize)]
pub struct ClosePositionRequest {
pub position_id: String,
}
#[derive(Debug, Serialize)]
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,
})
}
#[tokio::main]
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();
}