Let us kill a misconception upfront: "HFT" on MetaTrader doesn't mean nanosecond execution or co-located servers. You're not competing with Citadel. What it does mean is taking a high volume of small, positive-expectancy trades with tight risk controls and aggressive capital efficiency.
This article dissects a real production scalper we built – one that ran backtests across gold, forex crosses, and crypto from 2017 to 2022. The signal itself is almost embarrassingly simple. The secret sauce is in the lot sizing, spread gating, and equity management.
What This Thing Actually Does
Seven steps. That's the whole logic:
- Wait for a new trading day. No open positions.
- Check the spread. If it's too wide, do nothing.
- Buy the dip – enter when price drops below the previous bar's low.
- Size aggressively. Lots scale directly with account balance.
- Hit the equity target? Flatten everything.
- Hit the loss limit? Flatten everything.
- Tomorrow, recalculate based on the new balance. Repeat.
The edge isn't hiding in a clever indicator. It's in how the pieces – scaling, spread control, equity exits – work together.
Dynamic Lot Sizing: Where the Money Lives
Most EAs use fixed lots. That's fine for learning. It's terrible for making money. This scalper ties lot size directly to account balance – the bigger the account grows, the harder it swings.
MQL4 Implementation
// Input parameters for the scaling engine
input double ProfitFactor = 0.8; // Profit target multiplier
input double LossFactor = 3.0; // Max loss multiplier
input double AccountMultiplier = 0.5; // Account scaling factor
input int MaxTrades = 100; // Max trades per day
// Dynamic lot and target calculation – recalculated per tick
void RecalculateParameters()
{
double balance = AccountBalance();
// Core lot formula: scales linearly with balance
// $10,000 balance → ~7.6 standard lots
// $1,000 balance → ~0.76 lots
double baseLot = balance * 2.0 / 1316.0;
// Apply account multiplier for risk scaling
g_lotSize = baseLot * 0.7 * AccountMultiplier;
// Profit target: also scales with balance
g_profitTarget = balance * 2.0 * 0.3 * 0.7 * AccountMultiplier * ProfitFactor / 11.844;
// Max loss exit: wider than profit target (asymmetric R:R)
g_maxLoss = balance * 2.0 * 0.6 * 0.7 * AccountMultiplier * LossFactor / 3.948;
}The formula explained:
$$\text{LotSize} = \frac{\text{Balance} \times 2 \times 0.7 \times \text{AccountMultiplier}}{1316}$$
The divisor (1316) is calibrated to gold's margin requirements. Change the instrument, change the divisor. The AccountMultiplier input (default 0.5) is your risk dial – crank it up for aggression, dial it down to sleep at night.
Why is the loss limit wider than the profit target? Because this strategy needs room to breathe. Trades dip before they rip. A 0.8 profit factor with a 3.0 loss factor means you're hitting profits frequently while only rarely touching the loss floor.
Lot Splitting: When You Outgrow Your Broker
Good problem to have: your account grows and the lot formula spits out numbers your broker won't accept in a single order. Solution: split automatically.
MQL4 Implementation
#define MAX_LOT_PER_ORDER 100000 // Broker's max lot per single order
void PlaceBuyWithSplitting()
{
double remainingLots = g_lotSize;
while (remainingLots > 0)
{
double orderLot;
if (remainingLots > MAX_LOT_PER_ORDER)
{
orderLot = MAX_LOT_PER_ORDER;
remainingLots -= MAX_LOT_PER_ORDER;
}
else
{
orderLot = remainingLots;
remainingLots = 0;
}
int ticket = OrderSend(Symbol(), OP_BUY, orderLot, Ask, 0,
0, 0, NULL, MAGIC_BUY, 0, clrBlue);
if (ticket < 0)
Print("Order failed. Error: ", GetLastError());
}
}A 250K-lot order becomes three orders automatically. We've run production EAs that routinely hit six-figure lots. Without this pattern, the broker just rejects you and the EA sits there confused.
The Spread Gate: Non-Negotiable
This is what separates a scalper that works from one that bleeds. A 2-pip spread on a 5-pip target eats 40% of your edge before the trade even opens. Think about that.
MQL4 Implementation
// Entry condition: ONLY trade when spread is tight
void OnTick()
{
RecalculateParameters();
// SPREAD GATE – this is non-negotiable for scalpers
double spread = Ask - Bid;
if (spread > 0.002) // Max 0.2 pips (for gold/GC)
return; // Do nothing. Wait for tighter spread.
// Only enter ONCE per day (day filter reset)
static int lastTradeDay = 0;
if (Day() != lastTradeDay && OrdersTotal() == 0)
{
// DIP-BUY ENTRY: current price below previous bar's low
if (Ask < Low[1] && g_tradeCount < MaxTrades)
{
PlaceBuyWithSplitting();
g_lastEntryPrice = Close[0];
g_tradeCount++;
}
}
// EQUITY-BASED EXITS
CheckEquityExits();
}Python Equivalent – Spread-Gated Entry
class HFTScalper:
def __init__(self, profit_factor=0.8, loss_factor=3.0, account_mult=0.5):
self.profit_factor = profit_factor
self.loss_factor = loss_factor
self.account_mult = account_mult
self.last_entry_price = None
self.trade_count = 0
def calculate_parameters(self, balance):
"""Recalculate lot size and targets based on current balance."""
self.lot_size = balance * 2 * 0.7 * self.account_mult / 1316
self.profit_target = (balance * 2 * 0.3 * 0.7
* self.account_mult * self.profit_factor / 11.844)
self.max_loss = (balance * 2 * 0.6 * 0.7
* self.account_mult * self.loss_factor / 3.948)
def should_enter(self, ask, bid, previous_low, max_spread=0.002):
"""Check if entry conditions are met."""
spread = ask - bid
if spread > max_spread:
return False # Spread too wide
if ask < previous_low:
return True # Price dipped below previous bar's low
return False
def check_exit(self, equity, balance):
"""Check equity-based exit conditions."""
if equity > balance + self.profit_target:
return 'take_profit'
if equity < balance - self.max_loss:
return 'stop_loss'
return NoneEquity-Based Exit: The Heartbeat
No per-trade stop losses. No per-trade take profits. This strategy monitors total account equity against dynamic targets. When the combined portfolio hits the number, everything closes.
Why do it this way? With up to 100 simultaneous positions, individual stops would trigger independently – a cascading mess of partial closes. The equity-based approach treats the whole stack as one bet and makes one clean decision.
MQL4 Implementation
void CheckEquityExits()
{
double balance = AccountBalance();
double equity = AccountEquity();
// PROFIT TARGET: equity exceeds balance by target amount
if (equity > balance + g_profitTarget)
{
CloseAllBuyOrders();
CloseAllSellOrders();
// Reset balance reference to new equity level
// Next cycle calculates fresh targets from new balance
Print("PROFIT TARGET HIT. New balance: ", AccountBalance());
}
// LOSS LIMIT: equity drops below balance by max loss amount
if (equity < balance - g_maxLoss)
{
CloseAllBuyOrders();
CloseAllSellOrders();
Print("MAX LOSS HIT. Closing all. Balance: ", AccountBalance());
}
}All positions live and die together. That's the design.
Trailing the Entry Price
Subtle but powerful: as price rises after your entry, the EA tracks the move and re-anchors for the next dip-buy at a higher level:
// In OnTick(), after entry:
if (Close[0] > g_lastEntryPrice)
{
g_lastEntryPrice = Bid; // Update reference price upward
}So the bot follows momentum rather than fighting it. Dip-buys happen at progressively higher prices during an uptrend. Not counter-trend – with the trend.
The Complete Strategy Parameters
| Parameter | Default | Purpose |
|---|---|---|
| ProfitFactor | 0.8 | How aggressively to target profit (smaller = more conservative) |
| LossFactor | 3.0 | How much drawdown to tolerate (larger = more patient) |
| AccountMultiplier | 0.5 | Overall risk scaling (0.1 = very safe, 1.0 = aggressive) |
| MaxTrades | 100 | Maximum entries per day |
| MaxSpread | 0.002 | Maximum allowed spread for entry |
Suggested Parameter Sets
Conservative (recommended for starters):
| ProfitFactor | LossFactor | AccountMultiplier | MaxTrades |
|---|---|---|---|
| 0.5 | 1.5 | 0.3 | 20 |
Balanced (used in backtesting):
| ProfitFactor | LossFactor | AccountMultiplier | MaxTrades |
|---|---|---|---|
| 0.8 | 3.0 | 0.5 | 100 |
Aggressive (high-risk, high-return):
| ProfitFactor | LossFactor | AccountMultiplier | MaxTrades |
|---|---|---|---|
| 1.5 | 5.0 | 1.0 | 100 |
What the Backtests Showed
We ran this across multiple instruments and timeframes. Here's the honest picture:
| Instrument | Period | Timeframe | Account Start | Key Result |
|---|---|---|---|---|
| Gold (GC) | 2017-2021 | H1 | $1,000 | High yield configuration – aggressive scaling on gold futures |
| GBPCHF | 2019-2022 | H1 | $200 | Tested with spread limits of 100, 125, and 150 points |
| GBPNZD | 2019-2022 | H1/H4 | $200 | Tested with 150 and 200 point spread limits |
| AUDCHF | 2019-2022 | H1 | $200 | Tight spread CHF crosses |
| BTCUSD | 2019-2022 | H1 | $1,000 | Crypto volatility adaptation |
Takeaways that actually matter:
- Spread dominates everything. Loosen the spread filter and results fall off a cliff.
- CHF crosses were the winners. Low volatility, tight spreads, predictable dip patterns. Boring markets make the best scalping playgrounds.
- H1 was the sweet spot. H4 was too slow for dip-buying. M15 generated too much noise.
- Account size matters. The dynamic scaling formula needs at least $1,000 to produce meaningful lot sizes.
The Honest Warning
We're going to be blunt because we like you:
- Your broker matters more than your code. Execution speed, spread widening during news, slippage – these directly determine whether this strategy makes or loses money.
- Dynamic sizing cuts both ways. When the account shrinks, lots shrink too. But during the drawdown? Those lots were still sized off the peak balance. It hurts.
- The 3× loss factor means uncomfortable drawdowns. You will sit through red. By design.
- Those calibration numbers (1316, 11.844, 3.948) are instrument-specific. They're not magic constants. They were fitted to gold. You can't just slap them on EUR/USD and expect the same behavior.
Demo first. Always.
Want a Scalper Calibrated to Your Setup?
We've built HFT scalpers for gold, forex majors, crosses, crypto, and futures. The lot formulas, spread thresholds, and equity targets get calibrated specifically for your broker, instrument, and risk appetite. No copy-paste – custom.


