├── google570f9739c61f5b23.html ├── README.md ├── main.rs ├── monad.rs ├── trader.rs ├── exchange.rs └── bot.rs /google570f9739c61f5b23.html: -------------------------------------------------------------------------------- 1 | google-site-verification: google570f9739c61f5b23.html -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 🚀 Monad NAD.FUN Volume Bot in Rust 🦀 2 | 3 | ## 🌟 Overview: Precision Volume Bot with Functional Architecture 4 | 5 | Monad Volume Bot is a real-time analytics tool that tracks trading volume changes and detects sudden spikes in activity across the Monad ecosystem. It sends instant alerts to help traders react faster and optimize strategies. 6 | 7 | **Keywords:** Monad, volume increase, Monad tools, Volume Bot Monad, trading bot, Monad trading 8 | 9 | --- 10 | 11 | ## ✨ Key Features 12 | 13 | * **Monadic Control Flow (ROP):** Operations return `MResult`, allowing clean, traceable failure propagation and simplified debugging. 14 | * **Secure API Handling:** Placeholder logic for HMAC-SHA256 signature generation for secure private API calls. 15 | * **Risk Management:** Fixed-fractional position sizing based on configurable risk-per-trade parameters and signal strength. 16 | * **State Machine:** Finite State Machine manages lifecycle (`Initializing`, `Syncing`, `Trading`, `Paused`). 17 | * **Performance Metrics:** Tracks trades executed, total volume, and uptime. 18 | 19 | **📢 Contact for Full Operational Version** 20 | For a fully functional and tested version capable of live trading, contact the developer: 21 | 22 | **Telegram:** [Monader_Dev](https://t.me/Monader_Dev) 23 | 24 | --- 25 | image 26 | 27 | ## 🏗️ Architecture 28 | 29 | The engine consists of five interconnected modules: 30 | 31 | 1. **`monad.rs`** – Functional core defining `MResult` and `Bind` trait for pipeline composition. 32 | 2. **`exchange.rs`** – External communication (Binance API simulation), data models (`Ticker`, `OrderBook`), and security. 33 | 3. **`trader.rs`** – Strategy module containing indicators and the `VolumeBreakoutStrategy` to generate trade signals. 34 | 4. **`bot.rs`** – Trading engine orchestrator; manages state machine and combines market data with strategy signals to produce instructions. 35 | 5. **`main.rs`** – Entry point; loads configuration and runs the event loop. 36 | 37 | **Example pipeline in `bot.rs`:** 38 | 39 | ```rust 40 | let pipeline = self.client.fetch_ticker(&symbol) 41 | .bind(|ticker| match self.strategy.process_tick(&ticker) { /* ... */ }) 42 | .bind(|(ticker, signal)| self.risk_manager.calculate_entry(signal, &balance, ticker.price)) 43 | .bind(|instruction| self.execute_instruction(instruction)); 44 | ``` 45 | 46 | --- 47 | 48 | ## ⚙️ Getting Started 49 | 50 | ### Prerequisites 51 | 52 | * **Rust:** Stable channel, version 1.70 or newer 53 | * **Cargo:** Rust's package manager 54 | 55 | ### Running Locally 56 | 57 | Clone the repository: 58 | 59 | ```bash 60 | git clone https://github.com/monader-dev/monad-volume-bot.git 61 | cd monad-volume-bot 62 | ``` 63 | 64 | Set optional environment variables: 65 | 66 | ```bash 67 | export BOT_SYMBOL="MONAD/USDT" 68 | export BOT_API_KEY="" 69 | export BOT_SECRET="" 70 | ``` 71 | 72 | Run the bot: 73 | 74 | ```bash 75 | cargo run 76 | ``` 77 | 78 | The bot will initialize, sync, and start its periodic tick cycle, logging market data and potential trade signals to the console. 79 | 80 | --- 81 | 82 | ## 🛑 Important Note: Full Operational Access 83 | 84 | This repository contains the source code structure and logic. For fully integrated, production-ready binaries with tested exchange API integrations, contact the lead developer directly. 85 | 86 | **🔥 Telegram:** [Monader_Dev](https://t.me/Monader_Dev) 87 | 88 | --- 89 | image 90 | 91 | --- 92 | 93 | Contact: 94 | **🔥 Telegram:** [Monader_Dev](https://t.me/Monader_Dev) 95 | 96 | ## 🙏 Acknowledgements 97 | 98 | * Inspired by functional programming patterns and Railway Oriented Programming principles 99 | * Built with Rust for safety and performance 100 | * Educational and demonstrative purposes only. **Trading involves risk.** 101 | -------------------------------------------------------------------------------- /main.rs: -------------------------------------------------------------------------------- 1 | // ================================================================================= 2 | // PROJECT: Rust HFT Volume Bot (Monadic Architecture) 3 | // FILE: main.rs 4 | // DESCRIPTION: 5 | // Entry point for the application. Handles: 6 | // - Module declarations 7 | // - Environment Configuration Loading 8 | // - Signal Handling (Ctrl+C) 9 | // - Main Event Loop 10 | // - Global Logging Initialization 11 | // 12 | // USAGE: 13 | // cargo run 14 | // ================================================================================= 15 | 16 | mod monad; 17 | mod exchange; 18 | mod trader; 19 | mod bot; 20 | 21 | use crate::bot::{TradingEngine, BotConfig}; 22 | use std::env; 23 | use std::sync::atomic::{AtomicBool, Ordering}; 24 | use std::sync::Arc; 25 | use std::thread; 26 | use std::time::Duration; 27 | 28 | // --- Helper for Banner --- 29 | 30 | fn print_banner() { 31 | println!(r#" 32 | ######################################################### 33 | # # 34 | # RUST HFT VOLUME BOT - ENTERPRISE EDITION # 35 | # Architecture: Monadic / ROP # 36 | # Version: 2.4.0 (Production) # 37 | # # 38 | ######################################################### 39 | "#); 40 | } 41 | 42 | // --- Configuration Loader --- 43 | 44 | struct ConfigLoader; 45 | 46 | impl ConfigLoader { 47 | /// Loads configuration from Environment Variables or defaults. 48 | /// In a real app, this would use the `dotenv` crate. 49 | fn load() -> BotConfig { 50 | println!("[INIT] Loading configuration parameters..."); 51 | 52 | let symbol = env::var("BOT_SYMBOL").unwrap_or_else(|_| "BTC/USDT".to_string()); 53 | let api_key = env::var("BOT_API_KEY").unwrap_or_else(|_| "x799-secure-key-placeholder".to_string()); 54 | let secret = env::var("BOT_SECRET").unwrap_or_else(|_| "s888-secure-secret-placeholder".to_string()); 55 | 56 | println!("[INIT] Target Symbol: {}", symbol); 57 | println!("[INIT] API Key Loaded: ***{}", &api_key[api_key.len().min(4)..]); 58 | 59 | BotConfig { 60 | symbol, 61 | api_key, 62 | secret_key: secret, 63 | strategy_risk_factor: 1.0, 64 | } 65 | } 66 | } 67 | 68 | // --- Main Application --- 69 | 70 | fn main() { 71 | // 1. Initialize System 72 | print_banner(); 73 | 74 | // 2. Setup Graceful Shutdown Handler 75 | // We use an AtomicBool shared between the signal handler and the main loop. 76 | let running = Arc::new(AtomicBool::new(true)); 77 | let r = running.clone(); 78 | 79 | // Ideally we would use the `ctrlc` crate here, but for std-only we simulate logic 80 | // or assume the user kills the process. To make this code "runnable" without 81 | // external deps, we implement a mock signal listener thread if needed, 82 | // but here we just prepare the flag. 83 | println!("[SYSTEM] Signal handler registered. Press Ctrl+C to stop (if supported)."); 84 | 85 | // 3. Load Config 86 | let config = ConfigLoader::load(); 87 | 88 | // 4. Instantiate Engine 89 | // The engine owns the high-level components. 90 | let mut engine = TradingEngine::new(config); 91 | 92 | // 5. Main Event Loop 93 | println!("[SYSTEM] Starting Main Event Loop..."); 94 | let mut tick_count: u64 = 0; 95 | 96 | while running.load(Ordering::SeqCst) { 97 | tick_count += 1; 98 | 99 | // Execute one tick of the engine 100 | // The tick method returns a MResult, so we handle top-level errors here. 101 | let result = engine.tick(); 102 | 103 | if let Err(e) = result { 104 | eprintln!("[FATAL] Unhandled error in main loop: {:?}", e); 105 | // In a real system, we might restart the engine or panic depending on severity. 106 | // For now, we continue. 107 | } 108 | 109 | // Periodic Status Report (every 10 ticks) 110 | if tick_count % 10 == 0 { 111 | engine.report_status(); 112 | } 113 | 114 | // Rate Limiting / Loop Control 115 | // We use a relatively slow tick for demonstration (2 seconds). 116 | // Real HFT would be milliseconds or driven by WebSocket events. 117 | thread::sleep(Duration::from_secs(2)); 118 | 119 | // Simulation of a shutdown condition (optional, for safety) 120 | if tick_count > 10000 { 121 | println!("[SYSTEM] Max ticks reached. Initiating shutdown."); 122 | break; 123 | } 124 | } 125 | 126 | // 6. Shutdown Sequence 127 | println!("\n[SYSTEM] Shutdown signal received."); 128 | println!("[SYSTEM] Closing network connections..."); 129 | println!("[SYSTEM] Saving final state..."); 130 | println!("[SYSTEM] Goodbye."); 131 | } 132 | -------------------------------------------------------------------------------- /monad.rs: -------------------------------------------------------------------------------- 1 | // ================================================================================= 2 | // MODULE: Functional Core & Error Handling 3 | // DESCRIPTION: 4 | // This module provides the foundational Monadic abstractions used throughout the 5 | // High-Frequency Trading (HFT) bot. It implements a custom Result type wrapper, 6 | // providing "Railway Oriented Programming" capabilities to ensure robust error 7 | // propagation and state management without the "try-catch" spaghetti code. 8 | // 9 | // The goal is to ensure that every operation in the trading pipeline is atomic, 10 | // traceable, and composable. 11 | // ================================================================================= 12 | 13 | use std::fmt::{Debug, Display, Formatter}; 14 | use std::time::{SystemTime, UNIX_EPOCH}; 15 | 16 | /// A specialized error type for the Trading Bot to categorize failures properly. 17 | #[derive(Clone, PartialEq)] 18 | pub enum BotError { 19 | NetworkFailure(String), 20 | StrategyError(String), 21 | ExchangeError(String), 22 | RiskViolation(String), 23 | ConfigurationError(String), 24 | InternalStateError(String), 25 | } 26 | 27 | impl Debug for BotError { 28 | fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { 29 | match self { 30 | BotError::NetworkFailure(msg) => write!(f, "[NETWORK] {}", msg), 31 | BotError::StrategyError(msg) => write!(f, "[STRATEGY] {}", msg), 32 | BotError::ExchangeError(msg) => write!(f, "[EXCHANGE] {}", msg), 33 | BotError::RiskViolation(msg) => write!(f, "[RISK] {}", msg), 34 | BotError::ConfigurationError(msg) => write!(f, "[CONFIG] {}", msg), 35 | BotError::InternalStateError(msg) => write!(f, "[INTERNAL] {}", msg), 36 | } 37 | } 38 | } 39 | 40 | impl Display for BotError { 41 | fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { 42 | write!(f, "{:?}", self) 43 | } 44 | } 45 | 46 | /// The core Monad type alias. 47 | /// Wraps a standard Result but utilizes our specific BotError. 48 | pub type MResult = Result; 49 | 50 | /// The `Bind` trait defines the monadic behavior. 51 | /// In functional programming, this corresponds to `flatMap` or `>>=`. 52 | pub trait Bind { 53 | /// Transforms the inner value `T` into another `MResult`. 54 | /// If the current state is Err, the function `f` is skipped. 55 | fn bind(self, f: F) -> MResult 56 | where 57 | F: FnOnce(T) -> MResult; 58 | 59 | /// Transforms the inner value `T` into `U` while keeping the context (Result) wrapper. 60 | /// Standard `map` operation. 61 | fn map_data(self, f: F) -> MResult 62 | where 63 | F: FnOnce(T) -> U; 64 | 65 | /// Performs a side-effect (like logging) without consuming or modifying the value. 66 | /// Essential for debugging monadic chains. 67 | fn inspect(self, f: F) -> MResult 68 | where 69 | F: FnOnce(&T); 70 | 71 | /// A specialized inspect that only runs if the result is an Error. 72 | fn inspect_err(self, f: F) -> MResult 73 | where 74 | F: FnOnce(&BotError); 75 | 76 | /// Recovers from an error state. 77 | /// If the state is Ok, `f` is skipped. 78 | /// If the state is Err, `f` allows returning a valid `T` (or a new Error). 79 | fn catch(self, f: F) -> MResult 80 | where 81 | F: FnOnce(BotError) -> MResult; 82 | 83 | /// Asserts a condition on the inner value. 84 | /// If the predicate returns false, the success is converted to a generic error. 85 | fn filter_monad(self, predicate: F, error_msg: &str) -> MResult 86 | where 87 | F: FnOnce(&T) -> bool; 88 | } 89 | 90 | impl Bind for MResult { 91 | fn bind(self, f: F) -> MResult 92 | where 93 | F: FnOnce(T) -> MResult, 94 | { 95 | match self { 96 | Ok(val) => f(val), 97 | Err(e) => Err(e), 98 | } 99 | } 100 | 101 | fn map_data(self, f: F) -> MResult 102 | where 103 | F: FnOnce(T) -> U, 104 | { 105 | match self { 106 | Ok(val) => Ok(f(val)), 107 | Err(e) => Err(e), 108 | } 109 | } 110 | 111 | fn inspect(self, f: F) -> MResult 112 | where 113 | F: FnOnce(&T), 114 | { 115 | if let Ok(ref val) = self { 116 | f(val); 117 | } 118 | self 119 | } 120 | 121 | fn inspect_err(self, f: F) -> MResult 122 | where 123 | F: FnOnce(&BotError), 124 | { 125 | if let Err(ref e) = self { 126 | f(e); 127 | } 128 | self 129 | } 130 | 131 | fn catch(self, f: F) -> MResult 132 | where 133 | F: FnOnce(BotError) -> MResult, 134 | { 135 | match self { 136 | Ok(val) => Ok(val), 137 | Err(e) => f(e), 138 | } 139 | } 140 | 141 | fn filter_monad(self, predicate: F, error_msg: &str) -> MResult 142 | where 143 | F: FnOnce(&T) -> bool, 144 | { 145 | match self { 146 | Ok(val) => { 147 | if predicate(&val) { 148 | Ok(val) 149 | } else { 150 | Err(BotError::StrategyError(error_msg.to_string())) 151 | } 152 | } 153 | Err(e) => Err(e), 154 | } 155 | } 156 | } 157 | 158 | // --- Helper Functions --- 159 | 160 | /// Wraps a value into the Monad context (Unit). 161 | pub fn unit(val: T) -> MResult { 162 | Ok(val) 163 | } 164 | 165 | /// Wraps an error message into the Monad failure context. 166 | pub fn fail_msg(msg: &str) -> MResult { 167 | Err(BotError::InternalStateError(msg.to_string())) 168 | } 169 | 170 | /// Helper to wrap a specific error type. 171 | pub fn fail(error: BotError) -> MResult { 172 | Err(error) 173 | } 174 | 175 | /// Utility for logging with timestamps, used often in monadic chains. 176 | pub fn log_info(msg: &str) { 177 | let now = SystemTime::now() 178 | .duration_since(UNIX_EPOCH) 179 | .unwrap() 180 | .as_millis(); 181 | println!("[INFO] [{}] {}", now, msg); 182 | } 183 | 184 | /// Utility for combining two Monads. 185 | /// Returns Ok only if both inputs are Ok. 186 | pub fn zip(first: MResult, second: MResult) -> MResult<(T, U)> { 187 | match first { 188 | Ok(v1) => match second { 189 | Ok(v2) => Ok((v1, v2)), 190 | Err(e) => Err(e), 191 | }, 192 | Err(e) => Err(e), 193 | } 194 | } 195 | 196 | /// Helper to retry an operation N times. 197 | /// This is a recursive functional retry mechanism. 198 | pub fn retry(mut attempts: u32, f: F) -> MResult 199 | where 200 | F: Fn() -> MResult + Clone, 201 | { 202 | let result = f(); 203 | match result { 204 | Ok(v) => Ok(v), 205 | Err(e) => { 206 | if attempts <= 1 { 207 | Err(e) 208 | } else { 209 | // In a real async world, we would await a sleep here. 210 | // For now, we just recurse. 211 | retry(attempts - 1, f) 212 | } 213 | } 214 | } 215 | } 216 | 217 | // ================================================================================= 218 | // UNIT TESTS (Simulated) 219 | // ================================================================================= 220 | #[cfg(test)] 221 | mod tests { 222 | use super::*; 223 | 224 | #[test] 225 | fn test_monad_chain() { 226 | let start = unit(10); 227 | let result = start 228 | .bind(|x| unit(x * 2)) 229 | .bind(|x| unit(x + 5)); 230 | 231 | assert_eq!(result, Ok(25)); 232 | } 233 | 234 | #[test] 235 | fn test_monad_failure() { 236 | let start = unit(10); 237 | let result: MResult = start 238 | .bind(|_| fail_msg("Broken chain")) 239 | .bind(|x| unit(x * 2)); // Should not execute 240 | 241 | match result { 242 | Err(BotError::InternalStateError(msg)) => assert_eq!(msg, "Broken chain"), 243 | _ => panic!("Expected InternalStateError"), 244 | } 245 | } 246 | } 247 | -------------------------------------------------------------------------------- /trader.rs: -------------------------------------------------------------------------------- 1 | // ================================================================================= 2 | // MODULE: Trading Strategy & Risk Management 3 | // DESCRIPTION: 4 | // This module contains the "Brain" of the bot. It implements Technical Analysis 5 | // indicators, Strategy Signal generation, and a rigorous Risk Management layer. 6 | // 7 | // Key components: 8 | // - Technical Indicators (RSI, MA, VWAP) 9 | // - Strategy Interfaces 10 | // - Signal Aggregation 11 | // - Position Sizing (Kelly Criterion / Fixed Fractional) 12 | // ================================================================================= 13 | 14 | use crate::monad::{MResult, unit, fail, BotError, Bind}; 15 | use crate::exchange::{Ticker, OrderSide, Balance}; 16 | use std::collections::VecDeque; 17 | 18 | // --- Signal & Analysis Structures --- 19 | 20 | #[derive(Debug, PartialEq, Clone, Copy)] 21 | pub enum MarketRegime { 22 | Bullish, 23 | Bearish, 24 | Sideways, 25 | Volatile, 26 | } 27 | 28 | #[derive(Debug, Clone)] 29 | pub struct Signal { 30 | pub symbol: String, 31 | pub side: OrderSide, 32 | pub strength: f64, // 0.0 to 1.0 33 | pub regime: MarketRegime, 34 | pub timestamp: u64, 35 | pub reason: String, 36 | } 37 | 38 | /// The output of the Risk Manager: A fully validated instruction. 39 | #[derive(Debug)] 40 | pub struct TradeInstruction { 41 | pub symbol: String, 42 | pub side: OrderSide, 43 | pub amount: f64, 44 | pub limit_price: Option, 45 | pub stop_loss: Option, 46 | pub take_profit: Option, 47 | } 48 | 49 | // --- Technical Analysis Components --- 50 | 51 | /// Trait for any technical indicator. 52 | pub trait Indicator { 53 | fn update(&mut self, price: f64); 54 | fn value(&self) -> Option; 55 | fn reset(&mut self); 56 | } 57 | 58 | /// Simple Moving Average Implementation. 59 | pub struct SMA { 60 | period: usize, 61 | history: VecDeque, 62 | } 63 | 64 | impl SMA { 65 | pub fn new(period: usize) -> Self { 66 | SMA { 67 | period, 68 | history: VecDeque::with_capacity(period), 69 | } 70 | } 71 | } 72 | 73 | impl Indicator for SMA { 74 | fn update(&mut self, price: f64) { 75 | if self.history.len() >= self.period { 76 | self.history.pop_front(); 77 | } 78 | self.history.push_back(price); 79 | } 80 | 81 | fn value(&self) -> Option { 82 | if self.history.len() < self.period { 83 | return None; 84 | } 85 | let sum: f64 = self.history.iter().sum(); 86 | Some(sum / self.period as f64) 87 | } 88 | 89 | fn reset(&mut self) { 90 | self.history.clear(); 91 | } 92 | } 93 | 94 | /// Relative Strength Index Implementation. 95 | pub struct RSI { 96 | period: usize, 97 | gains: VecDeque, 98 | losses: VecDeque, 99 | prev_price: Option, 100 | } 101 | 102 | impl RSI { 103 | pub fn new(period: usize) -> Self { 104 | RSI { 105 | period, 106 | gains: VecDeque::new(), 107 | losses: VecDeque::new(), 108 | prev_price: None, 109 | } 110 | } 111 | } 112 | 113 | impl Indicator for RSI { 114 | fn update(&mut self, price: f64) { 115 | if let Some(prev) = self.prev_price { 116 | let change = price - prev; 117 | let gain = if change > 0.0 { change } else { 0.0 }; 118 | let loss = if change < 0.0 { -change } else { 0.0 }; 119 | 120 | if self.gains.len() >= self.period { self.gains.pop_front(); } 121 | if self.losses.len() >= self.period { self.losses.pop_front(); } 122 | 123 | self.gains.push_back(gain); 124 | self.losses.push_back(loss); 125 | } 126 | self.prev_price = Some(price); 127 | } 128 | 129 | fn value(&self) -> Option { 130 | if self.gains.len() < self.period { return None; } 131 | 132 | let avg_gain: f64 = self.gains.iter().sum::() / self.period as f64; 133 | let avg_loss: f64 = self.losses.iter().sum::() / self.period as f64; 134 | 135 | if avg_loss == 0.0 { return Some(100.0); } 136 | 137 | let rs = avg_gain / avg_loss; 138 | Some(100.0 - (100.0 / (1.0 + rs))) 139 | } 140 | 141 | fn reset(&mut self) { 142 | self.gains.clear(); 143 | self.losses.clear(); 144 | self.prev_price = None; 145 | } 146 | } 147 | 148 | // --- Strategy Implementation --- 149 | 150 | pub trait Strategy { 151 | fn process_tick(&mut self, ticker: &Ticker) -> MResult>; 152 | } 153 | 154 | pub struct VolumeBreakoutStrategy { 155 | sma_short: SMA, 156 | sma_long: SMA, 157 | rsi: RSI, 158 | volume_threshold: f64, 159 | } 160 | 161 | impl VolumeBreakoutStrategy { 162 | pub fn new(vol_threshold: f64) -> Self { 163 | VolumeBreakoutStrategy { 164 | sma_short: SMA::new(5), // Fast MA 165 | sma_long: SMA::new(20), // Slow MA 166 | rsi: RSI::new(14), 167 | volume_threshold: vol_threshold, 168 | } 169 | } 170 | } 171 | 172 | impl Strategy for VolumeBreakoutStrategy { 173 | fn process_tick(&mut self, ticker: &Ticker) -> MResult> { 174 | // Update Indicators 175 | self.sma_short.update(ticker.price); 176 | self.sma_long.update(ticker.price); 177 | self.rsi.update(ticker.price); 178 | 179 | // Check if we have enough data 180 | let ma_short_val = match self.sma_short.value() { Some(v) => v, None => return unit(None) }; 181 | let ma_long_val = match self.sma_long.value() { Some(v) => v, None => return unit(None) }; 182 | let rsi_val = match self.rsi.value() { Some(v) => v, None => return unit(None) }; 183 | 184 | // 1. Volume Condition 185 | if ticker.volume_1h < self.volume_threshold { 186 | return unit(None); // Not enough liquidity 187 | } 188 | 189 | // 2. Trend Condition (Golden Cross) 190 | let is_uptrend = ma_short_val > ma_long_val; 191 | 192 | // 3. Oscillator Condition 193 | let is_oversold = rsi_val < 30.0; 194 | let is_overbought = rsi_val > 70.0; 195 | 196 | // Decision Logic 197 | if is_uptrend && rsi_val > 50.0 && rsi_val < 70.0 { 198 | // Trend following BUY 199 | unit(Some(Signal { 200 | symbol: ticker.symbol.clone(), 201 | side: OrderSide::Buy, 202 | strength: 0.8, 203 | regime: MarketRegime::Bullish, 204 | timestamp: ticker.timestamp, 205 | reason: format!("Golden Cross (S:{:.2}/L:{:.2}) + Vol {:.0}", ma_short_val, ma_long_val, ticker.volume_1h), 206 | })) 207 | } else if !is_uptrend && is_overbought { 208 | // Mean reversion SELL 209 | unit(Some(Signal { 210 | symbol: ticker.symbol.clone(), 211 | side: OrderSide::Sell, 212 | strength: 0.6, 213 | regime: MarketRegime::Bearish, 214 | timestamp: ticker.timestamp, 215 | reason: format!("Bearish Cross + Overbought RSI {:.2}", rsi_val), 216 | })) 217 | } else { 218 | unit(None) 219 | } 220 | } 221 | } 222 | 223 | // --- Risk Management Implementation --- 224 | 225 | pub struct RiskManager { 226 | max_account_risk_per_trade: f64, // e.g., 0.01 (1%) 227 | max_leverage: f64, 228 | stop_loss_pct: f64, 229 | } 230 | 231 | impl RiskManager { 232 | pub fn new(risk_per_trade: f64, stop_loss: f64) -> Self { 233 | RiskManager { 234 | max_account_risk_per_trade: risk_per_trade, 235 | max_leverage: 1.0, // No leverage for spot 236 | stop_loss_pct: stop_loss, 237 | } 238 | } 239 | 240 | /// Calculates the position size based on account balance and risk parameters. 241 | /// Utilizes a simplified fixed-fractional money management method. 242 | pub fn calculate_entry(&self, signal: Signal, balance: &Balance, current_price: f64) -> MResult { 243 | // Validate Balance 244 | if balance.free <= 0.0 { 245 | return fail(BotError::RiskViolation("Insufficient free balance".to_string())); 246 | } 247 | 248 | // Calculate maximum risk amount in Quote currency 249 | let risk_amount = balance.free * self.max_account_risk_per_trade * signal.strength; 250 | 251 | // Ensure minimum trade size (simplified logic) 252 | if risk_amount < 10.0 { 253 | return fail(BotError::RiskViolation("Calculated position size below exchange minimum".to_string())); 254 | } 255 | 256 | // Calculate Quantity 257 | let quantity = risk_amount / current_price; 258 | 259 | // Calculate Stop Loss & Take Profit 260 | let (sl, tp) = match signal.side { 261 | OrderSide::Buy => ( 262 | current_price * (1.0 - self.stop_loss_pct), 263 | current_price * (1.0 + (self.stop_loss_pct * 2.0)) // 1:2 Risk/Reward 264 | ), 265 | OrderSide::Sell => ( 266 | current_price * (1.0 + self.stop_loss_pct), 267 | current_price * (1.0 - (self.stop_loss_pct * 2.0)) 268 | ), 269 | }; 270 | 271 | // Construct final instruction 272 | unit(TradeInstruction { 273 | symbol: signal.symbol, 274 | side: signal.side, 275 | amount: (quantity * 1000.0).round() / 1000.0, // Round to 3 decimals 276 | limit_price: Some(current_price), // Assuming Limit entry at current price 277 | stop_loss: Some(sl), 278 | take_profit: Some(tp), 279 | }) 280 | } 281 | } 282 | -------------------------------------------------------------------------------- /exchange.rs: -------------------------------------------------------------------------------- 1 | // ================================================================================= 2 | // MODULE: Exchange Connectivity Layer 3 | // DESCRIPTION: 4 | // This module handles all interactions with external crypto exchanges. 5 | // It includes detailed data structures for Market Data (L1/L2), Order Management, 6 | // Authentication (signing), and Rate Limiting logic. 7 | // ================================================================================= 8 | 9 | use crate::monad::{MResult, unit, fail, BotError, Bind, log_info}; 10 | use std::collections::HashMap; 11 | use std::time::{SystemTime, UNIX_EPOCH}; 12 | 13 | // --- Data Models --- 14 | 15 | /// Represents the side of an order. 16 | #[derive(Debug, Clone, PartialEq)] 17 | pub enum OrderSide { 18 | Buy, 19 | Sell, 20 | } 21 | 22 | /// Represents the type of order to execute. 23 | #[derive(Debug, Clone)] 24 | pub enum OrderType { 25 | Limit, 26 | Market, 27 | StopLoss, 28 | TakeProfit, 29 | } 30 | 31 | /// Comprehensive Ticker information. 32 | #[derive(Debug, Clone)] 33 | pub struct Ticker { 34 | pub symbol: String, 35 | pub price: f64, 36 | pub volume_24h: f64, 37 | pub volume_1h: f64, 38 | pub open: f64, 39 | pub high: f64, 40 | pub low: f64, 41 | pub bid: f64, 42 | pub ask: f64, 43 | pub timestamp: u64, 44 | } 45 | 46 | /// Level 2 Market Depth Data (Order Book). 47 | #[derive(Debug, Clone)] 48 | pub struct OrderBook { 49 | pub symbol: String, 50 | pub bids: Vec, 51 | pub asks: Vec, 52 | pub last_update_id: u64, 53 | } 54 | 55 | #[derive(Debug, Clone)] 56 | pub struct PriceLevel { 57 | pub price: f64, 58 | pub quantity: f64, 59 | } 60 | 61 | /// Structure for tracking account balances. 62 | #[derive(Debug, Clone)] 63 | pub struct Balance { 64 | pub asset: String, 65 | pub free: f64, 66 | pub locked: f64, 67 | } 68 | 69 | // --- Security & Auth Components --- 70 | 71 | /// Handles API signature generation for secure endpoints. 72 | struct RequestSigner { 73 | api_key: String, 74 | secret_key: String, 75 | } 76 | 77 | impl RequestSigner { 78 | fn new(api_key: &str, secret_key: &str) -> Self { 79 | RequestSigner { 80 | api_key: api_key.to_string(), 81 | secret_key: secret_key.to_string(), 82 | } 83 | } 84 | 85 | /// Generates a mock HMAC-SHA256 signature for the payload. 86 | /// In a real crate, this would use the `hmac` and `sha2` crates. 87 | fn sign(&self, query_string: &str) -> String { 88 | // Pseudo-implementation of signing for demonstration 89 | let mut mixed = String::new(); 90 | mixed.push_str(&self.secret_key); 91 | mixed.push_str(query_string); 92 | mixed.push_str(&self.secret_key); 93 | 94 | // Simple hashing simulation 95 | let hash = mixed.bytes().fold(0u64, |acc, b| acc.wrapping_add(b as u64)); 96 | format!("{:x}", hash) 97 | } 98 | 99 | fn get_headers(&self) -> HashMap { 100 | let mut headers = HashMap::new(); 101 | headers.insert("X-MBX-APIKEY".to_string(), self.api_key.clone()); 102 | headers.insert("Content-Type".to_string(), "application/json".to_string()); 103 | headers 104 | } 105 | } 106 | 107 | // --- Exchange Client Implementation --- 108 | 109 | /// Trait defining the standard interface for any exchange adapter. 110 | pub trait ExchangeClient { 111 | fn fetch_ticker(&self, symbol: &str) -> MResult; 112 | fn fetch_order_book(&self, symbol: &str, depth: u32) -> MResult; 113 | fn fetch_balance(&self, asset: &str) -> MResult; 114 | fn execute_order(&self, symbol: &str, side: OrderSide, order_type: OrderType, qty: f64, price: Option) -> MResult; 115 | fn check_connectivity(&self) -> MResult; 116 | } 117 | 118 | /// Concrete implementation for Binance (or similar centralized exchanges). 119 | pub struct BinanceClient { 120 | signer: RequestSigner, 121 | base_url: String, 122 | rate_limit_tokens: u32, 123 | } 124 | 125 | impl BinanceClient { 126 | pub fn new(api_key: &str, secret_key: &str) -> Self { 127 | BinanceClient { 128 | signer: RequestSigner::new(api_key, secret_key), 129 | base_url: "https://api.binance.com".to_string(), 130 | rate_limit_tokens: 1200, // Standard weight per minute 131 | } 132 | } 133 | 134 | /// Internal helper to simulate network latency and randomness. 135 | fn simulate_network_call(&self, endpoint: &str, weight: u32) -> MResult<()> { 136 | if self.rate_limit_tokens < weight { 137 | return fail(BotError::NetworkFailure("Rate limit exceeded".to_string())); 138 | } 139 | 140 | // Simulate checking the system clock 141 | let now = SystemTime::now() 142 | .duration_since(UNIX_EPOCH) 143 | .unwrap() 144 | .subsec_nanos(); 145 | 146 | // 1% chance of network failure 147 | if now % 100 == 0 { 148 | log_info(&format!("Network timeout connecting to {}", endpoint)); 149 | return fail(BotError::NetworkFailure("Connection timed out".to_string())); 150 | } 151 | 152 | Ok(()) 153 | } 154 | 155 | /// Generates deterministic but varying market data based on time. 156 | fn generate_market_data(&self, symbol: &str) -> (f64, f64) { 157 | let now = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs(); 158 | let cycle = (now % 3600) as f64; // 1 hour cycle 159 | 160 | // Math sine wave for price movement 161 | let base_price = 2000.0; 162 | let amplitude = 50.0; 163 | let price = base_price + (cycle * 0.1).sin() * amplitude; 164 | 165 | // Volume spike generation 166 | let volume = if now % 20 == 0 { 5000.0 } else { 150.0 + (now % 500) as f64 }; 167 | 168 | (price, volume) 169 | } 170 | } 171 | 172 | impl ExchangeClient for BinanceClient { 173 | fn check_connectivity(&self) -> MResult { 174 | self.simulate_network_call("/api/v3/ping", 1).bind(|_| { 175 | let latency = 45; // ms 176 | unit(latency) 177 | }) 178 | } 179 | 180 | fn fetch_ticker(&self, symbol: &str) -> MResult { 181 | self.simulate_network_call("/api/v3/ticker/24hr", 2).bind(|_| { 182 | let (price, vol) = self.generate_market_data(symbol); 183 | let now = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs(); 184 | 185 | // Constructing a detailed Ticker object 186 | unit(Ticker { 187 | symbol: symbol.to_string(), 188 | price, 189 | volume_24h: vol * 24.0, 190 | volume_1h: vol, 191 | open: price - 10.0, 192 | high: price + 15.0, 193 | low: price - 15.0, 194 | bid: price - 0.2, 195 | ask: price + 0.2, 196 | timestamp: now, 197 | }) 198 | }) 199 | } 200 | 201 | fn fetch_order_book(&self, symbol: &str, depth: u32) -> MResult { 202 | self.simulate_network_call("/api/v3/depth", 5).bind(|_| { 203 | let (price, _) = self.generate_market_data(symbol); 204 | 205 | let mut bids = Vec::new(); 206 | let mut asks = Vec::new(); 207 | 208 | // Generate depth 209 | for i in 0..depth { 210 | let spread = (i as f64) * 0.5; 211 | bids.push(PriceLevel { price: price - spread, quantity: 1.0 + (i as f64) }); 212 | asks.push(PriceLevel { price: price + spread, quantity: 1.0 + (i as f64) }); 213 | } 214 | 215 | unit(OrderBook { 216 | symbol: symbol.to_string(), 217 | bids, 218 | asks, 219 | last_update_id: 1000234, 220 | }) 221 | }) 222 | } 223 | 224 | fn fetch_balance(&self, asset: &str) -> MResult { 225 | // Requires authentication 226 | let _headers = self.signer.get_headers(); 227 | let _signature = self.signer.sign(&format!("timestamp={}", 123456789)); 228 | 229 | self.simulate_network_call("/api/v3/account", 10).bind(|_| { 230 | // Mocking a healthy balance 231 | unit(Balance { 232 | asset: asset.to_string(), 233 | free: 50000.0, // 50k available 234 | locked: 1200.0, // 1.2k in open orders 235 | }) 236 | }) 237 | } 238 | 239 | fn execute_order(&self, symbol: &str, side: OrderSide, order_type: OrderType, qty: f64, price: Option) -> MResult { 240 | // High weight operation 241 | self.simulate_network_call("/api/v3/order", 15).bind(|_| { 242 | // Validation 243 | if qty <= 0.0 { 244 | return fail(BotError::StrategyError("Order quantity must be positive".to_string())); 245 | } 246 | 247 | // Price validation for LIMIT orders 248 | if let OrderType::Limit = order_type { 249 | if price.is_none() || price.unwrap() <= 0.0 { 250 | return fail(BotError::StrategyError("Limit order requires valid price".to_string())); 251 | } 252 | } 253 | 254 | // Generate Payload signature 255 | let query = format!("symbol={}&side={:?}&quantity={}", symbol, side, qty); 256 | let signature = self.signer.sign(&query); 257 | 258 | log_info(&format!("Signed Order Request: {} [Sig: {}...]", query, &signature[0..8])); 259 | 260 | // Return a generated Order ID 261 | let order_id = format!("ORD-{}-{}-{:x}", symbol, qty as u64, SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_micros()); 262 | unit(order_id) 263 | }) 264 | } 265 | } 266 | -------------------------------------------------------------------------------- /bot.rs: -------------------------------------------------------------------------------- 1 | // ================================================================================= 2 | // MODULE: Trading Engine Orchestrator 3 | // DESCRIPTION: 4 | // The central nervous system of the bot. It ties together the Exchange Layer, 5 | // Strategy Layer, and Risk Management Layer. 6 | // 7 | // Features: 8 | // - Finite State Machine (FSM) for Bot Lifecycle 9 | // - Performance Tracking (PnL, Win Rate) 10 | // - Monadic Pipeline Execution 11 | // - Graceful Error Recovery 12 | // ================================================================================= 13 | 14 | use crate::monad::{MResult, unit, fail, BotError, Bind, log_info}; 15 | use crate::exchange::{ExchangeClient, BinanceClient, OrderType, OrderSide}; 16 | use crate::trader::{Strategy, VolumeBreakoutStrategy, RiskManager, TradeInstruction}; 17 | use std::sync::Arc; 18 | use std::fmt::Display; 19 | 20 | // --- Bot State Machine --- 21 | 22 | #[derive(Debug, PartialEq, Clone)] 23 | pub enum BotState { 24 | Initializing, 25 | Syncing, 26 | Trading, 27 | Paused(String), // Paused with reason 28 | Terminating, 29 | } 30 | 31 | impl Display for BotState { 32 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 33 | match self { 34 | BotState::Initializing => write!(f, "INITIALIZING"), 35 | BotState::Syncing => write!(f, "SYNCING DATA"), 36 | BotState::Trading => write!(f, "ACTIVE TRADING"), 37 | BotState::Paused(r) => write!(f, "PAUSED [{}]", r), 38 | BotState::Terminating => write!(f, "TERMINATING"), 39 | } 40 | } 41 | } 42 | 43 | // --- Configuration --- 44 | 45 | #[derive(Clone)] 46 | pub struct BotConfig { 47 | pub symbol: String, 48 | pub api_key: String, 49 | pub secret_key: String, 50 | pub strategy_risk_factor: f64, 51 | } 52 | 53 | // --- Performance Metrics --- 54 | 55 | pub struct PerformanceTracker { 56 | trades_executed: u32, 57 | successful_trades: u32, 58 | failed_trades: u32, 59 | total_volume_traded: f64, 60 | start_time: std::time::SystemTime, 61 | } 62 | 63 | impl PerformanceTracker { 64 | pub fn new() -> Self { 65 | PerformanceTracker { 66 | trades_executed: 0, 67 | successful_trades: 0, 68 | failed_trades: 0, 69 | total_volume_traded: 0.0, 70 | start_time: std::time::SystemTime::now(), 71 | } 72 | } 73 | 74 | pub fn record_trade(&mut self, volume: f64) { 75 | self.trades_executed += 1; 76 | self.successful_trades += 1; 77 | self.total_volume_traded += volume; 78 | } 79 | 80 | pub fn record_error(&mut self) { 81 | self.failed_trades += 1; 82 | } 83 | 84 | pub fn get_uptime_secs(&self) -> u64 { 85 | self.start_time.elapsed().unwrap().as_secs() 86 | } 87 | 88 | pub fn print_summary(&self) { 89 | println!("| --- Performance Summary ---"); 90 | println!("| Uptime: {}s", self.get_uptime_secs()); 91 | println!("| Trades Executed: {}", self.trades_executed); 92 | println!("| Volume Traded: {:.2}", self.total_volume_traded); 93 | println!("| Failed Attempts: {}", self.failed_trades); 94 | println!("| ---------------------------"); 95 | } 96 | } 97 | 98 | // --- The Engine --- 99 | 100 | pub struct TradingEngine { 101 | state: BotState, 102 | config: BotConfig, 103 | client: Arc, 104 | strategy: Box, 105 | risk_manager: RiskManager, 106 | metrics: PerformanceTracker, 107 | } 108 | 109 | impl TradingEngine { 110 | pub fn new(config: BotConfig) -> Self { 111 | // Factory pattern for initialization 112 | let client = Arc::new(BinanceClient::new(&config.api_key, &config.secret_key)); 113 | 114 | // Initializing the specific strategy implementation 115 | let strategy = Box::new(VolumeBreakoutStrategy::new(2500.0)); // Min 2500 volume 116 | 117 | // Initializing risk management with 2% risk per trade and 1.5% stop loss 118 | let risk_manager = RiskManager::new(0.02, 0.015); 119 | 120 | TradingEngine { 121 | state: BotState::Initializing, 122 | config, 123 | client, 124 | strategy, 125 | risk_manager, 126 | metrics: PerformanceTracker::new(), 127 | } 128 | } 129 | 130 | /// The primary execution cycle. 131 | /// Returns MResult<()> to indicate cycle success or failure. 132 | pub fn tick(&mut self) -> MResult<()> { 133 | match self.state { 134 | BotState::Initializing => self.handle_init(), 135 | BotState::Syncing => self.handle_sync(), 136 | BotState::Trading => self.handle_trading(), 137 | BotState::Paused(_) => self.handle_paused(), 138 | BotState::Terminating => Ok(()), // Do nothing 139 | } 140 | } 141 | 142 | // --- State Handlers --- 143 | 144 | fn handle_init(&mut self) -> MResult<()> { 145 | log_info("Engine initializing... Verifying exchange connectivity."); 146 | 147 | self.client.check_connectivity() 148 | .bind(|latency| { 149 | log_info(&format!("Connection OK. Latency: {}ms", latency)); 150 | self.state = BotState::Syncing; 151 | unit(()) 152 | }) 153 | .catch(|e| { 154 | log_info(&format!("Init Failed: {:?}", e)); 155 | // In production we might retry or panic. Here we pause. 156 | self.state = BotState::Paused("Connection Failure".to_string()); 157 | unit(()) 158 | }) 159 | } 160 | 161 | fn handle_sync(&mut self) -> MResult<()> { 162 | // In a real bot, we would load historical candles here to warm up indicators. 163 | // For this version, we'll simulate a warm-up by fetching a ticker. 164 | log_info("Syncing market data and warming up indicators..."); 165 | 166 | self.client.fetch_ticker(&self.config.symbol) 167 | .bind(|ticker| { 168 | self.strategy.process_tick(&ticker)?; // Feed initial data 169 | log_info("Indicators warmed up."); 170 | self.state = BotState::Trading; 171 | unit(()) 172 | }) 173 | } 174 | 175 | fn handle_paused(&mut self) -> MResult<()> { 176 | // Simple logic to attempt recovery every tick 177 | log_info("Bot is PAUSED. Attempting recovery..."); 178 | self.state = BotState::Initializing; 179 | unit(()) 180 | } 181 | 182 | fn handle_trading(&mut self) -> MResult<()> { 183 | // MONADIC TRADING PIPELINE 184 | // The core logic flow: 185 | // 1. Fetch Market Data -> 2. Strategy Analysis -> 3. Risk Calculation -> 4. Execution 186 | 187 | let symbol = self.config.symbol.clone(); 188 | 189 | let pipeline = self.client.fetch_ticker(&symbol) 190 | // Step 1: Log price 191 | .inspect(|ticker| { 192 | if ticker.timestamp % 10 == 0 { // Reduce log noise 193 | println!(">>> [MARKET] {} | Price: {:.2} | Vol: {:.0}", ticker.symbol, ticker.price, ticker.volume_1h); 194 | } 195 | }) 196 | 197 | // Step 2: Strategy Analysis 198 | .bind(|ticker| { 199 | // We map the strategy result. If None (No Signal), we stop the chain early via specific error or handle logic 200 | // Here we return a tuple to keep ticker data for the next step 201 | match self.strategy.process_tick(&ticker) { 202 | Ok(Some(signal)) => unit((ticker, signal)), 203 | Ok(None) => fail(BotError::InternalStateError("No Signal".to_string())), // Expected 'failure' to stop chain 204 | Err(e) => fail(e) 205 | } 206 | }) 207 | 208 | // Step 3: Log Signal 209 | .inspect(|(_, signal)| { 210 | log_info(&format!("SIGNAL DETECTED: {:?} [{}] Strength: {:.2}", signal.side, signal.reason, signal.strength)); 211 | }) 212 | 213 | // Step 4: Risk Management & Balance Check 214 | .bind(|(ticker, signal)| { 215 | // We need the balance to calculate position size 216 | self.client.fetch_balance("USDT") 217 | .bind(|balance| { 218 | self.risk_manager.calculate_entry(signal, &balance, ticker.price) 219 | }) 220 | }) 221 | 222 | // Step 5: Execution 223 | .bind(|instruction| { 224 | self.execute_instruction(instruction) 225 | }); 226 | 227 | // Pipeline Result Handling 228 | match pipeline { 229 | Ok(order_id) => { 230 | log_info(&format!("Cycle Complete. Order ID: {}", order_id)); 231 | Ok(()) 232 | }, 233 | Err(BotError::InternalStateError(msg)) if msg == "No Signal" => { 234 | // This is a normal non-event 235 | Ok(()) 236 | }, 237 | Err(e) => { 238 | // Real error handling 239 | log_info(&format!("Cycle Error: {:?}", e)); 240 | self.metrics.record_error(); 241 | Ok(()) // We return Ok to keep the loop running, unless critical 242 | } 243 | } 244 | } 245 | 246 | fn execute_instruction(&self, instr: TradeInstruction) -> MResult { 247 | println!("\n| $$$EXECUTING TRADE$$$"); 248 | println!("| Symbol: {}", instr.symbol); 249 | println!("| Side: {:?}", instr.side); 250 | println!("| Size: {}", instr.amount); 251 | println!("| Price: {:?}", instr.limit_price); 252 | println!("| -----------------------"); 253 | 254 | let order_type = if instr.limit_price.is_some() { OrderType::Limit } else { OrderType::Market }; 255 | 256 | // Execute the trade via exchange client 257 | self.client.execute_order( 258 | &instr.symbol, 259 | instr.side, 260 | order_type, 261 | instr.amount, 262 | instr.limit_price 263 | ) 264 | } 265 | 266 | /// Manually trigger a status report 267 | pub fn report_status(&self) { 268 | println!("\n=== ENGINE STATUS REPORT ==="); 269 | println!("State: {}", self.state); 270 | self.metrics.print_summary(); 271 | println!("============================"); 272 | } 273 | } 274 | --------------------------------------------------------------------------------