Wednesday, April 1, 2026

Author: Roberto Jacobs (3rjfx) | Featured on Forex Home Expert

Introduction

In the world of algorithmic trading, knowing the trend direction is never enough. The real question is: "How strong is the momentum?" For years, I have kept a secret tool in my personal trading arsenal—an indicator I developed in late 2023 and have continuously refined until its latest update on March 31, 2026. Today, I am officially introducing StochDegree.mq5 to the Forex Home Expert community.

What is StochDegree?

The StochDegree.mq5 is a custom MetaTrader 5 indicator that transforms the traditional Stochastic Oscillator into a unique angular representation. Instead of only showing overbought and oversold levels, it converts Stochastic values into degrees (0°–360°), making trend strength and direction easier to visualize.

Unlike standard oscillators that only show overbought or oversold levels, StochDegree uses a mathematical approach to measure the slope angle of the Stochastic movement. By calculating the Degree (0°–360°) of the rise or fall, traders can objectively see whether a trend is gaining strength or losing steam.

USDJPYH4-Stochastic and StochDegree IndicatorFigure 1: Stochastic and StochDegree Indicator

How it Works (The Logic)

The indicator utilizes the ArcTangent function to convert the price movement and the Stochastic value into a visual degree.

  • The Trigonometry: It calculates the vertical distance between the current Stochastic value and the previous bar, then converts it into a degree from 0° to 360°.
  • Visual Clarity: On your chart, you won't just see lines; you will see a real-time label (e.g., "5.5°") and trend status like "Strong Bullish" or "Down".

Indicator Concept

  • Rise Degree → Indicates bullish momentum.
  • Down Degree → Indicates bearish momentum.
  • Visual cues such as arrows, text labels, and colors are displayed directly on the chart.
  • Alerts and notifications are triggered when significant trend changes occur.

//+------------------------------------------------------------------+
//|                                                  StochDegree.mq5 |
//|        Copyright 2023, Roberto Jacobs (3rjfx) ~ Date: 2023-10-13 |
//|                              https://www.mql5.com/en/users/3rjfx |
//+------------------------------------------------------------------+
#property copyright "Copyright 2023, Roberto Jacobs (3rjfx) ~ Date: 2023-10-13"
#property link      "https://www.mql5.com/en/users/3rjfx"
#property version   "1.00"
/*-- Last Update: Date: 2026-03-31 */
#property indicator_chart_window
//--
#property indicator_buffers 3
#property indicator_plots   2
//--
#property indicator_label1  "Rise Degree" 
#property indicator_type1   DRAW_NONE 
#property indicator_label2  "Down Degree" 
#property indicator_type2   DRAW_NONE
//--
enum YN
 {
   No,
   Yes
 };
//---
input int                StocKperiod = 5;                 // The iStochastic K period (the number of bars for calculation) 
input int                StocDperiod = 3;                 // The iStochastic D period (the period of primary smoothing) 
input int                StocSlowing = 3;                 // Period of final smoothing       
input ENUM_MA_METHOD       ma_method = MODE_EMA;          // Type of smoothing    
input ENUM_STO_PRICE     price_field = STO_LOWHIGH;       // Method of calculation of the Stochastic 
input YN                      alerts = No;               // Display Alerts / Messages (Yes) or (No)
input YN               UseEmailAlert = No;                // Email Alert (Yes) or (No)
input YN               UseSendnotify = No;                // Send Notification (Yes) or (No)
//--
//--- buffers
double RiseDgr[];
double DownDgr[];
double StochMn[]; 
double StochSg[]; 
//--
ENUM_BASE_CORNER 
  corner=CORNER_RIGHT_UPPER;
color stgBull=clrBlue;
color stsBull=clrAqua;
color stsBear=clrYellow;
color stgBear=clrRed;
color txtrbl=clrWhite;
color txtblk=clrBlack;  
//--
int StoHandle;
int fs=67;
int dist_x=150;
int dist_xt=110;
int dist_y=100;
int posalert,
    prevalert;
//--
string dtext;
string indname;
string Albase,AlSubj;
string objname="stodg_";
string RndDg=objname+"RoundedDegrees";
string TxtDg=objname+"TextDegrees";
string ArrUpDg=objname+"ArrUpDegrees";
string ArrDwDg=objname+"ArrDnDegrees";
//--
//--- we will keep the number of values in the iStochastic indicator 
#define minbars 25
//---------//  
***Copyright © 2026 3rjfx ~ For educational purposes only.***

Initialization: OnInit()

The OnInit() function prepares the indicator for use:

  • Buffer Mapping: RiseDgr[], DownDgr[], StochMn[].
  • Stochastic Handle: Connects to the built-in iStochastic function.
  • Indicator Settings: Assigns a short name and sets precision.
  • Error Handling: Stops if initialization fails.

//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- indicator buffers mapping
    SetIndexBuffer(0,RiseDgr,INDICATOR_DATA);
    SetIndexBuffer(1,DownDgr,INDICATOR_DATA);
    SetIndexBuffer(2,StochMn,INDICATOR_CALCULATIONS);
    //--
    StoHandle=iStochastic(Symbol(),Period(),StocKperiod,StocDperiod,StocSlowing,ma_method,price_field);
    if(StoHandle==INVALID_HANDLE) 
      { 
       //--- tell about the failure and output the error code 
       PrintFormat("Failed to create handle of the iStochastic indicator for the symbol %s/%s, error code %d", 
                   Symbol(), 
                   EnumToString(Period()), 
                   GetLastError()); 
       //--- the indicator is stopped early 
       return(INIT_FAILED); 
      } 
    //--
    indname="StochDegrees ("+Symbol()+"~"+strTF(Period())+"~"+string(StocKperiod)+","+string(StocDperiod)+","+string(StocSlowing)+")";
    IndicatorSetString(INDICATOR_SHORTNAME,indname);
    IndicatorSetInteger(INDICATOR_DIGITS,2);
    //--
    return(INIT_SUCCEEDED);
//---
  } //-end OnInit()
//---------//
***Copyright © 2026 3rjfx ~ For educational purposes only.***

Deinitialization: OnDeinit()

When the indicator is removed or the chart changes:

  • Deletes chart objects (arrows, labels, text).
  • Releases the Stochastic handle.
  • Prints the reason for deinitialization using getUninitReasonText().

