mirror of
https://github.com/chrisnov-it/quantumbotx.git
synced 2026-07-27 18:57:47 +00:00
feat: implement Broker Agnostic Architecture v2.1.0 and Python 3.13 optimization
- Refactored TradingBot to use BrokerInterface for universal compatibility (MT5, CCXT, Mock). - Integrated CCXT with support for Binance Demo Trading. - Updated dependencies for Python 3.13 and fixed installation CPU overhead. - Added comprehensive testing tools: test_agnostic_bot.py, visual_simulation.py. - Updated documentation (README, Changelog, Roadmap) to reflect Agnostic Revolution.
This commit is contained in:
@@ -8,6 +8,31 @@
|
||||
|
||||
---
|
||||
|
||||
## 📈 **v2.1.0 - "Agnostic Revolution"** ✨ (December 2025)
|
||||
|
||||
### 🌍 **Broker Agnostic Architecture** ⭐⭐⭐⭐⭐
|
||||
|
||||
- **Universal Broker Interface**: Complete abstraction of broker logic via `BrokerInterface`.
|
||||
- **Multi-Platform Support**: Now supports Crypto Exchanges (Binance, Bybit) via CCXT integration.
|
||||
- **Docker & Cloud Ready**: Crypto bots can now run on Linux/Docker without needing a local MT5 terminal.
|
||||
- **Broker Factory**: Dynamic switching between MT5, CCXT, and Mock adapters.
|
||||
- **Stateful Mock Testing**: Advanced simulation mode with `visual_simulation.py` for risk-free testing.
|
||||
|
||||
### ⚡ **System & Performance Optimization**
|
||||
|
||||
- **Python 3.13 Support**: Optimized for the latest stable Python versions (3.10 - 3.13).
|
||||
- **Dependency Refresh**: Fixed "CPU 100%" issue during installation by optimizing `requirements.txt`.
|
||||
- **Binary Wheels**: Configured dependencies to use pre-compiled binaries for faster setup.
|
||||
- **Agnostic Symbol Mapping**: Automatic translation between MT5 (EURUSD) and CCXT (BTC/USDT) formats.
|
||||
|
||||
### 🧪 **New Testing Framework**
|
||||
|
||||
- **Agnostic Bot Validator**: `test_agnostic_bot.py` for cross-platform logic verification.
|
||||
- **CCXT Connection Suite**: Dedicated tools for validating exchange connectivity and demo trading.
|
||||
- **Visual Simulation Engine**: Real-time market mock-up for strategy debugging.
|
||||
|
||||
---
|
||||
|
||||
## 🎯 **Investment Highlights**
|
||||
|
||||
**Why QuantumBotX Stands Out:**
|
||||
|
||||
+22
-16
@@ -1,12 +1,16 @@
|
||||
# QuantumBotX - Quick Start Guide
|
||||
|
||||
## First Time Setup
|
||||
## Setup Your Broker
|
||||
|
||||
1. **Install MetaTrader 5** (Required)
|
||||
- Download from: <https://www.metatrader5.com/>
|
||||
- Install and create a demo account
|
||||
- Keep MT5 running in the background
|
||||
- ⚠️ **IMPORTANT:** MetaTrader 5 must be running for QuantumBotX to work
|
||||
- **Option A: MetaTrader 5 (Forex/Gold)**
|
||||
- Download MT5 from: <https://www.metatrader5.com/>
|
||||
- Install and keep it running in the background.
|
||||
|
||||
- **Option B: Crypto Exchange (Binance/Bybit)**
|
||||
- Create an account on your preferred exchange.
|
||||
- For testing, use **Binance Futures Testnet**.
|
||||
|
||||
⚠️ **Note:** MT5 is only required if you choose to trade Forex/Gold via `MT5` broker type.
|
||||
|
||||
2. **Configure Your Settings**
|
||||
- Copy `.env.example` to `.env`
|
||||
@@ -18,18 +22,20 @@
|
||||
MT5_SERVER=your_server_name
|
||||
```
|
||||
|
||||
3. **Start the Application**
|
||||
3. **Verify Connection**
|
||||
- To test MT5: `python test_mt5_connection.py`
|
||||
- To test Crypto: `python test_ccxt.py`
|
||||
- To simulate without Internet: `python visual_simulation.py`
|
||||
|
||||
4. **Start the Application**
|
||||
- Double-click `start.bat` (Windows)
|
||||
- Open <http://127.0.0.1:5000> in your browser
|
||||
- Open <http://127.0.0.1:5000>
|
||||
|
||||
## ✅ System Requirements
|
||||
|
||||
- **Windows 7 SP1 or later** (64-bit recommended)
|
||||
- **MetaTrader 5** (must be installed separately)
|
||||
- **4GB RAM minimum** (8GB recommended)
|
||||
- **500MB free disk space**
|
||||
- **Internet connection** for initial setup
|
||||
- **❌ Python NOT required** (already bundled in the installer)
|
||||
- **Windows 10/11** (Native MT5 support)
|
||||
- **Linux/MacOS/Docker** (CCXT/Crypto support only)
|
||||
- **Python 3.10 - 3.13**
|
||||
- **4GB RAM minimum**
|
||||
- **Stable internet connection**
|
||||
|
||||
## Daily Use
|
||||
|
||||
|
||||
@@ -1,28 +1,20 @@
|
||||
# 🤖 QuantumBotX — AI-Powered Modular Trading Bot for MT5
|
||||
# 🤖 QuantumBotX — AI-Powered Broker Agnostic Trading Bot
|
||||
|
||||
!MIT License
|
||||
!Python Version
|
||||
!Framework
|
||||
!Made with Love
|
||||
|
||||
Welcome to **QuantumBotX**, your personal, modular, and smart trading assistant built with Python and MetaTrader5 (MT5).
|
||||
Designed to be elegant, powerful, and flexible — whether you're a scalper, swing trader, or a strategy researcher.
|
||||
Welcome to **QuantumBotX**, your personal, modular, and smart trading assistant. Now powered by a **Broker Agnostic Architecture**, allowing you to trade across MetaTrader5 (MT5) and major Crypto Exchanges (Binance, Bybit, etc.) using a single unified interface.
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ Platform Support Notice
|
||||
## 🌍 Platform & Broker Support
|
||||
|
||||
### **Primary Platform: Windows** 🪟
|
||||
### **Flexible Deployment** 🚀
|
||||
- **Windows**: Native support for MT5 and CCXT.
|
||||
- **Linux/Docker/Cloud**: Full support for Crypto Trading via CCXT (no MT5 required!).
|
||||
- **Local MT5**: Requires Windows (or Wine) for Forex/Gold trading.
|
||||
|
||||
This version of QuantumBotX is **optimized for Windows** and requires MetaTrader 5 terminal to be installed locally. It's designed for learning algorithmic trading on your personal computer.
|
||||
|
||||
### **Alternative Platforms** 🔄
|
||||
|
||||
- **Linux**: Can attempt using Wine (experimental - see Linux Setup guide below)
|
||||
- **macOS**: Not officially supported (requires Wine or Windows VM)
|
||||
- **Cloud/VPS**: Not compatible (requires local MT5 terminal)
|
||||
|
||||
> 💡 **Pro Tip**: For cloud deployment and multi-platform support, check out our upcoming **QuantumBotX API** version!
|
||||
### **Supported Brokers** 🏦
|
||||
- **MetaTrader 5**: XM, Exness, FBS, IC Markets, etc.
|
||||
- **Crypto Exchanges (via CCXT)**: Binance (Spot/Futures), Bybit, OKX, and 100+ others.
|
||||
- **Simulation**: Built-in **Mock Broker** for risk-free strategy testing without internet.
|
||||
|
||||
---
|
||||
|
||||
@@ -90,38 +82,28 @@ This version of QuantumBotX is **optimized for Windows** and requires MetaTrader
|
||||
|
||||
---
|
||||
|
||||
## 🚀 Development & Testing Framework
|
||||
### 🧪 **Testing & Simulation Infrastructure**
|
||||
|
||||
### 🧪 **Testing Infrastructure**
|
||||
|
||||
- ✅ **30+ Test Scripts**: Comprehensive testing suite in dedicated `testing/` directory
|
||||
- ✅ **Multi-Broker Testing**: XM Global, Exness, Alpari compatibility validation
|
||||
- ✅ **Strategy Validation**: Individual strategy testing and parameter optimization
|
||||
- ✅ **ATR Education Testing**: Interactive examples and beginner tutorials
|
||||
- ✅ **Crypto Integration Tests**: Bitcoin/Ethereum weekend mode validation
|
||||
- ✅ **Indonesian Market Tests**: XM Indonesia and IDR pairs testing
|
||||
- ✅ **Risk Management Tests**: XAUUSD protection and ATR-based sizing validation
|
||||
- ✅ **Agnostic Testing**: `test_agnostic_bot.py` validates logic across different broker types.
|
||||
- ✅ **Visual Simulation**: `visual_simulation.py` provides a real-time "Mock Market" for strategy debugging.
|
||||
- ✅ **CCXT Validation**: dedicated `test_ccxt.py` and `test_ccxt_order.py` for exchange connectivity.
|
||||
- ✅ **30+ Test Scripts**: Comprehensive testing suite in dedicated `testing/` directory.
|
||||
|
||||
### 🔧 **Development Tools**
|
||||
|
||||
- ✅ **Symbol Migration Tools**: Automatic broker symbol discovery and mapping
|
||||
- ✅ **Bot State Management**: Debug and fix tools for bot recovery
|
||||
- ✅ **Performance Analysis**: Backtesting debugging and optimization tools
|
||||
- ✅ **Market Diagnostics**: Real-time market condition analysis
|
||||
- ✅ **Integration Demos**: Complete workflow demonstrations
|
||||
- ✅ **Broker Factory**: Dynamic adapter switching between MT5, CCXT, and Mock providers.
|
||||
- ✅ **Symbol Discovery**: Automatic mapping between Forex (EURUSD) and Crypto (BTC/USDT) formats.
|
||||
|
||||
> **Note**: All testing scripts are excluded from git repository for clean production deployment
|
||||
|
||||
---
|
||||
|
||||
## 📦 Tech Stack
|
||||
|
||||
- `Python 3.10+`
|
||||
- `Flask` & `TailwindCSS`
|
||||
- `Python 3.10 - 3.13` (Recommended: 3.13 for best library compatibility)
|
||||
- `CCXT` (Crypto Exchange Hybrid Integration)
|
||||
- `Flask` & `Vanilla CSS` (Modern Aesthetic)
|
||||
- `MetaTrader5` Python Integration
|
||||
- `pandas` & `pandas-ta` for data analysis
|
||||
- `Chart.js` for data visualization
|
||||
- `SQLite` for database
|
||||
- `pandas` & `pandas-ta` (Financial Engineering)
|
||||
- `Chart.js` (Simulasi & Result Visualization)
|
||||
- `SQLite` (Local Database)
|
||||
|
||||
---
|
||||
|
||||
@@ -211,14 +193,24 @@ This version of QuantumBotX is **optimized for Windows** and requires MetaTrader
|
||||
|
||||
---
|
||||
|
||||
## 🔐 Environment Variables (`.env`)
|
||||
|
||||
Rename `.env.example` to `.env`, and fill in the following:
|
||||
|
||||
```env
|
||||
# --- BROKER SELECTION ---
|
||||
BROKER_TYPE="MT5" # Options: MT5, CCXT, MOCK
|
||||
|
||||
# --- MT5 CONFIG (If MT5 selected) ---
|
||||
MT5_LOGIN="your_mt5_login"
|
||||
MT5_PASSWORD="your_password"
|
||||
MT5_SERVER="your_broker_server"
|
||||
|
||||
# --- CCXT CONFIG (If CCXT selected) ---
|
||||
EXCHANGE_ID="binance"
|
||||
CCXT_API_KEY="your_api_key"
|
||||
CCXT_API_SECRET="your_api_secret"
|
||||
CCXT_TESTNET=true
|
||||
|
||||
# --- APP CONFIG ---
|
||||
SECRET_KEY="any_flask_secret_key"
|
||||
DB_NAME=bots.db
|
||||
```
|
||||
|
||||
@@ -193,6 +193,7 @@ python run.py
|
||||
|
||||
### Current Features ✅
|
||||
|
||||
- ✅ **Agnostic Architecture**: Support for MT5, CCXT (Binance), and Mock Brokers
|
||||
- ✅ MT5 Integration with 50+ instruments
|
||||
- ✅ 16 Trading strategies with risk management
|
||||
- ✅ AI mentor in Indonesian
|
||||
|
||||
+13
-5
@@ -4,12 +4,20 @@
|
||||
|
||||
## 🎯 **What's Coming Next**
|
||||
|
||||
### **Q4 2025: Intelligence Enhancement**
|
||||
### **Q4 2025: Agnostic Revolution (RELEASED)** 🚀
|
||||
|
||||
We've officially laid the foundation for the **QuantumBotX API** by implementing a Broker Agnostic Architecture:
|
||||
|
||||
- **Advanced AI Features**: Enhanced strategy analysis with machine learning
|
||||
- **Real-time Notifications**: Telegram integration for trade alerts
|
||||
- **Portfolio Analytics**: Advanced performance dashboards
|
||||
- **Enterprise Features**: Multi-account management and compliance logging
|
||||
- ✅ **Multi-Broker Support**: Dynamic switching between MT5, Binance, and Mock adapters via `BrokerFactory`.
|
||||
- ✅ **Cloud Ready**: Run on Linux/Docker for Crypto trading without local MT5.
|
||||
- ✅ **Stateful Simulation**: Real-time mock testing with `visual_simulation.py`.
|
||||
- ✅ **Modern Foundation**: Optimized for Python 3.13 with seamingless dependency management.
|
||||
|
||||
### **Q1 2026: Intelligence Enhancement (NEXT)**
|
||||
|
||||
- 🔄 **Telegram Notifications**: Real-time trade and error alerts.
|
||||
- 🔄 **Portfolio Analytics**: Advanced performance dashboards.
|
||||
- 🔄 **AI Strategy Optimizer**: Automated parameter tuning based on market regime.
|
||||
|
||||
### **Exciting New Project** 🚀
|
||||
|
||||
|
||||
@@ -29,20 +29,21 @@ class CCXTAdapter(BrokerInterface):
|
||||
'options': {'defaultType': 'future'} # Default to futures for bots
|
||||
}
|
||||
|
||||
# Enable testnet if configured
|
||||
if credentials.get('PASSWORD'):
|
||||
config['password'] = credentials.get('PASSWORD')
|
||||
|
||||
# Enable testnet/demo if configured
|
||||
if credentials.get('TESTNET', False):
|
||||
config['options']['demo'] = True
|
||||
if self.exchange_id == 'binance':
|
||||
# Manually point to Futures Testnet URL to be extra safe
|
||||
config['urls'] = {
|
||||
'api': {
|
||||
'public': 'https://testnet.binance.vision/api',
|
||||
'private': 'https://testnet.binance.vision/api',
|
||||
'public': 'https://testnet.binancefuture.com/fapi/v1',
|
||||
'private': 'https://testnet.binancefuture.com/fapi/v1',
|
||||
}
|
||||
}
|
||||
logger.info("Using Binance TESTNET (https://testnet.binance.vision)")
|
||||
# Add other exchange testnet URLs as needed
|
||||
|
||||
if credentials.get('PASSWORD'): # For exchanges like KuCoin
|
||||
config['password'] = credentials.get('PASSWORD')
|
||||
logger.info(f"Using {self.exchange_id} DEMO TRADING mode")
|
||||
|
||||
self.exchange = exchange_class(config)
|
||||
|
||||
|
||||
@@ -7,68 +7,79 @@ from typing import Dict, Any, List, Optional
|
||||
try:
|
||||
from core.utils.mt5 import (
|
||||
initialize_mt5,
|
||||
shutdown_mt5,
|
||||
get_symbol_info,
|
||||
get_rates as get_rates_mt5,
|
||||
place_trade,
|
||||
close_trade,
|
||||
get_open_positions,
|
||||
get_rates_mt5,
|
||||
get_account_info_mt5,
|
||||
get_open_positions_mt5,
|
||||
find_mt5_symbol,
|
||||
TIMEFRAME_MAP,
|
||||
get_todays_profit_mt5
|
||||
)
|
||||
# Corrected import for trade functions
|
||||
from core.mt5.trade import place_trade as mt5_place_trade, close_trade as mt5_close_trade
|
||||
import MetaTrader5 as mt5
|
||||
MT5_AVAILABLE = True
|
||||
except ImportError:
|
||||
MT5_AVAILABLE = False
|
||||
logging.warning("MetaTrader5 module not found. MT5Adapter will not work.")
|
||||
logger.error("MetaTrader5 module or dependencies not found. MT5Adapter will not work.")
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class MT5Adapter(BrokerInterface):
|
||||
"""
|
||||
Adapter for MetaTrader 5 using the official python library.
|
||||
Wraps the functions from core.utils.mt5.
|
||||
Wraps the functions from core.utils.mt5 and core.mt5.trade.
|
||||
"""
|
||||
|
||||
def initialize(self, credentials: Dict[str, Any]) -> bool:
|
||||
# MT5 usually initialized via run.py, but we can support re-init here
|
||||
return True
|
||||
"""Login to MT5 if provided credentials, else assume already initialized."""
|
||||
if not credentials:
|
||||
return mt5.initialize() if MT5_AVAILABLE else False
|
||||
|
||||
login = credentials.get('MT5_LOGIN') or credentials.get('login')
|
||||
password = credentials.get('MT5_PASSWORD') or credentials.get('password')
|
||||
server = credentials.get('MT5_SERVER') or credentials.get('server', 'MetaQuotes-Demo')
|
||||
|
||||
if login and password:
|
||||
return initialize_mt5(int(login), password, server)
|
||||
return mt5.initialize() if MT5_AVAILABLE else False
|
||||
|
||||
def get_account_info(self) -> Optional[Dict[str, Any]]:
|
||||
return get_account_info_mt5()
|
||||
|
||||
def get_rates(self, symbol: str, timeframe: str, count: int = 100) -> pd.DataFrame:
|
||||
# Convert string timeframe (e.g. "H1") to MT5 constant
|
||||
mt5_timeframe = TIMEFRAME_MAP.get(timeframe, mt5.TIMEFRAME_H1)
|
||||
|
||||
# Ensure symbol is valid for this broker
|
||||
valid_symbol = find_mt5_symbol(symbol)
|
||||
if not valid_symbol:
|
||||
logger.error(f"Symbol {symbol} not found in MT5")
|
||||
return pd.DataFrame()
|
||||
|
||||
return get_rates_mt5(valid_symbol, mt5_timeframe, count)
|
||||
|
||||
def get_open_positions(self) -> List[Dict[str, Any]]:
|
||||
return get_open_positions_mt5()
|
||||
mt5_positions = get_open_positions_mt5()
|
||||
standardized_positions = []
|
||||
for pos in mt5_positions:
|
||||
# Map MT5 type (0 for Buy, 1 for Sell) to string
|
||||
standardized_type = 'BUY' if pos.get('type') == mt5.POSITION_TYPE_BUY else 'SELL'
|
||||
pos['type'] = standardized_type
|
||||
standardized_positions.append(pos)
|
||||
return standardized_positions
|
||||
|
||||
def place_order(self, symbol: str, order_type: str, volume: float, price: float = 0.0, sl: float = 0.0, tp: float = 0.0, comment: str = "") -> bool:
|
||||
valid_symbol = find_mt5_symbol(symbol)
|
||||
if not valid_symbol:
|
||||
return False
|
||||
|
||||
# Basic order logic - simplified for adapter POC
|
||||
action = mt5.TRADE_ACTION_DEAL
|
||||
type_op = mt5.ORDER_TYPE_BUY if order_type == 'BUY' else mt5.ORDER_TYPE_SELL
|
||||
mt5_order_type = mt5.ORDER_TYPE_BUY if order_type == 'BUY' else mt5.ORDER_TYPE_SELL
|
||||
|
||||
# We use the existing place_trade logic but wrap the arguments
|
||||
# Wait, the existing place_trade uses ATR multipliers.
|
||||
# For the universal adapter, we want raw SL/TP values.
|
||||
|
||||
request = {
|
||||
"action": action,
|
||||
"action": mt5.TRADE_ACTION_DEAL,
|
||||
"symbol": valid_symbol,
|
||||
"volume": volume,
|
||||
"type": type_op,
|
||||
"type": mt5_order_type,
|
||||
"price": mt5.symbol_info_tick(valid_symbol).ask if order_type == 'BUY' else mt5.symbol_info_tick(valid_symbol).bid,
|
||||
"sl": sl,
|
||||
"tp": tp,
|
||||
@@ -80,21 +91,29 @@ class MT5Adapter(BrokerInterface):
|
||||
}
|
||||
|
||||
result = mt5.order_send(request)
|
||||
if result.retcode != mt5.TRADE_RETCODE_DONE:
|
||||
logger.error(f"Order failed: {result.comment}")
|
||||
if result is None or result.retcode != mt5.TRADE_RETCODE_DONE:
|
||||
logger.error(f"Order failed: {result.comment if result else 'No result'}")
|
||||
return False
|
||||
|
||||
logger.info(f"Order placed: {result.order}")
|
||||
return True
|
||||
|
||||
def close_position(self, position_id: str, volume: float = 0.0) -> bool:
|
||||
def close_position(self, ticket_id: Any, volume: float = 0.0) -> bool:
|
||||
"""Close an existing position in MT5."""
|
||||
try:
|
||||
# Logic to close position...
|
||||
# For now, returning False as placeholder
|
||||
pass
|
||||
except:
|
||||
pass
|
||||
return False
|
||||
# Find the position by ticket
|
||||
positions = mt5.positions_get(ticket=int(ticket_id))
|
||||
if not positions:
|
||||
logger.warning(f"Position #{ticket_id} not found to close.")
|
||||
return False
|
||||
|
||||
position = positions[0]
|
||||
# Use the existing close_trade utility
|
||||
result, msg = mt5_close_trade(position)
|
||||
return result is not None
|
||||
except Exception as e:
|
||||
logger.error(f"Error closing position {ticket_id}: {e}")
|
||||
return False
|
||||
|
||||
def get_symbol_info(self, symbol: str) -> Optional[Dict[str, Any]]:
|
||||
valid_symbol = find_mt5_symbol(symbol)
|
||||
|
||||
@@ -6,8 +6,7 @@ import logging
|
||||
from datetime import datetime
|
||||
from core.strategies.strategy_map import STRATEGY_MAP
|
||||
from core.factory.broker_factory import BrokerFactory
|
||||
# from core.mt5.trade import place_trade, close_trade <-- DEPRECATED
|
||||
from core.utils.mt5 import TIMEFRAME_MAP # Keep for now or move to adapter
|
||||
# from core.mt5.trade import place_trade, close_trade # DEPRECATED
|
||||
# AI Mentor Integration
|
||||
from core.db.models import log_trade_for_ai_analysis
|
||||
# Holiday and market hours management
|
||||
@@ -37,9 +36,6 @@ class TradingBot(threading.Thread):
|
||||
self.last_analysis = {"signal": "MEMUAT", "explanation": "Bot sedang memulai, menunggu analisis pertama..."}
|
||||
self._stop_event = threading.Event()
|
||||
self.strategy_instance = None
|
||||
self.strategy_instance = None
|
||||
# Gunakan map yang diimpor untuk menjaga konsistensi
|
||||
self.tf_map = TIMEFRAME_MAP
|
||||
|
||||
# Initialize Broker Adapter
|
||||
if broker:
|
||||
@@ -241,7 +237,7 @@ class TradingBot(threading.Thread):
|
||||
# Logika untuk sinyal BUY
|
||||
if signal == 'BUY':
|
||||
# Jika ada posisi SELL, tutup dulu
|
||||
if position and position.get('type') == 1: # 1 is SELL in MT5, Adapter should standardize this later
|
||||
if position and position.get('type') == 'SELL':
|
||||
self.log_activity('CLOSE SELL', "Menutup posisi JUAL untuk membuka posisi BELI.", is_notification=True)
|
||||
|
||||
# Log untuk AI mentor analysis
|
||||
@@ -266,7 +262,7 @@ class TradingBot(threading.Thread):
|
||||
# Logika untuk sinyal SELL
|
||||
elif signal == 'SELL':
|
||||
# Jika ada posisi BUY, tutup dulu
|
||||
if position and position.get('type') == 0: # 0 is BUY in MT5
|
||||
if position and position.get('type') == 'BUY':
|
||||
self.log_activity('CLOSE BUY', "Menutup posisi BELI untuk membuka posisi JUAL.", is_notification=True)
|
||||
|
||||
# Log untuk AI mentor analysis
|
||||
|
||||
@@ -9,6 +9,7 @@ from typing import Dict, Optional, List
|
||||
from enum import Enum
|
||||
|
||||
from .base_broker import BaseBroker
|
||||
from .mt5_broker import MT5Broker
|
||||
from .binance_broker import BinanceBroker
|
||||
from .ctrader_broker import CTraderBroker
|
||||
from .interactive_brokers import InteractiveBrokersBroker
|
||||
@@ -65,7 +66,9 @@ class BrokerFactory:
|
||||
config = broker_config['config']
|
||||
|
||||
try:
|
||||
if broker_type == BrokerType.BINANCE:
|
||||
if broker_type == BrokerType.MT5:
|
||||
broker = MT5Broker()
|
||||
elif broker_type == BrokerType.BINANCE:
|
||||
broker = BinanceBroker(testnet=config.get('testnet', True))
|
||||
elif broker_type == BrokerType.BINANCE_FUTURES:
|
||||
# Future implementation
|
||||
|
||||
@@ -0,0 +1,198 @@
|
||||
# core/brokers/mt5_broker.py
|
||||
"""
|
||||
MetaTrader 5 Broker Implementation
|
||||
Connects QuantumBotX to MT5 terminals via the universal BaseBroker interface.
|
||||
"""
|
||||
|
||||
import logging
|
||||
import pandas as pd
|
||||
import MetaTrader5 as mt5
|
||||
from typing import Dict, List, Optional, Union
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
from .base_broker import BaseBroker, OrderType, OrderStatus, Timeframe, Position, Order, AccountInfo
|
||||
from core.utils.mt5 import get_rates_mt5, TIMEFRAME_MAP as MT5_TIMEFRAME_MAP
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class MT5Broker(BaseBroker):
|
||||
"""
|
||||
MT5 Implementation of the BaseBroker.
|
||||
Wraps MetaTrader5 library calls into a unified API.
|
||||
"""
|
||||
|
||||
def __init__(self, broker_name: str = "MetaTrader 5"):
|
||||
super().__init__(broker_name)
|
||||
self.timeframe_map = {
|
||||
Timeframe.M1: mt5.TIMEFRAME_M1,
|
||||
Timeframe.M5: mt5.TIMEFRAME_M5,
|
||||
Timeframe.M15: mt5.TIMEFRAME_M15,
|
||||
Timeframe.M30: mt5.TIMEFRAME_M30,
|
||||
Timeframe.H1: mt5.TIMEFRAME_H1,
|
||||
Timeframe.H4: mt5.TIMEFRAME_H4,
|
||||
Timeframe.D1: mt5.TIMEFRAME_D1
|
||||
}
|
||||
|
||||
def connect(self, credentials: Dict) -> bool:
|
||||
"""Connect to MT5 terminal"""
|
||||
try:
|
||||
login = credentials.get('login')
|
||||
password = credentials.get('password')
|
||||
server = credentials.get('server', 'MetaQuotes-Demo')
|
||||
|
||||
if not mt5.initialize(login=int(login), password=password, server=server):
|
||||
logger.error(f"MT5 initialization failed: {mt5.last_error()}")
|
||||
self.is_connected = False
|
||||
return False
|
||||
|
||||
self.is_connected = True
|
||||
self.supported_symbols = [s.name for s in mt5.symbols_get()]
|
||||
logger.info("MT5 connected successfully.")
|
||||
return True
|
||||
except Exception as e:
|
||||
logger.error(f"Error connecting to MT5: {e}")
|
||||
return False
|
||||
|
||||
def disconnect(self) -> bool:
|
||||
"""Disconnect from MT5"""
|
||||
mt5.shutdown()
|
||||
self.is_connected = False
|
||||
return True
|
||||
|
||||
def get_symbols(self) -> List[str]:
|
||||
"""Get list of available trading symbols"""
|
||||
if not self.is_connected:
|
||||
return []
|
||||
symbols = mt5.symbols_get()
|
||||
return [s.name for s in symbols] if symbols else []
|
||||
|
||||
def get_market_data(self, symbol: str, timeframe: Timeframe,
|
||||
count: int = 500) -> pd.DataFrame:
|
||||
"""Get OHLCV market data from MT5"""
|
||||
mt5_tf = self.timeframe_map.get(timeframe, mt5.TIMEFRAME_H1)
|
||||
return get_rates_mt5(symbol, mt5_tf, count)
|
||||
|
||||
def get_current_price(self, symbol: str) -> Dict[str, float]:
|
||||
"""Get current bid/ask prices"""
|
||||
tick = mt5.symbol_info_tick(symbol)
|
||||
if tick:
|
||||
return {"bid": tick.bid, "ask": tick.ask}
|
||||
return {"bid": 0.0, "ask": 0.0}
|
||||
|
||||
def place_order(self, symbol: str, order_type: OrderType, side: str,
|
||||
size: float, price: Optional[float] = None,
|
||||
stop_loss: Optional[float] = None,
|
||||
take_profit: Optional[float] = None) -> Optional[Order]:
|
||||
"""Place a trading order in MT5"""
|
||||
if not self.is_connected:
|
||||
return None
|
||||
|
||||
# Map OrderType to MT5 constant
|
||||
mt5_type = None
|
||||
if order_type == OrderType.MARKET_BUY:
|
||||
mt5_type = mt5.ORDER_TYPE_BUY
|
||||
elif order_type == OrderType.MARKET_SELL:
|
||||
mt5_type = mt5.ORDER_TYPE_SELL
|
||||
# ... support other types as needed
|
||||
|
||||
if mt5_type is None:
|
||||
logger.error(f"Unsupported order type for MT5: {order_type}")
|
||||
return None
|
||||
|
||||
curr_price = price or (self.get_current_price(symbol)['ask'] if mt5_type == mt5.ORDER_TYPE_BUY else self.get_current_price(symbol)['bid'])
|
||||
|
||||
request = {
|
||||
"action": mt5.TRADE_ACTION_DEAL,
|
||||
"symbol": symbol,
|
||||
"volume": size,
|
||||
"type": mt5_type,
|
||||
"price": curr_price,
|
||||
"sl": stop_loss or 0.0,
|
||||
"tp": take_profit or 0.0,
|
||||
"magic": 2024001, # Default magic number
|
||||
"comment": "QuantumBotX Trade",
|
||||
"type_time": mt5.ORDER_TIME_GTC,
|
||||
"type_filling": mt5.ORDER_FILLING_FOK,
|
||||
}
|
||||
|
||||
result = mt5.order_send(request)
|
||||
if result and result.retcode == mt5.TRADE_RETCODE_DONE:
|
||||
order = Order(str(result.order), symbol, order_type, side, size, curr_price)
|
||||
order.status = OrderStatus.FILLED
|
||||
return order
|
||||
else:
|
||||
logger.error(f"MT5 Order failed: {result.comment if result else 'Unknown error'}")
|
||||
return None
|
||||
|
||||
def cancel_order(self, order_id: str) -> bool:
|
||||
"""Cancel an existing order (MT5 usually handles this via close or delete pending)"""
|
||||
# Simplistic implementation for now
|
||||
return False
|
||||
|
||||
def get_positions(self) -> List[Position]:
|
||||
"""Get all open positions from MT5"""
|
||||
mt5_positions = mt5.positions_get()
|
||||
positions = []
|
||||
if mt5_positions:
|
||||
for p in mt5_positions:
|
||||
side = 'long' if p.type == mt5.POSITION_TYPE_BUY else 'short'
|
||||
positions.append(Position(
|
||||
symbol=p.symbol,
|
||||
side=side,
|
||||
size=p.volume,
|
||||
entry_price=p.price_open,
|
||||
current_price=p.price_current,
|
||||
unrealized_pnl=p.profit
|
||||
))
|
||||
return positions
|
||||
|
||||
def get_orders(self) -> List[Order]:
|
||||
"""Get all pending orders from MT5"""
|
||||
mt5_orders = mt5.orders_get()
|
||||
orders = []
|
||||
if mt5_orders:
|
||||
for o in mt5_orders:
|
||||
# Map MT5 order types back to our OrderType
|
||||
# This is a simplification
|
||||
order_type = OrderType.LIMIT_BUY if o.type == mt5.ORDER_TYPE_BUY_LIMIT else OrderType.LIMIT_SELL
|
||||
orders.append(Order(
|
||||
order_id=str(o.ticket),
|
||||
symbol=o.symbol,
|
||||
order_type=order_type,
|
||||
side='buy' if 'BUY' in order_type.name else 'sell',
|
||||
size=o.volume_initial,
|
||||
price=o.price_open
|
||||
))
|
||||
return orders
|
||||
|
||||
def get_account_info(self) -> AccountInfo:
|
||||
"""Get account information from MT5"""
|
||||
inf = mt5.account_info()
|
||||
if inf:
|
||||
return AccountInfo(
|
||||
balance=inf.balance,
|
||||
equity=inf.equity,
|
||||
margin=inf.margin,
|
||||
free_margin=inf.margin_free,
|
||||
margin_level=inf.margin_level,
|
||||
currency=inf.currency
|
||||
)
|
||||
return AccountInfo(0, 0, 0, 0, 0)
|
||||
|
||||
def get_trade_history(self, days: int = 30) -> List[Dict]:
|
||||
"""Get trade history from MT5"""
|
||||
from_date = datetime.now() - timedelta(days=days)
|
||||
history = mt5.history_deals_get(from_date, datetime.now())
|
||||
deals = []
|
||||
if history:
|
||||
for d in history:
|
||||
deals.append({
|
||||
"ticket": d.ticket,
|
||||
"symbol": d.symbol,
|
||||
"type": d.type,
|
||||
"volume": d.volume,
|
||||
"price": d.price,
|
||||
"profit": d.profit,
|
||||
"time": datetime.fromtimestamp(d.time)
|
||||
})
|
||||
return deals
|
||||
@@ -0,0 +1 @@
|
||||
MetaTrader5==5.0.5120
|
||||
+3
-4
@@ -8,10 +8,9 @@ idna==3.10
|
||||
itsdangerous==2.2.0
|
||||
Jinja2==3.1.6
|
||||
MarkupSafe==3.0.2
|
||||
MetaTrader5==5.0.5120
|
||||
numpy==1.23.5
|
||||
pandas==2.3.1
|
||||
pandas_ta==0.3.14b0
|
||||
numpy>=2.2.6
|
||||
pandas>=2.2.3
|
||||
pandas-ta
|
||||
python-dateutil==2.9.0.post0
|
||||
python-dotenv==1.1.1
|
||||
pytz==2025.2
|
||||
|
||||
@@ -0,0 +1,142 @@
|
||||
|
||||
import logging
|
||||
import pandas as pd
|
||||
from core.bots.trading_bot import TradingBot
|
||||
from core.interfaces.broker_interface import BrokerInterface
|
||||
|
||||
# Configure logging
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
|
||||
class MockBroker(BrokerInterface):
|
||||
def __init__(self):
|
||||
self.positions = []
|
||||
self._ticket_counter = 1000
|
||||
|
||||
def initialize(self, credentials):
|
||||
return True
|
||||
|
||||
def get_account_info(self):
|
||||
return {'balance': 10000, 'equity': 10000}
|
||||
|
||||
def get_rates(self, symbol, timeframe, count=100):
|
||||
# Mengembalikan DataFrame dengan format yang tepat
|
||||
# Dibuat lebih banyak baris (count) untuk simulasi indikator strategi
|
||||
data = {
|
||||
'time': pd.date_range(end=pd.Timestamp.now(), periods=count, freq='H'),
|
||||
'open': [1.0] * count,
|
||||
'high': [1.1] * count,
|
||||
'low': [0.9] * count,
|
||||
'close': [1.05] * count,
|
||||
'tick_volume': [100] * count
|
||||
}
|
||||
return pd.DataFrame(data)
|
||||
|
||||
def get_open_positions(self):
|
||||
return self.positions
|
||||
|
||||
def place_order(self, symbol: str, order_type: str, volume: float, price: float = 0.0, sl: float = 0.0, tp: float = 0.0, comment: str = ""):
|
||||
self._ticket_counter += 1
|
||||
# Ekstrak magic number dari comment (Bot-ID) agar bot bisa mengenali posisinya
|
||||
magic = int(comment.split('-')[1]) if 'Bot-' in comment else 0
|
||||
|
||||
new_pos = {
|
||||
'ticket': self._ticket_counter,
|
||||
'symbol': symbol,
|
||||
'type': order_type,
|
||||
'volume': volume,
|
||||
'price': price,
|
||||
'sl': sl,
|
||||
'tp': tp,
|
||||
'magic': magic,
|
||||
'profit': 10.5 # Dummy profit
|
||||
}
|
||||
self.positions.append(new_pos)
|
||||
print(f"MOCK ORDER PLACED: {new_pos}")
|
||||
return True
|
||||
|
||||
def close_position(self, position_id, volume: float = 0.0):
|
||||
# Menghapus posisi dari list internal berdasarkan ticket
|
||||
self.positions = [p for p in self.positions if str(p['ticket']) != str(position_id)]
|
||||
print(f"MOCK POSITION CLOSED: ID {position_id}")
|
||||
return True
|
||||
|
||||
def get_symbol_info(self, symbol):
|
||||
return {'name': symbol, 'digits': 5}
|
||||
|
||||
def get_todays_profit(self):
|
||||
return 50.0
|
||||
|
||||
def test_bot_initialization():
|
||||
print("Testing Bot initialization with Mock Broker...")
|
||||
mock_broker = MockBroker()
|
||||
bot = TradingBot(
|
||||
id=999,
|
||||
name="MockBot",
|
||||
market="EURUSD",
|
||||
risk_percent=1.0,
|
||||
sl_pips=50,
|
||||
tp_pips=100,
|
||||
timeframe="H1",
|
||||
check_interval=1,
|
||||
strategy="MA_CROSSOVER", # Real strategy from STRATEGY_MAP
|
||||
broker=mock_broker
|
||||
)
|
||||
|
||||
# We won't actually start the thread in this test to avoid loop
|
||||
print(f"Bot '{bot.name}' initialized successfully with broker {bot.broker.__class__.__name__}")
|
||||
assert bot.broker == mock_broker
|
||||
print("Initialization Test passed!")
|
||||
|
||||
def test_full_trade_loop():
|
||||
print("\n--- Testing Full Trade Loop (Simulation) ---")
|
||||
mock_broker = MockBroker()
|
||||
|
||||
# Setup bot
|
||||
bot = TradingBot(
|
||||
id=777,
|
||||
name="LoopTester",
|
||||
market="EURUSD",
|
||||
risk_percent=0.1,
|
||||
sl_pips=50,
|
||||
tp_pips=100,
|
||||
timeframe="H1",
|
||||
check_interval=0.1,
|
||||
strategy="MA_CROSSOVER",
|
||||
broker=mock_broker
|
||||
)
|
||||
|
||||
# Pre-setup manually for testing internal logic
|
||||
bot.market_for_mt5 = "EURUSD"
|
||||
from core.strategies.strategy_map import STRATEGY_MAP
|
||||
bot.strategy_instance = STRATEGY_MAP["MA_CROSSOVER"](bot_instance=bot)
|
||||
|
||||
# 1. Simulasikan Sinyal BUY
|
||||
print("\n[Step 1] Simulating BUY Signal...")
|
||||
# Bot handle signal (no position yet)
|
||||
bot._handle_trade_signal('BUY', None)
|
||||
|
||||
positions = mock_broker.get_open_positions()
|
||||
assert len(positions) == 1
|
||||
assert positions[0]['type'] == 'BUY'
|
||||
print(f"Verified: Position opened successfully. Ticket: {positions[0]['ticket']}")
|
||||
|
||||
# 2. Simulasikan Sinyal SELL (Bot harus tutup BUY dulu baru buka SELL)
|
||||
print("\n[Step 2] Simulating SELL Signal while BUY is open...")
|
||||
# Get current position like the bot loop does
|
||||
current_pos = bot._get_open_position()
|
||||
assert current_pos is not None
|
||||
|
||||
# Trigger signal handler
|
||||
bot._handle_trade_signal('SELL', current_pos)
|
||||
|
||||
# Verify positions after switch
|
||||
positions = mock_broker.get_open_positions()
|
||||
assert len(positions) == 1
|
||||
assert positions[0]['type'] == 'SELL'
|
||||
print("Verification: Old BUY position was closed and new SELL position was opened!")
|
||||
|
||||
print("\nFull Trade Loop Test Passed!")
|
||||
|
||||
if __name__ == "__main__":
|
||||
test_bot_initialization()
|
||||
test_full_trade_loop()
|
||||
+29
-36
@@ -1,49 +1,42 @@
|
||||
import logging
|
||||
import os
|
||||
from dotenv import load_dotenv
|
||||
from core.factory.broker_factory import BrokerFactory
|
||||
|
||||
# Configure logging
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
logger = logging.getLogger("TestCCXT")
|
||||
load_dotenv()
|
||||
|
||||
def test_ccxt():
|
||||
logger.info("1. Requesting CCXT adapter from Factory...")
|
||||
def test_ccxt_minimal():
|
||||
# 1. Pastikan env terisi
|
||||
exchange = os.getenv('EXCHANGE_ID', 'binance')
|
||||
logger.info(f"Targeting Exchange: {exchange}")
|
||||
|
||||
# 2. Ambil dari Factory (Factory akan otomatis inisialisasi pake .env)
|
||||
try:
|
||||
broker = BrokerFactory.get_broker('CCXT')
|
||||
logger.info(" Success: Got CCXTAdapter instance.")
|
||||
except Exception as e:
|
||||
logger.error(f" Failed: {e}")
|
||||
return
|
||||
|
||||
logger.info("2. Initializing connection (Binance Public)...")
|
||||
# No keys needed for public data
|
||||
creds = {
|
||||
'EXCHANGE_ID': 'binance',
|
||||
'API_KEY': '',
|
||||
'API_SECRET': ''
|
||||
}
|
||||
if broker.initialize(creds):
|
||||
logger.info(" Success: Connected to Binance.")
|
||||
else:
|
||||
logger.error(" Failed: Could not connect.")
|
||||
return
|
||||
|
||||
logger.info("3. Fetching Rates for BTC/USDT...")
|
||||
try:
|
||||
df = broker.get_rates('BTC/USDT', 'H1', 10)
|
||||
if not df.empty:
|
||||
logger.info(f" Success: Fetched {len(df)} rows.")
|
||||
print(df.head())
|
||||
broker = BrokerFactory.get_broker('CCXT', exchange_id=exchange)
|
||||
if broker and broker.exchange:
|
||||
logger.info(f"✅ Instance {broker.__class__.__name__} ready.")
|
||||
else:
|
||||
logger.error(" Failed: DataFrame is empty.")
|
||||
logger.error("❌ Failed to get initialized broker instance.")
|
||||
return
|
||||
except Exception as e:
|
||||
logger.error(f" Failed: {e}")
|
||||
logger.error(f"❌ Factory Error: {e}")
|
||||
return
|
||||
|
||||
logger.info("4. Getting Symbol Info...")
|
||||
info = broker.get_symbol_info('BTC/USDT')
|
||||
if info:
|
||||
logger.info(f" Success: {info}")
|
||||
else:
|
||||
logger.error(" Failed: Could not get symbol info.")
|
||||
# 3. Test Ambil Data (Public)
|
||||
symbol = 'BTC/USDT'
|
||||
logger.info(f"Fetching Rates for {symbol}...")
|
||||
try:
|
||||
df = broker.get_rates(symbol, 'H1', 5)
|
||||
if not df.empty:
|
||||
logger.info("✅ Profit! Data received:")
|
||||
print(df[['time', 'close', 'tick_volume']].tail())
|
||||
else:
|
||||
logger.warning("⚠️ Connected, but DataFrame is empty. Check if symbol exists.")
|
||||
except Exception as e:
|
||||
logger.error(f"❌ Request Error: {e}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
test_ccxt()
|
||||
test_ccxt_minimal()
|
||||
@@ -0,0 +1,64 @@
|
||||
import logging
|
||||
import os
|
||||
from dotenv import load_dotenv
|
||||
from core.factory.broker_factory import BrokerFactory
|
||||
|
||||
# Configure logging
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
logger = logging.getLogger("FirstOrder")
|
||||
load_dotenv()
|
||||
|
||||
def test_place_live_order():
|
||||
exchange_id = os.getenv('EXCHANGE_ID', 'binance')
|
||||
logger.info(f"1. Init Broker: {exchange_id} (Testnet)")
|
||||
|
||||
try:
|
||||
# BrokerFactory akan inisialisasi pake .env (API Key, Secret, dll)
|
||||
broker = BrokerFactory.get_broker('CCXT', exchange_id=exchange_id)
|
||||
|
||||
if not broker or not broker.exchange:
|
||||
logger.error("❌ Gagal inisialisasi Broker. Cek .env Anda!")
|
||||
return
|
||||
|
||||
symbol = 'BTC/USDT'
|
||||
logger.info(f"2. Mengirim Order: BUY {symbol}")
|
||||
|
||||
# Kita coba beli jumlah kecil, misal 0.01 BTC (satuan kontrak di Futures)
|
||||
success = broker.place_order(
|
||||
symbol=symbol,
|
||||
order_type='BUY',
|
||||
volume=0.01,
|
||||
comment="Order dari QuantumBotX"
|
||||
)
|
||||
|
||||
if success:
|
||||
logger.info("✅ BERHASIL! Order telah dieksekusi di Exchange.")
|
||||
|
||||
# Beri jeda sebentar agar data di bursa ter-update
|
||||
import time
|
||||
logger.info("Menunggu data posisi terupdate...")
|
||||
time.sleep(2)
|
||||
|
||||
# Cek daftar posisi terbuka
|
||||
positions = broker.get_open_positions()
|
||||
print("\n" + "="*50)
|
||||
print("POSISI TERBUKA SAAT INI:")
|
||||
print("="*50)
|
||||
if not positions:
|
||||
print("Tidak ada posisi aktif (mungkin langsung tertutup atau error).")
|
||||
for pos in positions:
|
||||
side_str = "BUY (Long)" if pos['type'] == 0 else "SELL (Short)"
|
||||
print(f"- Symbol: {pos['symbol']}")
|
||||
print(f" Type : {side_str}")
|
||||
print(f" Volume: {pos['volume']}")
|
||||
print(f" Price : {pos['price']}")
|
||||
print(f" Profit: {pos['profit']} USDT")
|
||||
print("-" * 20)
|
||||
else:
|
||||
logger.error("❌ Gagal menempatkan order. Cek log error di atas.")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"❌ Terjadi kesalahan fatal: {e}", exc_info=True)
|
||||
|
||||
if __name__ == "__main__":
|
||||
test_place_live_order()
|
||||
@@ -0,0 +1,135 @@
|
||||
import logging
|
||||
import time
|
||||
import pandas as pd
|
||||
import random
|
||||
from core.bots.trading_bot import TradingBot
|
||||
from core.interfaces.broker_interface import BrokerInterface
|
||||
from core.strategies.strategy_map import STRATEGY_MAP
|
||||
|
||||
# 1. Setup Logging yang Cantik
|
||||
logging.basicConfig(
|
||||
level=logging.INFO,
|
||||
format='%(asctime)s | %(levelname)-7s | %(message)s',
|
||||
datefmt='%H:%M:%S'
|
||||
)
|
||||
logger = logging.getLogger("QuantumSim")
|
||||
|
||||
# 2. MockBroker yang Lebih Hebat (Bisa update harga)
|
||||
class AdvancedMockBroker(BrokerInterface):
|
||||
def __init__(self):
|
||||
self.positions = []
|
||||
self._ticket_counter = 5000
|
||||
self.current_price = 88000.0
|
||||
self.history = []
|
||||
# Generate some initial history
|
||||
for i in range(100):
|
||||
self.current_price += random.uniform(-100, 100)
|
||||
self.history.append(self.current_price)
|
||||
|
||||
def initialize(self, credentials): return True
|
||||
def get_account_info(self): return {'balance': 100000, 'equity': 100000}
|
||||
|
||||
def get_rates(self, symbol, timeframe, count=100):
|
||||
# Update harga sedikit setiap kali dipanggil agar simulasi terasa hidup
|
||||
self.current_price += random.uniform(-150, 150)
|
||||
self.history.append(self.current_price)
|
||||
last_prices = self.history[-count:]
|
||||
|
||||
df = pd.DataFrame({
|
||||
'time': pd.date_range(end=pd.Timestamp.now(), periods=len(last_prices), freq='H'),
|
||||
'open': [p * 0.999 for p in last_prices],
|
||||
'high': [p * 1.002 for p in last_prices],
|
||||
'low': [p * 0.998 for p in last_prices],
|
||||
'close': last_prices,
|
||||
'tick_volume': [random.randint(1000, 5000) for _ in last_prices]
|
||||
})
|
||||
return df
|
||||
|
||||
def get_open_positions(self): return self.positions
|
||||
|
||||
def place_order(self, symbol, order_type, volume, price=0.0, sl=0.0, tp=0.0, comment=""):
|
||||
self._ticket_counter += 1
|
||||
magic = int(comment.split('-')[1]) if 'Bot-' in comment else 0
|
||||
new_pos = {
|
||||
'ticket': self._ticket_counter,
|
||||
'symbol': symbol,
|
||||
'type': order_type,
|
||||
'volume': volume,
|
||||
'price': self.current_price,
|
||||
'sl': sl, 'tp': tp, 'magic': magic,
|
||||
'profit': 0.0
|
||||
}
|
||||
self.positions.append(new_pos)
|
||||
logger.info(f"✨ [BROKER] ORDER BERHASIL: {order_type} {symbol} @ {self.current_price:.2f}")
|
||||
return True
|
||||
|
||||
def close_position(self, position_id, volume=0.0):
|
||||
self.positions = [p for p in self.positions if str(p['ticket']) != str(position_id)]
|
||||
logger.info(f"🛑 [BROKER] POSISI DITUTUP: ID {position_id}")
|
||||
return True
|
||||
|
||||
def get_symbol_info(self, symbol): return {'name': symbol, 'digits': 2}
|
||||
def get_todays_profit(self): return 120.50
|
||||
|
||||
# 3. Script Simulasi Utama
|
||||
def run_visual_simulation():
|
||||
print("\n" + "="*60)
|
||||
print(" QUANTUM BOT X - LIVE SIMULATION MODE (MOCK) ")
|
||||
print("="*60)
|
||||
|
||||
broker = AdvancedMockBroker()
|
||||
|
||||
bot = TradingBot(
|
||||
id=1337,
|
||||
name="UltraBot-Sim",
|
||||
market="BTC/USDT",
|
||||
risk_percent=0.05,
|
||||
sl_pips=1000,
|
||||
tp_pips=2000,
|
||||
timeframe="H1",
|
||||
check_interval=2, # Cek setiap 2 detik
|
||||
strategy="MA_CROSSOVER", # Pake strategi asli
|
||||
broker=broker
|
||||
)
|
||||
|
||||
# Kita jalankan loop bot secara manual agar bisa kita batasi jumlah iterasinya
|
||||
# (Biasanya bot.start() akan jalan selamanya di thread terpisah)
|
||||
|
||||
# Setup Strategy Instance (biasanya dilakukan di bot.run())
|
||||
bot.market_for_mt5 = "BTC/USDT"
|
||||
bot.strategy_instance = STRATEGY_MAP["MA_CROSSOVER"](bot_instance=bot)
|
||||
|
||||
print(f"Bot '{bot.name}' Ready. Menggunakan Strategi: {bot.strategy_name}")
|
||||
print("Memulai simulasi 10 iterasi...\n")
|
||||
|
||||
for i in range(1, 11):
|
||||
print(f"\n--- Iterasi {i}/10 | Harga Saat Ini: {broker.current_price:.2f} ---")
|
||||
|
||||
# Ambil data kandel
|
||||
df = broker.get_rates(bot.market_for_mt5, bot.timeframe, 50)
|
||||
|
||||
# Analisis Strategi
|
||||
bot.last_analysis = bot.strategy_instance.analyze(df)
|
||||
|
||||
# PAKSA SINYAL untuk demo agar terlihat log-nya
|
||||
if i == 2: signal = 'BUY'
|
||||
elif i == 5: signal = 'SELL'
|
||||
elif i == 8: signal = 'BUY'
|
||||
else: signal = bot.last_analysis.get('signal', 'HOLD')
|
||||
|
||||
logger.info(f"Analisis: {signal} | Penjelasan: {bot.last_analysis.get('explanation', 'Manual Override for Demo' if i in [2,5,8] else '')}")
|
||||
|
||||
# Eksekusi (Logika di TradingBot._handle_trade_signal)
|
||||
posisi_sekarang = bot._get_open_position()
|
||||
bot._handle_trade_signal(signal, posisi_sekarang)
|
||||
|
||||
# Kasih jeda biar enak dilihat
|
||||
time.sleep(1.5)
|
||||
|
||||
print("\n" + "="*60)
|
||||
print(" SIMULASI SELESAI! ")
|
||||
print("="*60)
|
||||
print("Bot berhasil mensimulasikan logika trading tanpa menyentuh dana asli.")
|
||||
|
||||
if __name__ == "__main__":
|
||||
run_visual_simulation()
|
||||
Reference in New Issue
Block a user