Advertisement IC Markets

Free SMC Trading Setups

Discuss 1-minute to 15-minute price action setups, fading intraday momentum, key support/resistance zones, and proven short-term trading methodologies.
PTScalper
Site Admin
Posts: 3027
Joined: Mon Jul 20, 2026 1:28 pm

Re: Free SMC Trading Setups

Post by PTScalper »

MetaTrader 5 (MQL5) Expert Advisor

MQL5 uses the modern CTrade library, making order management significantly cleaner. Array access requires setting timeseries formatting (ArraySetAsSeries) to mirror the typical [0] = current bar logic.

Code: Select all

//+------------------------------------------------------------------+
//|                                              SilverBullet_EA.mq5 |
//+------------------------------------------------------------------+
#property copyright "Execution Engine"
#property version   "1.00"
#include <Trade\Trade.mqh>

input string   StartTime         = "17:00"; 
input string   EndTime           = "18:00"; 
input double   RiskReward        = 2.0;
input double   RiskPercent       = 2.0;
input int      ExpirationBars    = 10;
input int      PivotLeft         = 5;
input int      PivotRight        = 2;
input int      MaxBarsToMSS      = 20;
input int      MaxBarsToFVG      = 15;
input ulong    MagicNumber       = 101101;

CTrade         trade;
int            seq_state         = 0;
datetime       state_time        = 0;
double         mss_trigger_level = 0.0;
double         sweep_extreme     = 0.0;
double         last_ph           = 0.0;
double         last_pl           = 0.0;
datetime       last_bar_time     = 0;

int OnInit()
{
    trade.SetExpertMagicNumber(MagicNumber);
    return(INIT_SUCCEEDED);
}

void OnTick()
{
    datetime time_array[];
    CopyTime(_Symbol, _Period, 0, 1, time_array);
    if(time_array[0] == last_bar_time) return;
    last_bar_time = time_array[0];

    // Load arrays
    double High[], Low[], Close[], Open[];
    ArraySetAsSeries(High, true); ArraySetAsSeries(Low, true);
    ArraySetAsSeries(Close, true); ArraySetAsSeries(Open, true);
    
    CopyHigh(_Symbol, _Period, 0, PivotLeft + PivotRight + 5, High);
    CopyLow(_Symbol, _Period, 0, PivotLeft + PivotRight + 5, Low);
    CopyClose(_Symbol, _Period, 0, 4, Close);
    CopyOpen(_Symbol, _Period, 0, 4, Open);

    int p_shift = PivotRight + 1;
    
    bool is_ph = true;
    for(int i = 1; i <= PivotLeft; i++)  { if(High[p_shift+i] > High[p_shift]) is_ph = false; }
    for(int i = 1; i <= PivotRight; i++) { if(High[p_shift-i] >= High[p_shift]) is_ph = false; }
    if(is_ph) last_ph = High[p_shift];

    bool is_pl = true;
    for(int i = 1; i <= PivotLeft; i++)  { if(Low[p_shift+i] < Low[p_shift]) is_pl = false; }
    for(int i = 1; i <= PivotRight; i++) { if(Low[p_shift-i] <= Low[p_shift]) is_pl = false; }
    if(is_pl) last_pl = Low[p_shift];

    int bars_passed = iBarShift(_Symbol, _Period, state_time);

    // Step 1: Detect Sweep
    if(High[1] > last_ph && last_ph > 0 && seq_state != 1 && seq_state != 2)
    {
        seq_state = 1; state_time = time_array[0];
        mss_trigger_level = last_pl; sweep_extreme = High[1];
    }
    else if(Low[1] < last_pl && last_pl > 0 && seq_state != -1 && seq_state != -2)
    {
        seq_state = -1; state_time = time_array[0];
        mss_trigger_level = last_ph; sweep_extreme = Low[1];
    }

    if(seq_state == 1 && High[1] > sweep_extreme) sweep_extreme = High[1];
    if(seq_state == -1 && Low[1] < sweep_extreme) sweep_extreme = Low[1];

    // Step 2: Detect MSS
    if(seq_state == 1 && Close[1] < mss_trigger_level)
    {
        if(bars_passed <= MaxBarsToMSS) { seq_state = 2; state_time = time_array[0]; }
        else seq_state = 0;
    }
    else if(seq_state == -1 && Close[1] > mss_trigger_level)
    {
        if(bars_passed <= MaxBarsToMSS) { seq_state = -2; state_time = time_array[0]; }
        else seq_state = 0;
    }

    // Step 3: FVG Execution
    if(!IsWithinWindow()) return;
    if(PositionsTotal() > 0 || OrdersTotal() > 0) return;

    bool bull_fvg = (Low[1] > High[3] && Close[2] > Open[2]);
    bool bear_fvg = (High[1] < Low[3] && Close[2] < Open[2]);

    double lot_size = CalculateLotSize(MathAbs(Close[1] - sweep_extreme));

    if(bull_fvg && seq_state == -2 && bars_passed <= MaxBarsToFVG)
    {
        double entry = High[3];
        double sl = sweep_extreme;
        double tp = entry + ((entry - sl) * RiskReward);
        datetime exp = TimeCurrent() + (ExpirationBars * PeriodSeconds());
        
        if(trade.BuyLimit(lot_size, entry, _Symbol, sl, tp, ORDER_TIME_SPECIFIED, exp, "SB Long")) seq_state = 0;
    }
    
    if(bear_fvg && seq_state == 2 && bars_passed <= MaxBarsToFVG)
    {
        double entry = Low[3];
        double sl = sweep_extreme;
        double tp = entry - ((sl - entry) * RiskReward);
        datetime exp = TimeCurrent() + (ExpirationBars * PeriodSeconds());
        
        if(trade.SellLimit(lot_size, entry, _Symbol, sl, tp, ORDER_TIME_SPECIFIED, exp, "SB Short")) seq_state = 0;
    }
}