//+------------------------------------------------------------------+ 
//| Indicator deinitialization function                              | 
//+------------------------------------------------------------------+ 
void OnDeinit(const int reason) 
  { 
//---
   Comment(""); 
   PrintFormat("%s: Deinitialization reason code=%d",__FUNCTION__,reason);
   Print(getUninitReasonText(reason));
   if(StoHandle!=INVALID_HANDLE)  IndicatorRelease(StoHandle);
   //--
   ObjectsDeleteAll(0,objname,-1,-1);
   //--
   return;
//---
  } //-end OnDeinit()
//---------//
***Copyright © 2026 3rjfx ~ For educational purposes only.***

Core Logic: OnCalculate()

This is the main function that runs on every tick or bar update:

  1. Data Preparation: Ensures enough bars and copies Stochastic values.
  2. Degree Calculation: Converts Stochastic values into angles (0°–360°).
  3. Trend Detection: Compares Main vs. Signal line to determine Rise or Down Degree.
  4. Visual Output: Draws arrows and text labels with color coding.
  5. Alerts: Calls PosAlerts() to trigger messages like “Strong Bullish” or “Bearish Reversal.”

//+------------------------------------------------------------------+
//| Custom indicator iteration function                              |
//+------------------------------------------------------------------+
int OnCalculate(const int rates_total,
                const int prev_calculated,
                const datetime &time[],
                const double &open[],
                const double &high[],
                const double &low[],
                const double &close[],
                const long &tick_volume[],
                const long &volume[],
                const int &spread[])
  {
//---
    if(rates_total<minbars) return(0);
    int limit=minbars;
//--- not all data may be calculated
    //--
    int j=0;
    int dtxt=0;
    bool dgrsUp=false,
         dgrsDn=false;
    //--
    color rndclr=clrNONE;
    color arrclr=clrNONE;
    color txtclr=clrNONE;
    //--
    double cur_degrees=0.0;
    double prev1_degrees=0.0;
    double prev2_degrees=0.0;
    //--
    double stcdif=4.5;
    double div1_degrees=0.0;
    double div0_degrees=0.0;
    //--
    ArrayResize(RiseDgr,limit,limit);
    ArrayResize(DownDgr,limit,limit);
    ArrayResize(StochMn,limit,limit);
    ArrayResize(StochSg,limit,limit);
    ArraySetAsSeries(RiseDgr,true);
    ArraySetAsSeries(DownDgr,true);
    ArraySetAsSeries(StochMn,true);
    ArraySetAsSeries(StochSg,true);
    ArraySetAsSeries(open,true);
    ArraySetAsSeries(high,true);
    ArraySetAsSeries(low,true);
    ArraySetAsSeries(close,true);
    ArraySetAsSeries(time,true);
    //--
//--- we can copy not all data
    int to_copy; 
    if(prev_calculated>rates_total || prev_calculated==0) 
      {
       to_copy=rates_total;
      }
    else
      {
       to_copy=rates_total-prev_calculated;
       if(to_copy<limit) to_copy=limit;
      }
    //--
    if(IsStopped()) return(0); //Checking for stop flag
    //-- get iStochastic indicator buffers
    if(CopyBuffer(StoHandle,0,0,to_copy,StochMn)<0)
      {
       Print("Getting the iStochastic indicator Main Line buffers is failed! Error",GetLastError());
       return(0);
      }
    if(CopyBuffer(StoHandle,1,0,to_copy,StochSg)<0)
      {
       Print("Getting the iStochastic indicator Signal Line buffers is failed! Error",GetLastError());
       return(0);
      }
    //--
    for(j=limit-3; j>=0; j--)
      {
        cur_degrees=NormalizeDouble(270+(NonZeroDiv(StochMn[j],100)*180),2);
        prev1_degrees=NormalizeDouble(270+(NonZeroDiv(StochMn[j+1],100)*180),2);
        prev2_degrees=NormalizeDouble(270+(NonZeroDiv(StochMn[j+2],100)*180),2);
        //--
        double pctcurdg=NormalizeDouble((NonZeroDiv(StochMn[j],100)*180),2);
        double pctpr1dg=NormalizeDouble((NonZeroDiv(StochMn[j+1],100)*180),2);
        double pctpr2dg=NormalizeDouble((NonZeroDiv(StochMn[j+2],100)*180),2);
        //--
        if(cur_degrees>360.0)  {cur_degrees=NormalizeDouble(cur_degrees-360.0,2);}
        if(cur_degrees==360.0) {cur_degrees=NormalizeDouble(0.0,2);}
        //- To give a value of 90.0 degrees to the indicator, when the price moves up very quickly.
        if(cur_degrees==90.0)  {cur_degrees=NormalizeDouble(90.0,2);}
        //- To give a value of 270.0 degrees to the indicator, when the price moves down very quickly.
        if(cur_degrees==270.0) {cur_degrees=NormalizeDouble(270.0,2);}
        //--
        div1_degrees=pctpr1dg - pctpr2dg;
        div0_degrees=pctcurdg - pctpr2dg;
        //--
        if((StochMn[j]>StochSg[j])&&(div0_degrees>div1_degrees+stcdif)&&(pctcurdg>pctpr1dg)) {dgrsUp=true; dgrsDn=false; RiseDgr[j]=cur_degrees; DownDgr[j]=0.0;}
        else
        if((StochMn[j]<StochSg[j])&&(div0_degrees<div1_degrees-stcdif)&&(pctcurdg<pctpr1dg)) {dgrsDn=true; dgrsUp=false; DownDgr[j]=cur_degrees; RiseDgr[j]=0.0;}
        //--
        if((cur_degrees>=270.0 && cur_degrees<315.0)&&(dgrsDn==true)) {rndclr=stgBear; arrclr=stgBear; txtclr=txtrbl; posalert=11;}
        else
        if((cur_degrees>=270.0 && cur_degrees<315.0)&&(dgrsUp==true)) {rndclr=stgBear; arrclr=stsBear; txtclr=txtrbl; posalert=12;}
        else
        if((cur_degrees>=315.0 && cur_degrees<360.0)&&(dgrsDn==true)) {rndclr=stsBear; arrclr=stgBear; txtclr=txtblk; posalert=21;}
        else
        if((cur_degrees>=315.0 && cur_degrees<360.0)&&(dgrsUp==true)) {rndclr=stsBear; arrclr=stsBull; txtclr=txtblk; posalert=23;}
        else
        if((cur_degrees>=0.0 && cur_degrees<45.0)&&(dgrsUp==true))    {rndclr=stsBull; arrclr=stgBull; txtclr=txtblk; posalert=34;}
        else
        if((cur_degrees>=0.0 && cur_degrees<45.0)&&(dgrsDn==true))    {rndclr=stsBull; arrclr=stsBear; txtclr=txtblk; posalert=32;}
        else
        if((cur_degrees>=45.0 && cur_degrees<=90.0)&&(dgrsUp==true))  {rndclr=stgBull; arrclr=stgBull; txtclr=txtrbl; posalert=44;}
        else
        if((cur_degrees>=45.0 && cur_degrees<=90.0)&&(dgrsDn==true))  {rndclr=stgBull; arrclr=stsBull; txtclr=txtrbl; posalert=43;}
        //--
        dtext=DoubleToString(cur_degrees,1)+""+CharToString(176);
        if(StringLen(dtext)>5) {dtxt=24;}
        else if(StringLen(dtext)==5) {dtxt=20;}
        else {dtxt=17;}
        //--
        if(j==0)
          {
            //--
            CreateArrowDegrees(0,RndDg,CharToString(108),"Wingdings",fs,rndclr,corner,dist_x,dist_y,true);
            CreateArrowDegrees(0,TxtDg,dtext,"Bodoni MT Black",8,txtclr,corner,dist_xt+dtxt,dist_y+41,true);
            //--
            if(dgrsUp)
              {
                ObjectDelete(0,ArrDwDg);
                //--
                CreateArrowDegrees(0,ArrUpDg,CharToString(217),"Wingdings",23,arrclr,corner,dist_xt+20,dist_y-2,true);
              }
            //--
            else
            if(dgrsDn)
              {
                ObjectDelete(0,ArrUpDg); 
                //--
                CreateArrowDegrees(0,ArrDwDg,CharToString(218),"Wingdings",23,arrclr,corner,dist_xt+20,dist_y+63,true);
              }
            //--
            ChartRedraw(0);
            //--
            PosAlerts(posalert);
          } //-- End if(j)
      }
    //--
    //--- memorize the number of values in the iStochastic indicator 
//--- return value of prev_calculated for next call
    return(rates_total);
//---
  } //-end OnCalculate()
