This commit is contained in:
zhutoutoutousan
2026-02-13 08:03:25 +01:00
parent 09c2f54c71
commit 98a87a69ca
134 changed files with 20003 additions and 253 deletions
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,632 @@
//+------------------------------------------------------------------+
//| tick-momentum-catcher.mq5 |
//| Tick-Level Momentum Catcher for BTCUSD |
//| |
//+------------------------------------------------------------------+
#property copyright "Tick Momentum Catcher"
#property link ""
#property version "1.00"
#property strict
#include <Trade\Trade.mqh>
CTrade trade;
//+------------------------------------------------------------------+
//| Input Parameters |
//+------------------------------------------------------------------+
input group "=== General Settings ==="
input string TradingSymbol = "BTCUSD"; // Trading Symbol
input double LotSize = 0.01; // Lot Size
input int MagicNumber = 88010; // Magic Number
input int Slippage = 3; // Slippage
input group "=== Tick Momentum Settings ==="
input int TickWindow = 20; // Tick Window for Momentum Calculation
input double MinTickMomentum = 0.05; // Min Tick Momentum % (0.05 = 0.05%)
input double VolumeSpikeMultiplier = 2.0; // Volume Spike Multiplier
input int MinTicksForSignal = 3; // Min Consecutive Ticks for Signal
input double OrderFlowImbalance = 1.5; // Order Flow Imbalance Ratio (1.5 = 50% more)
input group "=== Entry Settings ==="
input double EntryMomentumThreshold = 0.1; // Entry Momentum Threshold %
input int MaxBarsHold = 5; // Max Bars to Hold Position (1 minute bars)
input double MaxLossPercent = 0.3; // Max Loss % to Force Close
input double TakeProfitPercent = 0.15; // Take Profit % (0.15 = 0.15%)
input double StopLossPercent = 0.1; // Stop Loss % (0.1 = 0.1%)
input group "=== Self-Optimization ==="
input bool EnableAutoOptimization = true; // Enable Auto Optimization
input int OptimizationIntervalBars = 100; // Bars Between Optimizations
input int OptimizationPeriodMinutes = 30; // Backtesting Period (Minutes)
//+------------------------------------------------------------------+
//| Tick Data Structure |
//+------------------------------------------------------------------+
struct TickData
{
double price;
ulong volume; // Match MqlTick.volume type (ulong)
datetime time;
bool is_buy; // true if price moved up, false if down
double momentum; // Price change percentage
};
//+------------------------------------------------------------------+
//| Global Variables |
//+------------------------------------------------------------------+
TickData tick_buffer[];
int tick_buffer_size = 1000;
int last_optimization_bar = 0;
int bars_since_optimization = 0;
// Current momentum tracking
double current_momentum = 0.0;
int consecutive_buy_ticks = 0;
int consecutive_sell_ticks = 0;
double buy_volume_sum = 0.0;
double sell_volume_sum = 0.0;
// Position tracking
datetime position_entry_time = 0;
double position_entry_price = 0.0;
ENUM_POSITION_TYPE position_type = WRONG_VALUE;
//+------------------------------------------------------------------+
//| Expert initialization function |
//+------------------------------------------------------------------+
int OnInit()
{
ArrayResize(tick_buffer, tick_buffer_size);
ArraySetAsSeries(tick_buffer, false); // New ticks at end
trade.SetExpertMagicNumber(MagicNumber);
trade.SetDeviationInPoints(Slippage);
trade.SetTypeFilling(ORDER_FILLING_FOK);
Print("=== Tick Momentum Catcher Initialized ===");
Print("Symbol: ", TradingSymbol);
Print("Tick Window: ", TickWindow);
Print("Min Momentum: ", MinTickMomentum, "%");
return INIT_SUCCEEDED;
}
//+------------------------------------------------------------------+
//| Expert deinitialization function |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
ArrayFree(tick_buffer);
}
//+------------------------------------------------------------------+
//| Expert tick function |
//+------------------------------------------------------------------+
void OnTick()
{
// Get current tick
MqlTick tick;
if(!SymbolInfoTick(TradingSymbol, tick))
{
return;
}
// Process tick
ProcessTick(tick);
// Check for optimization
if(EnableAutoOptimization)
{
int current_bar = iBars(TradingSymbol, PERIOD_M1);
if(current_bar > last_optimization_bar)
{
bars_since_optimization++;
if(bars_since_optimization >= OptimizationIntervalBars)
{
OptimizeParameters();
bars_since_optimization = 0;
}
}
last_optimization_bar = current_bar;
}
// Check existing position
CheckPosition();
// Look for entry signals
if(!PositionSelect(TradingSymbol))
{
CheckEntrySignals();
}
}
//+------------------------------------------------------------------+
//| Process Tick Data |
//+------------------------------------------------------------------+
void ProcessTick(MqlTick &tick)
{
static double last_price = 0.0;
static datetime last_time = 0;
if(last_price == 0.0)
{
last_price = tick.last;
last_time = tick.time;
return;
}
// Calculate momentum
double price_change = tick.last - last_price;
double momentum_pct = 0.0;
if(last_price > 0)
{
momentum_pct = (price_change / last_price) * 100.0;
}
// Determine if buy or sell tick
bool is_buy = (price_change > 0);
// Add to buffer (circular buffer)
static int tick_index = 0;
tick_buffer[tick_index].price = tick.last;
tick_buffer[tick_index].volume = tick.volume; // ulong type matches
tick_buffer[tick_index].time = tick.time;
tick_buffer[tick_index].is_buy = is_buy;
tick_buffer[tick_index].momentum = momentum_pct;
tick_index++;
if(tick_index >= tick_buffer_size) tick_index = 0;
// Update momentum tracking
UpdateMomentumTracking(is_buy, momentum_pct, (double)(long)tick.volume); // Convert ulong to double via long
last_price = tick.last;
last_time = tick.time;
}
//+------------------------------------------------------------------+
//| Update Momentum Tracking |
//+------------------------------------------------------------------+
void UpdateMomentumTracking(bool is_buy, double momentum, double volume)
{
// Track consecutive ticks
if(is_buy)
{
consecutive_buy_ticks++;
consecutive_sell_ticks = 0;
buy_volume_sum += volume;
}
else
{
consecutive_sell_ticks++;
consecutive_buy_ticks = 0;
sell_volume_sum += volume;
}
// Calculate current momentum from recent ticks
int recent_ticks = MathMin(TickWindow, tick_buffer_size);
double momentum_sum = 0.0;
int count = 0;
for(int i = tick_buffer_size - 1; i >= 0 && count < recent_ticks; i--)
{
if(tick_buffer[i].price > 0)
{
momentum_sum += MathAbs(tick_buffer[i].momentum);
count++;
}
}
if(count > 0)
{
current_momentum = momentum_sum / count;
}
}
//+------------------------------------------------------------------+
//| Check Entry Signals |
//+------------------------------------------------------------------+
void CheckEntrySignals()
{
// Get current price
double ask = SymbolInfoDouble(TradingSymbol, SYMBOL_ASK);
double bid = SymbolInfoDouble(TradingSymbol, SYMBOL_BID);
// Calculate order flow imbalance
double order_flow_ratio = 0.0;
if(sell_volume_sum > 0)
{
order_flow_ratio = buy_volume_sum / sell_volume_sum;
}
else if(buy_volume_sum > 0)
{
order_flow_ratio = 999.0; // All buy volume
}
// Check for violent momentum (use optimized value if available)
double momentum_threshold = (optimized_min_tick_momentum > 0.0) ? optimized_min_tick_momentum : MinTickMomentum;
bool violent_momentum = (current_momentum >= momentum_threshold);
// Check for consecutive ticks in same direction
bool strong_buy_signal = (consecutive_buy_ticks >= MinTicksForSignal);
bool strong_sell_signal = (consecutive_sell_ticks >= MinTicksForSignal);
// Check order flow imbalance (use optimized value if available)
double imbalance_threshold = (optimized_order_flow_imbalance > 0.0) ? optimized_order_flow_imbalance : OrderFlowImbalance;
bool buy_imbalance = (order_flow_ratio >= imbalance_threshold);
bool sell_imbalance = (order_flow_ratio <= (1.0 / imbalance_threshold));
// Entry conditions
// BUY: Violent momentum + consecutive buy ticks + buy volume dominance
if(violent_momentum && strong_buy_signal && buy_imbalance)
{
double entry_momentum = CalculateEntryMomentum(true);
if(entry_momentum >= EntryMomentumThreshold)
{
OpenBuyPosition(ask);
}
}
// SELL: Violent momentum + consecutive sell ticks + sell volume dominance
else if(violent_momentum && strong_sell_signal && sell_imbalance)
{
double entry_momentum = CalculateEntryMomentum(false);
if(entry_momentum >= EntryMomentumThreshold)
{
OpenSellPosition(bid);
}
}
}
//+------------------------------------------------------------------+
//| Calculate Entry Momentum |
//+------------------------------------------------------------------+
double CalculateEntryMomentum(bool is_buy)
{
double momentum_sum = 0.0;
int count = 0;
int lookback = MathMin(MinTicksForSignal, tick_buffer_size);
for(int i = tick_buffer_size - 1; i >= 0 && count < lookback; i--)
{
if(tick_buffer[i].price > 0)
{
if(is_buy && tick_buffer[i].is_buy)
{
momentum_sum += tick_buffer[i].momentum;
count++;
}
else if(!is_buy && !tick_buffer[i].is_buy)
{
momentum_sum += MathAbs(tick_buffer[i].momentum);
count++;
}
}
}
if(count > 0)
{
return MathAbs(momentum_sum / count);
}
return 0.0;
}
//+------------------------------------------------------------------+
//| Normalize Stops According to Broker Requirements |
//+------------------------------------------------------------------+
void NormalizeStops(double price, double &sl, double &tp, bool is_buy)
{
double point = SymbolInfoDouble(TradingSymbol, SYMBOL_POINT);
int digits = (int)SymbolInfoInteger(TradingSymbol, SYMBOL_DIGITS);
int stops_level = (int)SymbolInfoInteger(TradingSymbol, SYMBOL_TRADE_STOPS_LEVEL);
// Calculate minimum distance in points
double min_stop_distance = stops_level * point;
if(min_stop_distance == 0) min_stop_distance = point * 10; // Default to 10 points if not specified
// Normalize to required digits
sl = NormalizeDouble(sl, digits);
tp = NormalizeDouble(tp, digits);
// Ensure stops meet minimum distance requirement
if(is_buy)
{
// For buy: SL below price, TP above price
double sl_distance = price - sl;
double tp_distance = tp - price;
if(sl_distance < min_stop_distance)
{
sl = NormalizeDouble(price - min_stop_distance, digits);
}
if(tp_distance < min_stop_distance)
{
tp = NormalizeDouble(price + min_stop_distance, digits);
}
}
else
{
// For sell: SL above price, TP below price
double sl_distance = sl - price;
double tp_distance = price - tp;
if(sl_distance < min_stop_distance)
{
sl = NormalizeDouble(price + min_stop_distance, digits);
}
if(tp_distance < min_stop_distance)
{
tp = NormalizeDouble(price - min_stop_distance, digits);
}
}
}
//+------------------------------------------------------------------+
//| Open Buy Position |
//+------------------------------------------------------------------+
void OpenBuyPosition(double price)
{
double sl = price * (1.0 - StopLossPercent / 100.0);
double tp = price * (1.0 + TakeProfitPercent / 100.0);
// Normalize stops according to broker requirements
NormalizeStops(price, sl, tp, true);
if(trade.Buy(LotSize, TradingSymbol, price, sl, tp, "Tick Momentum Buy"))
{
position_entry_time = TimeCurrent();
position_entry_price = price;
position_type = POSITION_TYPE_BUY;
double order_flow_display = (sell_volume_sum > 0) ? (buy_volume_sum / sell_volume_sum) : 999.0;
Print("BUY opened: Price=", price, " Momentum=", DoubleToString(current_momentum, 3),
"% Consecutive=", consecutive_buy_ticks, " OrderFlow=", DoubleToString(order_flow_display, 2));
// Reset tracking
consecutive_buy_ticks = 0;
consecutive_sell_ticks = 0;
buy_volume_sum = 0.0;
sell_volume_sum = 0.0;
}
}
//+------------------------------------------------------------------+
//| Open Sell Position |
//+------------------------------------------------------------------+
void OpenSellPosition(double price)
{
double sl = price * (1.0 + StopLossPercent / 100.0);
double tp = price * (1.0 - TakeProfitPercent / 100.0);
// Normalize stops according to broker requirements
NormalizeStops(price, sl, tp, false);
if(trade.Sell(LotSize, TradingSymbol, price, sl, tp, "Tick Momentum Sell"))
{
position_entry_time = TimeCurrent();
position_entry_price = price;
position_type = POSITION_TYPE_SELL;
double order_flow_display = (buy_volume_sum > 0) ? (sell_volume_sum / buy_volume_sum) : 999.0;
Print("SELL opened: Price=", price, " Momentum=", DoubleToString(current_momentum, 3),
"% Consecutive=", consecutive_sell_ticks, " OrderFlow=", DoubleToString(order_flow_display, 2));
// Reset tracking
consecutive_buy_ticks = 0;
consecutive_sell_ticks = 0;
buy_volume_sum = 0.0;
sell_volume_sum = 0.0;
}
}
//+------------------------------------------------------------------+
//| Check Position |
//+------------------------------------------------------------------+
void CheckPosition()
{
if(!PositionSelect(TradingSymbol)) return;
if(PositionGetInteger(POSITION_MAGIC) != MagicNumber) return;
ulong ticket = PositionGetInteger(POSITION_TICKET);
double open_price = PositionGetDouble(POSITION_PRICE_OPEN);
ENUM_POSITION_TYPE pos_type = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);
datetime open_time = (datetime)PositionGetInteger(POSITION_TIME);
// Get current price
double current_price = (pos_type == POSITION_TYPE_BUY) ?
SymbolInfoDouble(TradingSymbol, SYMBOL_BID) :
SymbolInfoDouble(TradingSymbol, SYMBOL_ASK);
// Calculate profit percentage
double profit_pct = 0.0;
if(pos_type == POSITION_TYPE_BUY)
profit_pct = ((current_price - open_price) / open_price) * 100.0;
else
profit_pct = ((open_price - current_price) / open_price) * 100.0;
// Check max loss
if(profit_pct < -MaxLossPercent)
{
trade.PositionClose(ticket);
Print("Position closed: Max loss reached ", DoubleToString(profit_pct, 2), "%");
return;
}
// Check time-based exit (1 minute bars)
int bars_held = (int)((TimeCurrent() - open_time) / 60); // Convert to minutes
if(bars_held >= MaxBarsHold)
{
trade.PositionClose(ticket);
Print("Position closed: Max bars held ", bars_held);
return;
}
// Check for momentum reversal (exit if momentum reverses)
if(pos_type == POSITION_TYPE_BUY)
{
// Exit if strong sell momentum develops
if(consecutive_sell_ticks >= MinTicksForSignal && current_momentum >= MinTickMomentum)
{
double reversal_momentum = CalculateEntryMomentum(false);
if(reversal_momentum >= EntryMomentumThreshold)
{
trade.PositionClose(ticket);
Print("Position closed: Momentum reversal detected");
return;
}
}
}
else // SELL
{
// Exit if strong buy momentum develops
if(consecutive_buy_ticks >= MinTicksForSignal && current_momentum >= MinTickMomentum)
{
double reversal_momentum = CalculateEntryMomentum(true);
if(reversal_momentum >= EntryMomentumThreshold)
{
trade.PositionClose(ticket);
Print("Position closed: Momentum reversal detected");
return;
}
}
}
}
//+------------------------------------------------------------------+
//| Global Variables for Optimization |
//+------------------------------------------------------------------+
double optimized_min_tick_momentum = 0.0;
double optimized_order_flow_imbalance = 0.0;
//+------------------------------------------------------------------+
//| Optimize Parameters |
//+------------------------------------------------------------------+
void OptimizeParameters()
{
Print("=== Optimizing Tick Momentum Parameters ===");
// Initialize optimized values if not set
if(optimized_min_tick_momentum == 0.0)
{
optimized_min_tick_momentum = MinTickMomentum;
}
if(optimized_order_flow_imbalance == 0.0)
{
optimized_order_flow_imbalance = OrderFlowImbalance;
}
// Simple optimization: test different thresholds
double best_profit = CalculateCurrentProfitability();
double best_momentum = optimized_min_tick_momentum;
double best_imbalance = optimized_order_flow_imbalance;
// Test momentum thresholds
for(double test_momentum = 0.03; test_momentum <= 0.15; test_momentum += 0.02)
{
optimized_min_tick_momentum = test_momentum;
double profit = BacktestParameters(OptimizationPeriodMinutes);
if(profit > best_profit)
{
best_profit = profit;
best_momentum = test_momentum;
}
}
// Test imbalance thresholds
for(double test_imbalance = 1.2; test_imbalance <= 2.5; test_imbalance += 0.2)
{
optimized_order_flow_imbalance = test_imbalance;
double profit = BacktestParameters(OptimizationPeriodMinutes);
if(profit > best_profit)
{
best_profit = profit;
best_imbalance = test_imbalance;
}
}
// Update if better found
if(best_profit > CalculateCurrentProfitability() * 1.1) // 10% improvement
{
optimized_min_tick_momentum = best_momentum;
optimized_order_flow_imbalance = best_imbalance;
Print("Parameters optimized: Momentum=", best_momentum, " Imbalance=", best_imbalance);
}
else
{
Print("Keeping current parameters. Profit: ", DoubleToString(best_profit, 2), "%");
}
}
//+------------------------------------------------------------------+
//| Calculate Current Profitability |
//+------------------------------------------------------------------+
double CalculateCurrentProfitability()
{
double total_profit = 0.0;
// Check open positions
if(PositionSelect(TradingSymbol))
{
if(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);
if(PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY)
total_profit = ((current_price - open_price) / open_price) * 100.0;
else
total_profit = ((open_price - current_price) / open_price) * 100.0;
}
}
// Check historical deals (last hour)
datetime end_time = TimeCurrent();
datetime start_time = end_time - 3600;
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 price = HistoryDealGetDouble(ticket, DEAL_PRICE);
if(price > 0 && volume > 0)
{
total_profit += (profit / (price * volume)) * 100.0;
}
}
}
}
}
return total_profit;
}
//+------------------------------------------------------------------+
//| Backtest Parameters |
//+------------------------------------------------------------------+
double BacktestParameters(int minutes)
{
// Simplified backtest - would need historical tick data
// For now, return current profitability as approximation
return CalculateCurrentProfitability();
}
//+------------------------------------------------------------------+