//+------------------------------------------------------------------+
bool IsWithinWindow()
{
    MqlDateTime dt;
    TimeToStruct(TimeCurrent(), dt);
    string current_time = StringFormat("%02d:%02d", dt.hour, dt.min);
    return (current_time >= StartTime && current_time <= EndTime);
}

double CalculateLotSize(double risk_points)
{
    if(risk_points == 0) return 0.01;
    double risk_amount = AccountInfoDouble(ACCOUNT_BALANCE) * (RiskPercent / 100.0);
    double tick_value = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_VALUE);
    double tick_size = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_SIZE);
    double step = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP);
    
    double lots = risk_amount / ((risk_points / tick_size) * tick_value);
    return MathMax(SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN), NormalizeDouble(MathFloor(lots/step)*step, 2));
}
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
Recommended broker for automated trading & scalping IC Markets
PTScalper
Site Admin
Posts: 3027
Joined: Mon Jul 20, 2026 1:28 pm

Re: Free SMC Trading Setups

Post by PTScalper »

Adding visual objects (lines and rectangles) in MQL5 requires interacting with the ObjectCreate and ObjectSetInteger functions, and capturing the specific datetime values of the fractals so the engine knows exactly where on the X-axis to anchor the drawings.

To keep the chart clean during testing, we also need to append a unique timestamp to each object's name and implement an OnDeinit() function to automatically delete the EA's visual elements when you remove it from the chart or restart a backtest.

Here is the updated MQL5 Expert Advisor with full visual debugging for the Market Structure Shifts (Trendlines) and Fair Value Gaps (Filled Rectangles).
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
PTScalper
Site Admin
Posts: 3027
Joined: Mon Jul 20, 2026 1:28 pm

Re: Free SMC Trading Setups

Post by PTScalper »

Updated MT5 Expert Advisor (with Visual Debugging)

Code: Select all

//+------------------------------------------------------------------+
//|                                     SilverBullet_EA_Visual.mq5   |
//+------------------------------------------------------------------+
#property copyright "Execution Engine"
#property version   "1.10"
#include <Trade\Trade.mqh>

input string   StartTime         = "17:00"; 
input string   EndTime           = "18:00"; 
input double   RiskReward        = 2.0;
input double   RiskPercent       = 2.0;
input int      ExpirationBars    = 10;
input int      PivotLeft         = 5;
input int      PivotRight        = 2;
input int      MaxBarsToMSS      = 20;
input int      MaxBarsToFVG      = 15;
input ulong    MagicNumber       = 101101;

CTrade         trade;
int            seq_state         = 0;
datetime       state_time        = 0;

double         mss_trigger_level = 0.0;
datetime       mss_trigger_time  = 0; // Tracks X-axis anchor for the MSS line
double         sweep_extreme     = 0.0;

double         last_ph           = 0.0;
datetime       last_ph_time      = 0;
double         last_pl           = 0.0;
datetime       last_pl_time      = 0;

datetime       last_bar_time     = 0;

int OnInit()
{
    trade.SetExpertMagicNumber(MagicNumber);
    return(INIT_SUCCEEDED);
}

void OnDeinit(const int reason)
{
    // Clean up all visual objects created by this EA when removed
    ObjectsDeleteAll(0, "SB_");
}