//---------//
//+------------------------------------------------------------------+
***Copyright © 2026 3rjfx ~ For educational purposes only.***

Supporting Functions

  • NonZeroDiv(): Safe division to avoid zero errors.
  • CreateArrowDegrees(): Creates chart labels and arrows.
  • strTF(): Converts timeframe enums into readable strings.
  • MqlReturnDateTime(): Extracts specific parts of datetime.
  • Do_Alerts(): Sends alerts, emails, or push notifications.
  • PosAlerts(): Defines alert messages based on angle and trend.
  • getUninitReasonText(): Returns explanation when indicator stops.
  
double NonZeroDiv(double val1,double val2)
  {
//---
   double resval=0;
   if(val1==0 || val2==0) resval=0.00;
   else
   resval=val1/val2;
   //--
   return(resval);
//---
  } //-end NonZeroDiv()
//---------//

bool CreateArrowDegrees(long   chart_id, 
                        string lable_name, 
                        string label_text,
                        string font_model,
                        int    font_size,
                        color  label_color,
                        int    chart_corner,
                        int    x_cor, 
                        int    y_cor,
                        bool   lable_hidden)
  { 
//--- 
    if(ObjectFind(0,lable_name)<0)
      {
        if(!ObjectCreate(chart_id,lable_name,OBJ_LABEL,0,0,0)) 
          { 
            Print(__FUNCTION__,": failed to create \"Arrow Label\" sign! Error code = ",GetLastError());
            return(false); 
          } 
        else
          {
            ObjectSetString(chart_id,lable_name,OBJPROP_TEXT,label_text);
            ObjectSetString(chart_id,lable_name,OBJPROP_FONT,font_model);
            ObjectSetInteger(chart_id,lable_name,OBJPROP_FONTSIZE,font_size);
            ObjectSetInteger(chart_id,lable_name,OBJPROP_COLOR,label_color);
            ObjectSetInteger(chart_id,lable_name,OBJPROP_CORNER,chart_corner);
            ObjectSetInteger(chart_id,lable_name,OBJPROP_XDISTANCE,x_cor);
            ObjectSetInteger(chart_id,lable_name,OBJPROP_YDISTANCE,y_cor);
            ObjectSetInteger(chart_id,lable_name,OBJPROP_HIDDEN,lable_hidden);
          }
      }
    else
      {
        //--
        ObjectSetString(chart_id,lable_name,OBJPROP_TEXT,label_text);
        ObjectSetString(chart_id,lable_name,OBJPROP_FONT,font_model); 
        ObjectSetInteger(chart_id,lable_name,OBJPROP_FONTSIZE,font_size);
        ObjectSetInteger(chart_id,lable_name,OBJPROP_COLOR,label_color);
        ObjectSetInteger(chart_id,lable_name,OBJPROP_CORNER,chart_corner);
        ObjectSetInteger(chart_id,lable_name,OBJPROP_XDISTANCE,x_cor);
        ObjectSetInteger(chart_id,lable_name,OBJPROP_YDISTANCE,y_cor);
        ObjectSetInteger(chart_id,lable_name,OBJPROP_HIDDEN,lable_hidden);
      }
    //--
    ChartRedraw(0);
    //--- successful execution 
    return(true);
    //--
  } //-end CreateArrowDegrees()
//---------//

string strTF(ENUM_TIMEFRAMES period)
  {
//---
   switch(period)
     {
       //--
       case PERIOD_M1:   return("M1");
       case PERIOD_M2:   return("M2");
       case PERIOD_M3:   return("M3");
       case PERIOD_M4:   return("M4");
       case PERIOD_M5:   return("M5");
       case PERIOD_M6:   return("M6");
       case PERIOD_M10:  return("M10");
       case PERIOD_M12:  return("M12");
       case PERIOD_M15:  return("M15");
       case PERIOD_M20:  return("M20");
       case PERIOD_M30:  return("M30");
       case PERIOD_H1:   return("H1");
       case PERIOD_H2:   return("H2");
       case PERIOD_H3:   return("H3");
       case PERIOD_H4:   return("H4");
       case PERIOD_H6:   return("H6");
       case PERIOD_H8:   return("H8");
       case PERIOD_H12:  return("H12");
       case PERIOD_D1:   return("D1");
       case PERIOD_W1:   return("W1");
       case PERIOD_MN1:  return("MN1");
       //--
     }
   //--
   return(string(period));
//---
  } //-end strTF()  
