BreakevenLine

Author: www.Wolfforex.com, 2025
0 Views
0 Downloads
0 Favorites
BreakevenLine
//+------------------------------------------------------------------+
//|                                            Negative Volume Index |
//|                                    Copyright © 2025 Wolfforex.com|
//|                                        https://www.wolfforex.com |
//+------------------------------------------------------------------+
#property copyright "www.Wolfforex.com, 2025"
#property version   "2.01"
#property strict
#property indicator_chart_window
#property indicator_plots 0

#property description "Displays a breakeven line for the current symbol."
#property description "You can hide/show the line by pressing Shift+B."

input bool IgnoreLong = false;
input bool IgnoreShort = false;
input color line_color_buy = clrTeal;
input color line_color_sell = clrPink;
input color line_color_neutral = clrSlateGray;
input ENUM_LINE_STYLE line_style = STYLE_SOLID;
input int line_width = 1;
input color font_color = clrSlateGray;
input int font_size = 12;
input string font_face = "Courier";
input string ObjectPrefix = "BEL_"; // ObjectPrefix: To prevent confusion with other indicators/EAs.

// Global variables:
int PositionsLong, PositionsShort, PositionsTotal;
double VolumeLong, PriceLong, ProfitLong, VolumeShort, PriceShort, ProfitShort, VolumeTotal, ProfitTotal, PriceAverage, DistancePoints;
double Pip_Value = 0;
double Pip_Size = 0;
double LotStep = 0;
int LotStep_digits = 0;
string object_line, object_label;

void OnInit()
{
    object_line = ObjectPrefix + "Line" + Symbol();
    object_label = ObjectPrefix + "Label" + Symbol();
    EventSetMillisecondTimer(200); // Five times per second.
}

void OnDeinit(const int reason)
{
    ObjectsDeleteAll(0, ObjectPrefix);
}

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 (iBars(Symbol(), Period()) <= 0) return 0; // Data not loaded yet.

    if (CalculateData()) DrawData();

    return rates_total;
}

//+------------------------------------------------------------------+
//| Chart event handler                                              |
//+------------------------------------------------------------------+
void OnChartEvent(const int id,
                  const long &lparam,
                  const double &dparam,
                  const string &sparam)
{
    if (id == CHARTEVENT_CHART_CHANGE) DrawData();
    else if (id == CHARTEVENT_KEYDOWN)
    {
        // Trade direction:
        if ((lparam == 'B') && (TerminalInfoInteger(TERMINAL_KEYSTATE_SHIFT) < 0))
        {
            if (ObjectGetInteger(0, object_line, OBJPROP_TIMEFRAMES) == OBJ_NO_PERIODS) // Was hidden.
            {
                ObjectSetInteger(0, object_line, OBJPROP_TIMEFRAMES, OBJ_ALL_PERIODS);
                ObjectSetInteger(0, object_label, OBJPROP_TIMEFRAMES, OBJ_ALL_PERIODS);
            }
            else // Was visible.
            {
                ObjectSetInteger(0, object_line, OBJPROP_TIMEFRAMES, OBJ_NO_PERIODS);
                ObjectSetInteger(0, object_label, OBJPROP_TIMEFRAMES, OBJ_NO_PERIODS);
            }
            ChartRedraw();
        }
    }
}

//+------------------------------------------------------------------+
//| Trade event handler                                              |
//+------------------------------------------------------------------+
void OnTrade()
{
    if (CalculateData()) DrawData();
    ChartRedraw();
}

//+------------------------------------------------------------------+
//| Timer event handler                                              |
//+------------------------------------------------------------------+
void OnTimer()
{
    /*if (CalculateData())*/ DrawData();
    ChartRedraw();
}