void OnTick()
{
    datetime time_array[];
    CopyTime(_Symbol, _Period, 0, PivotLeft + PivotRight + 5, time_array);
    if(time_array[0] == last_bar_time) return;
    last_bar_time = time_array[0];

    // Load arrays
    double High[], Low[], Close[], Open[];
    ArraySetAsSeries(High, true); ArraySetAsSeries(Low, true);
    ArraySetAsSeries(Close, true); ArraySetAsSeries(Open, true);
    ArraySetAsSeries(time_array, true);
    
    CopyHigh(_Symbol, _Period, 0, PivotLeft + PivotRight + 5, High);
    CopyLow(_Symbol, _Period, 0, PivotLeft + PivotRight + 5, Low);
    CopyClose(_Symbol, _Period, 0, 4, Close);
    CopyOpen(_Symbol, _Period, 0, 4, Open);

    int p_shift = PivotRight + 1;
    
    // 1. Fractal Identification (Price + Time)
    bool is_ph = true;
    for(int i = 1; i <= PivotLeft; i++)  { if(High[p_shift+i] > High[p_shift]) is_ph = false; }
    for(int i = 1; i <= PivotRight; i++) { if(High[p_shift-i] >= High[p_shift]) is_ph = false; }
    if(is_ph) { last_ph = High[p_shift]; last_ph_time = time_array[p_shift]; }

    bool is_pl = true;
    for(int i = 1; i <= PivotLeft; i++)  { if(Low[p_shift+i] < Low[p_shift]) is_pl = false; }
    for(int i = 1; i <= PivotRight; i++) { if(Low[p_shift-i] <= Low[p_shift]) is_pl = false; }
    if(is_pl) { last_pl = Low[p_shift]; last_pl_time = time_array[p_shift]; }

    int bars_passed = iBarShift(_Symbol, _Period, state_time);

    // 2. Step 1: Detect Sweep & Anchor the MSS Level
    if(High[1] > last_ph && last_ph > 0 && seq_state != 1 && seq_state != 2)
    {
        seq_state = 1; state_time = time_array[0];
        mss_trigger_level = last_pl; mss_trigger_time = last_pl_time; 
        sweep_extreme = High[1];
    }
    else if(Low[1] < last_pl && last_pl > 0 && seq_state != -1 && seq_state != -2)
    {
        seq_state = -1; state_time = time_array[0];
        mss_trigger_level = last_ph; mss_trigger_time = last_ph_time; 
        sweep_extreme = Low[1];
    }

    if(seq_state == 1 && High[1] > sweep_extreme) sweep_extreme = High[1];
    if(seq_state == -1 && Low[1] < sweep_extreme) sweep_extreme = Low[1];

    // 3. Step 2: Detect MSS & Draw Line
    if(seq_state == 1 && Close[1] < mss_trigger_level)
    {
        if(bars_passed <= MaxBarsToMSS) { 
            seq_state = 2; state_time = time_array[0]; 
            DrawMSSLine("SB_MSS_" + IntegerToString(GetTickCount()), mss_trigger_time, mss_trigger_level, time_array[1], clrRed);
        }
        else seq_state = 0;
    }
    else if(seq_state == -1 && Close[1] > mss_trigger_level)
    {
        if(bars_passed <= MaxBarsToMSS) { 
            seq_state = -2; state_time = time_array[0]; 
            DrawMSSLine("SB_MSS_" + IntegerToString(GetTickCount()), mss_trigger_time, mss_trigger_level, time_array[1], clrGreen);
        }
        else seq_state = 0;
    }

    // 4. Step 3: FVG Execution & Draw Box
    if(!IsWithinWindow()) return;
    if(PositionsTotal() > 0 || OrdersTotal() > 0) return;

    bool bull_fvg = (Low[1] > High[3] && Close[2] > Open[2]);
    bool bear_fvg = (High[1] < Low[3] && Close[2] < Open[2]);

    double lot_size = CalculateLotSize(MathAbs(Close[1] - sweep_extreme));
    datetime exp_time = TimeCurrent() + (ExpirationBars * PeriodSeconds());

    if(bull_fvg && seq_state == -2 && bars_passed <= MaxBarsToFVG)
    {
        double entry = High[3];
        double sl = sweep_extreme;
        double tp = entry + ((entry - sl) * RiskReward);
        
        if(trade.BuyLimit(lot_size, entry, _Symbol, sl, tp, ORDER_TIME_SPECIFIED, exp_time, "SB Long")) 
        {
            DrawFVGBox("SB_FVG_" + IntegerToString(GetTickCount()), time_array[3], High[3], exp_time, Low[1], clrDarkGreen);
            seq_state = 0;
        }
    }
    
    if(bear_fvg && seq_state == 2 && bars_passed <= MaxBarsToFVG)
    {
        double entry = Low[3];
        double sl = sweep_extreme;
        double tp = entry - ((sl - entry) * RiskReward);
        
        if(trade.SellLimit(lot_size, entry, _Symbol, sl, tp, ORDER_TIME_SPECIFIED, exp_time, "SB Short")) 
        {
            DrawFVGBox("SB_FVG_" + IntegerToString(GetTickCount()), time_array[3], Low[3], exp_time, High[1], clrDarkRed);
            seq_state = 0;
        }
    }
}

//+------------------------------------------------------------------+
//| Helper Visual Functions                                          |
//+------------------------------------------------------------------+
void DrawMSSLine(string name, datetime time1, double price1, datetime time2, color clr)
{
    ObjectCreate(0, name, OBJ_TREND, 0, time1, price1, time2, price1); // time2, price1 keeps it perfectly horizontal
    ObjectSetInteger(0, name, OBJPROP_COLOR, clr);
    ObjectSetInteger(0, name, OBJPROP_STYLE, STYLE_DASH);
    ObjectSetInteger(0, name, OBJPROP_RAY_RIGHT, false);
    ObjectSetInteger(0, name, OBJPROP_WIDTH, 2);
    ObjectSetInteger(0, name, OBJPROP_BACK, true);
    ObjectSetInteger(0, name, OBJPROP_HIDDEN, true); // Hides it from the object list menu
}

void DrawFVGBox(string name, datetime time1, double price1, datetime time2, double price2, color clr)
{
    ObjectCreate(0, name, OBJ_RECTANGLE, 0, time1, price1, time2, price2);
    ObjectSetInteger(0, name, OBJPROP_COLOR, clr);
    ObjectSetInteger(0, name, OBJPROP_BACK, true); // Fills the rectangle
    ObjectSetInteger(0, name, OBJPROP_HIDDEN, true);
}

