1048 lines
38 KiB
Plaintext
1048 lines
38 KiB
Plaintext
//+------------------------------------------------------------------+
|
|
//| simple-rsi-reversal.mq5 |
|
|
//| Simple RSI Reversal Self-Optimizer |
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
#property copyright "Simple RSI Reversal Self-Optimizer"
|
|
#property link ""
|
|
#property version "1.00"
|
|
#property strict
|
|
|
|
#include <Trade\Trade.mqh>
|
|
|
|
CTrade trade;
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Optimization Method Enum |
|
|
//+------------------------------------------------------------------+
|
|
enum ENUM_OPTIMIZATION_METHOD
|
|
{
|
|
OPT_GRID_SEARCH, // Exhaustive grid search
|
|
OPT_RANDOM_WALK, // Random walk exploration
|
|
OPT_UCB, // Upper Confidence Bound (Multi-Armed Bandit)
|
|
OPT_THOMPSON_SAMPLING // Thompson Sampling (Bayesian)
|
|
};
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Input Parameters |
|
|
//+------------------------------------------------------------------+
|
|
input group "=== General Settings ==="
|
|
input string TradingSymbol = "BTCUSD"; // Trading Symbol
|
|
input ENUM_TIMEFRAMES TimeFrame = PERIOD_M1; // Timeframe
|
|
input double LotSize = 0.01; // Lot Size
|
|
input int MagicNumber = 88000; // Magic Number
|
|
input int Slippage = 3; // Slippage
|
|
|
|
input group "=== Self-Optimization Settings ==="
|
|
input int OptimizationPeriodHours = 2; // Backtesting Period (Hours)
|
|
input int OptimizationPeriodMinutes = 0; // Additional Minutes (0-59)
|
|
input int OptimizationIntervalBars = 50; // Bars Between Optimizations
|
|
input int MinTradesForOptimization = 2; // Min Trades for Optimization
|
|
input bool EnableAutoOptimization = true; // Enable Auto Optimization
|
|
input double MinProfitabilityForKeep = 0.1; // Min Profitability % to Keep Parameters
|
|
input ENUM_OPTIMIZATION_METHOD OptimizationMethod = OPT_UCB; // Optimization Method
|
|
input double UCB_ExplorationFactor = 2.0; // UCB Exploration Factor (c)
|
|
input int MaxParameterArms = 50; // Max Parameter Arms to Track
|
|
|
|
input group "=== RSI Parameters (Initial/Range) ==="
|
|
input int RSI_Period_Start = 7; // RSI Period (Start)
|
|
input int RSI_Period_End = 21; // RSI Period (End)
|
|
input double RSI_Oversold_Start = 25.0; // RSI Oversold (Start)
|
|
input double RSI_Oversold_End = 35.0; // RSI Oversold (End)
|
|
input double RSI_Overbought_Start = 65.0; // RSI Overbought (Start)
|
|
input double RSI_Overbought_End = 75.0; // RSI Overbought (End)
|
|
|
|
input group "=== Exit Settings ==="
|
|
input int MaxBarsInTrade = 30; // Max Bars in Trade
|
|
input int MinBarsBeforeExit = 3; // Min Bars Before Exit
|
|
input bool ExitOnReversal = false; // Exit on Signal Reversal
|
|
input double MaxLossPercent = 0.5; // Max Loss % to Force Close
|
|
input double AdverseMoveThreshold = 0.15; // Adverse Move % to Trigger Exit
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Strategy Parameters Structure |
|
|
//+------------------------------------------------------------------+
|
|
struct StrategyParams
|
|
{
|
|
int rsi_period;
|
|
double rsi_oversold;
|
|
double rsi_overbought;
|
|
double profitability;
|
|
};
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Parameter Arm Structure (for Multi-Armed Bandit) |
|
|
//+------------------------------------------------------------------+
|
|
struct ParameterArm
|
|
{
|
|
StrategyParams params;
|
|
int pulls; // Number of times this arm was tested
|
|
double total_reward; // Cumulative reward
|
|
double mean_reward; // Average reward
|
|
double ucb_score; // UCB score for selection
|
|
datetime last_tested; // Last time this arm was tested
|
|
};
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Global Variables |
|
|
//+------------------------------------------------------------------+
|
|
StrategyParams current_params;
|
|
int rsi_handle = INVALID_HANDLE;
|
|
int atr_handle = INVALID_HANDLE;
|
|
int last_optimization_bar = 0;
|
|
int bars_since_optimization = 0;
|
|
|
|
// Multi-Armed Bandit variables
|
|
ParameterArm parameter_arms[];
|
|
int total_arm_pulls = 0;
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Expert initialization function |
|
|
//+------------------------------------------------------------------+
|
|
int OnInit()
|
|
{
|
|
// Initialize parameters
|
|
current_params.rsi_period = RSI_Period_Start;
|
|
current_params.rsi_oversold = RSI_Oversold_Start;
|
|
current_params.rsi_overbought = RSI_Overbought_Start;
|
|
current_params.profitability = 0.0;
|
|
|
|
// Create indicators
|
|
rsi_handle = iRSI(TradingSymbol, TimeFrame, current_params.rsi_period, PRICE_CLOSE);
|
|
atr_handle = iATR(TradingSymbol, TimeFrame, 14);
|
|
|
|
if(rsi_handle == INVALID_HANDLE || atr_handle == INVALID_HANDLE)
|
|
{
|
|
Print("ERROR: Failed to create indicators");
|
|
return INIT_FAILED;
|
|
}
|
|
|
|
// Set magic number
|
|
trade.SetExpertMagicNumber(MagicNumber);
|
|
trade.SetDeviationInPoints(Slippage);
|
|
trade.SetTypeFilling(ORDER_FILLING_FOK);
|
|
|
|
// Perform initial optimization
|
|
if(EnableAutoOptimization)
|
|
{
|
|
OptimizeStrategy();
|
|
}
|
|
|
|
Print("=== Simple RSI Reversal Strategy Initialized ===");
|
|
Print("RSI Period: ", current_params.rsi_period,
|
|
" Oversold: ", DoubleToString(current_params.rsi_oversold, 1),
|
|
" Overbought: ", DoubleToString(current_params.rsi_overbought, 1));
|
|
|
|
return INIT_SUCCEEDED;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Expert deinitialization function |
|
|
//+------------------------------------------------------------------+
|
|
void OnDeinit(const int reason)
|
|
{
|
|
if(rsi_handle != INVALID_HANDLE) IndicatorRelease(rsi_handle);
|
|
if(atr_handle != INVALID_HANDLE) IndicatorRelease(atr_handle);
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Expert tick function |
|
|
//+------------------------------------------------------------------+
|
|
void OnTick()
|
|
{
|
|
// Check if we need to optimize
|
|
if(EnableAutoOptimization)
|
|
{
|
|
int current_bar = iBars(TradingSymbol, TimeFrame);
|
|
if(current_bar > last_optimization_bar)
|
|
{
|
|
bars_since_optimization++;
|
|
if(bars_since_optimization >= OptimizationIntervalBars)
|
|
{
|
|
OptimizeStrategy();
|
|
bars_since_optimization = 0;
|
|
}
|
|
}
|
|
last_optimization_bar = current_bar;
|
|
}
|
|
|
|
// Run the strategy
|
|
RunStrategy();
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Optimize Strategy |
|
|
//+------------------------------------------------------------------+
|
|
void OptimizeStrategy()
|
|
{
|
|
Print("=== Starting Self-Optimization ===");
|
|
|
|
// Calculate backtesting period
|
|
datetime end_time = TimeCurrent();
|
|
int total_minutes = OptimizationPeriodHours * 60 + OptimizationPeriodMinutes;
|
|
datetime start_time = end_time - (total_minutes * 60);
|
|
|
|
Print("Backtesting period: Last ", OptimizationPeriodHours, " hour(s) (", total_minutes, " minutes total)");
|
|
Print("Period: ", TimeToString(start_time), " to ", TimeToString(end_time));
|
|
|
|
// Update current live profitability
|
|
current_params.profitability = CalculateStrategyProfitability();
|
|
Print("Current Live Profitability: ", DoubleToString(current_params.profitability, 2), "%");
|
|
|
|
// Test parameter combinations based on optimization method
|
|
StrategyParams best_params = current_params;
|
|
double best_profitability = current_params.profitability;
|
|
int tests_run = 0;
|
|
|
|
if(OptimizationMethod == OPT_GRID_SEARCH)
|
|
{
|
|
// Exhaustive grid search
|
|
best_params = OptimizeGridSearch(start_time, end_time, tests_run);
|
|
best_profitability = best_params.profitability;
|
|
}
|
|
else if(OptimizationMethod == OPT_RANDOM_WALK)
|
|
{
|
|
// Random walk exploration
|
|
best_params = OptimizeRandomWalk(start_time, end_time, tests_run);
|
|
best_profitability = best_params.profitability;
|
|
}
|
|
else if(OptimizationMethod == OPT_UCB)
|
|
{
|
|
// Upper Confidence Bound (Multi-Armed Bandit)
|
|
best_params = OptimizeUCB(start_time, end_time, tests_run);
|
|
best_profitability = best_params.profitability;
|
|
}
|
|
else if(OptimizationMethod == OPT_THOMPSON_SAMPLING)
|
|
{
|
|
// Thompson Sampling (Bayesian Multi-Armed Bandit)
|
|
best_params = OptimizeThompsonSampling(start_time, end_time, tests_run);
|
|
best_profitability = best_params.profitability;
|
|
}
|
|
|
|
Print("Optimization Results: Tests Run=", tests_run, " Current=",
|
|
DoubleToString(current_params.profitability, 2), "% Best Found=",
|
|
DoubleToString(best_profitability, 2), "%");
|
|
|
|
// Update parameters if better ones found
|
|
if(best_profitability > current_params.profitability + MinProfitabilityForKeep)
|
|
{
|
|
Print("Updating parameters to better set");
|
|
current_params = best_params;
|
|
|
|
// Recreate RSI indicator with new period
|
|
if(rsi_handle != INVALID_HANDLE) IndicatorRelease(rsi_handle);
|
|
rsi_handle = iRSI(TradingSymbol, TimeFrame, current_params.rsi_period, PRICE_CLOSE);
|
|
|
|
if(rsi_handle == INVALID_HANDLE)
|
|
{
|
|
Print("ERROR: Failed to recreate RSI indicator");
|
|
}
|
|
else
|
|
{
|
|
Print("Strategy Updated - RSI Period: ", current_params.rsi_period,
|
|
" Oversold: ", DoubleToString(current_params.rsi_oversold, 1),
|
|
" Overbought: ", DoubleToString(current_params.rsi_overbought, 1),
|
|
" Expected Profit: ", DoubleToString(best_profitability, 2), "%");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Print("Keeping current parameters. Current Profit: ",
|
|
DoubleToString(current_params.profitability, 2), "% Best Found: ",
|
|
DoubleToString(best_profitability, 2), "%");
|
|
}
|
|
|
|
Print("=== Self-Optimization Complete ===");
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Grid Search Optimization |
|
|
//+------------------------------------------------------------------+
|
|
StrategyParams OptimizeGridSearch(datetime start_time, datetime end_time, int &tests_run)
|
|
{
|
|
Print("Using GRID SEARCH optimization method");
|
|
StrategyParams best_params = current_params;
|
|
double best_profitability = current_params.profitability;
|
|
|
|
// Exhaustive grid search
|
|
for(int rsi_period = RSI_Period_Start; rsi_period <= RSI_Period_End; rsi_period += 2)
|
|
{
|
|
for(double oversold = RSI_Oversold_Start; oversold <= RSI_Oversold_End; oversold += 2.5)
|
|
{
|
|
for(double overbought = RSI_Overbought_Start; overbought <= RSI_Overbought_End; overbought += 2.5)
|
|
{
|
|
if(oversold >= overbought) continue;
|
|
|
|
StrategyParams test_params;
|
|
test_params.rsi_period = rsi_period;
|
|
test_params.rsi_oversold = oversold;
|
|
test_params.rsi_overbought = overbought;
|
|
|
|
double profitability = BacktestStrategy(test_params, start_time, end_time);
|
|
tests_run++;
|
|
|
|
if(profitability > best_profitability)
|
|
{
|
|
best_profitability = profitability;
|
|
best_params = test_params;
|
|
best_params.profitability = profitability;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return best_params;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Random Walk Optimization |
|
|
//+------------------------------------------------------------------+
|
|
StrategyParams OptimizeRandomWalk(datetime start_time, datetime end_time, int &tests_run)
|
|
{
|
|
Print("Using RANDOM WALK optimization method");
|
|
StrategyParams best_params = current_params;
|
|
double best_profitability = current_params.profitability;
|
|
|
|
// Start from current parameters
|
|
StrategyParams current = current_params;
|
|
int max_steps = 30; // Number of random steps
|
|
|
|
for(int step = 0; step < max_steps; step++)
|
|
{
|
|
// Random walk: small random changes to current parameters
|
|
StrategyParams test_params = current;
|
|
|
|
// Random walk in parameter space
|
|
int period_change = (MathRand() % 5) - 2; // -2 to +2
|
|
test_params.rsi_period = (int)MathMax(RSI_Period_Start,
|
|
MathMin(RSI_Period_End, current.rsi_period + period_change));
|
|
|
|
double oversold_change = (MathRand() % 11 - 5) * 0.5; // -2.5 to +2.5
|
|
test_params.rsi_oversold = MathMax(RSI_Oversold_Start,
|
|
MathMin(RSI_Oversold_End, current.rsi_oversold + oversold_change));
|
|
|
|
double overbought_change = (MathRand() % 11 - 5) * 0.5; // -2.5 to +2.5
|
|
test_params.rsi_overbought = MathMax(RSI_Overbought_Start,
|
|
MathMin(RSI_Overbought_End, current.rsi_overbought + overbought_change));
|
|
|
|
if(test_params.rsi_oversold >= test_params.rsi_overbought) continue;
|
|
|
|
double profitability = BacktestStrategy(test_params, start_time, end_time);
|
|
tests_run++;
|
|
|
|
// Accept if better, or with probability if worse (simulated annealing)
|
|
if(profitability > best_profitability)
|
|
{
|
|
best_profitability = profitability;
|
|
best_params = test_params;
|
|
best_params.profitability = profitability;
|
|
current = test_params; // Move to better position
|
|
}
|
|
else if(profitability > current.profitability)
|
|
{
|
|
current = test_params; // Accept improvement
|
|
}
|
|
// With small probability, accept worse (exploration)
|
|
else if(MathRand() % 100 < 10) // 10% chance
|
|
{
|
|
current = test_params; // Random exploration
|
|
}
|
|
}
|
|
|
|
return best_params;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| UCB (Upper Confidence Bound) Multi-Armed Bandit |
|
|
//+------------------------------------------------------------------+
|
|
StrategyParams OptimizeUCB(datetime start_time, datetime end_time, int &tests_run)
|
|
{
|
|
Print("Using UCB (Multi-Armed Bandit) optimization method");
|
|
|
|
// Initialize or update parameter arms
|
|
if(ArraySize(parameter_arms) == 0)
|
|
{
|
|
// First time: create initial arms from grid
|
|
InitializeParameterArms();
|
|
}
|
|
|
|
// Select arms to test using UCB
|
|
int arms_to_test = MathMin(20, ArraySize(parameter_arms)); // Test top 20 arms
|
|
|
|
for(int i = 0; i < arms_to_test; i++)
|
|
{
|
|
// Select arm with highest UCB score
|
|
int selected_arm = SelectUCBArm();
|
|
|
|
if(selected_arm < 0 || selected_arm >= ArraySize(parameter_arms)) break;
|
|
|
|
// Test this arm
|
|
double profitability = BacktestStrategy(parameter_arms[selected_arm].params, start_time, end_time);
|
|
tests_run++;
|
|
|
|
// Update arm statistics
|
|
parameter_arms[selected_arm].pulls++;
|
|
parameter_arms[selected_arm].total_reward += profitability;
|
|
parameter_arms[selected_arm].mean_reward = parameter_arms[selected_arm].total_reward /
|
|
parameter_arms[selected_arm].pulls;
|
|
parameter_arms[selected_arm].last_tested = TimeCurrent();
|
|
total_arm_pulls++;
|
|
|
|
// Update UCB score
|
|
UpdateUCBScores();
|
|
|
|
// Add new random arm occasionally (exploration)
|
|
if(MathRand() % 100 < 15 && ArraySize(parameter_arms) < MaxParameterArms) // 15% chance
|
|
{
|
|
AddRandomArm();
|
|
}
|
|
}
|
|
|
|
// Find best arm based on mean reward
|
|
int best_arm = 0;
|
|
double best_reward = parameter_arms[0].mean_reward;
|
|
for(int i = 1; i < ArraySize(parameter_arms); i++)
|
|
{
|
|
if(parameter_arms[i].pulls > 0 && parameter_arms[i].mean_reward > best_reward)
|
|
{
|
|
best_reward = parameter_arms[i].mean_reward;
|
|
best_arm = i;
|
|
}
|
|
}
|
|
|
|
StrategyParams result = parameter_arms[best_arm].params;
|
|
result.profitability = parameter_arms[best_arm].mean_reward;
|
|
|
|
return result;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Thompson Sampling (Bayesian Multi-Armed Bandit) |
|
|
//+------------------------------------------------------------------+
|
|
StrategyParams OptimizeThompsonSampling(datetime start_time, datetime end_time, int &tests_run)
|
|
{
|
|
Print("Using THOMPSON SAMPLING (Bayesian) optimization method");
|
|
|
|
// Initialize arms if needed
|
|
if(ArraySize(parameter_arms) == 0)
|
|
{
|
|
InitializeParameterArms();
|
|
}
|
|
|
|
int arms_to_test = MathMin(20, ArraySize(parameter_arms));
|
|
|
|
for(int i = 0; i < arms_to_test; i++)
|
|
{
|
|
// Select arm using Thompson Sampling
|
|
int selected_arm = SelectThompsonSamplingArm();
|
|
|
|
if(selected_arm < 0 || selected_arm >= ArraySize(parameter_arms)) break;
|
|
|
|
// Test this arm
|
|
double profitability = BacktestStrategy(parameter_arms[selected_arm].params, start_time, end_time);
|
|
tests_run++;
|
|
|
|
// Update Bayesian parameters (alpha, beta for Beta distribution)
|
|
// Normalize profitability to [0, 1] for Beta distribution
|
|
double normalized_reward = (profitability + 100.0) / 200.0; // Assume range [-100, 100]
|
|
normalized_reward = MathMax(0.0, MathMin(1.0, normalized_reward));
|
|
|
|
// Update arm statistics
|
|
parameter_arms[selected_arm].pulls++;
|
|
parameter_arms[selected_arm].total_reward += profitability;
|
|
parameter_arms[selected_arm].mean_reward = parameter_arms[selected_arm].total_reward /
|
|
parameter_arms[selected_arm].pulls;
|
|
parameter_arms[selected_arm].last_tested = TimeCurrent();
|
|
total_arm_pulls++;
|
|
|
|
// Add new random arm occasionally
|
|
if(MathRand() % 100 < 15 && ArraySize(parameter_arms) < MaxParameterArms)
|
|
{
|
|
AddRandomArm();
|
|
}
|
|
}
|
|
|
|
// Find best arm
|
|
int best_arm = 0;
|
|
double best_reward = parameter_arms[0].mean_reward;
|
|
for(int i = 1; i < ArraySize(parameter_arms); i++)
|
|
{
|
|
if(parameter_arms[i].pulls > 0 && parameter_arms[i].mean_reward > best_reward)
|
|
{
|
|
best_reward = parameter_arms[i].mean_reward;
|
|
best_arm = i;
|
|
}
|
|
}
|
|
|
|
StrategyParams result = parameter_arms[best_arm].params;
|
|
result.profitability = parameter_arms[best_arm].mean_reward;
|
|
|
|
return result;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Initialize Parameter Arms |
|
|
//+------------------------------------------------------------------+
|
|
void InitializeParameterArms()
|
|
{
|
|
ArrayResize(parameter_arms, 0);
|
|
|
|
// Create initial arms from grid (sparse sampling)
|
|
for(int rsi_period = RSI_Period_Start; rsi_period <= RSI_Period_End; rsi_period += 3)
|
|
{
|
|
for(double oversold = RSI_Oversold_Start; oversold <= RSI_Oversold_End; oversold += 5.0)
|
|
{
|
|
for(double overbought = RSI_Overbought_Start; overbought <= RSI_Overbought_End; overbought += 5.0)
|
|
{
|
|
if(oversold >= overbought) continue;
|
|
|
|
ParameterArm arm;
|
|
arm.params.rsi_period = rsi_period;
|
|
arm.params.rsi_oversold = oversold;
|
|
arm.params.rsi_overbought = overbought;
|
|
arm.params.profitability = 0.0;
|
|
arm.pulls = 0;
|
|
arm.total_reward = 0.0;
|
|
arm.mean_reward = 0.0;
|
|
arm.ucb_score = 999999.0; // High initial score for exploration
|
|
arm.last_tested = 0;
|
|
|
|
ArrayResize(parameter_arms, ArraySize(parameter_arms) + 1);
|
|
parameter_arms[ArraySize(parameter_arms) - 1] = arm;
|
|
|
|
if(ArraySize(parameter_arms) >= MaxParameterArms) break;
|
|
}
|
|
if(ArraySize(parameter_arms) >= MaxParameterArms) break;
|
|
}
|
|
if(ArraySize(parameter_arms) >= MaxParameterArms) break;
|
|
}
|
|
|
|
Print("Initialized ", ArraySize(parameter_arms), " parameter arms");
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Select Arm Using UCB |
|
|
//+------------------------------------------------------------------+
|
|
int SelectUCBArm()
|
|
{
|
|
if(ArraySize(parameter_arms) == 0) return -1;
|
|
|
|
int best_arm = 0;
|
|
double best_ucb = -999999.0;
|
|
|
|
for(int i = 0; i < ArraySize(parameter_arms); i++)
|
|
{
|
|
UpdateUCBScore(i);
|
|
if(parameter_arms[i].ucb_score > best_ucb)
|
|
{
|
|
best_ucb = parameter_arms[i].ucb_score;
|
|
best_arm = i;
|
|
}
|
|
}
|
|
|
|
return best_arm;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Update UCB Score for Single Arm |
|
|
//+------------------------------------------------------------------+
|
|
void UpdateUCBScore(int arm_index)
|
|
{
|
|
if(arm_index < 0 || arm_index >= ArraySize(parameter_arms)) return;
|
|
|
|
if(parameter_arms[arm_index].pulls == 0)
|
|
{
|
|
parameter_arms[arm_index].ucb_score = 999999.0; // Never pulled, high priority
|
|
}
|
|
else
|
|
{
|
|
// UCB formula: mean_reward + c * sqrt(ln(total_pulls) / pulls)
|
|
double exploration = UCB_ExplorationFactor * MathSqrt(MathLog(total_arm_pulls) / parameter_arms[arm_index].pulls);
|
|
parameter_arms[arm_index].ucb_score = parameter_arms[arm_index].mean_reward + exploration;
|
|
}
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Update All UCB Scores |
|
|
//+------------------------------------------------------------------+
|
|
void UpdateUCBScores()
|
|
{
|
|
for(int i = 0; i < ArraySize(parameter_arms); i++)
|
|
{
|
|
UpdateUCBScore(i);
|
|
}
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Select Arm Using Thompson Sampling |
|
|
//+------------------------------------------------------------------+
|
|
int SelectThompsonSamplingArm()
|
|
{
|
|
if(ArraySize(parameter_arms) == 0) return -1;
|
|
|
|
int best_arm = 0;
|
|
double best_sample = -999999.0;
|
|
|
|
for(int i = 0; i < ArraySize(parameter_arms); i++)
|
|
{
|
|
// Sample from Beta distribution (approximation)
|
|
// Beta(alpha, beta) where alpha = wins + 1, beta = losses + 1
|
|
double mean = parameter_arms[i].mean_reward;
|
|
double pulls = parameter_arms[i].pulls;
|
|
|
|
// Normalize mean to [0, 1]
|
|
double normalized_mean = (mean + 100.0) / 200.0;
|
|
normalized_mean = MathMax(0.01, MathMin(0.99, normalized_mean));
|
|
|
|
// Estimate alpha and beta
|
|
double alpha = normalized_mean * pulls + 1.0;
|
|
double beta = (1.0 - normalized_mean) * pulls + 1.0;
|
|
|
|
// Sample from Beta (simplified: use normal approximation)
|
|
double sample = normalized_mean + (MathRand() / 32767.0 - 0.5) * 0.2;
|
|
sample = MathMax(0.0, MathMin(1.0, sample));
|
|
|
|
// Convert back to profitability scale
|
|
sample = sample * 200.0 - 100.0;
|
|
|
|
if(sample > best_sample)
|
|
{
|
|
best_sample = sample;
|
|
best_arm = i;
|
|
}
|
|
}
|
|
|
|
return best_arm;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Add Random Parameter Arm |
|
|
//+------------------------------------------------------------------+
|
|
void AddRandomArm()
|
|
{
|
|
ParameterArm arm;
|
|
arm.params.rsi_period = (int)(RSI_Period_Start + MathRand() % (RSI_Period_End - RSI_Period_Start + 1));
|
|
arm.params.rsi_oversold = RSI_Oversold_Start +
|
|
(MathRand() % (int)((RSI_Oversold_End - RSI_Oversold_Start) * 10 + 1)) / 10.0;
|
|
arm.params.rsi_overbought = RSI_Overbought_Start +
|
|
(MathRand() % (int)((RSI_Overbought_End - RSI_Overbought_Start) * 10 + 1)) / 10.0;
|
|
|
|
if(arm.params.rsi_oversold >= arm.params.rsi_overbought) return;
|
|
|
|
arm.params.profitability = 0.0;
|
|
arm.pulls = 0;
|
|
arm.total_reward = 0.0;
|
|
arm.mean_reward = 0.0;
|
|
arm.ucb_score = 999999.0;
|
|
arm.last_tested = 0;
|
|
|
|
ArrayResize(parameter_arms, ArraySize(parameter_arms) + 1);
|
|
parameter_arms[ArraySize(parameter_arms) - 1] = arm;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Backtest Strategy |
|
|
//+------------------------------------------------------------------+
|
|
double BacktestStrategy(StrategyParams ¶ms, datetime start_time, datetime end_time)
|
|
{
|
|
// Create temporary RSI indicator for backtesting
|
|
int temp_rsi = iRSI(TradingSymbol, TimeFrame, params.rsi_period, PRICE_CLOSE);
|
|
if(temp_rsi == INVALID_HANDLE) return -999999.0;
|
|
|
|
double total_profit = 0.0;
|
|
int total_trades = 0;
|
|
int winning_trades = 0;
|
|
ulong virtual_position = 0;
|
|
double virtual_entry = 0;
|
|
datetime virtual_entry_time = 0;
|
|
ENUM_POSITION_TYPE virtual_position_type = WRONG_VALUE;
|
|
|
|
// Calculate how many bars we need
|
|
int period_seconds = PeriodSeconds(TimeFrame);
|
|
int bars_needed = (int)((end_time - start_time) / period_seconds) + 20;
|
|
|
|
// Get bars from end_time going backwards
|
|
int end_bar = iBarShift(TradingSymbol, TimeFrame, end_time, false);
|
|
if(end_bar < 0) end_bar = 0;
|
|
|
|
int start_bar = end_bar + bars_needed;
|
|
int max_bars = Bars(TradingSymbol, TimeFrame);
|
|
if(start_bar >= max_bars)
|
|
{
|
|
start_bar = max_bars - 1;
|
|
bars_needed = start_bar - end_bar;
|
|
}
|
|
|
|
int bars_to_test = start_bar - end_bar;
|
|
|
|
if(bars_to_test < 5)
|
|
{
|
|
IndicatorRelease(temp_rsi);
|
|
return -999999.0;
|
|
}
|
|
|
|
// Get data arrays
|
|
double rsi_buffer[];
|
|
double close_buffer[];
|
|
datetime time_buffer[];
|
|
ArraySetAsSeries(rsi_buffer, true);
|
|
ArraySetAsSeries(close_buffer, true);
|
|
ArraySetAsSeries(time_buffer, true);
|
|
|
|
// Copy data
|
|
if(CopyBuffer(temp_rsi, 0, end_bar, bars_to_test, rsi_buffer) < bars_to_test)
|
|
{
|
|
IndicatorRelease(temp_rsi);
|
|
return -999999.0;
|
|
}
|
|
if(CopyClose(TradingSymbol, TimeFrame, end_bar, bars_to_test, close_buffer) < bars_to_test)
|
|
{
|
|
IndicatorRelease(temp_rsi);
|
|
return -999999.0;
|
|
}
|
|
if(CopyTime(TradingSymbol, TimeFrame, end_bar, bars_to_test, time_buffer) < bars_to_test)
|
|
{
|
|
IndicatorRelease(temp_rsi);
|
|
return -999999.0;
|
|
}
|
|
|
|
// Iterate through historical bars (from oldest to newest)
|
|
for(int i = bars_to_test - 1; i >= 1; i--)
|
|
{
|
|
datetime bar_time = time_buffer[i];
|
|
double current_rsi = rsi_buffer[i];
|
|
double prev_rsi = rsi_buffer[i-1];
|
|
double current_price = close_buffer[i];
|
|
|
|
// Check existing virtual position
|
|
if(virtual_position > 0)
|
|
{
|
|
// Check exit conditions
|
|
int bars_held = (int)((bar_time - virtual_entry_time) / period_seconds);
|
|
|
|
// Time-based exit
|
|
if(bars_held >= MaxBarsInTrade)
|
|
{
|
|
double exit_price = current_price;
|
|
double profit = 0;
|
|
if(virtual_position_type == POSITION_TYPE_BUY)
|
|
profit = (exit_price - virtual_entry) / virtual_entry;
|
|
else
|
|
profit = (virtual_entry - exit_price) / virtual_entry;
|
|
|
|
total_profit += profit;
|
|
total_trades++;
|
|
if(profit > 0) winning_trades++;
|
|
|
|
virtual_position = 0;
|
|
}
|
|
// Signal reversal exit
|
|
else if(ExitOnReversal && bars_held >= MinBarsBeforeExit)
|
|
{
|
|
bool should_exit = false;
|
|
if(virtual_position_type == POSITION_TYPE_BUY && current_rsi > params.rsi_overbought)
|
|
should_exit = true;
|
|
else if(virtual_position_type == POSITION_TYPE_SELL && current_rsi < params.rsi_oversold)
|
|
should_exit = true;
|
|
|
|
if(should_exit)
|
|
{
|
|
double exit_price = current_price;
|
|
double profit = 0;
|
|
if(virtual_position_type == POSITION_TYPE_BUY)
|
|
profit = (exit_price - virtual_entry) / virtual_entry;
|
|
else
|
|
profit = (virtual_entry - exit_price) / virtual_entry;
|
|
|
|
total_profit += profit;
|
|
total_trades++;
|
|
if(profit > 0) winning_trades++;
|
|
|
|
virtual_position = 0;
|
|
}
|
|
}
|
|
// RSI extreme exit (if in profit)
|
|
else if(bars_held >= MinBarsBeforeExit)
|
|
{
|
|
double profit_pct = 0;
|
|
if(virtual_position_type == POSITION_TYPE_BUY)
|
|
profit_pct = ((current_price - virtual_entry) / virtual_entry) * 100.0;
|
|
else
|
|
profit_pct = ((virtual_entry - current_price) / virtual_entry) * 100.0;
|
|
|
|
// Exit if RSI reaches opposite extreme and we're in profit
|
|
if(profit_pct > 0.05)
|
|
{
|
|
bool should_exit = false;
|
|
if(virtual_position_type == POSITION_TYPE_BUY && current_rsi > params.rsi_overbought)
|
|
should_exit = true;
|
|
else if(virtual_position_type == POSITION_TYPE_SELL && current_rsi < params.rsi_oversold)
|
|
should_exit = true;
|
|
|
|
if(should_exit)
|
|
{
|
|
double profit = profit_pct / 100.0;
|
|
total_profit += profit;
|
|
total_trades++;
|
|
winning_trades++;
|
|
virtual_position = 0;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Check for new entry signals (only if no position)
|
|
if(virtual_position == 0)
|
|
{
|
|
// Buy signal: RSI crosses above oversold
|
|
if(prev_rsi < params.rsi_oversold && current_rsi >= params.rsi_oversold)
|
|
{
|
|
virtual_position = 1;
|
|
virtual_entry = current_price;
|
|
virtual_entry_time = bar_time;
|
|
virtual_position_type = POSITION_TYPE_BUY;
|
|
}
|
|
// Sell signal: RSI crosses below overbought
|
|
else if(prev_rsi > params.rsi_overbought && current_rsi <= params.rsi_overbought)
|
|
{
|
|
virtual_position = 1;
|
|
virtual_entry = current_price;
|
|
virtual_entry_time = bar_time;
|
|
virtual_position_type = POSITION_TYPE_SELL;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Close any remaining position at end
|
|
if(virtual_position > 0)
|
|
{
|
|
double exit_price = close_buffer[0];
|
|
double profit = 0;
|
|
if(virtual_position_type == POSITION_TYPE_BUY)
|
|
profit = (exit_price - virtual_entry) / virtual_entry;
|
|
else
|
|
profit = (virtual_entry - exit_price) / virtual_entry;
|
|
|
|
total_profit += profit;
|
|
total_trades++;
|
|
if(profit > 0) winning_trades++;
|
|
}
|
|
|
|
IndicatorRelease(temp_rsi);
|
|
|
|
// Check if we have enough trades
|
|
if(total_trades < MinTradesForOptimization)
|
|
{
|
|
if(total_trades > 0)
|
|
{
|
|
// Return scaled negative value if some trades but not enough
|
|
return (total_profit * 100.0) - (MinTradesForOptimization - total_trades) * 10.0;
|
|
}
|
|
return -999999.0;
|
|
}
|
|
|
|
// Return profitability percentage
|
|
return total_profit * 100.0;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Run Strategy |
|
|
//+------------------------------------------------------------------+
|
|
void RunStrategy()
|
|
{
|
|
// Check if indicators are ready
|
|
if(rsi_handle == INVALID_HANDLE || atr_handle == INVALID_HANDLE) return;
|
|
|
|
double rsi_buffer[];
|
|
double close_buffer[];
|
|
ArraySetAsSeries(rsi_buffer, true);
|
|
ArraySetAsSeries(close_buffer, true);
|
|
|
|
if(CopyBuffer(rsi_handle, 0, 0, 3, rsi_buffer) < 3) return;
|
|
if(CopyClose(TradingSymbol, TimeFrame, 0, 3, close_buffer) < 3) return;
|
|
|
|
double current_rsi = rsi_buffer[0];
|
|
double prev_rsi = rsi_buffer[1];
|
|
double current_price = close_buffer[0];
|
|
|
|
// Check existing positions
|
|
if(PositionSelect(TradingSymbol))
|
|
{
|
|
ulong pos_ticket = PositionGetInteger(POSITION_TICKET);
|
|
if(PositionGetInteger(POSITION_MAGIC) == MagicNumber)
|
|
{
|
|
// Get position details
|
|
double pos_open_price = PositionGetDouble(POSITION_PRICE_OPEN);
|
|
datetime pos_open_time = (datetime)PositionGetInteger(POSITION_TIME);
|
|
ENUM_POSITION_TYPE pos_type = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);
|
|
|
|
// Calculate bars held
|
|
datetime current_time = TimeCurrent();
|
|
int period_seconds = PeriodSeconds(TimeFrame);
|
|
int bars_held = (int)((current_time - pos_open_time) / period_seconds);
|
|
|
|
// Loss protection
|
|
double profit_pct = 0;
|
|
if(pos_type == POSITION_TYPE_BUY)
|
|
profit_pct = ((current_price - pos_open_price) / pos_open_price) * 100.0;
|
|
else
|
|
profit_pct = ((pos_open_price - current_price) / pos_open_price) * 100.0;
|
|
|
|
// Max loss exit
|
|
if(profit_pct < -MaxLossPercent)
|
|
{
|
|
trade.PositionClose(pos_ticket);
|
|
Print("Position closed due to max loss: ", DoubleToString(profit_pct, 2), "%");
|
|
return;
|
|
}
|
|
|
|
// Adverse move detection with ATR
|
|
double atr_buffer[];
|
|
ArraySetAsSeries(atr_buffer, true);
|
|
if(CopyBuffer(atr_handle, 0, 0, 1, atr_buffer) >= 1)
|
|
{
|
|
double atr_value = atr_buffer[0];
|
|
double adverse_move = (atr_value / current_price) * 100.0;
|
|
|
|
if(profit_pct < -AdverseMoveThreshold && adverse_move > AdverseMoveThreshold)
|
|
{
|
|
trade.PositionClose(pos_ticket);
|
|
Print("Position closed due to adverse move: ", DoubleToString(profit_pct, 2), "%");
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Time-based exit
|
|
if(bars_held >= MaxBarsInTrade)
|
|
{
|
|
trade.PositionClose(pos_ticket);
|
|
Print("Position closed due to max bars: ", bars_held);
|
|
return;
|
|
}
|
|
|
|
// Signal reversal exit
|
|
if(ExitOnReversal && bars_held >= MinBarsBeforeExit)
|
|
{
|
|
bool should_exit = false;
|
|
if(pos_type == POSITION_TYPE_BUY && current_rsi > current_params.rsi_overbought)
|
|
should_exit = true;
|
|
else if(pos_type == POSITION_TYPE_SELL && current_rsi < current_params.rsi_oversold)
|
|
should_exit = true;
|
|
|
|
if(should_exit)
|
|
{
|
|
trade.PositionClose(pos_ticket);
|
|
Print("Position closed due to signal reversal");
|
|
return;
|
|
}
|
|
}
|
|
|
|
// RSI extreme exit (if in profit)
|
|
if(bars_held >= MinBarsBeforeExit && profit_pct > 0.05)
|
|
{
|
|
bool should_exit = false;
|
|
if(pos_type == POSITION_TYPE_BUY && current_rsi > current_params.rsi_overbought)
|
|
should_exit = true;
|
|
else if(pos_type == POSITION_TYPE_SELL && current_rsi < current_params.rsi_oversold)
|
|
should_exit = true;
|
|
|
|
if(should_exit)
|
|
{
|
|
trade.PositionClose(pos_ticket);
|
|
Print("Position closed due to RSI extreme: ", DoubleToString(profit_pct, 2), "%");
|
|
return;
|
|
}
|
|
}
|
|
|
|
return; // Position exists, don't open new one
|
|
}
|
|
}
|
|
|
|
// Check for new entry signals
|
|
// Buy signal: RSI crosses above oversold
|
|
if(prev_rsi < current_params.rsi_oversold && current_rsi >= current_params.rsi_oversold)
|
|
{
|
|
double ask = SymbolInfoDouble(TradingSymbol, SYMBOL_ASK);
|
|
if(trade.Buy(LotSize, TradingSymbol, ask, 0, 0, "RSI Reversal Buy"))
|
|
{
|
|
Print("Buy order opened: RSI=", DoubleToString(current_rsi, 2),
|
|
" Oversold=", DoubleToString(current_params.rsi_oversold, 1));
|
|
}
|
|
}
|
|
// Sell signal: RSI crosses below overbought
|
|
else if(prev_rsi > current_params.rsi_overbought && current_rsi <= current_params.rsi_overbought)
|
|
{
|
|
double bid = SymbolInfoDouble(TradingSymbol, SYMBOL_BID);
|
|
if(trade.Sell(LotSize, TradingSymbol, bid, 0, 0, "RSI Reversal Sell"))
|
|
{
|
|
Print("Sell order opened: RSI=", DoubleToString(current_rsi, 2),
|
|
" Overbought=", DoubleToString(current_params.rsi_overbought, 1));
|
|
}
|
|
}
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Calculate Strategy Profitability |
|
|
//+------------------------------------------------------------------+
|
|
double CalculateStrategyProfitability()
|
|
{
|
|
double total_profit = 0.0;
|
|
|
|
// Check open positions
|
|
for(int i = PositionsTotal() - 1; i >= 0; i--)
|
|
{
|
|
ulong ticket = PositionGetTicket(i);
|
|
if(ticket > 0)
|
|
{
|
|
if(PositionGetString(POSITION_SYMBOL) == TradingSymbol &&
|
|
PositionGetInteger(POSITION_MAGIC) == MagicNumber)
|
|
{
|
|
double open_price = PositionGetDouble(POSITION_PRICE_OPEN);
|
|
double current_price = (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY) ?
|
|
SymbolInfoDouble(TradingSymbol, SYMBOL_BID) :
|
|
SymbolInfoDouble(TradingSymbol, SYMBOL_ASK);
|
|
|
|
double profit = 0;
|
|
if(PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY)
|
|
profit = (current_price - open_price) / open_price;
|
|
else
|
|
profit = (open_price - current_price) / open_price;
|
|
|
|
total_profit += profit * PositionGetDouble(POSITION_VOLUME) / LotSize;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Check historical deals (last 24 hours)
|
|
datetime end_time = TimeCurrent();
|
|
datetime start_time = end_time - 86400; // 24 hours
|
|
|
|
if(HistorySelect(start_time, end_time))
|
|
{
|
|
int total_deals = HistoryDealsTotal();
|
|
for(int i = 0; i < total_deals; i++)
|
|
{
|
|
ulong ticket = HistoryDealGetTicket(i);
|
|
if(ticket > 0)
|
|
{
|
|
if(HistoryDealGetString(ticket, DEAL_SYMBOL) == TradingSymbol &&
|
|
HistoryDealGetInteger(ticket, DEAL_MAGIC) == MagicNumber)
|
|
{
|
|
double profit = HistoryDealGetDouble(ticket, DEAL_PROFIT);
|
|
double volume = HistoryDealGetDouble(ticket, DEAL_VOLUME);
|
|
double open_price = HistoryDealGetDouble(ticket, DEAL_PRICE);
|
|
|
|
if(open_price > 0)
|
|
{
|
|
double profit_pct = (profit / (open_price * volume)) * 100.0;
|
|
total_profit += profit_pct / 100.0;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return total_profit * 100.0; // Return as percentage
|
|
}
|
|
//+------------------------------------------------------------------+
|