//---------//

enum TimeReturn
  {
    year        = 0,   // Year 
    mon         = 1,   // Month 
    day         = 2,   // Day 
    hour        = 3,   // Hour 
    min         = 4,   // Minutes 
    sec         = 5,   // Seconds 
    day_of_week = 6,   // Day of week (0-Sunday, 1-Monday, ... ,6-Saturday) 
    day_of_year = 7    // Day number of the year (January 1st is assigned the number value of zero) 
  };
//---------//

int MqlReturnDateTime(datetime reqtime,
                      const int mode) 
  {
//---
    MqlDateTime mqltm;
    TimeToStruct(reqtime,mqltm);
    int valdate=0;
    //--
    switch(mode)
      {
        case 0: valdate=mqltm.year; break;        // Return Year 
        case 1: valdate=mqltm.mon;  break;        // Return Month 
        case 2: valdate=mqltm.day;  break;        // Return Day 
        case 3: valdate=mqltm.hour; break;        // Return Hour 
        case 4: valdate=mqltm.min;  break;        // Return Minutes 
        case 5: valdate=mqltm.sec;  break;        // Return Seconds 
        case 6: valdate=mqltm.day_of_week; break; // Return Day of week (0-Sunday, 1-Monday, ... ,6-Saturday) 
        case 7: valdate=mqltm.day_of_year; break; // Return Day number of the year (January 1st is assigned the number value of zero) 
      }
    return(valdate);
//---
  } //-end MqlReturnDateTime()
//---------//

void Do_Alerts(string msgText)
  {
//---
    //--
    Print("--- "+Symbol()+": "+msgText+
          "\n--- at: ",TimeToString(TimeCurrent(),TIME_DATE|TIME_MINUTES));
    //--
    if(alerts==Yes)
      {
        Alert("--- "+Symbol()+": "+msgText+
              "--- at: ",TimeToString(TimeCurrent(),TIME_DATE|TIME_MINUTES));
      }
    //--
    if(UseEmailAlert==Yes) 
      SendMail(MQLInfoString(MQL_PROGRAM_NAME),"--- "+Symbol()+" "+strTF(Period())+": "+msgText+
                       "\n--- at: "+TimeToString(TimeCurrent(),TIME_DATE|TIME_MINUTES));
    //--
    if(UseSendnotify==Yes) 
      SendNotification(MQLInfoString(MQL_PROGRAM_NAME)+"--- "+Symbol()+" "+strTF(Period())+": "+msgText+
                      "\n--- at: "+TimeToString(iTime(Symbol(),0,0),TIME_DATE|TIME_MINUTES));
    //--
    return;
    //--
//---
  } //-end Do_Alerts()
//---------//

void PosAlerts(int curalerts) 
   {
//---
    if((curalerts!=prevalert)&&(curalerts==43))
       {     
         Albase=indname+" : Position "+dtext;
         AlSubj=Albase+" Trend Began to Fall, Bulish Weakened";
         Do_Alerts(AlSubj);
         prevalert=curalerts;
       }
    //---
    if((curalerts!=prevalert)&&(curalerts==32))
       {     
         Albase=indname+" : Position "+dtext;
         AlSubj=Albase+" Trend was Down, Bulish Reversal";
         Do_Alerts(AlSubj);
         prevalert=curalerts;
       }
    //---
    if((curalerts!=prevalert)&&(curalerts==21))
       {     
         Albase=indname+" : Position "+dtext;
         AlSubj=Albase+" Trend was Down, Bearish Strengthened";
         Do_Alerts(AlSubj);
         prevalert=curalerts;
       }              
    //---
    if((curalerts!=prevalert)&&(curalerts==11))
       {     
         Albase=indname+" : Position "+dtext;
         AlSubj=Albase+" Trend was Down, Strong Bearish";
         Do_Alerts(AlSubj);
         prevalert=curalerts;
       }
    //---//
    if((curalerts!=prevalert)&&(curalerts==12))
       {
         Albase=indname+" : Position "+dtext;
         AlSubj=Albase+" Trend Began to Rise, Bearish Weakened";
         Do_Alerts(AlSubj);
         prevalert=curalerts;
       }
    //---
    if((curalerts!=prevalert)&&(curalerts==23))
       {
         Albase=indname+" : Position "+dtext;
         AlSubj=Albase+" Trend was Up, Bearish Reversal";
         Do_Alerts(AlSubj);
         prevalert=curalerts;
       }
    //---
    if((curalerts!=prevalert)&&(curalerts==34))
       {
         Albase=indname+" : Position "+dtext;
         AlSubj=Albase+" Trend was Up, Bulish Strengthened";
         Do_Alerts(AlSubj);
         prevalert=curalerts;
       }
    //---
    if((curalerts!=prevalert)&&(curalerts==44))
       {
         Albase=indname+" : Position "+dtext;
         AlSubj=Albase+" Trend was Up, Strong Bulish";
         Do_Alerts(AlSubj);
         prevalert=curalerts;
       }
    //---
    return;
//----
   } //-end PosAlerts()
 //---------//

string getUninitReasonText(int reasonCode) 
  { 
//---
   string text=""; 
   //--- 
   switch(reasonCode) 
     { 
       case REASON_PROGRAM:
            text="The EA has stopped working calling by remove function."; break;
       case REASON_REMOVE: 
            text="Program "+__FILE__+" was removed from chart"; break;
       case REASON_RECOMPILE:
            text="Program recompiled."; break;    
       case REASON_CHARTCHANGE: 
            text="Symbol or timeframe was changed"; break;
       case REASON_CHARTCLOSE: 
            text="Chart was closed"; break; 
       case REASON_PARAMETERS: 
            text="Input-parameter was changed"; break;            
       case REASON_ACCOUNT: 
            text="Account was changed"; break; 
       case REASON_TEMPLATE: 
            text="New template was applied to chart"; break; 
       case REASON_INITFAILED:
            text="The OnInit() handler returned a non-zero value."; break;
       case REASON_CLOSE: 
            text="Terminal closed."; break;
       default: text="Another reason"; break;
     } 
   //--
   return text;
//---
  } //-end getUninitReasonText()