//+------------------------------------------------------------------+
//| Math & Environment Functions                                     |
//+------------------------------------------------------------------+
bool IsWithinWindow()
{
    MqlDateTime dt;
    TimeToStruct(TimeCurrent(), dt);
    string current_time = StringFormat("%02d:%02d", dt.hour, dt.min);
    return (current_time >= StartTime && current_time <= EndTime);
}

double CalculateLotSize(double risk_points)
{
    if(risk_points == 0) return 0.01;
    double risk_amount = AccountInfoDouble(ACCOUNT_BALANCE) * (RiskPercent / 100.0);
    double tick_value = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_VALUE);
    double tick_size = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_SIZE);
    double step = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP);
    
    double lots = risk_amount / ((risk_points / tick_size) * tick_value);
    return MathMax(SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN), NormalizeDouble(MathFloor(lots/step)*step, 2));
}
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
PTScalper
Site Admin
Posts: 3027
Joined: Mon Jul 20, 2026 1:28 pm

Re: Free SMC Trading Setups

Post by PTScalper »

Visual Engine Improvements:

Dynamic X-Axis Anchoring: time_array is now properly evaluated and mapped. When an MSS line draws, its starting anchor is locked precisely to the timestamp of the swing low/high (mss_trigger_time) that established the structure, while the ending anchor maps to the exact candle breaking that structure.

Object Prefix Targeting: All graphical elements use the "SB_" prefix combined with GetTickCount() to ensure unique naming. When testing completes, OnDeinit() targets that prefix and sweeps the chart clean instantly.

Background Fills: The FVG box uses OBJPROP_BACK set to true, which forces MT5 to render it as a filled color block rather than an empty outline, keeping it visually aligned with how TradingView renders the gap zone.
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
PTScalper
Site Admin
Posts: 3027
Joined: Mon Jul 20, 2026 1:28 pm

Re: Free SMC Trading Setups

Post by PTScalper »

Porting this logic into cTrader (cAlgo) allows us to take advantage of modern C# and the .NET runtime. Unlike MQL, cTrader lets us handle the New York timezone conversion programmatically using .NET's TimeZoneInfo, meaning you no longer have to manually offset the broker time.

We can also leverage C#'s Color.FromArgb() to create perfectly transparent FVG boxes that look native to the chart, and use the Chart object directly for clean rendering.
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
PTScalper
Site Admin
Posts: 3027
Joined: Mon Jul 20, 2026 1:28 pm

Re: Free SMC Trading Setups

Post by PTScalper »

Here is the full C# cBot implementation of the Sweep → MSS → FVG engine.

Code: Select all

using System;
using cAlgo.API;
using cAlgo.API.Internals;
using cAlgo.API.Indicators;

namespace cAlgo.Robots
{
    [Robot(TimeZone = TimeZones.UTC, AccessRights = AccessRights.None)]
    public class SilverBulletVisual : Robot
    {
        // --- 1. User Inputs ---
        [Parameter("Start Time (NY)", DefaultValue = "10:00", Group = "Time Window")]
        public string StartTimeStr { get; set; }

        [Parameter("End Time (NY)", DefaultValue = "11:00", Group = "Time Window")]
        public string EndTimeStr { get; set; }

        [Parameter("Risk:Reward Ratio", DefaultValue = 2.0, Group = "Risk Management")]
        public double RiskReward { get; set; }

        [Parameter("Risk Percent", DefaultValue = 2.0, Group = "Risk Management")]
        public double RiskPercent { get; set; }

        [Parameter("Limit Expiration (Bars)", DefaultValue = 10, Group = "Risk Management")]
        public int ExpirationBars { get; set; }

        [Parameter("Pivot Left Lookback", DefaultValue = 5, Group = "Structure")]
        public int PivotLeft { get; set; }

        [Parameter("Pivot Right Lookahead", DefaultValue = 2, Group = "Structure")]
        public int PivotRight { get; set; }

        [Parameter("Max Bars Sweep to MSS", DefaultValue = 20, Group = "Structure")]
        public int MaxBarsToMss { get; set; }

        [Parameter("Max Bars MSS to FVG", DefaultValue = 15, Group = "Structure")]
        public int MaxBarsToFvg { get; set; }

        // --- 2. State Machine Variables ---
        private int _seqState = 0;
        private int _stateBarIndex = 0;
        private double _mssTriggerLevel = 0.0;
        private DateTime _mssTriggerTime;
        private double _sweepExtreme = 0.0;

        private double _lastPh = 0.0;
        private DateTime _lastPhTime;
        private double _lastPl = 0.0;
        private DateTime _lastPlTime;

        private TimeSpan _startTime;
        private TimeSpan _endTime;
        private TimeZoneInfo _nyTimeZone;

        protected override void OnStart()
        {
            // Parse target window
            TimeSpan.TryParse(StartTimeStr, out _startTime);
            TimeSpan.TryParse(EndTimeStr, out _endTime);
            
            // Native .NET timezone handling for robust NY mapping regardless of broker server time
            _nyTimeZone = TimeZoneInfo.FindSystemTimeZoneById("Eastern Standard Time");
        }

