存成 MQL5/Scripts/Lesson13Sizing.mq5。它把 tick size 與 tick value 換成 1% 風險對應的手數、拿券商的限制驗證它,並檢查保證金。
//+------------------------------------------------------------------+
//| Lesson 13 - Risk-based position sizing for CFD |
//+------------------------------------------------------------------+
#property copyright "Strategist Academy"
#property version "1.00"
//+------------------------------------------------------------------+
//| Round volume down onto the broker's step |
//+------------------------------------------------------------------+
double NormalizeVolumeDown(const string sym, double vol)
{
double vMin = SymbolInfoDouble(sym, SYMBOL_VOLUME_MIN);
double vMax = SymbolInfoDouble(sym, SYMBOL_VOLUME_MAX);
double vStep = SymbolInfoDouble(sym, SYMBOL_VOLUME_STEP);
vol = MathMax(vMin, MathMin(vMax, vol));
if(vStep > 0)
vol = vMin + MathFloor((vol - vMin) / vStep + 0.0000001) * vStep;
return NormalizeDouble(vol, 2);
}
//+------------------------------------------------------------------+
//| Lots such that a stop of stopPoints costs at most riskMoney |
//+------------------------------------------------------------------+
double CalcLotsForRisk(const string sym, const double riskMoney, const double stopPoints)
{
double tickValue = SymbolInfoDouble(sym, SYMBOL_TRADE_TICK_VALUE);
double tickSize = SymbolInfoDouble(sym, SYMBOL_TRADE_TICK_SIZE);
if(tickValue <= 0 || tickSize <= 0 || stopPoints <= 0)
return 0.0;
// stopPoints * _Point is a PRICE distance. tickValue is per TICK, so
// convert price distance into ticks before multiplying.
double stopTicks = (stopPoints * _Point) / tickSize;
double riskPerLot = stopTicks * tickValue;
if(riskPerLot <= 0)
return 0.0;
return NormalizeVolumeDown(sym, riskMoney / riskPerLot);
}
void OnStart()
{
string sym = _Symbol;
double equity = AccountInfoDouble(ACCOUNT_EQUITY);
double riskPct = 1.0; // risk 1% of equity
double riskMoney = equity * riskPct / 100.0;
double tickValue = SymbolInfoDouble(sym, SYMBOL_TRADE_TICK_VALUE);
double tickSize = SymbolInfoDouble(sym, SYMBOL_TRADE_TICK_SIZE);
double contract = SymbolInfoDouble(sym, SYMBOL_TRADE_CONTRACT_SIZE);
long stopsLevel= SymbolInfoInteger(sym, SYMBOL_TRADE_STOPS_LEVEL);
Print("=== Sizing on ", sym, " ===");
Print("Equity: ", DoubleToString(equity, 2));
Print("Risk budget: ", DoubleToString(riskMoney, 2), " (", riskPct, "%)");
Print("Tick size: ", DoubleToString(tickSize, _Digits));
Print("Tick value: ", DoubleToString(tickValue, 2), " per lot per tick");
Print("Contract size: ", DoubleToString(contract, 2));
Print("Point: ", DoubleToString(_Point, _Digits),
" (point vs tick differ? ", (MathAbs(_Point - tickSize) > 0.0000000001 ? "YES" : "no"), ")");
Print("Stops level: ", stopsLevel, " points");
Print("Swap long/short: ", DoubleToString(SymbolInfoDouble(sym, SYMBOL_SWAP_LONG), 2), " / ",
DoubleToString(SymbolInfoDouble(sym, SYMBOL_SWAP_SHORT), 2));
// Try a few stop distances and see what 1% risk actually buys.
double testStops[4] = {50, 100, 200, 400};
for(int i = 0; i < 4; i++)
{
double sp = testStops[i];
double lots = CalcLotsForRisk(sym, riskMoney, sp);
string note = "";
if(sp < (double)stopsLevel)
note = " <-- inside broker stops level, would be REJECTED";
else if(lots <= 0)
note = " <-- too small for this symbol";
Print("Stop ", DoubleToString(sp, 0), " pts -> lots ", DoubleToString(lots, 2), note);
}
// Margin check on a candidate size
double lots = CalcLotsForRisk(sym, riskMoney, 200);
if(lots > 0)
{
double price = SymbolInfoDouble(sym, SYMBOL_ASK);
double margin = 0.0;
if(OrderCalcMargin(ORDER_TYPE_BUY, sym, lots, price, margin))
{
double free = AccountInfoDouble(ACCOUNT_MARGIN_FREE);
Print("Required margin for ", DoubleToString(lots, 2), " lots: ",
DoubleToString(margin, 2), " free: ", DoubleToString(free, 2),
" -> ", (margin <= free ? "affordable" : "NOT affordable"));
}
else
Print("OrderCalcMargin failed: ", GetLastError());
}
}