//---------//  
***Copyright © 2026 3rjfx ~ For educational purposes only.***

Practical Application

The StochDegree indicator is best used to:

  • Confirm trend strength with angular degrees.
  • Spot reversals earlier than traditional Stochastic signals.
  • Receive alerts for critical turning points.
  • Visualize momentum with arrows and text directly on the chart.

By combining numerical degrees with graphical elements, StochDegree provides a more intuitive way to read market momentum.

Why use StochDegree?

  • 1. No More Guesswork: You don't have to guess if the slope is steep enough. The indicator gives you the exact number.
  • 2. Filtering "Fakeouts": By combining this with other indicators like StepUpDown indicator, you can ignore signals where the "Degree" is too flat (e.g., below 15° ), which usually happens during messy sideways markets.
  • 3.MQL5 Performance: Optimized for MT5, ensuring zero lag and efficient CPU usage even when running multiple pairs.

Conclusion

The StochDegree indicator is a creative enhancement of the Stochastic Oscillator. By converting values into degrees and combining them with visual alerts, it gives traders a clearer picture of market dynamics. Whether you’re looking for strong bullish signals or early bearish reversals, this indicator helps you stay ahead of the curve.

The StochDegree is the result of years of observation and coding. It’s about bringing technical precision and long-term research into the art of trading.

⚠️ Important: Risk Disclaimer

Trading foreign exchange on margin carries a high level of risk and may not be suitable for all investors. The high degree of leverage can work against you as well as for you. Before deciding to invest in foreign exchange, you should carefully consider your investment objectives, level of experience, and risk appetite. The StochDegree indicators provided in this article are for educational purposes and do not guarantee profits. Past performance is not indicative of future results.

Vital Records

If you think the StochDegree is worthy of being used for automated trading as an Expert Advisor, please leave a comment below this article.

If at least 25 people agree that this indicator is worthy of being used as an Expert Advisor, I will create an Expert Advisor based on its signals and share it on this blog.

We hope that this article and the StochDegree indicator program will be useful for traders in learning and generating new ideas, which is ultimately expected to be successful in forex trading.

See you in the next article on Expert Advisor programs or indicators for MetaTrader 4 and MetaTrader 5.

If you have any ideas for developing this indicator program or have a new ideas, please leave your comments below this article.

Thanks for reading this article.

See a complete list of other indicators or Multi-Timeframe indicators, Multi-Currency Expert Advisors, MQL4/MQL5 tutorials and Python program, and algorithmic trading tools at:

Note: Please see the source program and download at the bottom of this article.

Risk Warning: Trading Forex and CFDs involves significant risk and may not be suitable for all investors. All content provided is for educational purposes only.


//+------------------------------------------------------------------+
//|                                                  StochDegree.mq5 |
//|        Copyright 2023, Roberto Jacobs (3rjfx) ~ Date: 2023-10-13 |
//|                              https://www.mql5.com/en/users/3rjfx |
//+------------------------------------------------------------------+
#property copyright "Copyright 2023, Roberto Jacobs (3rjfx) ~ Date: 2023-10-13"
#property link      "https://www.mql5.com/en/users/3rjfx"
#property version   "1.00"
/*-- Last Update: Date: 2026-03-31 */
#property indicator_chart_window
//--
#property indicator_buffers 3
#property indicator_plots   2
//--
#property indicator_label1  "Rise Degree" 
#property indicator_type1   DRAW_NONE 
#property indicator_label2  "Down Degree" 
#property indicator_type2   DRAW_NONE
//--
enum YN
 {
   No,
   Yes
 };
//---
input int                StocKperiod = 5;                 // The iStochastic K period (the number of bars for calculation) 
input int                StocDperiod = 3;                 // The iStochastic D period (the period of primary smoothing) 
input int                StocSlowing = 3;                 // Period of final smoothing       
input ENUM_MA_METHOD       ma_method = MODE_EMA;          // Type of smoothing    
input ENUM_STO_PRICE     price_field = STO_LOWHIGH;       // Method of calculation of the Stochastic 
input YN                      alerts = No;               // Display Alerts / Messages (Yes) or (No)
input YN               UseEmailAlert = No;                // Email Alert (Yes) or (No)
input YN               UseSendnotify = No;                // Send Notification (Yes) or (No)
//--
//--- buffers
double RiseDgr[];
double DownDgr[];
double StochMn[]; 
double StochSg[]; 
//--
ENUM_BASE_CORNER 
  corner=CORNER_RIGHT_UPPER;
color stgBull=clrBlue;
color stsBull=clrAqua;
color stsBear=clrYellow;
color stgBear=clrRed;
color txtrbl=clrWhite;
color txtblk=clrBlack;  
//--
int StoHandle;
int fs=67;
int dist_x=150;
int dist_xt=110;
int dist_y=100;
int posalert,
    prevalert;
//--
string dtext;
string indname;
string Albase,AlSubj;
string objname="stodg_";
string RndDg=objname+"RoundedDegrees";
string TxtDg=objname+"TextDegrees";
string ArrUpDg=objname+"ArrUpDegrees";
string ArrDwDg=objname+"ArrDnDegrees";
//--
//--- we will keep the number of values in the iStochastic indicator 
#define minbars 25
//---------//
//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- indicator buffers mapping
    SetIndexBuffer(0,RiseDgr,INDICATOR_DATA);
    SetIndexBuffer(1,DownDgr,INDICATOR_DATA);
    SetIndexBuffer(2,StochMn,INDICATOR_CALCULATIONS);
    //--
    StoHandle=iStochastic(Symbol(),Period(),StocKperiod,StocDperiod,StocSlowing,ma_method,price_field);
    if(StoHandle==INVALID_HANDLE) 
      { 
       //--- tell about the failure and output the error code 
       PrintFormat("Failed to create handle of the iStochastic indicator for the symbol %s/%s, error code %d", 
                   Symbol(), 
                   EnumToString(Period()), 
                   GetLastError()); 
       //--- the indicator is stopped early 
       return(INIT_FAILED); 
      } 
    //--
    indname="StochDegrees ("+Symbol()+"~"+strTF(Period())+"~"+string(StocKperiod)+","+string(StocDperiod)+","+string(StocSlowing)+")";
    IndicatorSetString(INDICATOR_SHORTNAME,indname);
    IndicatorSetInteger(INDICATOR_DIGITS,2);
    //--
    return(INIT_SUCCEEDED);
