mirror of
https://github.com/FxPouya/FxMathQuantWebApp.git
synced 2026-07-27 18:27:44 +00:00
Add files via upload
This commit is contained in:
@@ -0,0 +1,216 @@
|
||||
# FxMathQuant-Web Deployment Checklist
|
||||
|
||||
## ✅ Pre-Deployment Checklist
|
||||
|
||||
### Files Ready for GitHub Pages
|
||||
|
||||
- [x] DataProvider EAs copied to `downloads/` folder
|
||||
- `FxMathQuant_DataExporter_MT4.ex4`
|
||||
- `FxMathQuant_DataExporter_MT5.ex5`
|
||||
- `README.md` (DataProvider guide)
|
||||
|
||||
- [x] User Manual created
|
||||
- `USER_MANUAL.md` - Comprehensive guide
|
||||
|
||||
- [x] Contact Information Updated
|
||||
- Email: fxmathsolution@gmail.com
|
||||
- Telegram: https://t.me/FxMath
|
||||
|
||||
- [x] Logout Button Added
|
||||
- Located in header (top-right)
|
||||
- Icon: 🚪
|
||||
- Function: `logoutLicense()`
|
||||
|
||||
- [x] Session Management
|
||||
- **Duration**: Persists until manual logout or browser data clear
|
||||
- **No auto-logout**: Session remains active
|
||||
- **Stored in**: localStorage
|
||||
|
||||
- [x] CSV Upload Fix
|
||||
- Accepts capitalized column names (Time, Open, High, Low, Close)
|
||||
- Compatible with MT4/MT5 DataProvider EA exports
|
||||
|
||||
- [x] Cache Busting
|
||||
- `main.js?v=2.0` - Forces browser to load updated file
|
||||
|
||||
## 📋 Deployment Steps
|
||||
|
||||
### 1. Create GitHub Repository
|
||||
|
||||
```bash
|
||||
# On GitHub.com
|
||||
1. Click "New Repository"
|
||||
2. Name: FxMathQuant-Web
|
||||
3. Visibility: Public
|
||||
4. Add README: Yes
|
||||
5. Click "Create repository"
|
||||
```
|
||||
|
||||
### 2. Enable GitHub Pages
|
||||
|
||||
```bash
|
||||
# In repository settings
|
||||
1. Go to Settings → Pages
|
||||
2. Source: Deploy from a branch
|
||||
3. Branch: main
|
||||
4. Folder: / (root)
|
||||
5. Click "Save"
|
||||
```
|
||||
|
||||
### 3. Upload Files
|
||||
|
||||
**Option A: Web Interface**
|
||||
```bash
|
||||
1. Click "Add file" → "Upload files"
|
||||
2. Drag entire FxMathQuant-Web folder contents
|
||||
3. Commit message: "Initial deployment"
|
||||
4. Click "Commit changes"
|
||||
```
|
||||
|
||||
**Option B: Git Command Line**
|
||||
```bash
|
||||
cd /home/fxmathai/Desktop/2025-12/FxMathQuant-Web-Version/FxMathQuant-Web
|
||||
|
||||
git init
|
||||
git add .
|
||||
git commit -m "Initial deployment"
|
||||
git branch -M main
|
||||
git remote add origin https://github.com/YOUR_USERNAME/FxMathQuant-Web.git
|
||||
git push -u origin main
|
||||
```
|
||||
|
||||
### 4. Wait for Deployment
|
||||
|
||||
- GitHub Actions will build and deploy (1-2 minutes)
|
||||
- Check "Actions" tab for progress
|
||||
- Green checkmark = deployed successfully
|
||||
|
||||
### 5. Test Live Site
|
||||
|
||||
```
|
||||
https://YOUR_USERNAME.github.io/FxMathQuant-Web/
|
||||
```
|
||||
|
||||
Test:
|
||||
1. License activation
|
||||
2. CSV upload
|
||||
3. Strategy generation
|
||||
4. Downloads
|
||||
5. Logout button
|
||||
|
||||
## 🔧 Post-Deployment Configuration
|
||||
|
||||
### Update API URL (Already Done)
|
||||
|
||||
```javascript
|
||||
// js/login.js - Line 7
|
||||
const API_URL = 'https://fxmath.com/quantw/api/validate.php';
|
||||
```
|
||||
|
||||
### Server-Side Setup (Already Complete)
|
||||
|
||||
- ✅ Server deployed at: https://fxmath.com/quantw/
|
||||
- ✅ Admin panel: https://fxmath.com/quantw/admin/
|
||||
- ✅ API endpoint: https://fxmath.com/quantw/api/validate.php
|
||||
- ✅ License generation API: https://fxmath.com/quantw/api/create.php
|
||||
|
||||
## 📁 File Structure
|
||||
|
||||
```
|
||||
FxMathQuant-Web/
|
||||
├── index.html (main app)
|
||||
├── login.html (license activation)
|
||||
├── USER_MANUAL.md (comprehensive guide)
|
||||
├── css/
|
||||
│ └── style.css
|
||||
├── js/
|
||||
│ ├── main.js (v2.0 - cache busted)
|
||||
│ ├── login.js (API URL updated)
|
||||
│ ├── license-check.js
|
||||
│ ├── backtester.js
|
||||
│ ├── ga-engine.js
|
||||
│ ├── strategy.js
|
||||
│ ├── strategy-details.js
|
||||
│ ├── html-report-generator.js
|
||||
│ ├── ui-controller.js
|
||||
│ ├── rule-parser.js
|
||||
│ ├── mq4-converter.js
|
||||
│ ├── mq5-converter.js
|
||||
│ ├── ctrader-converter.js
|
||||
│ └── pine-converter.js
|
||||
├── downloads/
|
||||
│ ├── FxMathQuant_DataExporter_MT4.ex4
|
||||
│ ├── FxMathQuant_DataExporter_MT5.ex5
|
||||
│ └── README.md
|
||||
├── data/
|
||||
│ └── (sample CSV files)
|
||||
└── assets/
|
||||
└── (images, icons)
|
||||
```
|
||||
|
||||
## 🎯 Features Summary
|
||||
|
||||
### License System
|
||||
- ✅ License activation required
|
||||
- ✅ Lifetime and time-limited licenses supported
|
||||
- ✅ Session persists until logout
|
||||
- ✅ Logout button in header
|
||||
- ✅ API validation against production server
|
||||
|
||||
### Data Import
|
||||
- ✅ CSV upload (drag-and-drop or browse)
|
||||
- ✅ Accepts MT4/MT5 DataProvider EA format
|
||||
- ✅ Capitalized column names supported
|
||||
- ✅ 10,000+ bars recommended
|
||||
|
||||
### Strategy Generation
|
||||
- ✅ Genetic algorithm optimization
|
||||
- ✅ Configurable parameters
|
||||
- ✅ Real-time progress tracking
|
||||
- ✅ Multiple strategies per run
|
||||
|
||||
### Results & Downloads
|
||||
- ✅ Strategy details modal
|
||||
- ✅ BUY/SELL rules display
|
||||
- ✅ Equity curve chart
|
||||
- ✅ Hourly performance analysis
|
||||
- ✅ Trade statement
|
||||
- ✅ Download formats: MQ4, MQ5, cTrader, Pine Script, HTML, JSON
|
||||
|
||||
### User Support
|
||||
- ✅ Comprehensive user manual
|
||||
- ✅ DataProvider EA guide
|
||||
- ✅ Contact: fxmathsolution@gmail.com
|
||||
- ✅ Telegram: https://t.me/FxMath
|
||||
|
||||
## 🔒 Security
|
||||
|
||||
- ✅ License validation via API
|
||||
- ✅ HTTPS required for production
|
||||
- ✅ CORS enabled for GitHub Pages
|
||||
- ✅ No sensitive data stored client-side
|
||||
- ✅ Session in localStorage (can be cleared)
|
||||
|
||||
## 📊 Session Management
|
||||
|
||||
### How It Works
|
||||
1. User activates license on `login.html`
|
||||
2. License validated against API
|
||||
3. License info stored in `localStorage`
|
||||
4. `license-check.js` validates on every page load
|
||||
5. Session persists until:
|
||||
- User clicks logout button
|
||||
- User clears browser data
|
||||
- License expires (for time-limited)
|
||||
|
||||
### Logout Process
|
||||
1. User clicks 🚪 button in header
|
||||
2. Confirmation dialog appears
|
||||
3. `localStorage` cleared
|
||||
4. Redirected to `login.html`
|
||||
|
||||
## 🎉 Ready for Deployment!
|
||||
|
||||
All files are prepared and ready to upload to GitHub Pages.
|
||||
|
||||
**Next Action**: Upload to GitHub and test!
|
||||
@@ -0,0 +1,264 @@
|
||||
# FxMathQuant Web - AI-Powered Strategy Generator
|
||||
|
||||
🚀 **Generate profitable trading strategies using Genetic Algorithms - 100% in your browser!**
|
||||
|
||||
## Features
|
||||
|
||||
✅ **Client-Side Processing** - Everything runs in your browser, no server needed
|
||||
✅ **AI-Powered** - Genetic Algorithm evolves profitable trading strategies
|
||||
✅ **Fast Backtesting** - Optimized JavaScript with typed arrays
|
||||
✅ **MQ4/MQ5 Export** - Generate ready-to-use MetaTrader Expert Advisors
|
||||
✅ **HTML Reports** - Beautiful performance reports with interactive charts
|
||||
✅ **GitHub Pages Ready** - Host for FREE on GitHub Pages
|
||||
|
||||
## How It Works
|
||||
|
||||
1. **Upload CSV Data** - Export OHLC data from MT4/MT5 and upload
|
||||
2. **Configure GA** - Set population, generations, and performance filters
|
||||
3. **Generate Strategies** - AI evolves profitable trading strategies
|
||||
4. **Download Code** - Get MQ4, MQ5, and HTML reports
|
||||
|
||||
## Quick Start
|
||||
|
||||
### Option 1: Local Development
|
||||
|
||||
```bash
|
||||
# Clone or download this folder
|
||||
cd FxMathQuant-Web
|
||||
|
||||
# Serve with any HTTP server
|
||||
python -m http.server 8000
|
||||
# or
|
||||
npx serve
|
||||
|
||||
# Open browser
|
||||
open http://localhost:8000
|
||||
```
|
||||
|
||||
### Option 2: GitHub Pages (Recommended)
|
||||
|
||||
1. **Create GitHub Repository**
|
||||
```bash
|
||||
git init
|
||||
git add .
|
||||
git commit -m "Initial commit"
|
||||
git branch -M main
|
||||
git remote add origin https://github.com/yourusername/fxmathquant-web.git
|
||||
git push -u origin main
|
||||
```
|
||||
|
||||
2. **Enable GitHub Pages**
|
||||
- Go to repository Settings → Pages
|
||||
- Source: Deploy from branch `main`
|
||||
- Folder: `/ (root)`
|
||||
- Save
|
||||
|
||||
3. **Access Your App**
|
||||
- URL: `https://yourusername.github.io/fxmathquant-web/`
|
||||
|
||||
## Preparing Data
|
||||
|
||||
### MT4/MT5 Data Export
|
||||
|
||||
Create a simple EA to export OHLC data:
|
||||
|
||||
**MT4 Script (SaveOHLC.mq4):**
|
||||
```mql4
|
||||
//+------------------------------------------------------------------+
|
||||
//| Script to export OHLC data to CSV |
|
||||
//+------------------------------------------------------------------+
|
||||
void OnStart()
|
||||
{
|
||||
string filename = Symbol() + "_" + PeriodToString() + ".csv";
|
||||
int handle = FileOpen(filename, FILE_WRITE|FILE_CSV);
|
||||
|
||||
if(handle != INVALID_HANDLE)
|
||||
{
|
||||
FileWrite(handle, "time", "open", "high", "low", "close");
|
||||
|
||||
int bars = iBars(Symbol(), Period());
|
||||
for(int i = bars - 1; i >= 0; i--)
|
||||
{
|
||||
FileWrite(handle,
|
||||
TimeToString(iTime(Symbol(), Period(), i)),
|
||||
iOpen(Symbol(), Period(), i),
|
||||
iHigh(Symbol(), Period(), i),
|
||||
iLow(Symbol(), Period(), i),
|
||||
iClose(Symbol(), Period(), i)
|
||||
);
|
||||
}
|
||||
|
||||
FileClose(handle);
|
||||
Print("Data exported to: ", filename);
|
||||
}
|
||||
}
|
||||
|
||||
string PeriodToString()
|
||||
{
|
||||
switch(Period())
|
||||
{
|
||||
case PERIOD_M1: return "M1";
|
||||
case PERIOD_M5: return "M5";
|
||||
case PERIOD_M15: return "M15";
|
||||
case PERIOD_M30: return "M30";
|
||||
case PERIOD_H1: return "H1";
|
||||
case PERIOD_H4: return "H4";
|
||||
case PERIOD_D1: return "D1";
|
||||
default: return "Unknown";
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**MT5 Script (SaveOHLC.mq5):**
|
||||
```mql5
|
||||
//+------------------------------------------------------------------+
|
||||
//| Script to export OHLC data to CSV |
|
||||
//+------------------------------------------------------------------+
|
||||
void OnStart()
|
||||
{
|
||||
string filename = _Symbol + "_" + PeriodToString() + ".csv";
|
||||
int handle = FileOpen(filename, FILE_WRITE|FILE_CSV);
|
||||
|
||||
if(handle != INVALID_HANDLE)
|
||||
{
|
||||
FileWrite(handle, "time", "open", "high", "low", "close");
|
||||
|
||||
int bars = Bars(_Symbol, _Period);
|
||||
datetime time[];
|
||||
double open[], high[], low[], close[];
|
||||
|
||||
CopyTime(_Symbol, _Period, 0, bars, time);
|
||||
CopyOpen(_Symbol, _Period, 0, bars, open);
|
||||
CopyHigh(_Symbol, _Period, 0, bars, high);
|
||||
CopyLow(_Symbol, _Period, 0, bars, low);
|
||||
CopyClose(_Symbol, _Period, 0, bars, close);
|
||||
|
||||
for(int i = bars - 1; i >= 0; i--)
|
||||
{
|
||||
FileWrite(handle,
|
||||
TimeToString(time[i]),
|
||||
open[i],
|
||||
high[i],
|
||||
low[i],
|
||||
close[i]
|
||||
);
|
||||
}
|
||||
|
||||
FileClose(handle);
|
||||
Print("Data exported to: ", filename);
|
||||
}
|
||||
}
|
||||
|
||||
string PeriodToString()
|
||||
{
|
||||
switch(_Period)
|
||||
{
|
||||
case PERIOD_M1: return "M1";
|
||||
case PERIOD_M5: return "M5";
|
||||
case PERIOD_M15: return "M15";
|
||||
case PERIOD_M30: return "M30";
|
||||
case PERIOD_H1: return "H1";
|
||||
case PERIOD_H4: return "H4";
|
||||
case PERIOD_D1: return "D1";
|
||||
default: return "Unknown";
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Configuration Guide
|
||||
|
||||
### GA Parameters
|
||||
|
||||
| Parameter | Recommended | Description |
|
||||
|-----------|-------------|-------------|
|
||||
| Population | 100-200 | Strategies per generation |
|
||||
| Generations | 50-100 | Evolution cycles |
|
||||
| Strategies to Find | 5-10 | Target number of strategies |
|
||||
|
||||
### Performance Filters
|
||||
|
||||
| Filter | Recommended | Description |
|
||||
|--------|-------------|-------------|
|
||||
| Min Profit Factor | 1.5-2.0 | Minimum PF required |
|
||||
| Min Win Rate | 45-55% | Minimum win rate |
|
||||
| Max Drawdown | 20-30% | Maximum acceptable DD |
|
||||
| Min Trades | 30-50 | Minimum trade count |
|
||||
|
||||
## Performance
|
||||
|
||||
- **Processing Speed:** ~50-100 strategies/second (depends on device)
|
||||
- **Recommended Data Size:** 1,000-5,000 bars
|
||||
- **Browser Support:** Chrome, Firefox, Safari, Edge (latest versions)
|
||||
|
||||
## Technical Stack
|
||||
|
||||
- **Frontend:** Vanilla HTML5 + JavaScript (no frameworks)
|
||||
- **Charts:** Chart.js 4.4.0
|
||||
- **CSV Parsing:** PapaParse 5.4.1
|
||||
- **Styling:** Custom CSS with dark theme
|
||||
- **Hosting:** GitHub Pages compatible
|
||||
|
||||
## Project Structure
|
||||
|
||||
```
|
||||
FxMathQuant-Web/
|
||||
├── index.html # Main page
|
||||
├── css/
|
||||
│ └── style.css # Styles
|
||||
├── js/
|
||||
│ ├── main.js # App controller
|
||||
│ ├── strategy.js # Strategy class
|
||||
│ ├── backtester.js # Backtesting engine
|
||||
│ ├── ga-engine.js # Genetic Algorithm
|
||||
│ ├── mq4-generator.js # MQ4 code generator
|
||||
│ ├── mq5-generator.js # MQ5 code generator
|
||||
│ └── report-generator.js # HTML report generator
|
||||
└── README.md
|
||||
```
|
||||
|
||||
## Browser Compatibility
|
||||
|
||||
✅ Chrome 90+
|
||||
✅ Firefox 88+
|
||||
✅ Safari 14+
|
||||
✅ Edge 90+
|
||||
|
||||
## Limitations
|
||||
|
||||
- **Dataset Size:** Recommended max 5,000 bars for optimal performance
|
||||
- **Processing Time:** Depends on device CPU (slower on mobile)
|
||||
- **Storage:** All data stays in browser (not saved to server)
|
||||
|
||||
## FAQ
|
||||
|
||||
**Q: Is my data sent to a server?**
|
||||
A: No! Everything runs 100% in your browser. Your data never leaves your device.
|
||||
|
||||
**Q: Can I use this offline?**
|
||||
A: Yes, once loaded. Download the files and open `index.html` locally.
|
||||
|
||||
**Q: How accurate is the backtesting?**
|
||||
A: Very accurate. Uses the same logic as the Python version with ATR-based SL/TP.
|
||||
|
||||
**Q: Can I modify the generated MQ4/MQ5 code?**
|
||||
A: Yes! The code is clean and well-commented for easy customization.
|
||||
|
||||
## Support
|
||||
|
||||
- **Website:** https://fxmathquant.com
|
||||
- **Email:** support@fxmath.com
|
||||
- **GitHub:** https://github.com/yourusername/fxmathquant-web
|
||||
|
||||
## License
|
||||
|
||||
© 2025 FxMathQuant. All rights reserved.
|
||||
|
||||
## Disclaimer
|
||||
|
||||
⚠️ **Trading Risk Warning**
|
||||
|
||||
Trading forex and CFDs carries a high level of risk. Past performance is not indicative of future results. Always test strategies on demo accounts before live trading. Never invest more than you can afford to lose.
|
||||
|
||||
---
|
||||
|
||||
**Made with ❤️ by FxMathQuant**
|
||||
+443
@@ -0,0 +1,443 @@
|
||||
# FxMathQuant Strategy Finder - User Manual
|
||||
|
||||
**AI-Powered Trading Strategy Generator**
|
||||
|
||||
---
|
||||
|
||||
## 📋 Table of Contents
|
||||
|
||||
1. [Getting Started](#getting-started)
|
||||
2. [License Activation](#license-activation)
|
||||
3. [Exporting Data from MT4/MT5](#exporting-data)
|
||||
4. [Uploading Data](#uploading-data)
|
||||
5. [Configuring Strategy Generation](#configuration)
|
||||
6. [Generating Strategies](#generation)
|
||||
7. [Viewing Results](#results)
|
||||
8. [Downloading Strategies](#downloading)
|
||||
9. [Troubleshooting](#troubleshooting)
|
||||
10. [Contact Support](#support)
|
||||
|
||||
---
|
||||
|
||||
## 🚀 Getting Started
|
||||
|
||||
### What is FxMathQuant?
|
||||
|
||||
FxMathQuant is an AI-powered trading strategy generator that uses genetic algorithms to discover profitable trading strategies from your historical price data.
|
||||
|
||||
### System Requirements
|
||||
|
||||
- **Web Browser**: Chrome, Firefox, Safari, or Edge (latest version)
|
||||
- **MetaTrader**: MT4 or MT5 (for data export)
|
||||
- **License Key**: Required for access
|
||||
|
||||
---
|
||||
|
||||
## 🔑 License Activation
|
||||
|
||||
### Step 1: Obtain License Key
|
||||
|
||||
Purchase a license from:
|
||||
- **Website**: https://fxmath.com
|
||||
- **Email**: fxmathsolution@gmail.com
|
||||
- **Telegram**: https://t.me/FxMath
|
||||
|
||||
### Step 2: Activate License
|
||||
|
||||
1. **Open the application** in your web browser
|
||||
2. **Enter your license key** in the format: `XXXX-XXXX-XXXX-XXXX-XXXX-XXXX-XXXX-XXXX`
|
||||
3. **Click "Activate License"**
|
||||
4. **Wait for validation** (requires internet connection)
|
||||
5. **You'll be redirected** to the main application
|
||||
|
||||
### License Types
|
||||
|
||||
- **Lifetime License**: Never expires, unlimited use
|
||||
- **Time-Limited License**: Valid for specified days (30, 90, 365, etc.)
|
||||
|
||||
### Session Duration
|
||||
|
||||
- **Active Session**: Remains active until you logout or clear browser data
|
||||
- **Auto-Logout**: No automatic logout (session persists)
|
||||
- **Manual Logout**: Click the logout button in the top-right corner
|
||||
|
||||
---
|
||||
|
||||
## 📊 Exporting Data from MT4/MT5
|
||||
|
||||
### Download Data Provider EA
|
||||
|
||||
1. **Download the EA** from the application:
|
||||
- MT4: `FxMathQuant_DataExporter_MT4.ex4`
|
||||
- MT5: `FxMathQuant_DataExporter_MT5.ex5`
|
||||
|
||||
2. **Install in MetaTrader**:
|
||||
- Copy to `MQL4/Experts/` (MT4) or `MQL5/Experts/` (MT5)
|
||||
- Restart MetaTrader
|
||||
- Find in Navigator → Expert Advisors
|
||||
|
||||
### Export Historical Data
|
||||
|
||||
1. **Open a chart** (e.g., XAUUSD H1)
|
||||
2. **Drag the EA** onto the chart
|
||||
3. **Configure settings**:
|
||||
- **Bars to Export**: 10,000 (recommended)
|
||||
- **Remove Suffix**: true (removes broker suffix)
|
||||
- **Export Folder**: FxMathQuant
|
||||
4. **Click "Export to CSV"** button
|
||||
5. **Wait for completion** message
|
||||
6. **Find your file**: `MQL4/Files/FxMathQuant/XAUUSD_H1.csv`
|
||||
|
||||
### Recommended Data
|
||||
|
||||
| Timeframe | Bars | Use Case |
|
||||
|-----------|------|----------|
|
||||
| **M15** | 10,000 | Scalping strategies |
|
||||
| **H1** | 10,000 | Intraday trading |
|
||||
| **H4** | 5,000 | Swing trading |
|
||||
| **D1** | 3,000 | Position trading |
|
||||
|
||||
---
|
||||
|
||||
## 📤 Uploading Data
|
||||
|
||||
### Step 1: Upload CSV File
|
||||
|
||||
1. **Click "Browse"** or drag-and-drop your CSV file
|
||||
2. **Select the file** exported from MT4/MT5
|
||||
3. **Wait for parsing** (10,000 bars ≈ 2-3 seconds)
|
||||
4. **Confirmation**: "Data loaded successfully: X bars"
|
||||
|
||||
### Supported Format
|
||||
|
||||
```csv
|
||||
Time,Open,High,Low,Close,Tick_Volume,Spread,Real_Volume
|
||||
2024-04-18 01:00:00,2361.73,2367.69,2361.37,2365.29,2325,7,0
|
||||
```
|
||||
|
||||
**Note**: Column names can be lowercase or capitalized.
|
||||
|
||||
---
|
||||
|
||||
## ⚙️ Configuring Strategy Generation
|
||||
|
||||
### Genetic Algorithm Settings
|
||||
|
||||
| Parameter | Default | Range | Description |
|
||||
|-----------|---------|-------|-------------|
|
||||
| **Population Size** | 100 | 50-500 | Number of strategies per generation |
|
||||
| **Generations** | 50 | 10-200 | Number of evolution cycles |
|
||||
| **Mutation Rate** | 0.1 | 0.01-0.5 | Probability of random changes |
|
||||
| **Crossover Rate** | 0.7 | 0.3-0.9 | Probability of combining strategies |
|
||||
| **Elite Size** | 5 | 1-20 | Best strategies preserved each generation |
|
||||
|
||||
### Strategy Criteria
|
||||
|
||||
| Criteria | Default | Description |
|
||||
|----------|---------|-------------|
|
||||
| **Min Profit Factor** | 1.5 | Minimum ratio of profit to loss |
|
||||
| **Min Win Rate** | 50% | Minimum percentage of winning trades |
|
||||
| **Min Total Trades** | 30 | Minimum number of trades |
|
||||
| **Max Drawdown** | 30% | Maximum equity drawdown allowed |
|
||||
|
||||
### Recommended Settings
|
||||
|
||||
**For Beginners:**
|
||||
- Population: 100
|
||||
- Generations: 50
|
||||
- Min Profit Factor: 1.5
|
||||
- Min Win Rate: 50%
|
||||
|
||||
**For Advanced Users:**
|
||||
- Population: 200-300
|
||||
- Generations: 100-150
|
||||
- Min Profit Factor: 2.0
|
||||
- Min Win Rate: 55%
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Generating Strategies
|
||||
|
||||
### Step 1: Start Generation
|
||||
|
||||
1. **Review your settings**
|
||||
2. **Click "Start Generation"**
|
||||
3. **Monitor progress**:
|
||||
- Current generation
|
||||
- Strategies found
|
||||
- Best fitness score
|
||||
- Time elapsed
|
||||
|
||||
### Step 2: Wait for Completion
|
||||
|
||||
- **Time estimate**: 30 seconds to 5 minutes
|
||||
- **Depends on**:
|
||||
- Population size
|
||||
- Number of generations
|
||||
- Data size
|
||||
- Computer speed
|
||||
|
||||
### Step 3: Review Results
|
||||
|
||||
- **Strategies found**: Number of strategies meeting criteria
|
||||
- **Best strategy**: Highest fitness score
|
||||
- **Generation**: When the strategy was discovered
|
||||
|
||||
---
|
||||
|
||||
## 📈 Viewing Results
|
||||
|
||||
### Strategy List
|
||||
|
||||
Each strategy shows:
|
||||
- **Profit Factor**: Ratio of gross profit to gross loss
|
||||
- **Win Rate**: Percentage of winning trades
|
||||
- **Total Trades**: Number of trades executed
|
||||
- **Net Profit**: Total profit in currency
|
||||
- **Max Drawdown**: Largest equity drop
|
||||
|
||||
### Strategy Details
|
||||
|
||||
Click "View Details" to see:
|
||||
|
||||
1. **Performance Metrics**:
|
||||
- Profit Factor, Win Rate, Total Trades
|
||||
- Net Profit, Max Drawdown, Recovery Factor
|
||||
- Average Win/Loss, Largest Win/Loss
|
||||
|
||||
2. **BUY Rules**:
|
||||
- Entry conditions (e.g., `RSI(14) < 30`)
|
||||
- All rules must be true for BUY signal
|
||||
|
||||
3. **SELL Rules**:
|
||||
- Entry conditions (inverted from BUY)
|
||||
- All rules must be true for SELL signal
|
||||
|
||||
4. **Equity Curve**:
|
||||
- Visual representation of account growth
|
||||
- Shows drawdown periods
|
||||
|
||||
5. **Hourly Performance**:
|
||||
- Best/worst trading hours
|
||||
- Win rate by hour
|
||||
- Profit by hour
|
||||
|
||||
6. **Trade Statement**:
|
||||
- Complete list of all trades
|
||||
- Entry/exit times and prices
|
||||
- Profit/loss per trade
|
||||
|
||||
---
|
||||
|
||||
## 💾 Downloading Strategies
|
||||
|
||||
### Available Formats
|
||||
|
||||
1. **MetaTrader 4 (.mq4)**:
|
||||
- For MT4 platform
|
||||
- Includes all input parameters
|
||||
- Ready to compile and use
|
||||
|
||||
2. **MetaTrader 5 (.mq5)**:
|
||||
- For MT5 platform
|
||||
- Includes all input parameters
|
||||
- Ready to compile and use
|
||||
|
||||
3. **cTrader (.cs)**:
|
||||
- For cTrader platform
|
||||
- C# source code
|
||||
- Ready to compile
|
||||
|
||||
4. **TradingView (Pine Script)**:
|
||||
- For TradingView platform
|
||||
- Pine Script v5
|
||||
- Ready to use
|
||||
|
||||
5. **HTML Report**:
|
||||
- Comprehensive performance report
|
||||
- Includes all charts and metrics
|
||||
- Shareable and printable
|
||||
|
||||
6. **JSON Data**:
|
||||
- Raw strategy data
|
||||
- For custom integrations
|
||||
- Machine-readable format
|
||||
|
||||
### How to Download
|
||||
|
||||
1. **Click "Download"** button on strategy card
|
||||
2. **Select format** from dropdown
|
||||
3. **File downloads** automatically
|
||||
4. **Install in your platform**:
|
||||
- MT4/MT5: Copy to `Experts/` folder
|
||||
- cTrader: Import as cBot
|
||||
- TradingView: Copy-paste Pine Script
|
||||
|
||||
---
|
||||
|
||||
## 🐛 Troubleshooting
|
||||
|
||||
### CSV Upload Issues
|
||||
|
||||
**Error: "CSV must contain: time, open, high, low, close columns"**
|
||||
- **Solution**: Ensure CSV has required columns
|
||||
- **Check**: Column names can be lowercase or capitalized
|
||||
- **Try**: Re-export from MT4/MT5 using DataProvider EA
|
||||
|
||||
**Error: "CSV file is empty"**
|
||||
- **Solution**: Check file has data rows
|
||||
- **Try**: Open CSV in Excel/Notepad to verify
|
||||
|
||||
### License Issues
|
||||
|
||||
**Error: "Invalid license key"**
|
||||
- **Solution**: Check key format (32 characters with dashes)
|
||||
- **Try**: Copy-paste from email (avoid typing)
|
||||
|
||||
**Error: "License has expired"**
|
||||
- **Solution**: Contact support for renewal
|
||||
- **Email**: fxmathsolution@gmail.com
|
||||
|
||||
**Error: "License validation failed"**
|
||||
- **Solution**: Check internet connection
|
||||
- **Try**: Disable VPN/proxy
|
||||
- **Contact**: Support if issue persists
|
||||
|
||||
### Generation Issues
|
||||
|
||||
**No strategies found**
|
||||
- **Solution**: Relax criteria (lower Min Profit Factor, Win Rate)
|
||||
- **Try**: Increase population size and generations
|
||||
- **Check**: Data quality (enough bars, no gaps)
|
||||
|
||||
**Generation too slow**
|
||||
- **Solution**: Reduce population size and generations
|
||||
- **Try**: Use smaller dataset (fewer bars)
|
||||
- **Check**: Close other browser tabs
|
||||
|
||||
### Browser Issues
|
||||
|
||||
**Page not loading**
|
||||
- **Solution**: Clear browser cache (Ctrl+Shift+R)
|
||||
- **Try**: Use incognito/private mode
|
||||
- **Check**: JavaScript is enabled
|
||||
|
||||
**Charts not displaying**
|
||||
- **Solution**: Disable ad blockers
|
||||
- **Try**: Different browser
|
||||
- **Check**: Internet connection
|
||||
|
||||
---
|
||||
|
||||
## 📞 Contact Support
|
||||
|
||||
### Get Help
|
||||
|
||||
**Email**: fxmathsolution@gmail.com
|
||||
- Response time: 24-48 hours
|
||||
- Include: License key, error message, screenshots
|
||||
|
||||
**Telegram**: https://t.me/FxMath
|
||||
- Faster response
|
||||
- Community support
|
||||
- Updates and announcements
|
||||
|
||||
**Website**: https://fxmath.com
|
||||
- Documentation
|
||||
- Video tutorials
|
||||
- FAQ
|
||||
|
||||
### Before Contacting Support
|
||||
|
||||
Please provide:
|
||||
1. **License key** (first 8 characters)
|
||||
2. **Error message** (exact text or screenshot)
|
||||
3. **Browser** (Chrome, Firefox, etc.) and version
|
||||
4. **Steps to reproduce** the issue
|
||||
5. **CSV file** (if upload issue)
|
||||
|
||||
---
|
||||
|
||||
## 📚 Additional Resources
|
||||
|
||||
### Video Tutorials
|
||||
|
||||
- **Getting Started**: https://fxmath.com/tutorials/getting-started
|
||||
- **Data Export**: https://fxmath.com/tutorials/data-export
|
||||
- **Strategy Generation**: https://fxmath.com/tutorials/generation
|
||||
- **Advanced Settings**: https://fxmath.com/tutorials/advanced
|
||||
|
||||
### Documentation
|
||||
|
||||
- **DataProvider EA Guide**: See `downloads/README.md`
|
||||
- **API Documentation**: For developers
|
||||
- **License System**: Setup and management
|
||||
|
||||
### Community
|
||||
|
||||
- **Telegram Group**: https://t.me/FxMath
|
||||
- **Discord**: https://discord.gg/fxmath
|
||||
- **Forum**: https://forum.fxmath.com
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ Important Notes
|
||||
|
||||
### Disclaimer
|
||||
|
||||
- **Past performance** does not guarantee future results
|
||||
- **Backtest results** may differ from live trading
|
||||
- **Always test** strategies on demo account first
|
||||
- **Risk management** is essential
|
||||
- **No guarantee** of profitability
|
||||
|
||||
### Best Practices
|
||||
|
||||
1. **Test thoroughly** on demo before live
|
||||
2. **Use proper lot sizing** (1-2% risk per trade)
|
||||
3. **Monitor performance** regularly
|
||||
4. **Update data** monthly for fresh strategies
|
||||
5. **Diversify** across multiple strategies and pairs
|
||||
6. **Set stop-loss** on all trades
|
||||
7. **Keep records** of all trades
|
||||
|
||||
### Data Privacy
|
||||
|
||||
- **All data** is processed locally in your browser
|
||||
- **No data** is sent to external servers
|
||||
- **License validation** only sends license key
|
||||
- **CSV files** remain on your computer
|
||||
- **Strategies** are yours to keep
|
||||
|
||||
---
|
||||
|
||||
## 🎓 Tips for Success
|
||||
|
||||
### Data Quality
|
||||
|
||||
- Use **quality broker** with good historical data
|
||||
- Export **enough bars** (10,000+ recommended)
|
||||
- Check for **data gaps** (weekends are normal)
|
||||
- **Update regularly** (monthly) for current market conditions
|
||||
|
||||
### Strategy Selection
|
||||
|
||||
- **Don't overfit**: Avoid too many rules
|
||||
- **Diversify**: Use multiple strategies
|
||||
- **Validate**: Test on different time periods
|
||||
- **Monitor**: Track live performance
|
||||
|
||||
### Risk Management
|
||||
|
||||
- **Never risk** more than 2% per trade
|
||||
- **Use stop-loss** always
|
||||
- **Position sizing**: Based on account size
|
||||
- **Drawdown limit**: Stop trading at 20% drawdown
|
||||
|
||||
---
|
||||
|
||||
**Happy Trading! 🎯**
|
||||
|
||||
For support: fxmathsolution@gmail.com | https://t.me/FxMath
|
||||
|
||||
© 2025 FxMath Solution. All rights reserved.
|
||||
File diff suppressed because it is too large
Load Diff
+817
@@ -0,0 +1,817 @@
|
||||
/* FxMathQuant Web - Modern Dark Theme */
|
||||
|
||||
:root {
|
||||
--bg-primary: #0a0e27;
|
||||
--bg-secondary: #151932;
|
||||
--bg-card: #1a1f3a;
|
||||
--bg-hover: #252b4a;
|
||||
|
||||
--text-primary: #ffffff;
|
||||
--text-secondary: #a0aec0;
|
||||
--text-muted: #718096;
|
||||
|
||||
--accent-primary: #667eea;
|
||||
--accent-secondary: #764ba2;
|
||||
--accent-success: #48bb78;
|
||||
--accent-danger: #f56565;
|
||||
--accent-warning: #ed8936;
|
||||
|
||||
--border-color: #2d3748;
|
||||
--shadow: 0 10px 30px rgba(0, 0, 0, 0.3);
|
||||
--shadow-lg: 0 20px 60px rgba(0, 0, 0, 0.5);
|
||||
|
||||
--font-primary: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
||||
}
|
||||
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: var(--font-primary);
|
||||
background: linear-gradient(135deg, var(--bg-primary) 0%, #1a1f3a 100%);
|
||||
color: var(--text-primary);
|
||||
min-height: 100vh;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
padding: 0 20px;
|
||||
}
|
||||
|
||||
/* Header */
|
||||
.header {
|
||||
background: rgba(26, 31, 58, 0.8);
|
||||
backdrop-filter: blur(10px);
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
padding: 20px 0;
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
.header-content {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.logo h1 {
|
||||
font-size: 28px;
|
||||
font-weight: 700;
|
||||
background: linear-gradient(135deg, var(--accent-primary), var(--accent-secondary));
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
background-clip: text;
|
||||
}
|
||||
|
||||
.logo .subtitle {
|
||||
font-size: 12px;
|
||||
color: var(--text-secondary);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 2px;
|
||||
}
|
||||
|
||||
.btn-icon {
|
||||
background: var(--bg-card);
|
||||
border: 1px solid var(--border-color);
|
||||
color: var(--text-primary);
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.btn-icon:hover {
|
||||
background: var(--bg-hover);
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
/* Main Content */
|
||||
.main-content {
|
||||
padding: 40px 0;
|
||||
min-height: calc(100vh - 200px);
|
||||
}
|
||||
|
||||
.section {
|
||||
display: none;
|
||||
animation: fadeIn 0.5s ease;
|
||||
}
|
||||
|
||||
.section.active {
|
||||
display: block;
|
||||
}
|
||||
|
||||
@keyframes fadeIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(20px);
|
||||
}
|
||||
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
.section-header {
|
||||
text-align: center;
|
||||
margin-bottom: 40px;
|
||||
}
|
||||
|
||||
.section-header h2 {
|
||||
font-size: 32px;
|
||||
font-weight: 700;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.section-header p {
|
||||
color: var(--text-secondary);
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
/* Upload Zone */
|
||||
.upload-zone {
|
||||
background: var(--bg-card);
|
||||
border: 2px dashed var(--border-color);
|
||||
border-radius: 16px;
|
||||
padding: 60px 40px;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.upload-zone:hover {
|
||||
border-color: var(--accent-primary);
|
||||
background: var(--bg-hover);
|
||||
transform: translateY(-5px);
|
||||
box-shadow: var(--shadow);
|
||||
}
|
||||
|
||||
.upload-zone.dragover {
|
||||
border-color: var(--accent-success);
|
||||
background: rgba(72, 187, 120, 0.1);
|
||||
}
|
||||
|
||||
.upload-icon {
|
||||
font-size: 64px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.upload-zone h3 {
|
||||
font-size: 24px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.upload-zone p {
|
||||
color: var(--text-secondary);
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
/* Data Preview */
|
||||
.data-preview {
|
||||
background: var(--bg-card);
|
||||
border-radius: 16px;
|
||||
padding: 30px;
|
||||
box-shadow: var(--shadow);
|
||||
}
|
||||
|
||||
.data-preview.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.data-info {
|
||||
display: flex;
|
||||
gap: 30px;
|
||||
margin-bottom: 20px;
|
||||
padding: 15px;
|
||||
background: var(--bg-secondary);
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.data-info span {
|
||||
font-weight: 600;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
.table-container {
|
||||
overflow-x: auto;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
thead {
|
||||
background: var(--bg-secondary);
|
||||
}
|
||||
|
||||
th,
|
||||
td {
|
||||
padding: 12px;
|
||||
text-align: left;
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
}
|
||||
|
||||
th {
|
||||
font-weight: 600;
|
||||
color: var(--text-secondary);
|
||||
text-transform: uppercase;
|
||||
font-size: 12px;
|
||||
letter-spacing: 1px;
|
||||
}
|
||||
|
||||
tbody tr:hover {
|
||||
background: var(--bg-hover);
|
||||
}
|
||||
|
||||
/* Configuration */
|
||||
.config-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
|
||||
gap: 20px;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.config-card {
|
||||
background: var(--bg-card);
|
||||
border-radius: 16px;
|
||||
padding: 30px;
|
||||
box-shadow: var(--shadow);
|
||||
border: 1px solid var(--border-color);
|
||||
}
|
||||
|
||||
.config-card h3 {
|
||||
font-size: 20px;
|
||||
margin-bottom: 20px;
|
||||
color: var(--accent-primary);
|
||||
}
|
||||
|
||||
.form-group {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.form-group label {
|
||||
display: block;
|
||||
margin-bottom: 8px;
|
||||
font-weight: 600;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
.form-group input {
|
||||
width: 100%;
|
||||
padding: 12px;
|
||||
background: var(--bg-secondary);
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 8px;
|
||||
color: var(--text-primary);
|
||||
font-size: 16px;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.form-group input:focus {
|
||||
outline: none;
|
||||
border-color: var(--accent-primary);
|
||||
box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);
|
||||
}
|
||||
|
||||
.form-group small {
|
||||
display: block;
|
||||
margin-top: 5px;
|
||||
color: var(--text-muted);
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.range-inputs {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.range-inputs input {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.range-inputs span {
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
/* Buttons */
|
||||
.btn-primary,
|
||||
.btn-secondary,
|
||||
.btn-success,
|
||||
.btn-danger {
|
||||
padding: 12px 30px;
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background: linear-gradient(135deg, var(--accent-primary), var(--accent-secondary));
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-secondary {
|
||||
background: var(--bg-secondary);
|
||||
color: var(--text-primary);
|
||||
border: 1px solid var(--border-color);
|
||||
}
|
||||
|
||||
.btn-success {
|
||||
background: var(--accent-success);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-danger {
|
||||
background: var(--accent-danger);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-primary:hover,
|
||||
.btn-success:hover,
|
||||
.btn-danger:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: var(--shadow);
|
||||
}
|
||||
|
||||
.btn-secondary:hover {
|
||||
background: var(--bg-hover);
|
||||
}
|
||||
|
||||
.btn-small {
|
||||
padding: 6px 12px;
|
||||
font-size: 12px;
|
||||
border-radius: 6px;
|
||||
background: var(--accent-primary);
|
||||
color: white;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.btn-small:hover {
|
||||
background: var(--accent-secondary);
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.action-buttons {
|
||||
display: flex;
|
||||
gap: 15px;
|
||||
justify-content: center;
|
||||
margin-top: 30px;
|
||||
}
|
||||
|
||||
/* Progress */
|
||||
.progress-container {
|
||||
background: var(--bg-card);
|
||||
border-radius: 16px;
|
||||
padding: 40px;
|
||||
box-shadow: var(--shadow);
|
||||
}
|
||||
|
||||
.progress-stats {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||||
gap: 20px;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.stat-card {
|
||||
background: var(--bg-secondary);
|
||||
padding: 20px;
|
||||
border-radius: 12px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.stat-label {
|
||||
display: block;
|
||||
font-size: 12px;
|
||||
color: var(--text-muted);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 1px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.stat-value {
|
||||
display: block;
|
||||
font-size: 28px;
|
||||
font-weight: 700;
|
||||
color: var(--accent-primary);
|
||||
}
|
||||
|
||||
.progress-bar-container {
|
||||
background: var(--bg-secondary);
|
||||
height: 12px;
|
||||
border-radius: 6px;
|
||||
overflow: hidden;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.progress-bar {
|
||||
height: 100%;
|
||||
background: linear-gradient(90deg, var(--accent-primary), var(--accent-secondary));
|
||||
width: 0%;
|
||||
transition: width 0.3s ease;
|
||||
}
|
||||
|
||||
.progress-log {
|
||||
background: var(--bg-secondary);
|
||||
border-radius: 8px;
|
||||
padding: 20px;
|
||||
max-height: 300px;
|
||||
overflow-y: auto;
|
||||
font-family: 'Courier New', monospace;
|
||||
font-size: 13px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.progress-log::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
}
|
||||
|
||||
.progress-log::-webkit-scrollbar-track {
|
||||
background: var(--bg-primary);
|
||||
}
|
||||
|
||||
.progress-log::-webkit-scrollbar-thumb {
|
||||
background: var(--accent-primary);
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
/* Strategy Cards */
|
||||
.strategies-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(350px, 1fr));
|
||||
gap: 20px;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.strategy-card {
|
||||
background: var(--bg-card);
|
||||
border-radius: 16px;
|
||||
padding: 25px;
|
||||
box-shadow: var(--shadow);
|
||||
border: 1px solid var(--border-color);
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.strategy-card:hover {
|
||||
transform: translateY(-5px);
|
||||
box-shadow: var(--shadow-lg);
|
||||
border-color: var(--accent-primary);
|
||||
}
|
||||
|
||||
.strategy-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.strategy-name {
|
||||
font-size: 18px;
|
||||
font-weight: 700;
|
||||
color: var(--accent-primary);
|
||||
}
|
||||
|
||||
.strategy-metrics {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: 15px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.metric {
|
||||
background: var(--bg-secondary);
|
||||
padding: 12px;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.metric-label {
|
||||
display: block;
|
||||
font-size: 11px;
|
||||
color: var(--text-muted);
|
||||
text-transform: uppercase;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.metric-value {
|
||||
display: block;
|
||||
font-size: 20px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.metric-value.positive {
|
||||
color: var(--accent-success);
|
||||
}
|
||||
|
||||
.metric-value.negative {
|
||||
color: var(--accent-danger);
|
||||
}
|
||||
|
||||
.strategy-actions {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.strategy-actions button {
|
||||
flex: 1;
|
||||
padding: 10px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
/* Modal */
|
||||
.modal {
|
||||
display: none;
|
||||
position: fixed;
|
||||
z-index: 1000;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: rgba(0, 0, 0, 0.8);
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.modal-content {
|
||||
background: var(--bg-card);
|
||||
border-radius: 16px;
|
||||
width: 90%;
|
||||
max-width: 900px;
|
||||
max-height: 90vh;
|
||||
overflow-y: auto;
|
||||
box-shadow: var(--shadow-lg);
|
||||
border: 1px solid var(--border-color);
|
||||
}
|
||||
|
||||
.modal-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 25px 30px;
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
}
|
||||
|
||||
.modal-header h2 {
|
||||
font-size: 24px;
|
||||
color: var(--accent-primary);
|
||||
}
|
||||
|
||||
.modal-close {
|
||||
background: none;
|
||||
border: none;
|
||||
color: var(--text-secondary);
|
||||
font-size: 32px;
|
||||
cursor: pointer;
|
||||
transition: color 0.3s ease;
|
||||
}
|
||||
|
||||
.modal-close:hover {
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.modal-body {
|
||||
padding: 30px;
|
||||
}
|
||||
|
||||
.metrics-grid-detailed {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
|
||||
gap: 15px;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.metric-detailed {
|
||||
background: var(--bg-secondary);
|
||||
padding: 15px;
|
||||
border-radius: 8px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.metric-detailed .metric-label {
|
||||
display: block;
|
||||
font-size: 11px;
|
||||
color: var(--text-muted);
|
||||
text-transform: uppercase;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.metric-detailed .metric-value {
|
||||
display: block;
|
||||
font-size: 24px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.chart-section {
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.chart-section h3 {
|
||||
font-size: 18px;
|
||||
margin-bottom: 15px;
|
||||
color: var(--accent-primary);
|
||||
}
|
||||
|
||||
.chart-section canvas {
|
||||
height: 300px !important;
|
||||
}
|
||||
|
||||
.rules-section h3 {
|
||||
font-size: 18px;
|
||||
margin-bottom: 15px;
|
||||
color: var(--accent-primary);
|
||||
}
|
||||
|
||||
.rules-list {
|
||||
background: var(--bg-secondary);
|
||||
padding: 20px;
|
||||
border-radius: 8px;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.rule-item {
|
||||
padding: 8px 0;
|
||||
font-family: 'Courier New', monospace;
|
||||
font-size: 14px;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
.parameters-box {
|
||||
background: var(--bg-secondary);
|
||||
padding: 15px;
|
||||
border-radius: 8px;
|
||||
font-size: 14px;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
/* Form Select */
|
||||
.form-select {
|
||||
width: 100%;
|
||||
padding: 12px;
|
||||
background: var(--bg-secondary);
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 8px;
|
||||
color: var(--text-primary);
|
||||
font-size: 16px;
|
||||
transition: all 0.3s ease;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.form-select:focus {
|
||||
outline: none;
|
||||
border-color: var(--accent-primary);
|
||||
box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);
|
||||
}
|
||||
|
||||
/* Footer */
|
||||
.footer {
|
||||
background: var(--bg-secondary);
|
||||
border-top: 1px solid var(--border-color);
|
||||
padding: 30px 0;
|
||||
text-align: center;
|
||||
color: var(--text-muted);
|
||||
}
|
||||
|
||||
/* Responsive */
|
||||
@media (max-width: 768px) {
|
||||
.config-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.strategies-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.progress-stats {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
}
|
||||
|
||||
.section-header h2 {
|
||||
font-size: 24px;
|
||||
}
|
||||
}
|
||||
|
||||
/* Comparison View */
|
||||
.comparison-container {
|
||||
display: flex;
|
||||
gap: 20px;
|
||||
overflow-x: auto;
|
||||
padding: 20px 0;
|
||||
min-height: 400px;
|
||||
}
|
||||
|
||||
.comparison-item {
|
||||
flex: 0 0 400px;
|
||||
background: var(--bg-card);
|
||||
border-radius: 16px;
|
||||
padding: 25px;
|
||||
border: 1px solid var(--border-color);
|
||||
box-shadow: var(--shadow);
|
||||
}
|
||||
|
||||
.comparison-item h3 {
|
||||
margin-bottom: 20px;
|
||||
color: var(--accent-primary);
|
||||
}
|
||||
|
||||
/* Checkbox Style */
|
||||
.strategy-select {
|
||||
position: absolute;
|
||||
top: 15px;
|
||||
right: 15px;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
cursor: pointer;
|
||||
accent-color: var(--accent-success);
|
||||
}
|
||||
|
||||
.strategy-card {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
/* Trades Table in Modal */
|
||||
.trades-section {
|
||||
margin-top: 30px;
|
||||
}
|
||||
|
||||
.trades-table-container {
|
||||
max-height: 400px;
|
||||
overflow-y: auto;
|
||||
background: var(--bg-secondary);
|
||||
border-radius: 8px;
|
||||
margin-top: 15px;
|
||||
}
|
||||
|
||||
.trades-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.trades-table th {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
background: var(--bg-card);
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.trades-table td,
|
||||
.trades-table th {
|
||||
padding: 10px;
|
||||
text-align: left;
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
}
|
||||
|
||||
.modal-footer-actions {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
margin-top: 30px;
|
||||
padding-top: 20px;
|
||||
border-top: 1px solid var(--border-color);
|
||||
}
|
||||
|
||||
/* Badge for trade types */
|
||||
.badge {
|
||||
display: inline-block;
|
||||
padding: 4px 8px;
|
||||
background: var(--accent-primary);
|
||||
color: white;
|
||||
border-radius: 4px;
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
/* Animations */
|
||||
@keyframes pulse {
|
||||
|
||||
0%,
|
||||
100% {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
50% {
|
||||
opacity: 0.5;
|
||||
}
|
||||
}
|
||||
|
||||
.loading {
|
||||
animation: pulse 2s ease-in-out infinite;
|
||||
}
|
||||
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,465 @@
|
||||
# FxMath Quant Data Exporter
|
||||
|
||||
**Export historical price data from MetaTrader 4/5 to CSV format for FxMath Quant Strategy Finder**
|
||||
|
||||
## 📋 Overview
|
||||
|
||||
These Expert Advisors (EAs) allow you to easily export historical price data from your MetaTrader platform in the exact format required by FxMath Quant Strategy Finder and Optimizer.
|
||||
|
||||
## 📦 What's Included
|
||||
|
||||
- **FxMathQuant_DataExporter_MT4.mq4** - Expert Advisor for MetaTrader 4
|
||||
- **FxMathQuant_DataExporter_MT5.mq5** - Expert Advisor for MetaTrader 5
|
||||
- **README.md** - This file with complete instructions
|
||||
|
||||
## ✨ Features
|
||||
|
||||
✅ **Easy to Use** - Just attach to chart and click export button (or press 'E' key)
|
||||
✅ **Configurable Bars** - Set how many bars to export (default: 10,000)
|
||||
✅ **Automatic Suffix Removal** - Removes broker suffixes (e.g., XAUUSDpro → XAUUSD)
|
||||
✅ **Progress Tracking** - Shows export progress in Expert log
|
||||
✅ **CSV Format** - Exact format required by FxMath Quant application
|
||||
✅ **On-Chart Button** - Visual button to trigger export
|
||||
✅ **Keyboard Shortcut** - Press 'E' key to export
|
||||
✅ **Auto-Export Option** - Can export automatically when EA starts
|
||||
|
||||
## 📋 CSV Format Generated
|
||||
|
||||
The exported file follows this format (matching FxMath Quant requirements):
|
||||
|
||||
```csv
|
||||
Time,Open,High,Low,Close,Tick_Volume,Spread,Real_Volume
|
||||
2023-05-11 21:00:00,2013.87,2015.11,2013.40,2014.39,4858,24,0
|
||||
2023-05-11 22:00:00,2014.40,2016.50,2013.41,2014.15,4271,24,0
|
||||
2023-05-11 23:00:00,2014.17,2015.08,2013.68,2014.90,1616,24,0
|
||||
...
|
||||
```
|
||||
|
||||
## 🚀 Installation
|
||||
|
||||
### For MetaTrader 4
|
||||
|
||||
1. **Copy the File**
|
||||
- Copy `FxMathQuant_DataExporter_MT4.mq4` to:
|
||||
- `MetaTrader 4/MQL4/Experts/`
|
||||
|
||||
2. **Compile**
|
||||
- Open MetaEditor (F4 in MT4)
|
||||
- Navigate to Experts folder
|
||||
- Double-click `FxMathQuant_DataExporter_MT4.mq4`
|
||||
- Click "Compile" (F7)
|
||||
- Check for success message
|
||||
|
||||
3. **Verify Installation**
|
||||
- In MT4, open Navigator (Ctrl+N)
|
||||
- Expand "Expert Advisors"
|
||||
- You should see "FxMathQuant_DataExporter_MT4"
|
||||
|
||||
### For MetaTrader 5
|
||||
|
||||
1. **Copy the File**
|
||||
- Copy `FxMathQuant_DataExporter_MT5.mq5` to:
|
||||
- `MetaTrader 5/MQL5/Experts/`
|
||||
|
||||
2. **Compile**
|
||||
- Open MetaEditor (F4 in MT5)
|
||||
- Navigate to Experts folder
|
||||
- Double-click `FxMathQuant_DataExporter_MT5.mq5`
|
||||
- Click "Compile" (F7)
|
||||
- Check for success message
|
||||
|
||||
3. **Verify Installation**
|
||||
- In MT5, open Navigator (Ctrl+N)
|
||||
- Expand "Expert Advisors"
|
||||
- You should see "FxMathQuant_DataExporter_MT5"
|
||||
|
||||
## 📖 Usage Instructions
|
||||
|
||||
### Method 1: Using On-Chart Button (Easiest)
|
||||
|
||||
1. **Open Chart**
|
||||
- Open the chart for the symbol you want to export (e.g., XAUUSD, EURUSD)
|
||||
- Select the timeframe (H1, H4, D1, etc.)
|
||||
|
||||
2. **Attach EA**
|
||||
- Drag and drop the EA from Navigator onto the chart
|
||||
- Or right-click chart → Expert Advisors → FxMathQuant_DataExporter_MT4/MT5
|
||||
|
||||
3. **Configure Settings** (Optional)
|
||||
- In the EA parameters window:
|
||||
- **BarsToExport**: Number of bars (default: 10000)
|
||||
- **RemoveSuffix**: true/false (removes broker suffix)
|
||||
- **ExportFolder**: Folder name (default: "FxMathQuant")
|
||||
- **AutoExportOnStart**: false (set true for automatic export)
|
||||
- **ShowSuccessMessage**: true (shows completion dialog)
|
||||
- Click "OK"
|
||||
|
||||
4. **Export Data**
|
||||
- Click the blue "Export Data to CSV" button that appears on the chart
|
||||
- OR press the 'E' key on your keyboard
|
||||
- Wait for completion message
|
||||
|
||||
5. **Find Your File**
|
||||
- **MT4**: `MetaTrader 4/MQL4/Files/FxMathQuant/SYMBOL_TIMEFRAME.csv`
|
||||
- **MT5**: `MetaTrader 5/MQL5/Files/FxMathQuant/SYMBOL_TIMEFRAME.csv`
|
||||
- Example: `XAUUSD_H1.csv`, `EURUSD_M15.csv`
|
||||
|
||||
### Method 2: Auto-Export on Start
|
||||
|
||||
1. **Attach EA with Auto-Export Enabled**
|
||||
- Drag EA to chart
|
||||
- Set **AutoExportOnStart = true**
|
||||
- Click "OK"
|
||||
- Export starts immediately
|
||||
|
||||
2. **Wait for Completion**
|
||||
- Check Expert log (Tools → Experts tab)
|
||||
- Success message appears when done
|
||||
|
||||
### Method 3: Using Keyboard Shortcut
|
||||
|
||||
1. **Attach EA to Chart**
|
||||
- Drag EA onto chart
|
||||
- Click "OK" with default settings
|
||||
|
||||
2. **Press 'E' Key**
|
||||
- Make sure the chart is active
|
||||
- Press the 'E' key on your keyboard
|
||||
- Export begins immediately
|
||||
|
||||
## ⚙️ Parameter Reference
|
||||
|
||||
| Parameter | Type | Default | Description |
|
||||
|-----------|------|---------|-------------|
|
||||
| **BarsToExport** | int | 10000 | Number of historical bars to export |
|
||||
| **RemoveSuffix** | bool | true | Remove broker suffix from symbol name |
|
||||
| **ExportFolder** | string | "FxMathQuant" | Subfolder in MQL4/5/Files/ for exports |
|
||||
| **AutoExportOnStart** | bool | false | Export automatically when EA starts |
|
||||
| **ShowSuccessMessage** | bool | true | Show popup when export completes |
|
||||
|
||||
## 📁 File Naming Convention
|
||||
|
||||
The exported files are automatically named using this format:
|
||||
|
||||
```
|
||||
{SYMBOL}_{TIMEFRAME}.csv
|
||||
```
|
||||
|
||||
**Examples:**
|
||||
- `XAUUSD_H1.csv` - Gold on 1-hour chart
|
||||
- `EURUSD_M15.csv` - EUR/USD on 15-minute chart
|
||||
- `BTCUSD_D1.csv` - Bitcoin on daily chart
|
||||
|
||||
### Suffix Removal Examples
|
||||
|
||||
When **RemoveSuffix = true**, broker-specific suffixes are automatically removed:
|
||||
|
||||
| Broker Symbol | Exported As |
|
||||
|---------------|-------------|
|
||||
| XAUUSDpro | XAUUSD |
|
||||
| EURUSD.ecn | EURUSD |
|
||||
| GBPUSD.lmax | GBPUSD |
|
||||
| BTCUSDm | BTCUSD |
|
||||
| USDJPY.raw | USDJPY |
|
||||
|
||||
## 🔍 Supported Timeframes
|
||||
|
||||
### MetaTrader 4
|
||||
- M1, M5, M15, M30 (minutes)
|
||||
- H1, H4 (hours)
|
||||
- D1 (daily)
|
||||
- W1 (weekly)
|
||||
- MN1 (monthly)
|
||||
|
||||
### MetaTrader 5
|
||||
- M1, M2, M3, M4, M5, M6, M10, M12, M15, M20, M30 (minutes)
|
||||
- H1, H2, H3, H4, H6, H8, H12 (hours)
|
||||
- D1 (daily)
|
||||
- W1 (weekly)
|
||||
- MN1 (monthly)
|
||||
|
||||
## 📊 What Data is Exported
|
||||
|
||||
Each exported bar contains:
|
||||
|
||||
1. **Time** - Bar opening time (YYYY-MM-DD HH:MM:SS format)
|
||||
2. **Open** - Opening price
|
||||
3. **High** - Highest price
|
||||
4. **Low** - Lowest price
|
||||
5. **Close** - Closing price
|
||||
6. **Tick_Volume** - Number of ticks (always available)
|
||||
7. **Spread** - Bid/Ask spread in points
|
||||
8. **Real_Volume** - Exchange volume (0 if not available for symbol)
|
||||
|
||||
## 💡 Tips for Best Results
|
||||
|
||||
### Recommended Number of Bars
|
||||
|
||||
| Use Case | Recommended Bars |
|
||||
|----------|------------------|
|
||||
| **Quick Testing** | 1,000 - 3,000 |
|
||||
| **Standard Analysis** | 5,000 - 10,000 |
|
||||
| **Comprehensive Analysis** | 15,000 - 30,000 |
|
||||
| **Maximum Data** | 50,000+ |
|
||||
|
||||
**Note:** More bars = more reliable backtesting results, but slower processing in FxMath Quant.
|
||||
|
||||
### Best Timeframes for Strategy Development
|
||||
|
||||
- **H1 (1 Hour)** - Good balance of data and trading frequency
|
||||
- **H4 (4 Hours)** - Swing trading strategies
|
||||
- **D1 (Daily)** - Position trading strategies
|
||||
- **M15 (15 Minutes)** - Short-term trading strategies
|
||||
|
||||
### Data Quality Tips
|
||||
|
||||
✅ **Use Quality Brokers** - Choose brokers with good historical data
|
||||
✅ **Check Data Gaps** - Weekends and holidays create gaps (normal)
|
||||
✅ **Download More Than Needed** - Export 20-30% more bars as buffer
|
||||
✅ **Update Regularly** - Re-export data monthly for latest market conditions
|
||||
|
||||
## 🐛 Troubleshooting
|
||||
|
||||
### Issue: "Failed to create file"
|
||||
|
||||
**Causes:**
|
||||
- Permissions issue
|
||||
- Folder doesn't exist
|
||||
- Disk full
|
||||
|
||||
**Solutions:**
|
||||
1. Check MT4/MT5 has write permissions
|
||||
2. Try changing `ExportFolder` to empty string ""
|
||||
3. Free up disk space
|
||||
4. Run MT4/MT5 as administrator
|
||||
|
||||
### Issue: "Not enough bars available"
|
||||
|
||||
**Causes:**
|
||||
- Symbol doesn't have enough historical data
|
||||
- Data not downloaded yet
|
||||
|
||||
**Solutions:**
|
||||
1. In MT4/MT5, press F2 to open History Center
|
||||
2. Select the symbol and timeframe
|
||||
3. Download more history from broker
|
||||
4. Reduce `BarsToExport` parameter
|
||||
|
||||
### Issue: "Suffix not removed correctly"
|
||||
|
||||
**Causes:**
|
||||
- Unusual broker suffix not in the list
|
||||
|
||||
**Solutions:**
|
||||
1. Set `RemoveSuffix = false`
|
||||
2. Manually rename the file after export
|
||||
3. Contact support to add your broker's suffix
|
||||
|
||||
### Issue: Export button doesn't appear
|
||||
|
||||
**Causes:**
|
||||
- EA not attached correctly
|
||||
- AutoTrading disabled
|
||||
|
||||
**Solutions:**
|
||||
1. Enable AutoTrading (Ctrl+E or click button in toolbar)
|
||||
2. Re-attach EA to chart
|
||||
3. Check Expert log for error messages
|
||||
|
||||
### Issue: CSV format not recognized by FxMath Quant
|
||||
|
||||
**Causes:**
|
||||
- Different locale/region settings
|
||||
- Decimal separator issues
|
||||
|
||||
**Solutions:**
|
||||
1. Check exported file opens correctly in Excel/Notepad
|
||||
2. Verify format matches: `Time,Open,High,Low,Close,Tick_Volume,Spread,Real_Volume`
|
||||
3. Ensure decimal separator is period (.) not comma (,)
|
||||
|
||||
## 📂 Example Workflow
|
||||
|
||||
### Complete Export Process
|
||||
|
||||
1. **Open MetaTrader**
|
||||
- Launch MT4 or MT5
|
||||
|
||||
2. **Select Symbol and Timeframe**
|
||||
- Open XAUUSD chart
|
||||
- Switch to H1 timeframe
|
||||
|
||||
3. **Attach EA**
|
||||
- Drag `FxMathQuant_DataExporter_MT4` to chart
|
||||
- Set BarsToExport = 10000
|
||||
- Click OK
|
||||
|
||||
4. **Export Data**
|
||||
- Click "Export Data to CSV" button
|
||||
- Wait ~5-10 seconds (for 10,000 bars)
|
||||
|
||||
5. **Verify Export**
|
||||
- Check success message
|
||||
- Note file location
|
||||
|
||||
6. **Use in FxMath Quant**
|
||||
- Copy `XAUUSD_H1.csv` to your data folder
|
||||
- Or load directly in FxMath Quant application
|
||||
|
||||
7. **Repeat for Other Symbols/Timeframes**
|
||||
- EURUSD H1
|
||||
- GBPUSD H4
|
||||
- BTCUSD D1
|
||||
- etc.
|
||||
|
||||
## 🔄 Updating Data
|
||||
|
||||
### When to Re-Export
|
||||
|
||||
- **Monthly**: For active strategy development
|
||||
- **After Major Events**: Brexit, elections, etc.
|
||||
- **When Results Degrade**: Market conditions changed
|
||||
- **Before Going Live**: Use latest data for final validation
|
||||
|
||||
### Quick Update Process
|
||||
|
||||
1. Set `AutoExportOnStart = true`
|
||||
2. Attach EA to multiple charts
|
||||
3. Wait for all exports to complete
|
||||
4. Copy files to FxMath Quant data folder
|
||||
|
||||
## 📞 Support
|
||||
|
||||
### Getting Help
|
||||
|
||||
1. **Check Expert Log**
|
||||
- In MT4/MT5: View → Toolbox → Experts tab
|
||||
- Look for error messages
|
||||
- Note the error code
|
||||
|
||||
2. **Common Error Codes**
|
||||
- **4103**: File cannot be opened
|
||||
- **4051**: Invalid function parameter
|
||||
- **5004**: History not loaded
|
||||
|
||||
3. **Contact Support**
|
||||
- Email: support@fxmathquant.com
|
||||
- Include: Symbol, Timeframe, Error message, MT4/MT5 version
|
||||
|
||||
## 📄 Technical Details
|
||||
|
||||
### File Format Specification
|
||||
|
||||
```
|
||||
Header: Time,Open,High,Low,Close,Tick_Volume,Spread,Real_Volume
|
||||
Data: YYYY-MM-DD HH:MM:SS,FLOAT,FLOAT,FLOAT,FLOAT,INT,INT,INT
|
||||
Sorting: Chronological (oldest to newest)
|
||||
Encoding: ANSI/UTF-8
|
||||
Separator: Comma (,)
|
||||
Line Ending: CRLF (Windows)
|
||||
Decimal: Period (.)
|
||||
```
|
||||
|
||||
### Performance
|
||||
|
||||
| Bars | Export Time | File Size |
|
||||
|------|-------------|-----------|
|
||||
| 1,000 | 1-2 seconds | ~100 KB |
|
||||
| 10,000 | 5-10 seconds | ~1 MB |
|
||||
| 50,000 | 30-60 seconds | ~5 MB |
|
||||
| 100,000 | 1-2 minutes | ~10 MB |
|
||||
|
||||
**Note:** Times vary based on CPU speed and disk I/O.
|
||||
|
||||
## 📋 Checklist - First Time Export
|
||||
|
||||
Before your first export, make sure:
|
||||
|
||||
- [ ] EA file copied to correct MQL4/Experts or MQL5/Experts folder
|
||||
- [ ] EA compiled successfully in MetaEditor
|
||||
- [ ] AutoTrading enabled in MT4/MT5 (Ctrl+E)
|
||||
- [ ] Chart opened for desired symbol
|
||||
- [ ] Correct timeframe selected
|
||||
- [ ] Historical data downloaded (F2 → History Center)
|
||||
- [ ] Sufficient disk space available
|
||||
- [ ] EA attached to chart successfully
|
||||
- [ ] Export button visible on chart
|
||||
|
||||
## 🎓 Advanced Usage
|
||||
|
||||
### Exporting Multiple Symbols
|
||||
|
||||
Create a script to attach EA to multiple charts:
|
||||
|
||||
1. Open multiple charts (EURUSD, GBPUSD, XAUUSD, etc.)
|
||||
2. Set `AutoExportOnStart = true`
|
||||
3. Attach EA to all charts
|
||||
4. All exports run automatically
|
||||
|
||||
### Custom Export Folders
|
||||
|
||||
Organize by strategy or date:
|
||||
|
||||
```
|
||||
ExportFolder = "FxMathQuant/Strategy1"
|
||||
ExportFolder = "FxMathQuant/2025-12"
|
||||
ExportFolder = "FxMathQuant/Forex"
|
||||
```
|
||||
|
||||
### Batch Processing
|
||||
|
||||
For power users:
|
||||
|
||||
1. Create template with EA attached
|
||||
2. Apply template to multiple charts
|
||||
3. Set `AutoExportOnStart = true`
|
||||
4. All charts export simultaneously
|
||||
|
||||
## 🔒 Data Privacy
|
||||
|
||||
- All data is exported **locally** to your computer
|
||||
- No data is sent to external servers
|
||||
- No internet connection required for export
|
||||
- Files remain in your MT4/MT5 Files folder
|
||||
|
||||
## 📜 License
|
||||
|
||||
© 2025 FxMath Quant. All rights reserved.
|
||||
|
||||
This software is provided as a free tool for FxMath Quant users.
|
||||
|
||||
## 🚀 Version History
|
||||
|
||||
### Version 1.0 (December 2025)
|
||||
- Initial release
|
||||
- MT4 and MT5 support
|
||||
- Automatic suffix removal
|
||||
- On-chart button
|
||||
- Keyboard shortcut support
|
||||
- Configurable parameters
|
||||
- Progress tracking
|
||||
|
||||
---
|
||||
|
||||
## ✅ Quick Reference
|
||||
|
||||
### Common Commands
|
||||
|
||||
| Action | Method 1 | Method 2 |
|
||||
|--------|----------|----------|
|
||||
| **Export** | Click button on chart | Press 'E' key |
|
||||
| **Re-export** | Click button again | Restart EA |
|
||||
| **Find file** | MQL4/5/Files/FxMathQuant/ | Check success message |
|
||||
| **Change bars** | Edit EA settings | Detach and re-attach EA |
|
||||
|
||||
### Default File Locations
|
||||
|
||||
**MT4:** `C:\Users\YourName\AppData\Roaming\MetaQuotes\Terminal\{ID}\MQL4\Files\FxMathQuant\`
|
||||
|
||||
**MT5:** `C:\Users\YourName\AppData\Roaming\MetaQuotes\Terminal\{ID}\MQL5\Files\FxMathQuant\`
|
||||
|
||||
---
|
||||
|
||||
**Happy Trading! 🎯**
|
||||
|
||||
For more information about FxMath Quant Strategy Finder and Optimizer, visit:
|
||||
https://www.fxmathquant.com
|
||||
+306
@@ -0,0 +1,306 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>FxMathQuant Strategy Generator - AI-Powered Trading Strategies</title>
|
||||
|
||||
<!-- Favicon -->
|
||||
<link rel="icon" type="image/png" href="assets/favicon.png">
|
||||
|
||||
<!-- Fonts -->
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet">
|
||||
|
||||
<!-- Chart.js -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.0/dist/chart.umd.min.js"></script>
|
||||
|
||||
<!-- PapaParse for CSV -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/papaparse@5.4.1/papaparse.min.js"></script>
|
||||
|
||||
<!-- License Check (must load first) -->
|
||||
<script src="js/license-check.js"></script>
|
||||
|
||||
<!-- Styles -->
|
||||
<link rel="stylesheet" href="css/style.css">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<!-- Header -->
|
||||
<header class="header">
|
||||
<div class="container">
|
||||
<div class="header-content">
|
||||
<div class="logo">
|
||||
<h1>FxMathQuant</h1>
|
||||
<span class="subtitle">AI Strategy Generator</span>
|
||||
</div>
|
||||
<div class="header-actions">
|
||||
<button id="theme-toggle" class="btn-icon" title="Toggle Theme">
|
||||
<span class="icon">🌙</span>
|
||||
</button>
|
||||
<button id="logout-btn" class="btn-icon" title="Logout" onclick="logoutLicense()"
|
||||
style="margin-left: 10px;">
|
||||
<span class="icon">🚪</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<!-- Main Content -->
|
||||
<main class="main-content">
|
||||
<div class="container">
|
||||
|
||||
<!-- Step 1: Upload Data -->
|
||||
<section id="upload-section" class="section active">
|
||||
<div class="section-header">
|
||||
<h2>Step 1: Upload OHLC Data</h2>
|
||||
<p>Upload CSV file exported from MT4/MT5 (Time, Open, High, Low, Close)</p>
|
||||
</div>
|
||||
|
||||
<div class="upload-zone" id="upload-zone">
|
||||
<div class="upload-icon">📁</div>
|
||||
<h3>Drag & Drop CSV File</h3>
|
||||
<p>or click to browse</p>
|
||||
<input type="file" id="file-input" accept=".csv" hidden>
|
||||
<button class="btn-primary" id="browse-btn">Browse Files</button>
|
||||
</div>
|
||||
|
||||
<!-- DataProvider Download Links -->
|
||||
<div
|
||||
style="text-align: center; margin-top: 20px; padding: 15px; background: rgba(102, 126, 234, 0.1); border-radius: 10px;">
|
||||
<p style="margin-bottom: 10px; font-weight: 600;">📥 Need to export data from MT4/MT5?</p>
|
||||
<div style="display: flex; gap: 10px; justify-content: center; flex-wrap: wrap;">
|
||||
<a href="downloads/FxMathQuant_DataExporter_MT4.ex4" download class="btn-secondary"
|
||||
style="text-decoration: none; padding: 8px 16px; background: #667eea; color: white; border-radius: 6px; font-size: 14px;">
|
||||
📊 Download MT4 EA
|
||||
</a>
|
||||
<a href="downloads/FxMathQuant_DataExporter_MT5.ex5" download class="btn-secondary"
|
||||
style="text-decoration: none; padding: 8px 16px; background: #667eea; color: white; border-radius: 6px; font-size: 14px;">
|
||||
📊 Download MT5 EA
|
||||
</a>
|
||||
<a href="downloads/README.md" download class="btn-secondary"
|
||||
style="text-decoration: none; padding: 8px 16px; background: #764ba2; color: white; border-radius: 6px; font-size: 14px;">
|
||||
📖 EA Guide
|
||||
</a>
|
||||
<a href="manual.html" target="_blank" class="btn-secondary"
|
||||
style="text-decoration: none; padding: 8px 16px; background: #28a745; color: white; border-radius: 6px; font-size: 14px;">
|
||||
📚 User Manual
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="data-preview" class="data-preview hidden">
|
||||
<h3>Data Preview</h3>
|
||||
<div class="data-info">
|
||||
<span id="data-symbol">Symbol: -</span>
|
||||
<span id="data-bars">Bars: -</span>
|
||||
<span id="data-timeframe">Timeframe: -</span>
|
||||
</div>
|
||||
<div class="table-container">
|
||||
<table id="preview-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Time</th>
|
||||
<th>Open</th>
|
||||
<th>High</th>
|
||||
<th>Low</th>
|
||||
<th>Close</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="preview-body"></tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="form-group" style="margin-top: 20px;">
|
||||
<label>Data Size Limit (bars)</label>
|
||||
<select id="data-size-limit" class="form-select">
|
||||
<option value="0">Use All Data</option>
|
||||
<option value="1000">Last 1,000 bars</option>
|
||||
<option value="2000">Last 2,000 bars</option>
|
||||
<option value="5000" selected>Last 5,000 bars (Recommended)</option>
|
||||
<option value="10000">Last 10,000 bars</option>
|
||||
</select>
|
||||
<small>Limit data size for faster processing. Recommended: 5,000 bars</small>
|
||||
</div>
|
||||
<button class="btn-success" id="continue-btn">Continue to Configuration →</button>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Step 2: Configure GA -->
|
||||
<section id="config-section" class="section">
|
||||
<div class="section-header">
|
||||
<h2>Step 2: Configure Genetic Algorithm</h2>
|
||||
<p>Set parameters for strategy generation</p>
|
||||
</div>
|
||||
|
||||
<div class="config-grid">
|
||||
<!-- GA Parameters -->
|
||||
<div class="config-card">
|
||||
<h3>GA Parameters</h3>
|
||||
<div class="form-group">
|
||||
<label>Population Size</label>
|
||||
<input type="number" id="population" value="100" min="50" max="500">
|
||||
<small>Number of strategies per generation (50-500)</small>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Generations</label>
|
||||
<input type="number" id="generations" value="50" min="10" max="200">
|
||||
<small>Evolution cycles (10-200)</small>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Strategies to Find</label>
|
||||
<input type="number" id="strategies-count" value="5" min="1" max="20">
|
||||
<small>Target number of strategies (1-20)</small>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Rule Configuration -->
|
||||
<div class="config-card">
|
||||
<h3>Rule Configuration</h3>
|
||||
<div class="form-group">
|
||||
<label>Rules per Strategy</label>
|
||||
<div class="range-inputs">
|
||||
<input type="number" id="rules-min" value="3" min="1" max="10">
|
||||
<span>to</span>
|
||||
<input type="number" id="rules-max" value="8" min="1" max="10">
|
||||
</div>
|
||||
<small>Number of conditions (1-10)</small>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Lookback Period</label>
|
||||
<div class="range-inputs">
|
||||
<input type="number" id="shift-min" value="1" min="1" max="20">
|
||||
<span>to</span>
|
||||
<input type="number" id="shift-max" value="10" min="1" max="20">
|
||||
</div>
|
||||
<small>Bars to look back (1-20)</small>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Performance Filters -->
|
||||
<div class="config-card">
|
||||
<h3>Performance Filters</h3>
|
||||
<div class="form-group">
|
||||
<label>Min Profit Factor</label>
|
||||
<input type="number" id="min-pf" value="1.5" step="0.1" min="1.0" max="5.0">
|
||||
<small>Minimum PF required (1.0-5.0)</small>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Min Win Rate (%)</label>
|
||||
<input type="number" id="min-wr" value="45" min="30" max="80">
|
||||
<small>Minimum win rate (30-80%)</small>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Max Drawdown (%)</label>
|
||||
<input type="number" id="max-dd" value="25" min="5" max="50">
|
||||
<small>Maximum acceptable DD (5-50%)</small>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Min Trades</label>
|
||||
<input type="number" id="min-trades" value="30" min="10" max="200">
|
||||
<small>Minimum trade count (10-200)</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="action-buttons">
|
||||
<button class="btn-secondary" id="back-btn">← Back to Upload</button>
|
||||
<button class="btn-success" id="start-btn">Start Generation 🚀</button>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Step 3: Generation Progress -->
|
||||
<section id="progress-section" class="section">
|
||||
<div class="section-header">
|
||||
<h2>Step 3: Generating Strategies</h2>
|
||||
<p>AI is evolving profitable trading strategies...</p>
|
||||
</div>
|
||||
|
||||
<div class="progress-container">
|
||||
<div class="progress-stats">
|
||||
<div class="stat-card">
|
||||
<span class="stat-label">Current Generation</span>
|
||||
<span class="stat-value" id="current-gen">0 / 0</span>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<span class="stat-label">Best Fitness</span>
|
||||
<span class="stat-value" id="best-fitness">0.00</span>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<span class="stat-label">Strategies Found</span>
|
||||
<span class="stat-value" id="found-count">0 / 0</span>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<span class="stat-label">Elapsed Time</span>
|
||||
<span class="stat-value" id="elapsed-time">0s</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="progress-bar-container">
|
||||
<div class="progress-bar" id="progress-bar"></div>
|
||||
</div>
|
||||
|
||||
<div class="progress-log" id="progress-log"></div>
|
||||
|
||||
<button class="btn-danger" id="stop-btn">Stop Generation</button>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Step 4: Results -->
|
||||
<section id="results-section" class="section">
|
||||
<div class="section-header">
|
||||
<h2>Step 4: Generated Strategies</h2>
|
||||
<p>Your AI-discovered trading strategies are ready!</p>
|
||||
</div>
|
||||
|
||||
<div id="strategies-grid" class="strategies-grid"></div>
|
||||
|
||||
<div class="action-buttons">
|
||||
<button class="btn-secondary" id="new-search-btn">← New Search</button>
|
||||
<button class="btn-primary" id="compare-btn">Compare Selected (0)</button>
|
||||
<button class="btn-success" id="download-all-btn">Download All Strategies</button>
|
||||
</div>
|
||||
|
||||
<!-- Comparison Results Area (Hidden by default) -->
|
||||
<div id="comparison-section" class="hidden"
|
||||
style="margin-top: 40px; padding-top: 40px; border-top: 1px solid var(--border-color);">
|
||||
<div class="section-header">
|
||||
<h2>Strategy Comparison</h2>
|
||||
<p>Side-by-side performance analysis</p>
|
||||
</div>
|
||||
<div id="comparison-container" class="comparison-container"></div>
|
||||
<div class="action-buttons">
|
||||
<button class="btn-secondary" onclick="hideComparison()">Close Comparison</button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<!-- Footer -->
|
||||
<footer class="footer">
|
||||
<div class="container">
|
||||
<p>© 2025 FxMathQuant. All rights reserved. | AI-Powered Trading Strategy Generator</p>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<!-- Scripts -->
|
||||
<script src="js/strategy.js"></script>
|
||||
<script src="js/backtester.js"></script>
|
||||
<script src="js/ga-engine.js"></script>
|
||||
<script src="js/rule-parser.js"></script>
|
||||
<script src="js/mq4-converter.js"></script>
|
||||
<script src="js/mq5-converter.js"></script>
|
||||
<script src="js/ctrader-converter.js"></script>
|
||||
<script src="js/pine-converter.js"></script>
|
||||
<script src="js/mq4-generator.js"></script>
|
||||
<script src="js/mq5-generator.js"></script>
|
||||
<script src="js/report-generator.js"></script>
|
||||
<script src="js/strategy-details.js"></script>
|
||||
<script src="js/html-report-generator.js"></script>
|
||||
<script src="js/ui-controller.js"></script>
|
||||
<script src="js/main.js?v=2.0"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -0,0 +1,464 @@
|
||||
/**
|
||||
* Backtester - Fast backtesting engine using typed arrays
|
||||
*/
|
||||
class Backtester {
|
||||
constructor(data, strategy) {
|
||||
this.strategy = strategy;
|
||||
this.dataLength = data.length;
|
||||
|
||||
// Use typed arrays for performance
|
||||
this.open = new Float64Array(data.length);
|
||||
this.high = new Float64Array(data.length);
|
||||
this.low = new Float64Array(data.length);
|
||||
this.close = new Float64Array(data.length);
|
||||
this.time = new Array(data.length);
|
||||
|
||||
// Populate arrays
|
||||
for (let i = 0; i < data.length; i++) {
|
||||
this.open[i] = parseFloat(data[i].open) || 0;
|
||||
this.high[i] = parseFloat(data[i].high) || 0;
|
||||
this.low[i] = parseFloat(data[i].low) || 0;
|
||||
this.close[i] = parseFloat(data[i].close) || 0;
|
||||
this.time[i] = data[i].time || i;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Run backtest and return metrics
|
||||
*/
|
||||
run() {
|
||||
console.log('📊 Backtester.run() - Data length:', this.dataLength);
|
||||
const atr = this.calculateATR(this.strategy.atrPeriod);
|
||||
console.log('✅ ATR calculated, period:', this.strategy.atrPeriod);
|
||||
|
||||
let balance = 10000;
|
||||
let position = null;
|
||||
const trades = [];
|
||||
const equity = [balance];
|
||||
|
||||
// Start from ATR period to have enough data
|
||||
for (let i = this.strategy.atrPeriod + 10; i < this.dataLength; i++) {
|
||||
// Check for close at opposite signal first (if enabled)
|
||||
if (position && this.strategy.closeAtOpposite) {
|
||||
if (position.type === 'BUY' && this.checkSellSignal(i)) {
|
||||
// Close BUY and open SELL
|
||||
const closePrice = this.close[i];
|
||||
const profit = closePrice - position.entry;
|
||||
balance += profit;
|
||||
trades.push({
|
||||
type: 'BUY',
|
||||
entry: position.entry,
|
||||
exit: closePrice,
|
||||
profit: profit,
|
||||
reason: 'opposite',
|
||||
openTime: position.openTime,
|
||||
closeTime: this.time[i]
|
||||
});
|
||||
equity.push(balance);
|
||||
position = this.openPosition('SELL', i, atr[i]);
|
||||
continue;
|
||||
} else if (position.type === 'SELL' && this.checkBuySignal(i)) {
|
||||
// Close SELL and open BUY
|
||||
const closePrice = this.close[i];
|
||||
const profit = position.entry - closePrice;
|
||||
balance += profit;
|
||||
trades.push({
|
||||
type: 'SELL',
|
||||
entry: position.entry,
|
||||
exit: closePrice,
|
||||
profit: profit,
|
||||
reason: 'opposite',
|
||||
openTime: position.openTime,
|
||||
closeTime: this.time[i]
|
||||
});
|
||||
equity.push(balance);
|
||||
position = this.openPosition('BUY', i, atr[i]);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// Check for normal exit (TP/SL)
|
||||
if (position) {
|
||||
const exitResult = this.checkExit(position, i);
|
||||
if (exitResult) {
|
||||
balance += exitResult.profit;
|
||||
trades.push(exitResult);
|
||||
equity.push(balance);
|
||||
position = null;
|
||||
}
|
||||
}
|
||||
|
||||
// Check for entry signals (only if no position)
|
||||
if (!position) {
|
||||
if (this.checkBuySignal(i)) {
|
||||
position = this.openPosition('BUY', i, atr[i]);
|
||||
} else if (this.checkSellSignal(i)) {
|
||||
position = this.openPosition('SELL', i, atr[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Close any open position at the end
|
||||
if (position) {
|
||||
const exitPrice = this.close[this.dataLength - 1];
|
||||
const profit = exitPrice - position.entry;
|
||||
balance += profit;
|
||||
trades.push({
|
||||
type: 'BUY',
|
||||
entry: position.entry,
|
||||
exit: exitPrice,
|
||||
profit: profit,
|
||||
reason: 'end_of_data'
|
||||
});
|
||||
equity.push(balance);
|
||||
}
|
||||
|
||||
return this.calculateMetrics(trades, balance, equity);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if BUY signal is triggered
|
||||
*/
|
||||
checkBuySignal(index) {
|
||||
// All rules must be true
|
||||
for (const rule of this.strategy.rules) {
|
||||
if (!this.evaluateRule(rule, index)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if SELL signal is triggered (symmetrical to BUY with inverted operators)
|
||||
*/
|
||||
checkSellSignal(index) {
|
||||
// All SELL rules must be true (SELL rules = BUY rules with inverted operators)
|
||||
for (const rule of this.strategy.rules) {
|
||||
if (!this.evaluateSellRule(rule, index)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Evaluate a SELL rule (BUY rule with inverted operator)
|
||||
*/
|
||||
evaluateSellRule(rule, index) {
|
||||
let leftValue, rightValue;
|
||||
|
||||
if (rule.type === 'simple') {
|
||||
leftValue = this.getPriceValue(rule.left.price, index - rule.left.shift);
|
||||
rightValue = this.getPriceValue(rule.right.price, index - rule.right.shift);
|
||||
} else {
|
||||
// Arithmetic rule
|
||||
const left1 = this.getPriceValue(rule.left.price1, index - rule.left.shift1);
|
||||
const left2 = this.getPriceValue(rule.left.price2, index - rule.left.shift2);
|
||||
const rightPrice = this.getPriceValue(rule.right.price, index - rule.right.shift);
|
||||
|
||||
switch (rule.left.op) {
|
||||
case '+': leftValue = left1 + left2; break;
|
||||
case '-': leftValue = left1 - left2; break;
|
||||
case '*': leftValue = left1 * left2; break;
|
||||
default: leftValue = left1;
|
||||
}
|
||||
|
||||
rightValue = rightPrice * rule.right.multiplier;
|
||||
}
|
||||
|
||||
// Invert the operator for SELL
|
||||
switch (rule.operator) {
|
||||
case '>': return leftValue <= rightValue;
|
||||
case '<': return leftValue >= rightValue;
|
||||
case '>=': return leftValue < rightValue;
|
||||
case '<=': return leftValue > rightValue;
|
||||
default: return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Evaluate a single rule
|
||||
*/
|
||||
evaluateRule(rule, index) {
|
||||
let leftValue, rightValue;
|
||||
|
||||
if (rule.type === 'simple') {
|
||||
leftValue = this.getPriceValue(rule.left.price, index - rule.left.shift);
|
||||
rightValue = this.getPriceValue(rule.right.price, index - rule.right.shift);
|
||||
} else {
|
||||
// Arithmetic rule
|
||||
const left1 = this.getPriceValue(rule.left.price1, index - rule.left.shift1);
|
||||
const left2 = this.getPriceValue(rule.left.price2, index - rule.left.shift2);
|
||||
|
||||
switch (rule.left.op) {
|
||||
case '+': leftValue = left1 + left2; break;
|
||||
case '-': leftValue = left1 - left2; break;
|
||||
case '*': leftValue = left1 * left2; break;
|
||||
default: leftValue = left1;
|
||||
}
|
||||
|
||||
const rightPrice = this.getPriceValue(rule.right.price, index - rule.right.shift);
|
||||
rightValue = rightPrice * rule.right.multiplier;
|
||||
}
|
||||
|
||||
// Evaluate operator
|
||||
switch (rule.operator) {
|
||||
case '>': return leftValue > rightValue;
|
||||
case '<': return leftValue < rightValue;
|
||||
case '>=': return leftValue >= rightValue;
|
||||
case '<=': return leftValue <= rightValue;
|
||||
default: return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get price value at specific index
|
||||
*/
|
||||
getPriceValue(priceType, index) {
|
||||
if (index < 0 || index >= this.dataLength) return 0;
|
||||
|
||||
switch (priceType) {
|
||||
case 'open': return this.open[index];
|
||||
case 'high': return this.high[index];
|
||||
case 'low': return this.low[index];
|
||||
case 'close': return this.close[index];
|
||||
default: return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Open a new position
|
||||
*/
|
||||
openPosition(type, index, atr) {
|
||||
const entry = this.close[index];
|
||||
let sl, tp;
|
||||
|
||||
if (type === 'BUY') {
|
||||
sl = entry - (atr * this.strategy.slMultiplier);
|
||||
tp = entry + (atr * this.strategy.tpMultiplier);
|
||||
} else { // SELL
|
||||
sl = entry + (atr * this.strategy.slMultiplier);
|
||||
tp = entry - (atr * this.strategy.tpMultiplier);
|
||||
}
|
||||
|
||||
return {
|
||||
type: type,
|
||||
entry: entry,
|
||||
sl: sl,
|
||||
tp: tp,
|
||||
openIndex: index,
|
||||
openTime: this.time[index]
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if position should be exited
|
||||
*/
|
||||
checkExit(position, index) {
|
||||
const high = this.high[index];
|
||||
const low = this.low[index];
|
||||
|
||||
if (position.type === 'BUY') {
|
||||
// BUY: TP is above entry, SL is below
|
||||
if (high >= position.tp) {
|
||||
return {
|
||||
type: position.type,
|
||||
entry: position.entry,
|
||||
exit: position.tp,
|
||||
profit: position.tp - position.entry,
|
||||
reason: 'take_profit',
|
||||
openTime: position.openTime,
|
||||
closeTime: this.time[index]
|
||||
};
|
||||
}
|
||||
|
||||
if (low <= position.sl) {
|
||||
return {
|
||||
type: position.type,
|
||||
entry: position.entry,
|
||||
exit: position.sl,
|
||||
profit: position.sl - position.entry,
|
||||
reason: 'stop_loss',
|
||||
openTime: position.openTime,
|
||||
closeTime: this.time[index]
|
||||
};
|
||||
}
|
||||
} else { // SELL
|
||||
// SELL: TP is below entry, SL is above
|
||||
if (low <= position.tp) {
|
||||
return {
|
||||
type: position.type,
|
||||
entry: position.entry,
|
||||
exit: position.tp,
|
||||
profit: position.entry - position.tp,
|
||||
reason: 'take_profit',
|
||||
openTime: position.openTime,
|
||||
closeTime: this.time[index]
|
||||
};
|
||||
}
|
||||
|
||||
if (high >= position.sl) {
|
||||
return {
|
||||
type: position.type,
|
||||
entry: position.entry,
|
||||
exit: position.sl,
|
||||
profit: position.entry - position.sl,
|
||||
reason: 'stop_loss',
|
||||
openTime: position.openTime,
|
||||
closeTime: this.time[index]
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate ATR (Average True Range)
|
||||
*/
|
||||
calculateATR(period) {
|
||||
const atr = new Float64Array(this.dataLength);
|
||||
const tr = new Float64Array(this.dataLength);
|
||||
|
||||
// Calculate True Range
|
||||
for (let i = 1; i < this.dataLength; i++) {
|
||||
const hl = this.high[i] - this.low[i];
|
||||
const hc = Math.abs(this.high[i] - this.close[i - 1]);
|
||||
const lc = Math.abs(this.low[i] - this.close[i - 1]);
|
||||
tr[i] = Math.max(hl, hc, lc);
|
||||
}
|
||||
|
||||
// Calculate ATR using SMA
|
||||
for (let i = period; i < this.dataLength; i++) {
|
||||
let sum = 0;
|
||||
for (let j = 0; j < period; j++) {
|
||||
sum += tr[i - j];
|
||||
}
|
||||
atr[i] = sum / period;
|
||||
}
|
||||
|
||||
return atr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate performance metrics
|
||||
*/
|
||||
calculateMetrics(trades, finalBalance, equity) {
|
||||
if (trades.length === 0) {
|
||||
return {
|
||||
totalTrades: 0,
|
||||
buyTrades: 0,
|
||||
sellTrades: 0,
|
||||
winRate: 0,
|
||||
profitFactor: 0,
|
||||
maxDrawdown: 0,
|
||||
finalBalance: finalBalance,
|
||||
totalProfit: 0,
|
||||
avgWin: 0,
|
||||
avgLoss: 0,
|
||||
largestWin: 0,
|
||||
largestLoss: 0
|
||||
};
|
||||
}
|
||||
|
||||
const winners = trades.filter(t => t.profit > 0);
|
||||
const losers = trades.filter(t => t.profit <= 0);
|
||||
const buyTrades = trades.filter(t => t.type === 'BUY');
|
||||
const sellTrades = trades.filter(t => t.type === 'SELL');
|
||||
|
||||
const grossProfit = winners.reduce((sum, t) => sum + t.profit, 0);
|
||||
const grossLoss = Math.abs(losers.reduce((sum, t) => sum + t.profit, 0));
|
||||
|
||||
const profitFactor = grossLoss > 0 ? grossProfit / grossLoss : (grossProfit > 0 ? 10 : 0);
|
||||
const winRate = (winners.length / trades.length) * 100;
|
||||
|
||||
const maxDD = this.calculateMaxDrawdown(equity);
|
||||
|
||||
const avgWin = winners.length > 0 ? grossProfit / winners.length : 0;
|
||||
const avgLoss = losers.length > 0 ? grossLoss / losers.length : 0;
|
||||
|
||||
const largestWin = winners.length > 0 ? Math.max(...winners.map(t => t.profit)) : 0;
|
||||
const largestLoss = losers.length > 0 ? Math.min(...losers.map(t => t.profit)) : 0;
|
||||
|
||||
// Calculate hourly performance (0-23 hours)
|
||||
const hourlyStats = Array.from({ length: 24 }, () => ({
|
||||
trades: 0,
|
||||
wins: 0,
|
||||
losses: 0,
|
||||
profit: 0
|
||||
}));
|
||||
|
||||
trades.forEach(trade => {
|
||||
if (trade.openTime) {
|
||||
const hour = new Date(trade.openTime).getHours();
|
||||
hourlyStats[hour].trades++;
|
||||
if (trade.profit > 0) {
|
||||
hourlyStats[hour].wins++;
|
||||
} else {
|
||||
hourlyStats[hour].losses++;
|
||||
}
|
||||
hourlyStats[hour].profit += trade.profit;
|
||||
}
|
||||
});
|
||||
|
||||
// Find best and worst hours
|
||||
let bestHour = { hour: 0, profit: -Infinity };
|
||||
let worstHour = { hour: 0, profit: Infinity };
|
||||
|
||||
hourlyStats.forEach((stats, hour) => {
|
||||
if (stats.trades > 0) {
|
||||
if (stats.profit > bestHour.profit) {
|
||||
bestHour = { hour, profit: stats.profit };
|
||||
}
|
||||
if (stats.profit < worstHour.profit) {
|
||||
worstHour = { hour, profit: stats.profit };
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
totalTrades: trades.length,
|
||||
buyTrades: buyTrades.length,
|
||||
sellTrades: sellTrades.length,
|
||||
winningTrades: winners.length,
|
||||
losingTrades: losers.length,
|
||||
winRate: winRate,
|
||||
profitFactor: profitFactor,
|
||||
maxDrawdown: maxDD,
|
||||
finalBalance: finalBalance,
|
||||
totalProfit: finalBalance - 10000,
|
||||
grossProfit: grossProfit,
|
||||
grossLoss: grossLoss,
|
||||
avgWin: avgWin,
|
||||
avgLoss: avgLoss,
|
||||
largestWin: largestWin,
|
||||
largestLoss: largestLoss,
|
||||
hourlyStats: hourlyStats,
|
||||
bestHour: bestHour,
|
||||
worstHour: worstHour,
|
||||
equity: equity,
|
||||
trades: trades
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate maximum drawdown
|
||||
*/
|
||||
calculateMaxDrawdown(equity) {
|
||||
let maxDD = 0;
|
||||
let peak = equity[0];
|
||||
|
||||
for (let i = 1; i < equity.length; i++) {
|
||||
if (equity[i] > peak) {
|
||||
peak = equity[i];
|
||||
}
|
||||
const dd = ((peak - equity[i]) / peak) * 100;
|
||||
if (dd > maxDD) {
|
||||
maxDD = dd;
|
||||
}
|
||||
}
|
||||
|
||||
return maxDD;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,314 @@
|
||||
// cTrader (cBot) Converter
|
||||
class CTraderConverter {
|
||||
constructor(strategy) {
|
||||
this.strategy = strategy;
|
||||
this.parser = new RuleParser();
|
||||
}
|
||||
|
||||
generate() {
|
||||
const { parameters, buy_rules, sell_rules } = this.strategy;
|
||||
const symbol = parameters.symbol || 'EURUSD';
|
||||
const atrPeriod = parameters.atr_period || 14;
|
||||
const slMultiplier = parameters.sl_multiplier || 2.0;
|
||||
const tpMultiplier = parameters.tp_multiplier || 3.0;
|
||||
|
||||
// Generate random 6-digit magic number
|
||||
const magicNumber = Math.floor(100000 + Math.random() * 900000);
|
||||
|
||||
const buyConditions = this.parser.parseRules(buy_rules, 'csharp');
|
||||
const sellConditions = this.parser.parseRules(sell_rules, 'csharp');
|
||||
|
||||
// Convert MQL-style array access to cTrader MarketSeries
|
||||
const convertToCTrader = (condition) => {
|
||||
return condition
|
||||
.replace(/Open\[(\d+)\]/g, 'MarketSeries.Open.Last($1)')
|
||||
.replace(/High\[(\d+)\]/g, 'MarketSeries.High.Last($1)')
|
||||
.replace(/Low\[(\d+)\]/g, 'MarketSeries.Low.Last($1)')
|
||||
.replace(/Close\[(\d+)\]/g, 'MarketSeries.Close.Last($1)');
|
||||
};
|
||||
|
||||
const buyConditionsCTrader = buyConditions.map(convertToCTrader);
|
||||
const sellConditionsCTrader = sellConditions.map(convertToCTrader);
|
||||
|
||||
return `using System;
|
||||
using System.Linq;
|
||||
using cAlgo.API;
|
||||
using cAlgo.API.Indicators;
|
||||
using cAlgo.API.Internals;
|
||||
using cAlgo.Indicators;
|
||||
|
||||
namespace cAlgo.Robots
|
||||
{
|
||||
[Robot(TimeZone = TimeZones.UTC, AccessRights = AccessRights.None)]
|
||||
public class Strategy_${symbol} : Robot
|
||||
{
|
||||
//=== TRADE SETTINGS ===
|
||||
[Parameter("Lot Size", DefaultValue = 0.1, MinValue = 0.01)]
|
||||
public double LotSize { get; set; }
|
||||
|
||||
[Parameter("ATR Period", DefaultValue = ${atrPeriod}, MinValue = 1)]
|
||||
public int ATR_Period { get; set; }
|
||||
|
||||
[Parameter("SL ATR Multiplier", DefaultValue = ${slMultiplier.toFixed(2)}, MinValue = 0.1)]
|
||||
public double SL_Multiplier { get; set; }
|
||||
|
||||
[Parameter("TP ATR Multiplier", DefaultValue = ${tpMultiplier.toFixed(2)}, MinValue = 0.1)]
|
||||
public double TP_Multiplier { get; set; }
|
||||
|
||||
[Parameter("Magic Number", DefaultValue = ${magicNumber})]
|
||||
public int MagicNumber { get; set; }
|
||||
|
||||
[Parameter("Trade Comment", DefaultValue = "FxMath EA")]
|
||||
public string TradeComment { get; set; }
|
||||
|
||||
[Parameter("Slippage", DefaultValue = 3, MinValue = 0)]
|
||||
public int Slippage { get; set; }
|
||||
|
||||
//=== SL/TP SETTINGS ===
|
||||
[Parameter("Enable Hard SL", DefaultValue = true, Group = "SL/TP")]
|
||||
public bool EnableHardSL { get; set; }
|
||||
|
||||
[Parameter("Enable Hard TP", DefaultValue = true, Group = "SL/TP")]
|
||||
public bool EnableHardTP { get; set; }
|
||||
|
||||
//=== TRAILING STOP ===
|
||||
[Parameter("Enable Trailing", DefaultValue = false, Group = "Trailing")]
|
||||
public bool EnableTrailing { get; set; }
|
||||
|
||||
[Parameter("Trailing Start (pips)", DefaultValue = 30, MinValue = 1, Group = "Trailing")]
|
||||
public double TrailingStart { get; set; }
|
||||
|
||||
[Parameter("Trailing Step (pips)", DefaultValue = 10, MinValue = 1, Group = "Trailing")]
|
||||
public double TrailingStep { get; set; }
|
||||
|
||||
//=== BREAKEVEN ===
|
||||
[Parameter("Enable Breakeven", DefaultValue = false, Group = "Breakeven")]
|
||||
public bool EnableBreakeven { get; set; }
|
||||
|
||||
[Parameter("Breakeven Start (pips)", DefaultValue = 20, MinValue = 1, Group = "Breakeven")]
|
||||
public double BreakevenStart { get; set; }
|
||||
|
||||
[Parameter("Breakeven Offset (pips)", DefaultValue = 2, MinValue = 0, Group = "Breakeven")]
|
||||
public double BreakevenOffset { get; set; }
|
||||
|
||||
//=== TIME FILTER ===
|
||||
[Parameter("Enable Time Filter", DefaultValue = false, Group = "Time Filter")]
|
||||
public bool EnableTimeFilter { get; set; }
|
||||
|
||||
[Parameter("Start Hour", DefaultValue = 8, MinValue = 0, MaxValue = 23, Group = "Time Filter")]
|
||||
public int StartHour { get; set; }
|
||||
|
||||
[Parameter("Start Minute", DefaultValue = 0, MinValue = 0, MaxValue = 59, Group = "Time Filter")]
|
||||
public int StartMinute { get; set; }
|
||||
|
||||
[Parameter("End Hour", DefaultValue = 22, MinValue = 0, MaxValue = 23, Group = "Time Filter")]
|
||||
public int EndHour { get; set; }
|
||||
|
||||
[Parameter("End Minute", DefaultValue = 0, MinValue = 0, MaxValue = 59, Group = "Time Filter")]
|
||||
public int EndMinute { get; set; }
|
||||
|
||||
//=== SIGNAL SETTINGS ===
|
||||
[Parameter("Close on Opposite", DefaultValue = true, Group = "Signals")]
|
||||
public bool CloseOnOpposite { get; set; }
|
||||
|
||||
//=== DISPLAY SETTINGS ===
|
||||
[Parameter("Show Chart Info", DefaultValue = true, Group = "Display")]
|
||||
public bool ShowChartInfo { get; set; }
|
||||
|
||||
private AverageTrueRange atr;
|
||||
private Position currentPosition;
|
||||
|
||||
protected override void OnStart()
|
||||
{
|
||||
atr = Indicators.AverageTrueRange(ATR_Period, MovingAverageType.Simple);
|
||||
Print("Strategy cBot initialized for ${symbol}");
|
||||
Print("Magic Number: " + MagicNumber);
|
||||
}
|
||||
|
||||
protected override void OnTick()
|
||||
{
|
||||
currentPosition = Positions.Find(TradeComment, SymbolName);
|
||||
|
||||
if (currentPosition != null)
|
||||
{
|
||||
ManagePosition();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Check time filter
|
||||
if (EnableTimeFilter && !IsTimeAllowed())
|
||||
return;
|
||||
|
||||
// Check signals
|
||||
if (CheckBuySignal())
|
||||
{
|
||||
OpenBuyOrder();
|
||||
}
|
||||
else if (CheckSellSignal())
|
||||
{
|
||||
OpenSellOrder();
|
||||
}
|
||||
}
|
||||
|
||||
if (ShowChartInfo)
|
||||
DisplayChartInfo();
|
||||
}
|
||||
|
||||
private bool IsTimeAllowed()
|
||||
{
|
||||
var currentTime = Server.Time;
|
||||
int currentMinutes = currentTime.Hour * 60 + currentTime.Minute;
|
||||
int startMinutes = StartHour * 60 + StartMinute;
|
||||
int endMinutes = EndHour * 60 + EndMinute;
|
||||
|
||||
if (startMinutes < endMinutes)
|
||||
return currentMinutes >= startMinutes && currentMinutes < endMinutes;
|
||||
else
|
||||
return currentMinutes >= startMinutes || currentMinutes < endMinutes;
|
||||
}
|
||||
|
||||
private void ManagePosition()
|
||||
{
|
||||
if (currentPosition == null)
|
||||
return;
|
||||
|
||||
// Check for opposite signal
|
||||
if (CloseOnOpposite)
|
||||
{
|
||||
if (currentPosition.TradeType == TradeType.Buy && CheckSellSignal())
|
||||
{
|
||||
ClosePosition(currentPosition);
|
||||
return;
|
||||
}
|
||||
else if (currentPosition.TradeType == TradeType.Sell && CheckBuySignal())
|
||||
{
|
||||
ClosePosition(currentPosition);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
double currentPrice = currentPosition.TradeType == TradeType.Buy ? Symbol.Bid : Symbol.Ask;
|
||||
double pipValue = Symbol.PipSize;
|
||||
|
||||
// Breakeven
|
||||
if (EnableBreakeven)
|
||||
{
|
||||
double profit = currentPosition.TradeType == TradeType.Buy ?
|
||||
(currentPrice - currentPosition.EntryPrice) / pipValue :
|
||||
(currentPosition.EntryPrice - currentPrice) / pipValue;
|
||||
|
||||
if (profit >= BreakevenStart)
|
||||
{
|
||||
double newSL = currentPosition.EntryPrice + (BreakevenOffset * pipValue *
|
||||
(currentPosition.TradeType == TradeType.Buy ? 1 : -1));
|
||||
|
||||
if ((currentPosition.TradeType == TradeType.Buy && newSL > currentPosition.StopLoss) ||
|
||||
(currentPosition.TradeType == TradeType.Sell && (currentPosition.StopLoss == null || newSL < currentPosition.StopLoss)))
|
||||
{
|
||||
ModifyPosition(currentPosition, newSL, currentPosition.TakeProfit);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Trailing Stop
|
||||
if (EnableTrailing)
|
||||
{
|
||||
double profit = currentPosition.TradeType == TradeType.Buy ?
|
||||
(currentPrice - currentPosition.EntryPrice) / pipValue :
|
||||
(currentPosition.EntryPrice - currentPrice) / pipValue;
|
||||
|
||||
if (profit >= TrailingStart)
|
||||
{
|
||||
double newSL = currentPrice - (TrailingStep * pipValue *
|
||||
(currentPosition.TradeType == TradeType.Buy ? 1 : -1));
|
||||
|
||||
if ((currentPosition.TradeType == TradeType.Buy && newSL > currentPosition.StopLoss) ||
|
||||
(currentPosition.TradeType == TradeType.Sell && (currentPosition.StopLoss == null || newSL < currentPosition.StopLoss)))
|
||||
{
|
||||
ModifyPosition(currentPosition, newSL, currentPosition.TakeProfit);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void DisplayChartInfo()
|
||||
{
|
||||
string info = "\\n=== " + TradeComment + " ===\\n";
|
||||
info += "Symbol: " + SymbolName + "\\n";
|
||||
info += "Magic: " + MagicNumber + "\\n";
|
||||
|
||||
if (currentPosition != null)
|
||||
{
|
||||
info += "Position: " + currentPosition.TradeType + "\\n";
|
||||
info += "Profit: " + currentPosition.NetProfit.ToString("F2") + "\\n";
|
||||
}
|
||||
else
|
||||
{
|
||||
info += "No Position\\n";
|
||||
}
|
||||
|
||||
Chart.DrawStaticText("info", info, VerticalAlignment.Top, HorizontalAlignment.Left, Color.White);
|
||||
}
|
||||
|
||||
private bool CheckBuySignal()
|
||||
{
|
||||
return ${buyConditionsCTrader.join(' &&\n ')};
|
||||
}
|
||||
|
||||
private bool CheckSellSignal()
|
||||
{
|
||||
return ${sellConditionsCTrader.join(' &&\n ')};
|
||||
}
|
||||
|
||||
private void OpenBuyOrder()
|
||||
{
|
||||
double atrValue = atr.Result.LastValue;
|
||||
double price = Symbol.Ask;
|
||||
double sl = EnableHardSL ? price - (atrValue * SL_Multiplier) : 0;
|
||||
double tp = EnableHardTP ? price + (atrValue * TP_Multiplier) : 0;
|
||||
|
||||
double volumeInUnits = Symbol.QuantityToVolumeInUnits(LotSize);
|
||||
|
||||
var result = ExecuteMarketOrder(TradeType.Buy, SymbolName, volumeInUnits, TradeComment,
|
||||
EnableHardSL ? sl : (double?)null,
|
||||
EnableHardTP ? tp : (double?)null);
|
||||
|
||||
if (result.IsSuccessful)
|
||||
{
|
||||
Print("Buy order opened at " + price + " SL: " + sl + " TP: " + tp);
|
||||
}
|
||||
else
|
||||
{
|
||||
Print("Error opening buy order: " + result.Error);
|
||||
}
|
||||
}
|
||||
|
||||
private void OpenSellOrder()
|
||||
{
|
||||
double atrValue = atr.Result.LastValue;
|
||||
double price = Symbol.Bid;
|
||||
double sl = EnableHardSL ? price + (atrValue * SL_Multiplier) : 0;
|
||||
double tp = EnableHardTP ? price - (atrValue * TP_Multiplier) : 0;
|
||||
|
||||
double volumeInUnits = Symbol.QuantityToVolumeInUnits(LotSize);
|
||||
|
||||
var result = ExecuteMarketOrder(TradeType.Sell, SymbolName, volumeInUnits, TradeComment,
|
||||
EnableHardSL ? sl : (double?)null,
|
||||
EnableHardTP ? tp : (double?)null);
|
||||
|
||||
if (result.IsSuccessful)
|
||||
{
|
||||
Print("Sell order opened at " + price + " SL: " + sl + " TP: " + tp);
|
||||
}
|
||||
else
|
||||
{
|
||||
Print("Error opening sell order: " + result.Error);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
window.CTraderConverter = CTraderConverter;
|
||||
+331
@@ -0,0 +1,331 @@
|
||||
/**
|
||||
* Genetic Algorithm Engine - Evolves trading strategies
|
||||
*/
|
||||
class GeneticOptimizer {
|
||||
constructor(data, config) {
|
||||
this.data = data;
|
||||
this.config = {
|
||||
populationSize: config.populationSize || 100,
|
||||
generations: config.generations || 50,
|
||||
strategiesCount: config.strategiesCount || 5,
|
||||
rulesRange: config.rulesRange || [3, 8],
|
||||
shiftRange: config.shiftRange || [1, 10],
|
||||
minTrades: config.minTrades || 30,
|
||||
minPF: config.minPF || 1.5,
|
||||
maxDD: config.maxDD || 25,
|
||||
minWR: config.minWR || 45
|
||||
};
|
||||
|
||||
this.population = [];
|
||||
this.foundStrategies = [];
|
||||
this.isRunning = false;
|
||||
this.currentGeneration = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize random population
|
||||
*/
|
||||
initializePopulation() {
|
||||
this.population = [];
|
||||
for (let i = 0; i < this.config.populationSize; i++) {
|
||||
const strategy = new Strategy();
|
||||
strategy.generateRandomRules(this.config.rulesRange, this.config.shiftRange);
|
||||
strategy.randomizeParameters();
|
||||
this.population.push(strategy);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Evaluate fitness for all strategies
|
||||
*/
|
||||
evaluatePopulation() {
|
||||
for (const strategy of this.population) {
|
||||
const backtester = new Backtester(this.data, strategy);
|
||||
strategy.metrics = backtester.run();
|
||||
strategy.fitness = this.calculateFitness(strategy.metrics);
|
||||
}
|
||||
|
||||
// Sort by fitness (descending)
|
||||
this.population.sort((a, b) => b.fitness - a.fitness);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate fitness score
|
||||
*/
|
||||
calculateFitness(metrics) {
|
||||
if (metrics.totalTrades === 0) return 0;
|
||||
|
||||
const pf = Math.min(metrics.profitFactor, 10);
|
||||
const trades = Math.sqrt(metrics.totalTrades);
|
||||
const wrBonus = metrics.winRate > 50 ? 1 + (metrics.winRate - 50) / 100 : 1;
|
||||
const ddPenalty = 1 + (metrics.maxDrawdown / 100);
|
||||
|
||||
// Calculate BUY/SELL balance bonus
|
||||
const buyRatio = (metrics.buyTrades / metrics.totalTrades) * 100;
|
||||
const sellRatio = (metrics.sellTrades / metrics.totalTrades) * 100;
|
||||
|
||||
// Reward strategies with 30-70% split (most balanced)
|
||||
// Penalize strategies with <20% or >80% of one type
|
||||
let balanceBonus = 1.0;
|
||||
if (buyRatio >= 30 && buyRatio <= 70) {
|
||||
balanceBonus = 1.2; // 20% bonus for good balance
|
||||
} else if (buyRatio >= 20 && buyRatio <= 80) {
|
||||
balanceBonus = 1.1; // 10% bonus for acceptable balance
|
||||
} else {
|
||||
balanceBonus = 0.5; // 50% penalty for poor balance
|
||||
}
|
||||
|
||||
return (Math.pow(pf, 1.5) * trades * wrBonus * balanceBonus) / ddPenalty;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if strategy meets criteria
|
||||
*/
|
||||
meetsCriteria(strategy) {
|
||||
const m = strategy.metrics;
|
||||
|
||||
// Check basic criteria
|
||||
const meetsBasic = (
|
||||
m.totalTrades >= this.config.minTrades &&
|
||||
m.profitFactor >= this.config.minPF &&
|
||||
m.maxDrawdown <= this.config.maxDD &&
|
||||
m.winRate >= this.config.minWR
|
||||
);
|
||||
|
||||
if (!meetsBasic) return false;
|
||||
|
||||
// Check BUY/SELL balance (should be between 20-80%)
|
||||
const buyRatio = (m.buyTrades / m.totalTrades) * 100;
|
||||
const sellRatio = (m.sellTrades / m.totalTrades) * 100;
|
||||
|
||||
// Require at least 20% of each type for balance
|
||||
const isBalanced = buyRatio >= 20 && sellRatio >= 20;
|
||||
|
||||
console.log(`📊 Strategy balance: BUY=${buyRatio.toFixed(1)}%, SELL=${sellRatio.toFixed(1)}%, Balanced=${isBalanced}`);
|
||||
|
||||
return isBalanced;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tournament selection
|
||||
*/
|
||||
tournamentSelection() {
|
||||
const tournamentSize = 3;
|
||||
let best = null;
|
||||
|
||||
for (let i = 0; i < tournamentSize; i++) {
|
||||
const candidate = this.population[Math.floor(Math.random() * this.population.length)];
|
||||
if (!best || candidate.fitness > best.fitness) {
|
||||
best = candidate;
|
||||
}
|
||||
}
|
||||
|
||||
return best.copy();
|
||||
}
|
||||
|
||||
/**
|
||||
* Crossover two parent strategies
|
||||
*/
|
||||
crossover(parent1, parent2) {
|
||||
const child = new Strategy();
|
||||
|
||||
// Crossover rules
|
||||
const splitPoint = Math.floor(Math.random() * Math.min(parent1.rules.length, parent2.rules.length));
|
||||
child.rules = [
|
||||
...parent1.rules.slice(0, splitPoint),
|
||||
...parent2.rules.slice(splitPoint)
|
||||
];
|
||||
|
||||
// Ensure rules count is within range
|
||||
while (child.rules.length < this.config.rulesRange[0]) {
|
||||
child.rules.push(child.generateRule(this.config.shiftRange));
|
||||
}
|
||||
while (child.rules.length > this.config.rulesRange[1]) {
|
||||
child.rules.splice(Math.floor(Math.random() * child.rules.length), 1);
|
||||
}
|
||||
|
||||
// Crossover parameters
|
||||
child.atrPeriod = Math.random() < 0.5 ? parent1.atrPeriod : parent2.atrPeriod;
|
||||
child.slMultiplier = Math.random() < 0.5 ? parent1.slMultiplier : parent2.slMultiplier;
|
||||
child.tpMultiplier = Math.random() < 0.5 ? parent1.tpMultiplier : parent2.tpMultiplier;
|
||||
|
||||
return child;
|
||||
}
|
||||
|
||||
/**
|
||||
* Mutate a strategy
|
||||
*/
|
||||
mutate(strategy) {
|
||||
const mutationRate = 0.2;
|
||||
|
||||
if (Math.random() < mutationRate) {
|
||||
const mutationType = Math.random();
|
||||
|
||||
if (mutationType < 0.4) {
|
||||
// Mutate a rule
|
||||
if (strategy.rules.length > 0) {
|
||||
const index = Math.floor(Math.random() * strategy.rules.length);
|
||||
strategy.rules[index] = strategy.generateRule(this.config.shiftRange);
|
||||
}
|
||||
} else if (mutationType < 0.55) {
|
||||
// Add a rule
|
||||
if (strategy.rules.length < this.config.rulesRange[1]) {
|
||||
strategy.rules.push(strategy.generateRule(this.config.shiftRange));
|
||||
}
|
||||
} else if (mutationType < 0.7) {
|
||||
// Remove a rule
|
||||
if (strategy.rules.length > this.config.rulesRange[0]) {
|
||||
strategy.rules.splice(Math.floor(Math.random() * strategy.rules.length), 1);
|
||||
}
|
||||
} else if (mutationType < 0.85) {
|
||||
// Mutate ATR period
|
||||
strategy.atrPeriod = Math.floor(Math.random() * 31) + 10;
|
||||
} else {
|
||||
// Mutate SL/TP
|
||||
strategy.slMultiplier = (Math.random() * 3) + 1;
|
||||
strategy.tpMultiplier = strategy.slMultiplier + (Math.random() * 5) + 0.5;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Evolve one generation
|
||||
*/
|
||||
evolveGeneration() {
|
||||
const newPopulation = [];
|
||||
|
||||
// Elitism: keep top 2 strategies
|
||||
newPopulation.push(this.population[0].copy());
|
||||
newPopulation.push(this.population[1].copy());
|
||||
|
||||
// Generate rest through crossover and mutation
|
||||
while (newPopulation.length < this.config.populationSize) {
|
||||
const parent1 = this.tournamentSelection();
|
||||
const parent2 = this.tournamentSelection();
|
||||
|
||||
const child = this.crossover(parent1, parent2);
|
||||
this.mutate(child);
|
||||
|
||||
newPopulation.push(child);
|
||||
}
|
||||
|
||||
this.population = newPopulation;
|
||||
}
|
||||
|
||||
/**
|
||||
* Run the genetic algorithm
|
||||
*/
|
||||
async run(progressCallback) {
|
||||
console.log('🧬 GA.run() started');
|
||||
console.log('📊 Data rows:', this.data.length);
|
||||
console.log('⚙️ Config:', this.config);
|
||||
this.isRunning = true;
|
||||
this.foundStrategies = [];
|
||||
this.currentGeneration = 0;
|
||||
|
||||
const startTime = Date.now();
|
||||
|
||||
while (this.isRunning && this.foundStrategies.length < this.config.strategiesCount) {
|
||||
console.log('🔄 Starting new GA search, found so far:', this.foundStrategies.length);
|
||||
// Initialize new population for each search
|
||||
this.initializePopulation();
|
||||
console.log('✅ Population initialized:', this.population.length, 'strategies');
|
||||
|
||||
// Evolve for specified generations
|
||||
for (let gen = 0; gen < this.config.generations && this.isRunning; gen++) {
|
||||
this.currentGeneration = gen + 1;
|
||||
|
||||
// Evaluate fitness
|
||||
console.log(`Gen ${gen + 1}/${this.config.generations}: Evaluating population...`);
|
||||
this.evaluatePopulation();
|
||||
console.log(`Gen ${gen + 1}: Best fitness = ${this.population[0].fitness.toFixed(2)}, Trades = ${this.population[0].metrics.totalTrades}`);
|
||||
|
||||
// Report progress
|
||||
if (progressCallback) {
|
||||
const elapsed = Math.floor((Date.now() - startTime) / 1000);
|
||||
progressCallback({
|
||||
generation: gen + 1,
|
||||
totalGenerations: this.config.generations,
|
||||
bestFitness: this.population[0].fitness,
|
||||
avgFitness: this.population.reduce((sum, s) => sum + s.fitness, 0) / this.population.length,
|
||||
foundCount: this.foundStrategies.length,
|
||||
targetCount: this.config.strategiesCount,
|
||||
elapsedTime: elapsed
|
||||
});
|
||||
}
|
||||
|
||||
// Check if best strategy meets criteria
|
||||
const best = this.population[0];
|
||||
console.log(`Checking criteria: PF=${best.metrics.profitFactor.toFixed(2)}, WR=${best.metrics.winRate.toFixed(1)}%, Trades=${best.metrics.totalTrades}, DD=${best.metrics.maxDrawdown.toFixed(2)}%`);
|
||||
if (this.meetsCriteria(best) && !this.isDuplicate(best)) {
|
||||
console.log('✅ Strategy meets criteria!');
|
||||
this.foundStrategies.push(best.copy());
|
||||
|
||||
if (progressCallback) {
|
||||
progressCallback({
|
||||
strategyFound: best,
|
||||
foundCount: this.foundStrategies.length
|
||||
});
|
||||
}
|
||||
|
||||
// If we found enough, break
|
||||
if (this.foundStrategies.length >= this.config.strategiesCount) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Evolve to next generation
|
||||
if (gen < this.config.generations - 1) {
|
||||
this.evolveGeneration();
|
||||
}
|
||||
|
||||
// Allow UI to update
|
||||
await this.sleep(10);
|
||||
}
|
||||
}
|
||||
|
||||
this.isRunning = false;
|
||||
console.log('🏁 GA.run() complete. Returning', this.foundStrategies.length, 'strategies');
|
||||
console.log('Strategies to return:', this.foundStrategies);
|
||||
return this.foundStrategies;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if strategy is duplicate
|
||||
*/
|
||||
isDuplicate(strategy) {
|
||||
for (const existing of this.foundStrategies) {
|
||||
if (this.strategiesSimilar(strategy, existing)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if two strategies are similar
|
||||
*/
|
||||
strategiesSimilar(s1, s2) {
|
||||
// Simple check: if rules are identical
|
||||
if (s1.rules.length !== s2.rules.length) return false;
|
||||
|
||||
const rules1 = JSON.stringify(s1.rules);
|
||||
const rules2 = JSON.stringify(s2.rules);
|
||||
|
||||
return rules1 === rules2;
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop the optimization
|
||||
*/
|
||||
stop() {
|
||||
this.isRunning = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sleep helper for async
|
||||
*/
|
||||
sleep(ms) {
|
||||
return new Promise(resolve => setTimeout(resolve, ms));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,493 @@
|
||||
/**
|
||||
* HTML Report Generator
|
||||
* Generates comprehensive HTML reports with full trading statement
|
||||
*/
|
||||
|
||||
function generateHTMLReport(strategy, index) {
|
||||
const m = strategy.metrics;
|
||||
const name = `FxMath_${String(index + 1).padStart(3, '0')}_PF${(m.profitFactor || 0).toFixed(2).replace('.', '_')}_WR${Math.round(m.winRate || 0)}`;
|
||||
|
||||
// Generate SELL rules (inverted operators)
|
||||
const sellRulesHTML = strategy.rules.map((rule, i) => {
|
||||
const invertedOp = rule.operator === '>' ? '<=' :
|
||||
rule.operator === '<' ? '>=' :
|
||||
rule.operator === '>=' ? '<' :
|
||||
rule.operator === '<=' ? '>' : rule.operator;
|
||||
|
||||
let ruleText = '';
|
||||
if (rule.type === 'simple') {
|
||||
ruleText = `${rule.left.price.toUpperCase()}[${rule.left.shift}] ${invertedOp} ${rule.right.price.toUpperCase()}[${rule.right.shift}]`;
|
||||
} else {
|
||||
ruleText = `(${rule.left.price1.toUpperCase()}[${rule.left.shift1}] ${rule.left.op} ${rule.left.price2.toUpperCase()}[${rule.left.shift2}]) ${invertedOp} (${rule.right.price.toUpperCase()}[${rule.right.shift}] × ${rule.right.multiplier})`;
|
||||
}
|
||||
return `<div class="rule-item">${i + 1}. ${ruleText}</div>`;
|
||||
}).join('');
|
||||
|
||||
// Generate BUY rules
|
||||
const buyRulesHTML = strategy.rules.map((rule, i) => {
|
||||
let ruleText = '';
|
||||
if (rule.type === 'simple') {
|
||||
ruleText = `${rule.left.price.toUpperCase()}[${rule.left.shift}] ${rule.operator} ${rule.right.price.toUpperCase()}[${rule.right.shift}]`;
|
||||
} else {
|
||||
ruleText = `(${rule.left.price1.toUpperCase()}[${rule.left.shift1}] ${rule.left.op} ${rule.left.price2.toUpperCase()}[${rule.left.shift2}]) ${rule.operator} (${rule.right.price.toUpperCase()}[${rule.right.shift}] × ${rule.right.multiplier})`;
|
||||
}
|
||||
return `<div class="rule-item">${i + 1}. ${ruleText}</div>`;
|
||||
}).join('');
|
||||
|
||||
// Generate trades table
|
||||
const tradesHTML = m.trades.map((trade, i) => {
|
||||
const profitClass = trade.profit > 0 ? 'profit-positive' : 'profit-negative';
|
||||
const typeClass = trade.type === 'BUY' ? 'type-buy' : 'type-sell';
|
||||
return `
|
||||
<tr>
|
||||
<td>${i + 1}</td>
|
||||
<td><span class="badge ${typeClass}">${trade.type}</span></td>
|
||||
<td>${new Date(trade.openTime).toLocaleString()}</td>
|
||||
<td>${new Date(trade.closeTime).toLocaleString()}</td>
|
||||
<td>$${trade.entry.toFixed(2)}</td>
|
||||
<td>$${trade.exit.toFixed(2)}</td>
|
||||
<td class="${profitClass}">$${trade.profit.toFixed(2)}</td>
|
||||
<td>${trade.reason.toUpperCase()}</td>
|
||||
</tr>
|
||||
`;
|
||||
}).join('');
|
||||
|
||||
// Generate hourly stats table
|
||||
let hourlyStatsHTML = '';
|
||||
if (m.hourlyStats) {
|
||||
hourlyStatsHTML = m.hourlyStats.map((stats, hour) => {
|
||||
if (stats.trades === 0) return '';
|
||||
const profitClass = stats.profit > 0 ? 'profit-positive' : 'profit-negative';
|
||||
return `
|
||||
<tr>
|
||||
<td>${String(hour).padStart(2, '0')}:00</td>
|
||||
<td>${stats.trades}</td>
|
||||
<td>${stats.wins}</td>
|
||||
<td>${stats.losses}</td>
|
||||
<td class="${profitClass}">$${stats.profit.toFixed(2)}</td>
|
||||
<td>${stats.trades > 0 ? ((stats.wins / stats.trades) * 100).toFixed(1) : 0}%</td>
|
||||
</tr>
|
||||
`;
|
||||
}).join('');
|
||||
}
|
||||
|
||||
const html = `<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>${name} - Trading Strategy Report</title>
|
||||
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.0/dist/chart.umd.min.js"></script>
|
||||
<style>
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
body {
|
||||
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
padding: 20px;
|
||||
color: #333;
|
||||
}
|
||||
.container {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
background: white;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 10px 40px rgba(0,0,0,0.2);
|
||||
overflow: hidden;
|
||||
}
|
||||
.header {
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
color: white;
|
||||
padding: 30px;
|
||||
text-align: center;
|
||||
}
|
||||
.header h1 { font-size: 32px; margin-bottom: 10px; }
|
||||
.header p { font-size: 16px; opacity: 0.9; }
|
||||
.content { padding: 30px; }
|
||||
.section { margin-bottom: 40px; }
|
||||
.section h2 {
|
||||
font-size: 24px;
|
||||
color: #667eea;
|
||||
margin-bottom: 20px;
|
||||
padding-bottom: 10px;
|
||||
border-bottom: 2px solid #667eea;
|
||||
}
|
||||
.metrics-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||||
gap: 20px;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
.metric-card {
|
||||
background: #f7fafc;
|
||||
padding: 20px;
|
||||
border-radius: 8px;
|
||||
border-left: 4px solid #667eea;
|
||||
}
|
||||
.metric-label {
|
||||
font-size: 12px;
|
||||
color: #718096;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
.metric-value {
|
||||
font-size: 24px;
|
||||
font-weight: bold;
|
||||
color: #2d3748;
|
||||
}
|
||||
.metric-value.positive { color: #48bb78; }
|
||||
.metric-value.negative { color: #f56565; }
|
||||
.rules-box {
|
||||
background: #f7fafc;
|
||||
padding: 20px;
|
||||
border-radius: 8px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.rules-box h3 {
|
||||
font-size: 18px;
|
||||
color: #4a5568;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
.rule-item {
|
||||
padding: 8px 0;
|
||||
font-family: 'Courier New', monospace;
|
||||
color: #2d3748;
|
||||
}
|
||||
table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin-top: 20px;
|
||||
}
|
||||
th, td {
|
||||
padding: 12px;
|
||||
text-align: left;
|
||||
border-bottom: 1px solid #e2e8f0;
|
||||
}
|
||||
th {
|
||||
background: #f7fafc;
|
||||
font-weight: 600;
|
||||
color: #4a5568;
|
||||
text-transform: uppercase;
|
||||
font-size: 12px;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
tr:hover { background: #f7fafc; }
|
||||
.badge {
|
||||
padding: 4px 12px;
|
||||
border-radius: 12px;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
}
|
||||
.type-buy {
|
||||
background: #c6f6d5;
|
||||
color: #22543d;
|
||||
}
|
||||
.type-sell {
|
||||
background: #fed7d7;
|
||||
color: #742a2a;
|
||||
}
|
||||
.profit-positive { color: #48bb78; font-weight: 600; }
|
||||
.profit-negative { color: #f56565; font-weight: 600; }
|
||||
.best-worst {
|
||||
background: linear-gradient(135deg, rgba(102, 126, 234, 0.1) 0%, rgba(118, 75, 162, 0.1) 100%);
|
||||
padding: 20px;
|
||||
border-radius: 8px;
|
||||
margin: 20px 0;
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
text-align: center;
|
||||
}
|
||||
.best-worst div {
|
||||
flex: 1;
|
||||
}
|
||||
.best-worst h4 {
|
||||
font-size: 14px;
|
||||
color: #718096;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.best-worst .value {
|
||||
font-size: 20px;
|
||||
font-weight: bold;
|
||||
}
|
||||
.chart-container {
|
||||
background: #f7fafc;
|
||||
padding: 20px;
|
||||
border-radius: 8px;
|
||||
margin: 20px 0;
|
||||
height: 300px;
|
||||
}
|
||||
.footer {
|
||||
background: #f7fafc;
|
||||
padding: 20px;
|
||||
text-align: center;
|
||||
color: #718096;
|
||||
font-size: 14px;
|
||||
}
|
||||
@media print {
|
||||
body { background: white; padding: 0; }
|
||||
.container { box-shadow: none; }
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="header">
|
||||
<h1>${name}</h1>
|
||||
<p>FxMath Quant - Automated Trading Strategy Report</p>
|
||||
<p>Generated: ${new Date().toLocaleString()}</p>
|
||||
</div>
|
||||
|
||||
<div class="content">
|
||||
<!-- Performance Metrics -->
|
||||
<div class="section">
|
||||
<h2>Performance Metrics</h2>
|
||||
<div class="metrics-grid">
|
||||
<div class="metric-card">
|
||||
<div class="metric-label">Profit Factor</div>
|
||||
<div class="metric-value positive">${(m.profitFactor || 0).toFixed(2)}</div>
|
||||
</div>
|
||||
<div class="metric-card">
|
||||
<div class="metric-label">Win Rate</div>
|
||||
<div class="metric-value">${(m.winRate || 0).toFixed(1)}%</div>
|
||||
</div>
|
||||
<div class="metric-card">
|
||||
<div class="metric-label">Total Trades</div>
|
||||
<div class="metric-value">${m.totalTrades || 0}</div>
|
||||
</div>
|
||||
<div class="metric-card">
|
||||
<div class="metric-label">BUY Trades</div>
|
||||
<div class="metric-value" style="color: #48bb78;">${m.buyTrades || 0}</div>
|
||||
</div>
|
||||
<div class="metric-card">
|
||||
<div class="metric-label">SELL Trades</div>
|
||||
<div class="metric-value" style="color: #f56565;">${m.sellTrades || 0}</div>
|
||||
</div>
|
||||
<div class="metric-card">
|
||||
<div class="metric-label">Max Drawdown</div>
|
||||
<div class="metric-value negative">${(m.maxDrawdown || 0).toFixed(2)}%</div>
|
||||
</div>
|
||||
<div class="metric-card">
|
||||
<div class="metric-label">Total Profit</div>
|
||||
<div class="metric-value ${m.totalProfit >= 0 ? 'positive' : 'negative'}">$${(m.totalProfit || 0).toFixed(2)}</div>
|
||||
</div>
|
||||
<div class="metric-card">
|
||||
<div class="metric-label">Avg Win</div>
|
||||
<div class="metric-value positive">$${(m.avgWin || 0).toFixed(2)}</div>
|
||||
</div>
|
||||
<div class="metric-card">
|
||||
<div class="metric-label">Avg Loss</div>
|
||||
<div class="metric-value negative">$${Math.abs(m.avgLoss || 0).toFixed(2)}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Equity Curve Chart -->
|
||||
<div class="section">
|
||||
<h2>Equity Curve</h2>
|
||||
<div class="chart-container">
|
||||
<canvas id="equityChart"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
${m.hourlyStats ? `
|
||||
<!-- Hourly Performance Chart -->
|
||||
<div class="section">
|
||||
<h2>Hourly Performance (24h)</h2>
|
||||
<div class="chart-container">
|
||||
<canvas id="hourlyChart"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
` : ''}
|
||||
|
||||
${m.bestHour && m.worstHour ? `
|
||||
<!-- Best/Worst Hours -->
|
||||
<div class="section">
|
||||
<h2>Time-Based Performance</h2>
|
||||
<div class="best-worst">
|
||||
<div>
|
||||
<h4>Best Trading Hour</h4>
|
||||
<div class="value positive">${String(m.bestHour.hour).padStart(2, '0')}:00</div>
|
||||
<div class="profit-positive">$${m.bestHour.profit.toFixed(2)}</div>
|
||||
</div>
|
||||
<div>
|
||||
<h4>Worst Trading Hour</h4>
|
||||
<div class="value negative">${String(m.worstHour.hour).padStart(2, '0')}:00</div>
|
||||
<div class="profit-negative">$${m.worstHour.profit.toFixed(2)}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
` : ''}
|
||||
|
||||
<!-- Strategy Rules -->
|
||||
<div class="section">
|
||||
<h2>Strategy Rules</h2>
|
||||
<div class="rules-box">
|
||||
<h3>BUY when ALL of the following conditions are true:</h3>
|
||||
${buyRulesHTML}
|
||||
</div>
|
||||
<div class="rules-box">
|
||||
<h3>SELL when ALL of the following conditions are true:</h3>
|
||||
${sellRulesHTML}
|
||||
</div>
|
||||
<div class="rules-box">
|
||||
<h3>Parameters</h3>
|
||||
<div class="rule-item">ATR Period: ${strategy.atrPeriod}</div>
|
||||
<div class="rule-item">SL Multiplier: ${strategy.slMultiplier.toFixed(2)}</div>
|
||||
<div class="rule-item">TP Multiplier: ${strategy.tpMultiplier.toFixed(2)}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
${hourlyStatsHTML ? `
|
||||
<!-- Hourly Performance -->
|
||||
<div class="section">
|
||||
<h2>Hourly Performance Analysis</h2>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Hour</th>
|
||||
<th>Trades</th>
|
||||
<th>Wins</th>
|
||||
<th>Losses</th>
|
||||
<th>Profit/Loss</th>
|
||||
<th>Win Rate</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
${hourlyStatsHTML}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
` : ''}
|
||||
|
||||
<!-- Full Trading Statement -->
|
||||
<div class="section">
|
||||
<h2>Complete Trading Statement</h2>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>#</th>
|
||||
<th>Type</th>
|
||||
<th>Open Time</th>
|
||||
<th>Close Time</th>
|
||||
<th>Entry</th>
|
||||
<th>Exit</th>
|
||||
<th>Profit/Loss</th>
|
||||
<th>Exit Reason</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
${tradesHTML}
|
||||
</tbody>
|
||||
</table>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="footer">
|
||||
<p>FxMath Quant - Automated Trading Strategy Generator</p>
|
||||
<p>This report is for informational purposes only. Past performance does not guarantee future results.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// Render Equity Curve Chart
|
||||
const equityCtx = document.getElementById('equityChart');
|
||||
if (equityCtx) {
|
||||
new Chart(equityCtx, {
|
||||
type: 'line',
|
||||
data: {
|
||||
labels: ${JSON.stringify(m.equity.map((_, i) => i))},
|
||||
datasets: [{
|
||||
label: 'Account Balance',
|
||||
data: ${JSON.stringify(m.equity)},
|
||||
borderColor: '#667eea',
|
||||
backgroundColor: 'rgba(102, 126, 234, 0.1)',
|
||||
tension: 0.4,
|
||||
fill: true,
|
||||
pointRadius: 0
|
||||
}]
|
||||
},
|
||||
options: {
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
plugins: {
|
||||
legend: { display: false },
|
||||
tooltip: {
|
||||
backgroundColor: '#1a1f3a',
|
||||
titleColor: '#fff',
|
||||
bodyColor: '#a0aec0'
|
||||
}
|
||||
},
|
||||
scales: {
|
||||
y: {
|
||||
beginAtZero: false,
|
||||
grid: { color: 'rgba(0, 0, 0, 0.1)' },
|
||||
ticks: { color: '#4a5568' }
|
||||
},
|
||||
x: {
|
||||
grid: { display: false },
|
||||
ticks: { color: '#4a5568' }
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Render Hourly Performance Chart
|
||||
const hourlyCtx = document.getElementById('hourlyChart');
|
||||
if (hourlyCtx) {
|
||||
const hourlyData = ${JSON.stringify(m.hourlyStats || [])};
|
||||
new Chart(hourlyCtx, {
|
||||
type: 'bar',
|
||||
data: {
|
||||
labels: Array.from({ length: 24 }, (_, i) => String(i).padStart(2, '0') + ':00'),
|
||||
datasets: [{
|
||||
label: 'Profit/Loss',
|
||||
data: hourlyData.map(h => h.profit),
|
||||
backgroundColor: hourlyData.map(h => h.profit >= 0 ? 'rgba(72, 187, 120, 0.6)' : 'rgba(245, 101, 101, 0.6)'),
|
||||
borderColor: hourlyData.map(h => h.profit >= 0 ? 'rgba(72, 187, 120, 1)' : 'rgba(245, 101, 101, 1)'),
|
||||
borderWidth: 1
|
||||
}]
|
||||
},
|
||||
options: {
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
plugins: {
|
||||
legend: { display: false },
|
||||
tooltip: {
|
||||
callbacks: {
|
||||
label: function(context) {
|
||||
const hour = context.dataIndex;
|
||||
const stats = hourlyData[hour];
|
||||
return [
|
||||
'Profit: $' + stats.profit.toFixed(2),
|
||||
'Trades: ' + stats.trades,
|
||||
'Wins: ' + stats.wins + ' | Losses: ' + stats.losses
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
scales: {
|
||||
y: {
|
||||
beginAtZero: true,
|
||||
grid: { color: 'rgba(0, 0, 0, 0.1)' },
|
||||
ticks: { color: '#4a5568' }
|
||||
},
|
||||
x: {
|
||||
grid: { display: false },
|
||||
ticks: { color: '#4a5568', maxRotation: 45, minRotation: 45 }
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>`;
|
||||
|
||||
return html;
|
||||
}
|
||||
|
||||
// Export function
|
||||
window.generateHTMLReport = generateHTMLReport;
|
||||
@@ -0,0 +1,162 @@
|
||||
/**
|
||||
* License Check Module
|
||||
* Verifies license on app load and redirects if invalid
|
||||
*/
|
||||
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
// Check license immediately when script loads
|
||||
checkLicense();
|
||||
|
||||
/**
|
||||
* Main license check function
|
||||
*/
|
||||
function checkLicense() {
|
||||
const licenseData = localStorage.getItem('fxmath_license');
|
||||
|
||||
// No license found
|
||||
if (!licenseData) {
|
||||
redirectToLogin();
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const license = JSON.parse(licenseData);
|
||||
|
||||
// Validate license structure
|
||||
if (!license.key || !license.type || !license.status) {
|
||||
console.warn('Invalid license data structure');
|
||||
redirectToLogin();
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if license is active
|
||||
if (license.status !== 'active') {
|
||||
console.warn('License is not active');
|
||||
redirectToLogin();
|
||||
return;
|
||||
}
|
||||
|
||||
// Check expiry for limited licenses
|
||||
if (license.type === 'limited') {
|
||||
if (!license.expiry_date) {
|
||||
console.warn('Limited license missing expiry date');
|
||||
redirectToLogin();
|
||||
return;
|
||||
}
|
||||
|
||||
const expiryDate = new Date(license.expiry_date);
|
||||
const now = new Date();
|
||||
|
||||
if (now > expiryDate) {
|
||||
console.warn('License has expired');
|
||||
localStorage.removeItem('fxmath_license');
|
||||
alert('Your license has expired. Please renew your license to continue using FxMathQuant.');
|
||||
redirectToLogin();
|
||||
return;
|
||||
}
|
||||
|
||||
// Calculate days remaining
|
||||
const daysRemaining = Math.ceil((expiryDate - now) / (1000 * 60 * 60 * 24));
|
||||
|
||||
// Warn if license is expiring soon (7 days or less)
|
||||
if (daysRemaining <= 7 && daysRemaining > 0) {
|
||||
console.warn(`License expiring in ${daysRemaining} days`);
|
||||
showExpiryWarning(daysRemaining);
|
||||
}
|
||||
}
|
||||
|
||||
// License is valid
|
||||
console.log('License validated successfully');
|
||||
displayLicenseInfo(license);
|
||||
|
||||
} catch (error) {
|
||||
console.error('License check error:', error);
|
||||
redirectToLogin();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Redirect to login page
|
||||
*/
|
||||
function redirectToLogin() {
|
||||
// Only redirect if not already on login page
|
||||
if (!window.location.pathname.includes('login.html')) {
|
||||
window.location.href = 'login.html';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Show expiry warning
|
||||
*/
|
||||
function showExpiryWarning(daysRemaining) {
|
||||
// Create warning banner if it doesn't exist
|
||||
let banner = document.getElementById('license-expiry-warning');
|
||||
|
||||
if (!banner) {
|
||||
banner = document.createElement('div');
|
||||
banner.id = 'license-expiry-warning';
|
||||
banner.style.cssText = `
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
|
||||
color: white;
|
||||
padding: 12px 20px;
|
||||
text-align: center;
|
||||
font-weight: 600;
|
||||
z-index: 10000;
|
||||
box-shadow: 0 2px 10px rgba(0,0,0,0.2);
|
||||
`;
|
||||
|
||||
banner.innerHTML = `
|
||||
⚠️ Your license will expire in ${daysRemaining} day${daysRemaining !== 1 ? 's' : ''}.
|
||||
Please renew to continue using FxMathQuant.
|
||||
<button onclick="this.parentElement.remove()" style="
|
||||
background: rgba(255,255,255,0.3);
|
||||
border: none;
|
||||
color: white;
|
||||
padding: 4px 12px;
|
||||
margin-left: 15px;
|
||||
border-radius: 5px;
|
||||
cursor: pointer;
|
||||
font-weight: 600;
|
||||
">Dismiss</button>
|
||||
`;
|
||||
|
||||
document.body.insertBefore(banner, document.body.firstChild);
|
||||
|
||||
// Adjust body padding to account for banner
|
||||
document.body.style.paddingTop = '50px';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Display license info in console (for debugging)
|
||||
*/
|
||||
function displayLicenseInfo(license) {
|
||||
console.log('%c License Information ', 'background: #667eea; color: white; font-weight: bold; padding: 5px 10px;');
|
||||
console.log('Type:', license.type);
|
||||
console.log('Status:', license.status);
|
||||
|
||||
if (license.type === 'limited') {
|
||||
console.log('Expiry Date:', license.expiry_date);
|
||||
console.log('Days Remaining:', license.days_remaining);
|
||||
} else {
|
||||
console.log('Expiry:', 'Never (Lifetime)');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Expose logout function globally
|
||||
*/
|
||||
window.logoutLicense = function () {
|
||||
if (confirm('Are you sure you want to logout? You will need to enter your license key again.')) {
|
||||
localStorage.removeItem('fxmath_license');
|
||||
window.location.href = 'login.html';
|
||||
}
|
||||
};
|
||||
|
||||
})();
|
||||
+175
@@ -0,0 +1,175 @@
|
||||
/**
|
||||
* License Login Handler
|
||||
* FxMathQuant-Web
|
||||
*/
|
||||
|
||||
// API endpoint - Production server
|
||||
const API_URL = 'https://fxmath.com/quantw/api/validate.php';
|
||||
|
||||
// DOM elements
|
||||
const licenseForm = document.getElementById('licenseForm');
|
||||
const licenseKeyInput = document.getElementById('licenseKey');
|
||||
const activateBtn = document.getElementById('activateBtn');
|
||||
const alertBox = document.getElementById('alertBox');
|
||||
|
||||
// Initialize
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
// Check if already licensed
|
||||
if (isLicenseValid()) {
|
||||
window.location.href = 'index.html';
|
||||
}
|
||||
|
||||
// Format license key input
|
||||
licenseKeyInput.addEventListener('input', formatLicenseKey);
|
||||
|
||||
// Handle form submission
|
||||
licenseForm.addEventListener('submit', handleLicenseSubmit);
|
||||
});
|
||||
|
||||
/**
|
||||
* Format license key as user types (add dashes)
|
||||
*/
|
||||
function formatLicenseKey(e) {
|
||||
let value = e.target.value.replace(/[^A-Z0-9]/gi, '').toUpperCase();
|
||||
let formatted = '';
|
||||
|
||||
for (let i = 0; i < value.length && i < 32; i++) {
|
||||
if (i > 0 && i % 4 === 0) {
|
||||
formatted += '-';
|
||||
}
|
||||
formatted += value[i];
|
||||
}
|
||||
|
||||
e.target.value = formatted;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle license form submission
|
||||
*/
|
||||
async function handleLicenseSubmit(e) {
|
||||
e.preventDefault();
|
||||
|
||||
const licenseKey = licenseKeyInput.value.trim();
|
||||
|
||||
// Validate format
|
||||
if (!isValidLicenseFormat(licenseKey)) {
|
||||
showAlert('Please enter a valid license key format', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
// Disable button and show loading
|
||||
activateBtn.disabled = true;
|
||||
activateBtn.innerHTML = '<span class="spinner"></span>Validating...';
|
||||
|
||||
try {
|
||||
// Call validation API
|
||||
const response = await fetch(API_URL, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({ license_key: licenseKey })
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (data.success) {
|
||||
// Store license info in localStorage
|
||||
const licenseInfo = {
|
||||
key: licenseKey,
|
||||
type: data.license.type,
|
||||
status: data.license.status,
|
||||
validated_at: new Date().toISOString()
|
||||
};
|
||||
|
||||
if (data.license.type === 'limited') {
|
||||
licenseInfo.expiry_date = data.license.expiry_date;
|
||||
licenseInfo.days_remaining = data.license.days_remaining;
|
||||
}
|
||||
|
||||
localStorage.setItem('fxmath_license', JSON.stringify(licenseInfo));
|
||||
|
||||
// Show success message
|
||||
showAlert('License activated successfully! Redirecting...', 'success');
|
||||
|
||||
// Redirect to main app
|
||||
setTimeout(() => {
|
||||
window.location.href = 'index.html';
|
||||
}, 1500);
|
||||
|
||||
} else {
|
||||
// Show error message
|
||||
showAlert(data.message || 'License validation failed', 'error');
|
||||
activateBtn.disabled = false;
|
||||
activateBtn.innerHTML = 'Activate License';
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('Validation error:', error);
|
||||
showAlert('Connection error. Please check your server configuration.', 'error');
|
||||
activateBtn.disabled = false;
|
||||
activateBtn.innerHTML = 'Activate License';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate license key format
|
||||
*/
|
||||
function isValidLicenseFormat(key) {
|
||||
// Should be 32 alphanumeric characters with dashes every 4 characters
|
||||
const pattern = /^[A-Z0-9]{4}-[A-Z0-9]{4}-[A-Z0-9]{4}-[A-Z0-9]{4}-[A-Z0-9]{4}-[A-Z0-9]{4}-[A-Z0-9]{4}-[A-Z0-9]{4}$/;
|
||||
return pattern.test(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if license is valid
|
||||
*/
|
||||
function isLicenseValid() {
|
||||
const licenseData = localStorage.getItem('fxmath_license');
|
||||
|
||||
if (!licenseData) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
const license = JSON.parse(licenseData);
|
||||
|
||||
// Check if license exists
|
||||
if (!license.key || license.status !== 'active') {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check expiry for limited licenses
|
||||
if (license.type === 'limited') {
|
||||
const expiryDate = new Date(license.expiry_date);
|
||||
const now = new Date();
|
||||
|
||||
if (now > expiryDate) {
|
||||
// License expired
|
||||
localStorage.removeItem('fxmath_license');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
} catch (error) {
|
||||
console.error('License validation error:', error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Show alert message
|
||||
*/
|
||||
function showAlert(message, type) {
|
||||
alertBox.textContent = message;
|
||||
alertBox.className = `alert alert-${type} show`;
|
||||
|
||||
// Auto-hide after 5 seconds for non-success messages
|
||||
if (type !== 'success') {
|
||||
setTimeout(() => {
|
||||
alertBox.classList.remove('show');
|
||||
}, 5000);
|
||||
}
|
||||
}
|
||||
+725
@@ -0,0 +1,725 @@
|
||||
/**
|
||||
* Main Application Controller
|
||||
*/
|
||||
|
||||
// Global state
|
||||
let appData = {
|
||||
csvData: null,
|
||||
dataName: '',
|
||||
optimizer: null,
|
||||
foundStrategies: [],
|
||||
selectedStrategies: new Set(),
|
||||
isGenerating: false
|
||||
};
|
||||
|
||||
// Initialize app when DOM is loaded
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
initializeApp();
|
||||
});
|
||||
|
||||
function initializeApp() {
|
||||
setupEventListeners();
|
||||
showSection('upload-section');
|
||||
}
|
||||
|
||||
/**
|
||||
* Setup all event listeners
|
||||
*/
|
||||
function setupEventListeners() {
|
||||
// Upload zone
|
||||
const uploadZone = document.getElementById('upload-zone');
|
||||
const fileInput = document.getElementById('file-input');
|
||||
const browseBtn = document.getElementById('browse-btn');
|
||||
|
||||
uploadZone.addEventListener('click', () => fileInput.click());
|
||||
browseBtn.addEventListener('click', (e) => {
|
||||
e.stopPropagation();
|
||||
fileInput.click();
|
||||
});
|
||||
|
||||
fileInput.addEventListener('change', (e) => {
|
||||
if (e.target.files.length > 0) {
|
||||
handleFileUpload(e.target.files[0]);
|
||||
}
|
||||
});
|
||||
|
||||
// Drag and drop
|
||||
uploadZone.addEventListener('dragover', (e) => {
|
||||
e.preventDefault();
|
||||
uploadZone.classList.add('dragover');
|
||||
});
|
||||
|
||||
uploadZone.addEventListener('dragleave', () => {
|
||||
uploadZone.classList.remove('dragover');
|
||||
});
|
||||
|
||||
uploadZone.addEventListener('drop', (e) => {
|
||||
e.preventDefault();
|
||||
uploadZone.classList.remove('dragover');
|
||||
|
||||
if (e.dataTransfer.files.length > 0) {
|
||||
handleFileUpload(e.dataTransfer.files[0]);
|
||||
}
|
||||
});
|
||||
|
||||
// Navigation buttons
|
||||
document.getElementById('continue-btn').addEventListener('click', () => {
|
||||
showSection('config-section');
|
||||
});
|
||||
|
||||
document.getElementById('back-btn').addEventListener('click', () => {
|
||||
showSection('upload-section');
|
||||
});
|
||||
|
||||
document.getElementById('start-btn').addEventListener('click', startGeneration);
|
||||
document.getElementById('stop-btn').addEventListener('click', stopGeneration);
|
||||
document.getElementById('new-search-btn').addEventListener('click', () => {
|
||||
showSection('upload-section');
|
||||
appData.foundStrategies = [];
|
||||
});
|
||||
|
||||
document.getElementById('download-all-btn').addEventListener('click', downloadAllStrategies);
|
||||
|
||||
// Comparison button
|
||||
const compareBtn = document.getElementById('compare-btn');
|
||||
if (compareBtn) {
|
||||
compareBtn.addEventListener('click', () => {
|
||||
if (appData.selectedStrategies.size < 2) {
|
||||
alert('Please select at least 2 strategies to compare.');
|
||||
return;
|
||||
}
|
||||
showComparison();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle CSV file upload
|
||||
*/
|
||||
function handleFileUpload(file) {
|
||||
console.log('📁 File upload started:', file.name, 'Size:', file.size);
|
||||
if (!file.name.endsWith('.csv')) {
|
||||
alert('Please upload a CSV file');
|
||||
return;
|
||||
}
|
||||
|
||||
Papa.parse(file, {
|
||||
header: true,
|
||||
dynamicTyping: true,
|
||||
skipEmptyLines: true,
|
||||
complete: (results) => {
|
||||
console.log('📊 CSV parsed:', results.data.length, 'rows');
|
||||
if (results.data.length === 0) {
|
||||
alert('CSV file is empty');
|
||||
return;
|
||||
}
|
||||
|
||||
// Validate CSV structure (accept both lowercase and capitalized column names)
|
||||
const firstRow = results.data[0];
|
||||
const requiredFields = ['open', 'high', 'low', 'close'];
|
||||
const hasRequiredFields = requiredFields.every(field => {
|
||||
// Check lowercase, capitalized, and uppercase versions
|
||||
return field in firstRow ||
|
||||
field.charAt(0).toUpperCase() + field.slice(1) in firstRow ||
|
||||
field.toUpperCase() in firstRow;
|
||||
});
|
||||
|
||||
if (!hasRequiredFields) {
|
||||
alert('CSV must contain: time, open, high, low, close columns');
|
||||
return;
|
||||
}
|
||||
|
||||
// Normalize column names to lowercase
|
||||
appData.csvData = results.data.map(row => ({
|
||||
time: row.time || row.Time || row.TIME,
|
||||
open: parseFloat(row.open || row.Open || row.OPEN),
|
||||
high: parseFloat(row.high || row.High || row.HIGH),
|
||||
low: parseFloat(row.low || row.Low || row.LOW),
|
||||
close: parseFloat(row.close || row.Close || row.CLOSE)
|
||||
}));
|
||||
|
||||
appData.dataName = file.name;
|
||||
console.log('✅ Data loaded successfully:', appData.csvData.length, 'bars');
|
||||
|
||||
// Apply data size limit
|
||||
applyDataSizeLimit();
|
||||
|
||||
displayDataPreview();
|
||||
},
|
||||
error: (error) => {
|
||||
alert('Error parsing CSV: ' + error.message);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Display data preview
|
||||
*/
|
||||
function displayDataPreview() {
|
||||
const preview = document.getElementById('data-preview');
|
||||
preview.classList.remove('hidden');
|
||||
|
||||
// Update data info
|
||||
document.getElementById('data-symbol').textContent = `File: ${appData.dataName}`;
|
||||
document.getElementById('data-bars').textContent = `Bars: ${appData.csvData.length}`;
|
||||
|
||||
// Detect timeframe (simple heuristic)
|
||||
const timeframe = detectTimeframe();
|
||||
document.getElementById('data-timeframe').textContent = `Timeframe: ${timeframe}`;
|
||||
|
||||
// Show first 10 rows
|
||||
const tbody = document.getElementById('preview-body');
|
||||
tbody.innerHTML = '';
|
||||
|
||||
const previewRows = appData.csvData.slice(0, 10);
|
||||
previewRows.forEach(row => {
|
||||
const tr = document.createElement('tr');
|
||||
tr.innerHTML = `
|
||||
<td>${row.time || '-'}</td>
|
||||
<td>${row.open.toFixed(5)}</td>
|
||||
<td>${row.high.toFixed(5)}</td>
|
||||
<td>${row.low.toFixed(5)}</td>
|
||||
<td>${row.close.toFixed(5)}</td>
|
||||
`;
|
||||
tbody.appendChild(tr);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply data size limit
|
||||
*/
|
||||
function applyDataSizeLimit() {
|
||||
const limit = parseInt(document.getElementById('data-size-limit').value);
|
||||
if (limit > 0 && appData.csvData.length > limit) {
|
||||
console.log(`✂️ Limiting data from ${appData.csvData.length} to ${limit} bars`);
|
||||
appData.csvData = appData.csvData.slice(-limit); // Take last N bars
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Detect timeframe from data
|
||||
*/
|
||||
function detectTimeframe() {
|
||||
if (appData.csvData.length < 2) return 'Unknown';
|
||||
|
||||
// Try to parse time difference
|
||||
const time1 = new Date(appData.csvData[0].time);
|
||||
const time2 = new Date(appData.csvData[1].time);
|
||||
|
||||
if (isNaN(time1) || isNaN(time2)) return 'Unknown';
|
||||
|
||||
const diffMinutes = Math.abs(time2 - time1) / (1000 * 60);
|
||||
|
||||
if (diffMinutes <= 1) return 'M1';
|
||||
if (diffMinutes <= 5) return 'M5';
|
||||
if (diffMinutes <= 15) return 'M15';
|
||||
if (diffMinutes <= 30) return 'M30';
|
||||
if (diffMinutes <= 60) return 'H1';
|
||||
if (diffMinutes <= 240) return 'H4';
|
||||
if (diffMinutes <= 1440) return 'D1';
|
||||
|
||||
return 'Unknown';
|
||||
}
|
||||
|
||||
/**
|
||||
* Start strategy generation
|
||||
*/
|
||||
async function startGeneration() {
|
||||
console.log('🚀 Starting strategy generation...');
|
||||
if (!appData.csvData) {
|
||||
console.error('❌ No data loaded!');
|
||||
alert('Please upload data first');
|
||||
return;
|
||||
}
|
||||
|
||||
// Get configuration
|
||||
console.log('⚙️ Reading configuration...');
|
||||
const config = {
|
||||
populationSize: parseInt(document.getElementById('population').value),
|
||||
generations: parseInt(document.getElementById('generations').value),
|
||||
strategiesCount: parseInt(document.getElementById('strategies-count').value),
|
||||
rulesRange: [
|
||||
parseInt(document.getElementById('rules-min').value),
|
||||
parseInt(document.getElementById('rules-max').value)
|
||||
],
|
||||
shiftRange: [
|
||||
parseInt(document.getElementById('shift-min').value),
|
||||
parseInt(document.getElementById('shift-max').value)
|
||||
],
|
||||
minPF: parseFloat(document.getElementById('min-pf').value),
|
||||
minWR: parseFloat(document.getElementById('min-wr').value),
|
||||
maxDD: parseFloat(document.getElementById('max-dd').value),
|
||||
minTrades: parseInt(document.getElementById('min-trades').value)
|
||||
};
|
||||
|
||||
// Show progress section
|
||||
showSection('progress-section');
|
||||
appData.isGenerating = true;
|
||||
appData.foundStrategies = [];
|
||||
|
||||
// Reset buttons visibility
|
||||
const stopBtn = document.getElementById('stop-btn');
|
||||
if (stopBtn) stopBtn.classList.remove('hidden');
|
||||
|
||||
const viewResultsBtn = document.getElementById('view-results-btn');
|
||||
if (viewResultsBtn) viewResultsBtn.classList.add('hidden');
|
||||
|
||||
// Clear progress log
|
||||
document.getElementById('progress-log').innerHTML = '';
|
||||
|
||||
// Create optimizer
|
||||
console.log('🧬 Creating GA optimizer with config:', config);
|
||||
appData.optimizer = new GeneticOptimizer(appData.csvData, config);
|
||||
|
||||
// Start time
|
||||
const startTime = Date.now();
|
||||
|
||||
// Run optimization
|
||||
console.log('▶️ Starting GA run...');
|
||||
const strategies = await appData.optimizer.run((progress) => {
|
||||
updateProgress(progress, startTime);
|
||||
});
|
||||
|
||||
// Handle completion
|
||||
console.log('✅ GA complete! Found', strategies.length, 'strategies');
|
||||
appData.foundStrategies = strategies;
|
||||
appData.isGenerating = false;
|
||||
|
||||
// Stay on page as requested, show redirection button
|
||||
showGenerationCompleteUI();
|
||||
}
|
||||
|
||||
/**
|
||||
* Update progress display
|
||||
*/
|
||||
function updateProgress(progress, startTime) {
|
||||
// Update stats
|
||||
if (progress.generation !== undefined) {
|
||||
document.getElementById('current-gen').textContent =
|
||||
`${progress.generation} / ${progress.totalGenerations}`;
|
||||
document.getElementById('best-fitness').textContent =
|
||||
progress.bestFitness.toFixed(2);
|
||||
document.getElementById('found-count').textContent =
|
||||
`${progress.foundCount} / ${progress.targetCount}`;
|
||||
|
||||
// Update progress bar
|
||||
const progressPercent = (progress.generation / progress.totalGenerations) * 100;
|
||||
document.getElementById('progress-bar').style.width = progressPercent + '%';
|
||||
}
|
||||
|
||||
// Update elapsed time
|
||||
const elapsed = Math.floor((Date.now() - startTime) / 1000);
|
||||
document.getElementById('elapsed-time').textContent = formatTime(elapsed);
|
||||
|
||||
// Log strategy found
|
||||
if (progress.strategyFound) {
|
||||
// Don't add to appData.foundStrategies here - we'll get them from GA at the end
|
||||
// This prevents duplicate storage and ensures metrics are preserved
|
||||
|
||||
const log = document.getElementById('progress-log');
|
||||
const m = progress.strategyFound.metrics;
|
||||
const logEntry = document.createElement('div');
|
||||
logEntry.className = 'log-entry';
|
||||
logEntry.style.display = 'flex';
|
||||
logEntry.style.justifyContent = 'space-between';
|
||||
logEntry.style.alignItems = 'center';
|
||||
logEntry.style.padding = '5px 0';
|
||||
logEntry.style.color = '#48bb78';
|
||||
|
||||
const text = document.createElement('span');
|
||||
text.textContent = `✓ Strategy ${progress.foundCount}: PF=${m.profitFactor.toFixed(2)}, WR=${m.winRate.toFixed(1)}%, Trades=${m.totalTrades}`;
|
||||
|
||||
const viewBtn = document.createElement('button');
|
||||
viewBtn.className = 'btn-small';
|
||||
viewBtn.textContent = 'View Details';
|
||||
viewBtn.onclick = () => {
|
||||
const index = progress.foundCount - 1;
|
||||
viewStrategyDetails(index);
|
||||
};
|
||||
|
||||
logEntry.appendChild(text);
|
||||
logEntry.appendChild(viewBtn);
|
||||
log.appendChild(logEntry);
|
||||
log.scrollTop = log.scrollHeight;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop generation
|
||||
*/
|
||||
function stopGeneration() {
|
||||
if (appData.optimizer) {
|
||||
appData.optimizer.stop();
|
||||
appData.isGenerating = false;
|
||||
|
||||
// Update collection with whatever was found so far
|
||||
if (appData.optimizer.foundStrategies) {
|
||||
appData.foundStrategies = appData.optimizer.foundStrategies;
|
||||
}
|
||||
}
|
||||
|
||||
if (appData.foundStrategies.length > 0) {
|
||||
showGenerationCompleteUI();
|
||||
} else {
|
||||
alert('No strategies found yet');
|
||||
showSection('config-section');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Show UI when generation is complete/stopped
|
||||
*/
|
||||
function showGenerationCompleteUI() {
|
||||
const stopBtn = document.getElementById('stop-btn');
|
||||
if (stopBtn) stopBtn.classList.add('hidden');
|
||||
|
||||
let viewResultsBtn = document.getElementById('view-results-btn');
|
||||
if (!viewResultsBtn) {
|
||||
viewResultsBtn = document.createElement('button');
|
||||
viewResultsBtn.id = 'view-results-btn';
|
||||
viewResultsBtn.className = 'btn-success';
|
||||
viewResultsBtn.textContent = 'View All Results →';
|
||||
viewResultsBtn.style.marginLeft = '10px';
|
||||
viewResultsBtn.onclick = displayResults;
|
||||
if (stopBtn && stopBtn.parentNode) {
|
||||
stopBtn.parentNode.appendChild(viewResultsBtn);
|
||||
}
|
||||
} else {
|
||||
viewResultsBtn.classList.remove('hidden');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Display results
|
||||
*/
|
||||
function displayResults() {
|
||||
console.log('📊 Displaying results...', appData.foundStrategies.length, 'strategies');
|
||||
showSection('results-section');
|
||||
|
||||
const grid = document.getElementById('strategies-grid');
|
||||
grid.innerHTML = '';
|
||||
|
||||
if (appData.foundStrategies.length === 0) {
|
||||
console.warn('⚠️ No strategies to display!');
|
||||
grid.innerHTML = '<p style="color: #a0aec0; text-align: center; padding: 40px;">No strategies found. Try adjusting your criteria.</p>';
|
||||
return;
|
||||
}
|
||||
|
||||
appData.foundStrategies.forEach((strategy, index) => {
|
||||
console.log(`Creating card for strategy ${index + 1}:`, strategy.metrics);
|
||||
const card = createStrategyCard(strategy, index + 1);
|
||||
grid.appendChild(card);
|
||||
});
|
||||
|
||||
// Reset selection on display
|
||||
appData.selectedStrategies.clear();
|
||||
updateCompareButton();
|
||||
|
||||
console.log('✅ Strategy cards created:', grid.children.length);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create strategy card
|
||||
*/
|
||||
function createStrategyCard(strategy, number) {
|
||||
console.log('Creating card for strategy number:', number, 'Strategy:', strategy);
|
||||
|
||||
if (!strategy || !strategy.metrics) {
|
||||
console.error('Invalid strategy object:', strategy);
|
||||
const errorCard = document.createElement('div');
|
||||
errorCard.className = 'strategy-card';
|
||||
errorCard.innerHTML = '<p style="color: red;">Error: Invalid strategy data</p>';
|
||||
return errorCard;
|
||||
}
|
||||
|
||||
const m = strategy.metrics;
|
||||
const card = document.createElement('div');
|
||||
card.className = 'strategy-card';
|
||||
|
||||
card.innerHTML = `
|
||||
<div class="strategy-header">
|
||||
<div class="strategy-name">${name}</div>
|
||||
<input type="checkbox" class="strategy-select" onchange="toggleStrategySelection(${number - 1}, this.checked)">
|
||||
</div>
|
||||
<div class="strategy-metrics">
|
||||
<div class="metric">
|
||||
<span class="metric-label">Profit Factor</span>
|
||||
<span class="metric-value ${m.profitFactor >= 1.5 ? 'positive' : 'negative'}">
|
||||
${m.profitFactor.toFixed(2)}
|
||||
</span>
|
||||
</div>
|
||||
<div class="metric">
|
||||
<span class="metric-label">Win Rate</span>
|
||||
<span class="metric-value">${m.winRate.toFixed(1)}%</span>
|
||||
</div>
|
||||
<div class="metric">
|
||||
<span class="metric-label">Trades</span>
|
||||
<span class="metric-value">${m.totalTrades}</span>
|
||||
</div>
|
||||
<div class="metric">
|
||||
<span class="metric-label">Max DD</span>
|
||||
<span class="metric-value negative">${m.maxDrawdown.toFixed(2)}%</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="strategy-actions">
|
||||
<button class="btn-secondary" onclick="viewStrategyDetails(${number - 1})">View Details</button>
|
||||
<button class="btn-primary" onclick="downloadStrategy(${number - 1}, 'mq4')">MQ4</button>
|
||||
<button class="btn-primary" onclick="downloadStrategy(${number - 1}, 'mq5')">MQ5</button>
|
||||
<button class="btn-success" onclick="downloadStrategy(${number - 1}, 'report')">Report</button>
|
||||
</div>
|
||||
`;
|
||||
|
||||
console.log('Card HTML created successfully');
|
||||
return card;
|
||||
}
|
||||
|
||||
/**
|
||||
* Download single strategy
|
||||
*/
|
||||
function downloadStrategy(index, type) {
|
||||
console.log(`💾 Downloading strategy ${index} type ${type}...`);
|
||||
const strategy = appData.foundStrategies[index];
|
||||
if (!strategy) {
|
||||
console.error(`❌ Strategy at index ${index} not found!`);
|
||||
return;
|
||||
}
|
||||
|
||||
const m = strategy.metrics;
|
||||
const name = `FxMath_${String(index + 1).padStart(3, '0')}_PF${m.profitFactor.toFixed(2).replace('.', '_')}_WR${Math.round(m.winRate)}`;
|
||||
|
||||
let content, filename;
|
||||
|
||||
try {
|
||||
// Convert strategy to EA-Convertor format
|
||||
const strategyData = {
|
||||
parameters: {
|
||||
symbol: 'EURUSD',
|
||||
atr_period: strategy.atrPeriod,
|
||||
sl_multiplier: strategy.slMultiplier,
|
||||
tp_multiplier: strategy.tpMultiplier,
|
||||
close_at_opposite: strategy.closeAtOpposite || true
|
||||
},
|
||||
buy_rules: strategy.rules,
|
||||
sell_rules: strategy.rules // SELL rules are inverted in the converters
|
||||
};
|
||||
|
||||
if (type === 'mq4') {
|
||||
const converter = new MQ4Converter(strategyData);
|
||||
content = converter.generate();
|
||||
filename = name + '.mq4';
|
||||
} else if (type === 'mq5') {
|
||||
const converter = new MQ5Converter(strategyData);
|
||||
content = converter.generate();
|
||||
filename = name + '.mq5';
|
||||
} else if (type === 'ctrader') {
|
||||
const converter = new CTraderConverter(strategyData);
|
||||
content = converter.generate();
|
||||
filename = name + '.cs';
|
||||
} else if (type === 'pine') {
|
||||
const converter = new PineConverter(strategyData);
|
||||
content = converter.generate();
|
||||
filename = name + '.pine';
|
||||
} else if (type === 'report') {
|
||||
// Use new HTML report generator
|
||||
content = generateHTMLReport(strategy, index);
|
||||
filename = name + '_report.html';
|
||||
} else if (type === 'json') {
|
||||
content = JSON.stringify(strategy.toJSON(), null, 2);
|
||||
filename = name + '.json';
|
||||
}
|
||||
|
||||
console.log(`✅ Content generated for ${filename}, size: ${content.length}`);
|
||||
downloadFile(content, filename);
|
||||
} catch (error) {
|
||||
console.error(`❌ Error generating ${type} for strategy ${index}:`, error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Download all strategies
|
||||
*/
|
||||
function downloadAllStrategies() {
|
||||
console.log('📥 Download All Strategies started...', appData.foundStrategies.length, 'strategies');
|
||||
|
||||
if (appData.foundStrategies.length === 0) {
|
||||
alert('No strategies to download');
|
||||
return;
|
||||
}
|
||||
|
||||
const btn = document.getElementById('download-all-btn');
|
||||
const originalText = btn.textContent;
|
||||
btn.disabled = true;
|
||||
btn.textContent = 'Preparing Downloads...';
|
||||
|
||||
const total = appData.foundStrategies.length;
|
||||
let completed = 0;
|
||||
|
||||
appData.foundStrategies.forEach((strategy, index) => {
|
||||
setTimeout(() => {
|
||||
console.log(`⏱️ Triggering downloads for strategy ${index + 1}/${total}...`);
|
||||
btn.textContent = `Downloading ${index + 1}/${total}...`;
|
||||
|
||||
downloadStrategy(index, 'mq4');
|
||||
setTimeout(() => downloadStrategy(index, 'mq5'), 150);
|
||||
setTimeout(() => downloadStrategy(index, 'report'), 300);
|
||||
setTimeout(() => downloadStrategy(index, 'json'), 450);
|
||||
|
||||
completed++;
|
||||
if (completed === total) {
|
||||
setTimeout(() => {
|
||||
btn.disabled = false;
|
||||
btn.textContent = originalText;
|
||||
console.log('✅ All downloads triggered!');
|
||||
}, 1000);
|
||||
}
|
||||
}, index * 1200); // 1.2s per strategy to be safe
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Download file helper
|
||||
*/
|
||||
function downloadFile(content, filename) {
|
||||
const blob = new Blob([content], { type: 'text/plain' });
|
||||
const url = URL.createObjectURL(blob);
|
||||
const a = document.createElement('a');
|
||||
a.href = url;
|
||||
a.download = filename;
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
|
||||
// Delay removal and revocation to ensure browser captures the click
|
||||
setTimeout(() => {
|
||||
document.body.removeChild(a);
|
||||
URL.revokeObjectURL(url);
|
||||
}, 500);
|
||||
}
|
||||
|
||||
/**
|
||||
* Show specific section
|
||||
*/
|
||||
function showSection(sectionId) {
|
||||
const sections = document.querySelectorAll('.section');
|
||||
sections.forEach(section => section.classList.remove('active'));
|
||||
document.getElementById(sectionId).classList.add('active');
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggle strategy selection for comparison
|
||||
*/
|
||||
function toggleStrategySelection(index, isSelected) {
|
||||
if (isSelected) {
|
||||
appData.selectedStrategies.add(index);
|
||||
} else {
|
||||
appData.selectedStrategies.delete(index);
|
||||
}
|
||||
updateCompareButton();
|
||||
}
|
||||
|
||||
/**
|
||||
* Update comparison button text
|
||||
*/
|
||||
function updateCompareButton() {
|
||||
const btn = document.getElementById('compare-btn');
|
||||
if (btn) {
|
||||
const count = appData.selectedStrategies.size;
|
||||
btn.textContent = `Compare Selected (${count})`;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Show comparison section
|
||||
*/
|
||||
function showComparison() {
|
||||
const container = document.getElementById('comparison-container');
|
||||
container.innerHTML = '';
|
||||
|
||||
appData.selectedStrategies.forEach(index => {
|
||||
const strategy = appData.foundStrategies[index];
|
||||
const m = strategy.metrics;
|
||||
const name = `Strategy ${index + 1}`;
|
||||
|
||||
const item = document.createElement('div');
|
||||
item.className = 'comparison-item';
|
||||
item.innerHTML = `
|
||||
<h3>${name}</h3>
|
||||
<div class="strategy-metrics">
|
||||
<div class="metric">
|
||||
<span class="metric-label">PF</span>
|
||||
<span class="metric-value ${m.profitFactor >= 1.5 ? 'positive' : 'negative'}">${m.profitFactor.toFixed(2)}</span>
|
||||
</div>
|
||||
<div class="metric">
|
||||
<span class="metric-label">Win Rate</span>
|
||||
<span class="metric-value">${m.winRate.toFixed(1)}%</span>
|
||||
</div>
|
||||
<div class="metric">
|
||||
<span class="metric-label">Trades</span>
|
||||
<span class="metric-value">${m.totalTrades}</span>
|
||||
</div>
|
||||
<div class="metric">
|
||||
<span class="metric-label">Max DD</span>
|
||||
<span class="metric-value negative">${m.maxDrawdown.toFixed(2)}%</span>
|
||||
</div>
|
||||
</div>
|
||||
<div style="margin-top: 15px;">
|
||||
<canvas id="comp-chart-${index}" style="height: 150px;"></canvas>
|
||||
</div>
|
||||
<div class="strategy-actions" style="margin-top: 15px;">
|
||||
<button class="btn-small" onclick="viewStrategyDetails(${index})">Full Details</button>
|
||||
</div>
|
||||
`;
|
||||
container.appendChild(item);
|
||||
|
||||
// Draw small chart
|
||||
setTimeout(() => {
|
||||
const ctx = document.getElementById(`comp-chart-${index}`).getContext('2d');
|
||||
new Chart(ctx, {
|
||||
type: 'line',
|
||||
data: {
|
||||
labels: m.equity.map((_, i) => i),
|
||||
datasets: [{
|
||||
data: m.equity,
|
||||
borderColor: '#667eea',
|
||||
borderWidth: 2,
|
||||
pointRadius: 0,
|
||||
fill: false
|
||||
}]
|
||||
},
|
||||
options: {
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
plugins: { legend: { display: false } },
|
||||
scales: { x: { display: false }, y: { display: false } }
|
||||
}
|
||||
});
|
||||
}, 100);
|
||||
});
|
||||
|
||||
showSection('results-section'); // Ensure we are in results
|
||||
document.getElementById('comparison-section').classList.remove('hidden');
|
||||
document.getElementById('comparison-section').scrollIntoView({ behavior: 'smooth' });
|
||||
}
|
||||
|
||||
/**
|
||||
* Hide comparison section
|
||||
*/
|
||||
function hideComparison() {
|
||||
document.getElementById('comparison-section').classList.add('hidden');
|
||||
}
|
||||
|
||||
/**
|
||||
* Format time in seconds to readable format
|
||||
*/
|
||||
function formatTime(seconds) {
|
||||
if (seconds < 60) return seconds + 's';
|
||||
const minutes = Math.floor(seconds / 60);
|
||||
const secs = seconds % 60;
|
||||
return `${minutes}m ${secs}s`;
|
||||
}
|
||||
|
||||
// Explicitly expose functions to global scope for inline onclick handlers
|
||||
window.downloadStrategy = downloadStrategy;
|
||||
window.downloadAllStrategies = downloadAllStrategies;
|
||||
window.toggleStrategySelection = toggleStrategySelection;
|
||||
window.showComparison = showComparison;
|
||||
window.hideComparison = hideComparison;
|
||||
@@ -0,0 +1,328 @@
|
||||
// MQ4 Converter
|
||||
class MQ4Converter {
|
||||
constructor(strategy) {
|
||||
this.strategy = strategy;
|
||||
this.parser = new RuleParser();
|
||||
}
|
||||
|
||||
generate() {
|
||||
const { parameters, buy_rules, sell_rules } = this.strategy;
|
||||
const symbol = parameters.symbol || 'EURUSD';
|
||||
const atrPeriod = parameters.atr_period || 14;
|
||||
const slMultiplier = parameters.sl_multiplier || 2.0;
|
||||
const tpMultiplier = parameters.tp_multiplier || 3.0;
|
||||
|
||||
// Generate random 6-digit magic number
|
||||
const magicNumber = Math.floor(100000 + Math.random() * 900000);
|
||||
|
||||
const buyConditions = this.parser.parseRules(buy_rules, 'mql');
|
||||
const sellConditions = this.parser.parseRules(sell_rules, 'mql');
|
||||
|
||||
return `//+------------------------------------------------------------------+
|
||||
//| Strategy_${symbol}.mq4 |
|
||||
//| Generated by Strategy Converter |
|
||||
//+------------------------------------------------------------------+
|
||||
#property copyright "FxMath Quant"
|
||||
#property link "https://fxmath.com"
|
||||
#property version "1.00"
|
||||
#property strict
|
||||
|
||||
//=== TRADE SETTINGS ===
|
||||
input double LotSize = 0.1; // Lot Size
|
||||
input int ATR_Period = ${atrPeriod}; // ATR Period
|
||||
input double SL_Multiplier = ${slMultiplier.toFixed(2)}; // SL ATR Multiplier
|
||||
input double TP_Multiplier = ${tpMultiplier.toFixed(2)}; // TP ATR Multiplier
|
||||
input int MagicNumber = ${magicNumber}; // Magic Number
|
||||
input string TradeComment = "FxMath EA"; // Trade Comment
|
||||
input int Slippage = 3; // Slippage
|
||||
|
||||
//=== SL/TP SETTINGS ===
|
||||
input bool EnableHardSL = true; // Enable Hard Stop Loss
|
||||
input bool EnableHardTP = true; // Enable Hard Take Profit
|
||||
|
||||
//=== TRAILING STOP ===
|
||||
input bool EnableTrailing = false; // Enable Trailing Stop
|
||||
input double TrailingStart = 30; // Trailing Start (pips)
|
||||
input double TrailingStep = 10; // Trailing Step (pips)
|
||||
|
||||
//=== BREAKEVEN ===
|
||||
input bool EnableBreakeven = false; // Enable Breakeven
|
||||
input double BreakevenStart = 20; // Breakeven Start (pips)
|
||||
input double BreakevenOffset = 2; // Breakeven Offset (pips)
|
||||
|
||||
//=== TIME FILTER ===
|
||||
input bool EnableTimeFilter = false; // Enable Time Filter
|
||||
input int StartHour = 8; // Start Hour (Server Time)
|
||||
input int StartMinute = 0; // Start Minute
|
||||
input int EndHour = 22; // End Hour (Server Time)
|
||||
input int EndMinute = 0; // End Minute
|
||||
|
||||
//=== SIGNAL SETTINGS ===
|
||||
input bool CloseOnOpposite = true; // Close Trade on Opposite Signal
|
||||
|
||||
//=== DISPLAY SETTINGS ===
|
||||
input bool ShowChartInfo = true; // Show Info on Chart
|
||||
|
||||
// Global Variables
|
||||
double atrValue;
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Expert initialization function |
|
||||
//+------------------------------------------------------------------+
|
||||
int OnInit()
|
||||
{
|
||||
Print("Strategy EA initialized for ${symbol}");
|
||||
Print("Magic Number: ", MagicNumber);
|
||||
return(INIT_SUCCEEDED);
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Expert deinitialization function |
|
||||
//+------------------------------------------------------------------+
|
||||
void OnDeinit(const int reason)
|
||||
{
|
||||
Print("Strategy EA deinitialized");
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Expert tick function |
|
||||
//+------------------------------------------------------------------+
|
||||
void OnTick()
|
||||
{
|
||||
// Calculate ATR
|
||||
atrValue = iATR(Symbol(), 0, ATR_Period, 1);
|
||||
|
||||
// Check for open positions
|
||||
bool hasPosition = false;
|
||||
for(int i = OrdersTotal() - 1; i >= 0; i--)
|
||||
{
|
||||
if(OrderSelect(i, SELECT_BY_POS, MODE_TRADES))
|
||||
{
|
||||
if(OrderSymbol() == Symbol() && OrderMagicNumber() == MagicNumber)
|
||||
{
|
||||
hasPosition = true;
|
||||
ManagePosition();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If no position, check for entry signals
|
||||
if(!hasPosition)
|
||||
{
|
||||
// Check time filter
|
||||
if(EnableTimeFilter && !IsTimeAllowed())
|
||||
return;
|
||||
|
||||
// Check Buy Signal
|
||||
if(CheckBuySignal())
|
||||
{
|
||||
OpenBuyOrder();
|
||||
}
|
||||
// Check Sell Signal
|
||||
else if(CheckSellSignal())
|
||||
{
|
||||
OpenSellOrder();
|
||||
}
|
||||
}
|
||||
|
||||
// Display chart info
|
||||
if(ShowChartInfo)
|
||||
DisplayChartInfo();
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Check if current time is allowed for trading |
|
||||
//+------------------------------------------------------------------+
|
||||
bool IsTimeAllowed()
|
||||
{
|
||||
int currentHour = Hour();
|
||||
int currentMinute = Minute();
|
||||
int currentMinutes = currentHour * 60 + currentMinute;
|
||||
int startMinutes = StartHour * 60 + StartMinute;
|
||||
int endMinutes = EndHour * 60 + EndMinute;
|
||||
|
||||
if(startMinutes < endMinutes)
|
||||
return (currentMinutes >= startMinutes && currentMinutes < endMinutes);
|
||||
else
|
||||
return (currentMinutes >= startMinutes || currentMinutes < endMinutes);
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Manage existing position |
|
||||
//+------------------------------------------------------------------+
|
||||
void ManagePosition()
|
||||
{
|
||||
if(!OrderSelect(0, SELECT_BY_POS, MODE_TRADES))
|
||||
return;
|
||||
|
||||
int orderType = OrderType();
|
||||
double orderOpenPrice = OrderOpenPrice();
|
||||
double orderSL = OrderStopLoss();
|
||||
double orderTP = OrderTakeProfit();
|
||||
|
||||
// Check for opposite signal
|
||||
if(CloseOnOpposite)
|
||||
{
|
||||
if(orderType == OP_BUY && CheckSellSignal())
|
||||
{
|
||||
if(!OrderClose(OrderTicket(), OrderLots(), Bid, Slippage, clrRed))
|
||||
Print("Error closing buy order: ", GetLastError());
|
||||
return;
|
||||
}
|
||||
else if(orderType == OP_SELL && CheckBuySignal())
|
||||
{
|
||||
if(!OrderClose(OrderTicket(), OrderLots(), Ask, Slippage, clrGreen))
|
||||
Print("Error closing sell order: ", GetLastError());
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
double currentPrice = (orderType == OP_BUY) ? Bid : Ask;
|
||||
double point = Point;
|
||||
double pipValue = point * 10;
|
||||
|
||||
// Breakeven
|
||||
if(EnableBreakeven)
|
||||
{
|
||||
double profit = (orderType == OP_BUY) ?
|
||||
(currentPrice - orderOpenPrice) :
|
||||
(orderOpenPrice - currentPrice);
|
||||
|
||||
if(profit >= BreakevenStart * pipValue)
|
||||
{
|
||||
double newSL = orderOpenPrice + (BreakevenOffset * pipValue *
|
||||
((orderType == OP_BUY) ? 1 : -1));
|
||||
|
||||
if((orderType == OP_BUY && newSL > orderSL) ||
|
||||
(orderType == OP_SELL && (orderSL == 0 || newSL < orderSL)))
|
||||
{
|
||||
if(!OrderModify(OrderTicket(), orderOpenPrice, newSL, orderTP, 0, clrBlue))
|
||||
Print("Error modifying order for breakeven: ", GetLastError());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Trailing Stop
|
||||
if(EnableTrailing)
|
||||
{
|
||||
double profit = (orderType == OP_BUY) ?
|
||||
(currentPrice - orderOpenPrice) :
|
||||
(orderOpenPrice - currentPrice);
|
||||
|
||||
if(profit >= TrailingStart * pipValue)
|
||||
{
|
||||
double newSL = currentPrice - (TrailingStep * pipValue *
|
||||
((orderType == OP_BUY) ? 1 : -1));
|
||||
|
||||
if((orderType == OP_BUY && newSL > orderSL) ||
|
||||
(orderType == OP_SELL && (orderSL == 0 || newSL < orderSL)))
|
||||
{
|
||||
if(!OrderModify(OrderTicket(), orderOpenPrice, newSL, orderTP, 0, clrBlue))
|
||||
Print("Error modifying order for trailing stop: ", GetLastError());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Display chart information |
|
||||
//+------------------------------------------------------------------+
|
||||
void DisplayChartInfo()
|
||||
{
|
||||
string info = "\\n=== " + TradeComment + " ===\\n";
|
||||
info += "Symbol: " + Symbol() + "\\n";
|
||||
info += "Magic: " + IntegerToString(MagicNumber) + "\\n";
|
||||
|
||||
bool hasPosition = false;
|
||||
for(int i = OrdersTotal() - 1; i >= 0; i--)
|
||||
{
|
||||
if(OrderSelect(i, SELECT_BY_POS, MODE_TRADES))
|
||||
{
|
||||
if(OrderSymbol() == Symbol() && OrderMagicNumber() == MagicNumber)
|
||||
{
|
||||
hasPosition = true;
|
||||
info += "Position: " + (OrderType() == OP_BUY ? "BUY" : "SELL") + "\\n";
|
||||
info += "Profit: " + DoubleToStr(OrderProfit(), 2) + "\\n";
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(!hasPosition)
|
||||
info += "No Position\\n";
|
||||
|
||||
Comment(info);
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Check Buy Signal |
|
||||
//+------------------------------------------------------------------+
|
||||
bool CheckBuySignal()
|
||||
{
|
||||
return (${buyConditions.join(' &&\n ')});
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Check Sell Signal |
|
||||
//+------------------------------------------------------------------+
|
||||
bool CheckSellSignal()
|
||||
{
|
||||
return (${sellConditions.join(' &&\n ')});
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Open Buy Order |
|
||||
//+------------------------------------------------------------------+
|
||||
void OpenBuyOrder()
|
||||
{
|
||||
double price = Ask;
|
||||
double sl = EnableHardSL ? price - (atrValue * SL_Multiplier) : 0;
|
||||
double tp = EnableHardTP ? price + (atrValue * TP_Multiplier) : 0;
|
||||
|
||||
sl = (sl > 0) ? NormalizeDouble(sl, Digits) : 0;
|
||||
tp = (tp > 0) ? NormalizeDouble(tp, Digits) : 0;
|
||||
|
||||
int ticket = OrderSend(Symbol(), OP_BUY, LotSize, price, Slippage, sl, tp,
|
||||
TradeComment, MagicNumber, 0, clrGreen);
|
||||
|
||||
if(ticket > 0)
|
||||
{
|
||||
Print("Buy order opened at ", price, " SL: ", sl, " TP: ", tp);
|
||||
}
|
||||
else
|
||||
{
|
||||
Print("Error opening buy order: ", GetLastError());
|
||||
}
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Open Sell Order |
|
||||
//+------------------------------------------------------------------+
|
||||
void OpenSellOrder()
|
||||
{
|
||||
double price = Bid;
|
||||
double sl = EnableHardSL ? price + (atrValue * SL_Multiplier) : 0;
|
||||
double tp = EnableHardTP ? price - (atrValue * TP_Multiplier) : 0;
|
||||
|
||||
sl = (sl > 0) ? NormalizeDouble(sl, Digits) : 0;
|
||||
tp = (tp > 0) ? NormalizeDouble(tp, Digits) : 0;
|
||||
|
||||
int ticket = OrderSend(Symbol(), OP_SELL, LotSize, price, Slippage, sl, tp,
|
||||
TradeComment, MagicNumber, 0, clrRed);
|
||||
|
||||
if(ticket > 0)
|
||||
{
|
||||
Print("Sell order opened at ", price, " SL: ", sl, " TP: ", tp);
|
||||
}
|
||||
else
|
||||
{
|
||||
Print("Error opening sell order: ", GetLastError());
|
||||
}
|
||||
}
|
||||
//+------------------------------------------------------------------+
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
window.MQ4Converter = MQ4Converter;
|
||||
@@ -0,0 +1,152 @@
|
||||
/**
|
||||
* MQ4 Code Generator - Generates MetaTrader 4 Expert Advisor code
|
||||
*/
|
||||
class MQ4Generator {
|
||||
constructor(strategy, strategyName) {
|
||||
this.strategy = strategy;
|
||||
this.name = strategyName || this.generateName();
|
||||
}
|
||||
|
||||
generateName() {
|
||||
const pf = this.strategy.metrics.profitFactor.toFixed(2).replace('.', '_');
|
||||
const wr = Math.round(this.strategy.metrics.winRate);
|
||||
const timestamp = new Date().toISOString().slice(0, 10).replace(/-/g, '');
|
||||
return `FxMath_PF${pf}_WR${wr}_${timestamp}`;
|
||||
}
|
||||
|
||||
generate() {
|
||||
return `//+------------------------------------------------------------------+
|
||||
//| ${this.name}.mq4 |
|
||||
//| Generated by FxMathQuant Web |
|
||||
//| https://fxmathquant.com |
|
||||
//+------------------------------------------------------------------+
|
||||
#property copyright "FxMathQuant"
|
||||
#property link "https://fxmathquant.com"
|
||||
#property version "1.00"
|
||||
#property strict
|
||||
|
||||
//--- Input Parameters
|
||||
input double LotSize = 0.01; // Lot size
|
||||
input int MagicNumber = ${Math.floor(Math.random() * 9000) + 1000}; // Magic number
|
||||
input int ATR_Period = ${this.strategy.atrPeriod}; // ATR period
|
||||
input double SL_Multiplier = ${this.strategy.slMultiplier.toFixed(2)}; // Stop Loss multiplier
|
||||
input double TP_Multiplier = ${this.strategy.tpMultiplier.toFixed(2)}; // Take Profit multiplier
|
||||
input int Slippage = 3; // Slippage in points
|
||||
|
||||
//--- Global Variables
|
||||
int ticket = 0;
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Expert initialization function |
|
||||
//+------------------------------------------------------------------+
|
||||
int OnInit()
|
||||
{
|
||||
Print("${this.name} initialized");
|
||||
Print("Strategy Performance: PF=${this.strategy.metrics.profitFactor.toFixed(2)}, WR=${this.strategy.metrics.winRate.toFixed(1)}%");
|
||||
return(INIT_SUCCEEDED);
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Expert deinitialization function |
|
||||
//+------------------------------------------------------------------+
|
||||
void OnDeinit(const int reason)
|
||||
{
|
||||
Print("${this.name} stopped");
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Expert tick function |
|
||||
//+------------------------------------------------------------------+
|
||||
void OnTick()
|
||||
{
|
||||
// Check if we already have an open position
|
||||
if(OrdersTotal() > 0)
|
||||
{
|
||||
for(int i = 0; i < OrdersTotal(); i++)
|
||||
{
|
||||
if(OrderSelect(i, SELECT_BY_POS, MODE_TRADES))
|
||||
{
|
||||
if(OrderSymbol() == Symbol() && OrderMagicNumber() == MagicNumber)
|
||||
return; // Position already open
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate ATR
|
||||
double atr = iATR(Symbol(), Period(), ATR_Period, 0);
|
||||
if(atr == 0) return;
|
||||
|
||||
// Check for BUY signal
|
||||
if(CheckBuySignal())
|
||||
{
|
||||
double entry = Ask;
|
||||
double sl = entry - (atr * SL_Multiplier);
|
||||
double tp = entry + (atr * TP_Multiplier);
|
||||
|
||||
// Normalize prices
|
||||
sl = NormalizeDouble(sl, Digits);
|
||||
tp = NormalizeDouble(tp, Digits);
|
||||
|
||||
// Open BUY order
|
||||
ticket = OrderSend(Symbol(), OP_BUY, LotSize, entry, Slippage, sl, tp,
|
||||
"${this.name}", MagicNumber, 0, clrGreen);
|
||||
|
||||
if(ticket > 0)
|
||||
Print("BUY order opened: ", ticket, " @ ", entry, " SL:", sl, " TP:", tp);
|
||||
else
|
||||
Print("Error opening BUY order: ", GetLastError());
|
||||
}
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Check BUY signal based on strategy rules |
|
||||
//+------------------------------------------------------------------+
|
||||
bool CheckBuySignal()
|
||||
{
|
||||
${this.generateRulesCode()}
|
||||
|
||||
return true; // All rules passed
|
||||
}
|
||||
|
||||
${this.generateHelperFunctions()}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
`;
|
||||
}
|
||||
|
||||
generateRulesCode() {
|
||||
return this.strategy.rules.map((rule, index) => {
|
||||
const condition = this.ruleToMQ4(rule);
|
||||
return ` // Rule ${index + 1}
|
||||
if(!(${condition})) return false;`;
|
||||
}).join('\n');
|
||||
}
|
||||
|
||||
ruleToMQ4(rule) {
|
||||
if (rule.type === 'simple') {
|
||||
const left = this.priceToMQ4(rule.left.price, rule.left.shift);
|
||||
const right = this.priceToMQ4(rule.right.price, rule.right.shift);
|
||||
return `${left} ${rule.operator} ${right}`;
|
||||
} else {
|
||||
// Arithmetic rule
|
||||
const left1 = this.priceToMQ4(rule.left.price1, rule.left.shift1);
|
||||
const left2 = this.priceToMQ4(rule.left.price2, rule.left.shift2);
|
||||
const right = this.priceToMQ4(rule.right.price, rule.right.shift);
|
||||
|
||||
return `(${left1} ${rule.left.op} ${left2}) ${rule.operator} (${right} * ${rule.right.multiplier})`;
|
||||
}
|
||||
}
|
||||
|
||||
priceToMQ4(priceType, shift) {
|
||||
const type = priceType.charAt(0).toUpperCase() + priceType.slice(1);
|
||||
return `i${type}(Symbol(), Period(), ${shift})`;
|
||||
}
|
||||
|
||||
generateHelperFunctions() {
|
||||
return `//+------------------------------------------------------------------+
|
||||
//| Helper Functions |
|
||||
//+------------------------------------------------------------------+
|
||||
// Add any additional helper functions here
|
||||
`;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,341 @@
|
||||
// MQ5 Converter
|
||||
class MQ5Converter {
|
||||
constructor(strategy) {
|
||||
this.strategy = strategy;
|
||||
this.parser = new RuleParser();
|
||||
}
|
||||
|
||||
generate() {
|
||||
const { parameters, buy_rules, sell_rules } = this.strategy;
|
||||
const symbol = parameters.symbol || 'EURUSD';
|
||||
const atrPeriod = parameters.atr_period || 14;
|
||||
const slMultiplier = parameters.sl_multiplier || 2.0;
|
||||
const tpMultiplier = parameters.tp_multiplier || 3.0;
|
||||
|
||||
// Generate random 6-digit magic number
|
||||
const magicNumber = Math.floor(100000 + Math.random() * 900000);
|
||||
|
||||
const buyConditions = this.parser.parseRules(buy_rules, 'mq5');
|
||||
const sellConditions = this.parser.parseRules(sell_rules, 'mq5');
|
||||
|
||||
return `//+------------------------------------------------------------------+
|
||||
//| Strategy_${symbol}.mq5 |
|
||||
//| Generated by Strategy Converter |
|
||||
//+------------------------------------------------------------------+
|
||||
#property copyright "FxMath Quant"
|
||||
#property link "https://fxmath.com"
|
||||
#property version "1.00"
|
||||
|
||||
#include <Trade\\Trade.mqh>
|
||||
|
||||
//=== TRADING SETTINGS ===
|
||||
input group "=== Trade Settings ==="
|
||||
input double LotSize = 0.1; // Lot Size
|
||||
input int ATR_Period = ${atrPeriod}; // ATR Period
|
||||
input double SL_Multiplier = ${slMultiplier.toFixed(2)}; // SL ATR Multiplier
|
||||
input double TP_Multiplier = ${tpMultiplier.toFixed(2)}; // TP ATR Multiplier
|
||||
input ulong MagicNumber = ${magicNumber}; // Magic Number
|
||||
input string TradeComment = "FxMath EA"; // Trade Comment
|
||||
input int Slippage = 3; // Slippage
|
||||
|
||||
//=== SL/TP SETTINGS ===
|
||||
input group "=== SL/TP Settings ==="
|
||||
input bool EnableHardSL = true; // Enable Hard Stop Loss
|
||||
input bool EnableHardTP = true; // Enable Hard Take Profit
|
||||
|
||||
//=== TRAILING STOP ===
|
||||
input group "=== Trailing Stop ==="
|
||||
input bool EnableTrailing = false; // Enable Trailing Stop
|
||||
input double TrailingStart = 30; // Trailing Start (pips)
|
||||
input double TrailingStep = 10; // Trailing Step (pips)
|
||||
|
||||
//=== BREAKEVEN ===
|
||||
input group "=== Breakeven ==="
|
||||
input bool EnableBreakeven = false; // Enable Breakeven
|
||||
input double BreakevenStart = 20; // Breakeven Start (pips)
|
||||
input double BreakevenOffset = 2; // Breakeven Offset (pips)
|
||||
|
||||
//=== TIME FILTER ===
|
||||
input group "=== Time Filter ==="
|
||||
input bool EnableTimeFilter = false; // Enable Time Filter
|
||||
input int StartHour = 8; // Start Hour (Server Time)
|
||||
input int StartMinute = 0; // Start Minute
|
||||
input int EndHour = 22; // End Hour (Server Time)
|
||||
input int EndMinute = 0; // End Minute
|
||||
|
||||
//=== SIGNAL SETTINGS ===
|
||||
input group "=== Signal Settings ==="
|
||||
input bool CloseOnOpposite = true; // Close Trade on Opposite Signal
|
||||
|
||||
//=== DISPLAY SETTINGS ===
|
||||
input group "=== Display Settings ==="
|
||||
input bool ShowChartInfo = true; // Show Info on Chart
|
||||
|
||||
// Global Variables
|
||||
CTrade trade;
|
||||
int atrHandle;
|
||||
double atrBuffer[];
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Expert initialization function |
|
||||
//+------------------------------------------------------------------+
|
||||
int OnInit()
|
||||
{
|
||||
// Set trade parameters
|
||||
trade.SetExpertMagicNumber(MagicNumber);
|
||||
trade.SetDeviationInPoints(Slippage);
|
||||
trade.SetTypeFilling(ORDER_FILLING_FOK);
|
||||
|
||||
// Create ATR indicator handle
|
||||
atrHandle = iATR(_Symbol, PERIOD_CURRENT, ATR_Period);
|
||||
|
||||
if(atrHandle == INVALID_HANDLE)
|
||||
{
|
||||
Print("Error creating ATR indicator");
|
||||
return(INIT_FAILED);
|
||||
}
|
||||
|
||||
ArraySetAsSeries(atrBuffer, true);
|
||||
|
||||
Print("Strategy EA initialized for ${symbol}");
|
||||
Print("Magic Number: ", MagicNumber);
|
||||
return(INIT_SUCCEEDED);
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Expert deinitialization function |
|
||||
//+------------------------------------------------------------------+
|
||||
void OnDeinit(const int reason)
|
||||
{
|
||||
if(atrHandle != INVALID_HANDLE)
|
||||
IndicatorRelease(atrHandle);
|
||||
|
||||
Print("Strategy EA deinitialized");
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Expert tick function |
|
||||
//+------------------------------------------------------------------+
|
||||
void OnTick()
|
||||
{
|
||||
// Copy ATR values
|
||||
if(CopyBuffer(atrHandle, 0, 0, 2, atrBuffer) < 2)
|
||||
{
|
||||
Print("Error copying ATR buffer");
|
||||
return;
|
||||
}
|
||||
|
||||
double atrValue = atrBuffer[1];
|
||||
|
||||
// Check if we have an open position
|
||||
if(PositionSelect(_Symbol))
|
||||
{
|
||||
// Position exists - manage it
|
||||
ManagePosition(atrValue);
|
||||
}
|
||||
else
|
||||
{
|
||||
// No position - check for entry signals
|
||||
|
||||
// Check time filter
|
||||
if(EnableTimeFilter && !IsTimeAllowed())
|
||||
return;
|
||||
|
||||
// Check Buy Signal
|
||||
if(CheckBuySignal())
|
||||
{
|
||||
OpenBuyOrder(atrValue);
|
||||
}
|
||||
// Check Sell Signal
|
||||
else if(CheckSellSignal())
|
||||
{
|
||||
OpenSellOrder(atrValue);
|
||||
}
|
||||
}
|
||||
|
||||
// Display chart info
|
||||
if(ShowChartInfo)
|
||||
DisplayChartInfo();
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Check if current time is allowed for trading |
|
||||
//+------------------------------------------------------------------+
|
||||
bool IsTimeAllowed()
|
||||
{
|
||||
MqlDateTime dt;
|
||||
TimeToStruct(TimeCurrent(), dt);
|
||||
|
||||
int currentMinutes = dt.hour * 60 + dt.min;
|
||||
int startMinutes = StartHour * 60 + StartMinute;
|
||||
int endMinutes = EndHour * 60 + EndMinute;
|
||||
|
||||
if(startMinutes < endMinutes)
|
||||
return (currentMinutes >= startMinutes && currentMinutes < endMinutes);
|
||||
else
|
||||
return (currentMinutes >= startMinutes || currentMinutes < endMinutes);
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Manage existing position |
|
||||
//+------------------------------------------------------------------+
|
||||
void ManagePosition(double atrValue)
|
||||
{
|
||||
if(!PositionSelect(_Symbol))
|
||||
return;
|
||||
|
||||
long posType = PositionGetInteger(POSITION_TYPE);
|
||||
double posOpenPrice = PositionGetDouble(POSITION_PRICE_OPEN);
|
||||
double posSL = PositionGetDouble(POSITION_SL);
|
||||
double posTP = PositionGetDouble(POSITION_TP);
|
||||
|
||||
// Check for opposite signal
|
||||
if(CloseOnOpposite)
|
||||
{
|
||||
if(posType == POSITION_TYPE_BUY && CheckSellSignal())
|
||||
{
|
||||
trade.PositionClose(_Symbol);
|
||||
return;
|
||||
}
|
||||
else if(posType == POSITION_TYPE_SELL && CheckBuySignal())
|
||||
{
|
||||
trade.PositionClose(_Symbol);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
double currentPrice = (posType == POSITION_TYPE_BUY) ?
|
||||
SymbolInfoDouble(_Symbol, SYMBOL_BID) :
|
||||
SymbolInfoDouble(_Symbol, SYMBOL_ASK);
|
||||
|
||||
double point = SymbolInfoDouble(_Symbol, SYMBOL_POINT);
|
||||
double pipValue = point * 10;
|
||||
|
||||
// Breakeven
|
||||
if(EnableBreakeven)
|
||||
{
|
||||
double profit = (posType == POSITION_TYPE_BUY) ?
|
||||
(currentPrice - posOpenPrice) :
|
||||
(posOpenPrice - currentPrice);
|
||||
|
||||
if(profit >= BreakevenStart * pipValue)
|
||||
{
|
||||
double newSL = posOpenPrice + (BreakevenOffset * pipValue *
|
||||
((posType == POSITION_TYPE_BUY) ? 1 : -1));
|
||||
|
||||
if((posType == POSITION_TYPE_BUY && newSL > posSL) ||
|
||||
(posType == POSITION_TYPE_SELL && (posSL == 0 || newSL < posSL)))
|
||||
{
|
||||
trade.PositionModify(_Symbol, newSL, posTP);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Trailing Stop
|
||||
if(EnableTrailing)
|
||||
{
|
||||
double profit = (posType == POSITION_TYPE_BUY) ?
|
||||
(currentPrice - posOpenPrice) :
|
||||
(posOpenPrice - currentPrice);
|
||||
|
||||
if(profit >= TrailingStart * pipValue)
|
||||
{
|
||||
double newSL = currentPrice - (TrailingStep * pipValue *
|
||||
((posType == POSITION_TYPE_BUY) ? 1 : -1));
|
||||
|
||||
if((posType == POSITION_TYPE_BUY && newSL > posSL) ||
|
||||
(posType == POSITION_TYPE_SELL && (posSL == 0 || newSL < posSL)))
|
||||
{
|
||||
trade.PositionModify(_Symbol, newSL, posTP);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Display chart information |
|
||||
//+------------------------------------------------------------------+
|
||||
void DisplayChartInfo()
|
||||
{
|
||||
string info = "\\n=== " + TradeComment + " ===\\n";
|
||||
info += "Symbol: " + _Symbol + "\\n";
|
||||
info += "Magic: " + IntegerToString(MagicNumber) + "\\n";
|
||||
|
||||
if(PositionSelect(_Symbol))
|
||||
{
|
||||
info += "Position: " + EnumToString((ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE)) + "\\n";
|
||||
info += "Profit: " + DoubleToString(PositionGetDouble(POSITION_PROFIT), 2) + "\\n";
|
||||
}
|
||||
else
|
||||
{
|
||||
info += "No Position\\n";
|
||||
}
|
||||
|
||||
Comment(info);
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Check Buy Signal |
|
||||
//+------------------------------------------------------------------+
|
||||
bool CheckBuySignal()
|
||||
{
|
||||
return (${buyConditions.join(' &&\n ')});
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Check Sell Signal |
|
||||
//+------------------------------------------------------------------+
|
||||
bool CheckSellSignal()
|
||||
{
|
||||
return (${sellConditions.join(' &&\n ')});
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Open Buy Order |
|
||||
//+------------------------------------------------------------------+
|
||||
void OpenBuyOrder(double atrValue)
|
||||
{
|
||||
double price = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
|
||||
double sl = EnableHardSL ? price - (atrValue * SL_Multiplier) : 0;
|
||||
double tp = EnableHardTP ? price + (atrValue * TP_Multiplier) : 0;
|
||||
|
||||
sl = (sl > 0) ? NormalizeDouble(sl, _Digits) : 0;
|
||||
tp = (tp > 0) ? NormalizeDouble(tp, _Digits) : 0;
|
||||
|
||||
if(trade.Buy(LotSize, _Symbol, price, sl, tp, TradeComment))
|
||||
{
|
||||
Print("Buy order opened at ", price, " SL: ", sl, " TP: ", tp);
|
||||
}
|
||||
else
|
||||
{
|
||||
Print("Error opening buy order: ", GetLastError());
|
||||
}
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Open Sell Order |
|
||||
//+------------------------------------------------------------------+
|
||||
void OpenSellOrder(double atrValue)
|
||||
{
|
||||
double price = SymbolInfoDouble(_Symbol, SYMBOL_BID);
|
||||
double sl = EnableHardSL ? price + (atrValue * SL_Multiplier) : 0;
|
||||
double tp = EnableHardTP ? price - (atrValue * TP_Multiplier) : 0;
|
||||
|
||||
sl = (sl > 0) ? NormalizeDouble(sl, _Digits) : 0;
|
||||
tp = (tp > 0) ? NormalizeDouble(tp, _Digits) : 0;
|
||||
|
||||
if(trade.Sell(LotSize, _Symbol, price, sl, tp, TradeComment))
|
||||
{
|
||||
Print("Sell order opened at ", price, " SL: ", sl, " TP: ", tp);
|
||||
}
|
||||
else
|
||||
{
|
||||
Print("Error opening sell order: ", GetLastError());
|
||||
}
|
||||
}
|
||||
//+------------------------------------------------------------------+
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
window.MQ5Converter = MQ5Converter;
|
||||
@@ -0,0 +1,202 @@
|
||||
/**
|
||||
* MQ5 Code Generator - Generates MetaTrader 5 Expert Advisor code
|
||||
*/
|
||||
class MQ5Generator {
|
||||
constructor(strategy, strategyName) {
|
||||
this.strategy = strategy;
|
||||
this.name = strategyName || this.generateName();
|
||||
}
|
||||
|
||||
generateName() {
|
||||
const pf = this.strategy.metrics.profitFactor.toFixed(2).replace('.', '_');
|
||||
const wr = Math.round(this.strategy.metrics.winRate);
|
||||
const timestamp = new Date().toISOString().slice(0, 10).replace(/-/g, '');
|
||||
return `FxMath_PF${pf}_WR${wr}_${timestamp}`;
|
||||
}
|
||||
|
||||
generate() {
|
||||
return `//+------------------------------------------------------------------+
|
||||
//| ${this.name}.mq5 |
|
||||
//| Generated by FxMathQuant Web |
|
||||
//| https://fxmathquant.com |
|
||||
//+------------------------------------------------------------------+
|
||||
#property copyright "FxMathQuant"
|
||||
#property link "https://fxmathquant.com"
|
||||
#property version "1.00"
|
||||
|
||||
//--- Include libraries
|
||||
#include <Trade\\Trade.mqh>
|
||||
|
||||
//--- Input Parameters
|
||||
input double LotSize = 0.01; // Lot size
|
||||
input int MagicNumber = ${Math.floor(Math.random() * 9000) + 1000}; // Magic number
|
||||
input int ATR_Period = ${this.strategy.atrPeriod}; // ATR period
|
||||
input double SL_Multiplier = ${this.strategy.slMultiplier.toFixed(2)}; // Stop Loss multiplier
|
||||
input double TP_Multiplier = ${this.strategy.tpMultiplier.toFixed(2)}; // Take Profit multiplier
|
||||
input int Slippage = 3; // Slippage in points
|
||||
|
||||
//--- Global Variables
|
||||
CTrade trade;
|
||||
int atrHandle;
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Expert initialization function |
|
||||
//+------------------------------------------------------------------+
|
||||
int OnInit()
|
||||
{
|
||||
// Set magic number
|
||||
trade.SetExpertMagicNumber(MagicNumber);
|
||||
trade.SetDeviationInPoints(Slippage);
|
||||
|
||||
// Create ATR indicator handle
|
||||
atrHandle = iATR(_Symbol, _Period, ATR_Period);
|
||||
if(atrHandle == INVALID_HANDLE)
|
||||
{
|
||||
Print("Error creating ATR indicator");
|
||||
return(INIT_FAILED);
|
||||
}
|
||||
|
||||
Print("${this.name} initialized");
|
||||
Print("Strategy Performance: PF=${this.strategy.metrics.profitFactor.toFixed(2)}, WR=${this.strategy.metrics.winRate.toFixed(1)}%");
|
||||
|
||||
return(INIT_SUCCEEDED);
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Expert deinitialization function |
|
||||
//+------------------------------------------------------------------+
|
||||
void OnDeinit(const int reason)
|
||||
{
|
||||
// Release indicator handle
|
||||
if(atrHandle != INVALID_HANDLE)
|
||||
IndicatorRelease(atrHandle);
|
||||
|
||||
Print("${this.name} stopped");
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Expert tick function |
|
||||
//+------------------------------------------------------------------+
|
||||
void OnTick()
|
||||
{
|
||||
// Check if we already have an open position
|
||||
if(PositionSelect(_Symbol))
|
||||
{
|
||||
if(PositionGetInteger(POSITION_MAGIC) == MagicNumber)
|
||||
return; // Position already open
|
||||
}
|
||||
|
||||
// Get ATR value
|
||||
double atrBuffer[];
|
||||
ArraySetAsSeries(atrBuffer, true);
|
||||
if(CopyBuffer(atrHandle, 0, 0, 1, atrBuffer) <= 0)
|
||||
return;
|
||||
|
||||
double atr = atrBuffer[0];
|
||||
if(atr == 0) return;
|
||||
|
||||
// Check for BUY signal
|
||||
if(CheckBuySignal())
|
||||
{
|
||||
double entry = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
|
||||
double sl = entry - (atr * SL_Multiplier);
|
||||
double tp = entry + (atr * TP_Multiplier);
|
||||
|
||||
// Normalize prices
|
||||
sl = NormalizeDouble(sl, _Digits);
|
||||
tp = NormalizeDouble(tp, _Digits);
|
||||
|
||||
// Open BUY position
|
||||
if(trade.Buy(LotSize, _Symbol, entry, sl, tp, "${this.name}"))
|
||||
Print("BUY position opened @ ", entry, " SL:", sl, " TP:", tp);
|
||||
else
|
||||
Print("Error opening BUY position: ", trade.ResultRetcode());
|
||||
}
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Check BUY signal based on strategy rules |
|
||||
//+------------------------------------------------------------------+
|
||||
bool CheckBuySignal()
|
||||
{
|
||||
${this.generateRulesCode()}
|
||||
|
||||
return true; // All rules passed
|
||||
}
|
||||
|
||||
${this.generateHelperFunctions()}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
`;
|
||||
}
|
||||
|
||||
generateRulesCode() {
|
||||
return this.strategy.rules.map((rule, index) => {
|
||||
const condition = this.ruleToMQ5(rule);
|
||||
return ` // Rule ${index + 1}
|
||||
if(!(${condition})) return false;`;
|
||||
}).join('\n');
|
||||
}
|
||||
|
||||
ruleToMQ5(rule) {
|
||||
if (rule.type === 'simple') {
|
||||
const left = this.priceToMQ5(rule.left.price, rule.left.shift);
|
||||
const right = this.priceToMQ5(rule.right.price, rule.right.shift);
|
||||
return `${left} ${rule.operator} ${right}`;
|
||||
} else {
|
||||
// Arithmetic rule
|
||||
const left1 = this.priceToMQ5(rule.left.price1, rule.left.shift1);
|
||||
const left2 = this.priceToMQ5(rule.left.price2, rule.left.shift2);
|
||||
const right = this.priceToMQ5(rule.right.price, rule.right.shift);
|
||||
|
||||
return `(${left1} ${rule.left.op} ${left2}) ${rule.operator} (${right} * ${rule.right.multiplier})`;
|
||||
}
|
||||
}
|
||||
|
||||
priceToMQ5(priceType, shift) {
|
||||
const type = priceType.charAt(0).toUpperCase() + priceType.slice(1).toLowerCase();
|
||||
return `iClose(_Symbol, _Period, ${shift})`.replace('Close', type);
|
||||
}
|
||||
|
||||
generateHelperFunctions() {
|
||||
return `//+------------------------------------------------------------------+
|
||||
//| Get price value at specific shift |
|
||||
//+------------------------------------------------------------------+
|
||||
double iOpen(string symbol, ENUM_TIMEFRAMES timeframe, int shift)
|
||||
{
|
||||
double buffer[];
|
||||
ArraySetAsSeries(buffer, true);
|
||||
if(CopyOpen(symbol, timeframe, shift, 1, buffer) > 0)
|
||||
return buffer[0];
|
||||
return 0;
|
||||
}
|
||||
|
||||
double iHigh(string symbol, ENUM_TIMEFRAMES timeframe, int shift)
|
||||
{
|
||||
double buffer[];
|
||||
ArraySetAsSeries(buffer, true);
|
||||
if(CopyHigh(symbol, timeframe, shift, 1, buffer) > 0)
|
||||
return buffer[0];
|
||||
return 0;
|
||||
}
|
||||
|
||||
double iLow(string symbol, ENUM_TIMEFRAMES timeframe, int shift)
|
||||
{
|
||||
double buffer[];
|
||||
ArraySetAsSeries(buffer, true);
|
||||
if(CopyLow(symbol, timeframe, shift, 1, buffer) > 0)
|
||||
return buffer[0];
|
||||
return 0;
|
||||
}
|
||||
|
||||
double iClose(string symbol, ENUM_TIMEFRAMES timeframe, int shift)
|
||||
{
|
||||
double buffer[];
|
||||
ArraySetAsSeries(buffer, true);
|
||||
if(CopyClose(symbol, timeframe, shift, 1, buffer) > 0)
|
||||
return buffer[0];
|
||||
return 0;
|
||||
}
|
||||
`;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
// Pine Script Converter
|
||||
class PineConverter {
|
||||
constructor(strategy) {
|
||||
this.strategy = strategy;
|
||||
this.parser = new RuleParser();
|
||||
}
|
||||
|
||||
generate() {
|
||||
const { parameters, buy_rules, sell_rules } = this.strategy;
|
||||
const symbol = parameters.symbol || 'EURUSD';
|
||||
const atrPeriod = parameters.atr_period || 14;
|
||||
const slMultiplier = parameters.sl_multiplier || 2.0;
|
||||
const tpMultiplier = parameters.tp_multiplier || 3.0;
|
||||
|
||||
const buyConditions = this.parser.parseRules(buy_rules, 'pine');
|
||||
const sellConditions = this.parser.parseRules(sell_rules, 'pine');
|
||||
|
||||
return `//@version=5
|
||||
strategy("Strategy ${symbol}", overlay=true,
|
||||
initial_capital=10000,
|
||||
default_qty_type=strategy.percent_of_equity,
|
||||
default_qty_value=100,
|
||||
commission_type=strategy.commission.percent,
|
||||
commission_value=0.1)
|
||||
|
||||
// Input Parameters
|
||||
lotSize = input.float(0.01, "Lot Size", minval=0.01, step=0.01)
|
||||
atrPeriod = input.int(${atrPeriod}, "ATR Period", minval=1)
|
||||
slMultiplier = input.float(${slMultiplier.toFixed(2)}, "Stop Loss Multiplier", minval=0.1, step=0.1)
|
||||
tpMultiplier = input.float(${tpMultiplier.toFixed(2)}, "Take Profit Multiplier", minval=0.1, step=0.1)
|
||||
|
||||
// Calculate ATR
|
||||
atrValue = ta.atr(atrPeriod)
|
||||
|
||||
// Buy Signal Conditions
|
||||
buySignal = ${buyConditions.join(' and\n ')}
|
||||
|
||||
// Sell Signal Conditions
|
||||
sellSignal = ${sellConditions.join(' and\n ')}
|
||||
|
||||
// Calculate Stop Loss and Take Profit levels
|
||||
longStopLoss = close - (atrValue * slMultiplier)
|
||||
longTakeProfit = close + (atrValue * tpMultiplier)
|
||||
shortStopLoss = close + (atrValue * slMultiplier)
|
||||
shortTakeProfit = close - (atrValue * tpMultiplier)
|
||||
|
||||
// Strategy Entry and Exit
|
||||
if (buySignal)
|
||||
strategy.entry("Long", strategy.long, comment="Buy Signal")
|
||||
strategy.exit("Long Exit", "Long", stop=longStopLoss, limit=longTakeProfit)
|
||||
|
||||
if (sellSignal)
|
||||
strategy.entry("Short", strategy.short, comment="Sell Signal")
|
||||
strategy.exit("Short Exit", "Short", stop=shortStopLoss, limit=shortTakeProfit)
|
||||
|
||||
// Plot Buy and Sell signals
|
||||
plotshape(buySignal, title="Buy Signal", location=location.belowbar,
|
||||
color=color.green, style=shape.triangleup, size=size.small)
|
||||
plotshape(sellSignal, title="Sell Signal", location=location.abovebar,
|
||||
color=color.red, style=shape.triangledown, size=size.small)
|
||||
|
||||
// Plot Stop Loss and Take Profit levels
|
||||
plot(strategy.position_size > 0 ? longStopLoss : na,
|
||||
title="Long SL", color=color.red, style=plot.style_linebr, linewidth=1)
|
||||
plot(strategy.position_size > 0 ? longTakeProfit : na,
|
||||
title="Long TP", color=color.green, style=plot.style_linebr, linewidth=1)
|
||||
plot(strategy.position_size < 0 ? shortStopLoss : na,
|
||||
title="Short SL", color=color.red, style=plot.style_linebr, linewidth=1)
|
||||
plot(strategy.position_size < 0 ? shortTakeProfit : na,
|
||||
title="Short TP", color=color.green, style=plot.style_linebr, linewidth=1)
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
window.PineConverter = PineConverter;
|
||||
@@ -0,0 +1,272 @@
|
||||
/**
|
||||
* HTML Report Generator - Creates interactive performance reports
|
||||
*/
|
||||
class ReportGenerator {
|
||||
constructor(strategy, strategyName) {
|
||||
this.strategy = strategy;
|
||||
this.name = strategyName || 'Strategy Report';
|
||||
}
|
||||
|
||||
generate() {
|
||||
const m = this.strategy.metrics;
|
||||
|
||||
return `<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>${this.name} - Performance Report</title>
|
||||
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.0/dist/chart.umd.min.js"></script>
|
||||
<style>
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
body {
|
||||
font-family: 'Inter', -apple-system, sans-serif;
|
||||
background: linear-gradient(135deg, #0a0e27 0%, #1a1f3a 100%);
|
||||
color: #ffffff;
|
||||
padding: 40px 20px;
|
||||
}
|
||||
.container { max-width: 1200px; margin: 0 auto; }
|
||||
h1 { font-size: 32px; margin-bottom: 10px; color: #667eea; }
|
||||
.subtitle { color: #a0aec0; margin-bottom: 30px; }
|
||||
.metrics-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||||
gap: 20px;
|
||||
margin-bottom: 40px;
|
||||
}
|
||||
.metric-card {
|
||||
background: #1a1f3a;
|
||||
padding: 20px;
|
||||
border-radius: 12px;
|
||||
border: 1px solid #2d3748;
|
||||
}
|
||||
.metric-label {
|
||||
font-size: 12px;
|
||||
color: #718096;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 1px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
.metric-value {
|
||||
font-size: 28px;
|
||||
font-weight: 700;
|
||||
}
|
||||
.positive { color: #48bb78; }
|
||||
.negative { color: #f56565; }
|
||||
.neutral { color: #667eea; }
|
||||
.chart-container {
|
||||
background: #1a1f3a;
|
||||
padding: 30px;
|
||||
border-radius: 12px;
|
||||
margin-bottom: 30px;
|
||||
border: 1px solid #2d3748;
|
||||
}
|
||||
.rules-section {
|
||||
background: #1a1f3a;
|
||||
padding: 30px;
|
||||
border-radius: 12px;
|
||||
margin-bottom: 30px;
|
||||
border: 1px solid #2d3748;
|
||||
}
|
||||
.rule {
|
||||
padding: 10px;
|
||||
margin: 5px 0;
|
||||
background: #0a0e27;
|
||||
border-radius: 6px;
|
||||
font-family: 'Courier New', monospace;
|
||||
font-size: 14px;
|
||||
}
|
||||
table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
background: #1a1f3a;
|
||||
border-radius: 12px;
|
||||
overflow: hidden;
|
||||
}
|
||||
th, td {
|
||||
padding: 12px;
|
||||
text-align: left;
|
||||
border-bottom: 1px solid #2d3748;
|
||||
}
|
||||
th {
|
||||
background: #0a0e27;
|
||||
color: #a0aec0;
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
font-size: 11px;
|
||||
}
|
||||
tr:hover { background: #252b4a; }
|
||||
.footer {
|
||||
text-align: center;
|
||||
margin-top: 40px;
|
||||
color: #718096;
|
||||
font-size: 14px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<h1>${this.name}</h1>
|
||||
<p class="subtitle">Generated by FxMathQuant Web - AI-Powered Strategy Generator</p>
|
||||
|
||||
<div class="metrics-grid">
|
||||
<div class="metric-card">
|
||||
<div class="metric-label">Profit Factor</div>
|
||||
<div class="metric-value ${m.profitFactor >= 1.5 ? 'positive' : 'negative'}">
|
||||
${m.profitFactor.toFixed(2)}
|
||||
</div>
|
||||
</div>
|
||||
<div class="metric-card">
|
||||
<div class="metric-label">Win Rate</div>
|
||||
<div class="metric-value neutral">${m.winRate.toFixed(1)}%</div>
|
||||
</div>
|
||||
<div class="metric-card">
|
||||
<div class="metric-label">Total Trades</div>
|
||||
<div class="metric-value neutral">${m.totalTrades}</div>
|
||||
</div>
|
||||
<div class="metric-card">
|
||||
<div class="metric-label">Max Drawdown</div>
|
||||
<div class="metric-value negative">${m.maxDrawdown.toFixed(2)}%</div>
|
||||
</div>
|
||||
<div class="metric-card">
|
||||
<div class="metric-label">Total Profit</div>
|
||||
<div class="metric-value ${m.totalProfit >= 0 ? 'positive' : 'negative'}">
|
||||
$${m.totalProfit.toFixed(2)}
|
||||
</div>
|
||||
</div>
|
||||
<div class="metric-card">
|
||||
<div class="metric-label">Final Balance</div>
|
||||
<div class="metric-value positive">$${m.finalBalance.toFixed(2)}</div>
|
||||
</div>
|
||||
<div class="metric-card">
|
||||
<div class="metric-label">Avg Win</div>
|
||||
<div class="metric-value positive">$${m.avgWin.toFixed(2)}</div>
|
||||
</div>
|
||||
<div class="metric-card">
|
||||
<div class="metric-label">Avg Loss</div>
|
||||
<div class="metric-value negative">$${Math.abs(m.avgLoss).toFixed(2)}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="chart-container">
|
||||
<h2 style="margin-bottom: 20px;">Equity Curve</h2>
|
||||
<canvas id="equityChart"></canvas>
|
||||
</div>
|
||||
|
||||
<div class="rules-section">
|
||||
<h2 style="margin-bottom: 20px;">Strategy Rules</h2>
|
||||
<p style="margin-bottom: 15px; color: #a0aec0;">BUY when ALL of the following conditions are true:</p>
|
||||
${this.generateRulesHTML()}
|
||||
<div style="margin-top: 20px; padding: 15px; background: #0a0e27; border-radius: 6px;">
|
||||
<strong>Parameters:</strong><br>
|
||||
ATR Period: ${this.strategy.atrPeriod} |
|
||||
SL Multiplier: ${this.strategy.slMultiplier.toFixed(2)} |
|
||||
TP Multiplier: ${this.strategy.tpMultiplier.toFixed(2)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
${this.generateTradesTable()}
|
||||
|
||||
<div class="footer">
|
||||
<p>© 2025 FxMathQuant. All rights reserved.</p>
|
||||
<p>This report was generated automatically by AI-powered genetic algorithms.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// Equity Curve Chart
|
||||
const ctx = document.getElementById('equityChart').getContext('2d');
|
||||
new Chart(ctx, {
|
||||
type: 'line',
|
||||
data: {
|
||||
labels: ${JSON.stringify(m.equity.map((_, i) => i))},
|
||||
datasets: [{
|
||||
label: 'Account Balance',
|
||||
data: ${JSON.stringify(m.equity)},
|
||||
borderColor: '#667eea',
|
||||
backgroundColor: 'rgba(102, 126, 234, 0.1)',
|
||||
tension: 0.4,
|
||||
fill: true
|
||||
}]
|
||||
},
|
||||
options: {
|
||||
responsive: true,
|
||||
plugins: {
|
||||
legend: { display: false },
|
||||
tooltip: {
|
||||
backgroundColor: '#1a1f3a',
|
||||
titleColor: '#fff',
|
||||
bodyColor: '#a0aec0',
|
||||
borderColor: '#2d3748',
|
||||
borderWidth: 1
|
||||
}
|
||||
},
|
||||
scales: {
|
||||
y: {
|
||||
beginAtZero: false,
|
||||
grid: { color: '#2d3748' },
|
||||
ticks: { color: '#a0aec0' }
|
||||
},
|
||||
x: {
|
||||
grid: { color: '#2d3748' },
|
||||
ticks: { color: '#a0aec0' }
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>`;
|
||||
}
|
||||
|
||||
generateRulesHTML() {
|
||||
return this.strategy.rules.map((rule, index) => {
|
||||
let ruleText = '';
|
||||
if (rule.type === 'simple') {
|
||||
ruleText = `${rule.left.price.toUpperCase()}[${rule.left.shift}] ${rule.operator} ${rule.right.price.toUpperCase()}[${rule.right.shift}]`;
|
||||
} else {
|
||||
ruleText = `(${rule.left.price1.toUpperCase()}[${rule.left.shift1}] ${rule.left.op} ${rule.left.price2.toUpperCase()}[${rule.left.shift2}]) ${rule.operator} (${rule.right.price.toUpperCase()}[${rule.right.shift}] × ${rule.right.multiplier})`;
|
||||
}
|
||||
return `<div class="rule">${index + 1}. ${ruleText}</div>`;
|
||||
}).join('');
|
||||
}
|
||||
|
||||
generateTradesTable() {
|
||||
const trades = this.strategy.metrics.trades || [];
|
||||
if (trades.length === 0) return '';
|
||||
|
||||
// Show first 50 trades
|
||||
const displayTrades = trades.slice(0, 50);
|
||||
|
||||
return `
|
||||
<div style="background: #1a1f3a; padding: 30px; border-radius: 12px; border: 1px solid #2d3748;">
|
||||
<h2 style="margin-bottom: 20px;">Trade History ${trades.length > 50 ? '(First 50 trades)' : ''}</h2>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>#</th>
|
||||
<th>Type</th>
|
||||
<th>Entry</th>
|
||||
<th>Exit</th>
|
||||
<th>Profit</th>
|
||||
<th>Reason</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
${displayTrades.map((trade, i) => `
|
||||
<tr>
|
||||
<td>${i + 1}</td>
|
||||
<td>${trade.type}</td>
|
||||
<td>${trade.entry.toFixed(5)}</td>
|
||||
<td>${trade.exit.toFixed(5)}</td>
|
||||
<td class="${trade.profit >= 0 ? 'positive' : 'negative'}">
|
||||
$${trade.profit.toFixed(2)}
|
||||
</td>
|
||||
<td>${trade.reason.replace('_', ' ')}</td>
|
||||
</tr>
|
||||
`).join('')}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>`;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
// Rule Parser - Converts strategy rule objects to platform-specific syntax
|
||||
class RuleParser {
|
||||
constructor() { }
|
||||
|
||||
parseRules(rules, platform = 'mql') {
|
||||
return rules.map(rule => this.parseRule(rule, platform));
|
||||
}
|
||||
|
||||
parseRule(rule, platform = 'mql') {
|
||||
if (rule.type === 'simple') {
|
||||
return this.parseSimpleRule(rule, platform);
|
||||
} else {
|
||||
return this.parseArithmeticRule(rule, platform);
|
||||
}
|
||||
}
|
||||
|
||||
parseSimpleRule(rule, platform) {
|
||||
const left = this.getPriceReference(rule.left.price, rule.left.shift, platform);
|
||||
const right = this.getPriceReference(rule.right.price, rule.right.shift, platform);
|
||||
const operator = rule.operator;
|
||||
|
||||
return `${left} ${operator} ${right}`;
|
||||
}
|
||||
|
||||
parseArithmeticRule(rule, platform) {
|
||||
const left1 = this.getPriceReference(rule.left.price1, rule.left.shift1, platform);
|
||||
const left2 = this.getPriceReference(rule.left.price2, rule.left.shift2, platform);
|
||||
const leftOp = rule.left.op;
|
||||
const right = this.getPriceReference(rule.right.price, rule.right.shift, platform);
|
||||
const multiplier = rule.right.multiplier;
|
||||
const operator = rule.operator;
|
||||
|
||||
return `(${left1} ${leftOp} ${left2}) ${operator} (${right} * ${multiplier})`;
|
||||
}
|
||||
|
||||
getPriceReference(priceType, shift, platform) {
|
||||
const price = priceType.charAt(0).toUpperCase() + priceType.slice(1).toLowerCase();
|
||||
|
||||
if (platform === 'mql') {
|
||||
// MQ4 style
|
||||
return `${price}[${shift}]`;
|
||||
} else if (platform === 'mq5') {
|
||||
// MQ5 style
|
||||
return `i${price}(_Symbol, PERIOD_CURRENT, ${shift})`;
|
||||
} else if (platform === 'pine') {
|
||||
// Pine Script style
|
||||
const priceLower = priceType.toLowerCase();
|
||||
return shift === 0 ? priceLower : `${priceLower}[${shift}]`;
|
||||
} else if (platform === 'csharp') {
|
||||
// cTrader C# style
|
||||
return `MarketSeries.${price}.Last(${shift})`;
|
||||
}
|
||||
|
||||
return `${price}[${shift}]`;
|
||||
}
|
||||
}
|
||||
|
||||
// Export for use in other modules
|
||||
window.RuleParser = RuleParser;
|
||||
@@ -0,0 +1,329 @@
|
||||
/**
|
||||
* View detailed strategy information in a modal
|
||||
*/
|
||||
function viewStrategyDetails(index) {
|
||||
console.log('🔍 viewStrategyDetails called with index:', index);
|
||||
console.log('📊 appData.foundStrategies:', appData?.foundStrategies);
|
||||
|
||||
if (!appData || !appData.foundStrategies) {
|
||||
console.error('❌ appData or foundStrategies is undefined!');
|
||||
alert('Error: Strategy data not available');
|
||||
return;
|
||||
}
|
||||
|
||||
if (index < 0 || index >= appData.foundStrategies.length) {
|
||||
console.error('❌ Invalid index:', index, 'Length:', appData.foundStrategies.length);
|
||||
alert('Error: Invalid strategy index');
|
||||
return;
|
||||
}
|
||||
|
||||
const strategy = appData.foundStrategies[index];
|
||||
if (!strategy || !strategy.metrics) {
|
||||
console.error('❌ Invalid strategy at index:', index);
|
||||
alert('Error: Strategy data is corrupted');
|
||||
return;
|
||||
}
|
||||
|
||||
const m = strategy.metrics;
|
||||
|
||||
// Additional validation for required metrics
|
||||
if (!m.profitFactor || !m.winRate || !m.totalTrades) {
|
||||
console.error('❌ Missing required metrics:', m);
|
||||
alert('Error: Strategy metrics are incomplete');
|
||||
return;
|
||||
}
|
||||
|
||||
// Create modal if it doesn't exist
|
||||
let modal = document.getElementById('strategy-modal');
|
||||
if (!modal) {
|
||||
modal = document.createElement('div');
|
||||
modal.id = 'strategy-modal';
|
||||
modal.className = 'modal';
|
||||
document.body.appendChild(modal);
|
||||
}
|
||||
|
||||
const name = `FxMath_${String(index + 1).padStart(3, '0')}_PF${(m.profitFactor || 0).toFixed(2).replace('.', '_')}_WR${Math.round(m.winRate || 0)}`;
|
||||
|
||||
modal.innerHTML = `
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h2>${name}</h2>
|
||||
<button class="modal-close" onclick="closeStrategyModal()">×</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="metrics-grid-detailed">
|
||||
<div class="metric-detailed">
|
||||
<span class="metric-label">Profit Factor</span>
|
||||
<span class="metric-value ${m.profitFactor >= 1.5 ? 'positive' : 'negative'}">${m.profitFactor.toFixed(2)}</span>
|
||||
</div>
|
||||
<div class="metric-detailed">
|
||||
<span class="metric-label">Win Rate</span>
|
||||
<span class="metric-value">${m.winRate.toFixed(1)}%</span>
|
||||
</div>
|
||||
<div class="metric-detailed">
|
||||
<span class="metric-label">Total Trades</span>
|
||||
<span class="metric-value">${m.totalTrades}</span>
|
||||
</div>
|
||||
<div class="metric-detailed">
|
||||
<span class="metric-label">BUY Trades</span>
|
||||
<span class="metric-value" style="color: #48bb78;">${m.buyTrades || 0}</span>
|
||||
</div>
|
||||
<div class="metric-detailed">
|
||||
<span class="metric-label">SELL Trades</span>
|
||||
<span class="metric-value" style="color: #f56565;">${m.sellTrades || 0}</span>
|
||||
</div>
|
||||
<div class="metric-detailed">
|
||||
<span class="metric-label">Winning Trades</span>
|
||||
<span class="metric-value positive">${m.winningTrades || 0}</span>
|
||||
</div>
|
||||
<div class="metric-detailed">
|
||||
<span class="metric-label">Losing Trades</span>
|
||||
<span class="metric-value negative">${m.losingTrades || 0}</span>
|
||||
</div>
|
||||
<div class="metric-detailed">
|
||||
<span class="metric-label">Max Drawdown</span>
|
||||
<span class="metric-value negative">${m.maxDrawdown.toFixed(2)}%</span>
|
||||
</div>
|
||||
<div class="metric-detailed">
|
||||
<span class="metric-label">Total Profit</span>
|
||||
<span class="metric-value ${m.totalProfit >= 0 ? 'positive' : 'negative'}">$${m.totalProfit.toFixed(2)}</span>
|
||||
</div>
|
||||
<div class="metric-detailed">
|
||||
<span class="metric-label">Avg Win</span>
|
||||
<span class="metric-value positive">$${m.avgWin.toFixed(2)}</span>
|
||||
</div>
|
||||
<div class="metric-detailed">
|
||||
<span class="metric-label">Avg Loss</span>
|
||||
<span class="metric-value negative">$${Math.abs(m.avgLoss).toFixed(2)}</span>
|
||||
</div>
|
||||
<div class="metric-detailed">
|
||||
<span class="metric-label">Largest Win</span>
|
||||
<span class="metric-value positive">$${m.largestWin.toFixed(2)}</span>
|
||||
</div>
|
||||
<div class="metric-detailed">
|
||||
<span class="metric-label">Largest Loss</span>
|
||||
<span class="metric-value negative">$${Math.abs(m.largestLoss).toFixed(2)}</span>
|
||||
</div>
|
||||
${m.bestHour && m.worstHour ? `
|
||||
<div class="metric-detailed" style="grid-column: 1 / -1; margin-top: 10px; padding: 10px; background: rgba(66, 153, 225, 0.1); border-radius: 6px;">
|
||||
<span class="metric-label">Best Hour:</span>
|
||||
<span class="metric-value positive">${String(m.bestHour.hour).padStart(2, '0')}:00 ($${m.bestHour.profit.toFixed(2)})</span>
|
||||
<span style="margin: 0 15px;">|</span>
|
||||
<span class="metric-label">Worst Hour:</span>
|
||||
<span class="metric-value negative">${String(m.worstHour.hour).padStart(2, '0')}:00 ($${m.worstHour.profit.toFixed(2)})</span>
|
||||
</div>
|
||||
` : ''}
|
||||
</div>
|
||||
|
||||
<div class="chart-section">
|
||||
<h3>Equity Curve</h3>
|
||||
<canvas id="equity-chart"></canvas>
|
||||
</div>
|
||||
|
||||
${m.hourlyStats ? `
|
||||
<div class="chart-section">
|
||||
<h3>Hourly Performance (24h)</h3>
|
||||
<canvas id="hourly-chart"></canvas>
|
||||
</div>
|
||||
` : ''}
|
||||
|
||||
<div class="rules-section">
|
||||
<h3>Strategy Rules</h3>
|
||||
<p style="margin-bottom: 10px; color: #a0aec0;">BUY when ALL of the following conditions are true:</p>
|
||||
<div class="rules-list">
|
||||
${strategy.rules.map((rule, i) => {
|
||||
let ruleText = '';
|
||||
if (rule.type === 'simple') {
|
||||
ruleText = `${rule.left.price.toUpperCase()}[${rule.left.shift}] ${rule.operator} ${rule.right.price.toUpperCase()}[${rule.right.shift}]`;
|
||||
} else {
|
||||
ruleText = `(${rule.left.price1.toUpperCase()}[${rule.left.shift1}] ${rule.left.op} ${rule.left.price2.toUpperCase()}[${rule.left.shift2}]) ${rule.operator} (${rule.right.price.toUpperCase()}[${rule.right.shift}] × ${rule.right.multiplier})`;
|
||||
}
|
||||
return `<div class="rule-item">${i + 1}. ${ruleText}</div>`;
|
||||
}).join('')}
|
||||
</div>
|
||||
|
||||
<p style="margin: 20px 0 10px 0; color: #a0aec0;">SELL when ALL of the following conditions are true:</p>
|
||||
<div class="rules-list">
|
||||
${strategy.rules.map((rule, i) => {
|
||||
let ruleText = '';
|
||||
// Invert the operator for SELL rules
|
||||
const invertedOp = rule.operator === '>' ? '<=' :
|
||||
rule.operator === '<' ? '>=' :
|
||||
rule.operator === '>=' ? '<' :
|
||||
rule.operator === '<=' ? '>' : rule.operator;
|
||||
|
||||
if (rule.type === 'simple') {
|
||||
ruleText = `${rule.left.price.toUpperCase()}[${rule.left.shift}] ${invertedOp} ${rule.right.price.toUpperCase()}[${rule.right.shift}]`;
|
||||
} else {
|
||||
ruleText = `(${rule.left.price1.toUpperCase()}[${rule.left.shift1}] ${rule.left.op} ${rule.left.price2.toUpperCase()}[${rule.left.shift2}]) ${invertedOp} (${rule.right.price.toUpperCase()}[${rule.right.shift}] × ${rule.right.multiplier})`;
|
||||
}
|
||||
return `<div class="rule-item">${i + 1}. ${ruleText}</div>`;
|
||||
}).join('')}
|
||||
</div>
|
||||
<div class="parameters-box">
|
||||
<strong>Parameters:</strong> ATR Period: ${strategy.atrPeriod} | SL Multiplier: ${strategy.slMultiplier.toFixed(2)} | TP Multiplier: ${strategy.tpMultiplier.toFixed(2)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="trades-section">
|
||||
<h3>Trade Statement</h3>
|
||||
<div class="trades-table-container">
|
||||
<table class="trades-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>#</th>
|
||||
<th>Type</th>
|
||||
<th>Entry</th>
|
||||
<th>Exit</th>
|
||||
<th>Profit</th>
|
||||
<th>Reason</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
${(m.trades && m.trades.length > 0) ? m.trades.map((t, i) => `
|
||||
<tr>
|
||||
<td>${i + 1}</td>
|
||||
<td><span class="badge">${t.type}</span></td>
|
||||
<td>${t.entry.toFixed(5)}</td>
|
||||
<td>${t.exit.toFixed(5)}</td>
|
||||
<td class="${t.profit >= 0 ? 'positive' : 'negative'}">$${t.profit.toFixed(2)}</td>
|
||||
<td>${t.reason.replace('_', ' ')}</td>
|
||||
</tr>
|
||||
`).join('') : '<tr><td colspan="6" style="text-align: center; color: #a0aec0;">No trades recorded</td></tr>'}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="modal-footer-actions">
|
||||
<button class="btn-primary" onclick="downloadStrategy(${index}, 'mq4')">Download MQ4</button>
|
||||
<button class="btn-primary" onclick="downloadStrategy(${index}, 'mq5')">Download MQ5</button>
|
||||
<button class="btn-primary" onclick="downloadStrategy(${index}, 'ctrader')">cTrader</button>
|
||||
<button class="btn-primary" onclick="downloadStrategy(${index}, 'pine')">Pine Script</button>
|
||||
<button class="btn-success" onclick="downloadStrategy(${index}, 'report')">HTML Report</button>
|
||||
<button class="btn-secondary" onclick="downloadStrategy(${index}, 'json')">JSON</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
modal.style.display = 'flex';
|
||||
|
||||
// Draw equity chart
|
||||
setTimeout(() => {
|
||||
const ctx = document.getElementById('equity-chart').getContext('2d');
|
||||
new Chart(ctx, {
|
||||
type: 'line',
|
||||
data: {
|
||||
labels: m.equity.map((_, i) => i),
|
||||
datasets: [{
|
||||
label: 'Account Balance',
|
||||
data: m.equity,
|
||||
borderColor: '#667eea',
|
||||
backgroundColor: 'rgba(102, 126, 234, 0.1)',
|
||||
tension: 0.4,
|
||||
fill: true,
|
||||
pointRadius: 0
|
||||
}]
|
||||
},
|
||||
options: {
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
plugins: {
|
||||
legend: { display: false },
|
||||
tooltip: {
|
||||
backgroundColor: '#1a1f3a',
|
||||
titleColor: '#fff',
|
||||
bodyColor: '#a0aec0',
|
||||
borderColor: '#2d3748',
|
||||
borderWidth: 1
|
||||
}
|
||||
},
|
||||
scales: {
|
||||
y: {
|
||||
beginAtZero: false,
|
||||
grid: { color: '#2d3748' },
|
||||
ticks: { color: '#a0aec0' }
|
||||
},
|
||||
x: {
|
||||
grid: { color: '#2d3748' },
|
||||
ticks: { color: '#a0aec0' }
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Render hourly performance chart if data exists
|
||||
if (m.hourlyStats) {
|
||||
const hourlyCanvas = document.getElementById('hourly-chart');
|
||||
if (hourlyCanvas) {
|
||||
new Chart(hourlyCanvas, {
|
||||
type: 'bar',
|
||||
data: {
|
||||
labels: Array.from({ length: 24 }, (_, i) => `${String(i).padStart(2, '0')}:00`),
|
||||
datasets: [{
|
||||
label: 'Profit/Loss',
|
||||
data: m.hourlyStats.map(h => h.profit),
|
||||
backgroundColor: m.hourlyStats.map(h => h.profit >= 0 ? 'rgba(72, 187, 120, 0.6)' : 'rgba(245, 101, 101, 0.6)'),
|
||||
borderColor: m.hourlyStats.map(h => h.profit >= 0 ? 'rgba(72, 187, 120, 1)' : 'rgba(245, 101, 101, 1)'),
|
||||
borderWidth: 1
|
||||
}]
|
||||
},
|
||||
options: {
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
plugins: {
|
||||
legend: { display: false },
|
||||
tooltip: {
|
||||
callbacks: {
|
||||
label: function (context) {
|
||||
const hour = context.dataIndex;
|
||||
const stats = m.hourlyStats[hour];
|
||||
return [
|
||||
`Profit: $${stats.profit.toFixed(2)}`,
|
||||
`Trades: ${stats.trades}`,
|
||||
`Wins: ${stats.wins} | Losses: ${stats.losses}`
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
scales: {
|
||||
y: {
|
||||
beginAtZero: true,
|
||||
grid: { color: 'rgba(255, 255, 255, 0.1)' },
|
||||
ticks: { color: '#a0aec0' }
|
||||
},
|
||||
x: {
|
||||
grid: { display: false },
|
||||
ticks: { color: '#a0aec0', maxRotation: 45, minRotation: 45 }
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}, 100);
|
||||
}
|
||||
|
||||
/**
|
||||
* Close strategy modal
|
||||
*/
|
||||
function closeStrategyModal() {
|
||||
const modal = document.getElementById('strategy-modal');
|
||||
if (modal) {
|
||||
modal.style.display = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
// Close modal when clicking outside
|
||||
window.onclick = function (event) {
|
||||
const modal = document.getElementById('strategy-modal');
|
||||
if (event.target === modal) {
|
||||
closeStrategyModal();
|
||||
}
|
||||
}
|
||||
|
||||
// Explicitly expose functions to global scope for inline onclick handlers
|
||||
window.viewStrategyDetails = viewStrategyDetails;
|
||||
window.closeStrategyModal = closeStrategyModal;
|
||||
+152
@@ -0,0 +1,152 @@
|
||||
/**
|
||||
* Strategy Class - Represents a trading strategy with rules and parameters
|
||||
*/
|
||||
class Strategy {
|
||||
constructor() {
|
||||
this.rules = [];
|
||||
this.atrPeriod = 20;
|
||||
this.slMultiplier = 2.0;
|
||||
this.tpMultiplier = 3.0;
|
||||
this.closeAtOpposite = false; // Disable to allow independent BUY/SELL signals
|
||||
this.fitness = 0;
|
||||
this.metrics = {};
|
||||
this.id = this.generateId();
|
||||
}
|
||||
|
||||
generateId() {
|
||||
return 'strategy_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate random trading rules
|
||||
*/
|
||||
generateRandomRules(count, shiftRange) {
|
||||
const minRules = count[0] || 3;
|
||||
const maxRules = count[1] || 8;
|
||||
const numRules = Math.floor(Math.random() * (maxRules - minRules + 1)) + minRules;
|
||||
|
||||
for (let i = 0; i < numRules; i++) {
|
||||
this.rules.push(this.generateRule(shiftRange));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a single random rule
|
||||
*/
|
||||
generateRule(shiftRange) {
|
||||
const priceTypes = ['open', 'high', 'low', 'close'];
|
||||
const operators = ['>', '<', '>=', '<='];
|
||||
|
||||
const minShift = shiftRange[0] || 1;
|
||||
const maxShift = shiftRange[1] || 10;
|
||||
|
||||
// 70% simple rules, 30% arithmetic rules
|
||||
if (Math.random() < 0.7) {
|
||||
// Simple rule: CLOSE[1] > OPEN[2]
|
||||
return {
|
||||
type: 'simple',
|
||||
left: {
|
||||
price: priceTypes[Math.floor(Math.random() * 4)],
|
||||
shift: Math.floor(Math.random() * (maxShift - minShift + 1)) + minShift
|
||||
},
|
||||
operator: operators[Math.floor(Math.random() * 4)],
|
||||
right: {
|
||||
price: priceTypes[Math.floor(Math.random() * 4)],
|
||||
shift: Math.floor(Math.random() * (maxShift - minShift + 1)) + minShift
|
||||
}
|
||||
};
|
||||
} else {
|
||||
// Arithmetic rule: (HIGH[1] + LOW[1]) > (CLOSE[2] * 1.01)
|
||||
const arithmeticOps = ['+', '-', '*'];
|
||||
const multipliers = [0.99, 1.01, 1.02, 0.98];
|
||||
|
||||
return {
|
||||
type: 'arithmetic',
|
||||
left: {
|
||||
price1: priceTypes[Math.floor(Math.random() * 4)],
|
||||
shift1: Math.floor(Math.random() * (maxShift - minShift + 1)) + minShift,
|
||||
op: arithmeticOps[Math.floor(Math.random() * 3)],
|
||||
price2: priceTypes[Math.floor(Math.random() * 4)],
|
||||
shift2: Math.floor(Math.random() * (maxShift - minShift + 1)) + minShift
|
||||
},
|
||||
operator: operators[Math.floor(Math.random() * 4)],
|
||||
right: {
|
||||
price: priceTypes[Math.floor(Math.random() * 4)],
|
||||
shift: Math.floor(Math.random() * (maxShift - minShift + 1)) + minShift,
|
||||
multiplier: multipliers[Math.floor(Math.random() * 4)]
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Randomize ATR and SL/TP parameters
|
||||
*/
|
||||
randomizeParameters() {
|
||||
this.atrPeriod = Math.floor(Math.random() * 31) + 10; // 10-40
|
||||
this.slMultiplier = (Math.random() * 3) + 1; // 1.0-4.0
|
||||
this.tpMultiplier = this.slMultiplier + (Math.random() * 5) + 0.5; // SL + 0.5 to 5.5
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a deep copy of the strategy
|
||||
*/
|
||||
copy() {
|
||||
const newStrategy = new Strategy();
|
||||
newStrategy.rules = JSON.parse(JSON.stringify(this.rules));
|
||||
newStrategy.atrPeriod = this.atrPeriod;
|
||||
newStrategy.slMultiplier = this.slMultiplier;
|
||||
newStrategy.tpMultiplier = this.tpMultiplier;
|
||||
newStrategy.closeAtOpposite = this.closeAtOpposite;
|
||||
newStrategy.metrics = this.metrics ? JSON.parse(JSON.stringify(this.metrics)) : {};
|
||||
newStrategy.fitness = this.fitness || 0;
|
||||
return newStrategy;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert rules to human-readable format
|
||||
*/
|
||||
getRulesText() {
|
||||
return this.rules.map((rule, index) => {
|
||||
if (rule.type === 'simple') {
|
||||
return `${index + 1}. ${rule.left.price.toUpperCase()}[${rule.left.shift}] ${rule.operator} ${rule.right.price.toUpperCase()}[${rule.right.shift}]`;
|
||||
} else {
|
||||
return `${index + 1}. (${rule.left.price1.toUpperCase()}[${rule.left.shift1}] ${rule.left.op} ${rule.left.price2.toUpperCase()}[${rule.left.shift2}]) ${rule.operator} (${rule.right.price.toUpperCase()}[${rule.right.shift}] * ${rule.right.multiplier})`;
|
||||
}
|
||||
}).join('\n');
|
||||
}
|
||||
|
||||
/**
|
||||
* Export to JSON
|
||||
*/
|
||||
toJSON() {
|
||||
return {
|
||||
id: this.id,
|
||||
rules: this.rules,
|
||||
parameters: {
|
||||
atr_period: this.atrPeriod,
|
||||
sl_multiplier: this.slMultiplier,
|
||||
tp_multiplier: this.tpMultiplier,
|
||||
close_at_opposite: this.closeAtOpposite
|
||||
},
|
||||
performance: this.metrics,
|
||||
fitness: this.fitness
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Import from JSON
|
||||
*/
|
||||
static fromJSON(json) {
|
||||
const strategy = new Strategy();
|
||||
strategy.id = json.id;
|
||||
strategy.rules = json.rules;
|
||||
strategy.atrPeriod = json.parameters.atr_period;
|
||||
strategy.slMultiplier = json.parameters.sl_multiplier;
|
||||
strategy.tpMultiplier = json.parameters.tp_multiplier;
|
||||
strategy.closeAtOpposite = json.parameters.close_at_opposite || false;
|
||||
strategy.metrics = json.performance || {};
|
||||
strategy.fitness = json.fitness || 0;
|
||||
return strategy;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
// UI Controller - Simple helper functions for UI updates
|
||||
// (Most UI logic is in main.js)
|
||||
+240
@@ -0,0 +1,240 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>FxMathQuant - License Activation</title>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;600;700&display=swap" rel="stylesheet">
|
||||
<style>
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: 'Inter', sans-serif;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.login-container {
|
||||
background: white;
|
||||
border-radius: 20px;
|
||||
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
|
||||
padding: 50px 40px;
|
||||
width: 100%;
|
||||
max-width: 450px;
|
||||
animation: slideIn 0.5s ease-out;
|
||||
}
|
||||
|
||||
@keyframes slideIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(-30px);
|
||||
}
|
||||
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
.logo {
|
||||
text-align: center;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.logo h1 {
|
||||
color: #667eea;
|
||||
font-size: 32px;
|
||||
font-weight: 700;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.logo p {
|
||||
color: #6c757d;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.form-group {
|
||||
margin-bottom: 25px;
|
||||
}
|
||||
|
||||
.form-group label {
|
||||
display: block;
|
||||
color: #333;
|
||||
font-weight: 600;
|
||||
margin-bottom: 8px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.form-group input {
|
||||
width: 100%;
|
||||
padding: 14px 16px;
|
||||
border: 2px solid #e0e0e0;
|
||||
border-radius: 10px;
|
||||
font-size: 15px;
|
||||
transition: all 0.3s;
|
||||
font-family: 'Courier New', monospace;
|
||||
letter-spacing: 1px;
|
||||
}
|
||||
|
||||
.form-group input:focus {
|
||||
outline: none;
|
||||
border-color: #667eea;
|
||||
box-shadow: 0 0 0 4px rgba(102, 126, 234, 0.1);
|
||||
}
|
||||
|
||||
.btn-activate {
|
||||
width: 100%;
|
||||
padding: 16px;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 10px;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.btn-activate:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 10px 25px rgba(102, 126, 234, 0.4);
|
||||
}
|
||||
|
||||
.btn-activate:active {
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
.btn-activate:disabled {
|
||||
background: #ccc;
|
||||
cursor: not-allowed;
|
||||
transform: none;
|
||||
}
|
||||
|
||||
.alert {
|
||||
padding: 14px 16px;
|
||||
border-radius: 10px;
|
||||
margin-bottom: 20px;
|
||||
font-size: 14px;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.alert.show {
|
||||
display: block;
|
||||
animation: fadeIn 0.3s;
|
||||
}
|
||||
|
||||
@keyframes fadeIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
to {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.alert-error {
|
||||
background: #fee;
|
||||
color: #c33;
|
||||
border: 1px solid #fcc;
|
||||
}
|
||||
|
||||
.alert-success {
|
||||
background: #efe;
|
||||
color: #3c3;
|
||||
border: 1px solid #cfc;
|
||||
}
|
||||
|
||||
.alert-info {
|
||||
background: #eef;
|
||||
color: #33c;
|
||||
border: 1px solid #ccf;
|
||||
}
|
||||
|
||||
.spinner {
|
||||
display: inline-block;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
border: 2px solid rgba(255, 255, 255, 0.3);
|
||||
border-radius: 50%;
|
||||
border-top-color: white;
|
||||
animation: spin 0.8s linear infinite;
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
.help-text {
|
||||
text-align: center;
|
||||
margin-top: 20px;
|
||||
color: #6c757d;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.help-text a {
|
||||
color: #667eea;
|
||||
text-decoration: none;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.help-text a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.license-format {
|
||||
font-size: 12px;
|
||||
color: #999;
|
||||
margin-top: 5px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="login-container">
|
||||
<div class="logo">
|
||||
<h1>🔐 FxMathQuant</h1>
|
||||
<p>Strategy Finder & Optimizer</p>
|
||||
</div>
|
||||
|
||||
<div id="alertBox" class="alert"></div>
|
||||
|
||||
<form id="licenseForm">
|
||||
<div class="form-group">
|
||||
<label for="licenseKey">License Key</label>
|
||||
<input type="text" id="licenseKey" name="licenseKey"
|
||||
placeholder="XXXX-XXXX-XXXX-XXXX-XXXX-XXXX-XXXX-XXXX" required autocomplete="off" maxlength="39">
|
||||
<div class="license-format">Format: 32 characters with dashes</div>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="btn-activate" id="activateBtn">
|
||||
Activate License
|
||||
</button>
|
||||
</form>
|
||||
|
||||
<div class="help-text">
|
||||
Need a license? <a href="mailto:fxmathsolution@gmail.com">Contact Support</a>
|
||||
</div>
|
||||
<div class="help-text" style="margin-top: 10px; font-size: 12px;">
|
||||
📧 <a href="mailto:fxmathsolution@gmail.com">fxmathsolution@gmail.com</a> |
|
||||
💬 <a href="https://t.me/FxMath" target="_blank">Telegram</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="js/login.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
+419
@@ -0,0 +1,419 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>FxMathQuant User Manual</title>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet">
|
||||
<style>
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: 'Inter', sans-serif;
|
||||
line-height: 1.6;
|
||||
color: #333;
|
||||
background: #f5f7fa;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 900px;
|
||||
margin: 0 auto;
|
||||
background: white;
|
||||
padding: 40px;
|
||||
border-radius: 10px;
|
||||
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
h1 {
|
||||
color: #667eea;
|
||||
font-size: 2.5em;
|
||||
margin-bottom: 10px;
|
||||
border-bottom: 3px solid #667eea;
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
|
||||
h2 {
|
||||
color: #764ba2;
|
||||
font-size: 1.8em;
|
||||
margin-top: 30px;
|
||||
margin-bottom: 15px;
|
||||
border-left: 4px solid #764ba2;
|
||||
padding-left: 15px;
|
||||
}
|
||||
|
||||
h3 {
|
||||
color: #555;
|
||||
font-size: 1.3em;
|
||||
margin-top: 20px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
p {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
ul,
|
||||
ol {
|
||||
margin-left: 30px;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
li {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
code {
|
||||
background: #f4f4f4;
|
||||
padding: 2px 6px;
|
||||
border-radius: 3px;
|
||||
font-family: 'Courier New', monospace;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
pre {
|
||||
background: #2d2d2d;
|
||||
color: #f8f8f2;
|
||||
padding: 15px;
|
||||
border-radius: 5px;
|
||||
overflow-x: auto;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
th,
|
||||
td {
|
||||
padding: 12px;
|
||||
text-align: left;
|
||||
border-bottom: 1px solid #ddd;
|
||||
}
|
||||
|
||||
th {
|
||||
background: #667eea;
|
||||
color: white;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
tr:hover {
|
||||
background: #f5f5f5;
|
||||
}
|
||||
|
||||
.alert {
|
||||
padding: 15px;
|
||||
border-radius: 5px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.alert-info {
|
||||
background: #e3f2fd;
|
||||
border-left: 4px solid #2196f3;
|
||||
color: #1976d2;
|
||||
}
|
||||
|
||||
.alert-warning {
|
||||
background: #fff3e0;
|
||||
border-left: 4px solid #ff9800;
|
||||
color: #e65100;
|
||||
}
|
||||
|
||||
.alert-success {
|
||||
background: #e8f5e9;
|
||||
border-left: 4px solid #4caf50;
|
||||
color: #2e7d32;
|
||||
}
|
||||
|
||||
.btn {
|
||||
display: inline-block;
|
||||
padding: 10px 20px;
|
||||
background: #667eea;
|
||||
color: white;
|
||||
text-decoration: none;
|
||||
border-radius: 5px;
|
||||
margin: 5px;
|
||||
}
|
||||
|
||||
.btn:hover {
|
||||
background: #5568d3;
|
||||
}
|
||||
|
||||
.toc {
|
||||
background: #f9f9f9;
|
||||
padding: 20px;
|
||||
border-radius: 5px;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.toc a {
|
||||
color: #667eea;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.toc a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.contact-box {
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
color: white;
|
||||
padding: 20px;
|
||||
border-radius: 10px;
|
||||
text-align: center;
|
||||
margin-top: 30px;
|
||||
}
|
||||
|
||||
.contact-box a {
|
||||
color: white;
|
||||
text-decoration: underline;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="container">
|
||||
<h1>📚 FxMathQuant User Manual</h1>
|
||||
<p><strong>AI-Powered Trading Strategy Generator</strong></p>
|
||||
|
||||
<div class="toc">
|
||||
<h3>📋 Table of Contents</h3>
|
||||
<ol>
|
||||
<li><a href="#getting-started">Getting Started</a></li>
|
||||
<li><a href="#license">License Activation</a></li>
|
||||
<li><a href="#export">Exporting Data from MT4/MT5</a></li>
|
||||
<li><a href="#upload">Uploading Data</a></li>
|
||||
<li><a href="#config">Configuring Strategy Generation</a></li>
|
||||
<li><a href="#generate">Generating Strategies</a></li>
|
||||
<li><a href="#results">Viewing Results</a></li>
|
||||
<li><a href="#download">Downloading Strategies</a></li>
|
||||
<li><a href="#troubleshoot">Troubleshooting</a></li>
|
||||
<li><a href="#support">Contact Support</a></li>
|
||||
</ol>
|
||||
</div>
|
||||
|
||||
<h2 id="getting-started">🚀 Getting Started</h2>
|
||||
<h3>What is FxMathQuant?</h3>
|
||||
<p>FxMathQuant is an AI-powered trading strategy generator that uses genetic algorithms to discover profitable
|
||||
trading strategies from your historical price data.</p>
|
||||
|
||||
<h3>System Requirements</h3>
|
||||
<ul>
|
||||
<li><strong>Web Browser</strong>: Chrome, Firefox, Safari, or Edge (latest version)</li>
|
||||
<li><strong>MetaTrader</strong>: MT4 or MT5 (for data export)</li>
|
||||
<li><strong>License Key</strong>: Required for access</li>
|
||||
</ul>
|
||||
|
||||
<h2 id="license">🔑 License Activation</h2>
|
||||
<h3>Step 1: Obtain License Key</h3>
|
||||
<p>Purchase a license from:</p>
|
||||
<ul>
|
||||
<li><strong>Email</strong>: <a href="mailto:fxmathsolution@gmail.com">fxmathsolution@gmail.com</a></li>
|
||||
<li><strong>Telegram</strong>: <a href="https://t.me/FxMath" target="_blank">https://t.me/FxMath</a></li>
|
||||
</ul>
|
||||
|
||||
<h3>Step 2: Activate License</h3>
|
||||
<ol>
|
||||
<li>Open the application in your web browser</li>
|
||||
<li>Enter your license key in the format: <code>XXXX-XXXX-XXXX-XXXX-XXXX-XXXX-XXXX-XXXX</code></li>
|
||||
<li>Click "Activate License"</li>
|
||||
<li>Wait for validation (requires internet connection)</li>
|
||||
<li>You'll be redirected to the main application</li>
|
||||
</ol>
|
||||
|
||||
<div class="alert alert-info">
|
||||
<strong>Session Duration:</strong> Your session remains active until you logout or clear browser data. There
|
||||
is no automatic logout.
|
||||
</div>
|
||||
|
||||
<h2 id="export">📊 Exporting Data from MT4/MT5</h2>
|
||||
<h3>Download Data Provider EA</h3>
|
||||
<p>Download the EA from the application:</p>
|
||||
<ul>
|
||||
<li><strong>MT4</strong>: <code>FxMathQuant_DataExporter_MT4.ex4</code></li>
|
||||
<li><strong>MT5</strong>: <code>FxMathQuant_DataExporter_MT5.ex5</code></li>
|
||||
</ul>
|
||||
|
||||
<h3>Export Historical Data</h3>
|
||||
<ol>
|
||||
<li>Open a chart (e.g., XAUUSD H1)</li>
|
||||
<li>Drag the EA onto the chart</li>
|
||||
<li>Configure settings:
|
||||
<ul>
|
||||
<li><strong>Bars to Export</strong>: 10,000 (recommended)</li>
|
||||
<li><strong>Remove Suffix</strong>: true</li>
|
||||
<li><strong>Export Folder</strong>: FxMathQuant</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>Click "Export to CSV" button</li>
|
||||
<li>Wait for completion message</li>
|
||||
<li>Find your file: <code>MQL4/Files/FxMathQuant/XAUUSD_H1.csv</code></li>
|
||||
</ol>
|
||||
|
||||
<h3>Recommended Data</h3>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Timeframe</th>
|
||||
<th>Bars</th>
|
||||
<th>Use Case</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>M15</td>
|
||||
<td>10,000</td>
|
||||
<td>Scalping strategies</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>H1</td>
|
||||
<td>10,000</td>
|
||||
<td>Intraday trading</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>H4</td>
|
||||
<td>5,000</td>
|
||||
<td>Swing trading</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>D1</td>
|
||||
<td>3,000</td>
|
||||
<td>Position trading</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<h2 id="upload">📤 Uploading Data</h2>
|
||||
<ol>
|
||||
<li>Click "Browse" or drag-and-drop your CSV file</li>
|
||||
<li>Select the file exported from MT4/MT5</li>
|
||||
<li>Wait for parsing (10,000 bars ≈ 2-3 seconds)</li>
|
||||
<li>Confirmation: "Data loaded successfully: X bars"</li>
|
||||
</ol>
|
||||
|
||||
<h2 id="config">⚙️ Configuring Strategy Generation</h2>
|
||||
<h3>Genetic Algorithm Settings</h3>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Parameter</th>
|
||||
<th>Default</th>
|
||||
<th>Range</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>Population Size</td>
|
||||
<td>100</td>
|
||||
<td>50-500</td>
|
||||
<td>Number of strategies per generation</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Generations</td>
|
||||
<td>50</td>
|
||||
<td>10-200</td>
|
||||
<td>Number of evolution cycles</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Mutation Rate</td>
|
||||
<td>0.1</td>
|
||||
<td>0.01-0.5</td>
|
||||
<td>Probability of random changes</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Crossover Rate</td>
|
||||
<td>0.7</td>
|
||||
<td>0.3-0.9</td>
|
||||
<td>Probability of combining strategies</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<h2 id="generate">🎯 Generating Strategies</h2>
|
||||
<ol>
|
||||
<li>Review your settings</li>
|
||||
<li>Click "Start Generation"</li>
|
||||
<li>Monitor progress (current generation, strategies found, best fitness)</li>
|
||||
<li>Wait for completion (30 seconds to 5 minutes)</li>
|
||||
</ol>
|
||||
|
||||
<h2 id="results">📈 Viewing Results</h2>
|
||||
<p>Each strategy shows:</p>
|
||||
<ul>
|
||||
<li><strong>Profit Factor</strong>: Ratio of gross profit to gross loss</li>
|
||||
<li><strong>Win Rate</strong>: Percentage of winning trades</li>
|
||||
<li><strong>Total Trades</strong>: Number of trades executed</li>
|
||||
<li><strong>Net Profit</strong>: Total profit in currency</li>
|
||||
<li><strong>Max Drawdown</strong>: Largest equity drop</li>
|
||||
</ul>
|
||||
|
||||
<p>Click "View Details" to see:</p>
|
||||
<ul>
|
||||
<li>Performance Metrics</li>
|
||||
<li>BUY/SELL Rules</li>
|
||||
<li>Equity Curve Chart</li>
|
||||
<li>Hourly Performance</li>
|
||||
<li>Complete Trade Statement</li>
|
||||
</ul>
|
||||
|
||||
<h2 id="download">💾 Downloading Strategies</h2>
|
||||
<h3>Available Formats</h3>
|
||||
<ul>
|
||||
<li><strong>MetaTrader 4 (.mq4)</strong> - For MT4 platform</li>
|
||||
<li><strong>MetaTrader 5 (.mq5)</strong> - For MT5 platform</li>
|
||||
<li><strong>cTrader (.cs)</strong> - For cTrader platform</li>
|
||||
<li><strong>TradingView (Pine Script)</strong> - For TradingView</li>
|
||||
<li><strong>HTML Report</strong> - Comprehensive performance report</li>
|
||||
<li><strong>JSON Data</strong> - Raw strategy data</li>
|
||||
</ul>
|
||||
|
||||
<h2 id="troubleshoot">🐛 Troubleshooting</h2>
|
||||
|
||||
<h3>CSV Upload Issues</h3>
|
||||
<div class="alert alert-warning">
|
||||
<strong>Error:</strong> "CSV must contain: time, open, high, low, close columns"<br>
|
||||
<strong>Solution:</strong> Ensure CSV has required columns. Column names can be lowercase or capitalized.
|
||||
</div>
|
||||
|
||||
<h3>License Issues</h3>
|
||||
<div class="alert alert-warning">
|
||||
<strong>Error:</strong> "Invalid license key"<br>
|
||||
<strong>Solution:</strong> Check key format (32 characters with dashes). Copy-paste from email.
|
||||
</div>
|
||||
|
||||
<h3>Generation Issues</h3>
|
||||
<div class="alert alert-warning">
|
||||
<strong>No strategies found</strong><br>
|
||||
<strong>Solution:</strong> Relax criteria (lower Min Profit Factor, Win Rate). Increase population size and
|
||||
generations.
|
||||
</div>
|
||||
|
||||
<h2 id="support">📞 Contact Support</h2>
|
||||
|
||||
<div class="contact-box">
|
||||
<h3>Get Help</h3>
|
||||
<p><strong>📧 Email:</strong> <a href="mailto:fxmathsolution@gmail.com">fxmathsolution@gmail.com</a></p>
|
||||
<p><strong>💬 Telegram:</strong> <a href="https://t.me/FxMath" target="_blank">https://t.me/FxMath</a></p>
|
||||
<p style="margin-top: 15px; font-size: 0.9em;">Response time: 24-48 hours via email, faster on Telegram</p>
|
||||
</div>
|
||||
|
||||
<div class="alert alert-info" style="margin-top: 30px;">
|
||||
<strong>⚠️ Disclaimer:</strong> Past performance does not guarantee future results. Always test strategies
|
||||
on demo account first. Use proper risk management.
|
||||
</div>
|
||||
|
||||
<p style="text-align: center; margin-top: 30px; color: #999; font-size: 0.9em;">
|
||||
© 2025 FxMath Solution. All rights reserved.
|
||||
</p>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
Reference in New Issue
Block a user