        protected override void OnBar()
        {
            int pShift = PivotRight + 1;

            // --- 3. Fractal Identification ---
            bool isPh = true;
            for (int i = 1; i <= PivotLeft; i++) { if (Bars.HighPrices.Last(pShift + i) > Bars.HighPrices.Last(pShift)) isPh = false; }
            for (int i = 1; i <= PivotRight; i++) { if (Bars.HighPrices.Last(pShift - i) >= Bars.HighPrices.Last(pShift)) isPh = false; }

            if (isPh)
            {
                _lastPh = Bars.HighPrices.Last(pShift);
                _lastPhTime = Bars.OpenTimes.Last(pShift);
            }

            bool isPl = true;
            for (int i = 1; i <= PivotLeft; i++) { if (Bars.LowPrices.Last(pShift + i) < Bars.LowPrices.Last(pShift)) isPl = false; }
            for (int i = 1; i <= PivotRight; i++) { if (Bars.LowPrices.Last(pShift - i) <= Bars.LowPrices.Last(pShift)) isPl = false; }

            if (isPl)
            {
                _lastPl = Bars.LowPrices.Last(pShift);
                _lastPlTime = Bars.OpenTimes.Last(pShift);
            }

            int barsPassed = Bars.Count - _stateBarIndex;
            double high1 = Bars.HighPrices.Last(1);
            double low1 = Bars.LowPrices.Last(1);
            double close1 = Bars.ClosePrices.Last(1);
            double open1 = Bars.OpenPrices.Last(1);
            DateTime time1 = Bars.OpenTimes.Last(1);     // Last closed bar
            DateTime time0 = Bars.OpenTimes.Last(0);     // Current forming bar

            // --- 4. Step 1: Detect Sweep & Anchor MSS ---
            if (high1 > _lastPh && _lastPh > 0 && _seqState != 1 && _seqState != 2)
            {
                _seqState = 1; _stateBarIndex = Bars.Count;
                _mssTriggerLevel = _lastPl; _mssTriggerTime = _lastPlTime;
                _sweepExtreme = high1;
            }
            else if (low1 < _lastPl && _lastPl > 0 && _seqState != -1 && _seqState != -2)
            {
                _seqState = -1; _stateBarIndex = Bars.Count;
                _mssTriggerLevel = _lastPh; _mssTriggerTime = _lastPhTime;
                _sweepExtreme = low1;
            }

            // Track extreme wick during sweep for precise SL placement
            if (_seqState == 1 && high1 > _sweepExtreme) _sweepExtreme = high1;
            if (_seqState == -1 && low1 < _sweepExtreme) _sweepExtreme = low1;

            // --- 5. Step 2: Detect MSS & Draw Line ---
            if (_seqState == 1 && close1 < _mssTriggerLevel)
            {
                if (barsPassed <= MaxBarsToMss)
                {
                    _seqState = 2; _stateBarIndex = Bars.Count;
                    DrawMssLine($"SB_MSS_{Bars.Count}", _mssTriggerTime, _mssTriggerLevel, time1, Color.Red);
                }
                else _seqState = 0;
            }
            else if (_seqState == -1 && close1 > _mssTriggerLevel)
            {
                if (barsPassed <= MaxBarsToMss)
                {
                    _seqState = -2; _stateBarIndex = Bars.Count;
                    DrawMssLine($"SB_MSS_{Bars.Count}", _mssTriggerTime, _mssTriggerLevel, time1, Color.LimeGreen);
                }
                else _seqState = 0;
            }

            // --- 6. Step 3: FVG Execution ---
            if (!IsWithinWindow(time0)) return;
            if (Positions.Count > 0 || PendingOrders.Count > 0) return;

            double high3 = Bars.HighPrices.Last(3);
            double low3 = Bars.LowPrices.Last(3);
            double close2 = Bars.ClosePrices.Last(2);
            double open2 = Bars.OpenPrices.Last(2);
            DateTime time3 = Bars.OpenTimes.Last(3);

            bool bullFvg = (low1 > high3 && close2 > open2);
            bool bearFvg = (high1 < low3 && close2 < open2);

            if (!bullFvg && !bearFvg) return;

            double riskAmount = Account.Balance * (RiskPercent / 100.0);
            double riskRaw = Math.Abs(close1 - _sweepExtreme);
            if (riskRaw == 0) return;

            // Volume calculation logic based on tick sizes
            double volume = (riskAmount / (riskRaw / Symbol.TickSize * Symbol.TickValue)) * Symbol.VolumeInUnitsMin;
            volume = Symbol.NormalizeVolumeInUnits(volume, RoundingMode.Down);
            if (volume < Symbol.VolumeInUnitsMin) return;

            // Expiration timestamp mapping
            DateTime expTime = Server.Time.AddMinutes(ExpirationBars * TimeFrame.ToTimeSpan().TotalMinutes);

            if (bullFvg && _seqState == -2 && barsPassed <= MaxBarsToFvg)
            {
                double entry = high3;
                double sl = _sweepExtreme;
                double tp = entry + ((entry - sl) * RiskReward);

                var res = PlaceLimitOrder(TradeType.Buy, SymbolName, volume, entry, "SB Long", sl, tp, expTime);
                if (res.IsSuccessful)
                {
                    // ARGB color formulation creates the transparent fill look on the chart
                    DrawFvgBox($"SB_FVG_{Bars.Count}", time3, entry, expTime, low1, Color.FromArgb(70, Color.LimeGreen));
                    _seqState = 0;
                }
            }
            
            if (bearFvg && _seqState == 2 && barsPassed <= MaxBarsToFvg)
            {
                double entry = low3;
                double sl = _sweepExtreme;
                double tp = entry - ((sl - entry) * RiskReward);

                var res = PlaceLimitOrder(TradeType.Sell, SymbolName, volume, entry, "SB Short", sl, tp, expTime);
                if (res.IsSuccessful)
                {
                    DrawFvgBox($"SB_FVG_{Bars.Count}", time3, entry, expTime, high1, Color.FromArgb(70, Color.Crimson));
                    _seqState = 0;
                }
            }
        }