//---
  } //-end OnInit()
//---------//
//+------------------------------------------------------------------+ 
//| Indicator deinitialization function                              | 
//+------------------------------------------------------------------+ 
void OnDeinit(const int reason) 
  { 
//---
   Comment(""); 
   PrintFormat("%s: Deinitialization reason code=%d",__FUNCTION__,reason);
   Print(getUninitReasonText(reason));
   if(StoHandle!=INVALID_HANDLE)  IndicatorRelease(StoHandle);
   //--
   ObjectsDeleteAll(0,objname,-1,-1);
   //--
   return;
//---
  } //-end OnDeinit()
//---------//
//+------------------------------------------------------------------+
//| Custom indicator iteration function                              |
//+------------------------------------------------------------------+
int OnCalculate(const int rates_total,
                const int prev_calculated,
                const datetime &time[],
                const double &open[],
                const double &high[],
                const double &low[],
                const double &close[],
                const long &tick_volume[],
                const long &volume[],
                const int &spread[])
  {
//---
    if(rates_total<minbars) return(0);
    int limit=minbars;
//--- not all data may be calculated
    //--
    int j=0;
    int dtxt=0;
    bool dgrsUp=false,
         dgrsDn=false;
    //--
    color rndclr=clrNONE;
    color arrclr=clrNONE;
    color txtclr=clrNONE;
    //--
    double cur_degrees=0.0;
    double prev1_degrees=0.0;
    double prev2_degrees=0.0;
    //--
    double stcdif=4.5;
    double div1_degrees=0.0;
    double div0_degrees=0.0;
    //--
    ArrayResize(RiseDgr,limit,limit);
    ArrayResize(DownDgr,limit,limit);
    ArrayResize(StochMn,limit,limit);
    ArrayResize(StochSg,limit,limit);
    ArraySetAsSeries(RiseDgr,true);
    ArraySetAsSeries(DownDgr,true);
    ArraySetAsSeries(StochMn,true);
    ArraySetAsSeries(StochSg,true);
    ArraySetAsSeries(open,true);
    ArraySetAsSeries(high,true);
    ArraySetAsSeries(low,true);
    ArraySetAsSeries(close,true);
    ArraySetAsSeries(time,true);
    //--
//--- we can copy not all data
    int to_copy; 
    if(prev_calculated>rates_total || prev_calculated==0) 
      {
       to_copy=rates_total;
      }
    else
      {
       to_copy=rates_total-prev_calculated;
       if(to_copy<limit) to_copy=limit;
      }
    //--
    if(IsStopped()) return(0); //Checking for stop flag
    //-- get iStochastic indicator buffers
    if(CopyBuffer(StoHandle,0,0,to_copy,StochMn)<0)
      {
       Print("Getting the iStochastic indicator Main Line buffers is failed! Error",GetLastError());
       return(0);
      }
    if(CopyBuffer(StoHandle,1,0,to_copy,StochSg)<0)
      {
       Print("Getting the iStochastic indicator Signal Line buffers is failed! Error",GetLastError());
       return(0);
      }
    //--
    for(j=limit-3; j>=0; j--)
      {
        cur_degrees=NormalizeDouble(270+(NonZeroDiv(StochMn[j],100)*180),2);
        prev1_degrees=NormalizeDouble(270+(NonZeroDiv(StochMn[j+1],100)*180),2);
        prev2_degrees=NormalizeDouble(270+(NonZeroDiv(StochMn[j+2],100)*180),2);
        //--
        double pctcurdg=NormalizeDouble((NonZeroDiv(StochMn[j],100)*180),2);
        double pctpr1dg=NormalizeDouble((NonZeroDiv(StochMn[j+1],100)*180),2);
        double pctpr2dg=NormalizeDouble((NonZeroDiv(StochMn[j+2],100)*180),2);
        //--
        if(cur_degrees>360.0)  {cur_degrees=NormalizeDouble(cur_degrees-360.0,2);}
        if(cur_degrees==360.0) {cur_degrees=NormalizeDouble(0.0,2);}
        //- To give a value of 90.0 degrees to the indicator, when the price moves up very quickly.
        if(cur_degrees==90.0)  {cur_degrees=NormalizeDouble(90.0,2);}
        //- To give a value of 270.0 degrees to the indicator, when the price moves down very quickly.
        if(cur_degrees==270.0) {cur_degrees=NormalizeDouble(270.0,2);}
        //--
        div1_degrees=pctpr1dg - pctpr2dg;
        div0_degrees=pctcurdg - pctpr2dg;
        //--
        if((StochMn[j]>StochSg[j])&&(div0_degrees>div1_degrees+stcdif)&&(pctcurdg>pctpr1dg)) {dgrsUp=true; dgrsDn=false; RiseDgr[j]=cur_degrees; DownDgr[j]=0.0;}
        else
        if((StochMn[j]<StochSg[j])&&(div0_degrees<div1_degrees-stcdif)&&(pctcurdg<pctpr1dg)) {dgrsDn=true; dgrsUp=false; DownDgr[j]=cur_degrees; RiseDgr[j]=0.0;}
        //--
        if((cur_degrees>=270.0 && cur_degrees<315.0)&&(dgrsDn==true)) {rndclr=stgBear; arrclr=stgBear; txtclr=txtrbl; posalert=11;}
        else
        if((cur_degrees>=270.0 && cur_degrees<315.0)&&(dgrsUp==true)) {rndclr=stgBear; arrclr=stsBear; txtclr=txtrbl; posalert=12;}
        else
        if((cur_degrees>=315.0 && cur_degrees<360.0)&&(dgrsDn==true)) {rndclr=stsBear; arrclr=stgBear; txtclr=txtblk; posalert=21;}
        else
        if((cur_degrees>=315.0 && cur_degrees<360.0)&&(dgrsUp==true)) {rndclr=stsBear; arrclr=stsBull; txtclr=txtblk; posalert=23;}
        else
        if((cur_degrees>=0.0 && cur_degrees<45.0)&&(dgrsUp==true))    {rndclr=stsBull; arrclr=stgBull; txtclr=txtblk; posalert=34;}
        else
        if((cur_degrees>=0.0 && cur_degrees<45.0)&&(dgrsDn==true))    {rndclr=stsBull; arrclr=stsBear; txtclr=txtblk; posalert=32;}
        else
        if((cur_degrees>=45.0 && cur_degrees<=90.0)&&(dgrsUp==true))  {rndclr=stgBull; arrclr=stgBull; txtclr=txtrbl; posalert=44;}
        else
        if((cur_degrees>=45.0 && cur_degrees<=90.0)&&(dgrsDn==true))  {rndclr=stgBull; arrclr=stsBull; txtclr=txtrbl; posalert=43;}
        //--
        dtext=DoubleToString(cur_degrees,1)+""+CharToString(176);
        if(StringLen(dtext)>5) {dtxt=24;}
        else if(StringLen(dtext)==5) {dtxt=20;}
        else {dtxt=17;}
        //--
        if(j==0)
          {
            //--
            CreateArrowDegrees(0,RndDg,CharToString(108),"Wingdings",fs,rndclr,corner,dist_x,dist_y,true);
            CreateArrowDegrees(0,TxtDg,dtext,"Bodoni MT Black",8,txtclr,corner,dist_xt+dtxt,dist_y+41,true);
            //--
            if(dgrsUp)
              {
                ObjectDelete(0,ArrDwDg);
                //--
                CreateArrowDegrees(0,ArrUpDg,CharToString(217),"Wingdings",23,arrclr,corner,dist_xt+20,dist_y-2,true);
              }
            //--
            else
            if(dgrsDn)
              {
                ObjectDelete(0,ArrUpDg); 
                //--
                CreateArrowDegrees(0,ArrDwDg,CharToString(218),"Wingdings",23,arrclr,corner,dist_xt+20,dist_y+63,true);
              }
            //--
            ChartRedraw(0);
            //--
            PosAlerts(posalert);
          } //-- End if(j)
      }
    //--
    //--- memorize the number of values in the iStochastic indicator 
//--- return value of prev_calculated for next call
    return(rates_total);
//---
  } //-end OnCalculate()
