@@ -0,0 +1,620 @@
|
||||
//+------------------------------------------------------------------+
|
||||
//| BTCUSD_M1_EA.mq5 |
|
||||
//| Copyright 2025, MetaQuotes Ltd. |
|
||||
//| https://www.mql5.com |
|
||||
//+------------------------------------------------------------------+
|
||||
#property copyright "Copyright 2025, MetaQuotes Ltd."
|
||||
#property link "https://www.mql5.com"
|
||||
#property version "1.00"
|
||||
#property description "Expert Advisor using ONNX model for BTCUSD 1-minute price prediction"
|
||||
#property description "Based on: https://www.mql5.com/en/docs/onnx/onnx_test"
|
||||
|
||||
#include <Trade\Trade.mqh>
|
||||
|
||||
//--- Resource: Embed ONNX model in EA
|
||||
// Based on: https://www.mql5.com/en/docs/onnx/onnx_test
|
||||
// Path is relative to MQL5 directory (not starting with \\Files\\)
|
||||
#resource "Files\\BTCUSD_M1_model.onnx" as uchar ExtModel[]
|
||||
|
||||
//--- Input parameters
|
||||
input group "ONNX Model Settings"
|
||||
input string InpModelPath = ""; // ONNX Model Path (leave empty to use embedded resource)
|
||||
input int InpLookback = 60; // Lookback Period (bars)
|
||||
input bool InpUsePrediction = true; // Use Model Prediction
|
||||
|
||||
input group "Trading Settings"
|
||||
input double InpLotSize = 0.01; // Lot Size
|
||||
input int InpMagicNumber = 123456; // Magic Number
|
||||
input int InpSlippage = 3; // Slippage (points)
|
||||
input bool InpUsePredictedSLTP = true; // Use Predicted SL/TP (based on prediction & volatility)
|
||||
input int InpStopLoss = 50; // Stop Loss (pips) - used if InpUsePredictedSLTP=false
|
||||
input int InpTakeProfit = 100; // Take Profit (pips) - used if InpUsePredictedSLTP=false
|
||||
input double InpSLMultiplier = 1.5; // SL Multiplier (ATR-based, e.g., 1.5 = 1.5x ATR)
|
||||
input double InpTPMultiplier = 2.0; // TP Multiplier (ATR-based, e.g., 2.0 = 2x ATR)
|
||||
input double InpMinSLATR = 0.5; // Minimum SL (ATR multiplier)
|
||||
input double InpMinTPATR = 1.0; // Minimum TP (ATR multiplier)
|
||||
|
||||
input group "Prediction Settings"
|
||||
input double InpPredictionThreshold = 0.00005; // Min Prediction Change (0.005% as decimal, e.g., 0.00005 = 0.005%)
|
||||
input bool InpUseConfidence = true; // Use Confidence Filter
|
||||
input double InpMinConfidence = 0.1; // Minimum Confidence (0.1 = 10%)
|
||||
|
||||
//--- Global variables
|
||||
CTrade trade;
|
||||
long onnx_handle = INVALID_HANDLE;
|
||||
datetime last_bar_time = 0;
|
||||
double last_prediction = 0.0;
|
||||
double last_confidence = 0.0;
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Expert initialization function |
|
||||
//+------------------------------------------------------------------+
|
||||
int OnInit()
|
||||
{
|
||||
// Set trade parameters
|
||||
trade.SetExpertMagicNumber(InpMagicNumber);
|
||||
trade.SetDeviationInPoints(InpSlippage);
|
||||
trade.SetTypeFilling(ORDER_FILLING_FOK);
|
||||
|
||||
// Check symbol
|
||||
if(_Symbol != "BTCUSD" && _Symbol != "BTCUSD#")
|
||||
{
|
||||
Print("WARNING: This EA is designed for BTCUSD. Current symbol: ", _Symbol);
|
||||
}
|
||||
|
||||
// Check timeframe
|
||||
if(_Period != PERIOD_M1)
|
||||
{
|
||||
Print("WARNING: This EA is designed for M1 timeframe. Current timeframe: ", EnumToString(_Period));
|
||||
}
|
||||
|
||||
// Load ONNX model
|
||||
// Based on: https://www.mql5.com/en/docs/onnx/onnx_test
|
||||
Print("Loading ONNX model from embedded resource...");
|
||||
|
||||
// Create model from resource buffer
|
||||
onnx_handle = OnnxCreateFromBuffer(ExtModel, ONNX_DEBUG_LOGS);
|
||||
|
||||
if(onnx_handle == INVALID_HANDLE)
|
||||
{
|
||||
int error = GetLastError();
|
||||
Print("ERROR: Failed to create ONNX model from resource. Error: ", error);
|
||||
Print("Make sure the model file exists at: MQL5\\Files\\BTCUSD_M1_model.onnx");
|
||||
Print("Then recompile the EA to embed it as a resource.");
|
||||
return(INIT_FAILED);
|
||||
}
|
||||
|
||||
// Set input shape - per MQL5 documentation
|
||||
const long ExtInputShape[] = {1, InpLookback, 13}; // batch=1, lookback bars, 13 features
|
||||
if(!OnnxSetInputShape(onnx_handle, 0, ExtInputShape))
|
||||
{
|
||||
Print("OnnxSetInputShape failed, error ", GetLastError());
|
||||
OnnxRelease(onnx_handle);
|
||||
return(INIT_FAILED);
|
||||
}
|
||||
|
||||
// Set output shape - per MQL5 documentation
|
||||
const long ExtOutputShape[] = {1, 1}; // batch=1, single output value
|
||||
if(!OnnxSetOutputShape(onnx_handle, 0, ExtOutputShape))
|
||||
{
|
||||
Print("OnnxSetOutputShape failed, error ", GetLastError());
|
||||
OnnxRelease(onnx_handle);
|
||||
return(INIT_FAILED);
|
||||
}
|
||||
|
||||
// Get model info
|
||||
long input_count = OnnxGetInputCount(onnx_handle);
|
||||
long output_count = OnnxGetOutputCount(onnx_handle);
|
||||
|
||||
Print("ONNX Model loaded successfully");
|
||||
Print(" Inputs: ", input_count);
|
||||
Print(" Outputs: ", output_count);
|
||||
|
||||
if(input_count > 0)
|
||||
{
|
||||
string input_name = OnnxGetInputName(onnx_handle, 0);
|
||||
Print(" Input name: ", input_name);
|
||||
}
|
||||
|
||||
if(output_count > 0)
|
||||
{
|
||||
string output_name = OnnxGetOutputName(onnx_handle, 0);
|
||||
Print(" Output name: ", output_name);
|
||||
}
|
||||
|
||||
return(INIT_SUCCEEDED);
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Expert deinitialization function |
|
||||
//+------------------------------------------------------------------+
|
||||
void OnDeinit(const int reason)
|
||||
{
|
||||
// Release ONNX model
|
||||
if(onnx_handle != INVALID_HANDLE)
|
||||
{
|
||||
OnnxRelease(onnx_handle);
|
||||
Print("ONNX model released");
|
||||
}
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Expert tick function |
|
||||
//+------------------------------------------------------------------+
|
||||
void OnTick()
|
||||
{
|
||||
// Check if new bar
|
||||
datetime current_bar_time = iTime(_Symbol, PERIOD_CURRENT, 0);
|
||||
if(current_bar_time == last_bar_time)
|
||||
{
|
||||
return; // Still the same bar
|
||||
}
|
||||
last_bar_time = current_bar_time;
|
||||
|
||||
// Check if we should use prediction
|
||||
if(!InpUsePrediction)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Prepare input data
|
||||
float input_data[];
|
||||
if(!PrepareInputData(input_data))
|
||||
{
|
||||
Print("ERROR: Failed to prepare input data");
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if input data is valid
|
||||
if(ArraySize(input_data) != InpLookback * 13)
|
||||
{
|
||||
Print("ERROR: Input data size mismatch. Expected: ", InpLookback * 13, ", Got: ", ArraySize(input_data));
|
||||
return;
|
||||
}
|
||||
|
||||
// Convert flat array to matrixf for OnnxRun
|
||||
// Shape: [lookback, features] = [60, 13] - batch dimension is added automatically
|
||||
matrixf input_matrix;
|
||||
input_matrix.Resize(InpLookback, 13);
|
||||
|
||||
// Fill matrix from flat array
|
||||
int idx = 0;
|
||||
for(int i = 0; i < InpLookback; i++)
|
||||
{
|
||||
for(int j = 0; j < 13; j++)
|
||||
{
|
||||
if(idx >= ArraySize(input_data))
|
||||
{
|
||||
Print("ERROR: Index out of bounds when filling matrix. idx=", idx, ", array size=", ArraySize(input_data));
|
||||
return;
|
||||
}
|
||||
input_matrix[i][j] = input_data[idx++];
|
||||
}
|
||||
}
|
||||
|
||||
// Verify matrix is not empty
|
||||
if(input_matrix.Rows() == 0 || input_matrix.Cols() == 0)
|
||||
{
|
||||
Print("ERROR: Input matrix is empty. Rows: ", input_matrix.Rows(), ", Cols: ", input_matrix.Cols());
|
||||
return;
|
||||
}
|
||||
|
||||
// Run ONNX model - use matrixf and vectorf per MQL5 documentation
|
||||
vectorf output_vector(1);
|
||||
if(!RunONNXModel(input_matrix, output_vector))
|
||||
{
|
||||
Print("ERROR: Failed to run ONNX model");
|
||||
return;
|
||||
}
|
||||
|
||||
// Get prediction
|
||||
if(output_vector.Size() == 0)
|
||||
{
|
||||
Print("ERROR: Empty output from ONNX model");
|
||||
return;
|
||||
}
|
||||
|
||||
// Model now predicts price change percentage directly (e.g., -0.003 = -0.3%)
|
||||
double predicted_change_pct = output_vector[0];
|
||||
double current_price = SymbolInfoDouble(_Symbol, SYMBOL_BID);
|
||||
|
||||
// Check if prediction is percentage (between -1 and 1) or absolute price (old format)
|
||||
double price_change_pct;
|
||||
double predicted_price;
|
||||
|
||||
if(MathAbs(predicted_change_pct) < 1.0)
|
||||
{
|
||||
// New format: percentage (e.g., -0.003 = -0.3%)
|
||||
price_change_pct = predicted_change_pct * 100.0; // Convert to percentage
|
||||
predicted_price = current_price * (1.0 + predicted_change_pct); // Calculate predicted price
|
||||
}
|
||||
else
|
||||
{
|
||||
// Old format: absolute price
|
||||
predicted_price = predicted_change_pct;
|
||||
double price_change = predicted_price - current_price;
|
||||
price_change_pct = (price_change / current_price) * 100.0;
|
||||
}
|
||||
|
||||
// Calculate confidence (for percentage predictions: 0.001 = 0.1% = 10% confidence)
|
||||
double confidence;
|
||||
if(MathAbs(price_change_pct) < 1.0)
|
||||
{
|
||||
// It's a decimal percentage (e.g., 0.001 = 0.1%)
|
||||
confidence = MathMin(MathAbs(predicted_change_pct) / 0.01, 1.0); // 0.01 = 1% = 100% confidence
|
||||
}
|
||||
else
|
||||
{
|
||||
// It's already in percentage form
|
||||
confidence = MathMin(MathAbs(price_change_pct) / 1.0, 1.0);
|
||||
}
|
||||
|
||||
last_prediction = predicted_price;
|
||||
last_confidence = confidence;
|
||||
|
||||
// Calculate ATR for dynamic SL/TP
|
||||
double atr_value = 0.0;
|
||||
double atr_array[];
|
||||
ArraySetAsSeries(atr_array, true);
|
||||
int atr_handle = iATR(_Symbol, PERIOD_CURRENT, 14);
|
||||
if(atr_handle != INVALID_HANDLE)
|
||||
{
|
||||
if(CopyBuffer(atr_handle, 0, 0, 1, atr_array) > 0)
|
||||
{
|
||||
atr_value = atr_array[0];
|
||||
}
|
||||
IndicatorRelease(atr_handle);
|
||||
}
|
||||
|
||||
// Calculate predicted SL/TP based on prediction, confidence, and volatility
|
||||
double predicted_sl = 0.0;
|
||||
double predicted_tp = 0.0;
|
||||
if(InpUsePredictedSLTP && atr_value > 0)
|
||||
{
|
||||
// Calculate SL/TP based on ATR, prediction, and confidence
|
||||
double predicted_move = MathAbs(predicted_price - current_price);
|
||||
|
||||
// SL: Based on ATR and confidence
|
||||
// Higher confidence = tighter SL, lower confidence = wider SL
|
||||
double sl_atr_mult = InpSLMultiplier / MathMax(confidence, 0.1);
|
||||
sl_atr_mult = MathMax(sl_atr_mult, InpMinSLATR);
|
||||
predicted_sl = atr_value * sl_atr_mult;
|
||||
|
||||
// TP: Use a fraction of predicted move (not the full move)
|
||||
// Take 30-50% of predicted move as TP, but ensure minimum
|
||||
double tp_fraction = 0.3 + (confidence * 0.2); // 30-50% based on confidence
|
||||
double tp_from_prediction = predicted_move * tp_fraction;
|
||||
|
||||
// Also calculate TP from ATR multiplier
|
||||
double tp_from_atr = atr_value * InpTPMultiplier;
|
||||
|
||||
// Use the smaller of the two (more conservative)
|
||||
predicted_tp = MathMin(tp_from_prediction, tp_from_atr);
|
||||
predicted_tp = MathMax(predicted_tp, atr_value * InpMinTPATR); // Minimum TP
|
||||
|
||||
// Ensure TP is at least 1.5x SL for risk/reward
|
||||
if(predicted_tp < predicted_sl * 1.5)
|
||||
{
|
||||
predicted_tp = predicted_sl * 1.5;
|
||||
}
|
||||
|
||||
// Cap TP at maximum 80% of predicted move (don't be too greedy)
|
||||
double max_tp = predicted_move * 0.8;
|
||||
if(predicted_tp > max_tp)
|
||||
{
|
||||
predicted_tp = max_tp;
|
||||
}
|
||||
}
|
||||
|
||||
// Log prediction
|
||||
Print("Prediction: Current=", current_price,
|
||||
" Predicted Change=", price_change_pct, "%",
|
||||
" Predicted Price=", predicted_price,
|
||||
" Confidence=", confidence,
|
||||
" ATR=", atr_value);
|
||||
if(InpUsePredictedSLTP && predicted_sl > 0 && predicted_tp > 0)
|
||||
{
|
||||
Print(" Predicted SL=", predicted_sl, " (", predicted_sl/current_price*100, "%)",
|
||||
" Predicted TP=", predicted_tp, " (", predicted_tp/current_price*100, "%)");
|
||||
}
|
||||
|
||||
// Check if we should trade
|
||||
if(!InpUseConfidence || confidence >= InpMinConfidence)
|
||||
{
|
||||
// Check if prediction is significant
|
||||
// price_change_pct is in percentage (e.g., 5.72 = 5.72%)
|
||||
// InpPredictionThreshold is in decimal (e.g., 0.00005 = 0.005%)
|
||||
// Convert threshold to percentage for comparison
|
||||
double threshold_pct = InpPredictionThreshold * 100.0;
|
||||
double abs_change_pct = MathAbs(price_change_pct); // Already in percentage
|
||||
|
||||
Print("Trade Check: Change=", price_change_pct, "% Threshold=", threshold_pct, "% Confidence=", confidence);
|
||||
|
||||
if(abs_change_pct >= threshold_pct)
|
||||
{
|
||||
// Check existing position
|
||||
if(PositionSelect(_Symbol))
|
||||
{
|
||||
// Manage existing position
|
||||
ManagePosition(predicted_price, price_change_pct);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Open new position based on prediction
|
||||
if(price_change_pct > threshold_pct)
|
||||
{
|
||||
Print(">>> Opening BUY position: Change=", price_change_pct, "% Threshold=", threshold_pct, "%");
|
||||
OpenBuyPosition(predicted_sl, predicted_tp);
|
||||
}
|
||||
else if(price_change_pct < -threshold_pct)
|
||||
{
|
||||
Print(">>> Opening SELL position: Change=", price_change_pct, "% Threshold=", threshold_pct, "%");
|
||||
OpenSellPosition(predicted_sl, predicted_tp);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Print("Prediction below threshold: Change=", price_change_pct, "% < Threshold=", threshold_pct, "%");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Print("Confidence too low: ", confidence, " < ", InpMinConfidence);
|
||||
}
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Prepare input data for ONNX model |
|
||||
//+------------------------------------------------------------------+
|
||||
bool PrepareInputData(float &input_array[])
|
||||
{
|
||||
int lookback = InpLookback;
|
||||
int features = 13; // OHLC(4) + volume(1) + RSI(1) + EMA20(1) + EMA50(1) + ATR(1) + price_change(1) + high_low_ratio(1) + volume_ma(1) + volume_ratio(1) = 13
|
||||
|
||||
ArrayResize(input_array, lookback * features);
|
||||
ArrayInitialize(input_array, 0.0);
|
||||
|
||||
// Get historical data
|
||||
double open[], high[], low[], close[];
|
||||
long volume[]; // CopyTickVolume requires long[] not double[]
|
||||
ArraySetAsSeries(open, true);
|
||||
ArraySetAsSeries(high, true);
|
||||
ArraySetAsSeries(low, true);
|
||||
ArraySetAsSeries(close, true);
|
||||
ArraySetAsSeries(volume, true);
|
||||
|
||||
int copied_open = CopyOpen(_Symbol, PERIOD_CURRENT, 0, lookback + 50, open);
|
||||
if(copied_open < lookback)
|
||||
{
|
||||
Print("ERROR: CopyOpen failed. Got ", copied_open, " bars, need ", lookback);
|
||||
return false;
|
||||
}
|
||||
int copied_high = CopyHigh(_Symbol, PERIOD_CURRENT, 0, lookback + 50, high);
|
||||
if(copied_high < lookback)
|
||||
{
|
||||
Print("ERROR: CopyHigh failed. Got ", copied_high, " bars, need ", lookback);
|
||||
return false;
|
||||
}
|
||||
int copied_low = CopyLow(_Symbol, PERIOD_CURRENT, 0, lookback + 50, low);
|
||||
if(copied_low < lookback)
|
||||
{
|
||||
Print("ERROR: CopyLow failed. Got ", copied_low, " bars, need ", lookback);
|
||||
return false;
|
||||
}
|
||||
int copied_close = CopyClose(_Symbol, PERIOD_CURRENT, 0, lookback + 50, close);
|
||||
if(copied_close < lookback)
|
||||
{
|
||||
Print("ERROR: CopyClose failed. Got ", copied_close, " bars, need ", lookback);
|
||||
return false;
|
||||
}
|
||||
int copied_volume = CopyTickVolume(_Symbol, PERIOD_CURRENT, 0, lookback + 50, volume);
|
||||
if(copied_volume < lookback)
|
||||
{
|
||||
Print("ERROR: CopyTickVolume failed. Got ", copied_volume, " bars, need ", lookback);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Calculate indicators
|
||||
double rsi[], ema20[], ema50[], atr[];
|
||||
ArraySetAsSeries(rsi, true);
|
||||
ArraySetAsSeries(ema20, true);
|
||||
ArraySetAsSeries(ema50, true);
|
||||
ArraySetAsSeries(atr, true);
|
||||
|
||||
// Calculate RSI
|
||||
int rsi_handle = iRSI(_Symbol, PERIOD_CURRENT, 14, PRICE_CLOSE);
|
||||
if(rsi_handle == INVALID_HANDLE) return false;
|
||||
if(CopyBuffer(rsi_handle, 0, 0, lookback + 50, rsi) < lookback)
|
||||
{
|
||||
IndicatorRelease(rsi_handle);
|
||||
return false;
|
||||
}
|
||||
IndicatorRelease(rsi_handle);
|
||||
|
||||
// Calculate EMAs
|
||||
int ema20_handle = iMA(_Symbol, PERIOD_CURRENT, 20, 0, MODE_EMA, PRICE_CLOSE);
|
||||
int ema50_handle = iMA(_Symbol, PERIOD_CURRENT, 50, 0, MODE_EMA, PRICE_CLOSE);
|
||||
if(ema20_handle == INVALID_HANDLE || ema50_handle == INVALID_HANDLE) return false;
|
||||
|
||||
if(CopyBuffer(ema20_handle, 0, 0, lookback + 50, ema20) < lookback ||
|
||||
CopyBuffer(ema50_handle, 0, 0, lookback + 50, ema50) < lookback)
|
||||
{
|
||||
IndicatorRelease(ema20_handle);
|
||||
IndicatorRelease(ema50_handle);
|
||||
return false;
|
||||
}
|
||||
IndicatorRelease(ema20_handle);
|
||||
IndicatorRelease(ema50_handle);
|
||||
|
||||
// Calculate ATR
|
||||
int atr_handle = iATR(_Symbol, PERIOD_CURRENT, 14);
|
||||
if(atr_handle == INVALID_HANDLE) return false;
|
||||
if(CopyBuffer(atr_handle, 0, 0, lookback + 50, atr) < lookback)
|
||||
{
|
||||
IndicatorRelease(atr_handle);
|
||||
return false;
|
||||
}
|
||||
IndicatorRelease(atr_handle);
|
||||
|
||||
// Calculate volume MA for normalization
|
||||
double volume_ma[];
|
||||
ArraySetAsSeries(volume_ma, true);
|
||||
ArrayResize(volume_ma, lookback);
|
||||
ArrayInitialize(volume_ma, 0.0);
|
||||
|
||||
// Calculate volume MA (20-period rolling average)
|
||||
for(int j = 0; j < lookback; j++)
|
||||
{
|
||||
double sum = 0.0;
|
||||
int count = 0;
|
||||
for(int k = j; k < j + 20 && k < ArraySize(volume); k++)
|
||||
{
|
||||
sum += (double)volume[k];
|
||||
count++;
|
||||
}
|
||||
volume_ma[j] = count > 0 ? sum / count : (double)volume[j];
|
||||
}
|
||||
|
||||
// Prepare features - MUST match Python training exactly (13 features)
|
||||
int idx = 0;
|
||||
for(int i = 0; i < lookback; i++)
|
||||
{
|
||||
// Feature 1-4: OHLC
|
||||
input_array[idx++] = (float)open[i];
|
||||
input_array[idx++] = (float)high[i];
|
||||
input_array[idx++] = (float)low[i];
|
||||
input_array[idx++] = (float)close[i];
|
||||
|
||||
// Feature 5: Volume (normalized by 1,000,000)
|
||||
input_array[idx++] = (float)((double)volume[i] / 1000000.0);
|
||||
|
||||
// Feature 6: RSI (normalized by 100)
|
||||
input_array[idx++] = (float)(rsi[i] / 100.0);
|
||||
|
||||
// Feature 7: EMA20 normalized difference
|
||||
input_array[idx++] = (float)((ema20[i] - close[i]) / close[i]);
|
||||
|
||||
// Feature 8: EMA50 normalized difference
|
||||
input_array[idx++] = (float)((ema50[i] - close[i]) / close[i]);
|
||||
|
||||
// Feature 9: ATR normalized
|
||||
input_array[idx++] = (float)(atr[i] / close[i]);
|
||||
|
||||
// Feature 10: Price change (percentage)
|
||||
double price_change = i > 0 ? (close[i] - close[i+1]) / close[i+1] : 0.0;
|
||||
input_array[idx++] = (float)price_change;
|
||||
|
||||
// Feature 11: High/Low ratio
|
||||
input_array[idx++] = (float)(high[i] / low[i]);
|
||||
|
||||
// Feature 12: Volume MA (normalized by 1,000,000)
|
||||
input_array[idx++] = (float)(volume_ma[i] / 1000000.0);
|
||||
|
||||
// Feature 13: Volume ratio
|
||||
double vol_ratio = volume_ma[i] > 0 ? (double)volume[i] / volume_ma[i] : 1.0;
|
||||
input_array[idx++] = (float)vol_ratio;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Run ONNX model |
|
||||
//+------------------------------------------------------------------+
|
||||
bool RunONNXModel(matrixf &input_matrix, vectorf &output_vector)
|
||||
{
|
||||
if(onnx_handle == INVALID_HANDLE)
|
||||
return false;
|
||||
|
||||
// Run model - shapes are already set in OnInit per MQL5 documentation
|
||||
// Based on: https://www.mql5.com/en/docs/onnx/onnx_test
|
||||
// OnnxRun expects matrixf and vectorf, not flat arrays
|
||||
if(!OnnxRun(onnx_handle, ONNX_DEBUG_LOGS | ONNX_NO_CONVERSION, input_matrix, output_vector))
|
||||
{
|
||||
Print("ERROR: Failed to run ONNX model. Error: ", GetLastError());
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Open buy position |
|
||||
//+------------------------------------------------------------------+
|
||||
void OpenBuyPosition(double predicted_sl = 0.0, double predicted_tp = 0.0)
|
||||
{
|
||||
double price = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
|
||||
double sl = 0.0;
|
||||
double tp = 0.0;
|
||||
|
||||
if(InpUsePredictedSLTP && predicted_sl > 0 && predicted_tp > 0)
|
||||
{
|
||||
// Use predicted SL/TP
|
||||
sl = price - predicted_sl;
|
||||
tp = price + predicted_tp;
|
||||
Print("Using predicted SL/TP: SL=", sl, " TP=", tp);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Use fixed SL/TP from input parameters
|
||||
sl = InpStopLoss > 0 ? price - InpStopLoss * _Point * 10 : 0;
|
||||
tp = InpTakeProfit > 0 ? price + InpTakeProfit * _Point * 10 : 0;
|
||||
}
|
||||
|
||||
if(trade.Buy(InpLotSize, _Symbol, price, sl, tp, "ONNX Buy Signal"))
|
||||
{
|
||||
Print("Buy order opened. Ticket: ", trade.ResultOrder(), " Price: ", price, " SL: ", sl, " TP: ", tp);
|
||||
}
|
||||
else
|
||||
{
|
||||
Print("Failed to open buy order. Error: ", trade.ResultRetcodeDescription());
|
||||
}
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Open sell position |
|
||||
//+------------------------------------------------------------------+
|
||||
void OpenSellPosition(double predicted_sl = 0.0, double predicted_tp = 0.0)
|
||||
{
|
||||
double price = SymbolInfoDouble(_Symbol, SYMBOL_BID);
|
||||
double sl = 0.0;
|
||||
double tp = 0.0;
|
||||
|
||||
if(InpUsePredictedSLTP && predicted_sl > 0 && predicted_tp > 0)
|
||||
{
|
||||
// Use predicted SL/TP
|
||||
sl = price + predicted_sl;
|
||||
tp = price - predicted_tp;
|
||||
Print("Using predicted SL/TP: SL=", sl, " TP=", tp);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Use fixed SL/TP from input parameters
|
||||
sl = InpStopLoss > 0 ? price + InpStopLoss * _Point * 10 : 0;
|
||||
tp = InpTakeProfit > 0 ? price - InpTakeProfit * _Point * 10 : 0;
|
||||
}
|
||||
|
||||
if(trade.Sell(InpLotSize, _Symbol, price, sl, tp, "ONNX Sell Signal"))
|
||||
{
|
||||
Print("Sell order opened. Ticket: ", trade.ResultOrder(), " Price: ", price, " SL: ", sl, " TP: ", tp);
|
||||
}
|
||||
else
|
||||
{
|
||||
Print("Failed to open sell order. Error: ", trade.ResultRetcodeDescription());
|
||||
}
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Manage existing position |
|
||||
//+------------------------------------------------------------------+
|
||||
void ManagePosition(double predicted_price, double price_change_pct)
|
||||
{
|
||||
if(!PositionSelect(_Symbol))
|
||||
return;
|
||||
|
||||
// Simple position management - can be enhanced
|
||||
// For now, just log the position status
|
||||
double position_profit = PositionGetDouble(POSITION_PROFIT);
|
||||
Print("Position exists. Profit: ", position_profit, " Predicted change: ", price_change_pct, "%");
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
# BTCUSD 1-Minute ONNX Model Training
|
||||
|
||||
This directory contains the training script for a BTCUSD price prediction model using 1-minute timeframe data from 2017 to 2026.
|
||||
|
||||
## Requirements
|
||||
|
||||
```bash
|
||||
pip install yfinance tensorflow scikit-learn pandas numpy tf2onnx onnx tqdm
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
1. **Run the training script:**
|
||||
```bash
|
||||
cd ai/btcusd1min
|
||||
python main.py
|
||||
```
|
||||
|
||||
**Note:** yfinance 1-minute data is limited to the last 7 days. For longer historical training, the script will use the most recent available data.
|
||||
|
||||
## Configuration
|
||||
|
||||
The script is configured with:
|
||||
- **Symbol**: BTCUSD
|
||||
- **Timeframe**: M1 (1 minute)
|
||||
- **Lookback**: 60 bars (60 minutes of history)
|
||||
- **Date Range**: 2017-01-01 to 2026-01-01
|
||||
- **Model Architecture**: LSTM with 3 layers (128, 64, 32 units)
|
||||
- **Epochs**: 50 (with early stopping)
|
||||
- **Batch Size**: 64
|
||||
|
||||
## Output
|
||||
|
||||
The script will create:
|
||||
- `models/BTCUSD_M1_model.onnx` - The trained ONNX model
|
||||
- `models/BTCUSD_M1_model_scaler.pkl` - The MinMaxScaler used for normalization
|
||||
|
||||
## Model Features
|
||||
|
||||
The model uses 13 features:
|
||||
1. Open
|
||||
2. High
|
||||
3. Low
|
||||
4. Close
|
||||
5. Tick Volume
|
||||
6. RSI (14 period)
|
||||
7. EMA 20
|
||||
8. EMA 50
|
||||
9. ATR (14 period)
|
||||
10. Price Change (percentage)
|
||||
11. High/Low Ratio
|
||||
12. Volume MA (20 period)
|
||||
13. Volume Ratio
|
||||
|
||||
## Model Output
|
||||
|
||||
The model predicts the **price change percentage** for the next bar (1 minute ahead).
|
||||
|
||||
## Notes
|
||||
|
||||
- Training on 9 years of 1-minute data will take significant time and memory
|
||||
- The script fetches data in 3-month chunks to manage memory
|
||||
- Early stopping and learning rate reduction are enabled to prevent overfitting
|
||||
- The model uses dropout (0.3) for regularization
|
||||
|
||||
## Using the Model in MQL5
|
||||
|
||||
After training, copy the ONNX model to your MT5 `MQL5/Files/` directory and use it in an Expert Advisor similar to the XAUUSD H1 EA.
|
||||
@@ -0,0 +1,455 @@
|
||||
"""
|
||||
ONNX Model Training Script for BTCUSD 1-Minute Data
|
||||
Uses yfinance (Yahoo Finance) for historical data
|
||||
|
||||
This script trains a neural network model for BTCUSD price prediction on 1-minute timeframe
|
||||
and exports it to ONNX format.
|
||||
|
||||
Usage:
|
||||
python main.py
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
from datetime import datetime, timedelta
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
import yfinance as yf
|
||||
import tensorflow as tf
|
||||
from tensorflow import keras
|
||||
from tensorflow.keras import layers
|
||||
from sklearn.preprocessing import MinMaxScaler
|
||||
from sklearn.model_selection import train_test_split
|
||||
import tf2onnx
|
||||
import onnx
|
||||
from tqdm import tqdm
|
||||
import pickle
|
||||
|
||||
|
||||
class BTCUSD1MinTrainer:
|
||||
"""
|
||||
Trainer class for creating ONNX models from BTCUSD 1-minute MT5 data.
|
||||
"""
|
||||
|
||||
def __init__(self, symbol: str = "BTC-USD", lookback: int = 60,
|
||||
prediction_horizon: int = 1):
|
||||
"""
|
||||
Initialize the trainer.
|
||||
|
||||
Args:
|
||||
symbol: Trading symbol (default: 'BTC-USD' for Yahoo Finance)
|
||||
lookback: Number of bars to look back for prediction (default: 60)
|
||||
prediction_horizon: Number of bars ahead to predict (default: 1)
|
||||
"""
|
||||
self.symbol = symbol
|
||||
self.lookback = lookback
|
||||
self.prediction_horizon = prediction_horizon
|
||||
|
||||
self.scaler = MinMaxScaler()
|
||||
self.model = None
|
||||
|
||||
print(f"Using yfinance for data source. Symbol: {self.symbol}")
|
||||
|
||||
def fetch_data(self, start_date: datetime, end_date: datetime) -> pd.DataFrame:
|
||||
"""
|
||||
Fetch historical data from Yahoo Finance using yfinance.
|
||||
|
||||
Args:
|
||||
start_date: Start date for data
|
||||
end_date: End date for data
|
||||
|
||||
Returns:
|
||||
DataFrame with OHLCV data
|
||||
"""
|
||||
print(f"\nFetching {self.symbol} 1-minute data from {start_date.strftime('%Y-%m-%d')} to {end_date.strftime('%Y-%m-%d')}...")
|
||||
|
||||
# yfinance can only fetch 7 days of 1-minute data at a time
|
||||
# For longer periods, we need to fetch in chunks
|
||||
all_data = []
|
||||
current_start = start_date
|
||||
|
||||
# For 1-minute data, yfinance limits to last 7 days
|
||||
# So we'll fetch the most recent 7 days available
|
||||
print("Note: yfinance 1-minute data is limited to last 7 days")
|
||||
print("Fetching most recent available 1-minute data...")
|
||||
|
||||
# Get ticker
|
||||
ticker = yf.Ticker(self.symbol)
|
||||
|
||||
# Try to fetch 1-minute data (limited to 7 days)
|
||||
# If we need more data, we'll use daily data and resample
|
||||
try:
|
||||
# Fetch 1-minute data (max 7 days)
|
||||
df = ticker.history(start=start_date, end=end_date, interval='1m')
|
||||
|
||||
if df is None or len(df) == 0:
|
||||
print("Warning: No 1-minute data available, trying daily data...")
|
||||
# Fall back to daily data
|
||||
df = ticker.history(start=start_date, end=end_date, interval='1d')
|
||||
if df is None or len(df) == 0:
|
||||
raise ValueError(f"No data available for {self.symbol}")
|
||||
print(f"Using daily data instead (will resample to 1-minute for training)")
|
||||
except Exception as e:
|
||||
print(f"Error fetching 1-minute data: {e}")
|
||||
print("Falling back to daily data...")
|
||||
df = ticker.history(start=start_date, end=end_date, interval='1d')
|
||||
if df is None or len(df) == 0:
|
||||
raise ValueError(f"No data available for {self.symbol}: {e}")
|
||||
|
||||
# Rename columns to match expected format
|
||||
df.columns = [col.lower().replace(' ', '_') for col in df.columns]
|
||||
|
||||
# Ensure we have the required columns
|
||||
required_cols = ['open', 'high', 'low', 'close', 'volume']
|
||||
missing_cols = [col for col in required_cols if col not in df.columns]
|
||||
if missing_cols:
|
||||
raise ValueError(f"Missing required columns: {missing_cols}")
|
||||
|
||||
# Rename 'volume' to 'tick_volume' for consistency
|
||||
if 'volume' in df.columns:
|
||||
df['tick_volume'] = df['volume']
|
||||
df = df.drop('volume', axis=1)
|
||||
|
||||
# Remove duplicates and sort
|
||||
df = df[~df.index.duplicated(keep='first')]
|
||||
df = df.sort_index()
|
||||
|
||||
print(f"Total fetched: {len(df)} bars")
|
||||
if len(df) > 0:
|
||||
print(f"Date range: {df.index[0]} to {df.index[-1]}")
|
||||
print(f"Timeframe: {df.index[1] - df.index[0] if len(df) > 1 else 'N/A'}")
|
||||
else:
|
||||
raise ValueError("DataFrame is empty after processing")
|
||||
|
||||
return df
|
||||
|
||||
def prepare_features(self, df: pd.DataFrame) -> pd.DataFrame:
|
||||
"""
|
||||
Prepare features for training.
|
||||
|
||||
Args:
|
||||
df: Raw OHLCV data
|
||||
|
||||
Returns:
|
||||
DataFrame with features
|
||||
"""
|
||||
print("\nPreparing features...")
|
||||
|
||||
feature_df = df[['open', 'high', 'low', 'close', 'tick_volume']].copy()
|
||||
|
||||
# Add technical indicators as features
|
||||
print(" Calculating RSI...")
|
||||
feature_df['rsi'] = self._calculate_rsi(df['close'], period=14)
|
||||
|
||||
print(" Calculating EMAs...")
|
||||
feature_df['ema_20'] = df['close'].ewm(span=20, adjust=False).mean()
|
||||
feature_df['ema_50'] = df['close'].ewm(span=50, adjust=False).mean()
|
||||
|
||||
print(" Calculating ATR...")
|
||||
feature_df['atr'] = self._calculate_atr(df, period=14)
|
||||
|
||||
# Price changes
|
||||
feature_df['price_change'] = df['close'].pct_change()
|
||||
feature_df['high_low_ratio'] = df['high'] / df['low']
|
||||
|
||||
# Volume features
|
||||
feature_df['volume_ma'] = df['tick_volume'].rolling(window=20).mean()
|
||||
feature_df['volume_ratio'] = df['tick_volume'] / feature_df['volume_ma']
|
||||
|
||||
# Drop NaN values
|
||||
feature_df = feature_df.dropna()
|
||||
|
||||
print(f" Features prepared: {len(feature_df)} samples, {len(feature_df.columns)} features")
|
||||
print(f" Features: {list(feature_df.columns)}")
|
||||
|
||||
return feature_df
|
||||
|
||||
def _calculate_rsi(self, prices: pd.Series, period: int = 14) -> pd.Series:
|
||||
"""Calculate RSI indicator."""
|
||||
delta = prices.diff()
|
||||
gain = (delta.where(delta > 0, 0)).rolling(window=period).mean()
|
||||
loss = (-delta.where(delta < 0, 0)).rolling(window=period).mean()
|
||||
rs = gain / loss
|
||||
rsi = 100 - (100 / (1 + rs))
|
||||
return rsi
|
||||
|
||||
def _calculate_atr(self, df: pd.DataFrame, period: int = 14) -> pd.Series:
|
||||
"""Calculate ATR indicator."""
|
||||
high_low = df['high'] - df['low']
|
||||
high_close = np.abs(df['high'] - df['close'].shift())
|
||||
low_close = np.abs(df['low'] - df['close'].shift())
|
||||
tr = pd.concat([high_low, high_close, low_close], axis=1).max(axis=1)
|
||||
atr = tr.rolling(window=period).mean()
|
||||
return atr
|
||||
|
||||
def create_sequences(self, data: np.ndarray, target: np.ndarray) -> tuple:
|
||||
"""
|
||||
Create sequences for LSTM/RNN training.
|
||||
|
||||
Args:
|
||||
data: Feature data
|
||||
target: Target values (price change percentages)
|
||||
|
||||
Returns:
|
||||
Tuple of (X, y) sequences
|
||||
"""
|
||||
print("\nCreating sequences...")
|
||||
X, y = [], []
|
||||
|
||||
for i in tqdm(range(self.lookback, len(data) - self.prediction_horizon + 1), desc="Creating sequences"):
|
||||
X.append(data[i - self.lookback:i])
|
||||
y.append(target[i])
|
||||
|
||||
X = np.array(X)
|
||||
y = np.array(y)
|
||||
|
||||
print(f" Sequences created: X shape {X.shape}, y shape {y.shape}")
|
||||
|
||||
return X, y
|
||||
|
||||
def build_model(self, input_shape: tuple) -> keras.Model:
|
||||
"""
|
||||
Build the neural network model.
|
||||
|
||||
Args:
|
||||
input_shape: Shape of input data (lookback, features)
|
||||
|
||||
Returns:
|
||||
Compiled Keras model
|
||||
"""
|
||||
print(f"\nBuilding model with input shape: {input_shape}")
|
||||
|
||||
model = keras.Sequential([
|
||||
layers.LSTM(128, return_sequences=True, input_shape=input_shape),
|
||||
layers.Dropout(0.3),
|
||||
layers.LSTM(64, return_sequences=True),
|
||||
layers.Dropout(0.3),
|
||||
layers.LSTM(32),
|
||||
layers.Dropout(0.3),
|
||||
layers.Dense(32, activation='relu'),
|
||||
layers.Dense(16, activation='relu'),
|
||||
layers.Dense(1) # Predict price change percentage
|
||||
])
|
||||
|
||||
model.compile(
|
||||
optimizer=keras.optimizers.Adam(learning_rate=0.0005),
|
||||
loss='mse',
|
||||
metrics=['mae']
|
||||
)
|
||||
|
||||
print(f" Model parameters: {model.count_params():,}")
|
||||
model.summary()
|
||||
|
||||
return model
|
||||
|
||||
def train(self, start_date: datetime, end_date: datetime,
|
||||
epochs: int = 50, batch_size: int = 32,
|
||||
validation_split: float = 0.2, verbose: int = 1):
|
||||
"""
|
||||
Train the model.
|
||||
|
||||
Args:
|
||||
start_date: Start date for training data
|
||||
end_date: End date for training data
|
||||
epochs: Number of training epochs
|
||||
batch_size: Batch size for training
|
||||
validation_split: Fraction of data to use for validation
|
||||
verbose: Verbosity level
|
||||
"""
|
||||
# Fetch data
|
||||
df = self.fetch_data(start_date, end_date)
|
||||
feature_df = self.prepare_features(df)
|
||||
|
||||
# Prepare target: price change percentage for next bar
|
||||
# Calculate future price change: (next_close - current_close) / current_close
|
||||
close_prices = feature_df['close'].values
|
||||
target = []
|
||||
for i in range(len(close_prices)):
|
||||
if i + self.prediction_horizon < len(close_prices):
|
||||
current_price = close_prices[i]
|
||||
future_price = close_prices[i + self.prediction_horizon]
|
||||
price_change_pct = (future_price - current_price) / current_price if current_price > 0 else 0.0
|
||||
target.append(price_change_pct)
|
||||
else:
|
||||
target.append(0.0)
|
||||
target = pd.Series(target, index=feature_df.index)
|
||||
|
||||
# Keep 'close' in features - it's needed for the model
|
||||
|
||||
# Align target with features
|
||||
valid_idx = ~(target.isna() | feature_df.isna().any(axis=1))
|
||||
feature_df = feature_df[valid_idx]
|
||||
target = target[valid_idx]
|
||||
|
||||
print(f"\nValid samples after alignment: {len(feature_df)}")
|
||||
|
||||
# Normalize features
|
||||
print("\nNormalizing features...")
|
||||
feature_array = self.scaler.fit_transform(feature_df.values)
|
||||
|
||||
# Create sequences
|
||||
X, y = self.create_sequences(feature_array, target.values)
|
||||
|
||||
# Split into train and validation
|
||||
split_idx = int(len(X) * (1 - validation_split))
|
||||
X_train, X_val = X[:split_idx], X[split_idx:]
|
||||
y_train, y_val = y[:split_idx], y[split_idx:]
|
||||
|
||||
print(f"\nTrain set: {len(X_train)} samples")
|
||||
print(f"Validation set: {len(X_val)} samples")
|
||||
|
||||
# Build model
|
||||
input_shape = (self.lookback, feature_array.shape[1])
|
||||
self.model = self.build_model(input_shape)
|
||||
|
||||
# Train model
|
||||
print(f"\nTraining model for {epochs} epochs...")
|
||||
history = self.model.fit(
|
||||
X_train, y_train,
|
||||
batch_size=batch_size,
|
||||
epochs=epochs,
|
||||
validation_data=(X_val, y_val),
|
||||
verbose=verbose,
|
||||
callbacks=[
|
||||
keras.callbacks.EarlyStopping(
|
||||
monitor='val_loss',
|
||||
patience=10,
|
||||
restore_best_weights=True
|
||||
),
|
||||
keras.callbacks.ReduceLROnPlateau(
|
||||
monitor='val_loss',
|
||||
factor=0.5,
|
||||
patience=5,
|
||||
min_lr=1e-7
|
||||
)
|
||||
]
|
||||
)
|
||||
|
||||
# Evaluate
|
||||
print("\nEvaluating model...")
|
||||
train_loss = self.model.evaluate(X_train, y_train, verbose=0)
|
||||
val_loss = self.model.evaluate(X_val, y_val, verbose=0)
|
||||
|
||||
print(f"Train Loss: {train_loss[0]:.6f}, MAE: {train_loss[1]:.6f}")
|
||||
print(f"Val Loss: {val_loss[0]:.6f}, MAE: {val_loss[1]:.6f}")
|
||||
|
||||
return history
|
||||
|
||||
def export_to_onnx(self, output_path: str):
|
||||
"""
|
||||
Export the trained model to ONNX format.
|
||||
|
||||
Args:
|
||||
output_path: Path to save ONNX model
|
||||
"""
|
||||
if self.model is None:
|
||||
raise ValueError("Model must be trained before exporting")
|
||||
|
||||
print(f"\nExporting model to ONNX format: {output_path}")
|
||||
|
||||
# Get number of features
|
||||
num_features = self.model.input_shape[2] if len(self.model.input_shape) > 2 else self.model.input_shape[1]
|
||||
|
||||
print(f"Using {num_features} features for ONNX export")
|
||||
|
||||
# Create input signature
|
||||
input_shape = (None, self.lookback, num_features)
|
||||
spec = (tf.TensorSpec(input_shape, tf.float32, name="input"),)
|
||||
|
||||
# Fix output_names for Sequential model
|
||||
if not hasattr(self.model, 'output_names'):
|
||||
if hasattr(self.model, 'outputs') and self.model.outputs:
|
||||
self.model.output_names = [f'output_{i}' for i in range(len(self.model.outputs))]
|
||||
else:
|
||||
self.model.output_names = ['output']
|
||||
|
||||
# Convert to ONNX
|
||||
onnx_model, _ = tf2onnx.convert.from_keras(
|
||||
self.model,
|
||||
input_signature=spec,
|
||||
opset=13
|
||||
)
|
||||
|
||||
# Save ONNX model
|
||||
os.makedirs(os.path.dirname(output_path), exist_ok=True)
|
||||
onnx.save_model(onnx_model, output_path)
|
||||
|
||||
print(f"ONNX model saved to: {output_path}")
|
||||
|
||||
# Save scaler
|
||||
scaler_path = output_path.replace('.onnx', '_scaler.pkl')
|
||||
with open(scaler_path, 'wb') as f:
|
||||
pickle.dump(self.scaler, f)
|
||||
print(f"Scaler saved to: {scaler_path}")
|
||||
|
||||
def cleanup(self):
|
||||
"""Clean up (no-op for yfinance)."""
|
||||
pass
|
||||
|
||||
|
||||
def main():
|
||||
"""Main function."""
|
||||
print("="*60)
|
||||
print("BTCUSD 1-Minute ONNX Model Training")
|
||||
print("="*60)
|
||||
|
||||
# Training parameters
|
||||
symbol = "BTC-USD" # Yahoo Finance symbol
|
||||
lookback = 60 # 60 minutes of history
|
||||
epochs = 50
|
||||
batch_size = 64 # Larger batch for 1-minute data
|
||||
|
||||
# Date range: Use recent data (yfinance 1m data limited to 7 days)
|
||||
# For longer training, we'll use the most recent available data
|
||||
end_date = datetime.now()
|
||||
start_date = end_date - timedelta(days=7) # Last 7 days for 1-minute data
|
||||
|
||||
print(f"Note: yfinance 1-minute data is limited to last 7 days")
|
||||
print(f"Using date range: {start_date.strftime('%Y-%m-%d')} to {end_date.strftime('%Y-%m-%d')}")
|
||||
|
||||
# Output paths
|
||||
output_dir = "models"
|
||||
os.makedirs(output_dir, exist_ok=True)
|
||||
model_path = os.path.join(output_dir, f"{symbol}_M1_model.onnx")
|
||||
|
||||
trainer = None
|
||||
try:
|
||||
# Create trainer
|
||||
trainer = BTCUSD1MinTrainer(
|
||||
symbol=symbol,
|
||||
lookback=lookback
|
||||
)
|
||||
|
||||
# Train model
|
||||
history = trainer.train(
|
||||
start_date=start_date,
|
||||
end_date=end_date,
|
||||
epochs=epochs,
|
||||
batch_size=batch_size,
|
||||
validation_split=0.2,
|
||||
verbose=1
|
||||
)
|
||||
|
||||
# Export to ONNX
|
||||
trainer.export_to_onnx(model_path)
|
||||
|
||||
print("\n" + "="*60)
|
||||
print("Training completed successfully!")
|
||||
print("="*60)
|
||||
print(f"Model saved to: {model_path}")
|
||||
|
||||
except Exception as e:
|
||||
print(f"\nERROR: Training failed: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
return 1
|
||||
|
||||
finally:
|
||||
if trainer:
|
||||
trainer.cleanup()
|
||||
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
sys.exit(main())
|
||||
Binary file not shown.
Binary file not shown.
+231
-62
@@ -11,9 +11,14 @@
|
||||
|
||||
#include <Trade\Trade.mqh>
|
||||
|
||||
//--- Resource: Embed ONNX model in EA
|
||||
// Based on: https://www.mql5.com/en/docs/onnx/onnx_test
|
||||
// Path is relative to MQL5 directory (not starting with \\Files\\)
|
||||
#resource "Files\\XAUUSD_H1_model.onnx" as uchar ExtModel[]
|
||||
|
||||
//--- Input parameters
|
||||
input group "ONNX Model Settings"
|
||||
input string InpModelPath = "models\\XAUUSD_H1_model.onnx"; // ONNX Model Path
|
||||
input string InpModelPath = ""; // ONNX Model Path (leave empty to use embedded resource)
|
||||
input int InpLookback = 60; // Lookback Period (bars)
|
||||
input bool InpUsePrediction = true; // Use Model Prediction
|
||||
|
||||
@@ -21,8 +26,13 @@ input group "Trading Settings"
|
||||
input double InpLotSize = 0.01; // Lot Size
|
||||
input int InpMagicNumber = 123456; // Magic Number
|
||||
input int InpSlippage = 3; // Slippage (points)
|
||||
input int InpStopLoss = 50; // Stop Loss (pips)
|
||||
input int InpTakeProfit = 100; // Take Profit (pips)
|
||||
input bool InpUsePredictedSLTP = true; // Use Predicted SL/TP (based on prediction & volatility)
|
||||
input int InpStopLoss = 50; // Stop Loss (pips) - used if InpUsePredictedSLTP=false
|
||||
input int InpTakeProfit = 100; // Take Profit (pips) - used if InpUsePredictedSLTP=false
|
||||
input double InpSLMultiplier = 1.5; // SL Multiplier (ATR-based, e.g., 1.5 = 1.5x ATR)
|
||||
input double InpTPMultiplier = 2.0; // TP Multiplier (ATR-based, e.g., 2.0 = 2x ATR)
|
||||
input double InpMinSLATR = 0.5; // Minimum SL (ATR multiplier)
|
||||
input double InpMinTPATR = 1.0; // Minimum TP (ATR multiplier)
|
||||
|
||||
input group "Prediction Settings"
|
||||
input double InpPredictionThreshold = 0.00005; // Min Prediction Change (0.005% as decimal, e.g., 0.00005 = 0.005%)
|
||||
@@ -47,33 +57,40 @@ int OnInit()
|
||||
trade.SetTypeFilling(ORDER_FILLING_FOK);
|
||||
|
||||
// Load ONNX model
|
||||
string model_path = InpModelPath;
|
||||
// Based on: https://www.mql5.com/en/docs/onnx/onnx_test
|
||||
Print("Loading ONNX model from embedded resource...");
|
||||
|
||||
// Convert relative path to full path
|
||||
if(StringFind(model_path, "\\") == 0 || StringFind(model_path, "/") == 0)
|
||||
{
|
||||
// Already absolute path
|
||||
}
|
||||
else
|
||||
{
|
||||
// Relative path - prepend terminal data folder
|
||||
model_path = TerminalInfoString(TERMINAL_DATA_PATH) + "\\MQL5\\Files\\" + model_path;
|
||||
}
|
||||
|
||||
// Replace forward slashes with backslashes for Windows
|
||||
StringReplace(model_path, "/", "\\");
|
||||
|
||||
Print("Loading ONNX model from: ", model_path);
|
||||
|
||||
onnx_handle = OnnxCreate(model_path, ONNX_DEFAULT);
|
||||
// Create model from resource buffer
|
||||
onnx_handle = OnnxCreateFromBuffer(ExtModel, ONNX_DEBUG_LOGS);
|
||||
|
||||
if(onnx_handle == INVALID_HANDLE)
|
||||
{
|
||||
Print("ERROR: Failed to load ONNX model. Error: ", GetLastError());
|
||||
Print("Make sure the model file exists at: ", model_path);
|
||||
int error = GetLastError();
|
||||
Print("ERROR: Failed to create ONNX model from resource. Error: ", error);
|
||||
Print("Make sure the model file exists at: MQL5\\Files\\XAUUSD_H1_model.onnx");
|
||||
Print("Then recompile the EA to embed it as a resource.");
|
||||
return(INIT_FAILED);
|
||||
}
|
||||
|
||||
// Set input shape - per MQL5 documentation
|
||||
const long ExtInputShape[] = {1, InpLookback, 13}; // batch=1, lookback bars, 13 features
|
||||
if(!OnnxSetInputShape(onnx_handle, 0, ExtInputShape))
|
||||
{
|
||||
Print("OnnxSetInputShape failed, error ", GetLastError());
|
||||
OnnxRelease(onnx_handle);
|
||||
return(INIT_FAILED);
|
||||
}
|
||||
|
||||
// Set output shape - per MQL5 documentation
|
||||
const long ExtOutputShape[] = {1, 1}; // batch=1, single output value
|
||||
if(!OnnxSetOutputShape(onnx_handle, 0, ExtOutputShape))
|
||||
{
|
||||
Print("OnnxSetOutputShape failed, error ", GetLastError());
|
||||
OnnxRelease(onnx_handle);
|
||||
return(INIT_FAILED);
|
||||
}
|
||||
|
||||
|
||||
// Get model info
|
||||
long input_count = OnnxGetInputCount(onnx_handle);
|
||||
long output_count = OnnxGetOutputCount(onnx_handle);
|
||||
@@ -137,23 +154,59 @@ void OnTick()
|
||||
return;
|
||||
}
|
||||
|
||||
// Run ONNX model
|
||||
float output_data[];
|
||||
if(!RunONNXModel(input_data, output_data))
|
||||
// Check if input data is valid
|
||||
if(ArraySize(input_data) != InpLookback * 13)
|
||||
{
|
||||
Print("ERROR: Input data size mismatch. Expected: ", InpLookback * 13, ", Got: ", ArraySize(input_data));
|
||||
return;
|
||||
}
|
||||
|
||||
// Convert flat array to matrixf for OnnxRun
|
||||
// Shape: [lookback, features] = [60, 13] - batch dimension is added automatically
|
||||
matrixf input_matrix;
|
||||
input_matrix.Resize(InpLookback, 13);
|
||||
|
||||
// Fill matrix from flat array
|
||||
int idx = 0;
|
||||
for(int i = 0; i < InpLookback; i++)
|
||||
{
|
||||
for(int j = 0; j < 13; j++)
|
||||
{
|
||||
if(idx >= ArraySize(input_data))
|
||||
{
|
||||
Print("ERROR: Index out of bounds when filling matrix. idx=", idx, ", array size=", ArraySize(input_data));
|
||||
return;
|
||||
}
|
||||
input_matrix[i][j] = input_data[idx++];
|
||||
}
|
||||
}
|
||||
|
||||
// Verify matrix is not empty
|
||||
if(input_matrix.Rows() == 0 || input_matrix.Cols() == 0)
|
||||
{
|
||||
Print("ERROR: Input matrix is empty. Rows: ", input_matrix.Rows(), ", Cols: ", input_matrix.Cols());
|
||||
return;
|
||||
}
|
||||
|
||||
Print("Input matrix prepared: Rows=", input_matrix.Rows(), ", Cols=", input_matrix.Cols());
|
||||
|
||||
// Run ONNX model - use matrixf and vectorf per MQL5 documentation
|
||||
vectorf output_vector(1);
|
||||
if(!RunONNXModel(input_matrix, output_vector))
|
||||
{
|
||||
Print("ERROR: Failed to run ONNX model");
|
||||
return;
|
||||
}
|
||||
|
||||
// Get prediction
|
||||
if(ArraySize(output_data) == 0)
|
||||
if(output_vector.Size() == 0)
|
||||
{
|
||||
Print("ERROR: Empty output from ONNX model");
|
||||
return;
|
||||
}
|
||||
|
||||
// Model now predicts price change percentage directly (e.g., -0.003 = -0.3%)
|
||||
double predicted_change_pct = output_data[0];
|
||||
double predicted_change_pct = output_vector[0];
|
||||
double current_price = SymbolInfoDouble(_Symbol, SYMBOL_BID);
|
||||
|
||||
// Check if prediction is percentage (between -1 and 1) or absolute price (old format)
|
||||
@@ -190,20 +243,85 @@ void OnTick()
|
||||
last_prediction = predicted_price;
|
||||
last_confidence = confidence;
|
||||
|
||||
// Calculate ATR for dynamic SL/TP
|
||||
double atr_value = 0.0;
|
||||
double atr_array[];
|
||||
ArraySetAsSeries(atr_array, true);
|
||||
int atr_handle = iATR(_Symbol, PERIOD_CURRENT, 14);
|
||||
if(atr_handle != INVALID_HANDLE)
|
||||
{
|
||||
if(CopyBuffer(atr_handle, 0, 0, 1, atr_array) > 0)
|
||||
{
|
||||
atr_value = atr_array[0];
|
||||
}
|
||||
IndicatorRelease(atr_handle);
|
||||
}
|
||||
|
||||
// Calculate predicted SL/TP based on prediction, confidence, and volatility
|
||||
double predicted_sl = 0.0;
|
||||
double predicted_tp = 0.0;
|
||||
if(InpUsePredictedSLTP && atr_value > 0)
|
||||
{
|
||||
// Calculate SL/TP based on ATR, prediction, and confidence
|
||||
double predicted_move = MathAbs(predicted_price - current_price);
|
||||
|
||||
// SL: Based on ATR and confidence
|
||||
// Higher confidence = tighter SL, lower confidence = wider SL
|
||||
double sl_atr_mult = InpSLMultiplier / MathMax(confidence, 0.1);
|
||||
sl_atr_mult = MathMax(sl_atr_mult, InpMinSLATR);
|
||||
predicted_sl = atr_value * sl_atr_mult;
|
||||
|
||||
// TP: Use a fraction of predicted move (not the full move)
|
||||
// Take 30-50% of predicted move as TP, but ensure minimum
|
||||
double tp_fraction = 0.3 + (confidence * 0.2); // 30-50% based on confidence
|
||||
double tp_from_prediction = predicted_move * tp_fraction;
|
||||
|
||||
// Also calculate TP from ATR multiplier
|
||||
double tp_from_atr = atr_value * InpTPMultiplier;
|
||||
|
||||
// Use the smaller of the two (more conservative)
|
||||
predicted_tp = MathMin(tp_from_prediction, tp_from_atr);
|
||||
predicted_tp = MathMax(predicted_tp, atr_value * InpMinTPATR); // Minimum TP
|
||||
|
||||
// Ensure TP is at least 1.5x SL for risk/reward
|
||||
if(predicted_tp < predicted_sl * 1.5)
|
||||
{
|
||||
predicted_tp = predicted_sl * 1.5;
|
||||
}
|
||||
|
||||
// Cap TP at maximum 80% of predicted move (don't be too greedy)
|
||||
double max_tp = predicted_move * 0.8;
|
||||
if(predicted_tp > max_tp)
|
||||
{
|
||||
predicted_tp = max_tp;
|
||||
}
|
||||
}
|
||||
|
||||
// Log prediction
|
||||
Print("Prediction: Current=", current_price,
|
||||
" Predicted Change=", price_change_pct, "%",
|
||||
" Predicted Price=", predicted_price,
|
||||
" Confidence=", confidence);
|
||||
" Confidence=", confidence,
|
||||
" ATR=", atr_value);
|
||||
if(InpUsePredictedSLTP && predicted_sl > 0 && predicted_tp > 0)
|
||||
{
|
||||
Print(" Predicted SL=", predicted_sl, " (", predicted_sl/current_price*100, "%)",
|
||||
" Predicted TP=", predicted_tp, " (", predicted_tp/current_price*100, "%)");
|
||||
}
|
||||
|
||||
// Check if we should trade
|
||||
if(!InpUseConfidence || confidence >= InpMinConfidence)
|
||||
{
|
||||
// Check if prediction is significant
|
||||
// price_change_pct is now in percentage (e.g., 0.1 = 0.1%), so compare with threshold * 100
|
||||
// OR: if model outputs decimal (0.001), compare directly
|
||||
double abs_change_decimal = MathAbs(predicted_change_pct); // Use raw prediction for threshold check
|
||||
if(abs_change_decimal >= InpPredictionThreshold)
|
||||
// price_change_pct is in percentage (e.g., 5.72 = 5.72%)
|
||||
// InpPredictionThreshold is in decimal (e.g., 0.00005 = 0.005%)
|
||||
// Convert threshold to percentage for comparison
|
||||
double threshold_pct = InpPredictionThreshold * 100.0;
|
||||
double abs_change_pct = MathAbs(price_change_pct); // Already in percentage
|
||||
|
||||
Print("Trade Check: Change=", price_change_pct, "% Threshold=", threshold_pct, "% Confidence=", confidence);
|
||||
|
||||
if(abs_change_pct >= threshold_pct)
|
||||
{
|
||||
// Check existing position
|
||||
if(PositionSelect(_Symbol))
|
||||
@@ -214,17 +332,26 @@ void OnTick()
|
||||
else
|
||||
{
|
||||
// Open new position based on prediction
|
||||
// Use raw prediction (decimal format) for threshold comparison
|
||||
if(predicted_change_pct > InpPredictionThreshold)
|
||||
if(price_change_pct > threshold_pct)
|
||||
{
|
||||
OpenBuyPosition();
|
||||
Print(">>> Opening BUY position: Change=", price_change_pct, "% Threshold=", threshold_pct, "%");
|
||||
OpenBuyPosition(predicted_sl, predicted_tp);
|
||||
}
|
||||
else if(predicted_change_pct < -InpPredictionThreshold)
|
||||
else if(price_change_pct < -threshold_pct)
|
||||
{
|
||||
OpenSellPosition();
|
||||
Print(">>> Opening SELL position: Change=", price_change_pct, "% Threshold=", threshold_pct, "%");
|
||||
OpenSellPosition(predicted_sl, predicted_tp);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Print("Prediction below threshold: Change=", price_change_pct, "% < Threshold=", threshold_pct, "%");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Print("Confidence too low: ", confidence, " < ", InpMinConfidence);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -251,16 +378,36 @@ bool PrepareInputData(float &input_array[])
|
||||
ArraySetAsSeries(close, true);
|
||||
ArraySetAsSeries(volume, true);
|
||||
|
||||
if(CopyOpen(_Symbol, PERIOD_CURRENT, 0, lookback + 50, open) < lookback)
|
||||
int copied_open = CopyOpen(_Symbol, PERIOD_CURRENT, 0, lookback + 50, open);
|
||||
if(copied_open < lookback)
|
||||
{
|
||||
Print("ERROR: CopyOpen failed. Got ", copied_open, " bars, need ", lookback);
|
||||
return false;
|
||||
if(CopyHigh(_Symbol, PERIOD_CURRENT, 0, lookback + 50, high) < lookback)
|
||||
}
|
||||
int copied_high = CopyHigh(_Symbol, PERIOD_CURRENT, 0, lookback + 50, high);
|
||||
if(copied_high < lookback)
|
||||
{
|
||||
Print("ERROR: CopyHigh failed. Got ", copied_high, " bars, need ", lookback);
|
||||
return false;
|
||||
if(CopyLow(_Symbol, PERIOD_CURRENT, 0, lookback + 50, low) < lookback)
|
||||
}
|
||||
int copied_low = CopyLow(_Symbol, PERIOD_CURRENT, 0, lookback + 50, low);
|
||||
if(copied_low < lookback)
|
||||
{
|
||||
Print("ERROR: CopyLow failed. Got ", copied_low, " bars, need ", lookback);
|
||||
return false;
|
||||
if(CopyClose(_Symbol, PERIOD_CURRENT, 0, lookback + 50, close) < lookback)
|
||||
}
|
||||
int copied_close = CopyClose(_Symbol, PERIOD_CURRENT, 0, lookback + 50, close);
|
||||
if(copied_close < lookback)
|
||||
{
|
||||
Print("ERROR: CopyClose failed. Got ", copied_close, " bars, need ", lookback);
|
||||
return false;
|
||||
if(CopyTickVolume(_Symbol, PERIOD_CURRENT, 0, lookback + 50, volume) < lookback)
|
||||
}
|
||||
int copied_volume = CopyTickVolume(_Symbol, PERIOD_CURRENT, 0, lookback + 50, volume);
|
||||
if(copied_volume < lookback)
|
||||
{
|
||||
Print("ERROR: CopyTickVolume failed. Got ", copied_volume, " bars, need ", lookback);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Calculate indicators (simplified - you may need to match training exactly)
|
||||
double rsi[], ema20[], ema50[], atr[];
|
||||
@@ -379,21 +526,15 @@ bool PrepareInputData(float &input_array[])
|
||||
//+------------------------------------------------------------------+
|
||||
//| Run ONNX model |
|
||||
//+------------------------------------------------------------------+
|
||||
bool RunONNXModel(float &input_data[], float &output_data[])
|
||||
bool RunONNXModel(matrixf &input_matrix, vectorf &output_vector)
|
||||
{
|
||||
if(onnx_handle == INVALID_HANDLE)
|
||||
return false;
|
||||
|
||||
// Set input shape
|
||||
long input_shape[] = {1, InpLookback, 13}; // 13 features
|
||||
if(!OnnxSetInputShape(onnx_handle, 0, input_shape))
|
||||
{
|
||||
Print("ERROR: Failed to set input shape. Error: ", GetLastError());
|
||||
return false;
|
||||
}
|
||||
|
||||
// Run model
|
||||
if(!OnnxRun(onnx_handle, ONNX_NO_CONVERSION, input_data, output_data))
|
||||
// Run model - shapes are already set in OnInit per MQL5 documentation
|
||||
// Based on: https://www.mql5.com/en/docs/onnx/onnx_test
|
||||
// OnnxRun expects matrixf and vectorf, not flat arrays
|
||||
if(!OnnxRun(onnx_handle, ONNX_DEBUG_LOGS | ONNX_NO_CONVERSION, input_matrix, output_vector))
|
||||
{
|
||||
Print("ERROR: Failed to run ONNX model. Error: ", GetLastError());
|
||||
return false;
|
||||
@@ -405,15 +546,29 @@ bool RunONNXModel(float &input_data[], float &output_data[])
|
||||
//+------------------------------------------------------------------+
|
||||
//| Open buy position |
|
||||
//+------------------------------------------------------------------+
|
||||
void OpenBuyPosition()
|
||||
void OpenBuyPosition(double predicted_sl = 0.0, double predicted_tp = 0.0)
|
||||
{
|
||||
double price = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
|
||||
double sl = InpStopLoss > 0 ? price - InpStopLoss * _Point * 10 : 0;
|
||||
double tp = InpTakeProfit > 0 ? price + InpTakeProfit * _Point * 10 : 0;
|
||||
double sl = 0.0;
|
||||
double tp = 0.0;
|
||||
|
||||
if(InpUsePredictedSLTP && predicted_sl > 0 && predicted_tp > 0)
|
||||
{
|
||||
// Use predicted SL/TP
|
||||
sl = price - predicted_sl;
|
||||
tp = price + predicted_tp;
|
||||
Print("Using predicted SL/TP: SL=", sl, " TP=", tp);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Use fixed SL/TP from input parameters
|
||||
sl = InpStopLoss > 0 ? price - InpStopLoss * _Point * 10 : 0;
|
||||
tp = InpTakeProfit > 0 ? price + InpTakeProfit * _Point * 10 : 0;
|
||||
}
|
||||
|
||||
if(trade.Buy(InpLotSize, _Symbol, price, sl, tp, "ONNX Buy Signal"))
|
||||
{
|
||||
Print("Buy order opened. Ticket: ", trade.ResultOrder());
|
||||
Print("Buy order opened. Ticket: ", trade.ResultOrder(), " Price: ", price, " SL: ", sl, " TP: ", tp);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -424,15 +579,29 @@ void OpenBuyPosition()
|
||||
//+------------------------------------------------------------------+
|
||||
//| Open sell position |
|
||||
//+------------------------------------------------------------------+
|
||||
void OpenSellPosition()
|
||||
void OpenSellPosition(double predicted_sl = 0.0, double predicted_tp = 0.0)
|
||||
{
|
||||
double price = SymbolInfoDouble(_Symbol, SYMBOL_BID);
|
||||
double sl = InpStopLoss > 0 ? price + InpStopLoss * _Point * 10 : 0;
|
||||
double tp = InpTakeProfit > 0 ? price - InpTakeProfit * _Point * 10 : 0;
|
||||
double sl = 0.0;
|
||||
double tp = 0.0;
|
||||
|
||||
if(InpUsePredictedSLTP && predicted_sl > 0 && predicted_tp > 0)
|
||||
{
|
||||
// Use predicted SL/TP
|
||||
sl = price + predicted_sl;
|
||||
tp = price - predicted_tp;
|
||||
Print("Using predicted SL/TP: SL=", sl, " TP=", tp);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Use fixed SL/TP from input parameters
|
||||
sl = InpStopLoss > 0 ? price + InpStopLoss * _Point * 10 : 0;
|
||||
tp = InpTakeProfit > 0 ? price - InpTakeProfit * _Point * 10 : 0;
|
||||
}
|
||||
|
||||
if(trade.Sell(InpLotSize, _Symbol, price, sl, tp, "ONNX Sell Signal"))
|
||||
{
|
||||
Print("Sell order opened. Ticket: ", trade.ResultOrder());
|
||||
Print("Sell order opened. Ticket: ", trade.ResultOrder(), " Price: ", price, " SL: ", sl, " TP: ", tp);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
+34
-5
@@ -71,16 +71,45 @@ python predict_with_onnx.py --model models/XAUUSD_H1_model.onnx --symbol XAUUSD
|
||||
|
||||
### Step 3: Use in Expert Advisor
|
||||
|
||||
#### For Strategy Tester:
|
||||
1. Copy the ONNX model to Tester Files folder:
|
||||
```
|
||||
<MT5 Data Folder>\Tester\Files\XAUUSD_H1_model.onnx
|
||||
```
|
||||
Or use the full path shown in error messages if file not found.
|
||||
|
||||
2. Compile `ONNX_EA.mq5` in MetaEditor (F7)
|
||||
|
||||
3. Open Strategy Tester (View → Strategy Tester or Ctrl+R)
|
||||
|
||||
4. Configure:
|
||||
- Expert Advisor: `ONNX_EA`
|
||||
- Symbol: `XAUUSD` (or your symbol)
|
||||
- Period: `H1` (or your timeframe)
|
||||
- Inputs:
|
||||
- `InpModelPath`: `XAUUSD_H1_model.onnx` (just filename)
|
||||
- Adjust other parameters as needed
|
||||
|
||||
5. Click Start
|
||||
|
||||
#### For Live/Demo Trading:
|
||||
1. Copy the ONNX model to MT5's Files folder:
|
||||
```
|
||||
<MT5 Data Folder>\MQL5\Files\models\XAUUSD_H1_model.onnx
|
||||
<MT5 Data Folder>\MQL5\Files\XAUUSD_H1_model.onnx
|
||||
```
|
||||
To find your Data Folder: Tools → Options → Expert Advisors → Data Folder
|
||||
|
||||
2. Compile `ONNX_EA.mq5` in MetaEditor
|
||||
2. Compile `ONNX_EA.mq5` in MetaEditor (F7)
|
||||
|
||||
3. Attach the EA to a chart with:
|
||||
- Model path: `models\XAUUSD_H1_model.onnx`
|
||||
- Your trading parameters
|
||||
3. Open a chart (e.g., XAUUSD H1)
|
||||
|
||||
4. Drag `ONNX_EA` from Navigator (Ctrl+N) onto the chart
|
||||
|
||||
5. Configure inputs:
|
||||
- `InpModelPath`: `XAUUSD_H1_model.onnx` (just filename)
|
||||
- Adjust trading parameters
|
||||
|
||||
6. Click OK and enable AutoTrading if needed
|
||||
|
||||
## Detailed Usage
|
||||
|
||||
|
||||
@@ -0,0 +1,681 @@
|
||||
//+------------------------------------------------------------------+
|
||||
//| EURUSD_M15_EA.mq5 |
|
||||
//| Copyright 2025, MetaQuotes Ltd. |
|
||||
//| https://www.mql5.com |
|
||||
//+------------------------------------------------------------------+
|
||||
#property copyright "Copyright 2025, MetaQuotes Ltd."
|
||||
#property link "https://www.mql5.com"
|
||||
#property version "1.00"
|
||||
#property description "Expert Advisor using ONNX model for EURUSD 15-minute price prediction"
|
||||
#property description "Based on: https://www.mql5.com/en/docs/onnx/onnx_test"
|
||||
|
||||
#include <Trade\Trade.mqh>
|
||||
|
||||
//--- Resource: Embed ONNX model in EA
|
||||
// Based on: https://www.mql5.com/en/docs/onnx/onnx_test
|
||||
// Path is relative to MQL5 directory (not starting with \\Files\\)
|
||||
#resource "Files\\EURUSD_M15_model.onnx" as uchar ExtModel[]
|
||||
|
||||
//--- Input parameters
|
||||
input group "ONNX Model Settings"
|
||||
input string InpModelPath = ""; // ONNX Model Path (leave empty to use embedded resource)
|
||||
input int InpLookback = 60; // Lookback Period (bars = 15 hours)
|
||||
input bool InpUsePrediction = true; // Use Model Prediction
|
||||
|
||||
input group "Trading Settings"
|
||||
input double InpLotSize = 0.01; // Lot Size
|
||||
input int InpMagicNumber = 123456; // Magic Number
|
||||
input int InpSlippage = 3; // Slippage (points)
|
||||
input bool InpUsePredictedSLTP = true; // Use Predicted SL/TP (based on prediction & volatility)
|
||||
input int InpStopLoss = 50; // Stop Loss (pips) - used if InpUsePredictedSLTP=false
|
||||
input int InpTakeProfit = 100; // Take Profit (pips) - used if InpUsePredictedSLTP=false
|
||||
input double InpSLMultiplier = 1.5; // SL Multiplier (ATR-based, e.g., 1.5 = 1.5x ATR)
|
||||
input double InpTPMultiplier = 2.0; // TP Multiplier (ATR-based, e.g., 2.0 = 2x ATR)
|
||||
input double InpMinSLATR = 0.5; // Minimum SL (ATR multiplier)
|
||||
input double InpMinTPATR = 1.0; // Minimum TP (ATR multiplier)
|
||||
|
||||
input group "Prediction Settings"
|
||||
input double InpPredictionThreshold = 0.00005; // Min Prediction Change (0.005% as decimal, e.g., 0.00005 = 0.005%)
|
||||
input bool InpUseConfidence = true; // Use Confidence Filter
|
||||
input double InpMinConfidence = 0.1; // Minimum Confidence (0.1 = 10%)
|
||||
|
||||
//--- Global variables
|
||||
CTrade trade;
|
||||
long onnx_handle = INVALID_HANDLE;
|
||||
datetime last_bar_time = 0;
|
||||
double last_prediction = 0.0;
|
||||
double last_confidence = 0.0;
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Expert initialization function |
|
||||
//+------------------------------------------------------------------+
|
||||
int OnInit()
|
||||
{
|
||||
// Set trade parameters
|
||||
trade.SetExpertMagicNumber(InpMagicNumber);
|
||||
trade.SetDeviationInPoints(InpSlippage);
|
||||
trade.SetTypeFilling(ORDER_FILLING_FOK);
|
||||
|
||||
// Check symbol
|
||||
if(_Symbol != "EURUSD" && _Symbol != "EURUSD#")
|
||||
{
|
||||
Print("WARNING: This EA is designed for EURUSD. Current symbol: ", _Symbol);
|
||||
}
|
||||
|
||||
// Check timeframe
|
||||
if(_Period != PERIOD_M15)
|
||||
{
|
||||
Print("WARNING: This EA is designed for M15 timeframe. Current timeframe: ", EnumToString(_Period));
|
||||
}
|
||||
|
||||
// Load ONNX model
|
||||
// Based on: https://www.mql5.com/en/docs/onnx/onnx_test
|
||||
Print("Loading ONNX model from embedded resource...");
|
||||
|
||||
// Create model from resource buffer
|
||||
onnx_handle = OnnxCreateFromBuffer(ExtModel, ONNX_DEBUG_LOGS);
|
||||
|
||||
if(onnx_handle == INVALID_HANDLE)
|
||||
{
|
||||
int error = GetLastError();
|
||||
Print("ERROR: Failed to create ONNX model from resource. Error: ", error);
|
||||
Print("Make sure the model file exists at: MQL5\\Files\\EURUSD_M15_model.onnx");
|
||||
Print("Then recompile the EA to embed it as a resource.");
|
||||
return(INIT_FAILED);
|
||||
}
|
||||
|
||||
// Set input shape - per MQL5 documentation
|
||||
const long ExtInputShape[] = {1, InpLookback, 13}; // batch=1, lookback bars, 13 features
|
||||
if(!OnnxSetInputShape(onnx_handle, 0, ExtInputShape))
|
||||
{
|
||||
Print("OnnxSetInputShape failed, error ", GetLastError());
|
||||
OnnxRelease(onnx_handle);
|
||||
return(INIT_FAILED);
|
||||
}
|
||||
|
||||
// Set output shapes - per MQL5 documentation (multi-output: price_change, sl_atr, tp_atr)
|
||||
const long ExtOutputShape0[] = {1, 1}; // batch=1, price change output
|
||||
const long ExtOutputShape1[] = {1, 1}; // batch=1, SL (ATR) output
|
||||
const long ExtOutputShape2[] = {1, 1}; // batch=1, TP (ATR) output
|
||||
|
||||
if(!OnnxSetOutputShape(onnx_handle, 0, ExtOutputShape0))
|
||||
{
|
||||
Print("OnnxSetOutputShape[0] failed, error ", GetLastError());
|
||||
OnnxRelease(onnx_handle);
|
||||
return(INIT_FAILED);
|
||||
}
|
||||
if(!OnnxSetOutputShape(onnx_handle, 1, ExtOutputShape1))
|
||||
{
|
||||
Print("OnnxSetOutputShape[1] failed, error ", GetLastError());
|
||||
OnnxRelease(onnx_handle);
|
||||
return(INIT_FAILED);
|
||||
}
|
||||
if(!OnnxSetOutputShape(onnx_handle, 2, ExtOutputShape2))
|
||||
{
|
||||
Print("OnnxSetOutputShape[2] failed, error ", GetLastError());
|
||||
OnnxRelease(onnx_handle);
|
||||
return(INIT_FAILED);
|
||||
}
|
||||
|
||||
// Get model info
|
||||
long input_count = OnnxGetInputCount(onnx_handle);
|
||||
long output_count = OnnxGetOutputCount(onnx_handle);
|
||||
|
||||
Print("ONNX Model loaded successfully");
|
||||
Print(" Inputs: ", input_count);
|
||||
Print(" Outputs: ", output_count);
|
||||
|
||||
if(input_count > 0)
|
||||
{
|
||||
string input_name = OnnxGetInputName(onnx_handle, 0);
|
||||
Print(" Input name: ", input_name);
|
||||
}
|
||||
|
||||
if(output_count > 0)
|
||||
{
|
||||
string output_name = OnnxGetOutputName(onnx_handle, 0);
|
||||
Print(" Output name: ", output_name);
|
||||
}
|
||||
|
||||
return(INIT_SUCCEEDED);
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Expert deinitialization function |
|
||||
//+------------------------------------------------------------------+
|
||||
void OnDeinit(const int reason)
|
||||
{
|
||||
// Release ONNX model
|
||||
if(onnx_handle != INVALID_HANDLE)
|
||||
{
|
||||
OnnxRelease(onnx_handle);
|
||||
Print("ONNX model released");
|
||||
}
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Expert tick function |
|
||||
//+------------------------------------------------------------------+
|
||||
void OnTick()
|
||||
{
|
||||
// Check if new bar
|
||||
datetime current_bar_time = iTime(_Symbol, PERIOD_CURRENT, 0);
|
||||
if(current_bar_time == last_bar_time)
|
||||
{
|
||||
return; // Still the same bar
|
||||
}
|
||||
last_bar_time = current_bar_time;
|
||||
|
||||
// Check if we should use prediction
|
||||
if(!InpUsePrediction)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Prepare input data
|
||||
float input_data[];
|
||||
if(!PrepareInputData(input_data))
|
||||
{
|
||||
Print("ERROR: Failed to prepare input data");
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if input data is valid
|
||||
if(ArraySize(input_data) != InpLookback * 13)
|
||||
{
|
||||
Print("ERROR: Input data size mismatch. Expected: ", InpLookback * 13, ", Got: ", ArraySize(input_data));
|
||||
return;
|
||||
}
|
||||
|
||||
// Convert flat array to matrixf for OnnxRun
|
||||
// Shape: [lookback, features] = [60, 13] - batch dimension is added automatically
|
||||
matrixf input_matrix;
|
||||
input_matrix.Resize(InpLookback, 13);
|
||||
|
||||
// Fill matrix from flat array
|
||||
int idx = 0;
|
||||
for(int i = 0; i < InpLookback; i++)
|
||||
{
|
||||
for(int j = 0; j < 13; j++)
|
||||
{
|
||||
if(idx >= ArraySize(input_data))
|
||||
{
|
||||
Print("ERROR: Index out of bounds when filling matrix. idx=", idx, ", array size=", ArraySize(input_data));
|
||||
return;
|
||||
}
|
||||
input_matrix[i][j] = input_data[idx++];
|
||||
}
|
||||
}
|
||||
|
||||
// Verify matrix is not empty
|
||||
if(input_matrix.Rows() == 0 || input_matrix.Cols() == 0)
|
||||
{
|
||||
Print("ERROR: Input matrix is empty. Rows: ", input_matrix.Rows(), ", Cols: ", input_matrix.Cols());
|
||||
return;
|
||||
}
|
||||
|
||||
// Run ONNX model - multi-output: price_change, sl_atr, tp_atr
|
||||
vectorf output_price_change(1);
|
||||
vectorf output_sl_atr(1);
|
||||
vectorf output_tp_atr(1);
|
||||
|
||||
if(!RunONNXModel(input_matrix, output_price_change, output_sl_atr, output_tp_atr))
|
||||
{
|
||||
Print("ERROR: Failed to run ONNX model");
|
||||
return;
|
||||
}
|
||||
|
||||
// Get predictions
|
||||
if(output_price_change.Size() == 0 || output_sl_atr.Size() == 0 || output_tp_atr.Size() == 0)
|
||||
{
|
||||
Print("ERROR: Empty output from ONNX model");
|
||||
return;
|
||||
}
|
||||
|
||||
// Model outputs: [price_change_pct, sl_atr_multiple, tp_atr_multiple]
|
||||
double predicted_change_pct = output_price_change[0];
|
||||
double predicted_sl_atr = output_sl_atr[0];
|
||||
double predicted_tp_atr = output_tp_atr[0];
|
||||
|
||||
// Ensure positive values for SL/TP
|
||||
predicted_sl_atr = MathMax(0.5, predicted_sl_atr); // Minimum 0.5 ATR
|
||||
predicted_tp_atr = MathMax(1.0, predicted_tp_atr); // Minimum 1.0 ATR
|
||||
|
||||
double current_price = SymbolInfoDouble(_Symbol, SYMBOL_BID);
|
||||
|
||||
// Check if prediction is percentage (between -1 and 1) or absolute price (old format)
|
||||
double price_change_pct;
|
||||
double predicted_price;
|
||||
|
||||
if(MathAbs(predicted_change_pct) < 1.0)
|
||||
{
|
||||
// New format: percentage (e.g., -0.003 = -0.3%)
|
||||
price_change_pct = predicted_change_pct * 100.0; // Convert to percentage
|
||||
predicted_price = current_price * (1.0 + predicted_change_pct); // Calculate predicted price
|
||||
}
|
||||
else
|
||||
{
|
||||
// Old format: absolute price
|
||||
predicted_price = predicted_change_pct;
|
||||
double price_change = predicted_price - current_price;
|
||||
price_change_pct = (price_change / current_price) * 100.0;
|
||||
}
|
||||
|
||||
// Calculate confidence (for percentage predictions: 0.001 = 0.1% = 10% confidence)
|
||||
double confidence;
|
||||
if(MathAbs(price_change_pct) < 1.0)
|
||||
{
|
||||
// It's a decimal percentage (e.g., 0.001 = 0.1%)
|
||||
confidence = MathMin(MathAbs(predicted_change_pct) / 0.01, 1.0); // 0.01 = 1% = 100% confidence
|
||||
}
|
||||
else
|
||||
{
|
||||
// It's already in percentage form
|
||||
confidence = MathMin(MathAbs(price_change_pct) / 1.0, 1.0);
|
||||
}
|
||||
|
||||
last_prediction = predicted_price;
|
||||
last_confidence = confidence;
|
||||
|
||||
// Calculate ATR for converting model's ATR multiples to actual price distances
|
||||
double atr_value = 0.0;
|
||||
double atr_array[];
|
||||
ArraySetAsSeries(atr_array, true);
|
||||
int atr_handle = iATR(_Symbol, PERIOD_CURRENT, 14);
|
||||
if(atr_handle != INVALID_HANDLE)
|
||||
{
|
||||
if(CopyBuffer(atr_handle, 0, 0, 1, atr_array) > 0)
|
||||
{
|
||||
atr_value = atr_array[0];
|
||||
}
|
||||
IndicatorRelease(atr_handle);
|
||||
}
|
||||
|
||||
// Use model's predicted SL/TP (in ATR multiples) directly
|
||||
double predicted_sl = 0.0;
|
||||
double predicted_tp = 0.0;
|
||||
if(InpUsePredictedSLTP && atr_value > 0)
|
||||
{
|
||||
// Model predicts SL and TP in ATR multiples - convert to price distance
|
||||
predicted_sl = atr_value * predicted_sl_atr;
|
||||
predicted_tp = atr_value * predicted_tp_atr;
|
||||
|
||||
// Ensure minimums from input parameters
|
||||
predicted_sl = MathMax(predicted_sl, atr_value * InpMinSLATR);
|
||||
predicted_tp = MathMax(predicted_tp, atr_value * InpMinTPATR);
|
||||
|
||||
// Ensure TP is at least 1.5x SL for risk/reward
|
||||
if(predicted_tp < predicted_sl * 1.5)
|
||||
{
|
||||
predicted_tp = predicted_sl * 1.5;
|
||||
}
|
||||
|
||||
// Get broker's stop level requirements
|
||||
int stop_level = (int)SymbolInfoInteger(_Symbol, SYMBOL_TRADE_STOPS_LEVEL);
|
||||
double point = SymbolInfoDouble(_Symbol, SYMBOL_POINT);
|
||||
double min_stop_distance = stop_level * point;
|
||||
|
||||
// Ensure SL/TP meet broker's minimum stop level
|
||||
if(predicted_sl < min_stop_distance)
|
||||
{
|
||||
predicted_sl = min_stop_distance;
|
||||
}
|
||||
if(predicted_tp < min_stop_distance)
|
||||
{
|
||||
predicted_tp = min_stop_distance;
|
||||
}
|
||||
}
|
||||
|
||||
// Log prediction
|
||||
Print("Prediction: Current=", current_price,
|
||||
" Predicted Change=", price_change_pct, "%",
|
||||
" Predicted Price=", predicted_price,
|
||||
" Confidence=", confidence,
|
||||
" ATR=", atr_value);
|
||||
if(InpUsePredictedSLTP && predicted_sl > 0 && predicted_tp > 0)
|
||||
{
|
||||
Print(" Model SL=", predicted_sl_atr, "x ATR (", predicted_sl, " points, ", predicted_sl/current_price*100, "%)",
|
||||
" Model TP=", predicted_tp_atr, "x ATR (", predicted_tp, " points, ", predicted_tp/current_price*100, "%)");
|
||||
}
|
||||
|
||||
// Check if we should trade
|
||||
if(!InpUseConfidence || confidence >= InpMinConfidence)
|
||||
{
|
||||
// Check if prediction is significant
|
||||
// price_change_pct is in percentage (e.g., 5.72 = 5.72%)
|
||||
// InpPredictionThreshold is in decimal (e.g., 0.00005 = 0.005%)
|
||||
// Convert threshold to percentage for comparison
|
||||
double threshold_pct = InpPredictionThreshold * 100.0;
|
||||
double abs_change_pct = MathAbs(price_change_pct); // Already in percentage
|
||||
|
||||
Print("Trade Check: Change=", price_change_pct, "% Threshold=", threshold_pct, "% Confidence=", confidence);
|
||||
|
||||
if(abs_change_pct >= threshold_pct)
|
||||
{
|
||||
// Check existing position
|
||||
if(PositionSelect(_Symbol))
|
||||
{
|
||||
// Manage existing position
|
||||
ManagePosition(predicted_price, price_change_pct);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Open new position based on prediction
|
||||
if(price_change_pct > threshold_pct)
|
||||
{
|
||||
Print(">>> Opening BUY position: Change=", price_change_pct, "% Threshold=", threshold_pct, "%");
|
||||
OpenBuyPosition(predicted_sl, predicted_tp);
|
||||
}
|
||||
else if(price_change_pct < -threshold_pct)
|
||||
{
|
||||
Print(">>> Opening SELL position: Change=", price_change_pct, "% Threshold=", threshold_pct, "%");
|
||||
OpenSellPosition(predicted_sl, predicted_tp);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Print("Prediction below threshold: Change=", price_change_pct, "% < Threshold=", threshold_pct, "%");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Print("Confidence too low: ", confidence, " < ", InpMinConfidence);
|
||||
}
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Prepare input data for ONNX model |
|
||||
//+------------------------------------------------------------------+
|
||||
bool PrepareInputData(float &input_array[])
|
||||
{
|
||||
int lookback = InpLookback;
|
||||
int features = 13; // OHLC(4) + volume(1) + RSI(1) + EMA20(1) + EMA50(1) + ATR(1) + price_change(1) + high_low_ratio(1) + volume_ma(1) + volume_ratio(1) = 13
|
||||
|
||||
ArrayResize(input_array, lookback * features);
|
||||
ArrayInitialize(input_array, 0.0);
|
||||
|
||||
// Get historical data
|
||||
double open[], high[], low[], close[];
|
||||
long volume[]; // CopyTickVolume requires long[] not double[]
|
||||
ArraySetAsSeries(open, true);
|
||||
ArraySetAsSeries(high, true);
|
||||
ArraySetAsSeries(low, true);
|
||||
ArraySetAsSeries(close, true);
|
||||
ArraySetAsSeries(volume, true);
|
||||
|
||||
int copied_open = CopyOpen(_Symbol, PERIOD_CURRENT, 0, lookback + 50, open);
|
||||
if(copied_open < lookback)
|
||||
{
|
||||
Print("ERROR: CopyOpen failed. Got ", copied_open, " bars, need ", lookback);
|
||||
return false;
|
||||
}
|
||||
int copied_high = CopyHigh(_Symbol, PERIOD_CURRENT, 0, lookback + 50, high);
|
||||
if(copied_high < lookback)
|
||||
{
|
||||
Print("ERROR: CopyHigh failed. Got ", copied_high, " bars, need ", lookback);
|
||||
return false;
|
||||
}
|
||||
int copied_low = CopyLow(_Symbol, PERIOD_CURRENT, 0, lookback + 50, low);
|
||||
if(copied_low < lookback)
|
||||
{
|
||||
Print("ERROR: CopyLow failed. Got ", copied_low, " bars, need ", lookback);
|
||||
return false;
|
||||
}
|
||||
int copied_close = CopyClose(_Symbol, PERIOD_CURRENT, 0, lookback + 50, close);
|
||||
if(copied_close < lookback)
|
||||
{
|
||||
Print("ERROR: CopyClose failed. Got ", copied_close, " bars, need ", lookback);
|
||||
return false;
|
||||
}
|
||||
int copied_volume = CopyTickVolume(_Symbol, PERIOD_CURRENT, 0, lookback + 50, volume);
|
||||
if(copied_volume < lookback)
|
||||
{
|
||||
Print("ERROR: CopyTickVolume failed. Got ", copied_volume, " bars, need ", lookback);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Calculate indicators
|
||||
double rsi[], ema20[], ema50[], atr[];
|
||||
ArraySetAsSeries(rsi, true);
|
||||
ArraySetAsSeries(ema20, true);
|
||||
ArraySetAsSeries(ema50, true);
|
||||
ArraySetAsSeries(atr, true);
|
||||
|
||||
// Calculate RSI
|
||||
int rsi_handle = iRSI(_Symbol, PERIOD_CURRENT, 14, PRICE_CLOSE);
|
||||
if(rsi_handle == INVALID_HANDLE) return false;
|
||||
if(CopyBuffer(rsi_handle, 0, 0, lookback + 50, rsi) < lookback)
|
||||
{
|
||||
IndicatorRelease(rsi_handle);
|
||||
return false;
|
||||
}
|
||||
IndicatorRelease(rsi_handle);
|
||||
|
||||
// Calculate EMAs
|
||||
int ema20_handle = iMA(_Symbol, PERIOD_CURRENT, 20, 0, MODE_EMA, PRICE_CLOSE);
|
||||
int ema50_handle = iMA(_Symbol, PERIOD_CURRENT, 50, 0, MODE_EMA, PRICE_CLOSE);
|
||||
if(ema20_handle == INVALID_HANDLE || ema50_handle == INVALID_HANDLE) return false;
|
||||
|
||||
if(CopyBuffer(ema20_handle, 0, 0, lookback + 50, ema20) < lookback ||
|
||||
CopyBuffer(ema50_handle, 0, 0, lookback + 50, ema50) < lookback)
|
||||
{
|
||||
IndicatorRelease(ema20_handle);
|
||||
IndicatorRelease(ema50_handle);
|
||||
return false;
|
||||
}
|
||||
IndicatorRelease(ema20_handle);
|
||||
IndicatorRelease(ema50_handle);
|
||||
|
||||
// Calculate ATR
|
||||
int atr_handle = iATR(_Symbol, PERIOD_CURRENT, 14);
|
||||
if(atr_handle == INVALID_HANDLE) return false;
|
||||
if(CopyBuffer(atr_handle, 0, 0, lookback + 50, atr) < lookback)
|
||||
{
|
||||
IndicatorRelease(atr_handle);
|
||||
return false;
|
||||
}
|
||||
IndicatorRelease(atr_handle);
|
||||
|
||||
// Calculate volume MA for normalization
|
||||
double volume_ma[];
|
||||
ArraySetAsSeries(volume_ma, true);
|
||||
ArrayResize(volume_ma, lookback);
|
||||
ArrayInitialize(volume_ma, 0.0);
|
||||
|
||||
// Calculate volume MA (20-period rolling average)
|
||||
for(int j = 0; j < lookback; j++)
|
||||
{
|
||||
double sum = 0.0;
|
||||
int count = 0;
|
||||
for(int k = j; k < j + 20 && k < ArraySize(volume); k++)
|
||||
{
|
||||
sum += (double)volume[k];
|
||||
count++;
|
||||
}
|
||||
volume_ma[j] = count > 0 ? sum / count : (double)volume[j];
|
||||
}
|
||||
|
||||
// Prepare features - MUST match Python training exactly (13 features)
|
||||
int idx = 0;
|
||||
for(int i = 0; i < lookback; i++)
|
||||
{
|
||||
// Feature 1-4: OHLC
|
||||
input_array[idx++] = (float)open[i];
|
||||
input_array[idx++] = (float)high[i];
|
||||
input_array[idx++] = (float)low[i];
|
||||
input_array[idx++] = (float)close[i];
|
||||
|
||||
// Feature 5: Volume (normalized by 1,000,000)
|
||||
input_array[idx++] = (float)((double)volume[i] / 1000000.0);
|
||||
|
||||
// Feature 6: RSI (normalized by 100)
|
||||
input_array[idx++] = (float)(rsi[i] / 100.0);
|
||||
|
||||
// Feature 7: EMA20 normalized difference
|
||||
input_array[idx++] = (float)((ema20[i] - close[i]) / close[i]);
|
||||
|
||||
// Feature 8: EMA50 normalized difference
|
||||
input_array[idx++] = (float)((ema50[i] - close[i]) / close[i]);
|
||||
|
||||
// Feature 9: ATR normalized
|
||||
input_array[idx++] = (float)(atr[i] / close[i]);
|
||||
|
||||
// Feature 10: Price change (percentage)
|
||||
double price_change = i > 0 ? (close[i] - close[i+1]) / close[i+1] : 0.0;
|
||||
input_array[idx++] = (float)price_change;
|
||||
|
||||
// Feature 11: High/Low ratio
|
||||
input_array[idx++] = (float)(high[i] / low[i]);
|
||||
|
||||
// Feature 12: Volume MA (normalized by 1,000,000)
|
||||
input_array[idx++] = (float)(volume_ma[i] / 1000000.0);
|
||||
|
||||
// Feature 13: Volume ratio
|
||||
double vol_ratio = volume_ma[i] > 0 ? (double)volume[i] / volume_ma[i] : 1.0;
|
||||
input_array[idx++] = (float)vol_ratio;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Run ONNX model |
|
||||
//+------------------------------------------------------------------+
|
||||
bool RunONNXModel(matrixf &input_matrix, vectorf &output_price_change, vectorf &output_sl_atr, vectorf &output_tp_atr)
|
||||
{
|
||||
if(onnx_handle == INVALID_HANDLE)
|
||||
return false;
|
||||
|
||||
// For multi-output ONNX models in MQL5, OnnxRun expects all outputs as separate parameters
|
||||
// Function signature: OnnxRun(handle, flags, input, output1, output2, output3, ...)
|
||||
// This is 4 parameters total: handle, flags, input, and then all outputs
|
||||
|
||||
if(!OnnxRun(onnx_handle, ONNX_DEBUG_LOGS | ONNX_NO_CONVERSION, input_matrix,
|
||||
output_price_change, output_sl_atr, output_tp_atr))
|
||||
{
|
||||
Print("ERROR: Failed to run ONNX model. Error: ", GetLastError());
|
||||
Print("Model has ", OnnxGetOutputCount(onnx_handle), " outputs");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Open buy position |
|
||||
//+------------------------------------------------------------------+
|
||||
void OpenBuyPosition(double predicted_sl = 0.0, double predicted_tp = 0.0)
|
||||
{
|
||||
double price = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
|
||||
double sl = 0.0;
|
||||
double tp = 0.0;
|
||||
|
||||
// Get broker's stop level requirements
|
||||
int stop_level = (int)SymbolInfoInteger(_Symbol, SYMBOL_TRADE_STOPS_LEVEL);
|
||||
double point = SymbolInfoDouble(_Symbol, SYMBOL_POINT);
|
||||
double min_stop_distance = stop_level * point;
|
||||
|
||||
if(InpUsePredictedSLTP && predicted_sl > 0 && predicted_tp > 0)
|
||||
{
|
||||
// Use predicted SL/TP
|
||||
// For BUY: SL below entry, TP above entry
|
||||
sl = price - predicted_sl;
|
||||
tp = price + predicted_tp;
|
||||
|
||||
// Validate stops meet broker requirements
|
||||
if(sl > 0 && (price - sl) < min_stop_distance)
|
||||
{
|
||||
sl = price - min_stop_distance;
|
||||
}
|
||||
if(tp > 0 && (tp - price) < min_stop_distance)
|
||||
{
|
||||
tp = price + min_stop_distance;
|
||||
}
|
||||
|
||||
Print("Using predicted SL/TP: Entry=", price, " SL=", sl, " TP=", tp, " (SL distance: ", price - sl, ", TP distance: ", tp - price, ")");
|
||||
}
|
||||
else
|
||||
{
|
||||
// Use fixed SL/TP from input parameters
|
||||
sl = InpStopLoss > 0 ? price - InpStopLoss * _Point * 10 : 0;
|
||||
tp = InpTakeProfit > 0 ? price + InpTakeProfit * _Point * 10 : 0;
|
||||
}
|
||||
|
||||
if(trade.Buy(InpLotSize, _Symbol, price, sl, tp, "ONNX Buy Signal"))
|
||||
{
|
||||
Print("Buy order opened. Ticket: ", trade.ResultOrder(), " Price: ", price, " SL: ", sl, " TP: ", tp);
|
||||
}
|
||||
else
|
||||
{
|
||||
Print("Failed to open buy order. Error: ", trade.ResultRetcodeDescription());
|
||||
Print(" Entry: ", price, " SL: ", sl, " TP: ", tp);
|
||||
Print(" Stop Level: ", stop_level, " Min Distance: ", min_stop_distance);
|
||||
}
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Open sell position |
|
||||
//+------------------------------------------------------------------+
|
||||
void OpenSellPosition(double predicted_sl = 0.0, double predicted_tp = 0.0)
|
||||
{
|
||||
double price = SymbolInfoDouble(_Symbol, SYMBOL_BID);
|
||||
double sl = 0.0;
|
||||
double tp = 0.0;
|
||||
|
||||
// Get broker's stop level requirements
|
||||
int stop_level = (int)SymbolInfoInteger(_Symbol, SYMBOL_TRADE_STOPS_LEVEL);
|
||||
double point = SymbolInfoDouble(_Symbol, SYMBOL_POINT);
|
||||
double min_stop_distance = stop_level * point;
|
||||
|
||||
if(InpUsePredictedSLTP && predicted_sl > 0 && predicted_tp > 0)
|
||||
{
|
||||
// Use predicted SL/TP
|
||||
// For SELL: SL above entry, TP below entry
|
||||
sl = price + predicted_sl;
|
||||
tp = price - predicted_tp;
|
||||
|
||||
// Validate stops meet broker requirements
|
||||
if(sl > 0 && (sl - price) < min_stop_distance)
|
||||
{
|
||||
sl = price + min_stop_distance;
|
||||
}
|
||||
if(tp > 0 && (price - tp) < min_stop_distance)
|
||||
{
|
||||
tp = price - min_stop_distance;
|
||||
}
|
||||
|
||||
Print("Using predicted SL/TP: Entry=", price, " SL=", sl, " TP=", tp, " (SL distance: ", sl - price, ", TP distance: ", price - tp, ")");
|
||||
}
|
||||
else
|
||||
{
|
||||
// Use fixed SL/TP from input parameters
|
||||
sl = InpStopLoss > 0 ? price + InpStopLoss * _Point * 10 : 0;
|
||||
tp = InpTakeProfit > 0 ? price - InpTakeProfit * _Point * 10 : 0;
|
||||
}
|
||||
|
||||
if(trade.Sell(InpLotSize, _Symbol, price, sl, tp, "ONNX Sell Signal"))
|
||||
{
|
||||
Print("Sell order opened. Ticket: ", trade.ResultOrder(), " Price: ", price, " SL: ", sl, " TP: ", tp);
|
||||
}
|
||||
else
|
||||
{
|
||||
Print("Failed to open sell order. Error: ", trade.ResultRetcodeDescription());
|
||||
Print(" Entry: ", price, " SL: ", sl, " TP: ", tp);
|
||||
Print(" Stop Level: ", stop_level, " Min Distance: ", min_stop_distance);
|
||||
}
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Manage existing position |
|
||||
//+------------------------------------------------------------------+
|
||||
void ManagePosition(double predicted_price, double price_change_pct)
|
||||
{
|
||||
if(!PositionSelect(_Symbol))
|
||||
return;
|
||||
|
||||
// Simple position management - can be enhanced
|
||||
// For now, just log the position status
|
||||
double position_profit = PositionGetDouble(POSITION_PROFIT);
|
||||
Print("Position exists. Profit: ", position_profit, " Predicted change: ", price_change_pct, "%");
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
# EURUSD 15-Minute ONNX Model Training
|
||||
|
||||
This directory contains the training script for a EURUSD price prediction model using 15-minute timeframe data from 1990 to 2026 using MetaTrader 5 as the data source.
|
||||
|
||||
## Requirements
|
||||
|
||||
```bash
|
||||
pip install MetaTrader5 tensorflow scikit-learn pandas numpy tf2onnx onnx tqdm
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
1. **Make sure MetaTrader 5 is running and logged in**
|
||||
2. **Ensure EURUSD symbol is available in your broker**
|
||||
3. **Make sure you have historical data downloaded in MT5** (Tools → History Center → Download)
|
||||
|
||||
4. **Run the training script:**
|
||||
```bash
|
||||
cd ai/eurusd1min
|
||||
python main.py
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
The script is configured with:
|
||||
- **Symbol**: EURUSD
|
||||
- **Timeframe**: M15 (15 minutes)
|
||||
- **Lookback**: 60 bars (15 hours of history)
|
||||
- **Date Range**: 1990-01-01 to 2026-01-01
|
||||
- **Model Architecture**: LSTM with 3 layers (128, 64, 32 units)
|
||||
- **Epochs**: 50 (with early stopping)
|
||||
- **Batch Size**: 64
|
||||
|
||||
## Data Fetching
|
||||
|
||||
- The script fetches data in **1-month chunks** to manage memory
|
||||
- 15-minute data is more likely to be available for longer historical periods than 1-minute data
|
||||
- The script automatically skips chunks with only 1 bar (invalid/placeholder data)
|
||||
- Progress is shown for each chunk
|
||||
- Make sure you have sufficient historical data in MT5
|
||||
|
||||
## Output
|
||||
|
||||
The script will create:
|
||||
- `models/EURUSD_M15_model.onnx` - The trained ONNX model
|
||||
- `models/EURUSD_M15_model_scaler.pkl` - The MinMaxScaler used for normalization
|
||||
|
||||
## Model Features
|
||||
|
||||
The model uses 13 features:
|
||||
1. Open
|
||||
2. High
|
||||
3. Low
|
||||
4. Close
|
||||
5. Tick Volume
|
||||
6. RSI (14 period)
|
||||
7. EMA 20
|
||||
8. EMA 50
|
||||
9. ATR (14 period)
|
||||
10. Price Change (percentage)
|
||||
11. High/Low Ratio
|
||||
12. Volume MA (20 period)
|
||||
13. Volume Ratio
|
||||
|
||||
## Model Output
|
||||
|
||||
The model predicts the **price change percentage** for the next bar (15 minutes ahead).
|
||||
|
||||
## Notes
|
||||
|
||||
- Training on 36 years of 15-minute data will take significant time and memory
|
||||
- The script fetches data in 1-month chunks to manage memory
|
||||
- Chunks with only 1 bar are automatically skipped (invalid/placeholder data)
|
||||
- Early stopping and learning rate reduction are enabled to prevent overfitting
|
||||
- The model uses dropout (0.3) for regularization
|
||||
- 15-minute data is more manageable than 1-minute data for long historical periods
|
||||
|
||||
## Using the Model in MQL5
|
||||
|
||||
### Expert Advisor
|
||||
|
||||
An Expert Advisor (`EURUSD_M15_EA.mq5`) is provided in this directory. It uses the trained ONNX model for automated trading.
|
||||
|
||||
**Setup:**
|
||||
1. Copy `EURUSD_M15_model.onnx` to `MQL5/Files/` directory
|
||||
2. Compile `EURUSD_M15_EA.mq5` in MetaEditor
|
||||
3. The model will be embedded as a resource during compilation
|
||||
4. Attach the EA to a EURUSD chart with M15 timeframe
|
||||
|
||||
**Features:**
|
||||
- Embedded ONNX model (no file path issues)
|
||||
- Dynamic SL/TP based on prediction, confidence, and ATR
|
||||
- Configurable prediction threshold and confidence filter
|
||||
- Automatic position management
|
||||
|
||||
**Input Parameters:**
|
||||
- `InpLookback`: 60 bars (15 hours of history)
|
||||
- `InpUsePredictedSLTP`: Use dynamic SL/TP based on prediction
|
||||
- `InpPredictionThreshold`: Minimum prediction change to trade (default: 0.00005 = 0.005%)
|
||||
- `InpMinConfidence`: Minimum confidence to trade (default: 0.1 = 10%)
|
||||
@@ -0,0 +1,655 @@
|
||||
"""
|
||||
ONNX Model Training Script for EURUSD 15-Minute Data
|
||||
MetaTrader 5
|
||||
|
||||
This script trains a neural network model for EURUSD price prediction on 15-minute timeframe
|
||||
and exports it to ONNX format.
|
||||
|
||||
Usage:
|
||||
python main.py
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
from datetime import datetime, timedelta
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
import MetaTrader5 as mt5
|
||||
import tensorflow as tf
|
||||
from tensorflow import keras
|
||||
from tensorflow.keras import layers
|
||||
from sklearn.preprocessing import MinMaxScaler
|
||||
from sklearn.model_selection import train_test_split
|
||||
import tf2onnx
|
||||
import onnx
|
||||
from tqdm import tqdm
|
||||
import pickle
|
||||
|
||||
|
||||
class EURUSD15MinTrainer:
|
||||
"""
|
||||
Trainer class for creating ONNX models from EURUSD 15-minute MT5 data.
|
||||
"""
|
||||
|
||||
def __init__(self, symbol: str = "EURUSD", timeframe: int = mt5.TIMEFRAME_M15,
|
||||
lookback: int = 60, prediction_horizon: int = 1):
|
||||
"""
|
||||
Initialize the trainer.
|
||||
|
||||
Args:
|
||||
symbol: Trading symbol (default: 'EURUSD')
|
||||
timeframe: MT5 timeframe constant (default: M15)
|
||||
lookback: Number of bars to look back for prediction (default: 60 = 15 hours)
|
||||
prediction_horizon: Number of bars ahead to predict (default: 1)
|
||||
"""
|
||||
self.symbol = symbol
|
||||
self.timeframe = timeframe
|
||||
self.lookback = lookback
|
||||
self.prediction_horizon = prediction_horizon
|
||||
|
||||
self.scaler = MinMaxScaler()
|
||||
self.model = None
|
||||
|
||||
# Initialize MT5
|
||||
if not mt5.initialize():
|
||||
raise RuntimeError(f"MT5 initialization failed: {mt5.last_error()}")
|
||||
|
||||
print(f"MT5 initialized. Connected to: {mt5.terminal_info().name}")
|
||||
|
||||
# Check if symbol is available
|
||||
symbol_info = mt5.symbol_info(self.symbol)
|
||||
if symbol_info is None:
|
||||
print(f"WARNING: Symbol {self.symbol} not found. Available symbols:")
|
||||
symbols = mt5.symbols_get()
|
||||
if symbols:
|
||||
for i, sym in enumerate(symbols[:10]): # Show first 10
|
||||
print(f" {sym.name}")
|
||||
raise ValueError(f"Symbol {self.symbol} not available in MT5")
|
||||
|
||||
if not symbol_info.visible:
|
||||
print(f"WARNING: Symbol {self.symbol} is not visible. Trying to enable...")
|
||||
if not mt5.symbol_select(self.symbol, True):
|
||||
raise ValueError(f"Failed to enable symbol {self.symbol}")
|
||||
|
||||
def fetch_data(self, start_date: datetime, end_date: datetime) -> pd.DataFrame:
|
||||
"""
|
||||
Fetch historical data from MT5.
|
||||
|
||||
Args:
|
||||
start_date: Start date for data
|
||||
end_date: End date for data
|
||||
|
||||
Returns:
|
||||
DataFrame with OHLCV data
|
||||
"""
|
||||
print(f"\nFetching {self.symbol} 15-minute data from {start_date.strftime('%Y-%m-%d')} to {end_date.strftime('%Y-%m-%d')}...")
|
||||
print("Note: MT5 typically has 15-minute data available for longer periods than 1-minute data.")
|
||||
|
||||
# First, try to find the actual date range with data
|
||||
# Start from end_date and work backwards to find where data starts
|
||||
print("\nFinding available data range...")
|
||||
test_end = end_date
|
||||
test_start = end_date - timedelta(days=365) # Check last year first
|
||||
|
||||
test_rates = mt5.copy_rates_range(self.symbol, self.timeframe, test_start, test_end)
|
||||
if test_rates is None or len(test_rates) == 0:
|
||||
# Try even more recent
|
||||
test_start = end_date - timedelta(days=30)
|
||||
test_rates = mt5.copy_rates_range(self.symbol, self.timeframe, test_start, test_end)
|
||||
|
||||
if test_rates is None or len(test_rates) == 0:
|
||||
raise ValueError(f"No 15-minute data available for {self.symbol}. Make sure:")
|
||||
print(" 1. Historical data is downloaded in MT5 (Tools → History Center)")
|
||||
print(" 2. The symbol is available and enabled")
|
||||
print(" 3. You have 15-minute data for the requested period")
|
||||
|
||||
# Find actual data range by checking from end backwards
|
||||
actual_start = end_date
|
||||
chunk_size_days = 30
|
||||
|
||||
# Work backwards to find where data actually starts
|
||||
print("Scanning backwards to find data availability...")
|
||||
for days_back in range(0, 365*2, chunk_size_days): # Check up to 2 years back
|
||||
check_start = end_date - timedelta(days=days_back + chunk_size_days)
|
||||
check_end = end_date - timedelta(days=days_back)
|
||||
test_rates = mt5.copy_rates_range(self.symbol, self.timeframe, check_start, check_end)
|
||||
if test_rates is not None and len(test_rates) > 10: # More than just 1 bar
|
||||
actual_start = check_start
|
||||
print(f" Found data starting from: {actual_start.strftime('%Y-%m-%d')}")
|
||||
break
|
||||
|
||||
# Now fetch all available data
|
||||
print(f"\nFetching available data from {actual_start.strftime('%Y-%m-%d')} to {end_date.strftime('%Y-%m-%d')}...")
|
||||
all_rates = []
|
||||
current_start = actual_start
|
||||
|
||||
# Fetch in chunks (1 month at a time for 15-minute data)
|
||||
chunk_days = 30 # 1 month chunks for 15-minute data
|
||||
|
||||
chunks_with_data = 0
|
||||
chunks_without_data = 0
|
||||
|
||||
while current_start < end_date:
|
||||
chunk_end = min(current_start + timedelta(days=chunk_days), end_date)
|
||||
|
||||
rates = mt5.copy_rates_range(self.symbol, self.timeframe, current_start, chunk_end)
|
||||
|
||||
if rates is None or len(rates) == 0:
|
||||
chunks_without_data += 1
|
||||
current_start = chunk_end
|
||||
continue
|
||||
|
||||
# Skip chunks with only 1 bar (likely invalid/placeholder data)
|
||||
if len(rates) <= 1:
|
||||
chunks_without_data += 1
|
||||
current_start = chunk_end
|
||||
continue
|
||||
|
||||
chunks_with_data += 1
|
||||
|
||||
# MT5 returns structured numpy array - convert properly
|
||||
if isinstance(rates, np.ndarray) and rates.dtype.names:
|
||||
# Structured array - convert each row to dict
|
||||
for row in rates:
|
||||
all_rates.append({name: row[name] for name in rates.dtype.names})
|
||||
else:
|
||||
# Already a list or regular array
|
||||
all_rates.extend(rates if isinstance(rates, list) else rates.tolist())
|
||||
|
||||
if chunks_with_data % 10 == 0:
|
||||
print(f" Progress: {chunks_with_data} chunks with data, {len(all_rates)} total bars")
|
||||
|
||||
current_start = chunk_end
|
||||
|
||||
print(f"\nData fetch complete: {chunks_with_data} chunks with data, {chunks_without_data} chunks skipped")
|
||||
|
||||
if len(all_rates) == 0:
|
||||
raise ValueError(f"No data available for {self.symbol} in the specified date range")
|
||||
|
||||
# Convert to DataFrame
|
||||
df = pd.DataFrame(all_rates)
|
||||
|
||||
# MT5 returns 'time' field - convert from Unix timestamp to datetime
|
||||
if 'time' in df.columns:
|
||||
df['time'] = pd.to_datetime(df['time'], unit='s')
|
||||
df.set_index('time', inplace=True)
|
||||
else:
|
||||
# Debug: print available columns
|
||||
print(f"Available columns: {df.columns.tolist()}")
|
||||
print(f"First row sample: {df.iloc[0] if len(df) > 0 else 'Empty'}")
|
||||
raise ValueError(f"Could not find 'time' column in MT5 data. Available columns: {df.columns.tolist()}")
|
||||
|
||||
# Remove duplicates
|
||||
df = df[~df.index.duplicated(keep='first')]
|
||||
df = df.sort_index()
|
||||
|
||||
print(f"\nTotal fetched: {len(df)} bars")
|
||||
if len(df) > 0:
|
||||
print(f"Date range: {df.index[0]} to {df.index[-1]}")
|
||||
print(f"Timeframe: {df.index[1] - df.index[0] if len(df) > 1 else 'N/A'}")
|
||||
else:
|
||||
raise ValueError("DataFrame is empty after processing")
|
||||
|
||||
return df
|
||||
|
||||
def prepare_features(self, df: pd.DataFrame) -> pd.DataFrame:
|
||||
"""
|
||||
Prepare features for training.
|
||||
|
||||
Args:
|
||||
df: Raw OHLCV data
|
||||
|
||||
Returns:
|
||||
DataFrame with features
|
||||
"""
|
||||
print("\nPreparing features...")
|
||||
|
||||
feature_df = df[['open', 'high', 'low', 'close', 'tick_volume']].copy()
|
||||
|
||||
# Add technical indicators as features
|
||||
print(" Calculating RSI...")
|
||||
feature_df['rsi'] = self._calculate_rsi(df['close'], period=14)
|
||||
|
||||
print(" Calculating EMAs...")
|
||||
feature_df['ema_20'] = df['close'].ewm(span=20, adjust=False).mean()
|
||||
feature_df['ema_50'] = df['close'].ewm(span=50, adjust=False).mean()
|
||||
|
||||
print(" Calculating ATR...")
|
||||
feature_df['atr'] = self._calculate_atr(df, period=14)
|
||||
|
||||
# Price changes
|
||||
feature_df['price_change'] = df['close'].pct_change()
|
||||
feature_df['high_low_ratio'] = df['high'] / df['low']
|
||||
|
||||
# Volume features
|
||||
feature_df['volume_ma'] = df['tick_volume'].rolling(window=20).mean()
|
||||
feature_df['volume_ratio'] = df['tick_volume'] / feature_df['volume_ma']
|
||||
|
||||
# Drop NaN values
|
||||
feature_df = feature_df.dropna()
|
||||
|
||||
print(f" Features prepared: {len(feature_df)} samples, {len(feature_df.columns)} features")
|
||||
print(f" Features: {list(feature_df.columns)}")
|
||||
|
||||
return feature_df
|
||||
|
||||
def _calculate_rsi(self, prices: pd.Series, period: int = 14) -> pd.Series:
|
||||
"""Calculate RSI indicator."""
|
||||
delta = prices.diff()
|
||||
gain = (delta.where(delta > 0, 0)).rolling(window=period).mean()
|
||||
loss = (-delta.where(delta < 0, 0)).rolling(window=period).mean()
|
||||
rs = gain / loss
|
||||
rsi = 100 - (100 / (1 + rs))
|
||||
return rsi
|
||||
|
||||
def _calculate_atr(self, df: pd.DataFrame, period: int = 14) -> pd.Series:
|
||||
"""Calculate ATR indicator."""
|
||||
high_low = df['high'] - df['low']
|
||||
high_close = np.abs(df['high'] - df['close'].shift())
|
||||
low_close = np.abs(df['low'] - df['close'].shift())
|
||||
tr = pd.concat([high_low, high_close, low_close], axis=1).max(axis=1)
|
||||
atr = tr.rolling(window=period).mean()
|
||||
return atr
|
||||
|
||||
def create_sequences(self, data: np.ndarray, target: np.ndarray) -> tuple:
|
||||
"""
|
||||
Create sequences for LSTM/RNN training.
|
||||
|
||||
Args:
|
||||
data: Feature data
|
||||
target: Target values (price_change, sl_atr, tp_atr) - shape (n_samples, 3)
|
||||
|
||||
Returns:
|
||||
Tuple of (X, y) sequences
|
||||
"""
|
||||
print("\nCreating sequences...")
|
||||
X, y = [], []
|
||||
|
||||
for i in tqdm(range(self.lookback, len(data) - self.prediction_horizon + 1), desc="Creating sequences"):
|
||||
X.append(data[i - self.lookback:i])
|
||||
# Target is already aligned with data index
|
||||
y.append(target[i])
|
||||
|
||||
X = np.array(X)
|
||||
y = np.array(y)
|
||||
|
||||
print(f" Sequences created: X shape {X.shape}, y shape {y.shape}")
|
||||
|
||||
return X, y
|
||||
|
||||
def build_model(self, input_shape: tuple) -> keras.Model:
|
||||
"""
|
||||
Build the neural network model with multi-output (price change, SL, TP).
|
||||
|
||||
Args:
|
||||
input_shape: Shape of input data (lookback, features)
|
||||
|
||||
Returns:
|
||||
Compiled Keras model
|
||||
"""
|
||||
print(f"\nBuilding multi-output model with input shape: {input_shape}")
|
||||
|
||||
# Shared LSTM layers
|
||||
inputs = layers.Input(shape=input_shape)
|
||||
x = layers.LSTM(128, return_sequences=True)(inputs)
|
||||
x = layers.Dropout(0.3)(x)
|
||||
x = layers.LSTM(64, return_sequences=True)(x)
|
||||
x = layers.Dropout(0.3)(x)
|
||||
x = layers.LSTM(32)(x)
|
||||
x = layers.Dropout(0.3)(x)
|
||||
|
||||
# Shared dense layers
|
||||
shared = layers.Dense(32, activation='relu')(x)
|
||||
shared = layers.Dense(16, activation='relu')(shared)
|
||||
|
||||
# Separate outputs
|
||||
# Output 1: Price change percentage
|
||||
price_change = layers.Dense(8, activation='relu')(shared)
|
||||
price_change = layers.Dense(1, name='price_change')(price_change)
|
||||
|
||||
# Output 2: Stop Loss (ATR multiples)
|
||||
sl_output = layers.Dense(8, activation='relu')(shared)
|
||||
sl_output = layers.Dense(1, activation='relu', name='sl_atr')(sl_output) # ReLU to ensure positive
|
||||
|
||||
# Output 3: Take Profit (ATR multiples)
|
||||
tp_output = layers.Dense(8, activation='relu')(shared)
|
||||
tp_output = layers.Dense(1, activation='relu', name='tp_atr')(tp_output) # ReLU to ensure positive
|
||||
|
||||
model = keras.Model(inputs=inputs, outputs=[price_change, sl_output, tp_output])
|
||||
|
||||
# Compile with separate losses and metrics for each output
|
||||
model.compile(
|
||||
optimizer=keras.optimizers.Adam(learning_rate=0.0005),
|
||||
loss={
|
||||
'price_change': 'mse',
|
||||
'sl_atr': 'mse',
|
||||
'tp_atr': 'mse'
|
||||
},
|
||||
loss_weights={
|
||||
'price_change': 1.0,
|
||||
'sl_atr': 0.5, # Lower weight for SL/TP
|
||||
'tp_atr': 0.5
|
||||
},
|
||||
metrics={
|
||||
'price_change': ['mae'],
|
||||
'sl_atr': ['mae'],
|
||||
'tp_atr': ['mae']
|
||||
}
|
||||
)
|
||||
|
||||
print(f" Model parameters: {model.count_params():,}")
|
||||
model.summary()
|
||||
|
||||
return model
|
||||
|
||||
def train(self, start_date: datetime, end_date: datetime,
|
||||
epochs: int = 50, batch_size: int = 32,
|
||||
validation_split: float = 0.2, verbose: int = 1):
|
||||
"""
|
||||
Train the model.
|
||||
|
||||
Args:
|
||||
start_date: Start date for training data
|
||||
end_date: End date for training data
|
||||
epochs: Number of training epochs
|
||||
batch_size: Batch size for training
|
||||
validation_split: Fraction of data to use for validation
|
||||
verbose: Verbosity level
|
||||
"""
|
||||
# Fetch data
|
||||
df = self.fetch_data(start_date, end_date)
|
||||
feature_df = self.prepare_features(df)
|
||||
|
||||
# Prepare targets: price change, optimal SL, and optimal TP
|
||||
# Calculate future price change and optimal SL/TP by looking ahead
|
||||
close_prices = feature_df['close'].values
|
||||
high_prices = feature_df['high'].values
|
||||
low_prices = feature_df['low'].values
|
||||
atr_values = feature_df['atr'].values
|
||||
|
||||
# Look ahead window for calculating optimal SL/TP (e.g., 20 bars = 5 hours for M15)
|
||||
look_ahead_bars = 20
|
||||
|
||||
target_price_change = []
|
||||
target_sl = [] # SL in ATR multiples
|
||||
target_tp = [] # TP in ATR multiples
|
||||
|
||||
for i in range(len(close_prices)):
|
||||
if i + self.prediction_horizon < len(close_prices):
|
||||
current_price = close_prices[i]
|
||||
current_atr = atr_values[i] if atr_values[i] > 0 else current_price * 0.001
|
||||
|
||||
# Calculate price change
|
||||
future_price = close_prices[i + self.prediction_horizon]
|
||||
price_change_pct = (future_price - current_price) / current_price if current_price > 0 else 0.0
|
||||
|
||||
# Calculate optimal SL/TP by looking ahead
|
||||
# For BUY signals (positive price change expected)
|
||||
if price_change_pct > 0:
|
||||
# Look ahead to find maximum adverse and favorable excursions
|
||||
max_adverse = 0.0 # Maximum price drop (SL would be hit)
|
||||
max_favorable = 0.0 # Maximum price rise (TP could be set)
|
||||
|
||||
for j in range(i + 1, min(i + look_ahead_bars + 1, len(close_prices))):
|
||||
# Maximum adverse: lowest low below entry
|
||||
adverse_move = (current_price - low_prices[j]) / current_price
|
||||
max_adverse = max(max_adverse, adverse_move)
|
||||
|
||||
# Maximum favorable: highest high above entry
|
||||
favorable_move = (high_prices[j] - current_price) / current_price
|
||||
max_favorable = max(max_favorable, favorable_move)
|
||||
|
||||
# Optimal SL: Use 1.2x of maximum adverse excursion (slightly wider to avoid noise)
|
||||
# Convert to ATR multiples
|
||||
optimal_sl_pct = max_adverse * 1.2 if max_adverse > 0 else 0.005 # Default 0.5% if no adverse move
|
||||
optimal_sl_atr = optimal_sl_pct * current_price / current_atr if current_atr > 0 else 1.5
|
||||
optimal_sl_atr = max(0.5, min(optimal_sl_atr, 5.0)) # Clamp between 0.5 and 5.0 ATR
|
||||
|
||||
# Optimal TP: Use 0.6x of maximum favorable excursion (conservative)
|
||||
# Ensure minimum 1.5x risk/reward ratio
|
||||
optimal_tp_pct = max_favorable * 0.6 if max_favorable > 0 else optimal_sl_pct * 1.5
|
||||
optimal_tp_pct = max(optimal_sl_pct * 1.5, optimal_tp_pct) # At least 1.5x SL
|
||||
optimal_tp_atr = optimal_tp_pct * current_price / current_atr if current_atr > 0 else 2.0
|
||||
optimal_tp_atr = max(1.0, min(optimal_tp_atr, 10.0)) # Clamp between 1.0 and 10.0 ATR
|
||||
|
||||
# For SELL signals (negative price change expected)
|
||||
else:
|
||||
max_adverse = 0.0 # Maximum price rise (SL would be hit for sell)
|
||||
max_favorable = 0.0 # Maximum price drop (TP could be set)
|
||||
|
||||
for j in range(i + 1, min(i + look_ahead_bars + 1, len(close_prices))):
|
||||
# Maximum adverse: highest high above entry
|
||||
adverse_move = (high_prices[j] - current_price) / current_price
|
||||
max_adverse = max(max_adverse, adverse_move)
|
||||
|
||||
# Maximum favorable: lowest low below entry
|
||||
favorable_move = (current_price - low_prices[j]) / current_price
|
||||
max_favorable = max(max_favorable, favorable_move)
|
||||
|
||||
# Optimal SL: Use 1.2x of maximum adverse excursion
|
||||
optimal_sl_pct = max_adverse * 1.2 if max_adverse > 0 else 0.005
|
||||
optimal_sl_atr = optimal_sl_pct * current_price / current_atr if current_atr > 0 else 1.5
|
||||
optimal_sl_atr = max(0.5, min(optimal_sl_atr, 5.0))
|
||||
|
||||
# Optimal TP: Use 0.6x of maximum favorable excursion
|
||||
optimal_tp_pct = max_favorable * 0.6 if max_favorable > 0 else optimal_sl_pct * 1.5
|
||||
optimal_tp_pct = max(optimal_sl_pct * 1.5, optimal_tp_pct)
|
||||
optimal_tp_atr = optimal_tp_pct * current_price / current_atr if current_atr > 0 else 2.0
|
||||
optimal_tp_atr = max(1.0, min(optimal_tp_atr, 10.0))
|
||||
|
||||
target_price_change.append(price_change_pct)
|
||||
target_sl.append(optimal_sl_atr)
|
||||
target_tp.append(optimal_tp_atr)
|
||||
else:
|
||||
target_price_change.append(0.0)
|
||||
target_sl.append(1.5) # Default SL
|
||||
target_tp.append(2.0) # Default TP
|
||||
|
||||
# Create DataFrame with multiple targets
|
||||
target_df = pd.DataFrame({
|
||||
'price_change': target_price_change,
|
||||
'sl_atr': target_sl,
|
||||
'tp_atr': target_tp
|
||||
}, index=feature_df.index)
|
||||
|
||||
# Align targets with features
|
||||
valid_idx = ~(target_df.isna().any(axis=1) | feature_df.isna().any(axis=1))
|
||||
feature_df = feature_df[valid_idx]
|
||||
target_df = target_df[valid_idx]
|
||||
|
||||
print(f"\nValid samples after alignment: {len(feature_df)}")
|
||||
print(f"Target statistics:")
|
||||
print(f" Price Change: mean={target_df['price_change'].mean():.6f}, std={target_df['price_change'].std():.6f}")
|
||||
print(f" SL (ATR): mean={target_df['sl_atr'].mean():.2f}, std={target_df['sl_atr'].std():.2f}")
|
||||
print(f" TP (ATR): mean={target_df['tp_atr'].mean():.2f}, std={target_df['tp_atr'].std():.2f}")
|
||||
|
||||
# Normalize features
|
||||
print("\nNormalizing features...")
|
||||
feature_array = self.scaler.fit_transform(feature_df.values)
|
||||
|
||||
# Prepare multi-output target
|
||||
target_array = target_df[['price_change', 'sl_atr', 'tp_atr']].values
|
||||
|
||||
# Create sequences
|
||||
X, y = self.create_sequences(feature_array, target_array)
|
||||
|
||||
# Split into train and validation
|
||||
split_idx = int(len(X) * (1 - validation_split))
|
||||
X_train, X_val = X[:split_idx], X[split_idx:]
|
||||
y_train, y_val = y[:split_idx], y[split_idx:]
|
||||
|
||||
print(f"\nTrain set: {len(X_train)} samples")
|
||||
print(f"Validation set: {len(X_val)} samples")
|
||||
|
||||
# Build model
|
||||
input_shape = (self.lookback, feature_array.shape[1])
|
||||
self.model = self.build_model(input_shape)
|
||||
|
||||
# Prepare multi-output targets for training
|
||||
y_train_dict = {
|
||||
'price_change': y_train[:, 0],
|
||||
'sl_atr': y_train[:, 1],
|
||||
'tp_atr': y_train[:, 2]
|
||||
}
|
||||
y_val_dict = {
|
||||
'price_change': y_val[:, 0],
|
||||
'sl_atr': y_val[:, 1],
|
||||
'tp_atr': y_val[:, 2]
|
||||
}
|
||||
|
||||
# Train model
|
||||
print(f"\nTraining model for {epochs} epochs...")
|
||||
history = self.model.fit(
|
||||
X_train, y_train_dict,
|
||||
batch_size=batch_size,
|
||||
epochs=epochs,
|
||||
validation_data=(X_val, y_val_dict),
|
||||
verbose=verbose,
|
||||
callbacks=[
|
||||
keras.callbacks.EarlyStopping(
|
||||
monitor='val_loss',
|
||||
patience=10,
|
||||
restore_best_weights=True
|
||||
),
|
||||
keras.callbacks.ReduceLROnPlateau(
|
||||
monitor='val_loss',
|
||||
factor=0.5,
|
||||
patience=5,
|
||||
min_lr=1e-7
|
||||
)
|
||||
]
|
||||
)
|
||||
|
||||
# Evaluate
|
||||
print("\nEvaluating model...")
|
||||
train_eval = self.model.evaluate(X_train, y_train_dict, verbose=0)
|
||||
val_eval = self.model.evaluate(X_val, y_val_dict, verbose=0)
|
||||
|
||||
# Multi-output model returns list of losses/metrics
|
||||
print(f"Train - Total Loss: {train_eval[0]:.6f}")
|
||||
print(f" Price Change Loss: {train_eval[1]:.6f}, MAE: {train_eval[4]:.6f}")
|
||||
print(f" SL Loss: {train_eval[2]:.6f}, MAE: {train_eval[5]:.6f}")
|
||||
print(f" TP Loss: {train_eval[3]:.6f}, MAE: {train_eval[6]:.6f}")
|
||||
print(f"Val - Total Loss: {val_eval[0]:.6f}")
|
||||
print(f" Price Change Loss: {val_eval[1]:.6f}, MAE: {val_eval[4]:.6f}")
|
||||
print(f" SL Loss: {val_eval[2]:.6f}, MAE: {val_eval[5]:.6f}")
|
||||
print(f" TP Loss: {val_eval[3]:.6f}, MAE: {val_eval[6]:.6f}")
|
||||
|
||||
return history
|
||||
|
||||
def export_to_onnx(self, output_path: str):
|
||||
"""
|
||||
Export the trained model to ONNX format.
|
||||
|
||||
Args:
|
||||
output_path: Path to save ONNX model
|
||||
"""
|
||||
if self.model is None:
|
||||
raise ValueError("Model must be trained before exporting")
|
||||
|
||||
print(f"\nExporting model to ONNX format: {output_path}")
|
||||
|
||||
# Get number of features
|
||||
num_features = self.model.input_shape[2] if len(self.model.input_shape) > 2 else self.model.input_shape[1]
|
||||
|
||||
print(f"Using {num_features} features for ONNX export")
|
||||
|
||||
# Create input signature
|
||||
input_shape = (None, self.lookback, num_features)
|
||||
spec = (tf.TensorSpec(input_shape, tf.float32, name="input"),)
|
||||
|
||||
# Fix output_names for Sequential model
|
||||
if not hasattr(self.model, 'output_names'):
|
||||
if hasattr(self.model, 'outputs') and self.model.outputs:
|
||||
self.model.output_names = [f'output_{i}' for i in range(len(self.model.outputs))]
|
||||
else:
|
||||
self.model.output_names = ['output']
|
||||
|
||||
# Convert to ONNX
|
||||
onnx_model, _ = tf2onnx.convert.from_keras(
|
||||
self.model,
|
||||
input_signature=spec,
|
||||
opset=13
|
||||
)
|
||||
|
||||
# Save ONNX model
|
||||
os.makedirs(os.path.dirname(output_path), exist_ok=True)
|
||||
onnx.save_model(onnx_model, output_path)
|
||||
|
||||
print(f"ONNX model saved to: {output_path}")
|
||||
|
||||
# Save scaler
|
||||
scaler_path = output_path.replace('.onnx', '_scaler.pkl')
|
||||
with open(scaler_path, 'wb') as f:
|
||||
pickle.dump(self.scaler, f)
|
||||
print(f"Scaler saved to: {scaler_path}")
|
||||
|
||||
def cleanup(self):
|
||||
"""Clean up MT5 connection."""
|
||||
mt5.shutdown()
|
||||
|
||||
|
||||
def main():
|
||||
"""Main function."""
|
||||
print("="*60)
|
||||
print("EURUSD 15-Minute ONNX Model Training")
|
||||
print("="*60)
|
||||
|
||||
# Training parameters
|
||||
symbol = "EURUSD"
|
||||
timeframe = mt5.TIMEFRAME_M15
|
||||
lookback = 60 # 60 bars = 15 hours of history
|
||||
epochs = 50
|
||||
batch_size = 64
|
||||
|
||||
# Date range: 1990 to 2026
|
||||
start_date = datetime(1990, 1, 1)
|
||||
end_date = datetime(2026, 1, 1)
|
||||
|
||||
# Output paths
|
||||
output_dir = "models"
|
||||
os.makedirs(output_dir, exist_ok=True)
|
||||
model_path = os.path.join(output_dir, f"{symbol}_M15_model.onnx")
|
||||
|
||||
trainer = None
|
||||
try:
|
||||
# Create trainer
|
||||
trainer = EURUSD15MinTrainer(
|
||||
symbol=symbol,
|
||||
timeframe=timeframe,
|
||||
lookback=lookback
|
||||
)
|
||||
|
||||
# Train model
|
||||
history = trainer.train(
|
||||
start_date=start_date,
|
||||
end_date=end_date,
|
||||
epochs=epochs,
|
||||
batch_size=batch_size,
|
||||
validation_split=0.2,
|
||||
verbose=1
|
||||
)
|
||||
|
||||
# Export to ONNX
|
||||
trainer.export_to_onnx(model_path)
|
||||
|
||||
print("\n" + "="*60)
|
||||
print("Training completed successfully!")
|
||||
print("="*60)
|
||||
print(f"Model saved to: {model_path}")
|
||||
|
||||
except Exception as e:
|
||||
print(f"\nERROR: Training failed: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
return 1
|
||||
|
||||
finally:
|
||||
if trainer:
|
||||
trainer.cleanup()
|
||||
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
sys.exit(main())
|
||||
Binary file not shown.
Binary file not shown.
@@ -0,0 +1 @@
|
||||
[0401/051136.755:ERROR:third_party\crashpad\crashpad\util\win\registration_protocol_win.cc:108] CreateFile: 系统找不到指定的文件。 (0x2)
|
||||
@@ -0,0 +1,75 @@
|
||||
# Pepperstone US - Symbol Setup Guide
|
||||
|
||||
## Finding Correct Symbol Names in MetaTrader 5
|
||||
|
||||
### Step-by-Step Instructions:
|
||||
|
||||
1. **Open Market Watch Window**
|
||||
- Press `Ctrl+M` or go to `View > Market Watch`
|
||||
|
||||
2. **Show All Symbols**
|
||||
- Right-click in the Market Watch window
|
||||
- Select `Show All` or `Symbols`
|
||||
- This shows all available symbols from your broker
|
||||
|
||||
3. **Search for Your Symbols**
|
||||
- Use the search box in the Market Watch window
|
||||
- Search for: "AAPL", "MSFT", "NVDA", "TSLA", "BTCUSD", "XAUUSD"
|
||||
|
||||
4. **Note the Exact Symbol Name**
|
||||
- The symbol name shown in Market Watch is what you need to use
|
||||
- Common formats for Pepperstone US:
|
||||
- Stocks: `AAPL.US`, `MSFT.US`, `NVDA.US`, `TSLA.US`
|
||||
- Or: `NASDAQ:AAPL`, `NASDAQ:MSFT`, etc.
|
||||
- Or: Just `AAPL`, `MSFT`, etc. (if available)
|
||||
|
||||
5. **Add to Market Watch**
|
||||
- Double-click the symbol to add it to your Market Watch
|
||||
- Or right-click and select `Show`
|
||||
|
||||
6. **Update EA Inputs**
|
||||
- Open the EA inputs in MetaTrader 5
|
||||
- Update each symbol parameter with the exact name from Market Watch
|
||||
|
||||
## Common Pepperstone US Symbol Formats
|
||||
|
||||
### US Stocks:
|
||||
- **Apple**: `AAPL.US` or `NASDAQ:AAPL` or `AAPL`
|
||||
- **Microsoft**: `MSFT.US` or `NASDAQ:MSFT` or `MSFT`
|
||||
- **NVIDIA**: `NVDA.US` or `NASDAQ:NVDA` or `NVDA`
|
||||
- **Tesla**: `TSLA.US` or `NASDAQ:TSLA` or `TSLA`
|
||||
|
||||
### Cryptocurrencies:
|
||||
- **Bitcoin**: `BTCUSD` or `BTC/USD` or `BTCUSD.c`
|
||||
|
||||
### Precious Metals:
|
||||
- **Gold**: `XAUUSD` or `GOLD` or `XAU/USD`
|
||||
|
||||
## Important Notes:
|
||||
|
||||
1. **Symbol Names are Case-Sensitive**: Use exact capitalization
|
||||
2. **Add Symbols to Market Watch**: Symbols must be in Market Watch for the EA to access them
|
||||
3. **Check Trading Hours**: US stocks trade during US market hours (9:30 AM - 4:00 PM ET)
|
||||
4. **CFD vs Stock**: Pepperstone offers CFDs on stocks, not actual stocks
|
||||
5. **Spread**: Check the spread for each symbol - some may have wider spreads
|
||||
|
||||
## Troubleshooting:
|
||||
|
||||
### If Symbol Not Found:
|
||||
1. Check if you're connected to Pepperstone US server
|
||||
2. Verify your account type supports the symbol
|
||||
3. Contact Pepperstone support for symbol availability
|
||||
4. Check if symbol requires special account permissions
|
||||
|
||||
### If EA Shows "Symbol Not Available":
|
||||
1. Make sure symbol is added to Market Watch
|
||||
2. Verify symbol name matches exactly (including dots, colons, etc.)
|
||||
3. Check broker connection status
|
||||
4. Try different symbol format variations
|
||||
|
||||
## Testing Symbols:
|
||||
|
||||
You can test if a symbol works by:
|
||||
1. Opening a chart with that symbol
|
||||
2. If chart opens successfully, the symbol name is correct
|
||||
3. Use that exact symbol name in the EA inputs
|
||||
@@ -0,0 +1,607 @@
|
||||
//+------------------------------------------------------------------+
|
||||
//| PerformanceEvaluator.mqh |
|
||||
//| Copyright 2025, MetaQuotes Ltd. |
|
||||
//| https://www.mql5.com |
|
||||
//+------------------------------------------------------------------+
|
||||
#property copyright "Copyright 2025, MetaQuotes Ltd."
|
||||
#property link "https://www.mql5.com"
|
||||
#property version "1.00"
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Performance Metrics Structure |
|
||||
//+------------------------------------------------------------------+
|
||||
struct StrategyPerformance {
|
||||
string strategyName;
|
||||
string symbol; // Store symbol to determine if it's a stock
|
||||
int magicNumber;
|
||||
double initialLotSize;
|
||||
double currentLotSize;
|
||||
double quarterProfit;
|
||||
double quarterTrades;
|
||||
double quarterWins;
|
||||
double quarterLosses;
|
||||
double maxDrawdown;
|
||||
double winRate;
|
||||
datetime quarterStart;
|
||||
datetime quarterEnd;
|
||||
bool isActive;
|
||||
bool inPenaltyMode; // True if strategy is in penalty (worst performer)
|
||||
double lotSizeBeforePenalty; // Store lot size before penalty
|
||||
datetime penaltyStartTime; // When penalty started
|
||||
};
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Global Performance Tracking |
|
||||
//+------------------------------------------------------------------+
|
||||
StrategyPerformance strategyPerformances[];
|
||||
int totalStrategies = 0;
|
||||
datetime lastMonthCheck = 0;
|
||||
datetime currentMonthStart = 0;
|
||||
datetime currentMonthEnd = 0;
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Performance Adjustment Parameters |
|
||||
//+------------------------------------------------------------------+
|
||||
input group "=== Performance Evaluation Settings ==="
|
||||
input bool PE_EnableAutoAdjustment = true; // Enable automatic lot size adjustment
|
||||
input double PE_LotSizeIncreasePercent = 10.0; // % increase for top-ranked strategies
|
||||
input double PE_LotSizeDecreasePercent = 10.0; // % decrease for bottom-ranked strategies
|
||||
input double PE_MinLotSize = 0.01; // Minimum lot size for forex/crypto
|
||||
input double PE_MinLotSizeStocks = 5.0; // Minimum lot size for stocks (5-10 range)
|
||||
input double PE_MaxLotSize = 100.0; // Maximum lot size after adjustment
|
||||
input int PE_TopPerformersCount = 3; // Number of top strategies to increase lot size
|
||||
input int PE_BottomPerformersCount = 3; // Number of bottom strategies to decrease lot size
|
||||
input bool PE_UseWinRateWeight = true; // Consider win rate in ranking (50% profit, 50% win rate)
|
||||
input bool PE_EnableBlitzPlay = true; // Enable blitz play: worst performer gets minimum lot size penalty
|
||||
input bool PE_EnableLogging = true; // Enable performance logging
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Initialize Performance Tracking |
|
||||
//+------------------------------------------------------------------+
|
||||
void InitPerformanceTracking()
|
||||
{
|
||||
// Calculate current month dates
|
||||
MqlDateTime dt;
|
||||
TimeToStruct(TimeCurrent(), dt);
|
||||
|
||||
// Determine month start (first day of current month)
|
||||
dt.day = 1;
|
||||
dt.hour = 0;
|
||||
dt.min = 0;
|
||||
dt.sec = 0;
|
||||
currentMonthStart = StructToTime(dt);
|
||||
|
||||
// Calculate month end (first day of next month - 1 second)
|
||||
dt.mon += 1;
|
||||
if(dt.mon > 12)
|
||||
{
|
||||
dt.mon = 1;
|
||||
dt.year++;
|
||||
}
|
||||
currentMonthEnd = StructToTime(dt) - 1; // End of last day of month
|
||||
|
||||
lastMonthCheck = TimeCurrent();
|
||||
|
||||
if(PE_EnableLogging)
|
||||
{
|
||||
Print("Performance Evaluator: Initialized");
|
||||
Print("Current Month Start: ", TimeToString(currentMonthStart));
|
||||
Print("Current Month End: ", TimeToString(currentMonthEnd));
|
||||
}
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Check if Symbol is a Stock |
|
||||
//+------------------------------------------------------------------+
|
||||
bool IsStockSymbol(string symbol)
|
||||
{
|
||||
// Check if symbol contains common stock indicators
|
||||
if(StringFind(symbol, ".US") >= 0) return true;
|
||||
if(StringFind(symbol, "NASDAQ:") >= 0) return true;
|
||||
if(StringFind(symbol, "NYSE:") >= 0) return true;
|
||||
|
||||
// Note: Symbol category check removed to avoid enum conversion issues
|
||||
// String-based checks (.US, NASDAQ:, NYSE:, common tickers) are sufficient
|
||||
|
||||
// Common stock tickers (without .US suffix)
|
||||
string commonStocks[] = {"AAPL", "MSFT", "NVDA", "TSLA", "GOOGL", "AMZN", "META", "NFLX"};
|
||||
for(int i = 0; i < ArraySize(commonStocks); i++)
|
||||
{
|
||||
if(StringFind(symbol, commonStocks[i]) == 0) return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Get Minimum Lot Size for Symbol |
|
||||
//+------------------------------------------------------------------+
|
||||
double GetMinLotSizeForSymbol(string symbol)
|
||||
{
|
||||
if(IsStockSymbol(symbol))
|
||||
return PE_MinLotSizeStocks;
|
||||
else
|
||||
return PE_MinLotSize;
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Register Strategy for Performance Tracking |
|
||||
//+------------------------------------------------------------------+
|
||||
void RegisterStrategy(string strategyName, int magicNumber, double initialLotSize, string symbol = "")
|
||||
{
|
||||
// Check if strategy already registered
|
||||
for(int i = 0; i < ArraySize(strategyPerformances); i++)
|
||||
{
|
||||
if(strategyPerformances[i].strategyName == strategyName &&
|
||||
strategyPerformances[i].magicNumber == magicNumber)
|
||||
{
|
||||
if(PE_EnableLogging)
|
||||
Print("Performance Evaluator: Strategy '", strategyName, "' already registered");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Add new strategy
|
||||
int newSize = ArraySize(strategyPerformances) + 1;
|
||||
ArrayResize(strategyPerformances, newSize);
|
||||
|
||||
strategyPerformances[newSize - 1].strategyName = strategyName;
|
||||
strategyPerformances[newSize - 1].symbol = symbol;
|
||||
strategyPerformances[newSize - 1].magicNumber = magicNumber;
|
||||
strategyPerformances[newSize - 1].initialLotSize = initialLotSize;
|
||||
// Start with minimum lot size for safety (symbol-specific minimum)
|
||||
double minLot = GetMinLotSizeForSymbol(symbol);
|
||||
strategyPerformances[newSize - 1].currentLotSize = minLot;
|
||||
strategyPerformances[newSize - 1].quarterProfit = 0.0;
|
||||
strategyPerformances[newSize - 1].quarterTrades = 0;
|
||||
strategyPerformances[newSize - 1].quarterWins = 0;
|
||||
strategyPerformances[newSize - 1].quarterLosses = 0;
|
||||
strategyPerformances[newSize - 1].maxDrawdown = 0.0;
|
||||
strategyPerformances[newSize - 1].winRate = 0.0;
|
||||
strategyPerformances[newSize - 1].quarterStart = currentMonthStart;
|
||||
strategyPerformances[newSize - 1].quarterEnd = currentMonthEnd;
|
||||
strategyPerformances[newSize - 1].isActive = true;
|
||||
strategyPerformances[newSize - 1].inPenaltyMode = false;
|
||||
strategyPerformances[newSize - 1].lotSizeBeforePenalty = initialLotSize;
|
||||
strategyPerformances[newSize - 1].penaltyStartTime = 0;
|
||||
|
||||
totalStrategies = newSize;
|
||||
|
||||
if(PE_EnableLogging)
|
||||
Print("Performance Evaluator: Registered strategy '", strategyName,
|
||||
"' (Magic: ", magicNumber, ", Initial Lot: ", initialLotSize, ")");
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Update Strategy Performance Metrics |
|
||||
//+------------------------------------------------------------------+
|
||||
void UpdateStrategyPerformance(string strategyName, int magicNumber)
|
||||
{
|
||||
for(int i = 0; i < ArraySize(strategyPerformances); i++)
|
||||
{
|
||||
if(strategyPerformances[i].strategyName == strategyName &&
|
||||
strategyPerformances[i].magicNumber == magicNumber &&
|
||||
strategyPerformances[i].isActive)
|
||||
{
|
||||
// Calculate performance for current quarter
|
||||
double totalProfit = 0.0;
|
||||
int totalTrades = 0;
|
||||
int wins = 0;
|
||||
int losses = 0;
|
||||
double maxDD = 0.0;
|
||||
double peakBalance = 0.0;
|
||||
|
||||
// Scan all closed deals in current quarter
|
||||
datetime quarterStart = strategyPerformances[i].quarterStart;
|
||||
datetime quarterEnd = strategyPerformances[i].quarterEnd;
|
||||
|
||||
// Select history for the quarter
|
||||
if(HistorySelect(quarterStart, quarterEnd))
|
||||
{
|
||||
int totalDeals = HistoryDealsTotal();
|
||||
for(int j = 0; j < totalDeals; j++)
|
||||
{
|
||||
ulong ticket = HistoryDealGetTicket(j);
|
||||
if(ticket > 0)
|
||||
{
|
||||
long dealMagic = HistoryDealGetInteger(ticket, DEAL_MAGIC);
|
||||
if(dealMagic == magicNumber)
|
||||
{
|
||||
double profit = HistoryDealGetDouble(ticket, DEAL_PROFIT);
|
||||
double swap = HistoryDealGetDouble(ticket, DEAL_SWAP);
|
||||
double commission = HistoryDealGetDouble(ticket, DEAL_COMMISSION);
|
||||
double totalDealProfit = profit + swap + commission;
|
||||
|
||||
totalProfit += totalDealProfit;
|
||||
totalTrades++;
|
||||
|
||||
if(totalDealProfit > 0)
|
||||
wins++;
|
||||
else if(totalDealProfit < 0)
|
||||
losses++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate win rate
|
||||
double winRate = 0.0;
|
||||
if(totalTrades > 0)
|
||||
winRate = (double)wins / (double)totalTrades * 100.0;
|
||||
|
||||
// Update metrics
|
||||
strategyPerformances[i].quarterProfit = totalProfit;
|
||||
strategyPerformances[i].quarterTrades = totalTrades;
|
||||
strategyPerformances[i].quarterWins = wins;
|
||||
strategyPerformances[i].quarterLosses = losses;
|
||||
strategyPerformances[i].winRate = winRate;
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Strategy Ranking Structure |
|
||||
//+------------------------------------------------------------------+
|
||||
struct StrategyRank {
|
||||
int index;
|
||||
double score;
|
||||
};
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Calculate Strategy Score for Ranking |
|
||||
//+------------------------------------------------------------------+
|
||||
double CalculateStrategyScore(int strategyIndex)
|
||||
{
|
||||
double profit = strategyPerformances[strategyIndex].quarterProfit;
|
||||
double winRate = strategyPerformances[strategyIndex].winRate;
|
||||
double trades = strategyPerformances[strategyIndex].quarterTrades;
|
||||
|
||||
// Normalize profit (scale to 0-100 range, assuming max profit of $1000)
|
||||
double normalizedProfit = MathMin(profit / 10.0, 100.0);
|
||||
if(profit < 0) normalizedProfit = profit / 5.0; // Penalize losses more
|
||||
|
||||
// Calculate score
|
||||
double score = 0.0;
|
||||
if(PE_UseWinRateWeight)
|
||||
{
|
||||
// 50% profit, 50% win rate (if enough trades)
|
||||
if(trades >= 5)
|
||||
score = (normalizedProfit * 0.5) + (winRate * 0.5);
|
||||
else
|
||||
score = normalizedProfit; // Not enough trades, use profit only
|
||||
}
|
||||
else
|
||||
{
|
||||
// Profit only
|
||||
score = normalizedProfit;
|
||||
}
|
||||
|
||||
return score;
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Check if Month Ended and Evaluate Performance |
|
||||
//+------------------------------------------------------------------+
|
||||
void CheckMonthEnd()
|
||||
{
|
||||
datetime now = TimeCurrent();
|
||||
|
||||
// Check if we've entered a new month
|
||||
if(now >= currentMonthEnd)
|
||||
{
|
||||
if(PE_EnableLogging)
|
||||
Print("Performance Evaluator: Month ended. Evaluating and ranking strategies...");
|
||||
|
||||
// Update performance metrics for all strategies
|
||||
for(int i = 0; i < ArraySize(strategyPerformances); i++)
|
||||
{
|
||||
if(strategyPerformances[i].isActive)
|
||||
{
|
||||
UpdateStrategyPerformance(strategyPerformances[i].strategyName,
|
||||
strategyPerformances[i].magicNumber);
|
||||
}
|
||||
}
|
||||
|
||||
// Rank strategies
|
||||
int activeCount = 0;
|
||||
for(int i = 0; i < ArraySize(strategyPerformances); i++)
|
||||
{
|
||||
if(strategyPerformances[i].isActive)
|
||||
activeCount++;
|
||||
}
|
||||
|
||||
if(activeCount > 0)
|
||||
{
|
||||
// Create ranking array
|
||||
StrategyRank ranks[];
|
||||
ArrayResize(ranks, activeCount);
|
||||
int rankIndex = 0;
|
||||
|
||||
for(int i = 0; i < ArraySize(strategyPerformances); i++)
|
||||
{
|
||||
if(strategyPerformances[i].isActive)
|
||||
{
|
||||
ranks[rankIndex].index = i;
|
||||
ranks[rankIndex].score = CalculateStrategyScore(i);
|
||||
rankIndex++;
|
||||
}
|
||||
}
|
||||
|
||||
// Sort by score (descending - highest score first)
|
||||
for(int i = 0; i < activeCount - 1; i++)
|
||||
{
|
||||
for(int j = i + 1; j < activeCount; j++)
|
||||
{
|
||||
if(ranks[j].score > ranks[i].score)
|
||||
{
|
||||
StrategyRank temp = ranks[i];
|
||||
ranks[i] = ranks[j];
|
||||
ranks[j] = temp;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Adjust lot sizes based on ranking
|
||||
if(PE_EnableAutoAdjustment)
|
||||
{
|
||||
// Increase top performers (skip if in penalty mode)
|
||||
int topCount = MathMin(PE_TopPerformersCount, activeCount);
|
||||
for(int i = 0; i < topCount; i++)
|
||||
{
|
||||
int strategyIdx = ranks[i].index;
|
||||
|
||||
// Skip if strategy is in penalty mode
|
||||
if(strategyPerformances[strategyIdx].inPenaltyMode)
|
||||
continue;
|
||||
|
||||
double oldLotSize = strategyPerformances[strategyIdx].currentLotSize;
|
||||
double newLotSize = oldLotSize * (1.0 + PE_LotSizeIncreasePercent / 100.0);
|
||||
|
||||
if(newLotSize > PE_MaxLotSize)
|
||||
newLotSize = PE_MaxLotSize;
|
||||
|
||||
strategyPerformances[strategyIdx].currentLotSize = newLotSize;
|
||||
|
||||
if(PE_EnableLogging)
|
||||
Print("Performance Evaluator: Rank #", (i+1), " - Increasing '",
|
||||
strategyPerformances[strategyIdx].strategyName,
|
||||
"' lot size from ", oldLotSize, " to ", newLotSize,
|
||||
" (Score: ", DoubleToString(ranks[i].score, 2),
|
||||
", Profit: $", DoubleToString(strategyPerformances[strategyIdx].quarterProfit, 2),
|
||||
", Win Rate: ", DoubleToString(strategyPerformances[strategyIdx].winRate, 2), "%)");
|
||||
}
|
||||
|
||||
// Decrease bottom performers (skip worst one if blitz play is enabled)
|
||||
int bottomCount = MathMin(PE_BottomPerformersCount, activeCount);
|
||||
int startIdx = activeCount - bottomCount;
|
||||
|
||||
// If blitz play is enabled, skip the worst performer (it will get minimum penalty)
|
||||
if(PE_EnableBlitzPlay && activeCount > 0)
|
||||
startIdx = activeCount - bottomCount + 1;
|
||||
|
||||
for(int i = startIdx; i < activeCount; i++)
|
||||
{
|
||||
int strategyIdx = ranks[i].index;
|
||||
|
||||
// Skip if strategy is in penalty mode
|
||||
if(strategyPerformances[strategyIdx].inPenaltyMode)
|
||||
continue;
|
||||
|
||||
double oldLotSize = strategyPerformances[strategyIdx].currentLotSize;
|
||||
double newLotSize = oldLotSize * (1.0 - PE_LotSizeDecreasePercent / 100.0);
|
||||
|
||||
// Use symbol-specific minimum lot size
|
||||
double minLot = GetMinLotSizeForSymbol(strategyPerformances[strategyIdx].symbol);
|
||||
if(newLotSize < minLot)
|
||||
newLotSize = minLot;
|
||||
|
||||
strategyPerformances[strategyIdx].currentLotSize = newLotSize;
|
||||
|
||||
if(PE_EnableLogging)
|
||||
Print("Performance Evaluator: Rank #", (i+1), " - Decreasing '",
|
||||
strategyPerformances[strategyIdx].strategyName,
|
||||
"' lot size from ", oldLotSize, " to ", newLotSize,
|
||||
" (Score: ", DoubleToString(ranks[i].score, 2),
|
||||
", Profit: $", DoubleToString(strategyPerformances[strategyIdx].quarterProfit, 2),
|
||||
", Win Rate: ", DoubleToString(strategyPerformances[strategyIdx].winRate, 2), "%)");
|
||||
}
|
||||
}
|
||||
|
||||
// Blitz Play: Apply penalty to worst performer
|
||||
if(PE_EnableBlitzPlay && activeCount > 0)
|
||||
{
|
||||
// Find worst performer (last in ranking)
|
||||
int worstIdx = ranks[activeCount - 1].index;
|
||||
|
||||
// Remove penalty from previous worst performer (if any)
|
||||
for(int i = 0; i < ArraySize(strategyPerformances); i++)
|
||||
{
|
||||
if(strategyPerformances[i].isActive && strategyPerformances[i].inPenaltyMode)
|
||||
{
|
||||
// Check if penalty period has passed (one month)
|
||||
if(now - strategyPerformances[i].penaltyStartTime >= 2592000) // ~30 days
|
||||
{
|
||||
// Restore lot size to before penalty
|
||||
strategyPerformances[i].currentLotSize = strategyPerformances[i].lotSizeBeforePenalty;
|
||||
strategyPerformances[i].inPenaltyMode = false;
|
||||
strategyPerformances[i].penaltyStartTime = 0;
|
||||
|
||||
if(PE_EnableLogging)
|
||||
Print("Blitz Play: Penalty removed from '", strategyPerformances[i].strategyName,
|
||||
"'. Lot size restored to ", strategyPerformances[i].currentLotSize);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Apply penalty to new worst performer
|
||||
if(!strategyPerformances[worstIdx].inPenaltyMode)
|
||||
{
|
||||
strategyPerformances[worstIdx].lotSizeBeforePenalty = strategyPerformances[worstIdx].currentLotSize;
|
||||
// Use symbol-specific minimum lot size
|
||||
double minLot = GetMinLotSizeForSymbol(strategyPerformances[worstIdx].symbol);
|
||||
strategyPerformances[worstIdx].currentLotSize = minLot;
|
||||
strategyPerformances[worstIdx].inPenaltyMode = true;
|
||||
strategyPerformances[worstIdx].penaltyStartTime = now;
|
||||
|
||||
if(PE_EnableLogging)
|
||||
Print("Blitz Play: WORST PERFORMER - '", strategyPerformances[worstIdx].strategyName,
|
||||
"' penalized! Lot size reduced from ", strategyPerformances[worstIdx].lotSizeBeforePenalty,
|
||||
" to minimum ", minLot, " (Score: ", DoubleToString(ranks[activeCount - 1].score, 2),
|
||||
", Profit: $", DoubleToString(strategyPerformances[worstIdx].quarterProfit, 2), ")");
|
||||
}
|
||||
}
|
||||
|
||||
// Log performance report
|
||||
if(PE_EnableLogging)
|
||||
{
|
||||
Print("=== Monthly Performance Ranking ===");
|
||||
for(int i = 0; i < activeCount; i++)
|
||||
{
|
||||
int strategyIdx = ranks[i].index;
|
||||
Print("Rank #", (i+1), ": ", strategyPerformances[strategyIdx].strategyName,
|
||||
" - Score: ", DoubleToString(ranks[i].score, 2),
|
||||
", Profit: $", DoubleToString(strategyPerformances[strategyIdx].quarterProfit, 2),
|
||||
", Win Rate: ", DoubleToString(strategyPerformances[strategyIdx].winRate, 2), "%",
|
||||
", Trades: ", (int)strategyPerformances[strategyIdx].quarterTrades,
|
||||
", Lot Size: ", DoubleToString(strategyPerformances[strategyIdx].currentLotSize, 2));
|
||||
}
|
||||
Print("===================================");
|
||||
}
|
||||
}
|
||||
|
||||
// Reset month metrics for all strategies
|
||||
for(int i = 0; i < ArraySize(strategyPerformances); i++)
|
||||
{
|
||||
if(strategyPerformances[i].isActive)
|
||||
{
|
||||
strategyPerformances[i].quarterProfit = 0.0;
|
||||
strategyPerformances[i].quarterTrades = 0;
|
||||
strategyPerformances[i].quarterWins = 0;
|
||||
strategyPerformances[i].quarterLosses = 0;
|
||||
strategyPerformances[i].maxDrawdown = 0.0;
|
||||
strategyPerformances[i].winRate = 0.0;
|
||||
}
|
||||
}
|
||||
|
||||
// Update month dates
|
||||
MqlDateTime dt;
|
||||
TimeToStruct(now, dt);
|
||||
|
||||
// First day of current month
|
||||
dt.day = 1;
|
||||
dt.hour = 0;
|
||||
dt.min = 0;
|
||||
dt.sec = 0;
|
||||
currentMonthStart = StructToTime(dt);
|
||||
|
||||
// First day of next month - 1 second
|
||||
dt.mon += 1;
|
||||
if(dt.mon > 12)
|
||||
{
|
||||
dt.mon = 1;
|
||||
dt.year++;
|
||||
}
|
||||
currentMonthEnd = StructToTime(dt) - 1;
|
||||
|
||||
// Update month dates for all strategies
|
||||
for(int i = 0; i < ArraySize(strategyPerformances); i++)
|
||||
{
|
||||
strategyPerformances[i].quarterStart = currentMonthStart;
|
||||
strategyPerformances[i].quarterEnd = currentMonthEnd;
|
||||
}
|
||||
|
||||
lastMonthCheck = now;
|
||||
}
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Get Current Lot Size for Strategy |
|
||||
//+------------------------------------------------------------------+
|
||||
double GetStrategyLotSize(string strategyName, int magicNumber)
|
||||
{
|
||||
for(int i = 0; i < ArraySize(strategyPerformances); i++)
|
||||
{
|
||||
if(strategyPerformances[i].strategyName == strategyName &&
|
||||
strategyPerformances[i].magicNumber == magicNumber &&
|
||||
strategyPerformances[i].isActive)
|
||||
{
|
||||
return strategyPerformances[i].currentLotSize;
|
||||
}
|
||||
}
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Process Performance Evaluation (call from OnTick) |
|
||||
//+------------------------------------------------------------------+
|
||||
void ProcessPerformanceEvaluation()
|
||||
{
|
||||
// Check if month ended
|
||||
CheckMonthEnd();
|
||||
|
||||
// Check for penalty expiration (blitz play)
|
||||
if(PE_EnableBlitzPlay)
|
||||
{
|
||||
datetime now = TimeCurrent();
|
||||
for(int i = 0; i < ArraySize(strategyPerformances); i++)
|
||||
{
|
||||
if(strategyPerformances[i].isActive && strategyPerformances[i].inPenaltyMode)
|
||||
{
|
||||
// Check if penalty period has passed (one month = ~30 days)
|
||||
if(now - strategyPerformances[i].penaltyStartTime >= 2592000)
|
||||
{
|
||||
// Restore lot size to before penalty
|
||||
strategyPerformances[i].currentLotSize = strategyPerformances[i].lotSizeBeforePenalty;
|
||||
strategyPerformances[i].inPenaltyMode = false;
|
||||
strategyPerformances[i].penaltyStartTime = 0;
|
||||
|
||||
if(PE_EnableLogging)
|
||||
Print("Blitz Play: Penalty expired for '", strategyPerformances[i].strategyName,
|
||||
"'. Lot size restored to ", strategyPerformances[i].currentLotSize);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Update performance metrics periodically (every hour)
|
||||
static datetime lastUpdate = 0;
|
||||
if(TimeCurrent() - lastUpdate >= 3600)
|
||||
{
|
||||
for(int i = 0; i < ArraySize(strategyPerformances); i++)
|
||||
{
|
||||
if(strategyPerformances[i].isActive)
|
||||
{
|
||||
UpdateStrategyPerformance(strategyPerformances[i].strategyName,
|
||||
strategyPerformances[i].magicNumber);
|
||||
}
|
||||
}
|
||||
lastUpdate = TimeCurrent();
|
||||
}
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Get Performance Summary |
|
||||
//+------------------------------------------------------------------+
|
||||
string GetPerformanceSummary()
|
||||
{
|
||||
string summary = "\n=== Performance Summary ===\n";
|
||||
summary += "Current Month: " + TimeToString(currentMonthStart) + " to " + TimeToString(currentMonthEnd) + "\n\n";
|
||||
|
||||
for(int i = 0; i < ArraySize(strategyPerformances); i++)
|
||||
{
|
||||
if(strategyPerformances[i].isActive)
|
||||
{
|
||||
summary += strategyPerformances[i].strategyName + ":\n";
|
||||
summary += " Profit: $" + DoubleToString(strategyPerformances[i].quarterProfit, 2) + "\n";
|
||||
summary += " Trades: " + IntegerToString((int)strategyPerformances[i].quarterTrades) + "\n";
|
||||
summary += " Win Rate: " + DoubleToString(strategyPerformances[i].winRate, 2) + "%\n";
|
||||
summary += " Lot Size: " + DoubleToString(strategyPerformances[i].currentLotSize, 2) + "\n\n";
|
||||
}
|
||||
}
|
||||
|
||||
return summary;
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
@@ -0,0 +1,76 @@
|
||||
# United EA Strategy Configuration Summary
|
||||
|
||||
## Strategy Symbols and Magic Numbers
|
||||
|
||||
### Strategy 1: DarvasBox
|
||||
- **Symbol**: XAUUSD (Gold/USD)
|
||||
- **Magic Number**: 135790
|
||||
|
||||
### Strategy 2: EMASlopeDistance
|
||||
- **Symbol**: XAUUSD (Gold/USD)
|
||||
- **Magic Number**: 12350
|
||||
|
||||
### Strategy 3: RSICrossOverReversal
|
||||
- **Symbol**: XAUUSD (Gold/USD)
|
||||
- **Magic Number**: 7
|
||||
|
||||
### Strategy 4: RSIMidPointHijack
|
||||
- **Symbol**: XAUUSD (Gold/USD)
|
||||
- **Magic Numbers**:
|
||||
- RSIFollow: 1001
|
||||
- RSIReverse: 1002
|
||||
- EMACross: 1003
|
||||
|
||||
### Strategy 5: RSI Scalping APPL (Apple)
|
||||
- **Symbol**: AAPL (Apple stock)
|
||||
- **Magic Number**: 20001
|
||||
- **Note**: Changed from "APPL" to "AAPL" (correct ticker symbol)
|
||||
|
||||
### Strategy 6: RSI Scalping BTCUSD
|
||||
- **Symbol**: BTCUSD (Bitcoin/USD)
|
||||
- **Magic Number**: 123459123
|
||||
|
||||
### Strategy 7: RSI Scalping MSFT
|
||||
- **Symbol**: MSFT (Microsoft stock)
|
||||
- **Magic Number**: 20002
|
||||
|
||||
### Strategy 8: RSI Scalping NVDA
|
||||
- **Symbol**: NVDA (NVIDIA stock)
|
||||
- **Magic Number**: 20003
|
||||
|
||||
### Strategy 9: RSI Scalping TSLA
|
||||
- **Symbol**: TSLA (Tesla stock)
|
||||
- **Magic Number**: 125421321
|
||||
|
||||
### Strategy 10: RSI Scalping XAUUSD
|
||||
- **Symbol**: XAUUSD (Gold/USD)
|
||||
- **Magic Number**: 129102315
|
||||
|
||||
## Important Notes
|
||||
|
||||
1. **Stock Symbols**: Stock symbols (AAPL, MSFT, NVDA, TSLA) must be:
|
||||
- Added to Market Watch in MetaTrader 5
|
||||
- Available from your broker
|
||||
- Use the correct ticker symbol (e.g., "AAPL" not "APPL")
|
||||
|
||||
2. **Magic Numbers**: All strategies have unique magic numbers to prevent interference:
|
||||
- Each strategy can be identified by its magic number
|
||||
- RSIMidPointHijack uses 3 magic numbers (one for each sub-strategy)
|
||||
|
||||
3. **Symbol Configuration**: Each strategy trades on its own symbol:
|
||||
- You can change symbols in the input parameters
|
||||
- The EA will log warnings if a symbol is not available
|
||||
- Strategies with unavailable symbols will be skipped (EA continues running)
|
||||
|
||||
4. **RSI Scalping Strategies**:
|
||||
- Each RSI Scalping variant trades on a different symbol
|
||||
- They all use the same strategy logic but with different parameters
|
||||
- Buy and sell signals are generated based on RSI levels for each symbol
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
If stock symbols are not working:
|
||||
1. Check if the symbol exists in your broker's symbol list
|
||||
2. Add the symbol to Market Watch in MetaTrader 5
|
||||
3. Verify the symbol name matches your broker's naming convention
|
||||
4. Some brokers use prefixes/suffixes (e.g., "NASDAQ:AAPL" or "AAPL.US")
|
||||
@@ -0,0 +1,300 @@
|
||||
//+------------------------------------------------------------------+
|
||||
//| DarvasBoxStrategy.mqh |
|
||||
//+------------------------------------------------------------------+
|
||||
|
||||
bool InitDarvasBox(string symbol)
|
||||
{
|
||||
dbData.symbol = symbol;
|
||||
dbData.boxHigh = 0;
|
||||
dbData.boxLow = 0;
|
||||
dbData.boxFormed = false;
|
||||
dbData.lastBoxTime = 0;
|
||||
dbData.boxName = "DarvasBox_" + IntegerToString(DB_MagicNumber) + "_";
|
||||
|
||||
// Check if symbol exists
|
||||
if(!SymbolSelect(symbol, true))
|
||||
{
|
||||
Print("DarvasBox: Symbol '", symbol, "' not available in Market Watch. Please add it to Market Watch or check symbol name.");
|
||||
return false;
|
||||
}
|
||||
|
||||
Sleep(100); // Wait for symbol to be ready
|
||||
|
||||
dbData.point = SymbolInfoDouble(symbol, SYMBOL_POINT);
|
||||
dbData.minStopLevel = SymbolInfoInteger(symbol, SYMBOL_TRADE_STOPS_LEVEL) * dbData.point;
|
||||
|
||||
dbData.maHandle = iMA(symbol, DB_TrendTimeframe, DB_MA_Period, 0, DB_MA_Method, DB_MA_Price);
|
||||
dbData.volumeHandle = iVolumes(symbol, PERIOD_CURRENT, VOLUME_TICK);
|
||||
|
||||
if(dbData.maHandle == INVALID_HANDLE || dbData.volumeHandle == INVALID_HANDLE)
|
||||
{
|
||||
Print("DarvasBox: Error creating indicators for '", symbol, "'");
|
||||
return false;
|
||||
}
|
||||
|
||||
dbData.trade.SetDeviationInPoints(10);
|
||||
dbData.trade.SetTypeFilling(ORDER_FILLING_IOC);
|
||||
dbData.trade.SetAsyncMode(false);
|
||||
dbData.trade.SetExpertMagicNumber(DB_MagicNumber);
|
||||
|
||||
ObjectsDeleteAll(0, dbData.boxName);
|
||||
dbData.isInitialized = true;
|
||||
Print("DarvasBox: Successfully initialized for symbol '", symbol, "'");
|
||||
return true;
|
||||
}
|
||||
|
||||
void DeinitDarvasBox()
|
||||
{
|
||||
if(dbData.maHandle != INVALID_HANDLE) IndicatorRelease(dbData.maHandle);
|
||||
if(dbData.volumeHandle != INVALID_HANDLE) IndicatorRelease(dbData.volumeHandle);
|
||||
ObjectsDeleteAll(0, dbData.boxName);
|
||||
}
|
||||
|
||||
void DrawDarvasBox()
|
||||
{
|
||||
if(!dbData.boxFormed) return;
|
||||
|
||||
datetime time1 = iTime(dbData.symbol, PERIOD_H1, DB_BoxPeriod);
|
||||
datetime time2 = iTime(dbData.symbol, PERIOD_H1, 0);
|
||||
|
||||
ObjectsDeleteAll(0, dbData.boxName);
|
||||
|
||||
ObjectCreate(0, dbData.boxName + "Top", OBJ_TREND, 0, time1, dbData.boxHigh, time2, dbData.boxHigh);
|
||||
ObjectCreate(0, dbData.boxName + "Bottom", OBJ_TREND, 0, time1, dbData.boxLow, time2, dbData.boxLow);
|
||||
|
||||
ObjectSetInteger(0, dbData.boxName + "Top", OBJPROP_COLOR, DB_BoxColor);
|
||||
ObjectSetInteger(0, dbData.boxName + "Bottom", OBJPROP_COLOR, DB_BoxColor);
|
||||
ObjectSetInteger(0, dbData.boxName + "Top", OBJPROP_WIDTH, DB_BoxWidth);
|
||||
ObjectSetInteger(0, dbData.boxName + "Bottom", OBJPROP_WIDTH, DB_BoxWidth);
|
||||
ObjectSetInteger(0, dbData.boxName + "Top", OBJPROP_RAY_RIGHT, true);
|
||||
ObjectSetInteger(0, dbData.boxName + "Bottom", OBJPROP_RAY_RIGHT, true);
|
||||
}
|
||||
|
||||
void CalculateDarvasBox()
|
||||
{
|
||||
double high = 0;
|
||||
double low = DBL_MAX;
|
||||
|
||||
// Find highest high and lowest low in the period - EXACTLY like original
|
||||
for(int i = 0; i < DB_BoxPeriod; i++)
|
||||
{
|
||||
high = MathMax(high, iHigh(dbData.symbol, PERIOD_H1, i));
|
||||
low = MathMin(low, iLow(dbData.symbol, PERIOD_H1, i));
|
||||
}
|
||||
|
||||
double range = high - low;
|
||||
double allowedRange = DB_BoxDeviation * dbData.point; // Use dbData.point instead of _Point
|
||||
|
||||
if(DB_EnableLogging)
|
||||
{
|
||||
Print("DarvasBox: Box Calculation - High: ", high, " Low: ", low, " Range: ", range, " Allowed Range: ", allowedRange);
|
||||
}
|
||||
|
||||
// Check if box is formed - EXACTLY like original
|
||||
if(range <= allowedRange)
|
||||
{
|
||||
dbData.boxHigh = high;
|
||||
dbData.boxLow = low;
|
||||
dbData.boxFormed = true;
|
||||
dbData.lastBoxTime = iTime(dbData.symbol, PERIOD_CURRENT, 0);
|
||||
|
||||
// Draw the box
|
||||
DrawDarvasBox();
|
||||
|
||||
if(DB_EnableLogging)
|
||||
Print("DarvasBox: Box Formed - High: ", dbData.boxHigh, " Low: ", dbData.boxLow, " Time: ", dbData.lastBoxTime);
|
||||
}
|
||||
else
|
||||
{
|
||||
dbData.boxFormed = false;
|
||||
// Delete box if it exists
|
||||
ObjectsDeleteAll(0, dbData.boxName);
|
||||
}
|
||||
}
|
||||
|
||||
bool ValidateStopLevels(double price, double &sl, double &tp, ENUM_ORDER_TYPE orderType)
|
||||
{
|
||||
double minSlDistance = MathMax(dbData.minStopLevel, DB_StopLoss * dbData.point);
|
||||
double minTpDistance = MathMax(dbData.minStopLevel, DB_TakeProfit * dbData.point);
|
||||
|
||||
if(orderType == ORDER_TYPE_BUY)
|
||||
{
|
||||
sl = price - minSlDistance;
|
||||
tp = price + minTpDistance;
|
||||
}
|
||||
else
|
||||
{
|
||||
sl = price + minSlDistance;
|
||||
tp = price - minTpDistance;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool IsTrendFavorable(ENUM_ORDER_TYPE orderType)
|
||||
{
|
||||
double ma[];
|
||||
ArraySetAsSeries(ma, true);
|
||||
|
||||
if(CopyBuffer(dbData.maHandle, 0, 0, 2, ma) <= 0)
|
||||
return false;
|
||||
|
||||
double currentPrice = SymbolInfoDouble(dbData.symbol, SYMBOL_ASK);
|
||||
double trendStrength = MathAbs(currentPrice - ma[0]) / dbData.point;
|
||||
|
||||
if(orderType == ORDER_TYPE_BUY)
|
||||
return (currentPrice > ma[0] && trendStrength > DB_TrendThreshold);
|
||||
else
|
||||
return (currentPrice < ma[0] && trendStrength > DB_TrendThreshold);
|
||||
}
|
||||
|
||||
bool CheckVolumeConditions()
|
||||
{
|
||||
double volumes[];
|
||||
ArraySetAsSeries(volumes, true);
|
||||
|
||||
if(CopyBuffer(dbData.volumeHandle, 0, 0, DB_VolumeMA_Period + 1, volumes) <= 0)
|
||||
return false;
|
||||
|
||||
double volumeMA = 0;
|
||||
for(int i = 1; i <= DB_VolumeMA_Period; i++)
|
||||
volumeMA += volumes[i];
|
||||
volumeMA /= DB_VolumeMA_Period;
|
||||
|
||||
double currentVolume = volumes[0];
|
||||
double volumeRatio = currentVolume / volumeMA;
|
||||
|
||||
return (volumeRatio > DB_VolumeThresholdMultiplier);
|
||||
}
|
||||
|
||||
bool PlaceOrder(ENUM_ORDER_TYPE orderType, double price, double sl, double tp)
|
||||
{
|
||||
if(!ValidateStopLevels(price, sl, tp, orderType))
|
||||
{
|
||||
if(DB_EnableLogging)
|
||||
Print("DarvasBox: Order rejected - Stop levels validation failed");
|
||||
return false;
|
||||
}
|
||||
|
||||
if(!IsTrendFavorable(orderType))
|
||||
{
|
||||
if(DB_EnableLogging)
|
||||
Print("DarvasBox: Order rejected - Trend not favorable for ", EnumToString(orderType));
|
||||
return false;
|
||||
}
|
||||
|
||||
if(!CheckVolumeConditions())
|
||||
{
|
||||
if(DB_EnableLogging)
|
||||
Print("DarvasBox: Order rejected - Volume conditions not met");
|
||||
return false;
|
||||
}
|
||||
|
||||
bool result = false;
|
||||
|
||||
// Use market price (0) instead of explicit price - this ensures market order execution
|
||||
// In backtesting, explicit price might fail if price has moved
|
||||
if(orderType == ORDER_TYPE_BUY)
|
||||
result = dbData.trade.Buy(0.01, dbData.symbol, 0, sl, tp, "Darvas Box Breakout");
|
||||
else
|
||||
result = dbData.trade.Sell(0.01, dbData.symbol, 0, sl, tp, "Darvas Box Breakdown");
|
||||
|
||||
// Always log errors, success only if logging enabled
|
||||
if(result)
|
||||
{
|
||||
if(DB_EnableLogging)
|
||||
Print("DarvasBox: ", (orderType == ORDER_TYPE_BUY ? "Buy" : "Sell"), " Order Placed Successfully");
|
||||
}
|
||||
else
|
||||
{
|
||||
// Always log failures with detailed info
|
||||
uint retcode_uint = dbData.trade.ResultRetcode();
|
||||
int retcode = (int)retcode_uint;
|
||||
string desc = dbData.trade.ResultRetcodeDescription();
|
||||
ulong deal = dbData.trade.ResultDeal();
|
||||
ulong order = dbData.trade.ResultOrder();
|
||||
Print("DarvasBox: ", (orderType == ORDER_TYPE_BUY ? "Buy" : "Sell"),
|
||||
" Order Failed - Retcode: ", retcode,
|
||||
", Description: ", desc,
|
||||
", Deal: ", deal,
|
||||
", Order: ", order,
|
||||
", Symbol: ", dbData.symbol,
|
||||
", Requested Price: ", price,
|
||||
", SL: ", sl,
|
||||
", TP: ", tp);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void ProcessDarvasBox(string symbol)
|
||||
{
|
||||
// Skip if not initialized (symbol not available)
|
||||
if(!dbData.isInitialized)
|
||||
return;
|
||||
|
||||
dbData.symbol = symbol; // Update symbol in case it changed
|
||||
|
||||
// Calculate new box levels - EXACTLY like original (called every tick)
|
||||
CalculateDarvasBox();
|
||||
|
||||
// Check for trading signals - EXACTLY like original (checked every tick)
|
||||
if(dbData.boxFormed)
|
||||
{
|
||||
double currentPrice = SymbolInfoDouble(dbData.symbol, SYMBOL_ASK);
|
||||
long currentVolume_long = iVolume(dbData.symbol, PERIOD_CURRENT, 0);
|
||||
double currentVolume = (double)currentVolume_long;
|
||||
|
||||
if(DB_EnableLogging)
|
||||
{
|
||||
Print("DarvasBox: Current Price: ", currentPrice, " Box High: ", dbData.boxHigh, " Box Low: ", dbData.boxLow);
|
||||
Print("DarvasBox: Current Volume: ", currentVolume, " Volume Threshold: ", DB_VolumeThreshold);
|
||||
}
|
||||
|
||||
// Check for breakout above box - EXACTLY like original
|
||||
if(currentPrice > dbData.boxHigh && currentVolume > DB_VolumeThreshold)
|
||||
{
|
||||
if(DB_EnableLogging)
|
||||
Print("DarvasBox: Breakout Signal Detected - Price above box high");
|
||||
|
||||
// Buy signal
|
||||
if(!PositionExistsByMagic(dbData.symbol, (ulong)DB_MagicNumber)) // No existing positions with our magic number
|
||||
{
|
||||
double sl = currentPrice - DB_StopLoss * dbData.point;
|
||||
double tp = currentPrice + DB_TakeProfit * dbData.point;
|
||||
|
||||
if(DB_EnableLogging)
|
||||
Print("DarvasBox: Preparing Buy Order - Price: ", currentPrice, " SL: ", sl, " TP: ", tp);
|
||||
|
||||
PlaceOrder(ORDER_TYPE_BUY, currentPrice, sl, tp);
|
||||
}
|
||||
else if(DB_EnableLogging)
|
||||
Print("DarvasBox: Skipping Buy Signal - Position already exists");
|
||||
}
|
||||
|
||||
// Check for breakdown below box - EXACTLY like original
|
||||
if(currentPrice < dbData.boxLow && currentVolume > DB_VolumeThreshold)
|
||||
{
|
||||
if(DB_EnableLogging)
|
||||
Print("DarvasBox: Breakdown Signal Detected - Price below box low");
|
||||
|
||||
// Sell signal
|
||||
if(!PositionExistsByMagic(dbData.symbol, (ulong)DB_MagicNumber)) // No existing positions with our magic number
|
||||
{
|
||||
double sl = currentPrice + DB_StopLoss * dbData.point;
|
||||
double tp = currentPrice - DB_TakeProfit * dbData.point;
|
||||
|
||||
if(DB_EnableLogging)
|
||||
Print("DarvasBox: Preparing Sell Order - Price: ", currentPrice, " SL: ", sl, " TP: ", tp);
|
||||
|
||||
PlaceOrder(ORDER_TYPE_SELL, currentPrice, sl, tp);
|
||||
}
|
||||
else if(DB_EnableLogging)
|
||||
Print("DarvasBox: Skipping Sell Signal - Position already exists");
|
||||
}
|
||||
}
|
||||
else if(DB_EnableLogging)
|
||||
Print("DarvasBox: No Box Formed - Waiting for consolidation");
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
@@ -0,0 +1,496 @@
|
||||
//+------------------------------------------------------------------+
|
||||
//| EMASlopeDistanceStrategy.mqh |
|
||||
//+------------------------------------------------------------------+
|
||||
|
||||
bool InitEMASlopeDistance(string symbol)
|
||||
{
|
||||
esData.symbol = symbol;
|
||||
esData.letzte_überwachung_zeit = 0;
|
||||
esData.überwachung_aktiv = false;
|
||||
esData.preis_trigger_aktiv = false;
|
||||
esData.steigung_trigger_aktiv = false;
|
||||
esData.ticket = 0;
|
||||
esData.trades_in_current_crossover = 0;
|
||||
esData.crossover_detected = false;
|
||||
esData.trade_open_time = 0;
|
||||
esData.last_bar_time = 0;
|
||||
|
||||
// Check if symbol exists
|
||||
if(!SymbolSelect(symbol, true))
|
||||
{
|
||||
Print("EMASlopeDistance: Symbol '", symbol, "' not available in Market Watch. Please add it to Market Watch or check symbol name.");
|
||||
return false;
|
||||
}
|
||||
|
||||
Sleep(100); // Wait for symbol to be ready
|
||||
|
||||
esData.trade.SetExpertMagicNumber(ES_MagicNumber);
|
||||
esData.trade.SetDeviationInPoints(10);
|
||||
esData.trade.SetTypeFilling(ORDER_FILLING_IOC);
|
||||
|
||||
esData.ema_handle = iMA(symbol, ES_Timeframe, ES_EMA_Periode, 0, MODE_EMA, PRICE_CLOSE);
|
||||
|
||||
if(esData.ema_handle == INVALID_HANDLE)
|
||||
{
|
||||
Print("EMASlopeDistance: Error creating EMA indicator for '", symbol, "'");
|
||||
return false;
|
||||
}
|
||||
|
||||
ArraySetAsSeries(esData.ema_array, true);
|
||||
esData.isInitialized = true;
|
||||
Print("EMASlopeDistance: Successfully initialized for symbol '", symbol, "'");
|
||||
return true;
|
||||
}
|
||||
|
||||
void DeinitEMASlopeDistance()
|
||||
{
|
||||
if(esData.ema_handle != INVALID_HANDLE)
|
||||
IndicatorRelease(esData.ema_handle);
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| EMA Berechnung (EMA Calculation) |
|
||||
//+------------------------------------------------------------------+
|
||||
void BerechneEMA()
|
||||
{
|
||||
//--- EMA Werte vom Indicator kopieren (Copy EMA values from indicator)
|
||||
int copied = CopyBuffer(esData.ema_handle, 0, 0, 3, esData.ema_array);
|
||||
|
||||
if(copied <= 0)
|
||||
{
|
||||
Print("TRACE: Fehler beim Kopieren der EMA Werte - Copied: ", copied);
|
||||
return;
|
||||
}
|
||||
|
||||
Print("TRACE: EMA Werte kopiert: ", copied, " Bars");
|
||||
Print("TRACE: EMA [0]: ", esData.ema_array[0], " [1]: ", esData.ema_array[1], " [2]: ", esData.ema_array[2]);
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Trigger-Bedingungen prüfen (Check trigger conditions) |
|
||||
//+------------------------------------------------------------------+
|
||||
void PrüfeTrigger()
|
||||
{
|
||||
if(ArraySize(esData.ema_array) < 2)
|
||||
{
|
||||
Print("TRACE: Array zu klein - Größe: ", ArraySize(esData.ema_array));
|
||||
return;
|
||||
}
|
||||
|
||||
//--- Aktuelle Werte (Current values)
|
||||
double aktueller_preis = SymbolInfoDouble(esData.symbol, SYMBOL_BID);
|
||||
double aktueller_ask = SymbolInfoDouble(esData.symbol, SYMBOL_ASK);
|
||||
double aktueller_close = iClose(esData.symbol, ES_Timeframe, 0);
|
||||
int digits = (int)SymbolInfoInteger(esData.symbol, SYMBOL_DIGITS);
|
||||
double point = SymbolInfoDouble(esData.symbol, SYMBOL_POINT);
|
||||
double pips_multiplier = (digits == 3 || digits == 5) ? 10.0 : 1.0;
|
||||
|
||||
//--- EMA Werte in Variablen (EMA values in variables)
|
||||
double ema_aktuell = esData.ema_array[0];
|
||||
double ema_vorher = esData.ema_array[1];
|
||||
|
||||
//--- EMA Crossover Erkennung (EMA Crossover Detection)
|
||||
// Prüfe ob Preis die EMA kreuzt (Check if price crosses EMA)
|
||||
static double last_close = 0;
|
||||
static double last_ema = 0;
|
||||
|
||||
if(last_close != 0 && last_ema != 0)
|
||||
{
|
||||
bool crossover_bullish = (last_close <= last_ema) && (aktueller_close > ema_aktuell);
|
||||
bool crossover_bearish = (last_close >= last_ema) && (aktueller_close < ema_aktuell);
|
||||
|
||||
//--- Neues Crossover-Ereignis erkannt (New crossover event detected)
|
||||
if(crossover_bullish || crossover_bearish)
|
||||
{
|
||||
esData.trades_in_current_crossover = 0; // Reset trade counter
|
||||
Print("TRACE: EMA Crossover erkannt - ", (crossover_bullish ? "BULLISH" : "BEARISH"), " - Trade-Counter zurückgesetzt");
|
||||
Print("TRACE: Vorher: Close=", last_close, " EMA=", last_ema, " Jetzt: Close=", aktueller_close, " EMA=", ema_aktuell);
|
||||
}
|
||||
}
|
||||
|
||||
//--- Aktuelle Werte für nächsten Vergleich speichern (Save current values for next comparison)
|
||||
last_close = aktueller_close;
|
||||
last_ema = ema_aktuell;
|
||||
|
||||
//--- Preisbewegung zur EMA prüfen (Check price action to EMA)
|
||||
double preis_abstand = MathAbs(aktueller_close - ema_aktuell) / point / pips_multiplier;
|
||||
|
||||
Print("TRACE: Preis-Abstand: ", preis_abstand, " Pips (Schwelle: ", ES_PreisSchwelle, ")");
|
||||
Print("TRACE: Close: ", aktueller_close, " EMA: ", ema_aktuell);
|
||||
Print("TRACE: Trades im aktuellen Crossover: ", esData.trades_in_current_crossover, "/", ES_MaxTradesPerCrossover);
|
||||
|
||||
if(preis_abstand > ES_PreisSchwelle && !esData.preis_trigger_aktiv)
|
||||
{
|
||||
esData.preis_trigger_aktiv = true;
|
||||
Print("TRACE: Preis-Trigger aktiviert: ", preis_abstand, " Pips");
|
||||
}
|
||||
|
||||
//--- EMA Steigung prüfen (Check EMA slope)
|
||||
double steigung = (ema_aktuell - ema_vorher) / point / pips_multiplier;
|
||||
|
||||
Print("TRACE: EMA Steigung: ", steigung, " Pips (Schwelle: ", ES_SteigungSchwelle, ")");
|
||||
|
||||
if(MathAbs(steigung) > ES_SteigungSchwelle && !esData.steigung_trigger_aktiv)
|
||||
{
|
||||
esData.steigung_trigger_aktiv = true;
|
||||
Print("TRACE: Steigungs-Trigger aktiviert: ", steigung, " Pips");
|
||||
}
|
||||
|
||||
//--- Überwachung starten wenn beide Trigger aktiv sind (Start monitoring when both triggers are active)
|
||||
if(esData.preis_trigger_aktiv && esData.steigung_trigger_aktiv && !esData.überwachung_aktiv)
|
||||
{
|
||||
esData.überwachung_aktiv = true;
|
||||
|
||||
if(ES_UseBarData)
|
||||
{
|
||||
esData.letzte_überwachung_zeit = iTime(esData.symbol, ES_Timeframe, 0); // Aktuelle Bar-Zeit
|
||||
Print("TRACE: Überwachung gestartet - Beide Trigger aktiv (Bar: ", TimeToString(esData.letzte_überwachung_zeit), ")");
|
||||
}
|
||||
else
|
||||
{
|
||||
esData.letzte_überwachung_zeit = TimeCurrent(); // Aktuelle Tick-Zeit
|
||||
Print("TRACE: Überwachung gestartet - Beide Trigger aktiv (Tick)");
|
||||
}
|
||||
}
|
||||
|
||||
//--- Trade platzieren wenn Überwachung aktiv und Preis über/unter EMA (Place trade when monitoring active and price above/below EMA)
|
||||
if(esData.überwachung_aktiv)
|
||||
{
|
||||
bool bullish_signal = aktueller_close > ema_aktuell;
|
||||
bool bearish_signal = aktueller_close < ema_aktuell;
|
||||
|
||||
Print("TRACE: Signal Check - Bullish: ", bullish_signal, " Bearish: ", bearish_signal);
|
||||
Print("TRACE: Close: ", aktueller_close, " EMA: ", ema_aktuell);
|
||||
Print("TRACE: Differenz: ", aktueller_close - ema_aktuell);
|
||||
|
||||
//--- Trade-Limit prüfen (Check trade limit)
|
||||
if(esData.trades_in_current_crossover >= ES_MaxTradesPerCrossover)
|
||||
{
|
||||
Print("TRACE: Trade-Limit erreicht (", ES_MaxTradesPerCrossover, ") - Kein neuer Trade");
|
||||
return;
|
||||
}
|
||||
|
||||
if(bullish_signal && !PositionExistsByMagic(esData.symbol, (ulong)ES_MagicNumber))
|
||||
{
|
||||
Print("TRACE: Versuche KAUF-Trade zu platzieren (Trade #", esData.trades_in_current_crossover + 1, ")");
|
||||
if(PlatziereTrade(ORDER_TYPE_BUY))
|
||||
{
|
||||
esData.trades_in_current_crossover++;
|
||||
}
|
||||
}
|
||||
else if(bearish_signal && !PositionExistsByMagic(esData.symbol, (ulong)ES_MagicNumber))
|
||||
{
|
||||
Print("TRACE: Versuche VERKAUF-Trade zu platzieren (Trade #", esData.trades_in_current_crossover + 1, ")");
|
||||
if(PlatziereTrade(ORDER_TYPE_SELL))
|
||||
{
|
||||
esData.trades_in_current_crossover++;
|
||||
}
|
||||
}
|
||||
else if(PositionExistsByMagic(esData.symbol, (ulong)ES_MagicNumber))
|
||||
{
|
||||
Print("TRACE: Position bereits offen - kein neuer Trade");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Trade platzieren (Place trade) |
|
||||
//+------------------------------------------------------------------+
|
||||
bool PlatziereTrade(ENUM_ORDER_TYPE order_type)
|
||||
{
|
||||
Print("TRACE: Versuche Trade zu platzieren - Typ: ", (order_type == ORDER_TYPE_BUY) ? "KAUF" : "VERKAUF");
|
||||
Print("TRACE: Lot: ", g_ES_LotSize);
|
||||
|
||||
bool success = false;
|
||||
|
||||
if(order_type == ORDER_TYPE_BUY)
|
||||
{
|
||||
success = esData.trade.Buy(g_ES_LotSize, esData.symbol, 0, 0, 0, "EMA Crossover Trade");
|
||||
}
|
||||
else
|
||||
{
|
||||
success = esData.trade.Sell(g_ES_LotSize, esData.symbol, 0, 0, 0, "EMA Crossover Trade");
|
||||
}
|
||||
|
||||
if(success)
|
||||
{
|
||||
esData.ticket = (int)esData.trade.ResultOrder();
|
||||
Print("TRACE: Trade erfolgreich platziert: ", (order_type == ORDER_TYPE_BUY) ? "KAUF" : "VERKAUF", " Ticket: ", esData.ticket);
|
||||
|
||||
//--- Trade-Öffnungszeit speichern (Save trade opening time)
|
||||
esData.trade_open_time = iTime(esData.symbol, ES_Timeframe, 0);
|
||||
Print("TRACE: Trade-Öffnungszeit: ", TimeToString(esData.trade_open_time));
|
||||
|
||||
//--- Überwachung zurücksetzen (Reset monitoring)
|
||||
esData.überwachung_aktiv = false;
|
||||
esData.preis_trigger_aktiv = false;
|
||||
esData.steigung_trigger_aktiv = false;
|
||||
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
Print("TRACE: Fehler beim Platzieren des Trades - Retcode: ", esData.trade.ResultRetcode());
|
||||
Print("TRACE: Fehlerbeschreibung: ", esData.trade.ResultRetcodeDescription());
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Trades verwalten (Manage trades) |
|
||||
//+------------------------------------------------------------------+
|
||||
void VerwalteTrades()
|
||||
{
|
||||
if(!PositionSelectByMagic(esData.symbol, (ulong)ES_MagicNumber))
|
||||
return;
|
||||
|
||||
double position_profit = PositionGetDouble(POSITION_PROFIT);
|
||||
double position_open_price = PositionGetDouble(POSITION_PRICE_OPEN);
|
||||
double current_price = PositionGetDouble(POSITION_PRICE_CURRENT);
|
||||
ENUM_POSITION_TYPE position_type = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);
|
||||
|
||||
int digits = (int)SymbolInfoInteger(esData.symbol, SYMBOL_DIGITS);
|
||||
double point = SymbolInfoDouble(esData.symbol, SYMBOL_POINT);
|
||||
double pips_multiplier = (digits == 3 || digits == 5) ? 10.0 : 1.0;
|
||||
double trailing_stop_pips = ES_TrailingStop;
|
||||
|
||||
//--- Gleitender Stop (Trailing Stop) - nur wenn Position im Profit ist
|
||||
if(position_profit > 0) // Only apply trailing stop when in profit
|
||||
{
|
||||
if(position_type == POSITION_TYPE_BUY)
|
||||
{
|
||||
double new_stop_loss = current_price - (trailing_stop_pips * point * pips_multiplier);
|
||||
double current_stop_loss = PositionGetDouble(POSITION_SL);
|
||||
|
||||
// Only move stop loss if new stop is higher than current stop
|
||||
if(new_stop_loss > current_stop_loss)
|
||||
{
|
||||
ÄndereStopLoss(new_stop_loss);
|
||||
}
|
||||
}
|
||||
else if(position_type == POSITION_TYPE_SELL)
|
||||
{
|
||||
double new_stop_loss = current_price + (trailing_stop_pips * point * pips_multiplier);
|
||||
double current_stop_loss = PositionGetDouble(POSITION_SL);
|
||||
|
||||
// Only move stop loss if new stop is lower than current stop
|
||||
if(new_stop_loss < current_stop_loss || current_stop_loss == 0)
|
||||
{
|
||||
ÄndereStopLoss(new_stop_loss);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//--- Ausstieg bei Preis unter/über EMA (Exit when price below/above EMA)
|
||||
if(ArraySize(esData.ema_array) >= 1)
|
||||
{
|
||||
double aktueller_close = iClose(esData.symbol, ES_Timeframe, 0);
|
||||
double ema_aktuell = esData.ema_array[0];
|
||||
bool exit_bullish = (position_type == POSITION_TYPE_SELL && aktueller_close > ema_aktuell);
|
||||
bool exit_bearish = (position_type == POSITION_TYPE_BUY && aktueller_close < ema_aktuell);
|
||||
|
||||
if(exit_bullish || exit_bearish)
|
||||
{
|
||||
Print("TRACE: Ausstiegssignal - Close: ", aktueller_close, " EMA: ", ema_aktuell);
|
||||
SchließePosition("EMA Crossover Exit");
|
||||
|
||||
Print("TRACE: Position geschlossen - Trade-Counter bleibt bei ", esData.trades_in_current_crossover);
|
||||
}
|
||||
}
|
||||
|
||||
//--- Profit-Prüfung nach X Bars (Profit check after X bars)
|
||||
if(ES_CloseUnprofitableTrades && esData.trade_open_time != 0 && PositionExistsByMagic(esData.symbol, (ulong)ES_MagicNumber))
|
||||
{
|
||||
Print("TRACE: Profit-Prüfung aktiviert - CloseUnprofitableTrades: ", ES_CloseUnprofitableTrades);
|
||||
PrüfeProfitNachBars();
|
||||
}
|
||||
else if(!ES_CloseUnprofitableTrades)
|
||||
{
|
||||
Print("TRACE: Profit-Prüfung deaktiviert - CloseUnprofitableTrades: ", ES_CloseUnprofitableTrades);
|
||||
}
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Profit-Prüfung nach X Bars (Profit check after X bars) |
|
||||
//+------------------------------------------------------------------+
|
||||
void PrüfeProfitNachBars()
|
||||
{
|
||||
if(!PositionSelectByMagic(esData.symbol, (ulong)ES_MagicNumber))
|
||||
{
|
||||
return; // Keine Position offen
|
||||
}
|
||||
|
||||
datetime current_bar_time = iTime(esData.symbol, ES_Timeframe, 0);
|
||||
int bars_since_trade_open = iBarShift(esData.symbol, ES_Timeframe, esData.trade_open_time);
|
||||
|
||||
Print("TRACE: Bars seit Trade-Öffnung: ", bars_since_trade_open, "/", ES_ProfitCheckBars);
|
||||
|
||||
//--- Prüfe ob genügend Bars vergangen sind (Check if enough bars have passed)
|
||||
if(bars_since_trade_open >= ES_ProfitCheckBars)
|
||||
{
|
||||
double position_profit = PositionGetDouble(POSITION_PROFIT);
|
||||
double position_volume = PositionGetDouble(POSITION_VOLUME);
|
||||
ENUM_POSITION_TYPE position_type = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);
|
||||
|
||||
Print("TRACE: Profit-Prüfung nach ", ES_ProfitCheckBars, " Bars");
|
||||
Print("TRACE: Position Profit: ", position_profit, " USD");
|
||||
|
||||
//--- Schließe Position wenn nicht im Profit (Close position if not in profit)
|
||||
if(position_profit <= 0)
|
||||
{
|
||||
Print("TRACE: Position nicht im Profit - Schließe Position");
|
||||
SchließePosition("Profit Check - Unprofitable");
|
||||
|
||||
//--- Trade-Öffnungszeit zurücksetzen (Reset trade opening time)
|
||||
esData.trade_open_time = 0;
|
||||
Print("TRACE: Trade-Öffnungszeit zurückgesetzt");
|
||||
}
|
||||
else
|
||||
{
|
||||
Print("TRACE: Position im Profit - Behalte Position");
|
||||
//--- Trade-Öffnungszeit zurücksetzen um weitere Prüfungen zu vermeiden (Reset to avoid further checks)
|
||||
esData.trade_open_time = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Stop Loss ändern (Modify Stop Loss) |
|
||||
//+------------------------------------------------------------------+
|
||||
void ÄndereStopLoss(double new_stop_loss)
|
||||
{
|
||||
Print("TRACE: Versuche Stop Loss zu ändern auf: ", new_stop_loss);
|
||||
|
||||
bool success = ModifyPositionByMagic(esData.trade, esData.symbol, (ulong)ES_MagicNumber, new_stop_loss, PositionGetDouble(POSITION_TP));
|
||||
|
||||
if(success)
|
||||
{
|
||||
Print("TRACE: Stop Loss erfolgreich geändert auf: ", new_stop_loss);
|
||||
}
|
||||
else
|
||||
{
|
||||
Print("TRACE: Fehler beim Ändern des Stop Loss - Retcode: ", esData.trade.ResultRetcode());
|
||||
Print("TRACE: Fehlerbeschreibung: ", esData.trade.ResultRetcodeDescription());
|
||||
}
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Position schließen (Close position) |
|
||||
//+------------------------------------------------------------------+
|
||||
void SchließePosition(string reason = "Unbekannt")
|
||||
{
|
||||
Print("TRACE: Versuche Position zu schließen - Grund: ", reason);
|
||||
|
||||
bool success = ClosePositionByMagic(esData.trade, esData.symbol, (ulong)ES_MagicNumber);
|
||||
|
||||
if(success)
|
||||
{
|
||||
Print("TRACE: Position erfolgreich geschlossen - Grund: ", reason);
|
||||
}
|
||||
else
|
||||
{
|
||||
Print("TRACE: Fehler beim Schließen der Position - Retcode: ", esData.trade.ResultRetcode());
|
||||
Print("TRACE: Fehlerbeschreibung: ", esData.trade.ResultRetcodeDescription());
|
||||
}
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Expert tick function |
|
||||
//+------------------------------------------------------------------+
|
||||
void ProcessEMASlopeDistance(string symbol)
|
||||
{
|
||||
// Skip if not initialized (symbol not available)
|
||||
if(!esData.isInitialized)
|
||||
return;
|
||||
|
||||
esData.symbol = symbol; // Update symbol in case it changed
|
||||
|
||||
//--- Bar-Daten oder Tick-Daten verwenden (Use bar data or tick data)
|
||||
if(ES_UseBarData)
|
||||
{
|
||||
//--- Nur bei neuen Bars ausführen (Only execute on new bars)
|
||||
datetime current_bar_time = iTime(esData.symbol, ES_Timeframe, 0);
|
||||
|
||||
if(current_bar_time == esData.last_bar_time)
|
||||
{
|
||||
return; // Kein neuer Bar, nichts tun
|
||||
}
|
||||
|
||||
esData.last_bar_time = current_bar_time;
|
||||
}
|
||||
|
||||
//--- EMA Werte berechnen (Calculate EMA values)
|
||||
BerechneEMA();
|
||||
|
||||
//--- Debug: Aktuelle Werte ausgeben (Debug: Output current values)
|
||||
if(ArraySize(esData.ema_array) > 0)
|
||||
{
|
||||
double aktueller_close = iClose(esData.symbol, ES_Timeframe, 0);
|
||||
double ema_aktuell = esData.ema_array[0];
|
||||
double ema_vorher = esData.ema_array[1];
|
||||
int digits = (int)SymbolInfoInteger(esData.symbol, SYMBOL_DIGITS);
|
||||
double point = SymbolInfoDouble(esData.symbol, SYMBOL_POINT);
|
||||
double preis_abstand = MathAbs(aktueller_close - ema_aktuell) / point;
|
||||
double steigung = (ema_aktuell - ema_vorher) / point;
|
||||
|
||||
if(ES_UseBarData)
|
||||
{
|
||||
Print("=== DEBUG INFO (Neuer Bar) ===");
|
||||
Print("Bar Zeit: ", TimeToString(iTime(esData.symbol, ES_Timeframe, 0)));
|
||||
}
|
||||
else
|
||||
{
|
||||
Print("=== DEBUG INFO (Tick) ===");
|
||||
}
|
||||
|
||||
Print("Aktueller Close: ", aktueller_close);
|
||||
Print("EMA: ", ema_aktuell);
|
||||
Print("Preis-Abstand: ", preis_abstand, " Pips");
|
||||
Print("EMA Steigung: ", steigung, " Pips");
|
||||
Print("Differenz Close-EMA: ", aktueller_close - ema_aktuell);
|
||||
Print("Preis-Trigger: ", esData.preis_trigger_aktiv, " Steigungs-Trigger: ", esData.steigung_trigger_aktiv);
|
||||
Print("Überwachung aktiv: ", esData.überwachung_aktiv);
|
||||
Print("Position offen: ", PositionExistsByMagic(esData.symbol, (ulong)ES_MagicNumber));
|
||||
Print("Trades im aktuellen Crossover: ", esData.trades_in_current_crossover, "/", ES_MaxTradesPerCrossover);
|
||||
Print("==================");
|
||||
}
|
||||
|
||||
//--- Überwachung prüfen (Check monitoring)
|
||||
if(esData.überwachung_aktiv)
|
||||
{
|
||||
if(ES_UseBarData)
|
||||
{
|
||||
// Bar-basierte Überwachungszeit
|
||||
int bars_since_monitoring = iBarShift(esData.symbol, ES_Timeframe, esData.letzte_überwachung_zeit);
|
||||
int timeout_bars = (int)(ES_ÜberwachungTimeout / PeriodSeconds(ES_Timeframe));
|
||||
|
||||
if(bars_since_monitoring > timeout_bars)
|
||||
{
|
||||
esData.überwachung_aktiv = false;
|
||||
esData.preis_trigger_aktiv = false;
|
||||
esData.steigung_trigger_aktiv = false;
|
||||
Print("Überwachung beendet - Bar-basierte Zeitüberschreitung (", bars_since_monitoring, " Bars)");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Tick-basierte Überwachungszeit
|
||||
if(TimeCurrent() - esData.letzte_überwachung_zeit > ES_ÜberwachungTimeout)
|
||||
{
|
||||
esData.überwachung_aktiv = false;
|
||||
esData.preis_trigger_aktiv = false;
|
||||
esData.steigung_trigger_aktiv = false;
|
||||
Print("Überwachung beendet - Tick-basierte Zeitüberschreitung");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//--- Trigger-Bedingungen prüfen (Check trigger conditions)
|
||||
PrüfeTrigger();
|
||||
|
||||
//--- Trade Management (Trade management)
|
||||
VerwalteTrades();
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
@@ -0,0 +1,240 @@
|
||||
//+------------------------------------------------------------------+
|
||||
//| RSICrossOverReversalStrategy.mqh |
|
||||
//+------------------------------------------------------------------+
|
||||
|
||||
void WeekDays_Init()
|
||||
{
|
||||
rcData.WeekDays[0] = RC_Sunday;
|
||||
rcData.WeekDays[1] = RC_Monday;
|
||||
rcData.WeekDays[2] = RC_Tuesday;
|
||||
rcData.WeekDays[3] = RC_Wednesday;
|
||||
rcData.WeekDays[4] = RC_Thursday;
|
||||
rcData.WeekDays[5] = RC_Friday;
|
||||
rcData.WeekDays[6] = RC_Saturday;
|
||||
}
|
||||
|
||||
bool WeekDays_Check(datetime aTime)
|
||||
{
|
||||
MqlDateTime stm;
|
||||
TimeToStruct(aTime, stm);
|
||||
return(rcData.WeekDays[stm.day_of_week]);
|
||||
}
|
||||
|
||||
int TimeHour(datetime when = 0)
|
||||
{
|
||||
if(when == 0) when = TimeCurrent();
|
||||
MqlDateTime dt;
|
||||
TimeToStruct(when, dt);
|
||||
return dt.hour;
|
||||
}
|
||||
|
||||
bool InitRSICrossOverReversal(string symbol)
|
||||
{
|
||||
WeekDays_Init();
|
||||
|
||||
rcData.symbol = symbol;
|
||||
rcData.previousRSIDef = 0;
|
||||
rcData.lastTradeTime = 0;
|
||||
rcData.bartime = 0;
|
||||
rcData.lastBarTime = 0;
|
||||
|
||||
// Check if symbol exists
|
||||
if(!SymbolSelect(symbol, true))
|
||||
{
|
||||
Print("RSICrossOverReversal: Symbol '", symbol, "' not available in Market Watch. Please add it to Market Watch or check symbol name.");
|
||||
return false;
|
||||
}
|
||||
|
||||
Sleep(100); // Wait for symbol to be ready
|
||||
|
||||
rcData.rsiHandle = iRSI(symbol, RC_TimeFrame1, RC_rsiPeriod, PRICE_CLOSE);
|
||||
if(rcData.rsiHandle == INVALID_HANDLE)
|
||||
{
|
||||
Print("RSICrossOverReversal: Error creating RSI handle for '", symbol, "'");
|
||||
return false;
|
||||
}
|
||||
|
||||
rcData.emaHandle = iMA(symbol, RC_TimeFrame2, RC_emaPeriod, 0, MODE_EMA, PRICE_CLOSE);
|
||||
if(rcData.emaHandle == INVALID_HANDLE)
|
||||
{
|
||||
Print("RSICrossOverReversal: Error creating EMA handle for '", symbol, "'");
|
||||
return false;
|
||||
}
|
||||
|
||||
rcData.trade.SetExpertMagicNumber(RC_MagicNumber);
|
||||
rcData.isInitialized = true;
|
||||
Print("RSICrossOverReversal: Successfully initialized for symbol '", symbol, "'");
|
||||
return true;
|
||||
}
|
||||
|
||||
void DeinitRSICrossOverReversal()
|
||||
{
|
||||
if(rcData.rsiHandle != INVALID_HANDLE)
|
||||
IndicatorRelease(rcData.rsiHandle);
|
||||
if(rcData.emaHandle != INVALID_HANDLE)
|
||||
IndicatorRelease(rcData.emaHandle);
|
||||
}
|
||||
|
||||
void Close_Position_MN(ulong magicNumber)
|
||||
{
|
||||
ClosePositionByMagic(rcData.trade, rcData.symbol, (int)magicNumber);
|
||||
}
|
||||
|
||||
void ApplyTrailingStop()
|
||||
{
|
||||
if(!PositionSelectByMagic(rcData.symbol, RC_MagicNumber))
|
||||
return;
|
||||
|
||||
ulong PositionTicket = PositionGetInteger(POSITION_TICKET);
|
||||
ENUM_POSITION_TYPE trade_type = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);
|
||||
string symbol = rcData.symbol;
|
||||
|
||||
double POINT = SymbolInfoDouble(symbol, SYMBOL_POINT);
|
||||
int DIGIT = (int)SymbolInfoInteger(symbol, SYMBOL_DIGITS);
|
||||
|
||||
if(trade_type == POSITION_TYPE_BUY)
|
||||
{
|
||||
double Bid = NormalizeDouble(SymbolInfoDouble(symbol, SYMBOL_BID), DIGIT);
|
||||
|
||||
if(Bid - PositionGetDouble(POSITION_PRICE_OPEN) > NormalizeDouble(POINT * RC_TrailingStop, DIGIT))
|
||||
{
|
||||
if(PositionGetDouble(POSITION_SL) < NormalizeDouble(Bid - POINT * RC_TrailingStop, DIGIT))
|
||||
{
|
||||
ModifyPositionByMagic(rcData.trade, symbol, RC_MagicNumber,
|
||||
NormalizeDouble(Bid - POINT * RC_TrailingStop, DIGIT),
|
||||
PositionGetDouble(POSITION_TP));
|
||||
}
|
||||
}
|
||||
}
|
||||
else if(trade_type == POSITION_TYPE_SELL)
|
||||
{
|
||||
double Ask = NormalizeDouble(SymbolInfoDouble(symbol, SYMBOL_ASK), DIGIT);
|
||||
|
||||
if((PositionGetDouble(POSITION_PRICE_OPEN) - Ask) > NormalizeDouble(POINT * RC_TrailingStop, DIGIT))
|
||||
{
|
||||
if((PositionGetDouble(POSITION_SL) > NormalizeDouble(Ask + POINT * RC_TrailingStop, DIGIT)) ||
|
||||
(PositionGetDouble(POSITION_SL) == 0))
|
||||
{
|
||||
ModifyPositionByMagic(rcData.trade, symbol, RC_MagicNumber,
|
||||
NormalizeDouble(Ask + POINT * RC_TrailingStop, DIGIT),
|
||||
PositionGetDouble(POSITION_TP));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ProcessRSICrossOverReversal(string symbol)
|
||||
{
|
||||
// Skip if not initialized (symbol not available)
|
||||
if(!rcData.isInitialized)
|
||||
return;
|
||||
|
||||
rcData.symbol = symbol; // Update symbol in case it changed
|
||||
if(rcData.bartime == iTime(rcData.symbol, RC_BarTimeFrame, 0))
|
||||
return;
|
||||
rcData.bartime = iTime(rcData.symbol, RC_BarTimeFrame, 0);
|
||||
|
||||
double rsi[];
|
||||
if(CopyBuffer(rcData.rsiHandle, 0, 0, 2, rsi) <= 0)
|
||||
return;
|
||||
|
||||
double ema[];
|
||||
if(CopyBuffer(rcData.emaHandle, 0, 0, 2, ema) <= 0)
|
||||
return;
|
||||
|
||||
datetime currentTime = TimeCurrent();
|
||||
int currentHour = TimeHour(TimeCurrent());
|
||||
|
||||
if(!WeekDays_Check(TimeTradeServer()))
|
||||
{
|
||||
Close_Position_MN(RC_MagicNumber);
|
||||
return;
|
||||
}
|
||||
|
||||
if(!((currentHour < RC_tradingHourOneEnd && currentHour > RC_tradingHourOneBegin) ||
|
||||
(currentHour < RC_tradingHourTwoEnd && currentHour > RC_tradingHourTwoBegin)))
|
||||
{
|
||||
Close_Position_MN(RC_MagicNumber);
|
||||
return;
|
||||
}
|
||||
|
||||
bool hasPosition = PositionExistsByMagic(rcData.symbol, RC_MagicNumber);
|
||||
|
||||
double currentRSI = rsi[0];
|
||||
double previousRSI = rsi[1];
|
||||
|
||||
if(rcData.previousRSIDef == 0)
|
||||
{
|
||||
rcData.previousRSIDef = currentRSI;
|
||||
return;
|
||||
}
|
||||
|
||||
double currentEMA = ema[0];
|
||||
double previousEMA = ema[1];
|
||||
|
||||
double emaSlope = (currentEMA - previousEMA) * 100;
|
||||
double closeCurr = iClose(Symbol(), Period(), 0);
|
||||
double priceToEmaDistance = (closeCurr - currentEMA) * 10;
|
||||
|
||||
bool isBuyPosition = false;
|
||||
bool isSellPosition = false;
|
||||
if(hasPosition)
|
||||
{
|
||||
if(PositionSelectByMagic(rcData.symbol, RC_MagicNumber))
|
||||
{
|
||||
ENUM_POSITION_TYPE positionType = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);
|
||||
if(positionType == POSITION_TYPE_BUY)
|
||||
isBuyPosition = true;
|
||||
else if(positionType == POSITION_TYPE_SELL)
|
||||
isSellPosition = true;
|
||||
}
|
||||
}
|
||||
|
||||
ApplyTrailingStop();
|
||||
|
||||
bool cooldownPassed = (currentTime - rcData.lastTradeTime) >= RC_cooldownSeconds;
|
||||
bool isTrendStrong = MathAbs(emaSlope) > RC_emaSlopeThreshold || MathAbs(priceToEmaDistance) > RC_emaDistanceThreshold;
|
||||
|
||||
if(isBuyPosition && currentRSI > RC_exitBuyRSI)
|
||||
{
|
||||
Close_Position_MN(RC_MagicNumber);
|
||||
rcData.lastTradeTime = currentTime;
|
||||
}
|
||||
|
||||
if(isSellPosition && currentRSI < RC_exitSellRSI)
|
||||
{
|
||||
Close_Position_MN(RC_MagicNumber);
|
||||
rcData.lastTradeTime = currentTime;
|
||||
}
|
||||
|
||||
if(isTrendStrong)
|
||||
{
|
||||
Close_Position_MN(RC_MagicNumber);
|
||||
rcData.lastTradeTime = currentTime;
|
||||
return;
|
||||
}
|
||||
|
||||
if(currentRSI < RC_overboughtLevel - RC_entryRSISellSpread && rcData.previousRSIDef >= RC_overboughtLevel &&
|
||||
!isSellPosition && !hasPosition && cooldownPassed)
|
||||
{
|
||||
rcData.trade.SetExpertMagicNumber(RC_MagicNumber);
|
||||
if(rcData.trade.Sell(g_RC_LotSize, rcData.symbol, 0.0, 0.0, 0.0, "Sell Order"))
|
||||
{
|
||||
rcData.lastTradeTime = currentTime;
|
||||
}
|
||||
}
|
||||
|
||||
if(currentRSI > RC_oversoldLevel + RC_entryRSIBuySpread && rcData.previousRSIDef <= RC_oversoldLevel &&
|
||||
!isBuyPosition && !hasPosition && cooldownPassed)
|
||||
{
|
||||
rcData.trade.SetExpertMagicNumber(RC_MagicNumber);
|
||||
if(rcData.trade.Buy(g_RC_LotSize, rcData.symbol, 0.0, 0.0, 0.0, "Buy Order"))
|
||||
{
|
||||
rcData.lastTradeTime = currentTime;
|
||||
}
|
||||
}
|
||||
|
||||
rcData.previousRSIDef = currentRSI;
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
@@ -0,0 +1,471 @@
|
||||
//+------------------------------------------------------------------+
|
||||
//| RSIMidPointHijackStrategy.mqh |
|
||||
//+------------------------------------------------------------------+
|
||||
|
||||
bool IsNewBar(string symbol)
|
||||
{
|
||||
datetime time[];
|
||||
if(CopyTime(symbol, RM_InpTimeframe, 0, 1, time) > 0)
|
||||
{
|
||||
if(time[0] != rmData.lastBarTime)
|
||||
{
|
||||
rmData.lastBarTime = time[0];
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool IsWithinTradingHours(int startHour, int endHour)
|
||||
{
|
||||
MqlDateTime currentTime;
|
||||
TimeToStruct(TimeCurrent(), currentTime);
|
||||
|
||||
if(startHour <= endHour)
|
||||
return (currentTime.hour >= startHour && currentTime.hour < endHour);
|
||||
else
|
||||
return (currentTime.hour >= startHour || currentTime.hour < endHour);
|
||||
}
|
||||
|
||||
bool HasPosition(string symbol, int magic)
|
||||
{
|
||||
return PositionExistsByMagic(symbol, magic);
|
||||
}
|
||||
|
||||
bool HasProfitablePosition(int excludeMagic)
|
||||
{
|
||||
bool hasProfitable = false;
|
||||
for(int i = PositionsTotal() - 1; i >= 0; i--)
|
||||
{
|
||||
if(rmData.positionInfo.SelectByIndex(i))
|
||||
{
|
||||
if(rmData.positionInfo.Magic() != excludeMagic)
|
||||
{
|
||||
double profit = rmData.positionInfo.Profit();
|
||||
if(profit > RM_InpLockProfitThreshold * _Point)
|
||||
{
|
||||
hasProfitable = true;
|
||||
if(RM_InpCloseOppositeTrades)
|
||||
{
|
||||
if((excludeMagic == RM_InpMagicNumberRSIFollow && rmData.positionInfo.Magic() == RM_InpMagicNumberRSIReverse) ||
|
||||
(excludeMagic == RM_InpMagicNumberRSIReverse && rmData.positionInfo.Magic() == RM_InpMagicNumberRSIFollow) ||
|
||||
(excludeMagic == RM_InpMagicNumberEMACross && (rmData.positionInfo.Magic() == RM_InpMagicNumberRSIReverse || rmData.positionInfo.Magic() == RM_InpMagicNumberRSIFollow)) ||
|
||||
((excludeMagic == RM_InpMagicNumberRSIFollow || excludeMagic == RM_InpMagicNumberRSIReverse) && rmData.positionInfo.Magic() == RM_InpMagicNumberEMACross))
|
||||
{
|
||||
ClosePosition(rmData.symbol, (int)rmData.positionInfo.Magic());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return hasProfitable;
|
||||
}
|
||||
|
||||
bool IsRSIReverseInCooldown(string symbol)
|
||||
{
|
||||
if(RM_InpRSIReverseCooldownBars <= 0)
|
||||
return false;
|
||||
|
||||
if(!rmData.rsiReverseInCooldown)
|
||||
return false;
|
||||
|
||||
datetime time[];
|
||||
if(CopyTime(symbol, RM_InpTimeframe, 0, 1, time) > 0)
|
||||
{
|
||||
datetime currentBarTime = time[0];
|
||||
datetime cooldownEndTime = rmData.rsiReverseLastCloseTime + RM_InpRSIReverseCooldownBars * PeriodSeconds(RM_InpTimeframe);
|
||||
|
||||
if(currentBarTime >= cooldownEndTime)
|
||||
{
|
||||
rmData.rsiReverseInCooldown = false;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void CheckRSIFollowStrategy(string symbol)
|
||||
{
|
||||
if(!IsWithinTradingHours(RM_InpRSIFollowStartHour, RM_InpRSIFollowEndHour))
|
||||
{
|
||||
if(RM_InpRSIFollowCloseOutsideHours)
|
||||
{
|
||||
if(HasPosition(symbol, RM_InpMagicNumberRSIFollow))
|
||||
ClosePosition(symbol, RM_InpMagicNumberRSIFollow);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if(RM_InpEnableStrategyLock && HasProfitablePosition(RM_InpMagicNumberRSIFollow))
|
||||
return;
|
||||
|
||||
if(rmData.lastBarRSI > RM_InpRSIOverbought)
|
||||
rmData.rsiOverbought = true;
|
||||
else if(rmData.lastBarRSI < RM_InpRSIOversold)
|
||||
rmData.rsiOversold = true;
|
||||
|
||||
if(rmData.rsiOverbought && rmData.lastBarRSI < RM_InpRSIExitLevel)
|
||||
{
|
||||
if(!HasPosition(symbol, RM_InpMagicNumberRSIFollow))
|
||||
{
|
||||
rmData.trade.SetExpertMagicNumber(RM_InpMagicNumberRSIFollow);
|
||||
rmData.trade.Sell(g_RM_LotSize, symbol, 0, 0, 0, "RSI Follow");
|
||||
}
|
||||
rmData.rsiOverbought = false;
|
||||
}
|
||||
else if(rmData.rsiOversold && rmData.lastBarRSI > RM_InpRSIExitLevel)
|
||||
{
|
||||
if(!HasPosition(symbol, RM_InpMagicNumberRSIFollow))
|
||||
{
|
||||
rmData.trade.SetExpertMagicNumber(RM_InpMagicNumberRSIFollow);
|
||||
rmData.trade.Buy(g_RM_LotSize, symbol, 0, 0, 0, "RSI Follow");
|
||||
}
|
||||
rmData.rsiOversold = false;
|
||||
}
|
||||
}
|
||||
|
||||
void CheckRSIReverseStrategy(string symbol)
|
||||
{
|
||||
if(!IsWithinTradingHours(RM_InpRSIReverseStartHour, RM_InpRSIReverseEndHour))
|
||||
{
|
||||
if(RM_InpRSIReverseCloseOutsideHours)
|
||||
{
|
||||
if(HasPosition(symbol, RM_InpMagicNumberRSIReverse))
|
||||
ClosePosition(symbol, RM_InpMagicNumberRSIReverse);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if(RM_InpEnableStrategyLock && HasProfitablePosition(RM_InpMagicNumberRSIReverse))
|
||||
return;
|
||||
|
||||
if(IsRSIReverseInCooldown(symbol))
|
||||
return;
|
||||
|
||||
if(rmData.lastBarRSIReverse > RM_InpRSIReverseOverbought)
|
||||
rmData.rsiReverseOverbought = true;
|
||||
else if(rmData.lastBarRSIReverse < RM_InpRSIReverseOversold)
|
||||
rmData.rsiReverseOversold = true;
|
||||
|
||||
if(rmData.rsiReverseOverbought && rmData.lastBarRSIReverse < RM_InpRSIReverseCrossLevel)
|
||||
{
|
||||
if(!HasPosition(symbol, RM_InpMagicNumberRSIReverse))
|
||||
{
|
||||
rmData.trade.SetExpertMagicNumber(RM_InpMagicNumberRSIReverse);
|
||||
rmData.trade.Sell(g_RM_LotSize, symbol, 0, 0, 0, "RSI Reverse");
|
||||
}
|
||||
rmData.rsiReverseOverbought = false;
|
||||
}
|
||||
else if(rmData.rsiReverseOversold && rmData.lastBarRSIReverse > RM_InpRSIReverseCrossLevel)
|
||||
{
|
||||
if(!HasPosition(symbol, RM_InpMagicNumberRSIReverse))
|
||||
{
|
||||
rmData.trade.SetExpertMagicNumber(RM_InpMagicNumberRSIReverse);
|
||||
rmData.trade.Buy(g_RM_LotSize, symbol, 0, 0, 0, "RSI Reverse");
|
||||
}
|
||||
rmData.rsiReverseOversold = false;
|
||||
}
|
||||
}
|
||||
|
||||
void CheckEMACrossStrategy(string symbol)
|
||||
{
|
||||
if(!IsWithinTradingHours(RM_InpEMACrossStartHour, RM_InpEMACrossEndHour))
|
||||
{
|
||||
if(RM_InpEMACrossCloseOutsideHours)
|
||||
{
|
||||
if(HasPosition(symbol, RM_InpMagicNumberEMACross))
|
||||
ClosePosition(symbol, RM_InpMagicNumberEMACross);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if(RM_InpEnableStrategyLock && HasProfitablePosition(RM_InpMagicNumberEMACross))
|
||||
return;
|
||||
|
||||
if(rmData.lastBarEMAPrev < rmData.lastBarClosePrev && rmData.lastBarEMA > rmData.lastBarClose)
|
||||
{
|
||||
rmData.emaCrossBuySignal = true;
|
||||
rmData.emaCrossSellSignal = false;
|
||||
rmData.emaCrossSignalBar = 0;
|
||||
}
|
||||
else if(rmData.lastBarEMAPrev > rmData.lastBarClosePrev && rmData.lastBarEMA < rmData.lastBarClose)
|
||||
{
|
||||
rmData.emaCrossSellSignal = true;
|
||||
rmData.emaCrossBuySignal = false;
|
||||
rmData.emaCrossSignalBar = 0;
|
||||
}
|
||||
|
||||
if(RM_InpUseEMADistanceEntry)
|
||||
{
|
||||
if(rmData.emaCrossBuySignal)
|
||||
{
|
||||
bool distanceConditionMet = true;
|
||||
double emaHistory[], closeHistory[];
|
||||
ArraySetAsSeries(emaHistory, true);
|
||||
ArraySetAsSeries(closeHistory, true);
|
||||
|
||||
if(CopyBuffer(rmData.emaHandle, 0, 0, RM_InpEMADistancePeriod, emaHistory) > 0 &&
|
||||
CopyClose(symbol, RM_InpTimeframe, 0, RM_InpEMADistancePeriod, closeHistory) > 0)
|
||||
{
|
||||
double point = SymbolInfoDouble(symbol, SYMBOL_POINT);
|
||||
for(int i = 0; i < RM_InpEMADistancePeriod; i++)
|
||||
{
|
||||
double distance = (closeHistory[i] - emaHistory[i]) / point;
|
||||
if(distance < RM_InpEMADistancePips)
|
||||
{
|
||||
distanceConditionMet = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(distanceConditionMet && !HasPosition(symbol, RM_InpMagicNumberEMACross))
|
||||
{
|
||||
rmData.trade.SetExpertMagicNumber(RM_InpMagicNumberEMACross);
|
||||
rmData.trade.Buy(g_RM_LotSize, symbol, 0, 0, 0, "EMA Cross Distance");
|
||||
rmData.emaCrossBuySignal = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if(rmData.emaCrossSellSignal)
|
||||
{
|
||||
bool distanceConditionMet = true;
|
||||
double emaHistory[], closeHistory[];
|
||||
ArraySetAsSeries(emaHistory, true);
|
||||
ArraySetAsSeries(closeHistory, true);
|
||||
|
||||
if(CopyBuffer(rmData.emaHandle, 0, 0, RM_InpEMADistancePeriod, emaHistory) > 0 &&
|
||||
CopyClose(symbol, RM_InpTimeframe, 0, RM_InpEMADistancePeriod, closeHistory) > 0)
|
||||
{
|
||||
double point = SymbolInfoDouble(symbol, SYMBOL_POINT);
|
||||
for(int i = 0; i < RM_InpEMADistancePeriod; i++)
|
||||
{
|
||||
double distance = (emaHistory[i] - closeHistory[i]) / point;
|
||||
if(distance < RM_InpEMADistancePips)
|
||||
{
|
||||
distanceConditionMet = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(distanceConditionMet && !HasPosition(symbol, RM_InpMagicNumberEMACross))
|
||||
{
|
||||
rmData.trade.SetExpertMagicNumber(RM_InpMagicNumberEMACross);
|
||||
rmData.trade.Sell(g_RM_LotSize, symbol, 0, 0, 0, "EMA Cross Distance");
|
||||
rmData.emaCrossSellSignal = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if(rmData.lastBarEMAPrev < rmData.lastBarClosePrev && rmData.lastBarEMA > rmData.lastBarClose)
|
||||
{
|
||||
if(!HasPosition(symbol, RM_InpMagicNumberEMACross))
|
||||
{
|
||||
rmData.trade.SetExpertMagicNumber(RM_InpMagicNumberEMACross);
|
||||
rmData.trade.Buy(g_RM_LotSize, symbol, 0, 0, 0, "EMA Cross");
|
||||
}
|
||||
}
|
||||
else if(rmData.lastBarEMAPrev > rmData.lastBarClosePrev && rmData.lastBarEMA < rmData.lastBarClose)
|
||||
{
|
||||
if(!HasPosition(symbol, RM_InpMagicNumberEMACross))
|
||||
{
|
||||
rmData.trade.SetExpertMagicNumber(RM_InpMagicNumberEMACross);
|
||||
rmData.trade.Sell(g_RM_LotSize, symbol, 0, 0, 0, "EMA Cross");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(rmData.emaCrossBuySignal || rmData.emaCrossSellSignal)
|
||||
{
|
||||
rmData.emaCrossSignalBar++;
|
||||
if(rmData.emaCrossSignalBar > RM_InpEMADistancePeriod * 2)
|
||||
{
|
||||
rmData.emaCrossBuySignal = false;
|
||||
rmData.emaCrossSellSignal = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CheckExitConditions(string symbol)
|
||||
{
|
||||
if(RM_InpEnableRSIFollow)
|
||||
{
|
||||
if(HasPosition(symbol, RM_InpMagicNumberRSIFollow))
|
||||
{
|
||||
if(PositionSelectByMagic(symbol, RM_InpMagicNumberRSIFollow))
|
||||
{
|
||||
ENUM_POSITION_TYPE posType = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);
|
||||
if((posType == POSITION_TYPE_BUY && rmData.lastBarRSI < RM_InpRSIExitLevel) ||
|
||||
(posType == POSITION_TYPE_SELL && rmData.lastBarRSI > RM_InpRSIExitLevel))
|
||||
{
|
||||
ClosePosition(symbol, RM_InpMagicNumberRSIFollow);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(RM_InpEnableRSIReverse)
|
||||
{
|
||||
if(HasPosition(symbol, RM_InpMagicNumberRSIReverse))
|
||||
{
|
||||
if(PositionSelectByMagic(symbol, RM_InpMagicNumberRSIReverse))
|
||||
{
|
||||
ENUM_POSITION_TYPE posType = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);
|
||||
if((posType == POSITION_TYPE_BUY && rmData.lastBarRSIReverse < RM_InpRSIReverseExitLevel) ||
|
||||
(posType == POSITION_TYPE_SELL && rmData.lastBarRSIReverse > RM_InpRSIReverseExitLevel))
|
||||
{
|
||||
ClosePosition(symbol, RM_InpMagicNumberRSIReverse);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(RM_InpEnableEMACross)
|
||||
{
|
||||
if(HasPosition(symbol, RM_InpMagicNumberEMACross))
|
||||
{
|
||||
if(PositionSelectByMagic(symbol, RM_InpMagicNumberEMACross))
|
||||
{
|
||||
ENUM_POSITION_TYPE posType = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);
|
||||
if((posType == POSITION_TYPE_BUY && rmData.lastBarEMA > rmData.lastBarClose) ||
|
||||
(posType == POSITION_TYPE_SELL && rmData.lastBarEMA < rmData.lastBarClose))
|
||||
{
|
||||
ClosePosition(symbol, RM_InpMagicNumberEMACross);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ClosePosition(string symbol, int magic)
|
||||
{
|
||||
if(!PositionExistsByMagic(symbol, magic))
|
||||
return;
|
||||
|
||||
ulong ticket = GetPositionTicketByMagic(symbol, magic);
|
||||
if(ticket == 0)
|
||||
return;
|
||||
|
||||
if(magic == RM_InpMagicNumberRSIReverse)
|
||||
{
|
||||
if(PositionSelectByTicketSymbolAndMagic(ticket, symbol, magic))
|
||||
{
|
||||
datetime time[];
|
||||
if(CopyTime(symbol, RM_InpTimeframe, 0, 1, time) > 0)
|
||||
{
|
||||
rmData.rsiReverseLastCloseTime = time[0];
|
||||
double profit = PositionGetDouble(POSITION_PROFIT);
|
||||
if(!RM_InpRSIReverseCooldownOnLoss || profit < 0)
|
||||
{
|
||||
rmData.rsiReverseInCooldown = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ClosePositionByMagic(rmData.trade, symbol, magic);
|
||||
}
|
||||
|
||||
bool InitRSIMidPointHijack(string symbol)
|
||||
{
|
||||
rmData.symbol = symbol;
|
||||
rmData.rsiOverbought = false;
|
||||
rmData.rsiOversold = false;
|
||||
rmData.rsiReverseOverbought = false;
|
||||
rmData.rsiReverseOversold = false;
|
||||
rmData.emaCrossBuySignal = false;
|
||||
rmData.emaCrossSellSignal = false;
|
||||
rmData.emaCrossSignalBar = 0;
|
||||
rmData.rsiReverseInCooldown = false;
|
||||
rmData.lastBarRSI = 0;
|
||||
rmData.lastBarRSIReverse = 0;
|
||||
rmData.lastBarEMA = 0;
|
||||
rmData.lastBarClose = 0;
|
||||
rmData.lastBarEMAPrev = 0;
|
||||
rmData.lastBarClosePrev = 0;
|
||||
|
||||
// Check if symbol exists
|
||||
if(!SymbolSelect(symbol, true))
|
||||
{
|
||||
Print("RSIMidPointHijack: Symbol '", symbol, "' not available in Market Watch. Please add it to Market Watch or check symbol name.");
|
||||
return false;
|
||||
}
|
||||
|
||||
Sleep(100); // Wait for symbol to be ready
|
||||
|
||||
rmData.rsiHandle = iRSI(symbol, RM_InpTimeframe, RM_InpRSIPeriod, PRICE_CLOSE);
|
||||
rmData.rsiReverseHandle = iRSI(symbol, RM_InpTimeframe, RM_InpRSIReversePeriod, PRICE_CLOSE);
|
||||
rmData.emaHandle = iMA(symbol, RM_InpTimeframe, RM_InpEMAPeriod, 0, MODE_EMA, PRICE_CLOSE);
|
||||
|
||||
if(rmData.rsiHandle == INVALID_HANDLE || rmData.rsiReverseHandle == INVALID_HANDLE || rmData.emaHandle == INVALID_HANDLE)
|
||||
{
|
||||
Print("RSIMidPointHijack: Error creating indicators for '", symbol, "'");
|
||||
return false;
|
||||
}
|
||||
|
||||
rmData.trade.SetExpertMagicNumber(RM_InpMagicNumberRSIFollow);
|
||||
rmData.trade.SetMarginMode();
|
||||
rmData.trade.SetTypeFillingBySymbol(symbol);
|
||||
rmData.trade.SetDeviationInPoints(10);
|
||||
|
||||
datetime time[];
|
||||
if(CopyTime(symbol, RM_InpTimeframe, 0, 1, time) > 0)
|
||||
rmData.lastBarTime = time[0];
|
||||
|
||||
rmData.isInitialized = true;
|
||||
Print("RSIMidPointHijack: Successfully initialized for symbol '", symbol, "'");
|
||||
return true;
|
||||
}
|
||||
|
||||
void DeinitRSIMidPointHijack()
|
||||
{
|
||||
if(rmData.rsiHandle != INVALID_HANDLE) IndicatorRelease(rmData.rsiHandle);
|
||||
if(rmData.rsiReverseHandle != INVALID_HANDLE) IndicatorRelease(rmData.rsiReverseHandle);
|
||||
if(rmData.emaHandle != INVALID_HANDLE) IndicatorRelease(rmData.emaHandle);
|
||||
}
|
||||
|
||||
void ProcessRSIMidPointHijack(string symbol)
|
||||
{
|
||||
// Skip if not initialized (symbol not available)
|
||||
if(!rmData.isInitialized)
|
||||
return;
|
||||
|
||||
rmData.symbol = symbol; // Update symbol in case it changed
|
||||
if(!IsNewBar(rmData.symbol))
|
||||
return;
|
||||
|
||||
double rsi[], rsiReverse[], ema[], close[];
|
||||
ArraySetAsSeries(rsi, true);
|
||||
ArraySetAsSeries(rsiReverse, true);
|
||||
ArraySetAsSeries(ema, true);
|
||||
ArraySetAsSeries(close, true);
|
||||
|
||||
rmData.lastBarEMAPrev = rmData.lastBarEMA;
|
||||
rmData.lastBarClosePrev = rmData.lastBarClose;
|
||||
|
||||
if(CopyBuffer(rmData.rsiHandle, 0, 0, 1, rsi) > 0)
|
||||
rmData.lastBarRSI = rsi[0];
|
||||
|
||||
if(CopyBuffer(rmData.rsiReverseHandle, 0, 0, 1, rsiReverse) > 0)
|
||||
rmData.lastBarRSIReverse = rsiReverse[0];
|
||||
|
||||
if(CopyBuffer(rmData.emaHandle, 0, 0, 1, ema) > 0)
|
||||
rmData.lastBarEMA = ema[0];
|
||||
|
||||
if(CopyClose(rmData.symbol, RM_InpTimeframe, 0, 1, close) > 0)
|
||||
rmData.lastBarClose = close[0];
|
||||
|
||||
if(RM_InpEnableRSIFollow)
|
||||
CheckRSIFollowStrategy(rmData.symbol);
|
||||
if(RM_InpEnableRSIReverse)
|
||||
CheckRSIReverseStrategy(rmData.symbol);
|
||||
if(RM_InpEnableEMACross)
|
||||
CheckEMACrossStrategy(rmData.symbol);
|
||||
|
||||
CheckExitConditions(rmData.symbol);
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
@@ -0,0 +1,493 @@
|
||||
//+------------------------------------------------------------------+
|
||||
//| RSIReversalAsianStrategy.mqh |
|
||||
//+------------------------------------------------------------------+
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| RSI Reversal Asian Strategy Data Structure |
|
||||
//+------------------------------------------------------------------+
|
||||
struct RSIReversalAsianData {
|
||||
string symbol;
|
||||
bool isInitialized;
|
||||
int rsiHandle;
|
||||
CTrade trade;
|
||||
bool isPositionOpen;
|
||||
double positionOpenPrice;
|
||||
datetime positionOpenTime;
|
||||
ENUM_POSITION_TYPE lastPositionType;
|
||||
bool sessionCloseAttempted;
|
||||
|
||||
// RSI crossover variables
|
||||
double rsiCurrent;
|
||||
double rsiPrevious;
|
||||
double rsiPrevious2;
|
||||
bool rsiCrossedOverbought;
|
||||
bool rsiCrossedOversold;
|
||||
bool rsiCrossedExitLevel;
|
||||
|
||||
// Strategy parameters
|
||||
int RSIPeriod;
|
||||
double OverboughtLevel;
|
||||
double OversoldLevel;
|
||||
int TakeProfitPips;
|
||||
int StopLossPips;
|
||||
double MaxLotSize;
|
||||
int MaxSpread;
|
||||
int MaxDuration;
|
||||
bool UseStopLoss;
|
||||
bool UseTakeProfit;
|
||||
bool UseRSIExit;
|
||||
double RSIExitLevel;
|
||||
bool CloseOutsideSession;
|
||||
ENUM_TIMEFRAMES TimeFrame;
|
||||
int MagicNumber;
|
||||
int Slippage;
|
||||
double point;
|
||||
};
|
||||
|
||||
// Session times (UTC)
|
||||
const int AsianSessionStart = 0; // 00:00 UTC
|
||||
const int AsianSessionEnd = 8; // 08:00 UTC
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Check if current time is in Asian session |
|
||||
//+------------------------------------------------------------------+
|
||||
bool IsAsianSession()
|
||||
{
|
||||
datetime currentTime = TimeCurrent();
|
||||
MqlDateTime timeStruct;
|
||||
TimeToStruct(currentTime, timeStruct);
|
||||
|
||||
return (timeStruct.hour >= AsianSessionStart && timeStruct.hour < AsianSessionEnd);
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Check if trading is allowed for symbol |
|
||||
//+------------------------------------------------------------------+
|
||||
bool IsTradingAllowed(RSIReversalAsianData& data)
|
||||
{
|
||||
// Check if market is open
|
||||
long tradeMode = SymbolInfoInteger(data.symbol, SYMBOL_TRADE_MODE);
|
||||
if(tradeMode != SYMBOL_TRADE_MODE_FULL)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if we have enough money
|
||||
if(AccountInfoDouble(ACCOUNT_MARGIN_FREE) <= 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Check RSI crossover conditions |
|
||||
//+------------------------------------------------------------------+
|
||||
void CheckRSICrossover(RSIReversalAsianData& data)
|
||||
{
|
||||
// Reset crossover flags
|
||||
data.rsiCrossedOverbought = false;
|
||||
data.rsiCrossedOversold = false;
|
||||
data.rsiCrossedExitLevel = false;
|
||||
|
||||
// Check for overbought crossover (RSI crosses above overbought level)
|
||||
if(data.rsiPrevious < data.OverboughtLevel && data.rsiCurrent >= data.OverboughtLevel)
|
||||
{
|
||||
data.rsiCrossedOverbought = true;
|
||||
}
|
||||
|
||||
// Check for oversold crossover (RSI crosses below oversold level)
|
||||
if(data.rsiPrevious > data.OversoldLevel && data.rsiCurrent <= data.OversoldLevel)
|
||||
{
|
||||
data.rsiCrossedOversold = true;
|
||||
}
|
||||
|
||||
// Check for exit level crossover
|
||||
if(data.rsiPrevious < data.RSIExitLevel && data.rsiCurrent >= data.RSIExitLevel)
|
||||
{
|
||||
data.rsiCrossedExitLevel = true;
|
||||
}
|
||||
else if(data.rsiPrevious > data.RSIExitLevel && data.rsiCurrent <= data.RSIExitLevel)
|
||||
{
|
||||
data.rsiCrossedExitLevel = true;
|
||||
}
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Close all trades for the symbol |
|
||||
//+------------------------------------------------------------------+
|
||||
bool CloseAllTrades(RSIReversalAsianData& data, string reason = "")
|
||||
{
|
||||
bool allClosed = true;
|
||||
int totalPositions = PositionsTotal();
|
||||
|
||||
if(totalPositions == 0)
|
||||
return true;
|
||||
|
||||
for(int i = totalPositions - 1; i >= 0; i--)
|
||||
{
|
||||
if(PositionGetSymbol(i) == data.symbol)
|
||||
{
|
||||
ulong ticket = PositionGetTicket(i);
|
||||
if(ticket > 0 && PositionSelectByTicket(ticket))
|
||||
{
|
||||
if(PositionGetInteger(POSITION_MAGIC) == (ulong)data.MagicNumber)
|
||||
{
|
||||
// Try to close position with retry logic
|
||||
int retryCount = 0;
|
||||
bool positionClosed = false;
|
||||
|
||||
while(retryCount < 3 && !positionClosed)
|
||||
{
|
||||
if(data.trade.PositionClose(ticket))
|
||||
{
|
||||
data.isPositionOpen = false;
|
||||
positionClosed = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
int error = GetLastError();
|
||||
|
||||
// If error is 4756 (Trade disabled), wait longer before retry
|
||||
if(error == 4756)
|
||||
{
|
||||
Sleep(5000); // Wait 5 seconds before retry
|
||||
retryCount++;
|
||||
}
|
||||
else
|
||||
{
|
||||
// For other errors, break the loop
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(!positionClosed)
|
||||
{
|
||||
allClosed = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return allClosed;
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Initialize RSI Reversal Asian Strategy |
|
||||
//+------------------------------------------------------------------+
|
||||
bool InitRSIReversalAsian(RSIReversalAsianData& data, string symbol,
|
||||
int RSIPeriod, double OverboughtLevel, double OversoldLevel,
|
||||
int TakeProfitPips, int StopLossPips, double MaxLotSize,
|
||||
int MaxSpread, int MaxDuration, bool UseStopLoss,
|
||||
bool UseTakeProfit, bool UseRSIExit, double RSIExitLevel,
|
||||
bool CloseOutsideSession, ENUM_TIMEFRAMES TimeFrame,
|
||||
int MagicNumber, int Slippage)
|
||||
{
|
||||
data.symbol = symbol;
|
||||
data.isInitialized = false;
|
||||
|
||||
// Check if symbol exists
|
||||
if(!SymbolSelect(symbol, true))
|
||||
{
|
||||
Print("RSIReversalAsian: Symbol '", symbol, "' not available in Market Watch. Please add it to Market Watch or check symbol name.");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Wait a bit for symbol to be ready
|
||||
Sleep(100);
|
||||
|
||||
// Get symbol point
|
||||
data.point = SymbolInfoDouble(symbol, SYMBOL_POINT);
|
||||
|
||||
// Store parameters
|
||||
data.RSIPeriod = RSIPeriod;
|
||||
data.OverboughtLevel = OverboughtLevel;
|
||||
data.OversoldLevel = OversoldLevel;
|
||||
data.TakeProfitPips = TakeProfitPips;
|
||||
data.StopLossPips = StopLossPips;
|
||||
data.MaxLotSize = MaxLotSize;
|
||||
data.MaxSpread = MaxSpread;
|
||||
data.MaxDuration = MaxDuration;
|
||||
data.UseStopLoss = UseStopLoss;
|
||||
data.UseTakeProfit = UseTakeProfit;
|
||||
data.UseRSIExit = UseRSIExit;
|
||||
data.RSIExitLevel = RSIExitLevel;
|
||||
data.CloseOutsideSession = CloseOutsideSession;
|
||||
data.TimeFrame = TimeFrame;
|
||||
data.MagicNumber = MagicNumber;
|
||||
data.Slippage = Slippage;
|
||||
|
||||
// Initialize RSI indicator with retry logic (for insufficient history in backtesting)
|
||||
data.rsiHandle = INVALID_HANDLE;
|
||||
int retryCount = 0;
|
||||
int maxRetries = 5;
|
||||
|
||||
while(retryCount < maxRetries && data.rsiHandle == INVALID_HANDLE)
|
||||
{
|
||||
data.rsiHandle = iRSI(symbol, TimeFrame, RSIPeriod, PRICE_CLOSE);
|
||||
|
||||
if(data.rsiHandle == INVALID_HANDLE)
|
||||
{
|
||||
int error = GetLastError();
|
||||
|
||||
// Error 4805 = insufficient history - wait longer and retry
|
||||
if(error == 4805 && retryCount < maxRetries - 1)
|
||||
{
|
||||
Sleep(1000); // Wait 1 second for history to load
|
||||
retryCount++;
|
||||
continue;
|
||||
}
|
||||
|
||||
Print("RSIReversalAsian: Error creating RSI indicator for '", symbol, "' - Error: ", error, " (", error == 4805 ? "Insufficient history data" : "Unknown", ")");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if(data.rsiHandle == INVALID_HANDLE)
|
||||
{
|
||||
Print("RSIReversalAsian: Failed to create RSI indicator for '", symbol, "' after ", maxRetries, " retries");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Wait a bit for the indicator to be ready
|
||||
Sleep(100);
|
||||
|
||||
// Initialize RSI values with retry logic
|
||||
double rsi[];
|
||||
ArraySetAsSeries(rsi, true);
|
||||
|
||||
int retryCount = 0;
|
||||
bool rsiInitialized = false;
|
||||
|
||||
while(retryCount < 10 && !rsiInitialized)
|
||||
{
|
||||
int copied = CopyBuffer(data.rsiHandle, 0, 0, 3, rsi);
|
||||
if(copied >= 3)
|
||||
{
|
||||
data.rsiCurrent = rsi[0];
|
||||
data.rsiPrevious = rsi[1];
|
||||
data.rsiPrevious2 = rsi[2];
|
||||
rsiInitialized = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
retryCount++;
|
||||
Sleep(100);
|
||||
}
|
||||
}
|
||||
|
||||
if(!rsiInitialized)
|
||||
{
|
||||
// Don't fail initialization, just set default values
|
||||
data.rsiCurrent = 50.0;
|
||||
data.rsiPrevious = 50.0;
|
||||
data.rsiPrevious2 = 50.0;
|
||||
}
|
||||
|
||||
// Set trade parameters
|
||||
data.trade.SetExpertMagicNumber(MagicNumber);
|
||||
data.trade.SetDeviationInPoints(Slippage);
|
||||
data.trade.SetTypeFilling(ORDER_FILLING_IOC);
|
||||
|
||||
// Initialize state
|
||||
data.isPositionOpen = false;
|
||||
data.positionOpenPrice = 0;
|
||||
data.positionOpenTime = 0;
|
||||
data.lastPositionType = POSITION_TYPE_BUY;
|
||||
data.sessionCloseAttempted = false;
|
||||
data.rsiCrossedOverbought = false;
|
||||
data.rsiCrossedOversold = false;
|
||||
data.rsiCrossedExitLevel = false;
|
||||
|
||||
data.isInitialized = true;
|
||||
|
||||
Print("RSIReversalAsian: Successfully initialized for symbol '", symbol, "'");
|
||||
return true;
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Deinitialize RSI Reversal Asian Strategy |
|
||||
//+------------------------------------------------------------------+
|
||||
void DeinitRSIReversalAsian(RSIReversalAsianData& data)
|
||||
{
|
||||
if(data.rsiHandle != INVALID_HANDLE)
|
||||
IndicatorRelease(data.rsiHandle);
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Process RSI Reversal Asian Strategy |
|
||||
//+------------------------------------------------------------------+
|
||||
void ProcessRSIReversalAsian(RSIReversalAsianData& data, double lotSize)
|
||||
{
|
||||
if(!data.isInitialized)
|
||||
return;
|
||||
|
||||
// Check if trading is allowed
|
||||
if(!IsTradingAllowed(data))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if we're in Asian session
|
||||
if(!IsAsianSession())
|
||||
{
|
||||
// Close all positions if outside Asian session and CloseOutsideSession is true
|
||||
if(data.CloseOutsideSession && !data.sessionCloseAttempted)
|
||||
{
|
||||
CloseAllTrades(data, "Outside Asian session");
|
||||
data.sessionCloseAttempted = true;
|
||||
}
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Reset the session close attempt flag when we enter Asian session
|
||||
data.sessionCloseAttempted = false;
|
||||
}
|
||||
|
||||
// Get current spread
|
||||
double spread = SymbolInfoDouble(data.symbol, SYMBOL_ASK) - SymbolInfoDouble(data.symbol, SYMBOL_BID);
|
||||
int spreadInPips = (int)(spread / data.point);
|
||||
|
||||
// Check if spread is too high
|
||||
if(spreadInPips > data.MaxSpread)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Get RSI values from bar data
|
||||
double rsi[];
|
||||
ArraySetAsSeries(rsi, true);
|
||||
|
||||
int copied = CopyBuffer(data.rsiHandle, 0, 0, 3, rsi);
|
||||
if(copied < 3)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Update RSI values
|
||||
data.rsiPrevious2 = data.rsiPrevious;
|
||||
data.rsiPrevious = data.rsiCurrent;
|
||||
data.rsiCurrent = rsi[0];
|
||||
|
||||
// Validate RSI values
|
||||
if(data.rsiCurrent == 0 || data.rsiPrevious == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Check for RSI crossovers
|
||||
CheckRSICrossover(data);
|
||||
|
||||
// Get current prices
|
||||
double currentBid = SymbolInfoDouble(data.symbol, SYMBOL_BID);
|
||||
double currentAsk = SymbolInfoDouble(data.symbol, SYMBOL_ASK);
|
||||
|
||||
// Check for open position
|
||||
bool hasOpenPosition = PositionExistsByMagic(data.symbol, (ulong)data.MagicNumber);
|
||||
|
||||
if(hasOpenPosition)
|
||||
{
|
||||
// Get position details
|
||||
ulong ticket = GetPositionTicketByMagic(data.symbol, (ulong)data.MagicNumber);
|
||||
if(ticket > 0 && PositionSelectByTicket(ticket))
|
||||
{
|
||||
ENUM_POSITION_TYPE posType = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);
|
||||
datetime openTime = (datetime)PositionGetInteger(POSITION_TIME);
|
||||
|
||||
// Check for RSI exit if enabled
|
||||
if(data.UseRSIExit && data.rsiCrossedExitLevel)
|
||||
{
|
||||
bool shouldExit = false;
|
||||
|
||||
// For long positions, exit when RSI crosses above exit level
|
||||
if(posType == POSITION_TYPE_BUY && data.rsiCurrent >= data.RSIExitLevel && data.rsiPrevious < data.RSIExitLevel)
|
||||
{
|
||||
shouldExit = true;
|
||||
}
|
||||
// For short positions, exit when RSI crosses below exit level
|
||||
else if(posType == POSITION_TYPE_SELL && data.rsiCurrent <= data.RSIExitLevel && data.rsiPrevious > data.RSIExitLevel)
|
||||
{
|
||||
shouldExit = true;
|
||||
}
|
||||
|
||||
if(shouldExit)
|
||||
{
|
||||
CloseAllTrades(data, "RSI Exit Crossover");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Check for timeout
|
||||
if(TimeCurrent() - openTime > data.MaxDuration * 3600)
|
||||
{
|
||||
CloseAllTrades(data, "Timeout");
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If no position is open, look for entry signals based on RSI crossover
|
||||
if(!hasOpenPosition)
|
||||
{
|
||||
// Place buy order if RSI crosses below oversold level (oversold crossover)
|
||||
if(data.rsiCrossedOversold)
|
||||
{
|
||||
double sl = data.UseStopLoss ? currentBid - data.StopLossPips * data.point : 0;
|
||||
double tp = data.UseTakeProfit ? currentBid + data.TakeProfitPips * data.point : 0;
|
||||
|
||||
if(data.UseStopLoss && sl >= currentBid)
|
||||
return;
|
||||
if(data.UseTakeProfit && tp <= currentBid)
|
||||
return;
|
||||
|
||||
// Set trade parameters
|
||||
data.trade.SetDeviationInPoints(data.Slippage);
|
||||
data.trade.SetTypeFilling(ORDER_FILLING_IOC);
|
||||
data.trade.SetExpertMagicNumber(data.MagicNumber);
|
||||
|
||||
// Use dynamic lot size
|
||||
double tradeLotSize = lotSize > 0 ? lotSize : data.MaxLotSize;
|
||||
|
||||
// Place buy order using CTrade
|
||||
if(data.trade.Buy(tradeLotSize, data.symbol, currentAsk, sl, tp, "RSI Oversold Crossover Buy"))
|
||||
{
|
||||
data.isPositionOpen = true;
|
||||
data.positionOpenPrice = currentAsk;
|
||||
data.positionOpenTime = TimeCurrent();
|
||||
data.lastPositionType = POSITION_TYPE_BUY;
|
||||
}
|
||||
}
|
||||
// Place sell order if RSI crosses above overbought level (overbought crossover)
|
||||
else if(data.rsiCrossedOverbought)
|
||||
{
|
||||
double sl = data.UseStopLoss ? currentAsk + data.StopLossPips * data.point : 0;
|
||||
double tp = data.UseTakeProfit ? currentAsk - data.TakeProfitPips * data.point : 0;
|
||||
|
||||
if(data.UseStopLoss && sl <= currentAsk)
|
||||
return;
|
||||
if(data.UseTakeProfit && tp >= currentAsk)
|
||||
return;
|
||||
|
||||
// Set trade parameters
|
||||
data.trade.SetDeviationInPoints(data.Slippage);
|
||||
data.trade.SetTypeFilling(ORDER_FILLING_IOC);
|
||||
data.trade.SetExpertMagicNumber(data.MagicNumber);
|
||||
|
||||
// Use dynamic lot size
|
||||
double tradeLotSize = lotSize > 0 ? lotSize : data.MaxLotSize;
|
||||
|
||||
// Place sell order using CTrade
|
||||
if(data.trade.Sell(tradeLotSize, data.symbol, currentBid, sl, tp, "RSI Overbought Crossover Sell"))
|
||||
{
|
||||
data.isPositionOpen = true;
|
||||
data.positionOpenPrice = currentBid;
|
||||
data.positionOpenTime = TimeCurrent();
|
||||
data.lastPositionType = POSITION_TYPE_SELL;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,451 @@
|
||||
//+------------------------------------------------------------------+
|
||||
//| RSIScalpingStrategy.mqh |
|
||||
//+------------------------------------------------------------------+
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| RSI Scalping Strategy Data Structure |
|
||||
//+------------------------------------------------------------------+
|
||||
struct RSIScalpingData {
|
||||
string symbol;
|
||||
bool isInitialized;
|
||||
CTrade trade;
|
||||
int rsi_handle;
|
||||
double rsi_buffer[];
|
||||
double rsi_prev;
|
||||
double rsi_current;
|
||||
double rsi_two_bars_ago;
|
||||
bool position_open;
|
||||
ulong position_ticket;
|
||||
ENUM_POSITION_TYPE current_position_type;
|
||||
datetime last_bar_time;
|
||||
bool rsi_against_position;
|
||||
int bars_against_count;
|
||||
};
|
||||
|
||||
string ErrorDescription(int errorCode)
|
||||
{
|
||||
switch(errorCode)
|
||||
{
|
||||
case 4801: return "Symbol not found";
|
||||
case 4802: return "Symbol not selected";
|
||||
case 4803: return "Symbol not visible";
|
||||
case 4804: return "Symbol not available";
|
||||
case 4805: return "Cannot load indicator - insufficient history data";
|
||||
default: return "Unknown error " + IntegerToString(errorCode);
|
||||
}
|
||||
}
|
||||
|
||||
bool InitRSIScalping(RSIScalpingData& data, string symbol, ENUM_TIMEFRAMES TimeFrame, int RSI_Period,
|
||||
ENUM_APPLIED_PRICE RSI_Applied_Price, int MagicNumber, int Slippage)
|
||||
{
|
||||
data.symbol = symbol;
|
||||
data.isInitialized = false;
|
||||
|
||||
// Check if symbol exists
|
||||
if(!SymbolSelect(symbol, true))
|
||||
{
|
||||
Print("RSIScalping: Symbol '", symbol, "' not available in Market Watch. Please add it to Market Watch or check symbol name.");
|
||||
return false; // Return false but don't fail entire EA
|
||||
}
|
||||
|
||||
// Wait a bit for symbol to be ready
|
||||
Sleep(100);
|
||||
|
||||
// Try to create RSI indicator with retry logic (for insufficient history in backtesting)
|
||||
data.rsi_handle = INVALID_HANDLE;
|
||||
int retryCount = 0;
|
||||
int maxRetries = 5;
|
||||
|
||||
while(retryCount < maxRetries && data.rsi_handle == INVALID_HANDLE)
|
||||
{
|
||||
data.rsi_handle = iRSI(symbol, TimeFrame, RSI_Period, RSI_Applied_Price);
|
||||
|
||||
if(data.rsi_handle == INVALID_HANDLE)
|
||||
{
|
||||
int error = GetLastError();
|
||||
|
||||
// Error 4805 = insufficient history - wait longer and retry
|
||||
if(error == 4805 && retryCount < maxRetries - 1)
|
||||
{
|
||||
Sleep(1000); // Wait 1 second for history to load
|
||||
retryCount++;
|
||||
continue;
|
||||
}
|
||||
|
||||
Print("RSIScalping: Error creating RSI indicator for '", symbol, "' - Error: ", error, " (", ErrorDescription(error), ")");
|
||||
return false; // Return false but don't fail entire EA
|
||||
}
|
||||
}
|
||||
|
||||
if(data.rsi_handle == INVALID_HANDLE)
|
||||
{
|
||||
Print("RSIScalping: Failed to create RSI indicator for '", symbol, "' after ", maxRetries, " retries");
|
||||
return false;
|
||||
}
|
||||
|
||||
data.trade.SetExpertMagicNumber(MagicNumber);
|
||||
data.trade.SetDeviationInPoints(Slippage);
|
||||
data.trade.SetTypeFilling(ORDER_FILLING_FOK);
|
||||
|
||||
ArraySetAsSeries(data.rsi_buffer, true);
|
||||
data.position_open = false;
|
||||
data.position_ticket = 0;
|
||||
data.rsi_against_position = false;
|
||||
data.bars_against_count = 0;
|
||||
data.isInitialized = true;
|
||||
|
||||
Print("RSIScalping: Successfully initialized for symbol '", symbol, "'");
|
||||
return true;
|
||||
}
|
||||
|
||||
void DeinitRSIScalping(RSIScalpingData& data)
|
||||
{
|
||||
if(data.rsi_handle != INVALID_HANDLE)
|
||||
IndicatorRelease(data.rsi_handle);
|
||||
}
|
||||
|
||||
bool UpdateRSI(RSIScalpingData& data)
|
||||
{
|
||||
if(CopyBuffer(data.rsi_handle, 0, 0, 3, data.rsi_buffer) < 3)
|
||||
return false;
|
||||
|
||||
data.rsi_current = data.rsi_buffer[0];
|
||||
data.rsi_prev = data.rsi_buffer[1];
|
||||
data.rsi_two_bars_ago = data.rsi_buffer[2];
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void CheckExistingPosition(RSIScalpingData& data, ENUM_TIMEFRAMES TimeFrame, int MagicNumber,
|
||||
double RSI_Oversold, double RSI_Overbought, double RSI_Target_Buy,
|
||||
double RSI_Target_Sell, int BarsToWait)
|
||||
{
|
||||
// Always check if position exists, even if tracking says it doesn't
|
||||
bool positionExists = PositionExistsByMagic(data.symbol, MagicNumber);
|
||||
|
||||
if(!positionExists && data.position_open)
|
||||
{
|
||||
// Position was closed externally, reset tracking
|
||||
data.position_open = false;
|
||||
data.position_ticket = 0;
|
||||
data.rsi_against_position = false;
|
||||
data.bars_against_count = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
if(!positionExists)
|
||||
return;
|
||||
|
||||
// Update tracking if we have a position but tracking was lost
|
||||
if(!data.position_open && positionExists)
|
||||
{
|
||||
ulong ticket = GetPositionTicketByMagic(data.symbol, MagicNumber);
|
||||
if(ticket > 0 && PositionSelectByTicketSymbolAndMagic(ticket, data.symbol, MagicNumber))
|
||||
{
|
||||
data.position_ticket = ticket;
|
||||
data.position_open = true;
|
||||
data.current_position_type = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);
|
||||
}
|
||||
}
|
||||
|
||||
// Verify our tracked position still exists
|
||||
if(data.position_open && data.position_ticket > 0)
|
||||
{
|
||||
if(!PositionSelectByTicketSymbolAndMagic(data.position_ticket, data.symbol, MagicNumber))
|
||||
{
|
||||
// Try to find the position again
|
||||
ulong ticket = GetPositionTicketByMagic(data.symbol, MagicNumber);
|
||||
if(ticket > 0 && PositionSelectByTicketSymbolAndMagic(ticket, data.symbol, MagicNumber))
|
||||
{
|
||||
data.position_ticket = ticket;
|
||||
data.current_position_type = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Position doesn't exist, reset tracking
|
||||
data.position_open = false;
|
||||
data.position_ticket = 0;
|
||||
data.rsi_against_position = false;
|
||||
data.bars_against_count = 0;
|
||||
return;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Update position type in case it changed (shouldn't happen, but be safe)
|
||||
data.current_position_type = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);
|
||||
}
|
||||
}
|
||||
|
||||
if(data.current_position_type == POSITION_TYPE_BUY)
|
||||
{
|
||||
if(data.rsi_current < RSI_Oversold)
|
||||
{
|
||||
if(!data.rsi_against_position)
|
||||
{
|
||||
data.rsi_against_position = true;
|
||||
data.bars_against_count = 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
data.bars_against_count++;
|
||||
}
|
||||
|
||||
if(data.bars_against_count >= BarsToWait)
|
||||
{
|
||||
ClosePosition(data, MagicNumber);
|
||||
return;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if(data.rsi_against_position)
|
||||
{
|
||||
data.rsi_against_position = false;
|
||||
data.bars_against_count = 0;
|
||||
}
|
||||
|
||||
if(data.rsi_current >= RSI_Target_Buy)
|
||||
{
|
||||
ClosePosition(data, MagicNumber);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if(data.current_position_type == POSITION_TYPE_SELL)
|
||||
{
|
||||
if(data.rsi_current > RSI_Overbought)
|
||||
{
|
||||
if(!data.rsi_against_position)
|
||||
{
|
||||
data.rsi_against_position = true;
|
||||
data.bars_against_count = 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
data.bars_against_count++;
|
||||
}
|
||||
|
||||
if(data.bars_against_count >= BarsToWait)
|
||||
{
|
||||
ClosePosition(data, MagicNumber);
|
||||
return;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if(data.rsi_against_position)
|
||||
{
|
||||
data.rsi_against_position = false;
|
||||
data.bars_against_count = 0;
|
||||
}
|
||||
|
||||
if(data.rsi_current <= RSI_Target_Sell)
|
||||
{
|
||||
ClosePosition(data, MagicNumber);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CheckEntrySignals(RSIScalpingData& data, ENUM_TIMEFRAMES TimeFrame, int MagicNumber,
|
||||
double RSI_Oversold, double RSI_Overbought, double LotSize)
|
||||
{
|
||||
if(data.rsi_two_bars_ago <= RSI_Oversold && data.rsi_prev > RSI_Oversold)
|
||||
{
|
||||
OpenBuyPosition(data, MagicNumber, LotSize);
|
||||
}
|
||||
|
||||
if(data.rsi_two_bars_ago >= RSI_Overbought && data.rsi_prev < RSI_Overbought)
|
||||
{
|
||||
OpenSellPosition(data, MagicNumber, LotSize);
|
||||
}
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Normalize Lot Size According to Symbol Properties |
|
||||
//+------------------------------------------------------------------+
|
||||
double NormalizeLotSize(string symbol, double lotSize)
|
||||
{
|
||||
double minLot = SymbolInfoDouble(symbol, SYMBOL_VOLUME_MIN);
|
||||
double maxLot = SymbolInfoDouble(symbol, SYMBOL_VOLUME_MAX);
|
||||
double lotStep = SymbolInfoDouble(symbol, SYMBOL_VOLUME_STEP);
|
||||
|
||||
// Round to lot step
|
||||
if(lotStep > 0)
|
||||
lotSize = MathFloor(lotSize / lotStep) * lotStep;
|
||||
|
||||
// Apply min/max constraints
|
||||
if(lotSize < minLot)
|
||||
lotSize = minLot;
|
||||
if(lotSize > maxLot)
|
||||
lotSize = maxLot;
|
||||
|
||||
return lotSize;
|
||||
}
|
||||
|
||||
void OpenBuyPosition(RSIScalpingData& data, int MagicNumber, double LotSize)
|
||||
{
|
||||
if(PositionExistsByMagic(data.symbol, MagicNumber))
|
||||
return;
|
||||
|
||||
// Normalize lot size according to symbol properties
|
||||
double normalizedLot = NormalizeLotSize(data.symbol, LotSize);
|
||||
|
||||
double ask = SymbolInfoDouble(data.symbol, SYMBOL_ASK);
|
||||
|
||||
if(data.trade.Buy(normalizedLot, data.symbol, ask, 0, 0, "RSI Scalping Buy"))
|
||||
{
|
||||
ulong new_ticket = data.trade.ResultOrder();
|
||||
if(new_ticket > 0)
|
||||
{
|
||||
if(PositionSelectByTicketSymbolAndMagic(new_ticket, data.symbol, MagicNumber))
|
||||
{
|
||||
data.position_ticket = new_ticket;
|
||||
data.position_open = true;
|
||||
data.current_position_type = POSITION_TYPE_BUY;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void OpenSellPosition(RSIScalpingData& data, int MagicNumber, double LotSize)
|
||||
{
|
||||
if(PositionExistsByMagic(data.symbol, MagicNumber))
|
||||
return;
|
||||
|
||||
// Normalize lot size according to symbol properties
|
||||
double normalizedLot = NormalizeLotSize(data.symbol, LotSize);
|
||||
|
||||
double bid = SymbolInfoDouble(data.symbol, SYMBOL_BID);
|
||||
|
||||
if(data.trade.Sell(normalizedLot, data.symbol, bid, 0, 0, "RSI Scalping Sell"))
|
||||
{
|
||||
ulong new_ticket = data.trade.ResultOrder();
|
||||
if(new_ticket > 0)
|
||||
{
|
||||
if(PositionSelectByTicketSymbolAndMagic(new_ticket, data.symbol, MagicNumber))
|
||||
{
|
||||
data.position_ticket = new_ticket;
|
||||
data.position_open = true;
|
||||
data.current_position_type = POSITION_TYPE_SELL;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ClosePosition(RSIScalpingData& data, int MagicNumber)
|
||||
{
|
||||
// First verify position still exists
|
||||
if(!PositionExistsByMagic(data.symbol, MagicNumber))
|
||||
{
|
||||
// Position doesn't exist, reset tracking
|
||||
data.position_open = false;
|
||||
data.position_ticket = 0;
|
||||
data.rsi_against_position = false;
|
||||
data.bars_against_count = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
// Try to close by ticket first (more reliable)
|
||||
bool closed = false;
|
||||
if(data.position_ticket > 0)
|
||||
{
|
||||
if(PositionSelectByTicket(data.position_ticket))
|
||||
{
|
||||
// Verify it's our position
|
||||
if(PositionGetString(POSITION_SYMBOL) == data.symbol &&
|
||||
PositionGetInteger(POSITION_MAGIC) == MagicNumber)
|
||||
{
|
||||
closed = data.trade.PositionClose(data.position_ticket);
|
||||
if(!closed)
|
||||
{
|
||||
Print("RSIScalping: Failed to close position by ticket ", data.position_ticket,
|
||||
" - Error: ", data.trade.ResultRetcode(), " (", data.trade.ResultRetcodeDescription(), ")");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If ticket method failed, try magic number method
|
||||
if(!closed)
|
||||
{
|
||||
closed = ClosePositionByMagic(data.trade, data.symbol, MagicNumber);
|
||||
if(!closed)
|
||||
{
|
||||
Print("RSIScalping: Failed to close position by magic number for '", data.symbol,
|
||||
"' - Error: ", data.trade.ResultRetcode(), " (", data.trade.ResultRetcodeDescription(), ")");
|
||||
}
|
||||
}
|
||||
|
||||
// Verify position is actually closed
|
||||
if(closed)
|
||||
{
|
||||
// Wait a moment and verify
|
||||
Sleep(50);
|
||||
if(!PositionExistsByMagic(data.symbol, MagicNumber))
|
||||
{
|
||||
data.position_open = false;
|
||||
data.position_ticket = 0;
|
||||
data.rsi_against_position = false;
|
||||
data.bars_against_count = 0;
|
||||
Print("RSIScalping: Position successfully closed for '", data.symbol, "'");
|
||||
}
|
||||
else
|
||||
{
|
||||
Print("RSIScalping: Warning - Close returned success but position still exists for '", data.symbol, "'");
|
||||
// Try one more time
|
||||
Sleep(100);
|
||||
if(PositionExistsByMagic(data.symbol, MagicNumber))
|
||||
{
|
||||
ClosePositionByMagic(data.trade, data.symbol, MagicNumber);
|
||||
}
|
||||
// Reset tracking anyway to prevent getting stuck
|
||||
data.position_open = false;
|
||||
data.position_ticket = 0;
|
||||
data.rsi_against_position = false;
|
||||
data.bars_against_count = 0;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Close failed, but reset tracking to prevent getting stuck
|
||||
// The position might have been closed externally
|
||||
data.position_open = false;
|
||||
data.position_ticket = 0;
|
||||
data.rsi_against_position = false;
|
||||
data.bars_against_count = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void ProcessRSIScalping(RSIScalpingData& data, string symbol, ENUM_TIMEFRAMES TimeFrame, int RSI_Period,
|
||||
ENUM_APPLIED_PRICE RSI_Applied_Price, double RSI_Overbought,
|
||||
double RSI_Oversold, double RSI_Target_Buy, double RSI_Target_Sell,
|
||||
int BarsToWait, double LotSize, int MagicNumber)
|
||||
{
|
||||
// Skip if not initialized (symbol not available)
|
||||
if(!data.isInitialized)
|
||||
return;
|
||||
|
||||
data.symbol = symbol; // Update symbol in case it changed
|
||||
if(Bars(data.symbol, TimeFrame) < RSI_Period + 2)
|
||||
return;
|
||||
|
||||
datetime current_bar_time = iTime(data.symbol, TimeFrame, 0);
|
||||
if(current_bar_time == data.last_bar_time)
|
||||
return;
|
||||
|
||||
data.last_bar_time = current_bar_time;
|
||||
|
||||
if(!UpdateRSI(data))
|
||||
return;
|
||||
|
||||
CheckExistingPosition(data, TimeFrame, MagicNumber, RSI_Oversold, RSI_Overbought,
|
||||
RSI_Target_Buy, RSI_Target_Sell, BarsToWait);
|
||||
|
||||
if(!data.position_open && !PositionExistsByMagic(data.symbol, MagicNumber))
|
||||
{
|
||||
CheckEntrySignals(data, TimeFrame, MagicNumber, RSI_Oversold, RSI_Overbought, LotSize);
|
||||
}
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
+531
-895
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,683 @@
|
||||
//+------------------------------------------------------------------+
|
||||
//| UnitedEA.mq5 |
|
||||
//| Copyright 2025, MetaQuotes Ltd. |
|
||||
//| https://www.mql5.com |
|
||||
//+------------------------------------------------------------------+
|
||||
#property copyright "Copyright 2025, MetaQuotes Ltd."
|
||||
#property link "https://www.mql5.com"
|
||||
#property version "1.00"
|
||||
#property strict
|
||||
|
||||
#include <Trade\Trade.mqh>
|
||||
#include <Trade\PositionInfo.mqh>
|
||||
#include <Indicators\Trend.mqh>
|
||||
#include <Indicators\Volumes.mqh>
|
||||
#include "MagicNumberHelpers.mqh"
|
||||
#include "PerformanceEvaluator.mqh"
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Strategy Enable/Disable Switches |
|
||||
//+------------------------------------------------------------------+
|
||||
input group "=== Strategy Enable/Disable ==="
|
||||
input bool EnableDarvasBox = true;
|
||||
input bool EnableEMASlopeDistance = true;
|
||||
input bool EnableRSICrossOverReversal = true;
|
||||
input bool EnableRSIMidPointHijack = true;
|
||||
input bool EnableRSIScalpingAPPL = true;
|
||||
input bool EnableRSIScalpingBTCUSD = true;
|
||||
input bool EnableRSIScalpingMSFT = true;
|
||||
input bool EnableRSIScalpingNVDA = true;
|
||||
input bool EnableRSIScalpingTSLA = true;
|
||||
input bool EnableRSIScalpingXAUUSD = true;
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Strategy 1: DarvasBoxXAUUSD |
|
||||
//+------------------------------------------------------------------+
|
||||
input group "=== DarvasBox Strategy ==="
|
||||
input string DB_Symbol = "XAUUSD";
|
||||
input int DB_BoxPeriod = 165;
|
||||
input double DB_BoxDeviation = 30000; // Increased to allow larger ranges (was 25140)
|
||||
input int DB_VolumeThreshold = 0; // Set to 0 to disable volume threshold check. Volume data from indicator used instead.
|
||||
input double DB_StopLoss = 1665;
|
||||
input double DB_TakeProfit = 3685;
|
||||
input bool DB_EnableLogging = false;
|
||||
input color DB_BoxColor = clrBlue;
|
||||
input int DB_BoxWidth = 1;
|
||||
input ENUM_TIMEFRAMES DB_TrendTimeframe = PERIOD_H2;
|
||||
input int DB_MA_Period = 125;
|
||||
input ENUM_MA_METHOD DB_MA_Method = MODE_EMA;
|
||||
input ENUM_APPLIED_PRICE DB_MA_Price = PRICE_WEIGHTED;
|
||||
input double DB_TrendThreshold = 4.94;
|
||||
input int DB_VolumeMA_Period = 110;
|
||||
input double DB_VolumeThresholdMultiplier = 1.5;
|
||||
input int DB_MagicNumber = 135790;
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Strategy 2: EMASlopeDistanceCocktailXAUUSD |
|
||||
//| PEPPERSTONE US: Gold symbol is typically "XAUUSD" or "GOLD" |
|
||||
//+------------------------------------------------------------------+
|
||||
input group "=== EMA Slope Distance Strategy ==="
|
||||
input string ES_Symbol = "XAUUSD";
|
||||
input int ES_EMA_Periode = 46;
|
||||
input double ES_PreisSchwelle = 600.0;
|
||||
input double ES_SteigungSchwelle = 80.0;
|
||||
input int ES_ÜberwachungTimeout = 800;
|
||||
input double ES_TrailingStop = 250.0;
|
||||
input double ES_LotGröße = 0.03;
|
||||
input int ES_MagicNumber = 12350;
|
||||
input bool ES_UseSpreadAdjustment = true;
|
||||
input ENUM_TIMEFRAMES ES_Timeframe = PERIOD_H1;
|
||||
input bool ES_UseBarData = true;
|
||||
input int ES_MaxTradesPerCrossover = 9;
|
||||
input int ES_ProfitCheckBars = 18;
|
||||
input bool ES_CloseUnprofitableTrades = true;
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Strategy 3: RSICrossOverReversalXAUUSD |
|
||||
//| PEPPERSTONE US: Gold symbol is typically "XAUUSD" or "GOLD" |
|
||||
//+------------------------------------------------------------------+
|
||||
input group "=== RSI CrossOver Reversal Strategy ==="
|
||||
input string RC_Symbol = "XAUUSD";
|
||||
input int RC_MagicNumber = 7;
|
||||
input int RC_rsiPeriod = 19;
|
||||
input int RC_overboughtLevel = 93;
|
||||
input int RC_oversoldLevel = 22;
|
||||
input double RC_entryRSIBuySpread = 0;
|
||||
input double RC_entryRSISellSpread = 0;
|
||||
input double RC_lotSize = 0.01;
|
||||
input int RC_slippage = 3;
|
||||
input int RC_cooldownSeconds = 209;
|
||||
input ENUM_TIMEFRAMES RC_TimeFrame1 = PERIOD_M1;
|
||||
input ENUM_TIMEFRAMES RC_TimeFrame2 = PERIOD_M1;
|
||||
input ENUM_TIMEFRAMES RC_BarTimeFrame = PERIOD_M12;
|
||||
input int RC_emaPeriod = 140;
|
||||
input double RC_emaSlopeThreshold = 105;
|
||||
input double RC_exitBuyRSI = 86;
|
||||
input double RC_exitSellRSI = 10;
|
||||
input double RC_TrailingStop = 295;
|
||||
input double RC_emaDistanceThreshold = 165;
|
||||
input int RC_tradingHourOneBegin = 24;
|
||||
input int RC_tradingHourOneEnd = 22;
|
||||
input int RC_tradingHourTwoBegin = 6;
|
||||
input int RC_tradingHourTwoEnd = 19;
|
||||
input bool RC_Sunday = false;
|
||||
input bool RC_Monday = false;
|
||||
input bool RC_Tuesday = true;
|
||||
input bool RC_Wednesday = true;
|
||||
input bool RC_Thursday = true;
|
||||
input bool RC_Friday = false;
|
||||
input bool RC_Saturday = false;
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Strategy 4: RSIMidPointHijackXAUUSD |
|
||||
//| PEPPERSTONE US: Gold symbol is typically "XAUUSD" or "GOLD" |
|
||||
//+------------------------------------------------------------------+
|
||||
input group "=== RSI MidPoint Hijack Strategy ==="
|
||||
input string RM_Symbol = "XAUUSD";
|
||||
input ENUM_TIMEFRAMES RM_InpTimeframe = PERIOD_H1;
|
||||
input double RM_InpLotSize = 0.02;
|
||||
input int RM_InpMagicNumberRSIFollow = 1001;
|
||||
input int RM_InpMagicNumberRSIReverse = 1002;
|
||||
input int RM_InpMagicNumberEMACross = 1003;
|
||||
input bool RM_InpEnableRSIFollow = true;
|
||||
input bool RM_InpEnableRSIReverse = true;
|
||||
input bool RM_InpEnableEMACross = true;
|
||||
input bool RM_InpEnableStrategyLock = false;
|
||||
input double RM_InpLockProfitThreshold = 0.0;
|
||||
input bool RM_InpCloseOppositeTrades = false;
|
||||
input int RM_InpRSIPeriod = 32;
|
||||
input int RM_InpRSIOverbought = 78;
|
||||
input int RM_InpRSIOversold = 46;
|
||||
input int RM_InpRSIExitLevel = 44;
|
||||
input int RM_InpRSIFollowStartHour = 23;
|
||||
input int RM_InpRSIFollowEndHour = 8;
|
||||
input bool RM_InpRSIFollowCloseOutsideHours = false;
|
||||
input int RM_InpRSIReversePeriod = 59;
|
||||
input int RM_InpRSIReverseOverbought = 51;
|
||||
input int RM_InpRSIReverseOversold = 49;
|
||||
input int RM_InpRSIReverseCrossLevel = 53;
|
||||
input int RM_InpRSIReverseExitLevel = 48;
|
||||
input int RM_InpRSIReverseStartHour = 7;
|
||||
input int RM_InpRSIReverseEndHour = 13;
|
||||
input bool RM_InpRSIReverseCloseOutsideHours = false;
|
||||
input int RM_InpRSIReverseCooldownBars = 15;
|
||||
input bool RM_InpRSIReverseCooldownOnLoss = true;
|
||||
input int RM_InpEMAPeriod = 120;
|
||||
input int RM_InpEMACrossStartHour = 8;
|
||||
input int RM_InpEMACrossEndHour = 14;
|
||||
input bool RM_InpEMACrossCloseOutsideHours = true;
|
||||
input bool RM_InpUseEMADistanceEntry = true;
|
||||
input double RM_InpEMADistancePips = 160.0;
|
||||
input int RM_InpEMADistancePeriod = 26;
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Strategy 5-10: RSI Scalping Strategies |
|
||||
//| Each RSI Scalping strategy trades on its own symbol: |
|
||||
//| - APPL: Apple stock (AAPL) |
|
||||
//| - BTCUSD: Bitcoin/USD |
|
||||
//| - MSFT: Microsoft stock |
|
||||
//| - NVDA: NVIDIA stock |
|
||||
//| - TSLA: Tesla stock |
|
||||
//| - XAUUSD: Gold/USD |
|
||||
//| |
|
||||
//| PEPPERSTONE US SYMBOL FORMATS: |
|
||||
//| - Stocks may use: "AAPL.US", "NASDAQ:AAPL", or just "AAPL" |
|
||||
//| - To find correct symbols: |
|
||||
//| 1. Open Market Watch (Ctrl+M) |
|
||||
//| 2. Right-click > Show All |
|
||||
//| 3. Search for the stock name |
|
||||
//| 4. Use the exact symbol name shown |
|
||||
//+------------------------------------------------------------------+
|
||||
input group "=== RSI Scalping APPL (AAPL) - Pepperstone US ==="
|
||||
input string RS_APPL_Symbol = "AAPL.US"; // Try: "AAPL.US", "NASDAQ:AAPL", or "AAPL"
|
||||
input ENUM_TIMEFRAMES RS_APPL_TimeFrame = PERIOD_M10;
|
||||
input int RS_APPL_RSI_Period = 14;
|
||||
input ENUM_APPLIED_PRICE RS_APPL_RSI_Applied_Price = PRICE_CLOSE;
|
||||
input double RS_APPL_RSI_Overbought = 80;
|
||||
input double RS_APPL_RSI_Oversold = 78;
|
||||
input double RS_APPL_RSI_Target_Buy = 94;
|
||||
input double RS_APPL_RSI_Target_Sell = 44;
|
||||
input int RS_APPL_BarsToWait = 7;
|
||||
input double RS_APPL_LotSize = 25;
|
||||
input int RS_APPL_MagicNumber = 20001;
|
||||
input int RS_APPL_Slippage = 3;
|
||||
|
||||
input group "=== RSI Scalping BTCUSD ==="
|
||||
input string RS_BTCUSD_Symbol = "BTCUSD"; // Pepperstone may use: "BTCUSD", "BTC/USD", or "BTCUSD.c"
|
||||
input ENUM_TIMEFRAMES RS_BTCUSD_TimeFrame = PERIOD_H1;
|
||||
input int RS_BTCUSD_RSI_Period = 14;
|
||||
input ENUM_APPLIED_PRICE RS_BTCUSD_RSI_Applied_Price = PRICE_CLOSE;
|
||||
input double RS_BTCUSD_RSI_Overbought = 90;
|
||||
input double RS_BTCUSD_RSI_Oversold = 73;
|
||||
input double RS_BTCUSD_RSI_Target_Buy = 88;
|
||||
input double RS_BTCUSD_RSI_Target_Sell = 48;
|
||||
input int RS_BTCUSD_BarsToWait = 6;
|
||||
input double RS_BTCUSD_LotSize = 0.1;
|
||||
input int RS_BTCUSD_MagicNumber = 123459123;
|
||||
input int RS_BTCUSD_Slippage = 3;
|
||||
|
||||
input group "=== RSI Scalping MSFT - Pepperstone US ==="
|
||||
input string RS_MSFT_Symbol = "MSFT.US"; // Try: "MSFT.US", "NASDAQ:MSFT", or "MSFT"
|
||||
input ENUM_TIMEFRAMES RS_MSFT_TimeFrame = PERIOD_H3;
|
||||
input int RS_MSFT_RSI_Period = 14;
|
||||
input ENUM_APPLIED_PRICE RS_MSFT_RSI_Applied_Price = PRICE_CLOSE;
|
||||
input double RS_MSFT_RSI_Overbought = 19;
|
||||
input double RS_MSFT_RSI_Oversold = 50;
|
||||
input double RS_MSFT_RSI_Target_Buy = 71;
|
||||
input double RS_MSFT_RSI_Target_Sell = 70;
|
||||
input int RS_MSFT_BarsToWait = 1;
|
||||
input double RS_MSFT_LotSize = 50;
|
||||
input int RS_MSFT_MagicNumber = 20002;
|
||||
input int RS_MSFT_Slippage = 3;
|
||||
|
||||
input group "=== RSI Scalping NVDA - Pepperstone US ==="
|
||||
input string RS_NVDA_Symbol = "NVDA.US"; // Try: "NVDA.US", "NASDAQ:NVDA", or "NVDA"
|
||||
input ENUM_TIMEFRAMES RS_NVDA_TimeFrame = PERIOD_M15;
|
||||
input int RS_NVDA_RSI_Period = 8;
|
||||
input ENUM_APPLIED_PRICE RS_NVDA_RSI_Applied_Price = PRICE_CLOSE;
|
||||
input double RS_NVDA_RSI_Overbought = 36;
|
||||
input double RS_NVDA_RSI_Oversold = 38;
|
||||
input double RS_NVDA_RSI_Target_Buy = 90;
|
||||
input double RS_NVDA_RSI_Target_Sell = 70;
|
||||
input int RS_NVDA_BarsToWait = 5;
|
||||
input double RS_NVDA_LotSize = 50;
|
||||
input int RS_NVDA_MagicNumber = 20003;
|
||||
input int RS_NVDA_Slippage = 3;
|
||||
|
||||
input group "=== RSI Scalping TSLA - Pepperstone US ==="
|
||||
input string RS_TSLA_Symbol = "TSLA.US"; // Try: "TSLA.US", "NASDAQ:TSLA", or "TSLA"
|
||||
input ENUM_TIMEFRAMES RS_TSLA_TimeFrame = PERIOD_H1;
|
||||
input int RS_TSLA_RSI_Period = 14;
|
||||
input ENUM_APPLIED_PRICE RS_TSLA_RSI_Applied_Price = PRICE_CLOSE;
|
||||
input double RS_TSLA_RSI_Overbought = 54;
|
||||
input double RS_TSLA_RSI_Oversold = 73;
|
||||
input double RS_TSLA_RSI_Target_Buy = 87;
|
||||
input double RS_TSLA_RSI_Target_Sell = 33;
|
||||
input int RS_TSLA_BarsToWait = 1;
|
||||
input double RS_TSLA_LotSize = 50;
|
||||
input int RS_TSLA_MagicNumber = 125421321;
|
||||
input int RS_TSLA_Slippage = 3;
|
||||
|
||||
input group "=== RSI Scalping XAUUSD ==="
|
||||
input string RS_XAUUSD_Symbol = "XAUUSD";
|
||||
input ENUM_TIMEFRAMES RS_XAUUSD_TimeFrame = PERIOD_H1;
|
||||
input int RS_XAUUSD_RSI_Period = 14;
|
||||
input ENUM_APPLIED_PRICE RS_XAUUSD_RSI_Applied_Price = PRICE_CLOSE;
|
||||
input double RS_XAUUSD_RSI_Overbought = 71;
|
||||
input double RS_XAUUSD_RSI_Oversold = 57;
|
||||
input double RS_XAUUSD_RSI_Target_Buy = 80;
|
||||
input double RS_XAUUSD_RSI_Target_Sell = 57;
|
||||
input int RS_XAUUSD_BarsToWait = 4;
|
||||
input double RS_XAUUSD_LotSize = 0.1;
|
||||
input int RS_XAUUSD_MagicNumber = 129102315;
|
||||
input int RS_XAUUSD_Slippage = 3;
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Global Variables - DarvasBox |
|
||||
//+------------------------------------------------------------------+
|
||||
struct DarvasBoxData {
|
||||
string symbol;
|
||||
bool isInitialized;
|
||||
double boxHigh;
|
||||
double boxLow;
|
||||
bool boxFormed;
|
||||
datetime lastBoxTime;
|
||||
string boxName;
|
||||
double minStopLevel;
|
||||
double point;
|
||||
CTrade trade;
|
||||
int maHandle;
|
||||
int volumeHandle;
|
||||
datetime lastBarTime;
|
||||
};
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Global Variables - EMA Slope Distance |
|
||||
//+------------------------------------------------------------------+
|
||||
struct EMASlopeData {
|
||||
string symbol;
|
||||
bool isInitialized;
|
||||
int ema_handle;
|
||||
double ema_array[];
|
||||
datetime letzte_überwachung_zeit;
|
||||
bool überwachung_aktiv;
|
||||
bool preis_trigger_aktiv;
|
||||
bool steigung_trigger_aktiv;
|
||||
int ticket;
|
||||
CTrade trade;
|
||||
int trades_in_current_crossover;
|
||||
bool crossover_detected;
|
||||
datetime trade_open_time;
|
||||
datetime last_bar_time;
|
||||
};
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Global Variables - RSI CrossOver Reversal |
|
||||
//+------------------------------------------------------------------+
|
||||
struct RSICrossOverData {
|
||||
string symbol;
|
||||
bool isInitialized;
|
||||
int rsiHandle;
|
||||
int emaHandle;
|
||||
double previousRSIDef;
|
||||
CTrade trade;
|
||||
datetime lastTradeTime;
|
||||
datetime bartime;
|
||||
bool WeekDays[7];
|
||||
datetime lastBarTime;
|
||||
};
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Global Variables - RSI MidPoint Hijack |
|
||||
//+------------------------------------------------------------------+
|
||||
struct RSIMidPointData {
|
||||
string symbol;
|
||||
bool isInitialized;
|
||||
int rsiHandle;
|
||||
int rsiReverseHandle;
|
||||
int emaHandle;
|
||||
bool rsiOverbought;
|
||||
bool rsiOversold;
|
||||
bool rsiReverseOverbought;
|
||||
bool rsiReverseOversold;
|
||||
CTrade trade;
|
||||
CPositionInfo positionInfo;
|
||||
bool emaCrossBuySignal;
|
||||
bool emaCrossSellSignal;
|
||||
int emaCrossSignalBar;
|
||||
datetime lastBarTime;
|
||||
datetime rsiReverseLastCloseTime;
|
||||
bool rsiReverseInCooldown;
|
||||
double lastBarRSI;
|
||||
double lastBarRSIReverse;
|
||||
double lastBarEMA;
|
||||
double lastBarClose;
|
||||
double lastBarEMAPrev;
|
||||
double lastBarClosePrev;
|
||||
};
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Global Variables - RSI Scalping |
|
||||
//+------------------------------------------------------------------+
|
||||
struct RSIScalpingData {
|
||||
string symbol;
|
||||
bool isInitialized;
|
||||
CTrade trade;
|
||||
int rsi_handle;
|
||||
double rsi_buffer[];
|
||||
double rsi_prev;
|
||||
double rsi_current;
|
||||
double rsi_two_bars_ago;
|
||||
bool position_open;
|
||||
ulong position_ticket;
|
||||
ENUM_POSITION_TYPE current_position_type;
|
||||
datetime last_bar_time;
|
||||
bool rsi_against_position;
|
||||
int bars_against_count;
|
||||
};
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Global Strategy Instances |
|
||||
//+------------------------------------------------------------------+
|
||||
DarvasBoxData dbData;
|
||||
EMASlopeData esData;
|
||||
RSICrossOverData rcData;
|
||||
RSIMidPointData rmData;
|
||||
RSIScalpingData rsAPPLData;
|
||||
RSIScalpingData rsBTCUSDData;
|
||||
RSIScalpingData rsMSFTData;
|
||||
RSIScalpingData rsNVDAData;
|
||||
RSIScalpingData rsTSLAData;
|
||||
RSIScalpingData rsXAUUSDData;
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Global Variables for Dynamic Lot Sizes |
|
||||
//+------------------------------------------------------------------+
|
||||
// All strategies start with minimum lot size for safety (will be adjusted by performance evaluator)
|
||||
double g_DB_LotSize = 0.01; // DarvasBox uses fixed lot size
|
||||
double g_ES_LotSize = 0.01; // EMA Slope Distance - start with minimum
|
||||
double g_RC_LotSize = 0.01; // RSI CrossOver Reversal - start with minimum
|
||||
double g_RM_LotSize = 0.01; // RSI MidPoint Hijack - start with minimum
|
||||
double g_RS_APPL_LotSize = 5.0; // Stock - start with stock minimum (5.0)
|
||||
double g_RS_BTCUSD_LotSize = 0.01; // Crypto - start with forex minimum (0.01)
|
||||
double g_RS_MSFT_LotSize = 5.0; // Stock - start with stock minimum (5.0)
|
||||
double g_RS_NVDA_LotSize = 5.0; // Stock - start with stock minimum (5.0)
|
||||
double g_RS_TSLA_LotSize = 5.0; // Stock - start with stock minimum (5.0)
|
||||
double g_RS_XAUUSD_LotSize = 0.01; // Forex - start with forex minimum (0.01)
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Expert initialization function |
|
||||
//+------------------------------------------------------------------+
|
||||
int OnInit()
|
||||
{
|
||||
int initResult = INIT_SUCCEEDED;
|
||||
|
||||
// Initialize Performance Evaluator
|
||||
InitPerformanceTracking();
|
||||
|
||||
// Initialize strategies - log warnings but don't fail entire EA if symbol unavailable
|
||||
if(EnableDarvasBox)
|
||||
{
|
||||
if(!InitDarvasBox(DB_Symbol))
|
||||
Print("Warning: DarvasBox strategy failed to initialize for symbol '", DB_Symbol, "'");
|
||||
else
|
||||
RegisterStrategy("DarvasBox", DB_MagicNumber, 0.01, DB_Symbol); // Fixed lot size
|
||||
}
|
||||
|
||||
if(EnableEMASlopeDistance)
|
||||
{
|
||||
if(!InitEMASlopeDistance(ES_Symbol))
|
||||
Print("Warning: EMASlopeDistance strategy failed to initialize for symbol '", ES_Symbol, "'");
|
||||
else
|
||||
{
|
||||
RegisterStrategy("EMASlopeDistance", ES_MagicNumber, ES_LotGröße, ES_Symbol);
|
||||
// Start with minimum lot size (will be adjusted by performance evaluator)
|
||||
double minLot = GetMinLotSizeForSymbol(ES_Symbol);
|
||||
g_ES_LotSize = minLot;
|
||||
}
|
||||
}
|
||||
|
||||
if(EnableRSICrossOverReversal)
|
||||
{
|
||||
if(!InitRSICrossOverReversal(RC_Symbol))
|
||||
Print("Warning: RSICrossOverReversal strategy failed to initialize for symbol '", RC_Symbol, "'");
|
||||
else
|
||||
{
|
||||
RegisterStrategy("RSICrossOverReversal", RC_MagicNumber, RC_lotSize, RC_Symbol);
|
||||
// Start with minimum lot size (will be adjusted by performance evaluator)
|
||||
double minLot = GetMinLotSizeForSymbol(RC_Symbol);
|
||||
g_RC_LotSize = minLot;
|
||||
}
|
||||
}
|
||||
|
||||
if(EnableRSIMidPointHijack)
|
||||
{
|
||||
if(!InitRSIMidPointHijack(RM_Symbol))
|
||||
Print("Warning: RSIMidPointHijack strategy failed to initialize for symbol '", RM_Symbol, "'");
|
||||
else
|
||||
{
|
||||
RegisterStrategy("RSIMidPointHijack", RM_InpMagicNumberRSIFollow, RM_InpLotSize, RM_Symbol);
|
||||
RegisterStrategy("RSIMidPointHijack_Reverse", RM_InpMagicNumberRSIReverse, RM_InpLotSize, RM_Symbol);
|
||||
RegisterStrategy("RSIMidPointHijack_EMACross", RM_InpMagicNumberEMACross, RM_InpLotSize, RM_Symbol);
|
||||
// Start with minimum lot size (will be adjusted by performance evaluator)
|
||||
double minLot = GetMinLotSizeForSymbol(RM_Symbol);
|
||||
g_RM_LotSize = minLot;
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize RSI Scalping strategies - don't fail entire EA if symbol unavailable
|
||||
if(EnableRSIScalpingAPPL)
|
||||
{
|
||||
InitRSIScalping(rsAPPLData, RS_APPL_Symbol, RS_APPL_TimeFrame, RS_APPL_RSI_Period, RS_APPL_RSI_Applied_Price, RS_APPL_MagicNumber, RS_APPL_Slippage);
|
||||
RegisterStrategy("RSIScalpingAPPL", RS_APPL_MagicNumber, RS_APPL_LotSize, RS_APPL_Symbol);
|
||||
// Start with minimum lot size (will be adjusted by performance evaluator)
|
||||
double minLot = GetMinLotSizeForSymbol(RS_APPL_Symbol);
|
||||
g_RS_APPL_LotSize = minLot;
|
||||
}
|
||||
|
||||
if(EnableRSIScalpingBTCUSD)
|
||||
{
|
||||
InitRSIScalping(rsBTCUSDData, RS_BTCUSD_Symbol, RS_BTCUSD_TimeFrame, RS_BTCUSD_RSI_Period, RS_BTCUSD_RSI_Applied_Price, RS_BTCUSD_MagicNumber, RS_BTCUSD_Slippage);
|
||||
RegisterStrategy("RSIScalpingBTCUSD", RS_BTCUSD_MagicNumber, RS_BTCUSD_LotSize, RS_BTCUSD_Symbol);
|
||||
// Start with minimum lot size (will be adjusted by performance evaluator)
|
||||
double minLot = GetMinLotSizeForSymbol(RS_BTCUSD_Symbol);
|
||||
g_RS_BTCUSD_LotSize = minLot;
|
||||
}
|
||||
|
||||
if(EnableRSIScalpingMSFT)
|
||||
{
|
||||
InitRSIScalping(rsMSFTData, RS_MSFT_Symbol, RS_MSFT_TimeFrame, RS_MSFT_RSI_Period, RS_MSFT_RSI_Applied_Price, RS_MSFT_MagicNumber, RS_MSFT_Slippage);
|
||||
RegisterStrategy("RSIScalpingMSFT", RS_MSFT_MagicNumber, RS_MSFT_LotSize, RS_MSFT_Symbol);
|
||||
// Start with minimum lot size (will be adjusted by performance evaluator)
|
||||
double minLot = GetMinLotSizeForSymbol(RS_MSFT_Symbol);
|
||||
g_RS_MSFT_LotSize = minLot;
|
||||
}
|
||||
|
||||
if(EnableRSIScalpingNVDA)
|
||||
{
|
||||
InitRSIScalping(rsNVDAData, RS_NVDA_Symbol, RS_NVDA_TimeFrame, RS_NVDA_RSI_Period, RS_NVDA_RSI_Applied_Price, RS_NVDA_MagicNumber, RS_NVDA_Slippage);
|
||||
RegisterStrategy("RSIScalpingNVDA", RS_NVDA_MagicNumber, RS_NVDA_LotSize, RS_NVDA_Symbol);
|
||||
// Start with minimum lot size (will be adjusted by performance evaluator)
|
||||
double minLot = GetMinLotSizeForSymbol(RS_NVDA_Symbol);
|
||||
g_RS_NVDA_LotSize = minLot;
|
||||
}
|
||||
|
||||
if(EnableRSIScalpingTSLA)
|
||||
{
|
||||
InitRSIScalping(rsTSLAData, RS_TSLA_Symbol, RS_TSLA_TimeFrame, RS_TSLA_RSI_Period, RS_TSLA_RSI_Applied_Price, RS_TSLA_MagicNumber, RS_TSLA_Slippage);
|
||||
RegisterStrategy("RSIScalpingTSLA", RS_TSLA_MagicNumber, RS_TSLA_LotSize, RS_TSLA_Symbol);
|
||||
// Start with minimum lot size (will be adjusted by performance evaluator)
|
||||
double minLot = GetMinLotSizeForSymbol(RS_TSLA_Symbol);
|
||||
g_RS_TSLA_LotSize = minLot;
|
||||
}
|
||||
|
||||
if(EnableRSIScalpingXAUUSD)
|
||||
{
|
||||
InitRSIScalping(rsXAUUSDData, RS_XAUUSD_Symbol, RS_XAUUSD_TimeFrame, RS_XAUUSD_RSI_Period, RS_XAUUSD_RSI_Applied_Price, RS_XAUUSD_MagicNumber, RS_XAUUSD_Slippage);
|
||||
RegisterStrategy("RSIScalpingXAUUSD", RS_XAUUSD_MagicNumber, RS_XAUUSD_LotSize, RS_XAUUSD_Symbol);
|
||||
// Start with minimum lot size (will be adjusted by performance evaluator)
|
||||
double minLot = GetMinLotSizeForSymbol(RS_XAUUSD_Symbol);
|
||||
g_RS_XAUUSD_LotSize = minLot;
|
||||
}
|
||||
|
||||
// Load adjusted lot sizes from performance evaluator
|
||||
if(PE_EnableAutoAdjustment)
|
||||
{
|
||||
double adjustedLot;
|
||||
adjustedLot = GetStrategyLotSize("EMASlopeDistance", ES_MagicNumber);
|
||||
if(adjustedLot > 0) g_ES_LotSize = adjustedLot;
|
||||
|
||||
adjustedLot = GetStrategyLotSize("RSICrossOverReversal", RC_MagicNumber);
|
||||
if(adjustedLot > 0) g_RC_LotSize = adjustedLot;
|
||||
|
||||
adjustedLot = GetStrategyLotSize("RSIMidPointHijack", RM_InpMagicNumberRSIFollow);
|
||||
if(adjustedLot > 0) g_RM_LotSize = adjustedLot;
|
||||
|
||||
adjustedLot = GetStrategyLotSize("RSIScalpingAPPL", RS_APPL_MagicNumber);
|
||||
if(adjustedLot > 0) g_RS_APPL_LotSize = adjustedLot;
|
||||
|
||||
adjustedLot = GetStrategyLotSize("RSIScalpingBTCUSD", RS_BTCUSD_MagicNumber);
|
||||
if(adjustedLot > 0) g_RS_BTCUSD_LotSize = adjustedLot;
|
||||
|
||||
adjustedLot = GetStrategyLotSize("RSIScalpingMSFT", RS_MSFT_MagicNumber);
|
||||
if(adjustedLot > 0) g_RS_MSFT_LotSize = adjustedLot;
|
||||
|
||||
adjustedLot = GetStrategyLotSize("RSIScalpingNVDA", RS_NVDA_MagicNumber);
|
||||
if(adjustedLot > 0) g_RS_NVDA_LotSize = adjustedLot;
|
||||
|
||||
adjustedLot = GetStrategyLotSize("RSIScalpingTSLA", RS_TSLA_MagicNumber);
|
||||
if(adjustedLot > 0) g_RS_TSLA_LotSize = adjustedLot;
|
||||
|
||||
adjustedLot = GetStrategyLotSize("RSIScalpingXAUUSD", RS_XAUUSD_MagicNumber);
|
||||
if(adjustedLot > 0) g_RS_XAUUSD_LotSize = adjustedLot;
|
||||
}
|
||||
|
||||
Print("United EA initialized. Active strategies: ",
|
||||
(EnableDarvasBox ? "DarvasBox " : ""),
|
||||
(EnableEMASlopeDistance ? "EMASlope " : ""),
|
||||
(EnableRSICrossOverReversal ? "RSICrossOver " : ""),
|
||||
(EnableRSIMidPointHijack ? "RSIMidPoint " : ""),
|
||||
(EnableRSIScalpingAPPL ? "RSIScalpingAPPL " : ""),
|
||||
(EnableRSIScalpingBTCUSD ? "RSIScalpingBTCUSD " : ""),
|
||||
(EnableRSIScalpingMSFT ? "RSIScalpingMSFT " : ""),
|
||||
(EnableRSIScalpingNVDA ? "RSIScalpingNVDA " : ""),
|
||||
(EnableRSIScalpingTSLA ? "RSIScalpingTSLA " : ""),
|
||||
(EnableRSIScalpingXAUUSD ? "RSIScalpingXAUUSD " : ""));
|
||||
|
||||
if(PE_EnableLogging)
|
||||
Print(GetPerformanceSummary());
|
||||
|
||||
return initResult;
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Expert deinitialization function |
|
||||
//+------------------------------------------------------------------+
|
||||
void OnDeinit(const int reason)
|
||||
{
|
||||
if(EnableDarvasBox)
|
||||
DeinitDarvasBox();
|
||||
|
||||
if(EnableEMASlopeDistance)
|
||||
DeinitEMASlopeDistance();
|
||||
|
||||
if(EnableRSICrossOverReversal)
|
||||
DeinitRSICrossOverReversal();
|
||||
|
||||
if(EnableRSIMidPointHijack)
|
||||
DeinitRSIMidPointHijack();
|
||||
|
||||
if(EnableRSIScalpingAPPL)
|
||||
DeinitRSIScalping(rsAPPLData);
|
||||
|
||||
if(EnableRSIScalpingBTCUSD)
|
||||
DeinitRSIScalping(rsBTCUSDData);
|
||||
|
||||
if(EnableRSIScalpingMSFT)
|
||||
DeinitRSIScalping(rsMSFTData);
|
||||
|
||||
if(EnableRSIScalpingNVDA)
|
||||
DeinitRSIScalping(rsNVDAData);
|
||||
|
||||
if(EnableRSIScalpingTSLA)
|
||||
DeinitRSIScalping(rsTSLAData);
|
||||
|
||||
if(EnableRSIScalpingXAUUSD)
|
||||
DeinitRSIScalping(rsXAUUSDData);
|
||||
|
||||
Print("United EA deinitialized. Reason: ", reason);
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Expert tick function |
|
||||
//+------------------------------------------------------------------+
|
||||
void OnTick()
|
||||
{
|
||||
// Process performance evaluation (checks for quarter end and adjusts lot sizes)
|
||||
ProcessPerformanceEvaluation();
|
||||
|
||||
// Update lot sizes from performance evaluator if auto-adjustment is enabled
|
||||
if(PE_EnableAutoAdjustment)
|
||||
{
|
||||
double adjustedLot;
|
||||
adjustedLot = GetStrategyLotSize("EMASlopeDistance", ES_MagicNumber);
|
||||
if(adjustedLot > 0) g_ES_LotSize = adjustedLot;
|
||||
|
||||
adjustedLot = GetStrategyLotSize("RSICrossOverReversal", RC_MagicNumber);
|
||||
if(adjustedLot > 0) g_RC_LotSize = adjustedLot;
|
||||
|
||||
adjustedLot = GetStrategyLotSize("RSIMidPointHijack", RM_InpMagicNumberRSIFollow);
|
||||
if(adjustedLot > 0) g_RM_LotSize = adjustedLot;
|
||||
|
||||
adjustedLot = GetStrategyLotSize("RSIScalpingAPPL", RS_APPL_MagicNumber);
|
||||
if(adjustedLot > 0) g_RS_APPL_LotSize = adjustedLot;
|
||||
|
||||
adjustedLot = GetStrategyLotSize("RSIScalpingBTCUSD", RS_BTCUSD_MagicNumber);
|
||||
if(adjustedLot > 0) g_RS_BTCUSD_LotSize = adjustedLot;
|
||||
|
||||
adjustedLot = GetStrategyLotSize("RSIScalpingMSFT", RS_MSFT_MagicNumber);
|
||||
if(adjustedLot > 0) g_RS_MSFT_LotSize = adjustedLot;
|
||||
|
||||
adjustedLot = GetStrategyLotSize("RSIScalpingNVDA", RS_NVDA_MagicNumber);
|
||||
if(adjustedLot > 0) g_RS_NVDA_LotSize = adjustedLot;
|
||||
|
||||
adjustedLot = GetStrategyLotSize("RSIScalpingTSLA", RS_TSLA_MagicNumber);
|
||||
if(adjustedLot > 0) g_RS_TSLA_LotSize = adjustedLot;
|
||||
|
||||
adjustedLot = GetStrategyLotSize("RSIScalpingXAUUSD", RS_XAUUSD_MagicNumber);
|
||||
if(adjustedLot > 0) g_RS_XAUUSD_LotSize = adjustedLot;
|
||||
}
|
||||
|
||||
if(EnableDarvasBox)
|
||||
ProcessDarvasBox(DB_Symbol);
|
||||
|
||||
if(EnableEMASlopeDistance)
|
||||
ProcessEMASlopeDistance(ES_Symbol);
|
||||
|
||||
if(EnableRSICrossOverReversal)
|
||||
ProcessRSICrossOverReversal(RC_Symbol);
|
||||
|
||||
if(EnableRSIMidPointHijack)
|
||||
ProcessRSIMidPointHijack(RM_Symbol);
|
||||
|
||||
if(EnableRSIScalpingAPPL)
|
||||
ProcessRSIScalping(rsAPPLData, RS_APPL_Symbol, RS_APPL_TimeFrame, RS_APPL_RSI_Period, RS_APPL_RSI_Applied_Price,
|
||||
RS_APPL_RSI_Overbought, RS_APPL_RSI_Oversold, RS_APPL_RSI_Target_Buy, RS_APPL_RSI_Target_Sell,
|
||||
RS_APPL_BarsToWait, g_RS_APPL_LotSize, RS_APPL_MagicNumber);
|
||||
|
||||
if(EnableRSIScalpingBTCUSD)
|
||||
ProcessRSIScalping(rsBTCUSDData, RS_BTCUSD_Symbol, RS_BTCUSD_TimeFrame, RS_BTCUSD_RSI_Period, RS_BTCUSD_RSI_Applied_Price,
|
||||
RS_BTCUSD_RSI_Overbought, RS_BTCUSD_RSI_Oversold, RS_BTCUSD_RSI_Target_Buy, RS_BTCUSD_RSI_Target_Sell,
|
||||
RS_BTCUSD_BarsToWait, g_RS_BTCUSD_LotSize, RS_BTCUSD_MagicNumber);
|
||||
|
||||
if(EnableRSIScalpingMSFT)
|
||||
ProcessRSIScalping(rsMSFTData, RS_MSFT_Symbol, RS_MSFT_TimeFrame, RS_MSFT_RSI_Period, RS_MSFT_RSI_Applied_Price,
|
||||
RS_MSFT_RSI_Overbought, RS_MSFT_RSI_Oversold, RS_MSFT_RSI_Target_Buy, RS_MSFT_RSI_Target_Sell,
|
||||
RS_MSFT_BarsToWait, g_RS_MSFT_LotSize, RS_MSFT_MagicNumber);
|
||||
|
||||
if(EnableRSIScalpingNVDA)
|
||||
ProcessRSIScalping(rsNVDAData, RS_NVDA_Symbol, RS_NVDA_TimeFrame, RS_NVDA_RSI_Period, RS_NVDA_RSI_Applied_Price,
|
||||
RS_NVDA_RSI_Overbought, RS_NVDA_RSI_Oversold, RS_NVDA_RSI_Target_Buy, RS_NVDA_RSI_Target_Sell,
|
||||
RS_NVDA_BarsToWait, g_RS_NVDA_LotSize, RS_NVDA_MagicNumber);
|
||||
|
||||
if(EnableRSIScalpingTSLA)
|
||||
ProcessRSIScalping(rsTSLAData, RS_TSLA_Symbol, RS_TSLA_TimeFrame, RS_TSLA_RSI_Period, RS_TSLA_RSI_Applied_Price,
|
||||
RS_TSLA_RSI_Overbought, RS_TSLA_RSI_Oversold, RS_TSLA_RSI_Target_Buy, RS_TSLA_RSI_Target_Sell,
|
||||
RS_TSLA_BarsToWait, g_RS_TSLA_LotSize, RS_TSLA_MagicNumber);
|
||||
|
||||
if(EnableRSIScalpingXAUUSD)
|
||||
ProcessRSIScalping(rsXAUUSDData, RS_XAUUSD_Symbol, RS_XAUUSD_TimeFrame, RS_XAUUSD_RSI_Period, RS_XAUUSD_RSI_Applied_Price,
|
||||
RS_XAUUSD_RSI_Overbought, RS_XAUUSD_RSI_Oversold, RS_XAUUSD_RSI_Target_Buy, RS_XAUUSD_RSI_Target_Sell,
|
||||
RS_XAUUSD_BarsToWait, g_RS_XAUUSD_LotSize, RS_XAUUSD_MagicNumber);
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Include strategy implementations |
|
||||
//+------------------------------------------------------------------+
|
||||
#include "Strategies/DarvasBoxStrategy.mqh"
|
||||
#include "Strategies/EMASlopeDistanceStrategy.mqh"
|
||||
#include "Strategies/RSICrossOverReversalStrategy.mqh"
|
||||
#include "Strategies/RSIMidPointHijackStrategy.mqh"
|
||||
#include "Strategies/RSIScalpingStrategy.mqh"
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
@@ -0,0 +1,559 @@
|
||||
//+------------------------------------------------------------------+
|
||||
//| EMAPriceSlope.mq5 |
|
||||
//| Copyright 2025, MetaQuotes Ltd. |
|
||||
//| https://www.mql5.com |
|
||||
//+------------------------------------------------------------------+
|
||||
#property copyright "Copyright 2025, MetaQuotes Ltd."
|
||||
#property link "https://www.mql5.com"
|
||||
#property version "1.00"
|
||||
#property description "Expert Advisor using EMA Slope for intelligent trend trading"
|
||||
#property description "Trades based on EMA momentum, slope strength, and price confirmation"
|
||||
|
||||
#include <Trade\Trade.mqh>
|
||||
|
||||
//--- Input parameters
|
||||
input group "Timeframe Settings"
|
||||
input ENUM_TIMEFRAMES InpTimeframe = PERIOD_M15; // Trading Timeframe
|
||||
|
||||
input group "EMA Settings"
|
||||
input int InpEMAPeriod = 20; // EMA Period
|
||||
input int InpSlopeBars = 3; // Slope Calculation Bars (lookback for slope)
|
||||
|
||||
input group "Slope Trading Logic"
|
||||
input double InpMinSlopeStrength = 0.0001; // Minimum Slope Strength (0.01% per bar)
|
||||
input bool InpUseSlopeAcceleration = true; // Require slope acceleration (increasing momentum)
|
||||
input double InpMinAcceleration = 0.00005; // Minimum Acceleration Threshold
|
||||
input bool InpUsePriceConfirmation = true; // Require price above/below EMA for confirmation
|
||||
input double InpPriceDistanceMultiplier = 0.5; // Price distance from EMA (ATR multiplier)
|
||||
|
||||
input group "Entry Filters"
|
||||
input bool InpUseVolatilityFilter = true; // Use ATR volatility filter
|
||||
input double InpMinATR = 0.0002; // Minimum ATR for trading (filter low volatility)
|
||||
input double InpMaxATR = 0.01; // Maximum ATR for trading (filter high volatility)
|
||||
input bool InpUseRSIFilter = false; // Use RSI filter
|
||||
input int InpRSIPeriod = 14; // RSI Period
|
||||
input double InpRSIOverbought = 70; // RSI Overbought (avoid longs)
|
||||
input double InpRSIOversold = 30; // RSI Oversold (avoid shorts)
|
||||
|
||||
input group "Trading Hours (Server Time)"
|
||||
input int InpStartHour = 8; // Trading Start Hour (0-23)
|
||||
input int InpEndHour = 18; // Trading End Hour (0-23)
|
||||
input bool InpUseTimeFilter = true; // Use Trading Hours Filter
|
||||
|
||||
input group "Risk Management"
|
||||
input double InpLotSize = 0.01; // Lot Size
|
||||
input int InpStopLoss = 50; // Stop Loss (pips) - 0 = no SL
|
||||
input int InpTakeProfit = 100; // Take Profit (pips) - 0 = no TP
|
||||
input bool InpUseTrailingStop = true; // Use Trailing Stop
|
||||
input int InpTrailingStop = 30; // Trailing Stop (pips)
|
||||
input int InpTrailingStep = 5; // Trailing Step (pips)
|
||||
input int InpMagicNumber = 890123; // Magic Number
|
||||
input int InpSlippage = 3; // Slippage (points)
|
||||
|
||||
input group "Exit Strategy"
|
||||
input bool InpUseSlopeReversalExit = true; // Exit on slope reversal
|
||||
input double InpSlopeReversalThreshold = -0.00005; // Slope reversal threshold (negative slope for long exit)
|
||||
input bool InpUseEMAExit = false; // Exit when price crosses EMA
|
||||
|
||||
input group "Loss Minimization"
|
||||
input bool InpUseMaxDailyLoss = true; // Use Max Daily Loss
|
||||
input double InpMaxDailyLoss = 50.0; // Max Daily Loss (USD)
|
||||
|
||||
//--- Global variables
|
||||
CTrade trade;
|
||||
int ema_handle;
|
||||
int atr_handle;
|
||||
int rsi_handle;
|
||||
datetime last_bar_time = 0;
|
||||
double daily_profit = 0.0;
|
||||
datetime last_daily_reset = 0;
|
||||
double last_profit = 0.0;
|
||||
double last_slope = 0.0;
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Expert initialization function |
|
||||
//+------------------------------------------------------------------+
|
||||
int OnInit()
|
||||
{
|
||||
// Set trade parameters
|
||||
trade.SetExpertMagicNumber(InpMagicNumber);
|
||||
trade.SetDeviationInPoints(InpSlippage);
|
||||
trade.SetTypeFilling(ORDER_FILLING_FOK);
|
||||
|
||||
// Create indicators
|
||||
ema_handle = iMA(_Symbol, InpTimeframe, InpEMAPeriod, 0, MODE_EMA, PRICE_CLOSE);
|
||||
|
||||
if(InpUseVolatilityFilter)
|
||||
{
|
||||
atr_handle = iATR(_Symbol, InpTimeframe, 14);
|
||||
if(atr_handle == INVALID_HANDLE)
|
||||
{
|
||||
Print("ERROR: Failed to create ATR indicator");
|
||||
return(INIT_FAILED);
|
||||
}
|
||||
}
|
||||
|
||||
if(InpUseRSIFilter)
|
||||
{
|
||||
rsi_handle = iRSI(_Symbol, InpTimeframe, InpRSIPeriod, PRICE_CLOSE);
|
||||
if(rsi_handle == INVALID_HANDLE)
|
||||
{
|
||||
Print("ERROR: Failed to create RSI indicator");
|
||||
return(INIT_FAILED);
|
||||
}
|
||||
}
|
||||
|
||||
if(ema_handle == INVALID_HANDLE)
|
||||
{
|
||||
Print("ERROR: Failed to create EMA indicator");
|
||||
return(INIT_FAILED);
|
||||
}
|
||||
|
||||
// Initialize daily tracking
|
||||
last_daily_reset = TimeCurrent();
|
||||
daily_profit = 0.0;
|
||||
|
||||
Print("EMAPriceSlope EA initialized for ", _Symbol);
|
||||
Print("Timeframe: ", EnumToString(InpTimeframe));
|
||||
Print("EMA Period: ", InpEMAPeriod, " Slope Bars: ", InpSlopeBars);
|
||||
Print("Min Slope Strength: ", InpMinSlopeStrength);
|
||||
Print("Trading Hours: ", InpStartHour, ":00 - ", InpEndHour, ":00");
|
||||
|
||||
return(INIT_SUCCEEDED);
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Expert deinitialization function |
|
||||
//+------------------------------------------------------------------+
|
||||
void OnDeinit(const int reason)
|
||||
{
|
||||
// Release indicators
|
||||
if(ema_handle != INVALID_HANDLE)
|
||||
IndicatorRelease(ema_handle);
|
||||
if(atr_handle != INVALID_HANDLE)
|
||||
IndicatorRelease(atr_handle);
|
||||
if(rsi_handle != INVALID_HANDLE)
|
||||
IndicatorRelease(rsi_handle);
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Expert tick function |
|
||||
//+------------------------------------------------------------------+
|
||||
void OnTick()
|
||||
{
|
||||
// Check if new bar on the specified timeframe
|
||||
datetime current_bar_time = iTime(_Symbol, InpTimeframe, 0);
|
||||
if(current_bar_time == last_bar_time)
|
||||
{
|
||||
// Still same bar - only manage existing positions
|
||||
ManagePosition();
|
||||
return;
|
||||
}
|
||||
last_bar_time = current_bar_time;
|
||||
|
||||
// Reset daily profit at midnight
|
||||
MqlDateTime dt;
|
||||
TimeToStruct(TimeCurrent(), dt);
|
||||
MqlDateTime last_dt;
|
||||
TimeToStruct(last_daily_reset, last_dt);
|
||||
bool is_new_day = (dt.day != last_dt.day || dt.month != last_dt.month || dt.year != last_dt.year);
|
||||
|
||||
if(is_new_day)
|
||||
{
|
||||
daily_profit = 0.0;
|
||||
last_daily_reset = TimeCurrent();
|
||||
Print("Daily reset: New trading day started. Daily profit reset to 0.");
|
||||
}
|
||||
|
||||
// Check daily loss limit
|
||||
if(InpUseMaxDailyLoss && daily_profit <= -InpMaxDailyLoss)
|
||||
{
|
||||
Print("Daily loss limit reached: ", daily_profit, " USD. Trading stopped for today.");
|
||||
return;
|
||||
}
|
||||
|
||||
// Check trading hours
|
||||
if(InpUseTimeFilter && !IsWithinTradingHours())
|
||||
{
|
||||
return; // Outside trading hours
|
||||
}
|
||||
|
||||
// Get EMA values for slope calculation
|
||||
double ema[];
|
||||
ArraySetAsSeries(ema, true);
|
||||
|
||||
// Need enough bars for slope calculation
|
||||
int bars_needed = InpSlopeBars + 5;
|
||||
if(CopyBuffer(ema_handle, 0, 0, bars_needed, ema) < bars_needed)
|
||||
{
|
||||
Print("ERROR: Failed to copy EMA buffer");
|
||||
return;
|
||||
}
|
||||
|
||||
// Calculate EMA slope (rate of change)
|
||||
double current_ema = ema[0];
|
||||
double previous_ema = ema[InpSlopeBars];
|
||||
double slope = (current_ema - previous_ema) / previous_ema; // Percentage change
|
||||
|
||||
// Calculate slope acceleration (change in slope)
|
||||
double previous_slope = last_slope;
|
||||
double acceleration = 0.0;
|
||||
if(previous_slope != 0.0)
|
||||
{
|
||||
acceleration = slope - previous_slope;
|
||||
}
|
||||
last_slope = slope;
|
||||
|
||||
// Get current price
|
||||
double current_price = iClose(_Symbol, InpTimeframe, 0);
|
||||
double price_distance_from_ema = MathAbs(current_price - current_ema) / current_ema;
|
||||
|
||||
// Get ATR for volatility filter
|
||||
double atr_value = 0.0;
|
||||
if(InpUseVolatilityFilter)
|
||||
{
|
||||
double atr_array[];
|
||||
ArraySetAsSeries(atr_array, true);
|
||||
if(CopyBuffer(atr_handle, 0, 0, 1, atr_array) > 0)
|
||||
{
|
||||
atr_value = atr_array[0];
|
||||
}
|
||||
}
|
||||
|
||||
// Get RSI for filter
|
||||
double rsi_value = 50.0;
|
||||
if(InpUseRSIFilter)
|
||||
{
|
||||
double rsi_array[];
|
||||
ArraySetAsSeries(rsi_array, true);
|
||||
if(CopyBuffer(rsi_handle, 0, 0, 1, rsi_array) > 0)
|
||||
{
|
||||
rsi_value = rsi_array[0];
|
||||
}
|
||||
}
|
||||
|
||||
// Check existing position
|
||||
if(PositionSelect(_Symbol))
|
||||
{
|
||||
ManagePosition();
|
||||
|
||||
// Check exit conditions
|
||||
long position_type = PositionGetInteger(POSITION_TYPE);
|
||||
|
||||
// Exit on slope reversal
|
||||
if(InpUseSlopeReversalExit)
|
||||
{
|
||||
if(position_type == POSITION_TYPE_BUY && slope < InpSlopeReversalThreshold)
|
||||
{
|
||||
// Long position: exit on negative slope reversal
|
||||
if(trade.PositionClose(_Symbol))
|
||||
{
|
||||
Print("Position closed: Slope reversal (slope=", slope, ")");
|
||||
}
|
||||
return;
|
||||
}
|
||||
else if(position_type == POSITION_TYPE_SELL && slope > -InpSlopeReversalThreshold)
|
||||
{
|
||||
// Short position: exit on positive slope reversal
|
||||
if(trade.PositionClose(_Symbol))
|
||||
{
|
||||
Print("Position closed: Slope reversal (slope=", slope, ")");
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Exit when price crosses EMA (if enabled)
|
||||
if(InpUseEMAExit)
|
||||
{
|
||||
double prev_price = iClose(_Symbol, InpTimeframe, 1);
|
||||
if(position_type == POSITION_TYPE_BUY && current_price < current_ema && prev_price >= ema[1])
|
||||
{
|
||||
if(trade.PositionClose(_Symbol))
|
||||
{
|
||||
Print("Position closed: Price crossed below EMA");
|
||||
}
|
||||
return;
|
||||
}
|
||||
else if(position_type == POSITION_TYPE_SELL && current_price > current_ema && prev_price <= ema[1])
|
||||
{
|
||||
if(trade.PositionClose(_Symbol))
|
||||
{
|
||||
Print("Position closed: Price crossed above EMA");
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// No position - check for entry signals
|
||||
|
||||
// Volatility filter
|
||||
if(InpUseVolatilityFilter && atr_value > 0)
|
||||
{
|
||||
if(atr_value < InpMinATR || atr_value > InpMaxATR)
|
||||
{
|
||||
return; // Volatility out of range
|
||||
}
|
||||
}
|
||||
|
||||
// RSI filter
|
||||
if(InpUseRSIFilter)
|
||||
{
|
||||
if(rsi_value > InpRSIOverbought || rsi_value < InpRSIOversold)
|
||||
{
|
||||
return; // RSI in extreme zone
|
||||
}
|
||||
}
|
||||
|
||||
// BUY Signal: Positive slope with strength
|
||||
bool buy_signal = false;
|
||||
if(slope > InpMinSlopeStrength)
|
||||
{
|
||||
// Check acceleration (if enabled)
|
||||
if(InpUseSlopeAcceleration)
|
||||
{
|
||||
if(acceleration > InpMinAcceleration)
|
||||
{
|
||||
buy_signal = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
buy_signal = true;
|
||||
}
|
||||
|
||||
// Price confirmation (if enabled)
|
||||
if(buy_signal && InpUsePriceConfirmation)
|
||||
{
|
||||
double min_distance = atr_value * InpPriceDistanceMultiplier / current_price;
|
||||
if(price_distance_from_ema < min_distance || current_price < current_ema)
|
||||
{
|
||||
buy_signal = false; // Price too close to EMA or below EMA
|
||||
}
|
||||
}
|
||||
|
||||
// RSI filter for buy
|
||||
if(buy_signal && InpUseRSIFilter && rsi_value > InpRSIOverbought)
|
||||
{
|
||||
buy_signal = false;
|
||||
}
|
||||
}
|
||||
|
||||
// SELL Signal: Negative slope with strength
|
||||
bool sell_signal = false;
|
||||
if(slope < -InpMinSlopeStrength)
|
||||
{
|
||||
// Check acceleration (if enabled)
|
||||
if(InpUseSlopeAcceleration)
|
||||
{
|
||||
if(acceleration < -InpMinAcceleration)
|
||||
{
|
||||
sell_signal = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
sell_signal = true;
|
||||
}
|
||||
|
||||
// Price confirmation (if enabled)
|
||||
if(sell_signal && InpUsePriceConfirmation)
|
||||
{
|
||||
double min_distance = atr_value * InpPriceDistanceMultiplier / current_price;
|
||||
if(price_distance_from_ema < min_distance || current_price > current_ema)
|
||||
{
|
||||
sell_signal = false; // Price too close to EMA or above EMA
|
||||
}
|
||||
}
|
||||
|
||||
// RSI filter for sell
|
||||
if(sell_signal && InpUseRSIFilter && rsi_value < InpRSIOversold)
|
||||
{
|
||||
sell_signal = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Execute trades
|
||||
if(buy_signal)
|
||||
{
|
||||
Print("BUY Signal: Slope=", slope, " Acceleration=", acceleration, " Price=", current_price);
|
||||
OpenBuyPosition();
|
||||
}
|
||||
else if(sell_signal)
|
||||
{
|
||||
Print("SELL Signal: Slope=", slope, " Acceleration=", acceleration, " Price=", current_price);
|
||||
OpenSellPosition();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Check if current time is within trading hours |
|
||||
//+------------------------------------------------------------------+
|
||||
bool IsWithinTradingHours()
|
||||
{
|
||||
MqlDateTime dt;
|
||||
TimeToStruct(TimeCurrent(), dt);
|
||||
int current_hour = dt.hour;
|
||||
|
||||
// Handle case where end hour is before start hour (overnight)
|
||||
if(InpEndHour < InpStartHour)
|
||||
{
|
||||
return (current_hour >= InpStartHour || current_hour < InpEndHour);
|
||||
}
|
||||
else
|
||||
{
|
||||
return (current_hour >= InpStartHour && current_hour < InpEndHour);
|
||||
}
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Open buy position |
|
||||
//+------------------------------------------------------------------+
|
||||
void OpenBuyPosition()
|
||||
{
|
||||
double price = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
|
||||
double sl = 0.0;
|
||||
double tp = 0.0;
|
||||
|
||||
if(InpStopLoss > 0)
|
||||
{
|
||||
sl = price - InpStopLoss * _Point * 10;
|
||||
}
|
||||
if(InpTakeProfit > 0)
|
||||
{
|
||||
tp = price + InpTakeProfit * _Point * 10;
|
||||
}
|
||||
|
||||
// Validate stops
|
||||
int stop_level = (int)SymbolInfoInteger(_Symbol, SYMBOL_TRADE_STOPS_LEVEL);
|
||||
double point = SymbolInfoDouble(_Symbol, SYMBOL_POINT);
|
||||
double min_stop = stop_level * point;
|
||||
|
||||
if(sl > 0 && (price - sl) < min_stop)
|
||||
sl = price - min_stop;
|
||||
if(tp > 0 && (tp - price) < min_stop)
|
||||
tp = price + min_stop;
|
||||
|
||||
if(trade.Buy(InpLotSize, _Symbol, price, sl, tp, "EMA Slope Buy"))
|
||||
{
|
||||
Print("Buy order opened at ", price, " SL: ", sl, " TP: ", tp);
|
||||
}
|
||||
else
|
||||
{
|
||||
Print("Failed to open buy order: ", trade.ResultRetcodeDescription());
|
||||
}
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Open sell position |
|
||||
//+------------------------------------------------------------------+
|
||||
void OpenSellPosition()
|
||||
{
|
||||
double price = SymbolInfoDouble(_Symbol, SYMBOL_BID);
|
||||
double sl = 0.0;
|
||||
double tp = 0.0;
|
||||
|
||||
if(InpStopLoss > 0)
|
||||
{
|
||||
sl = price + InpStopLoss * _Point * 10;
|
||||
}
|
||||
if(InpTakeProfit > 0)
|
||||
{
|
||||
tp = price - InpTakeProfit * _Point * 10;
|
||||
}
|
||||
|
||||
// Validate stops
|
||||
int stop_level = (int)SymbolInfoInteger(_Symbol, SYMBOL_TRADE_STOPS_LEVEL);
|
||||
double point = SymbolInfoDouble(_Symbol, SYMBOL_POINT);
|
||||
double min_stop = stop_level * point;
|
||||
|
||||
if(sl > 0 && (sl - price) < min_stop)
|
||||
sl = price + min_stop;
|
||||
if(tp > 0 && (price - tp) < min_stop)
|
||||
tp = price - min_stop;
|
||||
|
||||
if(trade.Sell(InpLotSize, _Symbol, price, sl, tp, "EMA Slope Sell"))
|
||||
{
|
||||
Print("Sell order opened at ", price, " SL: ", sl, " TP: ", tp);
|
||||
}
|
||||
else
|
||||
{
|
||||
Print("Failed to open sell order: ", trade.ResultRetcodeDescription());
|
||||
}
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Manage existing position |
|
||||
//+------------------------------------------------------------------+
|
||||
void ManagePosition()
|
||||
{
|
||||
if(!PositionSelect(_Symbol))
|
||||
return;
|
||||
|
||||
// Update daily profit
|
||||
double current_profit = PositionGetDouble(POSITION_PROFIT);
|
||||
if(current_profit != last_profit)
|
||||
{
|
||||
daily_profit += (current_profit - last_profit);
|
||||
last_profit = current_profit;
|
||||
}
|
||||
|
||||
// Apply trailing stop
|
||||
if(InpUseTrailingStop && InpTrailingStop > 0)
|
||||
{
|
||||
ApplyTrailingStop();
|
||||
}
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Apply trailing stop |
|
||||
//+------------------------------------------------------------------+
|
||||
void ApplyTrailingStop()
|
||||
{
|
||||
if(!PositionSelect(_Symbol))
|
||||
return;
|
||||
|
||||
double position_sl = PositionGetDouble(POSITION_SL);
|
||||
double position_tp = PositionGetDouble(POSITION_TP);
|
||||
long position_type = PositionGetInteger(POSITION_TYPE);
|
||||
double current_price = (position_type == POSITION_TYPE_BUY) ?
|
||||
SymbolInfoDouble(_Symbol, SYMBOL_BID) :
|
||||
SymbolInfoDouble(_Symbol, SYMBOL_ASK);
|
||||
|
||||
double trailing_distance = InpTrailingStop * _Point * 10;
|
||||
double new_sl = 0;
|
||||
|
||||
if(position_type == POSITION_TYPE_BUY)
|
||||
{
|
||||
new_sl = current_price - trailing_distance;
|
||||
if(new_sl > position_sl && new_sl < current_price)
|
||||
{
|
||||
// Check trailing step
|
||||
if(position_sl == 0 || (new_sl - position_sl) >= InpTrailingStep * _Point * 10)
|
||||
{
|
||||
if(trade.PositionModify(_Symbol, new_sl, position_tp))
|
||||
{
|
||||
Print("Trailing stop updated: New SL=", new_sl);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else if(position_type == POSITION_TYPE_SELL)
|
||||
{
|
||||
new_sl = current_price + trailing_distance;
|
||||
if((position_sl == 0 || new_sl < position_sl) && new_sl > current_price)
|
||||
{
|
||||
// Check trailing step
|
||||
if(position_sl == 0 || (position_sl - new_sl) >= InpTrailingStep * _Point * 10)
|
||||
{
|
||||
if(trade.PositionModify(_Symbol, new_sl, position_tp))
|
||||
{
|
||||
Print("Trailing stop updated: New SL=", new_sl);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,342 @@
|
||||
//+------------------------------------------------------------------+
|
||||
//| TimeSelectiveEMA.mq5 |
|
||||
//| Copyright 2025, MetaQuotes Ltd. |
|
||||
//| https://www.mql5.com |
|
||||
//+------------------------------------------------------------------+
|
||||
#property copyright "Copyright 2025, MetaQuotes Ltd."
|
||||
#property link "https://www.mql5.com"
|
||||
#property version "1.00"
|
||||
#property description "EMA Crossover EA for EURUSD with Trading Hours Filter"
|
||||
#property description "Minimizes loss by trading only during optimal hours"
|
||||
|
||||
#include <Trade\Trade.mqh>
|
||||
|
||||
//--- Input parameters
|
||||
input group "Timeframe Settings"
|
||||
input ENUM_TIMEFRAMES InpTimeframe = PERIOD_M15; // Trading Timeframe
|
||||
|
||||
input group "EMA Settings"
|
||||
input int InpFastEMA = 12; // Fast EMA Period
|
||||
input int InpSlowEMA = 26; // Slow EMA Period
|
||||
|
||||
input group "Trading Hours (Server Time)"
|
||||
input int InpStartHour = 8; // Trading Start Hour (0-23)
|
||||
input int InpEndHour = 18; // Trading End Hour (0-23)
|
||||
input bool InpUseTimeFilter = true; // Use Trading Hours Filter
|
||||
|
||||
input group "Risk Management"
|
||||
input double InpLotSize = 0.01; // Lot Size
|
||||
input int InpMagicNumber = 789012; // Magic Number
|
||||
input int InpSlippage = 3; // Slippage (points)
|
||||
input bool InpUseRecrossExit = true; // Use EMA Recross as Exit (No SL/TP)
|
||||
|
||||
input group "Loss Minimization"
|
||||
input bool InpUseMaxDailyLoss = true; // Use Max Daily Loss
|
||||
input double InpMaxDailyLoss = 50.0; // Max Daily Loss (USD)
|
||||
|
||||
//--- Global variables
|
||||
CTrade trade;
|
||||
int fast_ema_handle;
|
||||
int slow_ema_handle;
|
||||
datetime last_bar_time = 0;
|
||||
double daily_profit = 0.0;
|
||||
datetime last_daily_reset = 0;
|
||||
double last_profit = 0.0;
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Expert initialization function |
|
||||
//+------------------------------------------------------------------+
|
||||
int OnInit()
|
||||
{
|
||||
// Check symbol
|
||||
if(_Symbol != "EURUSD" && _Symbol != "EURUSD#")
|
||||
{
|
||||
Alert("This EA is designed for EURUSD only. Current symbol: ", _Symbol);
|
||||
return(INIT_FAILED);
|
||||
}
|
||||
|
||||
// Set trade parameters
|
||||
trade.SetExpertMagicNumber(InpMagicNumber);
|
||||
trade.SetDeviationInPoints(InpSlippage);
|
||||
trade.SetTypeFilling(ORDER_FILLING_FOK);
|
||||
|
||||
// Create EMA indicators on specified timeframe
|
||||
fast_ema_handle = iMA(_Symbol, InpTimeframe, InpFastEMA, 0, MODE_EMA, PRICE_CLOSE);
|
||||
slow_ema_handle = iMA(_Symbol, InpTimeframe, InpSlowEMA, 0, MODE_EMA, PRICE_CLOSE);
|
||||
|
||||
if(fast_ema_handle == INVALID_HANDLE || slow_ema_handle == INVALID_HANDLE)
|
||||
{
|
||||
Print("ERROR: Failed to create EMA indicators");
|
||||
return(INIT_FAILED);
|
||||
}
|
||||
|
||||
// Initialize daily tracking
|
||||
last_daily_reset = TimeCurrent();
|
||||
daily_profit = 0.0;
|
||||
|
||||
Print("TimeSelectiveEMA EA initialized for ", _Symbol);
|
||||
Print("Timeframe: ", EnumToString(InpTimeframe));
|
||||
Print("Trading Hours: ", InpStartHour, ":00 - ", InpEndHour, ":00 (Server Time)");
|
||||
Print("EMA Crossover: Fast=", InpFastEMA, " Slow=", InpSlowEMA);
|
||||
|
||||
return(INIT_SUCCEEDED);
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Expert deinitialization function |
|
||||
//+------------------------------------------------------------------+
|
||||
void OnDeinit(const int reason)
|
||||
{
|
||||
// Release indicators
|
||||
if(fast_ema_handle != INVALID_HANDLE)
|
||||
IndicatorRelease(fast_ema_handle);
|
||||
if(slow_ema_handle != INVALID_HANDLE)
|
||||
IndicatorRelease(slow_ema_handle);
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Expert tick function |
|
||||
//+------------------------------------------------------------------+
|
||||
void OnTick()
|
||||
{
|
||||
// Check if new bar on the specified timeframe
|
||||
datetime current_bar_time = iTime(_Symbol, InpTimeframe, 0);
|
||||
if(current_bar_time == last_bar_time)
|
||||
{
|
||||
// Still same bar - only manage existing positions
|
||||
ManagePosition();
|
||||
return;
|
||||
}
|
||||
last_bar_time = current_bar_time;
|
||||
|
||||
// Reset daily profit and consecutive losses at midnight
|
||||
MqlDateTime dt;
|
||||
TimeToStruct(TimeCurrent(), dt);
|
||||
MqlDateTime last_dt;
|
||||
TimeToStruct(last_daily_reset, last_dt);
|
||||
|
||||
// Check if new day (day, month, or year changed)
|
||||
bool is_new_day = (dt.day != last_dt.day || dt.month != last_dt.month || dt.year != last_dt.year);
|
||||
|
||||
if(is_new_day)
|
||||
{
|
||||
// New day - reset daily profit
|
||||
daily_profit = 0.0;
|
||||
last_daily_reset = TimeCurrent();
|
||||
Print("Daily reset: New trading day started. Daily profit reset to 0.");
|
||||
}
|
||||
|
||||
// Check daily loss limit
|
||||
if(InpUseMaxDailyLoss && daily_profit <= -InpMaxDailyLoss)
|
||||
{
|
||||
Print("Daily loss limit reached: ", daily_profit, " USD. Trading stopped for today.");
|
||||
return;
|
||||
}
|
||||
|
||||
// Check trading hours
|
||||
if(InpUseTimeFilter && !IsWithinTradingHours())
|
||||
{
|
||||
return; // Outside trading hours
|
||||
}
|
||||
|
||||
// Get EMA values
|
||||
double fast_ema[], slow_ema[];
|
||||
ArraySetAsSeries(fast_ema, true);
|
||||
ArraySetAsSeries(slow_ema, true);
|
||||
|
||||
if(CopyBuffer(fast_ema_handle, 0, 0, 3, fast_ema) < 3 ||
|
||||
CopyBuffer(slow_ema_handle, 0, 0, 3, slow_ema) < 3)
|
||||
{
|
||||
Print("ERROR: Failed to copy EMA buffers");
|
||||
return;
|
||||
}
|
||||
|
||||
// Check for crossover signals
|
||||
bool bullish_cross = false;
|
||||
bool bearish_cross = false;
|
||||
|
||||
// Bullish: Fast EMA crosses above Slow EMA
|
||||
if(fast_ema[1] > slow_ema[1] && fast_ema[2] <= slow_ema[2])
|
||||
{
|
||||
bullish_cross = true;
|
||||
}
|
||||
|
||||
// Bearish: Fast EMA crosses below Slow EMA
|
||||
if(fast_ema[1] < slow_ema[1] && fast_ema[2] >= slow_ema[2])
|
||||
{
|
||||
bearish_cross = true;
|
||||
}
|
||||
|
||||
// Check existing position
|
||||
if(PositionSelect(_Symbol))
|
||||
{
|
||||
// Check for recross (opposite signal) - this is the exit signal
|
||||
long position_type = PositionGetInteger(POSITION_TYPE);
|
||||
if(InpUseRecrossExit)
|
||||
{
|
||||
if(position_type == POSITION_TYPE_BUY && bearish_cross)
|
||||
{
|
||||
// Close long position on bearish recross (Fast EMA crosses below Slow EMA)
|
||||
if(trade.PositionClose(_Symbol))
|
||||
{
|
||||
Print("Position closed due to bearish recross (Fast EMA crossed below Slow EMA)");
|
||||
}
|
||||
}
|
||||
else if(position_type == POSITION_TYPE_SELL && bullish_cross)
|
||||
{
|
||||
// Close short position on bullish recross (Fast EMA crosses above Slow EMA)
|
||||
if(trade.PositionClose(_Symbol))
|
||||
{
|
||||
Print("Position closed due to bullish recross (Fast EMA crossed above Slow EMA)");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Manage existing position (trailing stop, break-even if enabled)
|
||||
ManagePosition();
|
||||
}
|
||||
else
|
||||
{
|
||||
// No position - check for new entry
|
||||
if(bullish_cross)
|
||||
{
|
||||
OpenBuyPosition();
|
||||
}
|
||||
else if(bearish_cross)
|
||||
{
|
||||
OpenSellPosition();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Check if current time is within trading hours |
|
||||
//+------------------------------------------------------------------+
|
||||
bool IsWithinTradingHours()
|
||||
{
|
||||
MqlDateTime dt;
|
||||
TimeToStruct(TimeCurrent(), dt);
|
||||
int current_hour = dt.hour;
|
||||
|
||||
// Handle case where end hour is before start hour (overnight)
|
||||
if(InpEndHour < InpStartHour)
|
||||
{
|
||||
return (current_hour >= InpStartHour || current_hour < InpEndHour);
|
||||
}
|
||||
else
|
||||
{
|
||||
return (current_hour >= InpStartHour && current_hour < InpEndHour);
|
||||
}
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Open buy position |
|
||||
//+------------------------------------------------------------------+
|
||||
void OpenBuyPosition()
|
||||
{
|
||||
double price = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
|
||||
|
||||
// No SL/TP - exit only on EMA recross
|
||||
if(trade.Buy(InpLotSize, _Symbol, price, 0, 0, "EMA Crossover Buy"))
|
||||
{
|
||||
Print("Buy order opened at ", price, " (Exit on bearish recross)");
|
||||
}
|
||||
else
|
||||
{
|
||||
Print("Failed to open buy order: ", trade.ResultRetcodeDescription());
|
||||
}
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Open sell position |
|
||||
//+------------------------------------------------------------------+
|
||||
void OpenSellPosition()
|
||||
{
|
||||
double price = SymbolInfoDouble(_Symbol, SYMBOL_BID);
|
||||
|
||||
// No SL/TP - exit only on EMA recross
|
||||
if(trade.Sell(InpLotSize, _Symbol, price, 0, 0, "EMA Crossover Sell"))
|
||||
{
|
||||
Print("Sell order opened at ", price, " (Exit on bullish recross)");
|
||||
}
|
||||
else
|
||||
{
|
||||
Print("Failed to open sell order: ", trade.ResultRetcodeDescription());
|
||||
}
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Manage existing position |
|
||||
//+------------------------------------------------------------------+
|
||||
void ManagePosition()
|
||||
{
|
||||
if(!PositionSelect(_Symbol))
|
||||
return;
|
||||
|
||||
// Update daily profit
|
||||
double current_profit = PositionGetDouble(POSITION_PROFIT);
|
||||
if(current_profit != last_profit)
|
||||
{
|
||||
daily_profit += (current_profit - last_profit);
|
||||
last_profit = current_profit;
|
||||
}
|
||||
|
||||
// Position management: Only track profit/loss
|
||||
// Exit is handled by EMA recross signal in OnTick()
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Apply trailing stop (disabled - using recross exit only) |
|
||||
//+------------------------------------------------------------------+
|
||||
void ApplyTrailingStop()
|
||||
{
|
||||
// Trailing stop disabled - using EMA recross as exit signal only
|
||||
// This function kept for compatibility but does nothing
|
||||
return;
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Move stop loss to break-even (disabled - using recross exit only)|
|
||||
//+------------------------------------------------------------------+
|
||||
void MoveToBreakEven()
|
||||
{
|
||||
// Break-even disabled - using EMA recross as exit signal only
|
||||
// This function kept for compatibility but does nothing
|
||||
return;
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Trade transaction event handler |
|
||||
//+------------------------------------------------------------------+
|
||||
void OnTradeTransaction(const MqlTradeTransaction& trans,
|
||||
const MqlTradeRequest& request,
|
||||
const MqlTradeResult& result)
|
||||
{
|
||||
// Track daily profit only (consecutive losses feature removed)
|
||||
if(trans.type == TRADE_TRANSACTION_DEAL_ADD)
|
||||
{
|
||||
if(HistoryDealSelect(trans.deal))
|
||||
{
|
||||
long deal_type = HistoryDealGetInteger(trans.deal, DEAL_TYPE);
|
||||
if(deal_type == DEAL_TYPE_BALANCE || deal_type == DEAL_TYPE_COMMISSION)
|
||||
return;
|
||||
|
||||
// Check if deal is from current day
|
||||
datetime deal_time = (datetime)HistoryDealGetInteger(trans.deal, DEAL_TIME);
|
||||
MqlDateTime deal_dt, current_dt;
|
||||
TimeToStruct(deal_time, deal_dt);
|
||||
TimeToStruct(TimeCurrent(), current_dt);
|
||||
|
||||
// Only process deals from current day
|
||||
bool is_current_day = (deal_dt.day == current_dt.day &&
|
||||
deal_dt.month == current_dt.month &&
|
||||
deal_dt.year == current_dt.year);
|
||||
|
||||
if(is_current_day)
|
||||
{
|
||||
double deal_profit = HistoryDealGetDouble(trans.deal, DEAL_PROFIT);
|
||||
// Daily profit is tracked in ManagePosition()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
\chapter{私募股权与另类投资版图}
|
||||
|
||||
\section{另类投资在资产配置中的位置}
|
||||
|
||||
另类投资(Alternative Investments)泛指在传统股票与债券之外、流动性通常较低、结构更复杂、信息披露与监管框架与公募不同的资产类别。机构组合引入另类资产的核心动机包括:\textbf{分散化}(与公开市场相关性不完全同步)、\textbf{风险溢价}(流动性折价与复杂度溢价)、以及在某些市场环境下\textbf{绝对收益}或\textbf{通胀对冲}的诉求。私募股权(PE)是其中体量最大、制度最成熟的一条主线,但与对冲基金、实物资产、私募信贷等边界常有交叉。
|
||||
|
||||
\section{私募股权的主要形态}
|
||||
|
||||
\textbf{并购基金(Buyout)}通常以控股或重大少数股权方式收购成熟企业,依托杠杆、运营改善与行业整合提升退出倍数。\textbf{成长资本(Growth)}面向已盈利或接近盈利的扩张期企业,股权稀释程度与估值弹性介于 VC 与并购之间。\textbf{风险投资(VC)}聚焦早期技术与商业模式验证,单笔损失率可较高,依赖幂律分布中的极端成功项目。\textbf{夹层资本(Mezzanine)}与\textbf{困境/特殊机会}策略则更接近信贷或混合工具,在资本结构中的位置与纯股权 PE 不同。读者在本书语境下可将「PE」理解为\textbf{以非上市股权或相关工具为主、以主动治理与退出为收益来源}的广义家族,而不局限于某一细分标签。
|
||||
|
||||
\section{与公募、对冲基金的对照}
|
||||
|
||||
\textbf{期限与流动性:}典型 PE 基金有固定存续期与资本召唤(capital call)节奏,LP 在基金层面难以按净值每日申赎;公募与多数对冲基金则强调份额流动性(尽管侧袋、门控等机制会削弱这一特征)。\textbf{定价:}非上市股权缺乏连续报价,估值依赖季度评估与交易事件,业绩归因与「账面回报」解读需要额外谨慎。\textbf{监管:}面向零售的公募受最严格的信息披露与销售规则约束;私募链条在各国法域下对应不同「合格投资者」门槛与募集方式限制(详见本书合规篇)。\textbf{收益结构:}PE 更强调\textbf{控制或强影响}下的价值创造叙事;多策略对冲基金可能以市场中性、套利或宏观为主,与单一企业基本面绑定度较低。
|
||||
|
||||
\section{为何「版图」思维重要}
|
||||
|
||||
从零到一设立或参与一支基金时,先明确自己在版图中的\textbf{坐标}(阶段、行业、地域、币种、是否杠杆),能避免基金条款与团队能力与宣称策略错位。后续章节中的募资对象、尽调深度、条款谈判与退出假设,都隐含了这一坐标选择;合规篇则进一步约束该坐标在特定司法辖区是否可合法落地。
|
||||
@@ -0,0 +1,17 @@
|
||||
\chapter{生态链:LP、GP、被投企业与服务商}
|
||||
|
||||
\section{LP 与 GP:信托关系与经济条款}
|
||||
|
||||
有限合伙人(Limited Partner, LP)是基金的主要出资方,以认缴承诺为上限承担有限责任;普通合伙人(General Partner, GP)或其关联实体担任基金管理人,负责投资决议、投后管理与投资者关系。二者关系由\textbf{有限合伙协议(LPA)}及附属 side letter 约束,核心经济条款包括管理费基数与费率、业绩报酬(carried interest)与门槛收益率(hurdle)、分配瀑布(European vs American waterfall)以及 GP 跟投(GP commit)比例。法律上 LP 通常不得干预具体项目投资,以免丧失有限责任保护;实务中通过咨询委员会(advisory committee)与协议约定的重大事项同意权行使制衡。
|
||||
|
||||
\section{基金载体、管理人与被投企业}
|
||||
|
||||
常见结构为:一支(或数支并列)\textbf{有限合伙基金}作为投资主体,向被投企业注资;\textbf{基金管理公司}(常由 GP 关联方设立)向基金收取管理费并聘用投资团队。被投企业处于生态链的「资产端」:其董事会构成、信息披露节奏与股东协议中的保护条款,直接决定 GP 能否有效行使治理。理解这一\textbf{三角结构}有助于划分「基金层面的义务」与「管理公司层面的义务」,例如在跨境架构中税务与监管申报的责任主体。
|
||||
|
||||
\section{交易服务商的角色}
|
||||
|
||||
\textbf{财务顾问(FA)}组织买方或卖方流程、协调尽调数据室与谈判节奏,但不替代 GP 的投资判断。\textbf{律师事务所}起草交易文件与 LPA,并出具法律意见。\textbf{审计师}提供基金与被投企业的财务审计或商定程序报告,支撑估值与 LP 报告。\textbf{基金行政管理人(fund administrator)}处理 NAV、投资者报表与监管报送接口。\textbf{托管行}在适用法域下保管现金与证券。这些角色嵌入在「立项—签约—交割—季报—退出」的时间轴上;冷启动阶段可用外包与里程碑付款降低固定成本,但随着 AUM 与 LP 要求上升,服务商层级与独立性往往需加强。
|
||||
|
||||
\section{网络效应与声誉资本}
|
||||
|
||||
生态链不是静态供应链:同一批 LP、FA 与律师在多个基金周期中重复博弈,\textbf{声誉}与\textbf{执行记录}成为比单次条款更重要的资产。新 GP 在构建生态位时,应优先建立可验证的里程碑(透明报告、按时交割、清晰沟通),以便在后续轮次中获得更优的介绍链与条款空间。
|
||||
@@ -0,0 +1,17 @@
|
||||
\chapter{基金设立:载体选择与基本条款}
|
||||
|
||||
\section{有限合伙:PE 的主流选择}
|
||||
|
||||
在多数法域,\textbf{有限合伙}因其\textbf{税收穿透}(在符合条件下由合伙人层面纳税而非实体层面双重征税)、GP 无限责任与 LP 有限责任的清晰划分,成为私募股权基金的首选载体。基金期限、投资期与延长期、GP 移除与关键人条款等,均在 LPA 中锁定。设立成本包括注册费用、首次法律起草与监管登记(若适用);跨境 LP 时还需考虑反洗钱(KYC)与制裁筛查流程。
|
||||
|
||||
\section{公司制与契约型:何时出现}
|
||||
|
||||
\textbf{公司制}基金在部分辖区便于某些类型投资者(如养老基金子账户)持有,或用于上市载体(如部分投资信托结构);劣势常在于\textbf{双重征税}或治理灵活性不足。\textbf{契约型基金}在部分亚洲市场与资管产品中常见,依赖托管人与管理人合同网络;与经典离岸/在岸有限合伙相比,权利义务映射方式不同,需在律师协助下逐条对照 LPA 等价物。
|
||||
|
||||
\section{LPA 中的经济条款框架}
|
||||
|
||||
\textbf{管理费}通常按认缴或实缴或 NAV 的一定比例计提,投资期后可能调降或切换基数。\textbf{业绩报酬}在超过 hurdle 后对利润分成;需明确是\textbf{whole-of-fund}还是 deal-by-deal、是否存在 clawback(回拨)机制。\textbf{分配瀑布}决定税、费、本金与收益在 LP 与 GP 之间的先后顺序,直接影响 IRR 与 DPI 的时间形态。Side letter 可能对大型 LP 给予最惠国(MFN)、共同投资权或额外信息权,管理公司需建立台账避免承诺冲突。
|
||||
|
||||
\section{治理与管理条款}
|
||||
|
||||
投资委员会组成、关联交易审批、个人跟投(co-investment)规则、借款与担保限额、关键人事件与 GP 替换程序,构成管理条款的骨架。从零到一阶段,常见错误是\textbf{过度照搬大型基金模板}导致小团队无法合规执行;应在「可运营」与「LP 可接受」之间折中,并预留随基金规模扩大而修订的修正案机制(须符合适用法律)。
|
||||
@@ -0,0 +1,17 @@
|
||||
\chapter{募资从零到一:材料、路演与合规边界}
|
||||
|
||||
\section{材料体系:PPM、数据室与 DDQ}
|
||||
|
||||
私募配售备忘录(PPM / 私募备忘录)是面向合格投资者的\textbf{核心法律与营销混合文件},需披露策略、风险因素、费用、利益冲突、管理团队与历史业绩(若有)。\textbf{数据室(VDR)}存放基金章程、LPA 草案、合规政策样本、过往审计与参考投资组合(在允许范围内)。\textbf{尽职调查问卷(DDQ)}回复机构 LP 的标准问题,便于进入 allocator 的内部评分流程。三类材料应\textbf{同源更新},避免路演口述与书面文件不一致引发监管或民事风险。
|
||||
|
||||
\section{路演节奏与叙事}
|
||||
|
||||
首轮沟通通常以\textbf{一页 Teaser}与简短电话开始,确认策略与规模是否匹配 allocator 的授权。正式路演聚焦:投资逻辑可重复性、团队分工、风险控制、费用与条款、与同类基金的差异化。\textbf{演示材料}须经合规审查(若管理人已持牌或在持牌顾问指导下)。问答环节应如实记录;对无法承诺的事项(如未来收益)明确拒绝书面化。
|
||||
|
||||
\section{合格投资者与宣传边界}
|
||||
|
||||
各法域对「合格投资者」「专业客户」的定义不同;本书合规篇提供框架性索引。一般原则是:\textbf{非公开募集}、\textbf{不对不特定对象宣传保本或确定收益}、\textbf{适当性匹配}。社交媒体与公开会议上的表述极易构成「公开劝诱」的争议,冷启动章节亦强调有节制的触达;本章从基金文件角度要求:所有对外版本留有\textbf{版本号与审批记录}。
|
||||
|
||||
\section{首次交割与后续关闭}
|
||||
|
||||
首次关闭(first closing)确定首批 LP 的认缴与计费起点;后续关闭(subsequent closings)可能涉及\textbf{同等条款最惠}、对早期 LP 的补偿(equalisation)与对价调整。行政上需协调认购书签署、KYC、资金到账与工商/登记变更(视载体而定)。延长的募集期会增加法律与市场成本,应在 PPM 中设定明确截止条件与 GP 的权利。
|
||||
@@ -0,0 +1,17 @@
|
||||
\chapter{赛道与策略:如何定义你的「第一只基金」}
|
||||
|
||||
\section{聚焦优于泛化}
|
||||
|
||||
首只基金若宣称「全行业、全阶段、全球配置」,在 allocator 视角中往往等同于\textbf{无清晰边际优势}。更可行的做法是在\textbf{行业 × 地域 × 阶段 × 单笔规模}四维中至少锁定两维,形成可辩护的「为什么是你」的故事。聚焦不等于永久狭窄:可在 LPA 中保留\textbf{合理扩展投资范围}的修正案机制,但首轮沟通应强调核心圈。
|
||||
|
||||
\section{可重复的筛选标准}
|
||||
|
||||
建立书面化的\textbf{立项标准}:最低收入/利润门槛、市场集中度上限、禁止行业(如强监管敏感领域)、ESG 红线、以及技术与团队评分卡。标准应可回溯:每个过会/否决决策对应哪一条标准被触发。这样既能训练初级投资人员,也能向 LP 证明流程非拍脑袋。量化背景团队可将部分标准\textbf{指标化},但仍需与投资委员会的人文判断结合。
|
||||
|
||||
\section{基金规模与机会集的匹配}
|
||||
|
||||
目标募资额应与\textbf{可完成投资节奏}及\textbf{团队覆盖能力}一致:过小则管理费无法覆盖固定成本;过大则被迫降低标准或拉长投资期,损害 IRR 与声誉。冷启动阶段可采用较低硬上限 + 后续基金接力,而不是单支基金「一步到位」。
|
||||
|
||||
\section{品牌与执行的乘数}
|
||||
|
||||
赛道定义会通过每一次公开露面、案例研究与 LP 推荐被\textbf{重复强化}。频繁切换叙事会稀释品牌,使介绍链无法累积。建议每年复盘一次策略声明,仅在证据充分时调整表述,并同步修订 PPM 与网站(若有)。
|
||||
@@ -0,0 +1,17 @@
|
||||
\chapter{项目获取与初步判断}
|
||||
|
||||
\section{项目流的来源}
|
||||
|
||||
冷触达(cold outreach)在缺乏品牌时效率低但并非无效:关键是\textbf{高度定制}的切入点(对目标公司近期融资、产品或监管变化的具体评论)。更可持续的来源包括:创始人推荐、同行 FA、律师与会计师转介、行业会议与学术合作。\textbf{网络效应}随成功案例累积——第一单往往最难,需在冷启动章节所述触达策略与这里所述来源之间建立闭环。
|
||||
|
||||
\section{Teaser 与保密}
|
||||
|
||||
卖方 Teaser 通常为匿名一页纸,概述行业、财务区间与交易诉求。买方在签署\textbf{保密协议(NDA)}前应明确:接触信息的人员范围、用途限制、存续期与管辖法律。NDA 不是万能盾:敏感信息仍应分层披露,核心技术细节可推迟至 exclusivity 阶段。
|
||||
|
||||
\section{商业逻辑的快速体检}
|
||||
|
||||
在完整尽调之前,用有限时间回答:\textbf{客户为什么买单?}替代方案与切换成本?\textbf{单位经济模型}是否随规模改善?竞争格局是赢家通吃还是分散?若上述任一项无法在假设表格中自洽,可暂缓投入重度资源。市场大小(TAM/SAM/SOM)宜用多种方法交叉校验,避免单一夸张口径。
|
||||
|
||||
\section{与内部流程的衔接}
|
||||
|
||||
立项会应产生\textbf{书面备忘录}:投资主题、主要风险、下一步工作清单与负责人。避免「口头立项、无编号」导致后续 DD 与法务脱节。对于可能触发利益冲突的项目(如 LP 关联),应尽早启动合规标注与回避程序。
|
||||
@@ -0,0 +1,21 @@
|
||||
\chapter{尽职调查:财务、法律与商业}
|
||||
|
||||
\section{DD 的整体节奏}
|
||||
|
||||
尽职调查(Due Diligence)应在 Term Sheet 签署后、重大交割款项划出前完成,但\textbf{高风险事项}(诉讼、环保、核心知识产权归属)宜在签署前做\textbf{有限度预尽调}以避免排他期浪费。典型工作流:数据室清单 → 管理层问答(Q\&A)→ 现场走访 → 第三方报告(环境、税务、IT)→ 问题汇总与价格/条款调整建议。投资团队须指定\textbf{工作流负责人},防止律师与会计师各跑各的、结论无法合并。
|
||||
|
||||
\section{财务尽调}
|
||||
|
||||
关注\textbf{盈利质量}:收入确认政策是否激进?应收账款与现金流是否匹配?一次性收益与非经常性项目是否被误当作可持续利润?\textbf{正常化 EBITDA} 的调整须有证据支撑并在投资备忘录中披露假设。营运资金(NWC)目标与交割后「漏桶」条款常是谈判焦点。对早期企业,财务 DD 可能让位于\textbf{单位经济与跑道}分析。
|
||||
|
||||
\section{法律尽调}
|
||||
|
||||
重点扫描:重大合同(客户/供应商集中度、变更控制权条款)、诉讼与监管调查、土地与环保许可、知识产权链条(职务发明、开源许可污染)、劳动与社保合规、数据隐私。红色事项应进入\textbf{交割条件(CP)}或价格机制(earn-out、托管账户),而非仅停留在尽调报告附录。
|
||||
|
||||
\section{商业与运营尽调}
|
||||
|
||||
访谈中层与客户样本(在合规前提下)、考察供应链与产能利用率、评估数字化与内控成熟度。对 PE 而言,\textbf{100 日计划}的雏形往往在此阶段形成:投后哪些杠杆(采购、定价、组织)最可能兑现。
|
||||
|
||||
\section{内部与外部顾问的协作}
|
||||
|
||||
内部:投资负责人对结论负总责,需参与关键会议而非仅读摘要。外部:律师与审计师的范围说明书(SOW)应明确交付物与时间,避免范围蔓延。所有重大发现应进入\textbf{问题清单}并标注状态(已解决/留待交割后/放弃交易)。
|
||||
@@ -0,0 +1,21 @@
|
||||
\chapter{估值与交易结构入门}
|
||||
|
||||
\section{可比公司与可比交易}
|
||||
|
||||
上市公司估值倍数(EV/EBITDA、P/E 等)提供市场锚点,但需调整:流动性折价、控制权溢价、行业周期位置与会计差异。\textbf{可比交易}更接近一级市场,但样本稀疏、披露不全,应对区间取保守情景。两者结合形成\textbf{估值走廊},而非单点数字。
|
||||
|
||||
\section{DCF 与情景分析}
|
||||
|
||||
现金流折现(DCF)强迫显式列出增长、利润率、资本支出与终值假设;敏感性分析可展示关键驱动因子。早期企业 DCF 权重常低于\textbf{最近融资价格}或\textbf{风险投资法},但仍有内部讨论价值。注意 WACC 与终值增长率假设勿机械套用教科书 defaults。
|
||||
|
||||
\section{股权比例与对价形式}
|
||||
|
||||
估值决定入门股权比例;\textbf{员工期权池}是否投前扩池会稀释有效比例。对价可为现金、换股或分期支付;分期与\textbf{earn-out} 绑定未来业绩,降低买方 upfront 风险但增加争议与会计复杂性。
|
||||
|
||||
\section{对赌与调整机制}
|
||||
|
||||
业绩承诺、回购与现金补偿等条款在中文语境常称「对赌」,实质是\textbf{价格与风险的再分配}。设计时应避免不可执行的惩罚、与公司法冲突的约定,以及税务上的意外后果。所有对赌须由\textbf{律师与税务顾问}联合审阅;本书仅强调经济含义:对赌不是「保证收益」,而是条件支付。
|
||||
|
||||
\section{谈判中的艺术}
|
||||
|
||||
估值是\textbf{区间博弈}:卖方要叙事上限,买方要风险下限。交易结构(分期、托管、陈述保证保险)常比硬压估值更能成交。保留\textbf{双赢叙事}有利于投后合作——过度压榨条款往往在董事会层面反噬。
|
||||
@@ -0,0 +1,21 @@
|
||||
\chapter{Term Sheet 与谈判要点}
|
||||
|
||||
\section{Term Sheet 的法律地位}
|
||||
|
||||
Term Sheet(条款清单)可分为\textbf{有约束力}(如保密、费用、排他)与\textbf{无约束力}(商业核心条款)段落。签署前应明确哪些条款在最终协议中\textbf{不得劣化},避免签署后被对方律师「技术性推翻」。中英文版本并存时须指定\textbf{优先语言}。
|
||||
|
||||
\section{经济条款簇}
|
||||
|
||||
投资金额、投前/投后估值、股价与稀释、期权池、清算优先(1x non-participating vs participating)、股息(多为累积与否的象征性条款)。\textbf{反稀释}(full ratchet vs weighted average)在下行轮融资中争议最大,需与创始人心理预期和公司后续融资能力一并评估。
|
||||
|
||||
\section{治理与控制簇}
|
||||
|
||||
董事会席位、观察员席位、否决权清单(预算、并购、关联交易、高管任免)。过多否决权会瘫痪公司;过少则 LP 对资产保护不足。应在「关键少数」事项上集中否决权,日常运营授权管理层。
|
||||
|
||||
\section{流动性与协同退出}
|
||||
|
||||
\textbf{领售权(drag-along)}迫使小股东参与整体出售;\textbf{随售权(tag-along)}保护小股东参与买方要约。\textbf{优先认购权}与\textbf{共同出售权}影响后续轮次节奏。IPO 场景下需关注\textbf{注册权}与锁定期。
|
||||
|
||||
\section{谈判策略:底线与交换}
|
||||
|
||||
事先内部分类:\textbf{必须守住}(如核心信息权、重大资产处置否决)、\textbf{可交换}(如观察员人数)、\textbf{可放弃}(如象征性股息)。用次要让步换取对方在估值或关键治理上的让步。记录谈判历史,避免口头承诺遗失;重大让步须经投资委员会书面确认。
|
||||
@@ -0,0 +1,21 @@
|
||||
\chapter{交割、资金划付与投后治理}
|
||||
|
||||
\section{交割条件(CP)}
|
||||
|
||||
常见 CP 包括:陈述与保证真实、无法律禁令、关键第三方同意、政府审批(若需)、员工留任协议签署、无重大不利影响(MAC)未发生。GP 应维护\textbf{CP 检查表},由律师出具法律意见、财务确认资金路径。\textbf{划款指令}须双人复核,防范欺诈性邮件篡改账户。
|
||||
|
||||
\section{文件签署顺序}
|
||||
|
||||
通常先满足 CP,再签署最终股份认购/增资文件,最后释放托管或直接划款。跨境交易可能涉及\textbf{外汇登记}与税务代扣;时间表应预留监管机构处理窗口。签字页与正本份数在 closing memo 中列明。
|
||||
|
||||
\section{投后信息权}
|
||||
|
||||
股东协议与 LPA 往往规定:月度/季度报表、年度预算、重大事件即时通报、审计配合权。GP 应建立\textbf{被投企业报告模板}与内部 CRM 提醒,避免投后「失联」引发 LP 质疑。
|
||||
|
||||
\section{董事会节奏与赋能}
|
||||
|
||||
董事会频率与议程应匹配公司阶段:早期可更密,成熟期可季度。GP 董事应在\textbf{战略、资本结构、关键招聘}上发力,避免微观管理日常运营。赋能形式包括介绍客户、补充高管、协助下一轮融资——均须注意利益冲突披露。
|
||||
|
||||
\section{重大事项同意权}
|
||||
|
||||
基金层面可能对超过单笔金额或特定类型的投资要求 LPAC 或顾问委员会知情;被投企业层面的否决权行使应记录在案,以支持未来退出时的治理叙事。
|
||||
@@ -0,0 +1,21 @@
|
||||
\chapter{投后管理与价值创造}
|
||||
|
||||
\section{从监控到主动价值创造}
|
||||
|
||||
投后不仅是收取报表:优秀 GP 在交易完成前已草拟\textbf{价值创造路线图}(采购协同、定价、渠道、数字化、组织瘦身等)。执行依赖被投企业管理层的信任与激励——投后首 100 天的沟通节奏与「不做什么」的承诺同样重要。
|
||||
|
||||
\section{财务与运营 KPI}
|
||||
|
||||
建立与战略一致的 KPI 仪表盘:收入增长、利润率、现金转换周期、客户留存、安全与环境指标(若适用)。对比投资备忘录中的基准,定期复盘偏差原因;必要时触发\textbf{预警会}与纠偏措施。
|
||||
|
||||
\section{人才与组织}
|
||||
|
||||
关键人风险是 PE 失败主因之一。投后应参与\textbf{高管评估、薪酬与长期激励}设计,必要时引入猎头与外部董事。文化冲突在跨境并购中尤为突出,需预留整合预算与时间。
|
||||
|
||||
\section{追加投资与跟投}
|
||||
|
||||
后续轮次是否跟进,应基于\textbf{重新估值}与\textbf{权利维持}(防稀释),而非沉没成本。LP 侧共同投资(co-invest)机会需公平分配并记录,避免利益输送指控。
|
||||
|
||||
\section{危机项目}
|
||||
|
||||
业绩持续恶化、治理僵局或合规爆雷时,应启动\textbf{危机协议}:增派资源、更换管理层、寻求出售或重组。法律上注意董事\textbf{受信义务}在困境中的变化。早期识别比晚期救火成本更低;投后例会应保留「黄色/红色」分级机制。
|
||||
@@ -0,0 +1,21 @@
|
||||
\chapter{退出路径:IPO、并购与二级转让}
|
||||
|
||||
\section{退出是投资 thesis 的一部分}
|
||||
|
||||
进入时就应讨论\textbf{最可能的退出通道}:战略并购、财务买家、IPO、 secondary sale(卖老股)或 recap。不同通道对持股比例、治理清洁度、审计年限与合规记录要求不同;临退出才补洞往往代价高昂。
|
||||
|
||||
\section{并购退出}
|
||||
|
||||
战略买家通常愿付\textbf{协同溢价},但谈判周期与反垄断审查不确定。财务买家(另一家 PE)依赖杠杆与再次出售,尽调更偏财务模型。拍卖流程(broad auction vs targeted)影响价格与泄密风险;顾问费用与管理层激励(management equity rollover)需在条款中明确。
|
||||
|
||||
\section{IPO}
|
||||
|
||||
公开市场提供流动性与品牌,但面临\textbf{锁定期、持续披露成本与市场波动}。上市地选择(本地 vs 离岸)涉及估值倍数、监管环境与外汇安排。GP 需与承销商、公司律师协调\textbf{注册权}与减持节奏。
|
||||
|
||||
\section{二级份额转让}
|
||||
|
||||
基金到期或 LP 流动性需求可能触发\textbf{基金二级交易}(LP 份额转让)或\textbf{投资组合二级出售}(continuation fund)。定价依赖 NAV 与尽职调查;利益冲突(GP 同时代表买卖双方)须严格程序化并披露。
|
||||
|
||||
\section{基金期限与延期}
|
||||
|
||||
LPA 规定的基金到期后,未退出资产可通过\textbf{延期}(通常需一定比例 LP 同意)、\textbf{捆绑出售}或\textbf{接续基金}处理。延期费率与 GP 激励常重新谈判;透明沟通可减少纠纷。
|
||||
@@ -0,0 +1,46 @@
|
||||
\chapter{合规、内控与声誉风险}
|
||||
|
||||
\section{合规在私募中的含义}
|
||||
|
||||
「合规」不仅是应对监管检查,更是\textbf{保护 LP 资产、降低民事与刑事责任概率、维护品牌}的基础设施。对管理人而言,核心模块通常包括:反洗钱与客户尽职调查、利益冲突管理、内幕信息与市场滥用防范、宣传与销售适当性、记录保存与监管报送。各法域细节见本书合规篇;本章提供\textbf{内控搭建}的通用框架。
|
||||
|
||||
\section{反洗钱(AML)与客户尽职调查(KYC)}
|
||||
|
||||
Know Your Customer 要求识别最终受益所有人(UBO)、资金来源与制裁名单筛查。对机构 LP 与个人 LP 的尽调深度不同;高风险司法辖区或政治公众人物(PEP)需强化措施。交易环节须警惕\textbf{结构化分拆}规避门槛的行为。AML 政策应书面化并定期培训,异常交易有内部升级路径。
|
||||
|
||||
\section{利益冲突(COI)}
|
||||
|
||||
典型冲突包括:基金 A 与基金 B 竞争同一标的、GP 关联方提供服务收费、个人跟投与基金跟投机会分配不均、顾问同时服务卖方与买方。管理措施包括:\textbf{事前披露}、投资委员会回避、独立委员表决、side letter 台账与公平分配政策。未妥善处理的冲突是监管处罚与 LP 诉讼的高频来源。
|
||||
|
||||
\section{内幕信息与防火墙}
|
||||
|
||||
投研人员可能接触未公开重大信息(尤其临近上市或并购标的)。需建立\textbf{信息分级}、限制名单(restricted list)、墙(Chinese wall)与「清洁团队」程序。个人交易与礼品申报政策应明确。量化团队若与上市公司或卖方顾问共享物理或逻辑环境,更需日志与权限隔离。
|
||||
|
||||
\section{记录保存与对外披露}
|
||||
|
||||
电子通信、模型版本、投委会材料、定价依据与 LP 沟通记录应在保留期限内\textbf{可检索备份}。对外路演、网站与社交媒体发布应有双人复核与版本存档。监管检查与民事诉讼中,「找不到记录」往往被推定不利。
|
||||
|
||||
\begin{figure}[htbp]
|
||||
\centering
|
||||
\begin{tikzpicture}[font=\footnotesize]
|
||||
\node[compliance=gray!80!black, minimum width=2.4cm] (core) {内控核心};
|
||||
\node[compliance=red!70!black, above left=0.9cm and 1.1cm of core] (aml) {AML/KYC\\客户尽调};
|
||||
\node[compliance=orange!80!black, above right=0.9cm and 1.1cm of core] (coi) {利益冲突\\Chinese wall};
|
||||
\node[compliance=blue!70!black, below left=0.9cm and 1.1cm of core] (mn) {材料留痕\\宣传口径};
|
||||
\node[compliance=teal!70!black, below right=0.9cm and 1.1cm of core] (rep) {监管报送\\重大事项};
|
||||
|
||||
\foreach \x in {aml,coi,mn,rep}
|
||||
\draw[compliance arrow] (core) -- (\x);
|
||||
|
||||
\begin{scope}[on background layer]
|
||||
\node[draw=violet!40, rounded corners=14pt, inner sep=12pt, fill=violet!3,
|
||||
fit=(core)(aml)(coi)(mn)(rep)] {};
|
||||
\end{scope}
|
||||
\end{tikzpicture}
|
||||
\caption{私募管理人内控与合规常见支柱(示意)}
|
||||
\label{fig:compliance-pillars}
|
||||
\end{figure}
|
||||
|
||||
\section{声誉与「架构」的边界}
|
||||
|
||||
多层控股或离岸主体不能消除对\textbf{真实业务、适当性、非公开募集}等实质要求;监管与司法机关在特定情形下可否认法律人格独立性(刺破面纱)。合规篇各章所述境外牌照亦仅覆盖\textbf{获准司法辖区内的活动},与本书前文所述中国大陆登记备案相互独立,须分别论证。\textbf{声誉风险}往往在监管正式行动之前即通过 allocator 尽调、媒体报道与人才招聘受挫体现——内控投入是长期品牌的一部分。
|
||||
@@ -0,0 +1,53 @@
|
||||
\chapter{集团架构思维:控股、运营主体与风险隔离}
|
||||
|
||||
\section{为何采用控股—子公司结构}
|
||||
|
||||
商业动机通常包括:\textbf{品牌与合同主体分离}(运营风险不直接波及控股层持股)、\textbf{区域业务分拆}(不同法域雇佣与税务)、\textbf{融资与股权激励}(在子公司层面设期权池)、以及\textbf{未来资产剥离或引入战略投资人}时的清晰边界。对私募股权基金而言,GP、管理公司、顾问实体与特殊目的载体(SPV)的叠层亦属同类思维,但受监管披露与利益冲突规则约束更强。
|
||||
|
||||
\section{有限责任的有限性}
|
||||
|
||||
子公司原则上以自身资产对外承担责任,股东以出资为限承担风险。但若存在\textbf{人格混同}(账目、人员、业务、办公场所混用)、\textbf{资本显著不足}或\textbf{不当控制}损害债权人利益,法院可能刺破面纱,令集团内其他实体或股东担责。因此「隔离」是\textbf{可争取的法律效果},不是仅靠注册多一张纸就自动实现。
|
||||
|
||||
\section{关联交易与转让定价}
|
||||
|
||||
集团内服务协议(IT、人力、品牌许可)、资金池与交叉计费须符合\textbf{独立交易原则}(arm's length),并留存可比分析与董事会决议。税务机关与监管机构均可挑战\textbf{无商业实质}的腾挪。跨境支付还涉及代扣税与外汇合规。
|
||||
|
||||
\section{基金架构与管理人架构的区分}
|
||||
|
||||
\textbf{基金}作为投资载体有其独立寿命与 LP 关系;\textbf{管理公司}收取管理费并承担团队成本。混淆二者账户或费用分摊,易触发 LP 审计质疑与监管关注。量化策略若由「技术子公司」开发再许可给管理人,须明确 IP 归属、许可费率与披露路径。
|
||||
|
||||
\begin{figure}[htbp]
|
||||
\centering
|
||||
\begin{tikzpicture}[
|
||||
holding/.style args={#1}{
|
||||
rounded corners=8pt,
|
||||
minimum width=3.5cm,
|
||||
minimum height=1.1cm,
|
||||
align=center,
|
||||
draw=#1!65!black,
|
||||
thick,
|
||||
fill=#1!10,
|
||||
font=\small,
|
||||
drop shadow={shadow xshift=1pt, shadow yshift=-1pt, fill=black!18},
|
||||
},
|
||||
]
|
||||
\node[holding=violet] (parent) at (0,2.1) {控股 / 投资主体\\(持股、分红、再投资)};
|
||||
\node[holding=blue] (op) at (-2.6,0) {运营子公司 A\\合同、品牌、日常业务};
|
||||
\node[holding=teal] (tech) at (2.6,0) {运营子公司 B\\技术、IP、研发外包等};
|
||||
|
||||
\draw[compliance arrow] (parent) -- (op) node[midway, left, font=\scriptsize, xshift=-2pt] {股权};
|
||||
\draw[compliance arrow] (parent) -- (tech) node[midway, right, font=\scriptsize, xshift=2pt] {股权};
|
||||
|
||||
\draw[dashed, gray!55, thick] (op.east) -- node[above, font=\scriptsize, sloped] {服务协议 / 许可} (tech.west);
|
||||
|
||||
\node[font=\scriptsize, text width=9.2cm, align=center, below=0.65cm of op, xshift=1.3cm, text=red!55!black] {
|
||||
\textbf{有限性:}人格混同、不当控制、掏空公司时,可能被刺破面纱;\\
|
||||
证券/资管「牌照」与募集合规\textbf{不因多一层公司}而自动满足。};
|
||||
\end{tikzpicture}
|
||||
\caption{典型控股—运营分层与内部交易(示意)}
|
||||
\label{fig:holding-structure}
|
||||
\end{figure}
|
||||
|
||||
\section{与合规篇的衔接}
|
||||
|
||||
母公司通过\textbf{合法分红}或具有商业实质与定价依据的\textbf{服务费}向上游转移利润时,须留存转让定价文档并接受税务与金融监管双重审视。量化团队若以子公司形式存在,须与本书「合规与全球展业」篇中的管理人资格要求一并设计,避免「仅有壳公司、无持牌主体」的展业真空。架构讨论应始终与\textbf{持牌律师与会计师}同步,本书仅提供对话框架。
|
||||
@@ -0,0 +1,36 @@
|
||||
\chapter{从零到一检查清单与常见坑}
|
||||
|
||||
\section{募资前检查清单}
|
||||
|
||||
\begin{itemize}
|
||||
\item 策略陈述与 PPM、DDQ、路演材料是否\textbf{版本一致},且经合规/律师审阅(若适用)。
|
||||
\item 管理团队简历、过往业绩披露是否\textbf{可验证},无夸大或遗漏重大纪律处分。
|
||||
\item 基金载体、LPA 核心条款与管理人登记状态(或时间表)是否向首轮 LP 透明。
|
||||
\item 行政:银行账户、KYC 流程、认购书模板、费用与里程碑是否就绪。
|
||||
\end{itemize}
|
||||
|
||||
\section{首单投资前检查清单}
|
||||
|
||||
\begin{itemize}
|
||||
\item 立项备忘录、投委会决议、利益冲突审查是否归档。
|
||||
\item 财务/法律/商业尽调结论与\textbf{红线问题}关闭状态;CP 与划款路径确认。
|
||||
\item Term Sheet 与最终协议的关键经济条款\textbf{差异说明}。
|
||||
\item 投后 100 日计划初稿与报告节奏是否通知被投方。
|
||||
\end{itemize}
|
||||
|
||||
\section{年度复盘清单}
|
||||
|
||||
\begin{itemize}
|
||||
\item 组合层面:IRR、MOIC、DPI/TVPI 与可比基金基准;单一项目减值测试。
|
||||
\item 合规与运营:监管报送、税务、审计、数据安全与业务连续性演练。
|
||||
\item 团队:招聘、培训、激励与关键人备份;文化与健康度(尤其远程团队)。
|
||||
\item 战略:赛道假设是否仍成立,下一支基金或延续基金是否进入筹备窗口。
|
||||
\end{itemize}
|
||||
|
||||
\section{常见失败模式与改进}
|
||||
|
||||
\textbf{条款理解偏差:}投资团队未吃透清算优先与反稀释的数值后果——改进:建模培训与双人复核。\textbf{尽调流于形式:}过度依赖管理层 PPT——改进:独立来源与现场走访清单。\textbf{投后失联:}交割后数月无董事会材料——改进:强制 CRM 与季度投后评审会。\textbf{募资与投资脱节:}承诺的策略与实际项目类型漂移——改进:投资委员会章程与 LP 沟通纪律。
|
||||
|
||||
\section{与全书其他部分的衔接}
|
||||
|
||||
冷启动与 Networking 解决「人与信任从哪来」;合规篇解决「能否合法做」;本清单解决「落地时别漏项」。建议将清单电子化为可勾选项,并在每年审计前更新一版。
|
||||
@@ -0,0 +1,112 @@
|
||||
\chapter{冷启动:从策略掌握者到最小可行团队}
|
||||
|
||||
本章讨论:在已具备可验证交易策略或组合的前提下,如何跨越「只有一个人、没有品牌、没有合规壳」的冷启动期,通过\textbf{Networking}与\textbf{有节制的触达}组建团队、积累信任,并与本书\textbf{合规篇}(第十七章起)所述合规路径衔接。\textbf{未获必要许可前,任何对外募资或投资顾问表述须严格遵守当地法律};下文中的「触达」包含人才、合作方与\textbf{未来}资本关系,不代表可绕过监管。
|
||||
|
||||
\section{冷启动的三类约束}
|
||||
|
||||
\begin{itemize}
|
||||
\item \textbf{可信度:}同类策略供给过剩, allocator 与联合创始人默认怀疑;需要\textbf{可复核}的过程记录(回测假设、实盘滑点、版本管理),而非仅口头「稳定盈利」。
|
||||
\item \textbf{合规壳:}在多数司法辖区,面向外部资金开展管理或募集前须具备相应资格(参见\textbf{合规篇})。冷启动阶段常见做法是:\textbf{自有/极窄关系资金}验证、或\textbf{入职持牌机构}输出策略、或与已持牌方\textbf{投顾/白标}合作——具体路径须律师确认。
|
||||
\item \textbf{资本与时间:}团队现金跑道与机会成本;过早扩张固定人力往往比「外包+里程碑」更危险。
|
||||
\end{itemize}
|
||||
|
||||
\section{阶段模型:从单人到最小团队}
|
||||
|
||||
\begin{figure}[htbp]
|
||||
\centering
|
||||
\begin{tikzpicture}[node distance=0.75cm, font=\small]
|
||||
\node[compliance=purple] (s0) {0. 策略持有人\\回测/仿真/小资金实盘};
|
||||
\node[compliance=violet, below=of s0] (s1) {1. 可展示记录\\文档化、可重复运行、风险指标};
|
||||
\node[compliance=blue, below=of s1] (s2) {2. 第一个同类\\联创 / 资深兼职 / 顾问};
|
||||
\node[compliance=teal, below=of s2] (s3) {3. 最小 pods\\研究+工程 或 工程+运维};
|
||||
\node[compliance=green!50!black, below=of s3] (s4) {4. 公司化与分工\\合规、运营、市场/IR};
|
||||
|
||||
\foreach \a/\b in {s0/s1,s1/s2,s2/s3,s3/s4}
|
||||
\draw[compliance arrow] (\a) -- (\b);
|
||||
|
||||
\begin{scope}[on background layer]
|
||||
\node[rounded corners=12pt, inner sep=11pt, fill=gray!6, draw=gray!45, dashed, fit=(s0)(s4)] {};
|
||||
\end{scope}
|
||||
\end{tikzpicture}
|
||||
\caption{从「掌握策略」到「可运转组织」的常见阶段(非严格线性,可回退迭代)}
|
||||
\label{fig:coldstart-stages}
|
||||
\end{figure}
|
||||
|
||||
\textbf{阶段 0--1:}重点不是扩张人头,而是把策略变成\textbf{他人可接手}的流水线:代码仓库规范、参数与数据版本、失败案例与最大回撤说明。此阶段 Networking 的目标多是\textbf{同行切磋与找漏洞},而非直接募资。
|
||||
|
||||
\textbf{阶段 2:}第一个关键合作者应补齐你最弱的一环:若你强研究弱工程,优先找能\textbf{工程化与上线}的人;若你强开发弱市场,可先找\textbf{行业顾问}(兼职)帮对接资源,再考虑全职。
|
||||
|
||||
\textbf{阶段 3--4:}出现固定交易时段运维、对手方对接、估值与报表需求时,再拆出\textbf{运营/风控合规};市场与投资者关系(IR)通常在\textbf{真有产品或持牌主体}后加重,避免过早「销售驱动」导致合规风险。
|
||||
|
||||
\section{团队角色清单(精简版)}
|
||||
|
||||
\begin{description}
|
||||
\item[投资/研究负责人] 策略逻辑、组合与风险预算;对外投资叙事与 allocator 对话(须在合规框架内)。
|
||||
\item[量化工程] 回测框架、执行系统、低延迟与稳定性;与经纪商/交易所 API 对接。
|
||||
\item[交易运维] 盘中监控、故障切换、日志与事故复盘;常与工程合并由小团队兼任。
|
||||
\item[合规与法务] 持牌后常需专职或外包律所;冷启动期至少要有\textbf{固定外部律师}审阅对外材料。
|
||||
\item[运营与财务] 基金会计、估值、投资者报告模板;公司日常账务。
|
||||
\item[市场/IR] 募资材料、路演排期、数据室维护;与「公开宣传」边界强相关,需合规双人复核。
|
||||
\end{description}
|
||||
|
||||
早期不必一人一岗:常见「2+1」是 \textbf{PM+工程} 加 \textbf{外包财务/律师};或 \textbf{PM+运营} 加 \textbf{外包开发}。原则是\textbf{不可外包的核心}(投资决策权、风控哲学)留在内部。
|
||||
|
||||
\section{Networking:去哪里、见谁、谈什么}
|
||||
|
||||
\begin{figure}[htbp]
|
||||
\centering
|
||||
\begin{tikzpicture}[font=\footnotesize]
|
||||
\node[ellipse, draw=teal!70, fill=teal!6, minimum width=3.6cm, minimum height=1.5cm, align=center] (hub) {你\\(策略与记录)};
|
||||
\node[reg node=blue, above left=1.2cm and 1.8cm of hub] (alumni) {校友与\\前同事};
|
||||
\node[reg node=orange, above right=1.2cm and 1.8cm of hub] (broker) {经纪商/\\期货商活动};
|
||||
\node[reg node=violet, left=2.5cm of hub] (conf) {行业会议\\与闭门沙龙};
|
||||
\node[reg node=purple, right=2.5cm of hub] (online) {线上社群\\(筛选质量)};
|
||||
\node[reg node=green!60!black, below left=1.2cm and 1.5cm of hub] (vendor) {数据商/\\云厂商生态};
|
||||
\node[reg node=red!65!black, below right=1.2cm and 1.5cm of hub] (intro) {介绍链\\(warm intro)};
|
||||
|
||||
\foreach \x in {alumni,broker,conf,online,vendor,intro}
|
||||
\draw[compliance arrow, shorten <=2pt, shorten >=2pt] (hub) -- (\x);
|
||||
\end{tikzpicture}
|
||||
\caption{Networking 渠道示意(优先 warm intro 与高质量闭门场景)}
|
||||
\label{fig:networking-hub}
|
||||
\end{figure}
|
||||
|
||||
\begin{itemize}
|
||||
\item \textbf{Warm intro 优先:}同样一封邮件,经共同信任人转发,回复率远高于陌生冷信。可维护简易表格:姓名、机构、关注点、上次联系日期、可提供的价值(非收益承诺)。
|
||||
\item \textbf{经纪商/期货商/主经纪会议:}偏执行与基础设施,适合找\textbf{运维与对手方}人脉,不宜在无证时当作募资场合。
|
||||
\item \textbf{会议与沙龙:}目标应是\textbf{学习监管口径、认识服务方、找潜在联合创始人},而非群发「代客理财」类话术。
|
||||
\item \textbf{线上社群:}质量参差;可贡献可验证的技术内容建立声誉,避免在公开频道讨论\textbf{具体客户资金与收益保证}。
|
||||
\item \textbf{校友网络:}律所、审计、投行背景校友常能在\textbf{公司设立与合规路径}上提供高质量引荐。
|
||||
\end{itemize}
|
||||
|
||||
\section{Cold call 与 Cold email:原则与节奏}
|
||||
|
||||
\textbf{对象区分:}
|
||||
\begin{description}
|
||||
\item[人才/技术合作] 邮件宜短:你是谁、策略领域(统计套利/CTA/做市等)、当前阶段、对方为何相关、明确\textbf{一次性}请求(15 分钟通话/咖啡)。附件可给\textbf{一页 Teaser(不含违规承诺)}。
|
||||
\item[Allocator/家办/FOF] 须事先研究其\textbf{策略容量、历史偏好、最低投资线}。首封邮件忌长 PDF;可附\textbf{合规审查过的}一页摘要与数据室链接(若有)。跟进间隔以周计,避免日催。
|
||||
\item[服务采购(数据、托管、系统)] 明确技术栈与接口需求,便于对方判断是否匹配。
|
||||
\end{description}
|
||||
|
||||
\textbf{共同禁忌:}保本保收益、公开招揽不特定对象、借用他人牌照名义、夸大未经审计的业绩。此类表述在多国可能同时触发\textbf{证券法与刑事风险}。
|
||||
|
||||
\textbf{可执行习惯:}每周固定数量\textbf{高质量触达}(如 5--10 封定制邮件)优于百封群发;每次对话后写三行纪要(对方痛点、下一步、承诺事项)。
|
||||
|
||||
\section{从交易执行者到「团队负责人」}
|
||||
|
||||
角色转变要点:
|
||||
\begin{enumerate}
|
||||
\item \textbf{决策透明:}研究日志、策略变更审批、仓位与风险限额书面化,减少「只有创始人脑子里有」的单点风险。
|
||||
\item \textbf{招聘顺序:}先\textbf{标准与文档},再扩人;否则新人只能当「第二个你」,无法放大杠杆。
|
||||
\item \textbf{激励:}早期现金有限时,可用里程碑奖金、虚拟股或期权,但须\textbf{律师起草}并与未来持牌主体股权结构一致。
|
||||
\item \textbf{冲突处理:}联合创始人间事先约定\textbf{股权兑现、退出、竞业},避免策略分歧时无法拆伙。
|
||||
\end{enumerate}
|
||||
|
||||
\section{与本书其他章节的衔接}
|
||||
|
||||
\begin{itemize}
|
||||
\item 募资与条款设计见前文各章;\textbf{冷启动期}更强调「可验证记录 + 合规路径 + 小步团队」,而非一步到位的大基金结构。
|
||||
\item 集团架构与跨境展业见第十四章(图 \ref{fig:holding-structure})及\textbf{合规篇};Networking 获得的外资意向须在\textbf{具体辖区规则}下重新评估。
|
||||
\end{itemize}
|
||||
|
||||
本章不提供「话术模板」式的募资脚本;若需要,应在持牌律师与合规审查后定制。冷启动的本质是:用\textbf{可审计的进步}替代\textbf{不可验证的叙事},用\textbf{精准触达}替代\textbf{噪音式群发},用\textbf{最小团队}撑到\textbf{合规壳与首关资金}同时就绪的那一刻。
|
||||
@@ -0,0 +1,54 @@
|
||||
\chapter{量化开发者合规路径:从灰色地带到持牌展业}
|
||||
|
||||
本章面向已具备相对稳定盈利策略组合的量化开发者,梳理从「借贷式资管」「代客理财」等非正规安排,过渡到在主要司法辖区合法管理外部资金的典型阶梯。\textbf{本书不构成法律意见};具体能否展业须由当地持牌律师结合资金性质、募集方式、客户类型与跨境因素认定。
|
||||
|
||||
\section{为何「借贷 / 配资 / 代客理财」本身风险高}
|
||||
|
||||
中国证监会投资者教育案例明确指出:证券从业人员\textbf{私下代客理财、约定收益分成},违反《证券法》第143--145条及经纪人相关规则;纠纷中投资者往往只能向个人追责。参见官方案例:\url{http://www.csrc.gov.cn/csrc/c100211/c1452037/content.shtml}。
|
||||
|
||||
实务中常与下列风险标签一并讨论(是否构成违法或犯罪须个案认定):面向\textbf{不特定对象}、公开或变相公开宣传、\textbf{保本保收益}承诺,可能触及非法集资类刑事风险;场外配资长期为监管打击重点——\textbf{结构违法则策略再稳也不安全}。
|
||||
|
||||
\section{对外资管的合规阶梯(概念模型)}
|
||||
|
||||
\begin{figure}[htbp]
|
||||
\centering
|
||||
\begin{tikzpicture}[node distance=1.15cm and 0.9cm]
|
||||
\node[compliance=purple] (A) {A. 自有或极窄关系户\\范围与账户出借、税务等仍须个案评估};
|
||||
\node[compliance=violet, below=of A] (B) {B. 入职持牌机构\\券商资管、公募、期货资管、银行理财子等};
|
||||
\node[compliance=blue!70!cyan, below=of B] (C) {C. 证券投资咨询机构\\证监会许可范围内;咨询 $\neq$ 代客全权委托};
|
||||
\node[compliance=teal, below=of C] (D) {D. 私募基金管理人\\中基协登记 + 产品备案(证券类)};
|
||||
\node[compliance=green!50!black, below=of D] (E) {E. 投顾输出(如 MOM)\\满足条件后向银行、信托、券商资管提供投顾服务};
|
||||
|
||||
\draw[compliance arrow] (A) -- (B);
|
||||
\draw[compliance arrow] (B) -- (C);
|
||||
\draw[compliance arrow] (C) -- (D);
|
||||
\draw[compliance arrow] (D) -- (E);
|
||||
|
||||
\begin{scope}[on background layer]
|
||||
\node[fill=gray!5, rounded corners=14pt, inner sep=14pt, yshift=-2pt,
|
||||
fit=(A)(E), draw=gray!40, dashed] {};
|
||||
\end{scope}
|
||||
\end{tikzpicture}
|
||||
\caption{量化开发者常见合规演进阶梯(示意,非唯一路径)}
|
||||
\label{fig:quant-ladder}
|
||||
\end{figure}
|
||||
|
||||
对已能稳定盈利的策略组合,实务上常见顺序是:\textbf{B} 验证规模与合规耐受 $\rightarrow$ 有出资人与团队后再上 \textbf{D};若主要输出「研究/模型」而非直接管账,可重点评估 \textbf{C/E} 与合同边界。
|
||||
|
||||
\section{中国大陆:私募登记与系统入口}
|
||||
|
||||
根据证监会公开的流程说明,私募基金管理人应向\textbf{基金业协会}办理登记,基金募集完毕后办理备案;系统为 \textbf{AMBERS}:\url{https://ambers.amac.org.cn}。流程索引:\url{http://www.csrc.gov.cn/csrc/c101939/c1045416/content.shtml}。《私募投资基金监督管理暂行办法》第九条明确:协会登记备案\textbf{不构成}对管理人投资能力、持续合规的认可,\textbf{也不保证}基金财产安全。
|
||||
|
||||
《私募投资基金登记备案办法》(中基协发〔2023〕5号)及配套指引下,市场常讨论的硬性方向包括(以协会\textbf{最新材料清单}为准):私募基金管理人实缴货币资本不低于\textbf{1000万元人民币}(或等值可自由兑换货币)等;证券类负责投资的高管常需满足\textbf{最近5年内连续2年以上}担任基金经理或投资决策负责人等,且单只产品管理规模不低于\textbf{2000万元}等证明要求;合规风控负责人须具备合规、风控、法律、会计等相关经验;近年内监管处罚、重大风险机构任职等构成负面情形。
|
||||
|
||||
\section{美国(简要锚点)}
|
||||
|
||||
管理外部客户资产(含多数对冲基金架构)常需注册为 \textbf{Investment Adviser},提交 \textbf{Form ADV},通过 \textbf{IARD}。SEC 与州管辖取决于 AUM 等(\textbf{Rule 203A-1} 等)。入口参考:\url{https://www.sec.gov/divisions/investment/iaregulation/regia.htm}。策略以期货/互换为主时可能叠加 \textbf{CFTC/NFA}。
|
||||
|
||||
\section{新加坡(简要锚点)}
|
||||
|
||||
从事《证券与期货法》(SFA)项下基金管理,须申请持有 \textbf{CMS} 牌照的 \textbf{LFMC};仅管理风投基金可申请简化 \textbf{VCFM}。MAS 官方说明含豁免情形、Form 1、eLicensing、审批约6个月、年费 S\$4{,}000 等:\url{https://www.mas.gov.sg/regulation/capital-markets/apply-for-licensing-or-registration-of-capital-market-entities/fund-management-licensing}。
|
||||
|
||||
\section{与集团架构、分工条款的衔接}
|
||||
|
||||
「母公司—子公司」主要服务治理、合同主体与税务筹划讨论,\textbf{不能替代}管理人登记/产品备案或相应辖区资管牌照。「技术方只写策略、资金方承担一切」在协会材料中,投资或高管岗位仍可能被审核;任职与对外披露须由律师设计。
|
||||
@@ -0,0 +1,43 @@
|
||||
\chapter{中国大陆:私募登记、代客理财边界与系统流程}
|
||||
|
||||
\section{监管依据与流程入口}
|
||||
|
||||
《私募投资基金监督管理暂行办法》第七条、第八条要求各类私募基金管理人根据\textbf{基金业协会}规定申请\textbf{登记},私募基金募集完毕后办理\textbf{备案}。证监会办事说明:\url{http://www.csrc.gov.cn/csrc/c101939/c1045416/content.shtml}。资产管理综合报送平台(\textbf{AMBERS}):\url{https://ambers.amac.org.cn}。
|
||||
|
||||
\begin{figure}[htbp]
|
||||
\centering
|
||||
\begin{tikzpicture}[
|
||||
font=\small,
|
||||
box/.style={compliance=#1, minimum width=3.6cm, minimum height=1.05cm},
|
||||
]
|
||||
\node[box=orange] (law) {行政法规与证监会规章\\《暂行办法》等};
|
||||
\node[box=blue, right=1.8cm of law] (amac) {基金业协会\\管理人登记 + 基金备案};
|
||||
\node[box=teal, right=1.8cm of amac] (amber) {AMBERS 报送平台\\材料提交与公示};
|
||||
|
||||
\draw[compliance arrow] (law) -- node[above, font=\scriptsize] {授权/自律} (amac);
|
||||
\draw[compliance arrow] (amac) -- node[above, font=\scriptsize] {电子化} (amber);
|
||||
|
||||
\node[below=0.55cm of amac, text width=8.5cm, align=center, font=\scriptsize, text=gray!70!black] {第九条:登记备案不构成对投资能力的认可,不构成对基金财产安全的保证。};
|
||||
\end{tikzpicture}
|
||||
\caption{中国私募:法律依据、协会职能与报送系统关系(示意)}
|
||||
\label{fig:chn-amac-flow}
|
||||
\end{figure}
|
||||
|
||||
\section{与「代客理财」的区分}
|
||||
|
||||
正规证券公司资产管理业务以\textbf{公司}为主体签署资管合同;从业人员个人与客户私下签约或全权操作账户,属违规代客理财。投资者教育案例:\url{http://www.csrc.gov.cn/csrc/c100211/c1452037/content.shtml}。
|
||||
|
||||
\section{登记备案办法下的常见硬性方向}
|
||||
|
||||
以下摘自公开解读与协会配套规则中常被引用的方向,\textbf{具体数字与表格以协会最新《登记申请材料清单》及有效规则文本为准}:
|
||||
|
||||
\begin{itemize}
|
||||
\item 私募基金管理人实缴货币资本不低于 \textbf{1000万元人民币}(或等值可自由兑换货币)等要求;
|
||||
\item 证券类私募:负责投资的高管常需 \textbf{最近5年内连续2年以上} 相关投资管理经验,单只产品管理规模不低于 \textbf{2000万元} 等证明材料;
|
||||
\item \textbf{合规风控负责人} 应具备合规、风控、法律、会计等相关工作经验;
|
||||
\item 近年内被采取行政监管措施、协会纪律处分、重大负面机构任职等,可能构成障碍。
|
||||
\end{itemize}
|
||||
|
||||
\section{证券投资咨询与私募管理人的关系}
|
||||
|
||||
两者为不同监管线条:投资咨询机构从事证监会许可的咨询业务;私募基金管理人在中基协登记后管理私募基金。向银行理财子、信托、券商资管等提供\textbf{投资顾问}服务,需满足另行规定的条件(如备案年限、人员「3+3」等,以当时有效规则为准)。两类业务人员兼任限制须严格遵守监管规定。
|
||||
@@ -0,0 +1,54 @@
|
||||
\chapter{全球区域合规总览:CHN、IND、EMEA、AMER 与亚太}
|
||||
|
||||
对外管理客户资金或设立基金时,须先厘清:\textbf{谁在决策}(全权委托 / 建议 / 纯软件)、\textbf{是否集合投资}、\textbf{客户是否零售}、\textbf{募集与销售是否跨境}、\textbf{是否涉及衍生品或高杠杆}。下列为各区域\textbf{框架性}摘要,实施须结合当地律师意见。
|
||||
|
||||
\begin{figure}[htbp]
|
||||
\centering
|
||||
\begin{tikzpicture}[scale=0.98, every node/.style={transform shape}]
|
||||
% 装饰性背景
|
||||
\begin{scope}[on background layer]
|
||||
\fill[blue!4] (-5.6,-3.2) rectangle (5.6,3.4);
|
||||
\draw[decorate, decoration={snake, amplitude=0.6pt, segment length=8pt}, gray!30]
|
||||
(-5.3,0) -- (5.3,0);
|
||||
\end{scope}
|
||||
|
||||
\node[fancy ring=red!75!black, fill=red!10, minimum size=2.9cm] (CHN) at (-3,1.6) {CHN\\\footnotesize 中基协登记};
|
||||
\node[fancy ring=orange!80!black, fill=orange!8, minimum size=2.9cm] (IND) at (3,1.6) {IND\\\footnotesize SEBI AIF};
|
||||
\node[fancy ring=blue!70!black, fill=blue!6, minimum size=2.9cm] (EMEA) at (-3,-1.5) {EMEA\\\footnotesize AIFMD / FCA};
|
||||
\node[fancy ring=teal!70!black, fill=teal!6, minimum size=2.9cm] (AMER) at (3,-1.5) {AMER\\\footnotesize SEC RIA / IFM};
|
||||
|
||||
\draw[compliance arrow, shorten >=2pt, shorten <=2pt] (CHN) -- (EMEA);
|
||||
\draw[compliance arrow, shorten >=2pt, shorten <=2pt] (IND) -- (AMER);
|
||||
\draw[dashed, gray!50, thick] (CHN) -- (IND);
|
||||
\draw[dashed, gray!50, thick] (EMEA) -- (AMER);
|
||||
|
||||
\node[font=\scriptsize, text=gray!60!black, align=center] at (0,3.05)
|
||||
{跨境募集时,四象限规则可能\textbf{同时}适用 $\Rightarrow$ 须逐司法辖区确认};
|
||||
\end{tikzpicture}
|
||||
\caption{主要区域监管锚点示意(非地理比例;中东、非洲、拉美须单列国别)}
|
||||
\label{fig:global-quad}
|
||||
\end{figure}
|
||||
|
||||
\section{欧盟(EU):AIFMD 与 AIFMD II}
|
||||
|
||||
管理欧盟内另类投资基金(AIF)或向欧盟投资者营销,通常需取得成员国主管机关(NCA)授权的 \textbf{AIFM},并遵守资本、托管、杠杆、流动性、报告及委托管理等要求。\textbf{AIFMD II} 已于2024年生效,成员国转置与全面适用存在过渡期(公开材料常见表述约至2026年中,以各国立法为准)。授权材料趋向更细:人员、委托/再委托、技术资源、尽调程序等。欧盟委员会立法索引:\url{https://finance.ec.europa.eu/regulation-and-supervision/financial-services-legislation/implementing-and-delegated-acts/alternative-investment-fund-managers-directive_en}。
|
||||
|
||||
\section{英国(UK)}
|
||||
|
||||
脱欧后适用 \textbf{UK AIFM} 制度:\textbf{full-scope authorised}、\textbf{small authorised}、\textbf{small registered UK AIFM} 等路径门槛与义务不同。FCA 入口:\url{https://www.fca.org.uk/firms/aifmd-uk}。另:\textbf{MiFID} 投资业务、FCA 其他授权与「仅做 AIFM」为不同维度。
|
||||
|
||||
\section{印度(IND):SEBI AIF}
|
||||
|
||||
另类投资基金依 \textbf{SEBI (AIF) Regulations, 2012} 及后续 \textbf{Master Circular}、年度 \textbf{Circular} 更新;申请通过 \textbf{SIPortal} 等提交。2024 年起市场关注要点包括:Category I/II AIF 对参股公司股权设立负担的框架、关键投资团队\textbf{认证要求}等——以 \url{https://www.sebi.gov.in} 现行文件为准。证券类量化策略须与 \textbf{FPI/PMS} 等路径是否竞合一并论证。
|
||||
|
||||
\section{美国与加拿大(AMER 核心)}
|
||||
|
||||
\textbf{美国:} 管理外部客户资产常需 \textbf{RIA} 注册与 \textbf{Form ADV}(\textbf{IARD});联邦与州管辖取决于 AUM(\textbf{Rule 203A-1} 等)。\textbf{加拿大:} \textbf{NI 31-103} 协调各省注册,基金管理常涉及 \textbf{Investment Fund Manager (IFM)} 等类别,除非适用豁免。
|
||||
|
||||
\section{新加坡、香港、澳大利亚、日本(亚太摘录)}
|
||||
|
||||
\textbf{新加坡:} \textbf{CMS} 牌照下 \textbf{LFMC};\textbf{VCFM} 针对风投基金;\textbf{SFA 第99条} 等豁免情形见 MAS 页面。\textbf{香港:} 组合资产管理常见证监会 \textbf{第9类}牌照,通常至少两名 \textbf{RO}。\textbf{澳大利亚:} 面向客户的 \textbf{AFSL};批发基金常配合 \textbf{Responsible Entity / MIS}。\textbf{日本:} \textbf{金商法} 下第二类登记等,投信/投助为另一线条。
|
||||
|
||||
\section{中东与非洲(示例)}
|
||||
|
||||
\textbf{迪拜 DIFC:} \textbf{DFSA} 监管,常见 \textbf{Fund Manager} 类别,可设面向合格投资者的 \textbf{QIF、Exempt Fund} 等(规则见 DFSA Rulebook)。\textbf{阿布扎比 ADGM、沙特 CMA} 等为独立框架。\textbf{非洲} 各国差异大(如南非 \textbf{FSCA} 体系),须国别附录,不可与欧盟或美国规则混用。
|
||||
@@ -0,0 +1,47 @@
|
||||
\chapter{EMEA 深度:AIFMD 栈、英国路径与中东枢纽}
|
||||
|
||||
\section{欧盟 AIFM 典型结构}
|
||||
|
||||
\begin{figure}[htbp]
|
||||
\centering
|
||||
\begin{tikzpicture}[node distance=0.85cm]
|
||||
\node[reg node=blue] (nca) {NCA\\成员国主管机关};
|
||||
\node[reg node=purple, below=of nca] (aifm) {AIFM\\另类投资管理人};
|
||||
\node[reg node=teal, below left=of aifm, xshift=-0.2cm] (aif) {AIF\\基金载体};
|
||||
\node[reg node=orange, below right=of aifm, xshift=0.2cm] (dep) {托管等\\(依规则)};
|
||||
\node[reg node=gray, below=2.1cm of aifm, minimum width=6.2cm] (inv) {合格/专业投资者(依基金类型与营销规则)};
|
||||
|
||||
\draw[compliance arrow] (nca) -- (aifm) node[midway, right, font=\scriptsize] {授权};
|
||||
\draw[compliance arrow] (aifm) -- (aif);
|
||||
\draw[compliance arrow] (aifm) -- (dep);
|
||||
\draw[compliance arrow] (aif) -- (inv);
|
||||
\draw[compliance arrow] (dep) -- (inv);
|
||||
|
||||
\begin{scope}[on background layer]
|
||||
\node[fill=blue!3, rounded corners=16pt, inner sep=16pt,
|
||||
fit=(nca)(aifm)(aif)(dep)(inv), draw=blue!25] {};
|
||||
\end{scope}
|
||||
\end{tikzpicture}
|
||||
\caption{欧盟语境下 AIFM--AIF--投资者关系(高度简化;不含 NPPR、护照等营销细节)}
|
||||
\label{fig:aifmd-stack}
|
||||
\end{figure}
|
||||
|
||||
\section{AIFMD II 动向(摘要)}
|
||||
|
||||
授权申请须提供更充分的人员、委托链、技术资源与尽调安排描述;部分规则对\textbf{流动性管理工具、杠杆披露、监管报告}等提出强化要求。实施日期以各成员国完成转置后的适用日为准。
|
||||
|
||||
\section{英国:注册与授权 AIFM}
|
||||
|
||||
\textbf{Small registered UK AIFM} 适用于特定窄类情形(如部分封闭式投资信托、小型房地产 AIFM、社会企业/风险资本标签基金等),监管负担轻于 full-scope,但仍须遵守 AIFMD 第3条等核心义务(报告等)。具体资格见 FCA「Apply to be a registered / authorised AIFM」页面。
|
||||
|
||||
\section{瑞士、卢森堡、爱尔兰}
|
||||
|
||||
瑞士 \textbf{CISA}、卢森堡/爱尔兰的 \textbf{AIF/UCITS} 载体常与欧盟营销路径(反向邀约、NPPR、护照)结合设计,须基金律师与税务顾问联合建模。
|
||||
|
||||
\section{中东:DIFC 与 DFSA}
|
||||
|
||||
迪拜国际金融中心(DIFC)由 \textbf{DFSA} 监管。市场常见表述包括:面向合格投资者的 \textbf{Qualified Investor Fund (QIF)}、\textbf{Exempt Fund} 等类型,以及 \textbf{Category 3C Fund Manager} 等牌照维度;最低认购额、资本与快速通道审批等以 \textbf{DFSA Rulebook} 与当时政策为准。\textbf{ADGM(阿布扎比)}、\textbf{沙特 CMA} 等为平行但独立的监管环境。
|
||||
|
||||
\section{非洲}
|
||||
|
||||
南非等国存在集合投资计划与金融服务提供商许可体系;其余国家规则分散。EMEA 报告中的「非洲」应做\textbf{国别表},不宜套用欧盟或英国条款。
|
||||
@@ -0,0 +1,36 @@
|
||||
\chapter{AMER:美国 RIA、商品池与加拿大 IFM}
|
||||
|
||||
\section{美国:投资顾问注册}
|
||||
|
||||
\begin{figure}[htbp]
|
||||
\centering
|
||||
\begin{tikzpicture}
|
||||
\node[compliance=teal, minimum width=4.2cm] (adv) {Form ADV\\IARD 电子申报};
|
||||
\node[compliance=blue, right=2.2cm of adv, minimum width=4.2cm] (sec) {SEC 或州监管\\视 AUM 与规则 203A-1};
|
||||
\path (adv) -- node[coordinate, midway] (midadvsec) {} (sec);
|
||||
\node[compliance=green!50!black, below=1.5cm of midadvsec, minimum width=8cm] (pf) {私募基金管理人:Schedule A/B 等披露\\对冲基金顾问额外信息(历史规则演进,以现行表格为准)};
|
||||
|
||||
\draw[compliance arrow] (adv) -- (sec) node[midway, above, font=\scriptsize] {注册/审查};
|
||||
\draw[compliance arrow] (adv.south) -- ++(0,-0.35) -| (pf.north);
|
||||
\draw[compliance arrow] (sec.south) -- ++(0,-0.35) -| (pf.north);
|
||||
|
||||
\node[font=\scriptsize, text width=9cm, align=center, below=0.35cm of pf]
|
||||
{持续义务:账簿记录、宣传材料、合规政策、年检与修正案等。};
|
||||
\end{tikzpicture}
|
||||
\caption{美国投资顾问注册与私募披露关系(概念图)}
|
||||
\label{fig:us-ria}
|
||||
\end{figure}
|
||||
|
||||
\textbf{要点:} 管理外部客户资产(含多数对冲基金架构)通常须注册为 \textbf{Investment Adviser};\textbf{仅管理自有资产}或符合 \textbf{Exempt Reporting Adviser} 等情形可能减轻部分披露,但不等于无监管。\textbf{CFTC/NFA:} 策略以期货、商品池为主时可能需另行注册。
|
||||
|
||||
官方入口示例:\url{https://www.sec.gov/divisions/investment/iaregulation/regia.htm};AUM 门槛与联邦/州切换见 \textbf{17 CFR §275.203A-1} 等(以现行有效文本为准)。
|
||||
|
||||
\section{加拿大:NI 31-103}
|
||||
|
||||
\textbf{National Instrument 31-103} 协调各省证券监管,涵盖投资交易商、顾问、\textbf{Portfolio Manager}、\textbf{Investment Fund Manager} 等类别。新设基金管理人通常须在开展业务前完成相应注册或确认豁免适用。各省证监会网站提供非官方汇编与政策更新。
|
||||
|
||||
示例索引:\url{https://www.osc.ca/en/securities-law/instruments-rules-policies/3/31-103/unofficial-consolidation-national-instrument-31-103-registration-requirements-exemptions-and}。
|
||||
|
||||
\section{拉丁美洲(摘要)}
|
||||
|
||||
\textbf{巴西 CVM:} 各类 \textbf{Fundo de Investimento} 须按类型注册并遵守治理与披露。\textbf{墨西哥} 等对基金与另类载体另有规定。\textbf{结论:} 拉美须\textbf{国别附录},不可与美国 RIA 路径混为一谈。
|
||||
@@ -0,0 +1,63 @@
|
||||
\chapter{亚太与印度细则、跨境三角与误区}
|
||||
|
||||
\section{新加坡 MAS}
|
||||
|
||||
\begin{itemize}
|
||||
\item 从事 SFA 项下\textbf{基金管理}:申请 \textbf{LFMC},持有 \textbf{CMS} 牌照。
|
||||
\item 仅管理\textbf{风投基金}:可考虑 \textbf{VCFM} 简化路径。
|
||||
\item \textbf{豁免:} 如仅为关联公司或关联家族管理资产、仅为合格/机构投资者管理不动产或非资本市场产品池等(见 SFA 第99条及《证券期货(牌照与业务操守)规例》附表二第5段等)。
|
||||
\item 官方:\url{https://www.mas.gov.sg/regulation/capital-markets/apply-for-licensing-or-registration-of-capital-market-entities/fund-management-licensing}
|
||||
\end{itemize}
|
||||
|
||||
页面载明:完整且符合条件的申请审查约 \textbf{6个月};持牌基金管理公司年费 \textbf{S\$4{,}000} 起(另加代表费用规则);须\textbf{专用安全办公场所}。
|
||||
|
||||
\section{香港证监会}
|
||||
|
||||
证券/期货组合资产管理通常需 \textbf{第9类(提供资产管理)}牌照;常见要求包括\textbf{两名负责人员(RO)}、香港公司或注册非香港公司、实体办公等。详见 \url{https://www.sfc.hk}。
|
||||
|
||||
\section{澳大利亚 ASIC}
|
||||
|
||||
面向客户提供金融产品建议或发行管理投资计划权益,通常需 \textbf{AFSL} 相应授权。\textbf{批发}基金面向批发投资者时,披露义务与零售不同,但仍须结构合规(如 \textbf{Responsible Entity} 角色)。具体授权范围由律师与 ASIC 指南对齐。
|
||||
|
||||
\section{日本}
|
||||
|
||||
\textbf{金融商品交易法}下第二类金融商品交易业登记等;公募投信、投资助言、私募领域线条各异,须日文合规资料与本地律师。
|
||||
|
||||
\section{印度 SEBI AIF(补充)}
|
||||
|
||||
\textbf{Category I / II / III} 在杠杆、投资范围、披露上不同;2024 年前后市场关注:股权负担框架、关键投资团队\textbf{认证}过渡期等。申请费、表格与 First Schedule 要求以 SEBI 现行规则与 \textbf{SIPortal} 流程为准。
|
||||
|
||||
\section{跨境:三地司法辖区三角}
|
||||
|
||||
\begin{figure}[htbp]
|
||||
\centering
|
||||
\begin{tikzpicture}[scale=1.02, every node/.style={transform shape}]
|
||||
\coordinate (top) at (90:2.5);
|
||||
\coordinate (lr) at (-30:2.5);
|
||||
\coordinate (ll) at (210:2.5);
|
||||
|
||||
\fill[teal!8] (top) -- (lr) -- (ll) -- cycle;
|
||||
\draw[thick, teal!60!black] (top) -- (lr) -- (ll) -- cycle;
|
||||
|
||||
\node[compliance=teal, minimum width=3.4cm] at (top) {管理人\\注册地法律};
|
||||
\node[compliance=blue, minimum width=3.4cm] at (lr) {基金载体\\注册地};
|
||||
\node[compliance=orange, minimum width=3.4cm] at (ll) {投资者\\国籍/所在地};
|
||||
|
||||
\node[font=\scriptsize, text width=7.8cm, align=center] at (0,-0.15)
|
||||
{仅在一地拿牌 \textbf{不自动}覆盖向他国居民募资或远程下单;\\欧盟护照、NPPR、私募配售等均为\textbf{程序工具},非「全球免牌」。};
|
||||
\end{tikzpicture}
|
||||
\caption{跨境资管常须分别评估的三类连接点}
|
||||
\label{fig:cross-border-triangle}
|
||||
\end{figure}
|
||||
|
||||
\section{常见误区}
|
||||
|
||||
\begin{enumerate}
|
||||
\item \textbf{技术中立:} 代码与回测本身通常不单独触发牌照;一旦绑定\textbf{客户资金、下单权或基金份额募集},即进入各辖区证券/基金法。
|
||||
\item \textbf{合同规避:} 名为「借贷」「合作」「分成」但实质为面向不特定对象的保本或全权委托,仍可能被重新定性。
|
||||
\item \textbf{集团架构:} 母公司--子公司可服务治理与税务讨论,\textbf{不能替代}管理人资格与产品注册/备案。
|
||||
\end{enumerate}
|
||||
|
||||
\section{维护与更新}
|
||||
|
||||
规则持续变化(如 \textbf{AIFMD II}、\textbf{SEBI AIF} 年度通函、\textbf{SEC} 对顾问门槛的审议等)。本书内容反映整理时的公开材料;正式展业前须以\textbf{监管机构官网与律师备忘录}为准并标注检索日期。
|
||||
@@ -0,0 +1,48 @@
|
||||
\chapter{Web3 技术纵深:账本、共识、智能合约与市场原语}
|
||||
|
||||
\section{分布式账本与密码学基础}
|
||||
|
||||
\textbf{分布式账本技术(DLT)}指多节点共同维护、通过密码学与协议规则达成一致的数据结构,不必依赖单一中心化记账方。\textbf{公链}(如比特币、以太坊及众多 Layer~1)向任意满足规则的参与者开放验证与交易广播;\textbf{联盟链/私有链}则限制节点集合,更贴近机构间清算与供应链场景。密码学工具包括:哈希链保证篡改可检测、非对称签名保证授权与不可否认、默克尔树支撑轻客户端验证、零知识证明(ZKP)在隐私与扩容方案中日益重要。理解这些构件有助于区分「真正链上结算」与仅借用区块链品牌营销的中心化数据库。
|
||||
|
||||
\section{共识、结算与最终性}
|
||||
|
||||
\textbf{工作量证明(PoW)}通过算力竞争出块,能源与安全假设明确;\textbf{权益证明(PoS)}以质押与罚没(slashing)约束验证者行为,资本效率与委托结构不同。\textbf{最终性(finality)}指交易被撤销的概率足够低的时间点——不同链的确认数与重组风险不同,跨链与交易所入账规则必须与之对齐。\textbf{Layer~2(Rollup 等)}将执行从主链外移,通过欺诈证明或有效性证明(ZK-Rollup)向 L1 提交状态承诺,在吞吐与费用上折中,但引入排序器(sequencer)中心化与桥接风险等新攻击面。
|
||||
|
||||
\section{账户模型、钱包与托管}
|
||||
|
||||
以太坊等采用\textbf{账户模型}(EOA 合约账户);比特币为\textbf{UTXO} 模型。钱包按密钥控制方式分为\textbf{自托管}(用户持有私钥或助记词)与\textbf{托管钱包}(交易所/银行代持)。机构场景下常见 \textbf{MPC}(多方计算)、\textbf{HSM} 与策略引擎组合,以满足职责分离与交易审批。链上地址不等于实名身份;合规上的「谁最终控制资产」须结合托管协议与链下 KYC 映射。
|
||||
|
||||
\section{智能合约与可组合性}
|
||||
|
||||
\textbf{智能合约}是在链上虚拟机中执行的确定性程序;升级机制(代理合约、多签治理)与\textbf{权限角色}(owner、admin、pause)直接决定资产安全。以太坊生态的\textbf{可组合性}允许协议在无许可情况下相互调用,加速创新也放大\textbf{级联清算}与漏洞传染风险。常见攻击类型包括:重入、预言机操纵、闪电贷辅助套利、跨链桥逻辑错误、治理劫持等——技术尽调须阅读审计报告与链上权限而不仅看白皮书。
|
||||
|
||||
\section{Token 标准与资产表示}
|
||||
|
||||
\textbf{同质化代币}(如 ERC-20)适合份额与积分式权利;\textbf{NFT}(如 ERC-721/1155)适合唯一凭证与 RWA(实物资产)映射中的编号权利。\textbf{包装资产}(如封装 BTC)依赖托管方与铸造/销毁规则,信用风险回到链下对手方。\textbf{治理代币}的经济与法律属性高度情境化:可能仅投票工具,也可能因募资方式被认定为证券(见下一章)。
|
||||
|
||||
\section{DeFi 核心原语(金融市场视角)}
|
||||
|
||||
\textbf{自动做市商(AMM)}用曲线函数替代订单簿,流动性由 LP 提供,承担无常损失与智能合约风险。\textbf{超额抵押借贷}(如稳定币铸造、循环杠杆)依赖清算机器人与预言机价格;参数(LTV、清算罚金)决定系统性压力下的级联。\textbf{稳定币机制}包括法币储备型、加密抵押型、算法型(历史波动极大);各自脱锚与储备透明度是监管焦点。\textbf{预言机}将链外价格与事件喂入合约,是操纵与 MEV(最大可提取价值)争夺的关键环节——排序器、区块构建者与搜索者之间的利益结构,使「链上公平」成为工程与经济学交叉问题。
|
||||
|
||||
\section{跨链桥与互操作性}
|
||||
|
||||
桥将资产或消息在链间转移,技术路径包括锁定-铸造、流动性网络、轻客户端验证等。\textbf{桥}历史上是高损事件集中区:合约漏洞、多签私钥泄露、经济攻击(虚假消息)均可导致巨额盗取。机构参与跨链头寸须单独进行风险限额与应急演练。
|
||||
|
||||
\begin{figure}[htbp]
|
||||
\centering
|
||||
\begin{tikzpicture}[font=\footnotesize, node distance=0.55cm]
|
||||
\node[reg node=violet, minimum width=6.8cm] (app) {应用层:钱包、聚合器、RWA 门户、游戏};
|
||||
\node[reg node=blue, below=of app, minimum width=6.8cm] (defi) {DeFi:DEX、借贷、稳定币、收益聚合、衍生品协议};
|
||||
\node[reg node=teal, below=of defi, minimum width=6.8cm] (l2) {执行与互操作:L2 / Rollup、桥、预言机、MEV 基础设施};
|
||||
\node[reg node=orange, below=of l2, minimum width=6.8cm] (l1) {结算层:L1 共识与数据可用性};
|
||||
\draw[compliance arrow] (app) -- (defi);
|
||||
\draw[compliance arrow] (defi) -- (l2);
|
||||
\draw[compliance arrow] (l2) -- (l1);
|
||||
\end{tikzpicture}
|
||||
\caption{Web3 协议栈简化分层(示意;各项目实际架构可能跨层)}
|
||||
\label{fig:web3-stack}
|
||||
\end{figure}
|
||||
|
||||
\section{与本书其他部分的衔接}
|
||||
|
||||
技术理解是阅读监管与市场章节的前提:同一「代币」在不同层(支付工具、治理凭证、收益权、抵押品)上的功能组合,会改变法律定性。量化与系统化团队若参与链上策略,还需将\textbf{节点延迟、Gas、回滚、预言机刷新频率}纳入与传统资产不同的执行模型。
|
||||
@@ -0,0 +1,54 @@
|
||||
\chapter{加密资产监管与政策:法域逻辑与执法难点}
|
||||
|
||||
\textbf{本章不构成法律意见。}加密规则演进极快,正式展业须以各辖区\textbf{成文法、监管规则与律师意见}为准,并标注检索日期。
|
||||
|
||||
\section{欧盟 MiCA:统一框架与稳定币分层}
|
||||
|
||||
欧盟《加密资产市场监管条例》(Markets in Crypto-Assets, \textbf{MiCA},条例编号如 2023/1114)将加密资产定义为可用分布式账本或类似技术以电子方式转移或存储的价值或权利的数字表示,并针对\textbf{其他金融服务立法未涵盖}的加密资产与服务建立统一规则。欧盟委员会概述与立法时间线见:\url{https://finance.ec.europa.eu/digital-finance/crypto-assets_en};全文见 EUR-Lex:\url{https://eur-lex.europa.eu/legal-content/EN/ALL/?uri=CELEX:32023R1114}。
|
||||
|
||||
监管逻辑上,MiCA 对\textbf{资产参考代币(ART)}与\textbf{电子货币代币(EMT)}类稳定币发行人设定更严格的授权、储备、赎回与治理义务;对\textbf{其他加密资产}的发行与营销设定信息披露与白皮书制度;对\textbf{加密资产服务提供商(CASP)}(交易、托管、撮合、顾问等)设定许可与运营要求,并由 ESMA 与各国主管机关协调监督。MiCA 与支付服务、反洗钱(AML)及\textbf{DORA}(数字运营韧性)等规则在机构层面交叉——持牌金融机构参与链上业务时,常需同时满足多条欧盟立法。
|
||||
|
||||
\section{美国:证券与商品的「双头监管」叙事}
|
||||
|
||||
在美国,同一数字资产是否构成\textbf{证券}(归 SEC 管辖的投资合同分析,常引用 \textbf{Howey} 判例标准:金钱投资、共同事业、对他人努力的合理期待利润)与是否属于\textbf{商品}(CFTC 对特定虚拟货币的界定与衍生品监管)长期存在事实与法律争议。\textbf{现货}加密交易平台、经纪与托管是否需注册为经纪交易商、交易所或适用州级\textbf{货币传输(money transmission)}许可,取决于具体商业模式与资产分类。近年立法与执法动向频繁(包括稳定币专项立法讨论、ETF 批准条件、执法和解与规则提案),本书无法逐条固化;读者应订阅 SEC、CFTC、FinCEN、OCC、美联储与州监管机构更新,并由美国证券律师出具产品备忘录。
|
||||
|
||||
\section{稳定币与支付稳定币:政策焦点}
|
||||
|
||||
稳定币连接\textbf{链上 DeFi}与\textbf{链下法币体系},政策关切集中在:储备资产质量与透明度、赎回压力下的流动性、发行人破产时持有人顺位、系统性重要性(多链与多应用共用同一稳定币)、以及 AML/制裁合规。欧盟 MiCA 对 ART/EMT 的强化规则即体现该思路。美国层面是否存在联邦统一框架及与州法的优先关系,以\textbf{当时已签署生效的联邦法与实施细则}为准——设计产品者须做「双轨」合规评估而非依赖单一博客解读。
|
||||
|
||||
\section{FATF 与「旅行规则」}
|
||||
|
||||
金融行动特别工作组(FATF)对虚拟资产服务提供商(VASP)提出与银行类似的\textbf{AML/CFT} 期望,包括\textbf{旅行规则(Travel Rule)}:在转账超过阈值时交换并记录发起方与受益方信息。各国转化程度不同,但机构级托管、交易所与链上分析工具已广泛部署交易监控与地址标注。DeFi 无许可接口与自托管钱包使\textbf{合规边界}模糊,成为国际讨论中的难点。
|
||||
|
||||
\section{亚太摘录}
|
||||
|
||||
\textbf{香港:}虚拟资产交易平台(VATP)等事项由证监会(SFC)等机关规管;规则与持牌名单以官网为准:\url{https://www.sfc.hk/en/Rules-and-standards/Virtual-assets/Virtual-asset-trading-platforms-operators}。实务上常同时涉及证券型代币与非证券型代币业务线条,申请与持续合规成本较高。
|
||||
|
||||
\textbf{新加坡:}支付型数字代币服务可能落入\textbf{PSA} 下牌照与豁免框架;证券型代币仍适用《证券期货法》。MAS 对零售加密营销与稳定币监管持续收紧,须查阅现行指南。
|
||||
|
||||
\textbf{中国大陆:}对虚拟货币交易炒作、代币发行融资等采取严格限制立场;境内机构与个人参与境外平台与代币发行仍可能涉及多重法律风险。本书读者若以人民币区为主要基地,须单独取得境内律师书面意见。
|
||||
|
||||
\section{DeFi、DAO 与「无许可」执法难题}
|
||||
|
||||
协议可匿名部署,治理代币可全球分发,物理运营团队可分散司法辖区——监管机构可能选择\textbf{对可识别主体}(前端运营者、核心开发者、营销方、稳定币发行人、托管入口)执法,或通过\textbf{制裁与域名/基础设施封锁}施加压力。产品设计若假设「链上无监管」,常与\textbf{入口合规}(法币出入金、应用商店、API 供应商)冲突。
|
||||
|
||||
\begin{figure}[htbp]
|
||||
\centering
|
||||
\begin{tikzpicture}[font=\scriptsize]
|
||||
\node[ellipse, draw=blue!70, fill=blue!5, minimum width=3.6cm, minimum height=2.2cm, align=center] (eu) {\textbf{欧盟}\\MiCA/CASP\\ART/EMT};
|
||||
\node[ellipse, draw=teal!70, fill=teal!5, minimum width=3.6cm, minimum height=2.2cm, align=center, right=1.8cm of eu] (us) {\textbf{美国}\\SEC/CFTC\\州法/FinCEN};
|
||||
\path (eu) -- node[coordinate, midway] (midewus) {} (us);
|
||||
\node[ellipse, draw=orange!70, fill=orange!5, minimum width=3.6cm, minimum height=2.2cm, align=center, below=1.1cm of midewus] (ap) {\textbf{亚太}\\MAS/SFC 等\\VASP/PSA};
|
||||
\draw[compliance arrow, dashed] (eu) -- (us);
|
||||
\draw[compliance arrow, dashed] (eu.south) -- (ap.north west);
|
||||
\draw[compliance arrow, dashed] (us.south) -- (ap.north east);
|
||||
\node[below=0.15cm of ap, text width=8.5cm, align=center, text=gray!55!black]
|
||||
{跨境业务常触发\textbf{多法域并行适用};代币「标签」不改变经济实质认定。};
|
||||
\end{tikzpicture}
|
||||
\caption{监管地理示意(高度简化;非穷举)}
|
||||
\label{fig:reg-geo-web3}
|
||||
\end{figure}
|
||||
|
||||
\section{与私募、资管业务的接口}
|
||||
|
||||
若基金或管理人计划:配置加密现货/衍生品、投资代币化基金份额、运行节点或质押策略、或向 LP 提供链上收益产品,须在 LPA 与侧信中披露\textbf{策略边界与风险},并完成本书合规篇所述\textbf{管理人资格与产品注册/备案}在加密场景下的增量论证。代币化证券若属于「证券」,通常须遵循传统证券发行与交易平台规则,而非仅依赖「链上自治」叙事。
|
||||
@@ -0,0 +1,58 @@
|
||||
\chapter{加密金融市场:价格形成、机构参与与 RWA}
|
||||
|
||||
\section{现货、衍生品与价格发现}
|
||||
|
||||
加密市场\textbf{7×24}交易,现货价格由中心化交易所(CEX)订单簿与链上 DEX 共同发现;二者常存在\textbf{基差}与延迟套利空间。\textbf{永续合约}通过资金费率(funding)平衡多空,使价格锚定指数;极端行情下可能出现插针、清算瀑布与交易所风控停机。\textbf{期权与结构化产品}在机构参与度上升后逐步深化,但流动性仍集中于少数标的。量化策略须将\textbf{提币限制、API 限速、合约乘数、标记价格与指数成分}纳入与传统市场不同的回测假设。
|
||||
|
||||
\section{ETF、托管与主经纪}
|
||||
|
||||
部分司法辖区已批准与现货或期货挂钩的\textbf{上市基金产品},使传统经纪账户可获得敞口,但底层持有结构、申赎机制与税务处理与直接持币不同。\textbf{机构托管}分冷/温/热存储与保险安排;托管证明与链上储备证明(PoR)成为透明度竞争点。\textbf{主经纪/场外借贷}市场连接对冲基金与做市商,信用与抵押品估值(haircut)在波动期快速重定价——与 2008 年回购市场类似的\textbf{流动性螺旋}在加密周期中已多次出现。
|
||||
|
||||
\section{与宏观风险资产的相关性}
|
||||
|
||||
比特币等曾被叙事为「宏观独立」;实证上在部分阶段与\textbf{纳斯达克、流动性预期、美元实际利率}呈现阶段性正相关,在危机时刻亦可能同步下跌。稳定币市值与链上活动可作为\textbf{风险偏好与杠杆载体}的代理变量之一,但因果解读需谨慎。资产配置模型若引入加密,应明确\textbf{再平衡频率、最大回撤约束与杠杆上限}。
|
||||
|
||||
\section{链上数据与科学}
|
||||
|
||||
公开账本带来\textbf{链上分析}(地址聚类、交易所流入流出、矿工行为、质押队列、MEV 占比)新维度;与传统订单流结合可构建因子。挑战包括:地址标签噪声、混币与跨链剥离、以及重组与预言机异常。研究型团队常自建节点与延迟优化基础设施,成本与运维纳入管理费模型。
|
||||
|
||||
\section{稳定币在支付与链上金融中的角色}
|
||||
|
||||
稳定币在跨境汇款、新兴市场美元化工具、以及 DeFi 抵押与交易对中占据核心位置;其\textbf{利率传导}与\textbf{链下货币市场}(美债、回购)通过储备资产相连。监管对储备与赎回的要求将改变发行方盈利模式与 DeFi 可组合性——策略团队须跟踪主要稳定币的\textbf{储备披露与黑名单冻结能力}(合规需求与去中心化叙事的张力)。
|
||||
|
||||
\section{代币化现实世界资产(RWA)}
|
||||
|
||||
债券、货币基金、房地产与私人信贷等\textbf{上链}可缩短结算周期、扩大分销半径,但法律上须明确:代币持有人权利是\textbf{直接所有权}还是\textbf{信托/ SPV 份额},破产隔离如何实现,以及信息披露适用证券法抑或另类框架。机构试点常与\textbf{许可链 + 持牌托管 + 白名单投资者}组合,与公链无许可 DeFi 的融合度因法域而异。
|
||||
|
||||
\section{风险管理清单(机构视角)}
|
||||
|
||||
\begin{itemize}
|
||||
\item \textbf{对手方:}交易所、托管方、稳定币发行人、借贷对手、桥与预言机——集中度与信用限额。
|
||||
\item \textbf{操作:}私钥流程、链上多签、内部作恶、钓鱼与供应链攻击。
|
||||
\item \textbf{市场:}波动、流动性枯竭、负费率极端、稳定币脱锚。
|
||||
\item \textbf{合规:}制裁名单、旅行规则、税务申报、跨境营销限制。
|
||||
\item \textbf{模型:}回测过拟合、链上结构突变(协议升级、硬分叉、参数治理投票)。
|
||||
\end{itemize}
|
||||
|
||||
\begin{figure}[htbp]
|
||||
\centering
|
||||
\begin{tikzpicture}[font=\footnotesize]
|
||||
\node[reg node=blue] (spot) {现货 CEX/DEX};
|
||||
\node[reg node=teal, right=1.2cm of spot] (der) {衍生品\\永续/期权};
|
||||
\node[reg node=violet, right=1.2cm of der] (cust) {托管/ETF\\PoR/保险};
|
||||
\path (spot) -- node[coordinate, midway] (midspotder) {} (der);
|
||||
\node[reg node=orange, below=1.1cm of midspotder] (defi) {链上 DeFi\\借贷/AMM};
|
||||
\node[reg node=purple, below=1.1cm of cust] (rwa) {RWA/代币化};
|
||||
\draw[compliance arrow] (spot) -- (der);
|
||||
\draw[compliance arrow] (der) -- (cust);
|
||||
\draw[compliance arrow] (spot.south) -- ++(0,-0.35) -| (defi.north);
|
||||
\draw[compliance arrow] (cust.south) -- ++(0,-0.35) -| (rwa.north);
|
||||
\draw[dashed, gray!50] (defi.east) -- (rwa.west);
|
||||
\end{tikzpicture}
|
||||
\caption{加密金融市场主要模块与资金流联系(示意)}
|
||||
\label{fig:crypto-mkt-map}
|
||||
\end{figure}
|
||||
|
||||
\section{小结}
|
||||
|
||||
Web3 同时是\textbf{技术运动、监管对象与资本市场板块}:技术层决定攻击面与可组合性,政策层决定准入与营销边界,市场层决定流动性与风险溢价。本书合规篇的传统私募规则在「代币化募资、链上管理账户、跨境稳定币」等场景下均需\textbf{增量分析};建议在基金 PPM 与 DDQ 中单独设置\textbf{数字资产附录},并在冷启动触达材料中避免与技术白皮书相矛盾的收益承诺。
|
||||
@@ -0,0 +1,41 @@
|
||||
% TikZ 样式与颜色 — 合规章节共用
|
||||
\tikzset{
|
||||
compliance/.style args={#1}{
|
||||
rounded corners=10pt,
|
||||
draw=#1!70!black,
|
||||
thick,
|
||||
fill=#1!12,
|
||||
align=center,
|
||||
inner sep=10pt,
|
||||
font=\small,
|
||||
drop shadow={shadow xshift=1.2pt, shadow yshift=-1.2pt, fill=black!20},
|
||||
},
|
||||
compliance arrow/.style={
|
||||
-{Stealth[length=3mm]},
|
||||
thick,
|
||||
draw=gray!70,
|
||||
},
|
||||
reg node/.style args={#1}{
|
||||
rectangle,
|
||||
rounded corners=6pt,
|
||||
minimum width=2.8cm,
|
||||
minimum height=1cm,
|
||||
align=center,
|
||||
draw=#1!65!black,
|
||||
fill=#1!8,
|
||||
font=\footnotesize,
|
||||
},
|
||||
fancy ring/.style args={#1}{
|
||||
circle,
|
||||
draw=#1,
|
||||
line width=1.6pt,
|
||||
inner sep=6pt,
|
||||
align=center,
|
||||
font=\scriptsize\bfseries,
|
||||
},
|
||||
}
|
||||
|
||||
\newcommand{\ComplianceColorCHN}{red}
|
||||
\newcommand{\ComplianceColorIND}{orange}
|
||||
\newcommand{\ComplianceColorEMEA}{blue}
|
||||
\newcommand{\ComplianceColorAMER}{teal}
|
||||
@@ -0,0 +1,80 @@
|
||||
% 私募:从零到一 — 主文件(分章见 chapter/)
|
||||
% 编译:XeLaTeX 或 LuaLaTeX(需 ctex、tikz、hyperref)
|
||||
\documentclass[UTF8,openany]{ctexbook}
|
||||
|
||||
\usepackage{geometry}
|
||||
\geometry{a4paper, margin=2.4cm}
|
||||
|
||||
\usepackage{xcolor}
|
||||
\usepackage{hyperref}
|
||||
\hypersetup{
|
||||
colorlinks=true,
|
||||
linkcolor=teal!60!black,
|
||||
urlcolor=blue!60!black,
|
||||
citecolor=gray!60!black,
|
||||
}
|
||||
|
||||
\usepackage{tikz}
|
||||
\usetikzlibrary{
|
||||
arrows.meta,
|
||||
positioning,
|
||||
shapes.geometric,
|
||||
calc,
|
||||
backgrounds,
|
||||
fit,
|
||||
shadows,
|
||||
decorations.pathmorphing,
|
||||
}
|
||||
|
||||
\input{macros/tikz-compliance}
|
||||
|
||||
\title{私募:从零到一}
|
||||
\author{}
|
||||
\date{\today}
|
||||
|
||||
\begin{document}
|
||||
|
||||
\frontmatter
|
||||
\maketitle
|
||||
\tableofcontents
|
||||
|
||||
\mainmatter
|
||||
|
||||
\include{chapter/ch01}
|
||||
\include{chapter/ch02}
|
||||
\include{chapter/ch03}
|
||||
\include{chapter/ch04}
|
||||
\include{chapter/ch05}
|
||||
\include{chapter/ch06}
|
||||
\include{chapter/ch07}
|
||||
\include{chapter/ch08}
|
||||
\include{chapter/ch09}
|
||||
\include{chapter/ch10}
|
||||
\include{chapter/ch11}
|
||||
\include{chapter/ch12}
|
||||
\include{chapter/ch13}
|
||||
\include{chapter/ch14}
|
||||
\include{chapter/ch15}
|
||||
|
||||
\part{冷启动、团队与触达}
|
||||
|
||||
\include{chapter/ch16}
|
||||
|
||||
\part{合规与全球展业}
|
||||
|
||||
\include{chapter/ch17}
|
||||
\include{chapter/ch18}
|
||||
\include{chapter/ch19}
|
||||
\include{chapter/ch20}
|
||||
\include{chapter/ch21}
|
||||
\include{chapter/ch22}
|
||||
|
||||
\part{Web3 与加密金融市场}
|
||||
|
||||
\include{chapter/ch23}
|
||||
\include{chapter/ch24}
|
||||
\include{chapter/ch25}
|
||||
|
||||
\backmatter
|
||||
|
||||
\end{document}
|
||||
Reference in New Issue
Block a user