//+------------------------------------------------------------------+
//| Calculates BE distance and other values.                         |
//+------------------------------------------------------------------+
bool CalculateData()
{
    PositionsLong = 0;
    VolumeLong = 0;
    PriceLong = 0;
    ProfitLong = 0;
    
    PositionsShort = 0;
    VolumeShort = 0;
    PriceShort = 0;
    ProfitShort = 0;
    
    PositionsTotal = 0;
    VolumeTotal = 0;
    ProfitTotal = 0;
    
    PriceAverage = 0;
    DistancePoints = 0;

    Pip_Value = SymbolInfoDouble(Symbol(), SYMBOL_TRADE_TICK_VALUE);
    Pip_Size = SymbolInfoDouble(Symbol(), SYMBOL_TRADE_TICK_SIZE);
    
    if ((Pip_Value == 0) || (Pip_Size == 0)) return false; // No symbol information yet.

    int total = PositionsTotal();

    for (int i = 0; i < total; i++)
    {
        string pos_symbol = PositionGetSymbol(i);
        if (pos_symbol != "")
        {
            if (pos_symbol != Symbol()) continue; // Should only consider trades on the current symbol.
            if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY)
            {
                if (IgnoreLong) continue;
                PositionsLong++;
                PriceLong += PositionGetDouble(POSITION_PRICE_OPEN) * PositionGetDouble(POSITION_VOLUME);
                VolumeLong += PositionGetDouble(POSITION_VOLUME);
                ulong ticket = PositionGetInteger(POSITION_TICKET);
                HistoryDealGetDouble(ticket, DEAL_COMMISSION);
                ProfitLong += PositionGetDouble(POSITION_PROFIT) + PositionGetDouble(POSITION_SWAP) + HistoryDealGetDouble(ticket, DEAL_COMMISSION);
            }
            else if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL)
            {
                if (IgnoreShort) continue;
                PositionsShort++;
                PriceShort += PositionGetDouble(POSITION_PRICE_OPEN) * PositionGetDouble(POSITION_VOLUME);
                VolumeShort += PositionGetDouble(POSITION_VOLUME);
                ulong ticket = PositionGetInteger(POSITION_TICKET);
                HistoryDealGetDouble(ticket, DEAL_COMMISSION);
                ProfitShort += PositionGetDouble(POSITION_PROFIT) + PositionGetDouble(POSITION_SWAP) + HistoryDealGetDouble(ticket, DEAL_COMMISSION);
            }
        }
        else Print("Error selecting position #", i, ": ", GetLastError());
    }

    if (PriceLong > 0)
    {
        PriceLong /= VolumeLong; // Average buy price.
    }
    if (PriceShort > 0)
    {
        PriceShort /= VolumeShort; // Average sell price.
    }
    
    PositionsTotal = PositionsLong + PositionsShort;
    VolumeTotal = VolumeLong - VolumeShort;
    ProfitTotal = ProfitLong + ProfitShort;

    if (PositionsTotal > 0)
    {
        double Ask = SymbolInfoDouble(Symbol(), SYMBOL_ASK);
        double Bid = SymbolInfoDouble(Symbol(), SYMBOL_BID);
        if (VolumeTotal != 0)
        {
            DistancePoints = ProfitTotal / MathAbs(VolumeTotal * Pip_Value) * Pip_Size;
            if (VolumeTotal > 0)
            {
                PriceAverage = Bid - DistancePoints;
            }
            else //  VolumeTotal < 0
            {
                PriceAverage = Ask + DistancePoints;
            }
        }
        else // VolumeTotal == 0
        {
            DistancePoints = ProfitTotal / Pip_Value * Pip_Size;
            PriceAverage = (Ask + Bid) / 2 - DistancePoints;
        }
    }

    return true;
}

//+------------------------------------------------------------------+
//| Creates or moves chart objects.                                  |
//+------------------------------------------------------------------+
void DrawData()
{
    // Line:
    if (ObjectFind(0, object_line) < 0)
    {
        ObjectCreate(0, object_line, OBJ_HLINE, 0, 0, PriceAverage);
        ObjectSetInteger(0, object_line, OBJPROP_WIDTH, 0, line_width);
        ObjectSetInteger(0, object_line, OBJPROP_STYLE, 0, line_style);
        ObjectSetInteger(0, object_line, OBJPROP_SELECTABLE, false);
        ObjectSetInteger(0, object_line, OBJPROP_HIDDEN, true);
    }
    else
    {
        ObjectMove(0, object_line, 0, 0, PriceAverage);
    }
    color colour = line_color_buy;
    if (VolumeTotal < 0) colour = line_color_sell;
    else if (VolumeTotal == 0) colour = line_color_neutral;
    ObjectSetInteger(0, object_line, OBJPROP_COLOR, colour);
    
    // Label:
    if (ObjectFind(0, object_label) < 0)
    {
        ObjectCreate(0, object_label, OBJ_LABEL, 0, 0, 0);
        ObjectSetInteger(0, object_label, OBJPROP_SELECTABLE, false);
        ObjectSetInteger(0, object_label, OBJPROP_HIDDEN, true);
        ObjectSetInteger(0, object_label, OBJPROP_FONTSIZE, font_size);
        ObjectSetString(0, object_label, OBJPROP_FONT, font_face);
        ObjectSetInteger(0, object_label, OBJPROP_COLOR, font_color);
        // This needs to run only once:
        LotStep = SymbolInfoDouble(Symbol(), SYMBOL_VOLUME_STEP); 
        LotStep_digits = CountDecimalPlaces(LotStep);
    }

    int x, y;
    long real_x;
    uint w, h;

    string text = IntegerToString((int)MathRound(DistancePoints / _Point)) + " (" + DoubleToString(ProfitTotal, 2) + " " + AccountInfoString(ACCOUNT_CURRENCY) + ") " + DoubleToString(VolumeTotal, LotStep_digits) + " lots, N = " + IntegerToString(PositionsTotal);
    ObjectSetString(0, object_label, OBJPROP_TEXT, text);

    real_x = ChartGetInteger(0, CHART_WIDTH_IN_PIXELS) - 2;
    // Needed only for y, x is derived from the chart width.
    ChartTimePriceToXY(0, 0, iTime(Symbol(), Period(), 0), PriceAverage, x, y);
    // Get the width of the text based on font and its size. Negative because OS-dependent, *10 because set in 1/10 of pt.
    TextSetFont(font_face, font_size * -10);
    TextGetSize(text, w, h);
    ObjectSetInteger(0, object_label, OBJPROP_XDISTANCE, real_x - w);
    y -= int(h + 1); // Above the line.
    ObjectSetInteger(0, object_label, OBJPROP_YDISTANCE, y);
}

//+------------------------------------------------------------------+
//| Counts decimal places.                                           |
//+------------------------------------------------------------------+
int CountDecimalPlaces(double number)
{
    // 100 as maximum length of number.
    for (int i = 0; i < 100; i++)
    {
        double pwr = MathPow(10, i);
        if (MathRound(number * pwr) / pwr == number) return(i);
    }
    return(-1);
}
//+------------------------------------------------------------------+

Comments