//---------//
//+------------------------------------------------------------------+

double NonZeroDiv(double val1,double val2)
  {
//---
   double resval=0;
   if(val1==0 || val2==0) resval=0.00;
   else
   resval=val1/val2;
   //--
   return(resval);
//---
  } //-end NonZeroDiv()
//---------//

bool CreateArrowDegrees(long   chart_id, 
                        string lable_name, 
                        string label_text,
                        string font_model,
                        int    font_size,
                        color  label_color,
                        int    chart_corner,
                        int    x_cor, 
                        int    y_cor,
                        bool   lable_hidden)
  { 
//--- 
    if(ObjectFind(0,lable_name)<0)
      {
        if(!ObjectCreate(chart_id,lable_name,OBJ_LABEL,0,0,0)) 
          { 
            Print(__FUNCTION__,": failed to create \"Arrow Label\" sign! Error code = ",GetLastError());
            return(false); 
          } 
        else
          {
            ObjectSetString(chart_id,lable_name,OBJPROP_TEXT,label_text);
            ObjectSetString(chart_id,lable_name,OBJPROP_FONT,font_model);
            ObjectSetInteger(chart_id,lable_name,OBJPROP_FONTSIZE,font_size);
            ObjectSetInteger(chart_id,lable_name,OBJPROP_COLOR,label_color);
            ObjectSetInteger(chart_id,lable_name,OBJPROP_CORNER,chart_corner);
            ObjectSetInteger(chart_id,lable_name,OBJPROP_XDISTANCE,x_cor);
            ObjectSetInteger(chart_id,lable_name,OBJPROP_YDISTANCE,y_cor);
            ObjectSetInteger(chart_id,lable_name,OBJPROP_HIDDEN,lable_hidden);
          }
      }
    else
      {
        //--
        ObjectSetString(chart_id,lable_name,OBJPROP_TEXT,label_text);
        ObjectSetString(chart_id,lable_name,OBJPROP_FONT,font_model); 
        ObjectSetInteger(chart_id,lable_name,OBJPROP_FONTSIZE,font_size);
        ObjectSetInteger(chart_id,lable_name,OBJPROP_COLOR,label_color);
        ObjectSetInteger(chart_id,lable_name,OBJPROP_CORNER,chart_corner);
        ObjectSetInteger(chart_id,lable_name,OBJPROP_XDISTANCE,x_cor);
        ObjectSetInteger(chart_id,lable_name,OBJPROP_YDISTANCE,y_cor);
        ObjectSetInteger(chart_id,lable_name,OBJPROP_HIDDEN,lable_hidden);
      }
    //--
    ChartRedraw(0);
    //--- successful execution 
    return(true);
    //--
  } //-end CreateArrowDegrees()
//---------//

string strTF(ENUM_TIMEFRAMES period)
  {
//---
   switch(period)
     {
       //--
       case PERIOD_M1:   return("M1");
       case PERIOD_M2:   return("M2");
       case PERIOD_M3:   return("M3");
       case PERIOD_M4:   return("M4");
       case PERIOD_M5:   return("M5");
       case PERIOD_M6:   return("M6");
       case PERIOD_M10:  return("M10");
       case PERIOD_M12:  return("M12");
       case PERIOD_M15:  return("M15");
       case PERIOD_M20:  return("M20");
       case PERIOD_M30:  return("M30");
       case PERIOD_H1:   return("H1");
       case PERIOD_H2:   return("H2");
       case PERIOD_H3:   return("H3");
       case PERIOD_H4:   return("H4");
       case PERIOD_H6:   return("H6");
       case PERIOD_H8:   return("H8");
       case PERIOD_H12:  return("H12");
       case PERIOD_D1:   return("D1");
       case PERIOD_W1:   return("W1");
       case PERIOD_MN1:  return("MN1");
       //--
     }
   //--
   return(string(period));
//---
  } //-end strTF()  
//---------//

enum TimeReturn
  {
    year        = 0,   // Year 
    mon         = 1,   // Month 
    day         = 2,   // Day 
    hour        = 3,   // Hour 
    min         = 4,   // Minutes 
    sec         = 5,   // Seconds 
    day_of_week = 6,   // Day of week (0-Sunday, 1-Monday, ... ,6-Saturday) 
    day_of_year = 7    // Day number of the year (January 1st is assigned the number value of zero) 
  };