        // --- 7. Chart Rendering ---
        private void DrawMssLine(string name, DateTime time1, double price1, DateTime time2, Color clr)
        {
            var line = Chart.DrawTrendLine(name, time1, price1, time2, price1, clr);
            line.LineStyle = LineStyle.Lines;
            line.Thickness = 2;
            line.IsInteractive = false;
        }

        private void DrawFvgBox(string name, DateTime time1, double price1, DateTime time2, double price2, Color clr)
        {
            var rect = Chart.DrawRectangle(name, time1, price1, time2, price2, clr);
            rect.IsFilled = true;
            rect.IsInteractive = false;
        }

        // --- 8. Timezone Mapping ---
        private bool IsWithinWindow(DateTime currentBarTimeUtc)
        {
            DateTime nyTime = TimeZoneInfo.ConvertTimeFromUtc(currentBarTimeUtc, _nyTimeZone);
            TimeSpan timeOfDay = nyTime.TimeOfDay;
            return timeOfDay >= _startTime && timeOfDay <= _endTime;
        }
    }
}
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
PTScalper
Site Admin
Posts: 3027
Joined: Mon Jul 20, 2026 1:28 pm

Re: Free SMC Trading Setups

Post by PTScalper »

Architectural Benefits Over MQL:

TimeZoneInfo Environment Logic: You no longer have to guess what UTC offset a broker uses during winter vs summer backtesting. TimeZoneInfo.ConvertTimeFromUtc automatically handles Daylight Saving Time (DST) shifts for New York natively in .NET.

Transparent Object Fills: Color.FromArgb(70, Color.LimeGreen) gives you a perfectly clean, 70/255 opacity fill for the FVG boxes, exactly mirroring how TradingView renders it.

Volume Normalization: Symbol.NormalizeVolumeInUnits(volume, RoundingMode.Down) removes the usual pip-math rounding errors that crash EAs in MT4/MT5 when tick values shift on specific pairs.
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
PTScalper
Site Admin
Posts: 3027
Joined: Mon Jul 20, 2026 1:28 pm

Re: Free SMC Trading Setups

Post by PTScalper »

To implement partial profit taking and break-even stop loss management, we shift from a purely OnBar execution model to a hybrid model. The entry logic remains strictly structural (calculating on the bar close), but the trade management requires an OnTick handler to monitor live price action so it can execute the moment a 1:1 Risk:Reward ratio is reached.

To make this robust against platform restarts, we avoid using volatile global bool flags. Instead, the bot mathematically compares the current Stop Loss to the Entry Price. If they are different, it knows the trade is unmanaged; once the Stop Loss equals the Entry Price, it knows the 50% scale-out has already been executed.
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
PTScalper
Site Admin
Posts: 3027
Joined: Mon Jul 20, 2026 1:28 pm

Re: Free SMC Trading Setups

Post by PTScalper »

Here is the updated C# cBot.

Code: Select all

using System;
using cAlgo.API;
using cAlgo.API.Internals;
using cAlgo.API.Indicators;

namespace cAlgo.Robots
{
    [Robot(TimeZone = TimeZones.UTC, AccessRights = AccessRights.None)]
    public class SilverBulletVisual : Robot
    {
        // --- 1. User Inputs ---
        [Parameter("Start Time (NY)", DefaultValue = "10:00", Group = "Time Window")]
        public string StartTimeStr { get; set; }

        [Parameter("End Time (NY)", DefaultValue = "11:00", Group = "Time Window")]
        public string EndTimeStr { get; set; }

        [Parameter("Risk:Reward Ratio (Final)", DefaultValue = 2.0, Group = "Risk Management")]
        public double RiskReward { get; set; }

        [Parameter("Risk Percent", DefaultValue = 2.0, Group = "Risk Management")]
        public double RiskPercent { get; set; }

        [Parameter("Limit Expiration (Bars)", DefaultValue = 10, Group = "Risk Management")]
        public int ExpirationBars { get; set; }

        [Parameter("Pivot Left Lookback", DefaultValue = 5, Group = "Structure")]
        public int PivotLeft { get; set; }

        [Parameter("Pivot Right Lookahead", DefaultValue = 2, Group = "Structure")]
        public int PivotRight { get; set; }

