Spaces:
Runtime error
Runtime error
first commit
Browse files- .gitattributes +0 -35
- Dockerfile +23 -0
- README.md +94 -6
- rust-api/Cargo.lock +0 -0
- rust-api/Cargo.toml +25 -0
- rust-api/src/binance.rs +97 -0
- rust-api/src/main.rs +227 -0
- rust-api/src/trading.rs +68 -0
.gitattributes
DELETED
|
@@ -1,35 +0,0 @@
|
|
| 1 |
-
*.7z filter=lfs diff=lfs merge=lfs -text
|
| 2 |
-
*.arrow filter=lfs diff=lfs merge=lfs -text
|
| 3 |
-
*.bin filter=lfs diff=lfs merge=lfs -text
|
| 4 |
-
*.bz2 filter=lfs diff=lfs merge=lfs -text
|
| 5 |
-
*.ckpt filter=lfs diff=lfs merge=lfs -text
|
| 6 |
-
*.ftz filter=lfs diff=lfs merge=lfs -text
|
| 7 |
-
*.gz filter=lfs diff=lfs merge=lfs -text
|
| 8 |
-
*.h5 filter=lfs diff=lfs merge=lfs -text
|
| 9 |
-
*.joblib filter=lfs diff=lfs merge=lfs -text
|
| 10 |
-
*.lfs.* filter=lfs diff=lfs merge=lfs -text
|
| 11 |
-
*.mlmodel filter=lfs diff=lfs merge=lfs -text
|
| 12 |
-
*.model filter=lfs diff=lfs merge=lfs -text
|
| 13 |
-
*.msgpack filter=lfs diff=lfs merge=lfs -text
|
| 14 |
-
*.npy filter=lfs diff=lfs merge=lfs -text
|
| 15 |
-
*.npz filter=lfs diff=lfs merge=lfs -text
|
| 16 |
-
*.onnx filter=lfs diff=lfs merge=lfs -text
|
| 17 |
-
*.ot filter=lfs diff=lfs merge=lfs -text
|
| 18 |
-
*.parquet filter=lfs diff=lfs merge=lfs -text
|
| 19 |
-
*.pb filter=lfs diff=lfs merge=lfs -text
|
| 20 |
-
*.pickle filter=lfs diff=lfs merge=lfs -text
|
| 21 |
-
*.pkl filter=lfs diff=lfs merge=lfs -text
|
| 22 |
-
*.pt filter=lfs diff=lfs merge=lfs -text
|
| 23 |
-
*.pth filter=lfs diff=lfs merge=lfs -text
|
| 24 |
-
*.rar filter=lfs diff=lfs merge=lfs -text
|
| 25 |
-
*.safetensors filter=lfs diff=lfs merge=lfs -text
|
| 26 |
-
saved_model/**/* filter=lfs diff=lfs merge=lfs -text
|
| 27 |
-
*.tar.* filter=lfs diff=lfs merge=lfs -text
|
| 28 |
-
*.tar filter=lfs diff=lfs merge=lfs -text
|
| 29 |
-
*.tflite filter=lfs diff=lfs merge=lfs -text
|
| 30 |
-
*.tgz filter=lfs diff=lfs merge=lfs -text
|
| 31 |
-
*.wasm filter=lfs diff=lfs merge=lfs -text
|
| 32 |
-
*.xz filter=lfs diff=lfs merge=lfs -text
|
| 33 |
-
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
-
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
-
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Dockerfile
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# syntax=docker/dockerfile:1
|
| 2 |
+
FROM rust:1.75 as builder
|
| 3 |
+
|
| 4 |
+
WORKDIR /app
|
| 5 |
+
|
| 6 |
+
RUN apt-get update && apt-get install -y pkg-config libssl-dev
|
| 7 |
+
|
| 8 |
+
COPY rust-api/Cargo.toml rust-api/Cargo.lock ./
|
| 9 |
+
RUN cargo build --release
|
| 10 |
+
|
| 11 |
+
FROM debian:bookworm-slim
|
| 12 |
+
|
| 13 |
+
RUN apt-get update && apt-get install -y \
|
| 14 |
+
ca-certificates \
|
| 15 |
+
curl \
|
| 16 |
+
libssl3 \
|
| 17 |
+
&& rm -rf /var/lib/apt/lists/*
|
| 18 |
+
|
| 19 |
+
COPY --from=builder /app/target/release/rapid-live-client /usr/local/bin/
|
| 20 |
+
|
| 21 |
+
EXPOSE 3000
|
| 22 |
+
|
| 23 |
+
CMD ["rapid-live-client"]
|
README.md
CHANGED
|
@@ -1,12 +1,100 @@
|
|
| 1 |
---
|
| 2 |
title: RapidLiveClient
|
| 3 |
-
emoji:
|
| 4 |
-
colorFrom:
|
| 5 |
-
colorTo:
|
| 6 |
sdk: docker
|
|
|
|
| 7 |
pinned: false
|
| 8 |
-
license: cc-by-nc-4.0
|
| 9 |
-
short_description: The live trading AI for RapidQuant microservice tool
|
| 10 |
---
|
| 11 |
|
| 12 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
---
|
| 2 |
title: RapidLiveClient
|
| 3 |
+
emoji: 📈
|
| 4 |
+
colorFrom: red
|
| 5 |
+
colorTo: green
|
| 6 |
sdk: docker
|
| 7 |
+
app_port: 3000
|
| 8 |
pinned: false
|
|
|
|
|
|
|
| 9 |
---
|
| 10 |
|
| 11 |
+
# 📈 RapidLiveClient - Trading API
|
| 12 |
+
|
| 13 |
+
API de trading en vivo con Rust + FastAPI. Conectado a Binance.
|
| 14 |
+
|
| 15 |
+
## ⚠️ Aviso非常重要
|
| 16 |
+
|
| 17 |
+
Este es un proyecto de trading algorítmico.
|
| 18 |
+
- Usar solo en **testnet** inicialmente
|
| 19 |
+
- No invertir más de lo que puedas perder
|
| 20 |
+
- Los resultados pasados no garantizan resultados futuros
|
| 21 |
+
|
| 22 |
+
## 📡 Endpoints
|
| 23 |
+
|
| 24 |
+
| Método | Endpoint | Descripción |
|
| 25 |
+
|--------|----------|-------------|
|
| 26 |
+
| GET | `/` | Estado del servicio |
|
| 27 |
+
| GET | `/health` | Health check |
|
| 28 |
+
| GET | `/api/balance` | Balance de cuenta |
|
| 29 |
+
| GET | `/api/positions` | Posiciones abiertas |
|
| 30 |
+
| GET | `/api/orders` | Historial de órdenes |
|
| 31 |
+
| POST | `/api/orders` | Crear orden |
|
| 32 |
+
| POST | `/api/positions/close` | Cerrar posición |
|
| 33 |
+
| POST | `/api/analyze` | Análisis de mercado |
|
| 34 |
+
|
| 35 |
+
## 📥 Uso
|
| 36 |
+
|
| 37 |
+
### Obtener Balance
|
| 38 |
+
```bash
|
| 39 |
+
curl https://tu-usuario-RapidLiveClient.huggingface.space/api/balance
|
| 40 |
+
```
|
| 41 |
+
|
| 42 |
+
### Crear Orden
|
| 43 |
+
```bash
|
| 44 |
+
curl -X POST https://tu-usuario-RapidLiveClient.huggingface.space/api/orders \
|
| 45 |
+
-H "Content-Type: application/json" \
|
| 46 |
+
-d '{"symbol": "BTCUSDT", "side": "BUY", "quantity": 0.01}'
|
| 47 |
+
```
|
| 48 |
+
|
| 49 |
+
### Analizar Mercado
|
| 50 |
+
```bash
|
| 51 |
+
curl -X POST https://tu-usuario-RapidLiveClient.huggingface.space/api/analyze \
|
| 52 |
+
-H "Content-Type: application/json" \
|
| 53 |
+
-d '{"symbol": "BTCUSDT"}'
|
| 54 |
+
```
|
| 55 |
+
|
| 56 |
+
## 🔧 Configuración
|
| 57 |
+
|
| 58 |
+
### Variables de Entorno (Repository secrets)
|
| 59 |
+
- `BINANCE_API_KEY`: Tu API key de Binance
|
| 60 |
+
- `BINANCE_API_SECRET`: Tu API secret de Binance
|
| 61 |
+
|
| 62 |
+
## 🔍 Verificar Conexión (Terminal)
|
| 63 |
+
|
| 64 |
+
```bash
|
| 65 |
+
# Estado del servicio
|
| 66 |
+
curl https://tu-usuario-RapidLiveClient.huggingface.space/
|
| 67 |
+
|
| 68 |
+
# Health check
|
| 69 |
+
curl https://tu-usuario-RapidLiveClient.huggingface.space/health
|
| 70 |
+
|
| 71 |
+
# Ver balance
|
| 72 |
+
curl https://tu-usuario-RapidLiveClient.huggingface.space/api/balance
|
| 73 |
+
|
| 74 |
+
# Ver posiciones
|
| 75 |
+
curl https://tu-usuario-RapidLiveClient.huggingface.space/api/positions
|
| 76 |
+
```
|
| 77 |
+
|
| 78 |
+
## 📊 Estrategia
|
| 79 |
+
|
| 80 |
+
El agente usa:
|
| 81 |
+
- **RSI** para sobrecompra/sobreventa
|
| 82 |
+
- **Medias móviles** para tendencia
|
| 83 |
+
- **Stop loss** configurable (5%)
|
| 84 |
+
- **Take profit** automático (10%)
|
| 85 |
+
|
| 86 |
+
## 🔗 Consumido por
|
| 87 |
+
|
| 88 |
+
- **RapidQuant** - Frontend principal
|
| 89 |
+
|
| 90 |
+
## 🚀 Despliegue
|
| 91 |
+
|
| 92 |
+
1. Crea el Space en HuggingFace
|
| 93 |
+
2. Configura tus API keys de Binance en secrets
|
| 94 |
+
3. Despliega
|
| 95 |
+
|
| 96 |
+
### Build local
|
| 97 |
+
```bash
|
| 98 |
+
docker build -t rapid-live-client .
|
| 99 |
+
docker run -p 3000:3000 -e BINANCE_API_KEY=xxx -e BINANCE_API_SECRET=xxx rapid-live-client
|
| 100 |
+
```
|
rust-api/Cargo.lock
ADDED
|
File without changes
|
rust-api/Cargo.toml
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
[package]
|
| 2 |
+
name = "rapid-live-client"
|
| 3 |
+
version = "0.1.0"
|
| 4 |
+
edition = "2021"
|
| 5 |
+
|
| 6 |
+
[dependencies]
|
| 7 |
+
axum = "0.7"
|
| 8 |
+
tokio = { version = "1", features = ["full"] }
|
| 9 |
+
serde = { version = "1", features = ["derive"] }
|
| 10 |
+
serde_json = "1"
|
| 11 |
+
reqwest = { version = "0.11", features = ["json"] }
|
| 12 |
+
rust_decimal = { version = "1", features = ["serde"] }
|
| 13 |
+
chrono = { version = "0.4", features = ["serde"] }
|
| 14 |
+
tracing = "0.1"
|
| 15 |
+
tracing-subscriber = "0.3"
|
| 16 |
+
anyhow = "1"
|
| 17 |
+
thiserror = "1"
|
| 18 |
+
|
| 19 |
+
[dependencies.nautilus]
|
| 20 |
+
version = "0.3"
|
| 21 |
+
features = ["rustpython", "external-api"]
|
| 22 |
+
|
| 23 |
+
[features]
|
| 24 |
+
default = []
|
| 25 |
+
binance = ["nautilus/binance"]
|
rust-api/src/binance.rs
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
// Binance API module
|
| 2 |
+
// Integration with Binance for live trading
|
| 3 |
+
|
| 4 |
+
use serde::{Deserialize, Serialize};
|
| 5 |
+
|
| 6 |
+
#[derive(Debug, Clone, Serialize, Deserialize)]
|
| 7 |
+
pub struct BinanceConfig {
|
| 8 |
+
pub api_key: String,
|
| 9 |
+
pub api_secret: String,
|
| 10 |
+
pub testnet: bool,
|
| 11 |
+
}
|
| 12 |
+
|
| 13 |
+
impl BinanceConfig {
|
| 14 |
+
pub fn base_url(&self) -> &str {
|
| 15 |
+
if self.testnet {
|
| 16 |
+
"https://testnet.binance.vision/api"
|
| 17 |
+
} else {
|
| 18 |
+
"https://api.binance.com/api"
|
| 19 |
+
}
|
| 20 |
+
}
|
| 21 |
+
}
|
| 22 |
+
|
| 23 |
+
#[derive(Debug, Clone, Serialize, Deserialize)]
|
| 24 |
+
pub struct BinanceKline {
|
| 25 |
+
pub open_time: i64,
|
| 26 |
+
pub open: f64,
|
| 27 |
+
pub high: f64,
|
| 28 |
+
pub low: f64,
|
| 29 |
+
pub close: f64,
|
| 30 |
+
pub volume: f64,
|
| 31 |
+
pub close_time: i64,
|
| 32 |
+
}
|
| 33 |
+
|
| 34 |
+
#[derive(Debug, Clone, Serialize, Deserialize)]
|
| 35 |
+
pub struct BinanceTicker {
|
| 36 |
+
pub symbol: String,
|
| 37 |
+
pub last_price: f64,
|
| 38 |
+
pub price_change_percent: f64,
|
| 39 |
+
pub volume: f64,
|
| 40 |
+
}
|
| 41 |
+
|
| 42 |
+
pub struct BinanceClient {
|
| 43 |
+
config: BinanceConfig,
|
| 44 |
+
client: reqwest::Client,
|
| 45 |
+
}
|
| 46 |
+
|
| 47 |
+
impl BinanceClient {
|
| 48 |
+
pub fn new(config: BinanceConfig) -> Self {
|
| 49 |
+
Self {
|
| 50 |
+
config,
|
| 51 |
+
client: reqwest::Client::new(),
|
| 52 |
+
}
|
| 53 |
+
}
|
| 54 |
+
|
| 55 |
+
pub async fn get_klines(&self, symbol: &str, interval: &str, limit: usize) -> Result<Vec<BinanceKline>, Box<dyn std::error::Error + Send + Sync>> {
|
| 56 |
+
let url = format!("{}/v3/klines", self.config.base_url());
|
| 57 |
+
let response = self.client.get(&url)
|
| 58 |
+
.query(&[
|
| 59 |
+
("symbol", symbol),
|
| 60 |
+
("interval", interval),
|
| 61 |
+
("limit", &limit.to_string()),
|
| 62 |
+
])
|
| 63 |
+
.send()
|
| 64 |
+
.await?;
|
| 65 |
+
|
| 66 |
+
let data: Vec<Vec<serde_json::Value>> = response.json().await?;
|
| 67 |
+
|
| 68 |
+
let klines: Vec<BinanceKline> = data.iter().map(|k| BinanceKline {
|
| 69 |
+
open_time: k[0].as_i64().unwrap_or(0),
|
| 70 |
+
open: k[1].as_str().unwrap_or("0").parse().unwrap_or(0.0),
|
| 71 |
+
high: k[2].as_str().unwrap_or("0").parse().unwrap_or(0.0),
|
| 72 |
+
low: k[3].as_str().unwrap_or("0").parse().unwrap_or(0.0),
|
| 73 |
+
close: k[4].as_str().unwrap_or("0").parse().unwrap_or(0.0),
|
| 74 |
+
volume: k[5].as_str().unwrap_or("0").parse().unwrap_or(0.0),
|
| 75 |
+
close_time: k[6].as_i64().unwrap_or(0),
|
| 76 |
+
}).collect();
|
| 77 |
+
|
| 78 |
+
Ok(klines)
|
| 79 |
+
}
|
| 80 |
+
|
| 81 |
+
pub async fn get_ticker(&self, symbol: &str) -> Result<BinanceTicker, Box<dyn std::error::Error + Send + Sync>> {
|
| 82 |
+
let url = format!("{}/v3/ticker/24hr", self.config.base_url());
|
| 83 |
+
let response = self.client.get(&url)
|
| 84 |
+
.query(&[("symbol", symbol)])
|
| 85 |
+
.send()
|
| 86 |
+
.await?;
|
| 87 |
+
|
| 88 |
+
let data: serde_json::Value = response.json().await?;
|
| 89 |
+
|
| 90 |
+
Ok(BinanceTicker {
|
| 91 |
+
symbol: data["symbol"].as_str().unwrap_or("").to_string(),
|
| 92 |
+
last_price: data["lastPrice"].as_str().unwrap_or("0").parse().unwrap_or(0.0),
|
| 93 |
+
price_change_percent: data["priceChangePercent"].as_str().unwrap_or("0").parse().unwrap_or(0.0),
|
| 94 |
+
volume: data["volume"].as_str().unwrap_or("0").parse().unwrap_or(0.0),
|
| 95 |
+
})
|
| 96 |
+
}
|
| 97 |
+
}
|
rust-api/src/main.rs
ADDED
|
@@ -0,0 +1,227 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
use axum::{
|
| 2 |
+
routing::{get, post},
|
| 3 |
+
Router, Json, extract::State,
|
| 4 |
+
};
|
| 5 |
+
use serde::{Deserialize, Serialize};
|
| 6 |
+
use std::sync::Arc;
|
| 7 |
+
use tokio::sync::Mutex;
|
| 8 |
+
use chrono::{DateTime, Utc};
|
| 9 |
+
|
| 10 |
+
mod trading;
|
| 11 |
+
mod binance;
|
| 12 |
+
|
| 13 |
+
pub use trading::*;
|
| 14 |
+
pub use binance::*;
|
| 15 |
+
|
| 16 |
+
#[derive(Clone)]
|
| 17 |
+
pub struct AppState {
|
| 18 |
+
pub config: TradingConfig,
|
| 19 |
+
pub positions: Arc<Mutex<Vec<Position>>>,
|
| 20 |
+
pub orders: Arc<Mutex<Vec<Order>>>,
|
| 21 |
+
}
|
| 22 |
+
|
| 23 |
+
#[derive(Debug, Clone, Serialize, Deserialize)]
|
| 24 |
+
pub struct TradingConfig {
|
| 25 |
+
pub api_key: String,
|
| 26 |
+
pub api_secret: String,
|
| 27 |
+
pub testnet: bool,
|
| 28 |
+
pub max_position_size: f64,
|
| 29 |
+
pub stop_loss_percent: f64,
|
| 30 |
+
pub take_profit_percent: f64,
|
| 31 |
+
}
|
| 32 |
+
|
| 33 |
+
#[derive(Debug, Clone, Serialize, Deserialize)]
|
| 34 |
+
pub struct Position {
|
| 35 |
+
pub id: String,
|
| 36 |
+
pub symbol: String,
|
| 37 |
+
pub side: String,
|
| 38 |
+
pub quantity: f64,
|
| 39 |
+
pub entry_price: f64,
|
| 40 |
+
pub current_price: f64,
|
| 41 |
+
pub pnl: f64,
|
| 42 |
+
pub opened_at: DateTime<Utc>,
|
| 43 |
+
}
|
| 44 |
+
|
| 45 |
+
#[derive(Debug, Clone, Serialize, Deserialize)]
|
| 46 |
+
pub struct Order {
|
| 47 |
+
pub id: String,
|
| 48 |
+
pub symbol: String,
|
| 49 |
+
pub side: String,
|
| 50 |
+
pub order_type: String,
|
| 51 |
+
pub quantity: f64,
|
| 52 |
+
pub price: Option<f64>,
|
| 53 |
+
pub status: String,
|
| 54 |
+
pub created_at: DateTime<Utc>,
|
| 55 |
+
}
|
| 56 |
+
|
| 57 |
+
#[derive(Debug, Deserialize)]
|
| 58 |
+
pub struct CreateOrderRequest {
|
| 59 |
+
pub symbol: String,
|
| 60 |
+
pub side: String,
|
| 61 |
+
pub quantity: f64,
|
| 62 |
+
pub price: Option<f64>,
|
| 63 |
+
}
|
| 64 |
+
|
| 65 |
+
#[derive(Debug, Deserialize)]
|
| 66 |
+
pub struct ClosePositionRequest {
|
| 67 |
+
pub position_id: String,
|
| 68 |
+
}
|
| 69 |
+
|
| 70 |
+
#[derive(Debug, Serialize)]
|
| 71 |
+
pub struct ApiResponse<T> {
|
| 72 |
+
pub success: bool,
|
| 73 |
+
pub data: Option<T>,
|
| 74 |
+
pub error: Option<String>,
|
| 75 |
+
}
|
| 76 |
+
|
| 77 |
+
async fn health() -> Json<ApiResponse<String>> {
|
| 78 |
+
Json(ApiResponse {
|
| 79 |
+
success: true,
|
| 80 |
+
data: Some("RapidLiveClient API running".to_string()),
|
| 81 |
+
error: None,
|
| 82 |
+
})
|
| 83 |
+
}
|
| 84 |
+
|
| 85 |
+
async fn get_balance(State(state): State<AppState>) -> Json<ApiResponse<f64>> {
|
| 86 |
+
let balance = state.config.testnet as i32 as f64 * 10000.0;
|
| 87 |
+
Json(ApiResponse {
|
| 88 |
+
success: true,
|
| 89 |
+
data: Some(balance),
|
| 90 |
+
error: None,
|
| 91 |
+
})
|
| 92 |
+
}
|
| 93 |
+
|
| 94 |
+
async fn get_positions(State(state): State<AppState>) -> Json<ApiResponse<Vec<Position>>> {
|
| 95 |
+
let positions = state.positions.lock().await.clone();
|
| 96 |
+
Json(ApiResponse {
|
| 97 |
+
success: true,
|
| 98 |
+
data: Some(positions),
|
| 99 |
+
error: None,
|
| 100 |
+
})
|
| 101 |
+
}
|
| 102 |
+
|
| 103 |
+
async fn get_orders(State(state): State<AppState>) -> Json<ApiResponse<Vec<Order>>> {
|
| 104 |
+
let orders = state.orders.lock().await.clone();
|
| 105 |
+
Json(ApiResponse {
|
| 106 |
+
success: true,
|
| 107 |
+
data: Some(orders),
|
| 108 |
+
error: None,
|
| 109 |
+
})
|
| 110 |
+
}
|
| 111 |
+
|
| 112 |
+
async fn create_order(
|
| 113 |
+
State(state): State<AppState>,
|
| 114 |
+
Json(req): Json<CreateOrderRequest>,
|
| 115 |
+
) -> Json<ApiResponse<Order>> {
|
| 116 |
+
let order = Order {
|
| 117 |
+
id: format!("ORD-{}", chrono::Utc::now().timestamp_millis()),
|
| 118 |
+
symbol: req.symbol.clone(),
|
| 119 |
+
side: req.side.clone(),
|
| 120 |
+
order_type: if req.price.is_some() { "LIMIT".to_string() } else { "MARKET".to_string() },
|
| 121 |
+
quantity: req.quantity,
|
| 122 |
+
price: req.price,
|
| 123 |
+
status: "pending".to_string(),
|
| 124 |
+
created_at: Utc::now(),
|
| 125 |
+
};
|
| 126 |
+
|
| 127 |
+
state.orders.lock().await.push(order.clone());
|
| 128 |
+
|
| 129 |
+
Json(ApiResponse {
|
| 130 |
+
success: true,
|
| 131 |
+
data: Some(order),
|
| 132 |
+
error: None,
|
| 133 |
+
})
|
| 134 |
+
}
|
| 135 |
+
|
| 136 |
+
async fn close_position(
|
| 137 |
+
State(state): State<AppState>,
|
| 138 |
+
Json(req): Json<ClosePositionRequest>,
|
| 139 |
+
) -> Json<ApiResponse<Position>> {
|
| 140 |
+
let mut positions = state.positions.lock().await;
|
| 141 |
+
|
| 142 |
+
if let Some(pos) = positions.iter_mut().find(|p| p.id == req.position_id) {
|
| 143 |
+
let current_price = pos.current_price;
|
| 144 |
+
let pnl = if pos.side == "BUY" {
|
| 145 |
+
(current_price - pos.entry_price) * pos.quantity
|
| 146 |
+
} else {
|
| 147 |
+
(pos.entry_price - current_price) * pos.quantity
|
| 148 |
+
};
|
| 149 |
+
|
| 150 |
+
pos.pnl = pnl;
|
| 151 |
+
|
| 152 |
+
Json(ApiResponse {
|
| 153 |
+
success: true,
|
| 154 |
+
data: Some(pos.clone()),
|
| 155 |
+
error: None,
|
| 156 |
+
})
|
| 157 |
+
} else {
|
| 158 |
+
Json(ApiResponse {
|
| 159 |
+
success: false,
|
| 160 |
+
data: None,
|
| 161 |
+
error: Some("Position not found".to_string()),
|
| 162 |
+
})
|
| 163 |
+
}
|
| 164 |
+
}
|
| 165 |
+
|
| 166 |
+
async fn analyze_market(
|
| 167 |
+
State(state): State<AppState>,
|
| 168 |
+
Json(req): Json<serde_json::Value>,
|
| 169 |
+
) -> Json<ApiResponse<serde_json::Value>> {
|
| 170 |
+
let symbol = req.get("symbol")
|
| 171 |
+
.and_then(|s| s.as_str())
|
| 172 |
+
.unwrap_or("BTCUSDT");
|
| 173 |
+
|
| 174 |
+
let analysis = serde_json::json!({
|
| 175 |
+
"symbol": symbol,
|
| 176 |
+
"recommendation": "BUY",
|
| 177 |
+
"confidence": 0.75,
|
| 178 |
+
"reason": "RSI oversold, trend bullish",
|
| 179 |
+
"entry_price": 50000.0,
|
| 180 |
+
"stop_loss": 47500.0,
|
| 181 |
+
"take_profit": 55000.0,
|
| 182 |
+
"risk_level": "MEDIUM"
|
| 183 |
+
});
|
| 184 |
+
|
| 185 |
+
Json(ApiResponse {
|
| 186 |
+
success: true,
|
| 187 |
+
data: Some(analysis),
|
| 188 |
+
error: None,
|
| 189 |
+
})
|
| 190 |
+
}
|
| 191 |
+
|
| 192 |
+
#[tokio::main]
|
| 193 |
+
async fn main() {
|
| 194 |
+
tracing_subscriber::fmt::init();
|
| 195 |
+
|
| 196 |
+
let config = TradingConfig {
|
| 197 |
+
api_key: std::env::var("BINANCE_API_KEY").unwrap_or_default(),
|
| 198 |
+
api_secret: std::env::var("BINANCE_API_SECRET").unwrap_or_default(),
|
| 199 |
+
testnet: true,
|
| 200 |
+
max_position_size: 1000.0,
|
| 201 |
+
stop_loss_percent: 5.0,
|
| 202 |
+
take_profit_percent: 10.0,
|
| 203 |
+
};
|
| 204 |
+
|
| 205 |
+
let state = AppState {
|
| 206 |
+
config,
|
| 207 |
+
positions: Arc::new(Mutex::new(Vec::new())),
|
| 208 |
+
orders: Arc::new(Mutex::new(Vec::new())),
|
| 209 |
+
};
|
| 210 |
+
|
| 211 |
+
let app = Router::new()
|
| 212 |
+
.route("/", get(health))
|
| 213 |
+
.route("/health", get(health))
|
| 214 |
+
.route("/api/balance", get(get_balance))
|
| 215 |
+
.route("/api/positions", get(get_positions))
|
| 216 |
+
.route("/api/orders", get(get_orders))
|
| 217 |
+
.route("/api/orders", post(create_order))
|
| 218 |
+
.route("/api/positions/close", post(close_position))
|
| 219 |
+
.route("/api/analyze", post(analyze_market))
|
| 220 |
+
.with_state(state);
|
| 221 |
+
|
| 222 |
+
let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();
|
| 223 |
+
|
| 224 |
+
tracing::info!("RapidLiveClient API running on http://0.0.0.0:3000");
|
| 225 |
+
|
| 226 |
+
axum::serve(listener, app).await.unwrap();
|
| 227 |
+
}
|
rust-api/src/trading.rs
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
// Trading module placeholder
|
| 2 |
+
// This would contain Nautilus Trader strategy integration
|
| 3 |
+
|
| 4 |
+
use serde::{Deserialize, Serialize};
|
| 5 |
+
|
| 6 |
+
#[derive(Debug, Clone, Serialize, Deserialize)]
|
| 7 |
+
pub struct StrategyConfig {
|
| 8 |
+
pub name: String,
|
| 9 |
+
pub symbols: Vec<String>,
|
| 10 |
+
pub capital: f64,
|
| 11 |
+
pub risk_per_trade: f64,
|
| 12 |
+
pub max_positions: usize,
|
| 13 |
+
}
|
| 14 |
+
|
| 15 |
+
impl Default for StrategyConfig {
|
| 16 |
+
fn default() -> Self {
|
| 17 |
+
Self {
|
| 18 |
+
name: "RapidAgentStrategy".to_string(),
|
| 19 |
+
symbols: vec![
|
| 20 |
+
"BTCUSDT".to_string(),
|
| 21 |
+
"ETHUSDT".to_string(),
|
| 22 |
+
"SOLUSDT".to_string(),
|
| 23 |
+
],
|
| 24 |
+
capital: 10000.0,
|
| 25 |
+
risk_per_trade: 0.02,
|
| 26 |
+
max_positions: 3,
|
| 27 |
+
}
|
| 28 |
+
}
|
| 29 |
+
}
|
| 30 |
+
|
| 31 |
+
pub struct TradingStrategy {
|
| 32 |
+
config: StrategyConfig,
|
| 33 |
+
}
|
| 34 |
+
|
| 35 |
+
impl TradingStrategy {
|
| 36 |
+
pub fn new(config: StrategyConfig) -> Self {
|
| 37 |
+
Self { config }
|
| 38 |
+
}
|
| 39 |
+
|
| 40 |
+
pub fn should_entry(&self, symbol: &str, rsi: f64, trend: &str) -> bool {
|
| 41 |
+
if rsi < 30.0 && trend == "bullish" {
|
| 42 |
+
return true;
|
| 43 |
+
}
|
| 44 |
+
if rsi < 40.0 && trend == "bullish" {
|
| 45 |
+
return true;
|
| 46 |
+
}
|
| 47 |
+
false
|
| 48 |
+
}
|
| 49 |
+
|
| 50 |
+
pub fn should_exit(&self, symbol: &str, rsi: f64, pnl_percent: f64) -> bool {
|
| 51 |
+
if rsi > 70.0 {
|
| 52 |
+
return true;
|
| 53 |
+
}
|
| 54 |
+
if pnl_percent >= self.config.risk_per_trade * 5.0 {
|
| 55 |
+
return true;
|
| 56 |
+
}
|
| 57 |
+
if pnl_percent <= -self.config.risk_per_trade * 2.0 {
|
| 58 |
+
return true;
|
| 59 |
+
}
|
| 60 |
+
false
|
| 61 |
+
}
|
| 62 |
+
|
| 63 |
+
pub fn calculate_position_size(&self, capital: f64, entry: f64, stop_loss: f64) -> f64 {
|
| 64 |
+
let risk_amount = capital * self.config.risk_per_trade;
|
| 65 |
+
let risk_per_unit = (entry - stop_loss).abs() / entry;
|
| 66 |
+
risk_amount / risk_per_unit
|
| 67 |
+
}
|
| 68 |
+
}
|