//---------//

int MqlReturnDateTime(datetime reqtime,
                      const int mode) 
  {
//---
    MqlDateTime mqltm;
    TimeToStruct(reqtime,mqltm);
    int valdate=0;
    //--
    switch(mode)
      {
        case 0: valdate=mqltm.year; break;        // Return Year 
        case 1: valdate=mqltm.mon;  break;        // Return Month 
        case 2: valdate=mqltm.day;  break;        // Return Day 
        case 3: valdate=mqltm.hour; break;        // Return Hour 
        case 4: valdate=mqltm.min;  break;        // Return Minutes 
        case 5: valdate=mqltm.sec;  break;        // Return Seconds 
        case 6: valdate=mqltm.day_of_week; break; // Return Day of week (0-Sunday, 1-Monday, ... ,6-Saturday) 
        case 7: valdate=mqltm.day_of_year; break; // Return Day number of the year (January 1st is assigned the number value of zero) 
      }
    return(valdate);
//---
  } //-end MqlReturnDateTime()
//---------//

void Do_Alerts(string msgText)
  {
//---
    //--
    Print("--- "+Symbol()+": "+msgText+
          "\n--- at: ",TimeToString(TimeCurrent(),TIME_DATE|TIME_MINUTES));
    //--
    if(alerts==Yes)
      {
        Alert("--- "+Symbol()+": "+msgText+
              "--- at: ",TimeToString(TimeCurrent(),TIME_DATE|TIME_MINUTES));
      }
    //--
    if(UseEmailAlert==Yes) 
      SendMail(MQLInfoString(MQL_PROGRAM_NAME),"--- "+Symbol()+" "+strTF(Period())+": "+msgText+
                       "\n--- at: "+TimeToString(TimeCurrent(),TIME_DATE|TIME_MINUTES));
    //--
    if(UseSendnotify==Yes) 
      SendNotification(MQLInfoString(MQL_PROGRAM_NAME)+"--- "+Symbol()+" "+strTF(Period())+": "+msgText+
                      "\n--- at: "+TimeToString(iTime(Symbol(),0,0),TIME_DATE|TIME_MINUTES));
    //--
    return;
    //--
//---
  } //-end Do_Alerts()
//---------//

void PosAlerts(int curalerts) 
   {
//---
    if((curalerts!=prevalert)&&(curalerts==43))
       {     
         Albase=indname+" : Position "+dtext;
         AlSubj=Albase+" Trend Began to Fall, Bulish Weakened";
         Do_Alerts(AlSubj);
         prevalert=curalerts;
       }
    //---
    if((curalerts!=prevalert)&&(curalerts==32))
       {     
         Albase=indname+" : Position "+dtext;
         AlSubj=Albase+" Trend was Down, Bulish Reversal";
         Do_Alerts(AlSubj);
         prevalert=curalerts;
       }
    //---
    if((curalerts!=prevalert)&&(curalerts==21))
       {     
         Albase=indname+" : Position "+dtext;
         AlSubj=Albase+" Trend was Down, Bearish Strengthened";
         Do_Alerts(AlSubj);
         prevalert=curalerts;
       }              
    //---
    if((curalerts!=prevalert)&&(curalerts==11))
       {     
         Albase=indname+" : Position "+dtext;
         AlSubj=Albase+" Trend was Down, Strong Bearish";
         Do_Alerts(AlSubj);
         prevalert=curalerts;
       }
    //---//
    if((curalerts!=prevalert)&&(curalerts==12))
       {
         Albase=indname+" : Position "+dtext;
         AlSubj=Albase+" Trend Began to Rise, Bearish Weakened";
         Do_Alerts(AlSubj);
         prevalert=curalerts;
       }
    //---
    if((curalerts!=prevalert)&&(curalerts==23))
       {
         Albase=indname+" : Position "+dtext;
         AlSubj=Albase+" Trend was Up, Bearish Reversal";
         Do_Alerts(AlSubj);
         prevalert=curalerts;
       }
    //---
    if((curalerts!=prevalert)&&(curalerts==34))
       {
         Albase=indname+" : Position "+dtext;
         AlSubj=Albase+" Trend was Up, Bulish Strengthened";
         Do_Alerts(AlSubj);
         prevalert=curalerts;
       }
    //---
    if((curalerts!=prevalert)&&(curalerts==44))
       {
         Albase=indname+" : Position "+dtext;
         AlSubj=Albase+" Trend was Up, Strong Bulish";
         Do_Alerts(AlSubj);
         prevalert=curalerts;
       }
    //---
    return;
//----
   } //-end PosAlerts()
 //---------//

string getUninitReasonText(int reasonCode) 
  { 
//---
   string text=""; 
   //--- 
   switch(reasonCode) 
     { 
       case REASON_PROGRAM:
            text="The EA has stopped working calling by remove function."; break;
       case REASON_REMOVE: 
            text="Program "+__FILE__+" was removed from chart"; break;
       case REASON_RECOMPILE:
            text="Program recompiled."; break;    
       case REASON_CHARTCHANGE: 
            text="Symbol or timeframe was changed"; break;
       case REASON_CHARTCLOSE: 
            text="Chart was closed"; break; 
       case REASON_PARAMETERS: 
            text="Input-parameter was changed"; break;            
       case REASON_ACCOUNT: 
            text="Account was changed"; break; 
       case REASON_TEMPLATE: 
            text="New template was applied to chart"; break; 
       case REASON_INITFAILED:
            text="The OnInit() handler returned a non-zero value."; break;
       case REASON_CLOSE: 
            text="Terminal closed."; break;
       default: text="Another reason"; break;
     } 
   //--
   return text;
//---
  } //-end getUninitReasonText()
//---------//
***Copyright © 2026 3rjfx ~ For educational purposes only.***

Please download the StochDegree indicator: StochDegree

If you want to get the source code of the program, please send your request via the Contact page by mentioning the article and program you want.


© 2026 StochDegree - Developed by Roberto Jacobs (3rjfx)

Tagged: , , , , , , , , , ,

0 comments:

Post a Comment

Featured Post

How to create a simple Multi-Currency Expert Advisor using MQL5 with Zigzag and RSI Indicators Signal

Author: Roberto Jacobs (3rjfx) | Featured on Forex Home Expert Introduction The Expert Advisor discussed in this article is a mult...