        [Parameter("Max Bars Sweep to MSS", DefaultValue = 20, Group = "Structure")]
        public int MaxBarsToMss { get; set; }

        [Parameter("Max Bars MSS to FVG", DefaultValue = 15, Group = "Structure")]
        public int MaxBarsToFvg { get; set; }

        // --- 2. State Machine Variables ---
        private int _seqState = 0;
        private int _stateBarIndex = 0;
        private double _mssTriggerLevel = 0.0;
        private DateTime _mssTriggerTime;
        private double _sweepExtreme = 0.0;

        private double _lastPh = 0.0;
        private DateTime _lastPhTime;
        private double _lastPl = 0.0;
        private DateTime _lastPlTime;

        private TimeSpan _startTime;
        private TimeSpan _endTime;
        private TimeZoneInfo _nyTimeZone;

        protected override void OnStart()
        {
            TimeSpan.TryParse(StartTimeStr, out _startTime);
            TimeSpan.TryParse(EndTimeStr, out _endTime);
            _nyTimeZone = TimeZoneInfo.FindSystemTimeZoneById("Eastern Standard Time");
        }

        // --- 3. Live Trade Management (Partial Closes & BE) ---
        protected override void OnTick()
        {
            foreach (var position in Positions)
            {
                // Only manage Silver Bullet trades
                if (position.SymbolName != SymbolName || !position.Label.StartsWith("SB ")) continue;

                // Skip if position has no Stop Loss
                if (!position.StopLoss.HasValue) continue;

                double entry = position.EntryPrice;
                double currentSl = position.StopLoss.Value;

                // State Check: If SL is already at Break-Even, this trade has already been managed.
                // Using TickSize comparison to avoid floating-point math errors.
                if (Math.Abs(currentSl - entry) < Symbol.TickSize / 2) continue;

                // Calculate the original risk to determine the 1R target
                double risk = Math.Abs(entry - currentSl);
                bool isLong = position.TradeType == TradeType.Buy;

                double target1R = isLong ? entry + risk : entry - risk;
                
                // Check if current price has hit the 1:1 threshold
                bool hit1R = isLong ? Symbol.Bid >= target1R : Symbol.Ask <= target1R;

                if (hit1R)
                {
                    // 1. Calculate 50% of current volume, normalized to broker step sizes
                    double volumeToClose = Symbol.NormalizeVolumeInUnits(position.VolumeInUnits / 2, RoundingMode.Down);
                    
                    // 2. Execute Partial Close (Ensure it meets broker minimums)
                    if (volumeToClose >= Symbol.VolumeInUnitsMin)
                    {
                        ClosePosition(position, volumeToClose);
                    }

                    // 3. Move remaining Stop Loss to Break-Even
                    ModifyPosition(position, entry, position.TakeProfit);
                }
            }
        }

        protected override void OnBar()
        {
            int pShift = PivotRight + 1;

            // --- 4. Fractal Identification ---
            bool isPh = true;
            for (int i = 1; i <= PivotLeft; i++) { if (Bars.HighPrices.Last(pShift + i) > Bars.HighPrices.Last(pShift)) isPh = false; }
            for (int i = 1; i <= PivotRight; i++) { if (Bars.HighPrices.Last(pShift - i) >= Bars.HighPrices.Last(pShift)) isPh = false; }

            if (isPh)
            {
                _lastPh = Bars.HighPrices.Last(pShift);
                _lastPhTime = Bars.OpenTimes.Last(pShift);
            }

            bool isPl = true;
            for (int i = 1; i <= PivotLeft; i++) { if (Bars.LowPrices.Last(pShift + i) < Bars.LowPrices.Last(pShift)) isPl = false; }
            for (int i = 1; i <= PivotRight; i++) { if (Bars.LowPrices.Last(pShift - i) <= Bars.LowPrices.Last(pShift)) isPl = false; }

            if (isPl)
            {
                _lastPl = Bars.LowPrices.Last(pShift);
                _lastPlTime = Bars.OpenTimes.Last(pShift);
            }

            int barsPassed = Bars.Count - _stateBarIndex;
            double high1 = Bars.HighPrices.Last(1);
            double low1 = Bars.LowPrices.Last(1);
            double close1 = Bars.ClosePrices.Last(1);
            double open1 = Bars.OpenPrices.Last(1);
            DateTime time1 = Bars.OpenTimes.Last(1);     
            DateTime time0 = Bars.OpenTimes.Last(0);     

            // --- 5. Step 1: Detect Sweep & Anchor MSS ---
            if (high1 > _lastPh && _lastPh > 0 && _seqState != 1 && _seqState != 2)
            {
                _seqState = 1; _stateBarIndex = Bars.Count;
                _mssTriggerLevel = _lastPl; _mssTriggerTime = _lastPlTime;
                _sweepExtreme = high1;
            }
            else if (low1 < _lastPl && _lastPl > 0 && _seqState != -1 && _seqState != -2)
            {
                _seqState = -1; _stateBarIndex = Bars.Count;
                _mssTriggerLevel = _lastPh; _mssTriggerTime = _lastPhTime;
                _sweepExtreme = low1;
            }

            if (_seqState == 1 && high1 > _sweepExtreme) _sweepExtreme = high1;
            if (_seqState == -1 && low1 < _sweepExtreme) _sweepExtreme = low1;

            // --- 6. Step 2: Detect MSS & Draw Line ---
            if (_seqState == 1 && close1 < _mssTriggerLevel)
            {
                if (barsPassed <= MaxBarsToMss)
                {
                    _seqState = 2; _stateBarIndex = Bars.Count;
                    DrawMssLine($"SB_MSS_{Bars.Count}", _mssTriggerTime, _mssTriggerLevel, time1, Color.Red);
                }
                else _seqState = 0;
            }
            else if (_seqState == -1 && close1 > _mssTriggerLevel)
            {
                if (barsPassed <= MaxBarsToMss)
                {
                    _seqState = -2; _stateBarIndex = Bars.Count;
                    DrawMssLine($"SB_MSS_{Bars.Count}", _mssTriggerTime, _mssTriggerLevel, time1, Color.LimeGreen);
                }
                else _seqState = 0;
            }

            // --- 7. Step 3: FVG Execution ---
            if (!IsWithinWindow(time0)) return;
            if (Positions.Count > 0 || PendingOrders.Count > 0) return;

            double high3 = Bars.HighPrices.Last(3);
            double low3 = Bars.LowPrices.Last(3);
            double close2 = Bars.ClosePrices.Last(2);
            double open2 = Bars.OpenPrices.Last(2);
            DateTime time3 = Bars.OpenTimes.Last(3);

            bool bullFvg = (low1 > high3 && close2 > open2);
            bool bearFvg = (high1 < low3 && close2 < open2);

            if (!bullFvg && !bearFvg) return;

            double riskAmount = Account.Balance * (RiskPercent / 100.0);
            double riskRaw = Math.Abs(close1 - _sweepExtreme);
            if (riskRaw == 0) return;

            double volume = (riskAmount / (riskRaw / Symbol.TickSize * Symbol.TickValue)) * Symbol.VolumeInUnitsMin;
            volume = Symbol.NormalizeVolumeInUnits(volume, RoundingMode.Down);
            if (volume < Symbol.VolumeInUnitsMin) return;

            DateTime expTime = Server.Time.AddMinutes(ExpirationBars * TimeFrame.ToTimeSpan().TotalMinutes);

            if (bullFvg && _seqState == -2 && barsPassed <= MaxBarsToFvg)
            {
                double entry = high3;
                double sl = _sweepExtreme;
                double tp = entry + ((entry - sl) * RiskReward);

                var res = PlaceLimitOrder(TradeType.Buy, SymbolName, volume, entry, "SB Long", sl, tp, expTime);
                if (res.IsSuccessful)
                {
                    DrawFvgBox($"SB_FVG_{Bars.Count}", time3, entry, expTime, low1, Color.FromArgb(70, Color.LimeGreen));
                    _seqState = 0;
                }
            }
            
            if (bearFvg && _seqState == 2 && barsPassed <= MaxBarsToFvg)
            {
                double entry = low3;
                double sl = _sweepExtreme;
                double tp = entry - ((sl - entry) * RiskReward);

                var res = PlaceLimitOrder(TradeType.Sell, SymbolName, volume, entry, "SB Short", sl, tp, expTime);
                if (res.IsSuccessful)
                {
                    DrawFvgBox($"SB_FVG_{Bars.Count}", time3, entry, expTime, high1, Color.FromArgb(70, Color.Crimson));
                    _seqState = 0;
                }
            }
        }

        // --- 8. Chart Rendering ---
        private void DrawMssLine(string name, DateTime time1, double price1, DateTime time2, Color clr)
        {
            var line = Chart.DrawTrendLine(name, time1, price1, time2, price1, clr);
            line.LineStyle = LineStyle.Lines;
            line.Thickness = 2;
            line.IsInteractive = false;
        }

        private void DrawFvgBox(string name, DateTime time1, double price1, DateTime time2, double price2, Color clr)
        {
            var rect = Chart.DrawRectangle(name, time1, price1, time2, price2, clr);
            rect.IsFilled = true;
            rect.IsInteractive = false;
        }

        // --- 9. Timezone Mapping ---
        private bool IsWithinWindow(DateTime currentBarTimeUtc)
        {
            DateTime nyTime = TimeZoneInfo.ConvertTimeFromUtc(currentBarTimeUtc, _nyTimeZone);
            TimeSpan timeOfDay = nyTime.TimeOfDay;
            return timeOfDay >= _startTime && timeOfDay <= _endTime;
        }
    }
}
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
PTScalper
Site Admin
Posts: 3027
Joined: Mon Jul 20, 2026 1:28 pm

Re: Free SMC Trading Setups

Post by PTScalper »

A note on lot sizes for partial execution: Broker volume rules apply here. If your account size and 2% risk rule calculate an initial entry volume of 0.01 lots (the broker minimum), cTrader mathematically cannot close 50% of it.
In this scenario, the engine acts smartly: it will detect that the 50% volume is below the broker minimum, bypass the partial close, and strictly move the Stop Loss to Break-Even for the full 0.01 volume instead.
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
Post Reply