-- Available @ https://fxcodebase.com/code/viewtopic.php?f=17&t=76018

-- +------------------------------------------------------------------------------------------------+
-- |                                                              Copyright 2025, Gehtsoft USA LLC  | 
-- |                                                                         http://fxcodebase.com  |
-- |                                                               Paypal:  https://goo.gl/9Rj74e   |
-- +------------------------------------------------------------------------------------------------+
-- |                                                                   Developed by : Mario Jemic   |                    
-- |                                                                       mario.jemic@gmail.com    |
-- |                                                                       https://mario-jemic.com/ | 
-- |                                                             Patreon :  http://tiny.cc/1ybwxz   |   
-- |                                                      Buy Me a Coffee:  http://tiny.cc/bj7vxz   |  
-- +-----------------+----------------------+-------------------------------------------------------+
-- |  Cryptocurrency |  Network             |  Address                                              |
-- +-----------------+----------------------+-------------------------------------------------------+
-- |  BTC            |  BTC                 |  16F5k43RXibTmna4np8bPVgmXM1CzjXFJJ                   | 
-- |  SOL            |  SOL                 |  3nh5rpUKopcYLNU4zGCdUFAkM3iRQq8VVUmuzVG6VDf2         | 
-- |  ETH            |  ERC20               |  0xe53aab6bc468a963a02d1319660ee60cf80fc8e7           |
-- |  BNB            |  BEP20               |  0xe53aab6bc468a963a02d1319660ee60cf80fc8e7           | 
-- |  USDT           |  BEP20               |  0xe53aab6bc468a963a02d1319660ee60cf80fc8e7           | 
-- |  XRP            |  BEP20               |  0xe53aab6bc468a963a02d1319660ee60cf80fc8e7           | 
-- +-----------------+----------------------+-------------------------------------------------------+ 
 
function Init()
    tool:name("Fibonacci Retracement Money Management");
    tool:description("");
    tool:icon("");
    tool.creationStrategy:setPattern(core.DragClick);
    tool.creationStrategy:setMaxClickCount(0);
    tool.creationStrategy:setNeedParamsAfterPattern(true);
    tool:setTag("group", "Fibonacci");
    tool:setTag("orderInGroup", "1");
    tool:setTag("NonResetableParameters", "param1,param2");
    
    tool.parameters:addDate("start_date", "Date start", "", 0);
    tool.parameters:setFlag("start_date", core.FLAG_DATETIME);
    tool.parameters:addDate("end_date", "Date end", "", 0);
    tool.parameters:setFlag("end_date", core.FLAG_DATETIME);
    tool.parameters:addDouble("start_rate", "Rate top", "", 0);
    tool.parameters:setFlag("start_rate", core.FLAG_PRICE);
    tool.parameters:addDouble("end_rate", "Rate bottom", "", 0);
    tool.parameters:setFlag("end_rate", core.FLAG_PRICE);

    tool.parameters:addString("account", "Account to trade on", "", "");
    tool.parameters:setFlag("account", core.FLAG_ACCOUNT);
    tool.parameters:addInteger("lots", "Lots", "", 1, 1, 1000000);

    tool.parameters:addDouble("entry_level", "Entry level", "", 1.0);
    tool.parameters:addColor("entry_color", "Entry level Color", "Color", core.colors().Green);
    tool.parameters:addInteger("entry_width", "Entry level Width", "Width", 2, 1, 5);
    tool.parameters:addInteger("entry_style", "Entry level Style", "Style", core.LINE_SOLID);
    tool.parameters:setFlag("entry_style", core.FLAG_LINE_STYLE);

    tool.parameters:addDouble("stop_level", "Stop level", "", 0.0);
    tool.parameters:addColor("stop_color", "Stop level Color", "description Color", core.colors().Red);
    tool.parameters:addInteger("stop_width", "Stop level Width", "description Width", 2, 1, 5);
    tool.parameters:addInteger("stop_style", "Stop level Style", "description Style", core.LINE_SOLID);
    tool.parameters:setFlag("stop_style", core.FLAG_LINE_STYLE);

    tool.parameters:addDouble("t1_level", "Target 1 level", "", 2.0);
    tool.parameters:addColor("t1_color", "Target 1 level Color", "description Color", core.colors().Blue);
    tool.parameters:addInteger("t1_width", "Target 1 level Width", "description Width", 2, 1, 5);
    tool.parameters:addInteger("t1_style", "Target 1 level Style", "description Style", core.LINE_SOLID);
    tool.parameters:setFlag("t1_style", core.FLAG_LINE_STYLE);
    tool.parameters:addDouble("t1_level_stop", "Target 1 level: Move stop to", "", 1.25);
    tool.parameters:addDouble("t1_level_close", "Target 1 level: Partial close %", "", 50);
    tool.parameters:addColor("t1_level_stop_color", "Target 1 level stop Color", "description Color", core.colors().Blue);
    tool.parameters:addInteger("t1_level_stop_width", "Target 1 level stop Width", "description Width", 1, 1, 5);
    tool.parameters:addInteger("t1_level_stop_style", "Target 1 level stop Style", "description Style", core.LINE_DASHDOT);
    tool.parameters:setFlag("t1_level_stop_style", core.FLAG_LINE_STYLE);

    tool.parameters:addDouble("t2_level", "Target 2 level", "", 2.5);
    tool.parameters:addColor("t2_color", "Target 2 level Color", "description Color", core.colors().Blue);
    tool.parameters:addInteger("t2_width", "Target 2 level Width", "description Width", 1, 1, 5);
    tool.parameters:addInteger("t2_style", "Target 2 level Style", "description Style", core.LINE_DASHDOT);
    tool.parameters:setFlag("t2_style", core.FLAG_LINE_STYLE);
    tool.parameters:addDouble("t2_level_stop", "Target 2 level: Move stop to", "", 1.5);
    tool.parameters:addDouble("t2_level_close", "Target 2 level: Partial close %", "", 0);
    tool.parameters:addColor("t2_level_stop_color", "Target 2 level stop Color", "description Color", core.colors().Blue);
    tool.parameters:addInteger("t2_level_stop_width", "Target 2 level stop Width", "description Width", 1, 1, 5);
    tool.parameters:addInteger("t2_level_stop_style", "Target 2 level stop Style", "description Style", core.LINE_DASHDOT);
    tool.parameters:setFlag("t2_level_stop_style", core.FLAG_LINE_STYLE);

    tool.parameters:addDouble("t3_level", "Target 3 level", "", 3.0);
    tool.parameters:addColor("t3_color", "Target 3 level Color", "description Color", core.colors().Blue);
    tool.parameters:addInteger("t3_width", "Target 3 level Width", "description Width", 1, 1, 5);
    tool.parameters:addInteger("t3_style", "Target 3 level Style", "description Style", core.LINE_SOLID);
    tool.parameters:setFlag("t3_style", core.FLAG_LINE_STYLE);
    tool.parameters:addDouble("t3_level_stop", "Target 3 level: Move stop to", "", 2.0);
    tool.parameters:addDouble("t3_level_close", "Target 3 level: Partial close %", "", 0);
    tool.parameters:addColor("t3_level_stop_color", "Target 3 level stop Color", "description Color", core.colors().Blue);
    tool.parameters:addInteger("t3_level_stop_width", "Target 3 level stop Width", "description Width", 1, 1, 5);
    tool.parameters:addInteger("t3_level_stop_style", "Target 3 level stop Style", "description Style", core.LINE_DASHDOT);
    tool.parameters:setFlag("t3_level_stop_style", core.FLAG_LINE_STYLE);
end

function CreationStarted()
end
function CreationFinished()
end
local click = 1;
function Click(x, y, price, date)
    if click == 1 then
        start_rate = price;
        start_date = date;
        instance.parameters.start_rate = price;
        instance.parameters.start_date = date;
        instance.parameters.end_rate = price;
        instance.parameters.end_date = date;
    else
        instance.parameters.start_rate = price;
        instance.parameters.start_date = date;
    end
    click = click + 1;
end
function DoubleClick(x, y, price, date)
end

local last_stream_size = 0;
function UpdateReferencePoints()
    local pane = core.host.Window.CurrentPane;
    local stream = pane.Data:getStream(0);
    last_stream_size = stream:size();
    local referencePoints = core.host.ReferencePoints;
    referencePoints:setReferencePoint(1, instance.parameters.start_date, instance.parameters.start_rate, referencePoints.DATE + referencePoints.PRICE, instance.parameters.stop_color, instance.parameters.stop_width + 1);
    referencePoints:setReferencePoint(2, instance.parameters.end_date, instance.parameters.end_rate, referencePoints.DATE + referencePoints.PRICE, instance.parameters.entry_color, instance.parameters.entry_width + 1);
end

function Drag(x, y, price, date)    
    instance.parameters.end_rate = price;
    instance.parameters.end_date = date;
    UpdateReferencePoints();
end

function DragEnd(x, y, price, date)
    click = click + 1;
end

function CreationFinished()
end

local movingPointID;
function MoveReferencePointStart(id)
    movingPointID = id;
end

function MoveReferencePoint(x, y, price, date)
    if movingPointID == 1 then
        instance.parameters.start_date = date;
        instance.parameters.start_rate = price;
        UpdateReferencePoints();
    elseif movingPointID == 2 then
        instance.parameters.end_date = date;
        instance.parameters.end_rate = price;
        UpdateReferencePoints();
    elseif movingPointID == 3 then
        local min = math.min(instance.parameters.end_rate, instance.parameters.start_rate);
        local max = math.max(instance.parameters.end_rate, instance.parameters.start_rate);
        UpdateReferencePoints();
    end
end

function MoveReferencePointFinished()
    executed = false;
end

local start_rate = nil;
local start_period = nil;
local start_rate_start;
local period_1_start;
local end_rate_start;
local middle_rate_start;
local period_2_start;
function MoveStart()
    local pane = core.host.Window.CurrentPane;
    local stream = pane.Data:getStream(0);
    
    start_rate = nil;
    start_period = nil;
    start_rate_start = instance.parameters.start_rate;
    period_1_start = core.host:execute("calculatePositionOfDate", stream, instance.parameters.start_date);
    end_rate_start = instance.parameters.end_rate;
    period_2_start = core.host:execute("calculatePositionOfDate", stream, instance.parameters.end_date);
end

function Move(x, y, price, date)
    local pane = core.host.Window.CurrentPane;
    local stream = pane.Data:getStream(0);
    if start_rate == nil then
        start_rate = price;
        start_period = core.host:execute("calculatePositionOfDate", stream, date);
    else
        local rate_diff = price - start_rate;
        local period_diff = core.host:execute("calculatePositionOfDate", stream, date) - start_period;
        instance.parameters.start_rate = start_rate_start + rate_diff;
        instance.parameters.start_date = core.host:execute("calculateDate", stream, period_1_start + period_diff);
        instance.parameters.end_rate = end_rate_start + rate_diff;
        instance.parameters.end_date = core.host:execute("calculateDate", stream, period_2_start + period_diff);
        UpdateReferencePoints();
    end
end

function MoveFinished()
    start_rate = nil;
    start_period = nil;
    executed = false;
end

local init = false;

function get_point_coordinates(date, price, context)
    local x, x1, x2 = context:positionOfDate(date);
    local visible, y = context:pointOfPrice(price);
    return x, y;
end

local entry_pen = 1;
local stop_pen = 2;
local t1_pen = 3;
local t1_level_stop_pen = 4;
local t2_pen = 5;
local t2_level_stop_pen = 6;
local t3_pen = 7;
local t3_level_stop_pen = 8;
local font = 9;
local transparency;
function Draw(stage, context)
    if stage == 2 then
        if not init then
            context:createPen(entry_pen, context:convertPenStyle(instance.parameters.entry_style), instance.parameters.entry_width, instance.parameters.entry_color);
            context:createPen(stop_pen, context:convertPenStyle(instance.parameters.stop_style), instance.parameters.stop_width, instance.parameters.stop_color);
            context:createPen(t1_pen, context:convertPenStyle(instance.parameters.t1_style), instance.parameters.t1_width, instance.parameters.t1_color);
            context:createPen(t1_level_stop_pen, context:convertPenStyle(instance.parameters.t1_level_stop_style), instance.parameters.t1_level_stop_width, instance.parameters.t1_level_stop_color);
            context:createPen(t2_pen, context:convertPenStyle(instance.parameters.t2_style), instance.parameters.t2_width, instance.parameters.t2_color);
            context:createPen(t2_level_stop_pen, context:convertPenStyle(instance.parameters.t2_level_stop_style), instance.parameters.t2_level_stop_width, instance.parameters.t2_level_stop_color);
            context:createPen(t3_pen, context:convertPenStyle(instance.parameters.t3_style), instance.parameters.t3_width, instance.parameters.t3_color);
            context:createPen(t3_level_stop_pen, context:convertPenStyle(instance.parameters.t3_level_stop_style), instance.parameters.t3_level_stop_width, instance.parameters.t3_level_stop_color);
            context:createFont(font, "Arial", 0, context:pointsToPixels(10), context.LEFT);
            
            init = true;
        end
        local pane = core.host.Window.CurrentPane;
        local stream = pane.Data:getStream(0);
        if last_stream_size ~= stream:size() then
            UpdateReferencePoints();
        end
        
        local x1, y1 = get_point_coordinates(instance.parameters.start_date, instance.parameters.start_rate, context);
        local x2, _, _ = context:positionOfDate(instance.parameters.end_date);
        context:setClipRectangle(context:left(), context:top(), context:right(), context:bottom());
        local range = instance.parameters.end_rate - instance.parameters.start_rate;

        DrawLevel(context, stream, range, instance.parameters.entry_level, x1, x2, entry_pen);
        DrawLevel(context, stream, range, instance.parameters.stop_level, x1, x2, stop_pen);
        DrawLevel(context, stream, range, instance.parameters.t1_level, x1, x2, t1_pen);
        DrawLevel(context, stream, range, instance.parameters.t1_level_stop, x1, x2, t1_level_stop_pen);
        DrawLevel(context, stream, range, instance.parameters.t2_level, x1, x2, t2_pen);
        DrawLevel(context, stream, range, instance.parameters.t2_level_stop, x1, x2, t2_level_stop_pen);
        DrawLevel(context, stream, range, instance.parameters.t3_level, x1, x2, t3_pen);
        DrawLevel(context, stream, range, instance.parameters.t3_level_stop, x1, x2, t3_level_stop_pen);

        context:resetClipRectangle();
    end
end

function DrawLevel(context, stream, range, level, x1, x2, pen)
    local rate = instance.parameters.start_rate + range * level;
    local _, y = context:pointOfPrice(rate);
    context:drawLine(pen, x1, y, x2, y)
    local label = win32.formatNumber(level, false, 3) .. " " .. win32.formatNumber(rate, false, stream:getDisplayPrecision());
    local w, h = context:measureText(font, label, context.LEFT);
    context:drawText(font, label, core.COLOR_LABEL, -1, x1, y - h, x1 + w, y, context.LEFT);
end
local executed = false;
local TIMER_ID = 1;
function Prepare(onlyName)
    local name = profile:name();
    instance:name(name);
    
    if onlyName then
        return;
    end
    
    init = false;
    last_stream_size = 0;
    UpdateReferencePoints();
    core.host:execute("setTimer", TIMER_ID, 1)
end
function AsyncOperationFinished(cookie, success, message, message1, message2)
    if cookie == TIMER_ID and not executed then
        local pane = core.host.Window.CurrentPane;
        local stream = pane.Data:getStream(0);
        if base_size == nil then
            base_size = core.host:execute("getTradingProperty", "baseUnitSize", stream:instrument(), instance.parameters.account);
            offer_id = core.host:findTable("offers"):find("Instrument", stream:instrument()).OfferID;
        end
        local range = instance.parameters.end_rate - instance.parameters.start_rate;
        local rate = instance.parameters.start_rate + range * instance.parameters.entry_level;
        if core.crossesOver(stream, rate) and range > 0 then
            executed = true;
            OpenTrade(stream, "B", range);
        elseif core.crossesUnder(stream, rate) and range < 0 then
            executed = true;
            OpenTrade(stream, "S", range);
        end
    end
end

function CreateCrossOverCondition(rate)
    local condition = {};
    condition.rate = rate;
    function condition:IsPass(data)
        if self.executed then
            return false;
        end
        local pane = core.host.Window.CurrentPane;
        local stream = pane.Data:getStream(0);
        if stream:tick(NOW) > self.rate then
            self.executed = true;
            return true;
        end
    end
end
function CreateCrossUnderCondition(rate)
    local condition = {};
    condition.rate = rate;
    function condition:IsPass(data)
        if self.executed then
            return false;
        end
        local pane = core.host.Window.CurrentPane;
        local stream = pane.Data:getStream(0);
        if stream:tick(NOW) < self.rate then
            self.executed = true;
            return true;
        end
    end
end

function CreateMoveToAction(rate)
    local action = {};
    function action:Execute(data)
        if self._command ~= nil then
            return self._command.Finished;
        end
        local trade = data:GetTrade();
        if trade == nil then
            return false;
        end
        if not trade:refresh() then
            return true;
        end
        self._command = self._parent._trading:MoveStop(trade, self._to, nil);
        return false;
    end
    return action;
end

function OpenTrade(stream, side, range)
    local result = trading:MarketOrder(stream:instrument())
        :SetSide(side)
        :SetAccountID(instance.parameters.account)
        :SetAmount(instance.parameters.lots)
        :Execute();
    
    local controller = breakeven:CreateController(side == "B" 
        and CreateCrossOverCondition(instance.parameters.start_rate + range * instance.parameters.t1_level) 
        or CreateCrossUnderCondition(instance.parameters.start_rate + range * instance.parameters.t1_level));
    controller:AddAction(CreateMoveToAction(instance.parameters.t1_level_stop, nil));
    if (instance.parameters.t1_level_close > 0) then
        controller:AddAction(breakeven:CreatePartialClose(instance.parameters.t1_level_close, "initial_lots"));
    end
    controller = breakeven:CreateController(side == "B" 
        and CreateCrossOverCondition(instance.parameters.start_rate + range * instance.parameters.t2_level) 
        or CreateCrossUnderCondition(instance.parameters.start_rate + range * instance.parameters.t2_level));
    controller:AddAction(CreateMoveToAction(instance.parameters.t2_level_stop, nil));
    if (instance.parameters.t2_level_close > 0) then
        controller:AddAction(breakeven:CreatePartialClose(instance.parameters.t2_level_close, "initial_lots"));
    end
    controller = breakeven:CreateController(side == "B" 
        and CreateCrossOverCondition(instance.parameters.start_rate + range * instance.parameters.t3_level) 
        or CreateCrossUnderCondition(instance.parameters.start_rate + range * instance.parameters.t3_level));
    controller:AddAction(CreateMoveToAction(instance.parameters.t3_level_stop, nil));
    if (instance.parameters.t3_level_close > 0) then
        controller:AddAction(breakeven:CreatePartialClose(instance.parameters.t3_level_close, "initial_lots"));
    end
end

local temp_params;
function CheckParameters(params)
    temp_params = params;
    return profile:name();
end

function ChangeParameters()
    executed = false;
    -- copy all the params because of FXTS2 bug
    instance.parameters.start_rate = temp_params.start_rate;
    instance.parameters.start_date = temp_params.start_date;
    instance.parameters.end_rate = temp_params.end_rate;
    instance.parameters.end_date = temp_params.end_date;

    instance.parameters.entry_level = temp_params.entry_level;
    instance.parameters.entry_color = temp_params.entry_color;
    instance.parameters.entry_width = temp_params.entry_width;
    instance.parameters.entry_style = temp_params.entry_style;

    instance.parameters.stop_level = temp_params.stop_level;
    instance.parameters.stop_color = temp_params.stop_color;
    instance.parameters.stop_width = temp_params.stop_width;
    instance.parameters.stop_style = temp_params.stop_style;

    instance.parameters.t1_level = temp_params.t1_level;
    instance.parameters.t1_color = temp_params.t1_color;
    instance.parameters.t1_width = temp_params.t1_width;
    instance.parameters.t1_style = temp_params.t1_style;
    instance.parameters.t1_level_stop = temp_params.t1_level_stop;
    instance.parameters.t1_level_close = temp_params.t1_level_close;
    instance.parameters.t1_level_stop_color = temp_params.t1_level_stop_color;
    instance.parameters.t1_level_stop_width = temp_params.t1_level_stop_width;
    instance.parameters.t1_level_stop_style = temp_params.t1_level_stop_style;

    instance.parameters.t2_level = temp_params.t2_level;
    instance.parameters.t2_color = temp_params.t2_color;
    instance.parameters.t2_width = temp_params.t2_width;
    instance.parameters.t2_style = temp_params.t2_style;
    instance.parameters.t2_level_stop = temp_params.t2_level_stop;
    instance.parameters.t2_level_close = temp_params.t2_level_close;
    instance.parameters.t2_level_stop_color = temp_params.t2_level_stop_color;
    instance.parameters.t2_level_stop_width = temp_params.t2_level_stop_width;
    instance.parameters.t2_level_stop_style = temp_params.t2_level_stop_style;

    instance.parameters.t3_level = temp_params.t3_level;
    instance.parameters.t3_color = temp_params.t3_color;
    instance.parameters.t3_width = temp_params.t3_width;
    instance.parameters.t3_style = temp_params.t3_style;
    instance.parameters.t3_level_stop = temp_params.t3_level_stop;
    instance.parameters.t3_level_close = temp_params.t3_level_close;
    instance.parameters.t3_level_stop_color = temp_params.t3_level_stop_color;
    instance.parameters.t3_level_stop_width = temp_params.t3_level_stop_width;
    instance.parameters.t3_level_stop_style = temp_params.t3_level_stop_style;
    instance.parameters.account = temp_params.account;
    instance.parameters.lots = temp_params.lots;
    Prepare(false);
end


trading = {};
trading.Name = "Trading";
trading.Version = "4.35";
trading.Debug = false;
trading.AddAmountParameter = true;
trading.AddStopParameter = true;
trading.AddLimitParameter = true;
trading._ids_start = 100;
trading._signaler = nil;
trading._account = nil;
trading._all_modules = {};
trading._request_id = {};
trading._waiting_requests = {};
trading._used_stop_orders = {};
trading._used_limit_orders = {};
function trading:trace(str) if not self.Debug then return; end core.host:trace(self.Name .. ": " .. str); end
function trading:RegisterModule(modules) for _, module in pairs(modules) do self:OnNewModule(module); module:OnNewModule(self); end modules[#modules + 1] = self; self._ids_start = (#modules) * 100; end

function trading:GetBreakeven(id)
    local be = {};
    be.UseBreakeven = instance.parameters:getBoolean("use_breakeven" .. id);
    be.BreakevenWhen = instance.parameters:getDouble("breakeven_when" .. id);
    be.BreakevenTo = instance.parameters:getDouble("breakeven_to" .. id);
    be.MoveStop = instance.parameters:getBoolean("move_be_stop" .. id);
    be.BreakevenTrailing = instance.parameters:getString("breakeven_trailing");
    be.BreakevenTrailingValue = instance.parameters:getInteger("trailing" .. id);
    be.PartialCloseMode = instance.parameters:getString("breakeven_close" .. id);
    if be.PartialCloseMode ~= "no" then
        be.PartialCloseAmount = instance.parameters:getDouble("breakeven_close_amount" .. id);
    end
    function be:AddBreakeven(result)
        if self.UseBreakeven == false then
            return;
        end
        if self.UseBreakeven == nil and self.PartialCloseAmount == nil then
            return;
        end
    
        local condition = breakeven:CreatePLGTCondition(self.BreakevenWhen);
        local controller = breakeven:CreateController(condition);
        if self.BreakevenTo ~= nil then
            controller:AddAction(breakeven:CreateMoveStopAction(self.BreakevenTo,
                self.BreakevenTrailing == "set" and self.BreakevenTrailingValue or nil)
            );
        end
        if self.PartialCloseAmount ~= nil then
            controller:AddAction(breakeven:CreatePartialClose(self.PartialCloseAmount, self.PartialCloseMode));
        end
        controller:SetRequestID(result.RequestID);

        local controller = breakeven:CreateBreakeven()
            :SetRequestID(result.RequestID)
            :SetWhen(self.BreakevenWhen);
        if self.MoveStop then
            controller:SetTo(self.BreakevenTo);
        end
        if self.BreakevenTrailing == "set" then
            controller:SetTrailing(self.BreakevenTrailingValue);
        end
    end
    return be;
end
function trading:addBreakevenParameters(parameters, section_id, id, label)
    parameters:addGroup("  Breakeven parameters #" .. label .. section_id);
    parameters:addBoolean("use_breakeven" .. id, "Use Breakeven", "", false);
    parameters:addDouble("breakeven_when" .. id, "Breakeven Activation Value, in pips", "", 10);
    parameters:addBoolean("move_be_stop" .. id, "Move Stop", "", false);
    parameters:addDouble("breakeven_to" .. id, "Breakeven To, in pips", "", 0);
    parameters:addString("breakeven_trailing" .. id, "Trailing after breakeven", "", "default");
    parameters:addStringAlternative("breakeven_trailing" .. id, "Do not change", "", "default");
    parameters:addStringAlternative("breakeven_trailing" .. id, "Set trailing", "", "set");

    parameters:addString("breakeven_close" .. id, "Partial close", "", "no");
    parameters:addStringAlternative("breakeven_close" .. id, "No partial close", "", "no");
    parameters:addStringAlternative("breakeven_close" .. id, "Partial close (% of lots)", "", "lots");
    parameters:addStringAlternative("breakeven_close" .. id, "Partial close (% of initial lots)", "", "initial_lots");
    parameters:addDouble("breakeven_close_amount" .. id, "Partial close amount", "", 50);
end

function trading:AddPositionParameters(parameters, id, section_id)
    if self.AddAmountParameter then
        parameters:addDouble("amount" .. id, "Trade Amount", "", 1);
        parameters:addString("amount_type" .. id, "Amount Unit", "", "lots");
        parameters:addStringAlternative("amount_type" .. id, "In Lots", "", "lots");
        parameters:addStringAlternative("amount_type" .. id, "% of Equity", "", "equity");
        parameters:addStringAlternative("amount_type" .. id, "% of Margin", "", "margin");
        parameters:addStringAlternative("amount_type" .. id, "Risk % of Equity", "", "risk_equity");
    end
    if CreateStopParameters == nil or not CreateStopParameters(parameters, id) then
        parameters:addGroup("  Stop parameters " .. section_id);
        parameters:addString("stop_type" .. id, "Stop Order", "", "no");
        parameters:addStringAlternative("stop_type" .. id, "No stop", "", "no");
        parameters:addStringAlternative("stop_type" .. id, "In Pips", "", "pips");
        if not DISABLE_ATR_STOP_LIMIT then
            parameters:addStringAlternative("stop_type" .. id, "ATR", "", "atr");
        end
        parameters:addStringAlternative("stop_type" .. id, "High/low", "", "highlow");
        parameters:addDouble("stop" .. id, "Stop Value", "In pips or ATR period", 30);
        if not DISABLE_ATR_STOP_LIMIT then
            parameters:addDouble("atr_stop_mult" .. id, "ATR Stop Multiplicator", "", 2.0);
        end
        parameters:addBoolean("use_trailing" .. id, "Trailing stop order", "", false);
        parameters:addInteger("trailing" .. id, "Trailing in pips", "Use 1 for dynamic and 10 or greater for the fixed trailing", 1);
    end
    if CreateLimitParameters == nil or not CreateLimitParameters(parameters, id) then
        parameters:addGroup("  Limit parameters " .. section_id);
        parameters:addString("limit_type" .. id, "Limit Order", "", "no");
        parameters:addStringAlternative("limit_type" .. id, "No limit", "", "no");
        parameters:addStringAlternative("limit_type" .. id, "In Pips", "", "pips");
        if not DISABLE_ATR_STOP_LIMIT then
            parameters:addStringAlternative("limit_type" .. id, "ATR", "", "atr");
        end
        parameters:addStringAlternative("limit_type" .. id, "Multiplicator of stop", "", "stop");
        parameters:addStringAlternative("limit_type" .. id, "High/low", "", "highlow");
        parameters:addDouble("limit" .. id, "Limit Value", "In pips or ATR period", 30);
        if not DISABLE_ATR_STOP_LIMIT then
            parameters:addDouble("atr_limit_mult" .. id, "ATR Limit Multiplicator", "", 2.0);
        end
        if not DISABLE_LIMIT_TRAILING then
            parameters:addString("TRAILING_LIMIT_TYPE" .. id, "Trailing Limit", "", "Off");
            parameters:addStringAlternative("TRAILING_LIMIT_TYPE" .. id, "Off", "", "Off");
            parameters:addStringAlternative("TRAILING_LIMIT_TYPE" .. id, "Favorable", "moves limit up for long/buy positions, vice versa for short/sell", "Favorable");
            parameters:addStringAlternative("TRAILING_LIMIT_TYPE" .. id, "Unfavorable", "moves limit down for long/buy positions, vice versa for short/sell", "Unfavorable");
            parameters:addDouble("TRAILING_LIMIT_TRIGGER" .. id, "Trailing Limit Trigger in Pips", "", 0);
            parameters:addDouble("TRAILING_LIMIT_STEP" .. id, "Trailing Limit Step in Pips", "", 10);
        end
    end
    if CreateCustomBreakeven == nil then
        self:addBreakevenParameters(parameters, section_id, id, "1");
    end
end

function trading:Init(parameters, count)
    parameters:addBoolean("allow_trade", "Allow strategy to trade", "", true);
    parameters:setFlag("allow_trade", core.FLAG_ALLOW_TRADE);
    parameters:addString("account", "Account to trade on", "", "");
    parameters:setFlag("account", core.FLAG_ACCOUNT);
    parameters:addString("allow_side", "Allow side", "", "both")
    parameters:addStringAlternative("allow_side", "Both", "", "both")
    parameters:addStringAlternative("allow_side", "Long/buy only", "", "buy")
    parameters:addStringAlternative("allow_side", "Short/sell only", "", "sell")
    parameters:addString("custom_id", "Custom ID", "", "id");
    parameters:addString("execution_mode", "Execution mode", "", "IOC");
    parameters:addStringAlternative("execution_mode", "Immediate Or Cancel", "", "IOC");
    parameters:addStringAlternative("execution_mode", "Fill Or Kill", "", "FOK");
    parameters:addStringAlternative("execution_mode", "Good Till Cancelled", "", "GTC");
    parameters:addStringAlternative("execution_mode", "Day", "", "DAY");
    parameters:addBoolean("close_on_opposite", "Close on Opposite", "", true);
    if ENFORCE_POSITION_CAP ~= true then
        parameters:addBoolean("position_cap", "Position Cap", "", false);
        parameters:addInteger("no_of_positions", "Max # of open positions", "", 1);
        parameters:addInteger("no_of_buy_position", "Max # of buy positions", "", 1);
        parameters:addInteger("no_of_sell_position", "Max # of sell positions", "", 1);
    end
    
    if count == nil or count == 1 then
        parameters:addGroup("Position");
        self:AddPositionParameters(parameters, "", "");
    else
        for i = 1, count do
            parameters:addGroup("Position #" .. i);
            parameters:addBoolean("use_position_" .. i, "Open position #" .. i, "", i == 1);
            self:AddPositionParameters(parameters, "_" .. i, "#" .. i);
        end
    end
end

function trading:Prepare(name_only)
    if name_only then return; end
end

function trading:ExtUpdate(id, source, period)
end

function trading:OnNewModule(module)
    if module.Name == "Signaler" then self._signaler = module; end
    self._all_modules[#self._all_modules + 1] = module;
end

function trading:AsyncOperationFinished(cookie, success, message, message1, message2)
    local res = self._waiting_requests[cookie];
    if res ~= nil then
        res.Finished = true;
        res.Success = success;
        if not success then
            res.Error = message;
            if self._signaler ~= nil then
                self._signaler:Signal(res.Error);
            else
                self:trace(res.Error);
            end
        elseif res.OnSuccess ~= nil then
            res:OnSuccess();
        end
        self._waiting_requests[cookie] = nil;
    elseif cookie == self._order_update_id then
        for _, order in ipairs(self._monitored_orders) do
            if order.RequestID == message2 then
                order.FixStatus = message1;
            end
        end
    elseif cookie == self._ids_start + 2 then
        if not success then
            if self._signaler ~= nil then
                self._signaler:Signal("Close order failed: " .. message);
            else
                self:trace("Close order failed: " .. message);
            end
        end
    end
end

function trading:getOppositeSide(side) if side == "B" then return "S"; end return "B"; end

function trading:getId()
    for id = self._ids_start, self._ids_start + 100 do
        if self._waiting_requests[id] == nil then return id; end
    end
    return self._ids_start;
end

function trading:CreateStopOrder(trade, stop_rate, trailing)
    local valuemap = core.valuemap();
    valuemap.Command = "CreateOrder";
    valuemap.OfferID = trade.OfferID;
    valuemap.Rate = stop_rate;
    if trade.BS == "B" then
        valuemap.BuySell = "S";
    else
        valuemap.BuySell = "B";
    end

    local can_close = core.host:execute("getTradingProperty", "canCreateMarketClose", trade.Instrument, trade.AccountID);
    if can_close then
        valuemap.OrderType = "S";
        valuemap.AcctID  = trade.AccountID;
        valuemap.TradeID = trade.TradeID;
        valuemap.Quantity = trade.Lot;
        valuemap.TrailUpdatePips = trailing;
    else
        valuemap.OrderType = "SE"
        valuemap.AcctID  = trade.AccountID;
        valuemap.NetQtyFlag = "Y"
    end

    local id = self:getId();
    local success, msg = terminal:execute(id, valuemap);
    if not(success) then
        local message = "Failed create stop " .. msg;
        self:trace(message);
        if self._signaler ~= nil then
            self._signaler:Signal(message);
        end
        local res = {};
        res.Finished = true;
        res.Success = false;
        res.Error = message;
        return res;
    end
    local res = {};
    res.Finished = false;
    res.RequestID = msg;
    self._waiting_requests[id] = res;
    self._request_id[trade.TradeID] = msg;
    return res;
end

function trading:CreateLimitOrder(trade, limit_rate)
    local valuemap = core.valuemap();
    valuemap.Command = "CreateOrder";
    valuemap.OfferID = trade.OfferID;
    valuemap.Rate = limit_rate;
    if trade.BS == "B" then
        valuemap.BuySell = "S";
    else
        valuemap.BuySell = "B";
    end
    local can_close = core.host:execute("getTradingProperty", "canCreateMarketClose", trade.Instrument, trade.AccountID);
    if can_close then
        valuemap.OrderType = "L";
        valuemap.AcctID  = trade.AccountID;
        valuemap.TradeID = trade.TradeID;
        valuemap.Quantity = trade.Lot;
    else
        valuemap.OrderType = "LE"
        valuemap.AcctID  = trade.AccountID;
        valuemap.NetQtyFlag = "Y"
    end
    local success, msg = terminal:execute(200, valuemap);
    if not(success) then
        terminal:alertMessage(trade.Instrument, limit_rate, "Failed create limit " .. msg, core.now());
    else
        self._request_id[trade.TradeID] = msg;
    end
end

function trading:trailingChanged(old, new)
    return (new == nil and old ~= 0)
        or math.abs(order.TrlMinMove - trailing) >= 0.1;
end

function trading:ChangeOrder(order, rate, trailing)
    local min_change = core.host:findTable("offers"):find("Instrument", order.Instrument).PointSize;
    if math.abs(rate - order.Rate) > min_change or self:trailingChanged(order.TrlMinMove, trailing) then
        -- stop exists
        local valuemap = core.valuemap();
        valuemap.Command = "EditOrder";
        valuemap.AcctID  = order.AccountID;
        valuemap.OrderID = order.OrderID;
        valuemap.TrailUpdatePips = trailing;
        valuemap.Rate = rate;
        local id = self:getId();
        local success, msg = terminal:execute(id, valuemap);
        if not(success) then
            local message = "Failed change order " .. msg;
            self:trace(message);
            if self._signaler ~= nil then
                self._signaler:Signal(message);
            end
            local res = {};
            res.Finished = true;
            res.Success = false;
            res.Error = message;
            return res;
        end
        local res = {};
        res.Finished = false;
        res.RequestID = msg;
        self._waiting_requests[id] = res;
        return res;
    end
    local res = {};
    res.Finished = true;
    res.Success = true;
    return res;
end

function trading:IsLimitOrder(order)
    local order_type = order.Type;
    if order_type == "L" or order_type == "LT" or order_type == "LTE" then
        return true;
    end
    return order.ContingencyType == 3 and order_type == "LE";
end

function trading:IsStopOrder(order) 
    local order_type = order.Type;
    if order_type == "S" or order_type == "ST" or order_type == "STE" then
        return true;
    end
    return order.ContingencyType == 3 and order_type == "SE";
end

function trading:IsLimitOrderType(order_type) return order_type == "L" or order_type == "LE" or order_type == "LT" or order_type == "LTE"; end

function trading:IsStopOrderType(order_type) return order_type == "S" or order_type == "SE" or order_type == "ST" or order_type == "STE"; end

function trading:FindLimitOrder(trade)
    local can_close = core.host:execute("getTradingProperty", "canCreateMarketClose", trade.Instrument, trade.AccountID);
    if can_close then
        local order_id;
        if trade.LimitOrderID ~= nil and trade.LimitOrderID ~= "" then
            order_id = trade.LimitOrderID;
            self:trace("Using limit order id from the trade");
        elseif self._request_id[trade.TradeID] ~= nil then
            self:trace("Searching limit order by request id: " .. tostring(self._request_id[trade.TradeID]));
            local order = core.host:findTable("orders"):find("RequestID", self._request_id[trade.TradeID]);
            if order ~= nil then
                order_id = order.OrderID;
                self._request_id[trade.TradeID] = nil;
            end
        end
        -- Check that order is stil exist
        if order_id ~= nil then return core.host:findTable("orders"):find("OrderID", order_id); end
    else
        local enum = core.host:findTable("orders"):enumerator();
        local row = enum:next();
        while (row ~= nil) do
            if self:IsLimitOrder(row) and self._used_limit_orders[row.OrderID] ~= true then
                self._used_limit_orders[row.OrderID] = true;
                return row;
            end
            row = enum:next();
        end
    end
    return nil;
end

function trading:FindStopOrder(trade)
    local can_close = core.host:execute("getTradingProperty", "canCreateMarketClose", trade.Instrument, trade.AccountID);
    if can_close then
        local order_id;
        if trade.StopOrderID ~= nil and trade.StopOrderID ~= "" then
            order_id = trade.StopOrderID;
            self:trace("Using stop order id from the trade");
        elseif self._request_id[trade.TradeID] ~= nil then
            self:trace("Searching stop order by request id: " .. tostring(self._request_id[trade.TradeID]));
            local order = core.host:findTable("orders"):find("RequestID", self._request_id[trade.TradeID]);
            if order ~= nil then
                order_id = order.OrderID;
                self._request_id[trade.TradeID] = nil;
            end
        end
        -- Check that order is stil exist
        if order_id ~= nil then return core.host:findTable("orders"):find("OrderID", order_id); end
    else
        local enum = core.host:findTable("orders"):enumerator();
        local row = enum:next();
        while (row ~= nil) do
            if self:IsStopOrder(row) and self._used_stop_orders[row.OrderID] ~= true then
                self._used_stop_orders[row.OrderID] = true;
                return row;
            end
            row = enum:next();
        end
    end
    return nil;
end

function trading:MoveStop(trade, stop_rate, trailing)
    local order = self:FindStopOrder(trade);
    if order == nil then
        if trailing == 0 then
            trailing = nil;
        end
        return self:CreateStopOrder(trade, stop_rate, trailing);
    else
        if trailing == 0 then
            if order.TrlMinMove ~= 0 then
                trailing = order.TrlMinMove
            else
                trailing = nil;
            end
        end
        return self:ChangeOrder(order, stop_rate, trailing);
    end
end

function trading:MoveLimit(trade, limit_rate)
    self:trace("Searching for a limit");
    local order = self:FindLimitOrder(trade);
    if order == nil then
        self:trace("Limit order not found, creating a new one");
        return self:CreateLimitOrder(trade, limit_rate);
    else
        return self:ChangeOrder(order, limit_rate);
    end
end

function trading:RemoveStop(trade)
    self:trace("Searching for a stop");
    local order = self:FindStopOrder(trade);
    if order == nil then self:trace("No stop"); return nil; end
    self:trace("Deleting order");
    return self:DeleteOrder(order);
end

function trading:RemoveLimit(trade)
    self:trace("Searching for a limit");
    local order = self:FindLimitOrder(trade);
    if order == nil then self:trace("No limit"); return nil; end
    self:trace("Deleting order");
    return self:DeleteOrder(order);
end

function trading:DeleteOrder(order)
    self:trace(string.format("Deleting order %s", order.OrderID));
    local valuemap = core.valuemap();
    valuemap.Command = "DeleteOrder";
    valuemap.OrderID = order.OrderID;

    local id = self:getId();
    local success, msg = terminal:execute(id, valuemap);
    if not(success) then
        local message = "Delete order failed: " .. msg;
        self:trace(message);
        if self._signaler ~= nil then
            self._signaler:Signal(message);
        end
        local res = {};
        res.Finished = true;
        res.Success = false;
        res.Error = message;
        return res;
    end
    local res = {};
    res.Finished = false;
    res.RequestID = msg;
    self._waiting_requests[id] = res;
    return res;
end

function trading:GetCustomID(qtxt)
    if qtxt == nil then
        return nil;
    end
    local metadata = self:GetMetadata(qtxt);
    if metadata == nil then
        return qtxt;
    end
    return metadata.CustomID;
end

function trading:FindOrder()
    local search = {};
    function search:WhenCustomID(custom_id) self.CustomID = custom_id; return self; end
    function search:WhenSide(bs) self.Side = bs; return self; end
    function search:WhenInstrument(instrument) self.Instrument = instrument; return self; end
    function search:WhenAccountID(account_id) self.AccountID = account_id; return self; end
    function search:WhenRate(rate) self.Rate = rate; return self; end
    function search:WhenOrderType(orderType) self.OrderType = orderType; return self; end
    function search:Do(action)
        local enum = core.host:findTable("orders"):enumerator();
        local row = enum:next();
        local count = 0
        while (row ~= nil) do
            if self:PassFilter(row) then
                if action(row) then
                    count = count + 1;
                end
            end
            row = enum:next();
        end
        return count;
    end
    function search:Summ(action)
        local enum = core.host:findTable("orders"):enumerator();
        local row = enum:next();
        local summ = 0
        while (row ~= nil) do
            if self:PassFilter(row) then
                summ = summ + action(row);
            end
            row = enum:next();
        end
        return summ;
    end
    function search:PassFilter(row)
        return (row.Instrument == self.Instrument or not self.Instrument)
            and (row.BS == self.Side or not self.Side)
            and (row.AccountID == self.AccountID or not self.AccountID)
            and (trading:GetCustomID(row.QTXT) == self.CustomID or not self.CustomID)
            and (row.Rate == self.Rate or not self.Rate)
            and (row.Type == self.OrderType or not self.OrderType);
    end
    function search:All()
        local enum = core.host:findTable("orders"):enumerator();
        local row = enum:next();
        local orders = {};
        while (row ~= nil) do
            if self:PassFilter(row) then orders[#orders + 1] = row; end
            row = enum:next();
        end
        return orders;
    end
    function search:First()
        local enum = core.host:findTable("orders"):enumerator();
        local row = enum:next();
        while (row ~= nil) do
            if self:PassFilter(row) then return row; end
            row = enum:next();
        end
        return nil;
    end
    function search:Count()
        local count = 0;
        local enum = core.host:findTable("orders"):enumerator();
        local row = enum:next();
        while (row ~= nil) do
            if self:PassFilter(row) then count = count + 1; end
            row = enum:next();
        end
        return count;
    end
    return search;
end

function trading:FindTrade()
    local search = {};
    function search:WhenCustomID(custom_id) self.CustomID = custom_id; return self; end
    function search:WhenSide(bs) self.Side = bs; return self; end
    function search:WhenInstrument(instrument) self.Instrument = instrument; return self; end
    function search:WhenAccountID(account_id) self.AccountID = account_id; return self; end
    function search:WhenOpen(open) self.Open = open; return self; end
    function search:WhenOpenOrderReqID(open_order_req_id) self.OpenOrderReqID = open_order_req_id; return self; end
    function search:Do(action)
        local enum = core.host:findTable("trades"):enumerator();
        local row = enum:next();
        local count = 0
        while (row ~= nil) do
            if self:PassFilter(row) then
                if action(row) then
                    count = count + 1;
                end
            end
            row = enum:next();
        end
        return count;
    end
    function search:Summ(action)
        local enum = core.host:findTable("trades"):enumerator();
        local row = enum:next();
        local summ = 0
        while (row ~= nil) do
            if self:PassFilter(row) then
                summ = summ + action(row);
            end
            row = enum:next();
        end
        return summ;
    end
    function search:PassFilter(row)
        return (row.Instrument == self.Instrument or not self.Instrument)
            and (row.BS == self.Side or not self.Side)
            and (row.AccountID == self.AccountID or not self.AccountID)
            and (trading:GetCustomID(row.QTXT) == self.CustomID or not self.CustomID)
            and (row.Open == self.Open or not self.Open)
            and (row.OpenOrderReqID == self.OpenOrderReqID or not self.OpenOrderReqID);
    end
    function search:All()
        local enum = core.host:findTable("trades"):enumerator();
        local row = enum:next();
        local trades = {};
        while (row ~= nil) do
            if self:PassFilter(row) then trades[#trades + 1] = row; end
            row = enum:next();
        end
        return trades;
    end
    function search:Any()
        local enum = core.host:findTable("trades"):enumerator();
        local row = enum:next();
        while (row ~= nil) do
            if self:PassFilter(row) then 
                return true;
            end
            row = enum:next();
        end
        return false;
    end
    function search:Count()
        local enum = core.host:findTable("trades"):enumerator();
        local row = enum:next();
        local count = 0;
        while (row ~= nil) do
            if self:PassFilter(row) then count = count + 1; end
            row = enum:next();
        end
        return count;
    end
    function search:First()
        local enum = core.host:findTable("trades"):enumerator();
        local row = enum:next();
        while (row ~= nil) do
            if self:PassFilter(row) then return row; end
            row = enum:next();
        end
        return nil;
    end
    return search;
end

function trading:FindClosedTrade()
    local search = {};
    function search:WhenCustomID(custom_id) self.CustomID = custom_id; return self; end
    function search:WhenSide(bs) self.Side = bs; return self; end
    function search:WhenInstrument(instrument) self.Instrument = instrument; return self; end
    function search:WhenAccountID(account_id) self.AccountID = account_id; return self; end
    function search:WhenOpenOrderReqID(open_order_req_id) self.OpenOrderReqID = open_order_req_id; return self; end
    function search:WhenTradeIDRemain(trade_id_remain) self.TradeIDRemain = trade_id_remain; return self; end
    function search:WhenCloseOrderID(close_order_id) self.CloseOrderID = close_order_id; return self; end
    function search:PassFilter(row)
        if self.TradeIDRemain ~= nil and row.TradeIDRemain ~= self.TradeIDRemain then return false; end
        if self.CloseOrderID ~= nil and row.CloseOrderID ~= self.CloseOrderID then return false; end
        return (row.Instrument == self.Instrument or not self.Instrument)
            and (row.BS == self.Side or not self.Side)
            and (row.AccountID == self.AccountID or not self.AccountID)
            and (trading:GetCustomID(row.QTXT) == self.CustomID or not self.CustomID)
            and (row.OpenOrderReqID == self.OpenOrderReqID or not self.OpenOrderReqID);
    end
    function search:Do(action)
        local enum = core.host:findTable("closed trades"):enumerator();
        local row = enum:next();
        local count = 0
        while (row ~= nil) do
            if self:PassFilter(row) then
                if action(row) then
                    count = count + 1;
                end
            end
            row = enum:next();
        end
        return count;
    end
    function search:Any()
        local enum = core.host:findTable("closed trades"):enumerator();
        local row = enum:next();
        while (row ~= nil) do
            if self:PassFilter(row) then
                return true;
            end
            row = enum:next();
        end
        return false;
    end
    function search:All()
        local enum = core.host:findTable("closed trades"):enumerator();
        local row = enum:next();
        local trades = {};
        while (row ~= nil) do
            if self:PassFilter(row) then trades[#trades + 1] = row; end
            row = enum:next();
        end
        return trades;
    end
    function search:First()
        local enum = core.host:findTable("closed trades"):enumerator();
        local row = enum:next();
        while (row ~= nil) do
            if self:PassFilter(row) then return row; end
            row = enum:next();
        end
        return nil;
    end
    return search;
end

function trading:PartialClose(trade, amount)
    -- not finished
    local account = core.host:findTable("accounts"):find("AccountID", trade.AccountID);
    local id = self:getId();
    if account.Hedging == "Y" then
        local valuemap = core.valuemap();
        valuemap.BuySell = trade.BS == "B" and "S" or "B";
        valuemap.OrderType = "CM";
        valuemap.OfferID = trade.OfferID;
        valuemap.AcctID = trade.AccountID;
        valuemap.TradeID = trade.TradeID;
        valuemap.Quantity = math.min(amount, trade.Lot);
        local success, msg = terminal:execute(id, valuemap);
        if success then
            local res = trading:ClosePartialSuccessResult(msg);
            self._waiting_requests[id] = res;
            return res;
        end
        return trading:ClosePartialFailResult(msg);
    end

    local valuemap = core.valuemap();
    valuemap.OrderType = "OM";
    valuemap.OfferID = trade.OfferID;
    valuemap.AcctID = trade.AccountID;
    valuemap.Quantity = math.min(amount, trade.Lot);
    valuemap.BuySell = trading:getOppositeSide(trade.BS);
    local success, msg = terminal:execute(id, valuemap);
    if success then
        local res = trading:ClosePartialSuccessResult(msg);
        self._waiting_requests[id] = res;
        return res;
    end
    return trading:ClosePartialFailResult(msg);
end

function trading:ClosePartialSuccessResult(msg)
    local res = {};
    if msg ~= nil then res.Finished = false; else res.Finished = true; end
    res.RequestID = msg;
    function res:ToJSON()
        return trading:ObjectToJson(self);
    end
    return res;
end
function trading:ClosePartialFailResult(message)
    local res = {};
    res.Finished = true;
    res.Success = false;
    res.Error = message;
    return res;
end

function trading:Close(trade)
    local valuemap = core.valuemap();
    valuemap.BuySell = trade.BS == "B" and "S" or "B";
    valuemap.OrderType = "CM";
    valuemap.OfferID = trade.OfferID;
    valuemap.AcctID = trade.AccountID;
    valuemap.TradeID = trade.TradeID;
    valuemap.Quantity = trade.Lot;
    local success, msg = terminal:execute(self._ids_start + 3, valuemap);
    if not(success) then
        if self._signaler ~= nil then self._signaler:Signal("Close failed: " .. msg); end
        return false;
    end

    return true;
end

function trading:ObjectToJson(obj)
    local json = {};
    function json:AddStr(name, value)
        local separator = "";
        if self.str ~= nil then separator = ","; else self.str = ""; end
        self.str = self.str .. string.format("%s\"%s\":\"%s\"", separator, tostring(name), tostring(value));
    end
    function json:AddNumber(name, value)
        local separator = "";
        if self.str ~= nil then separator = ","; else self.str = ""; end
        self.str = self.str .. string.format("%s\"%s\":%f", separator, tostring(name), value or 0);
    end
    function json:AddBool(name, value)
        local separator = "";
        if self.str ~= nil then separator = ","; else self.str = ""; end
        self.str = self.str .. string.format("%s\"%s\":%s", separator, tostring(name), value and "true" or "false");
    end
    function json:AddTable(name, value)
        local str = trading:ObjectToJson(value);
        local separator = "";
        if self.str ~= nil then separator = ","; else self.str = ""; end
        self.str = self.str .. string.format("%s\"%s\":%s", separator, tostring(name), tostring(str));
    end
    function json:ToString() return "{" .. (self.str or "") .. "}"; end
    
    local first = true;
    for idx,t in pairs(obj) do
        local stype = type(t)
        if stype == "number" then json:AddNumber(idx, t);
        elseif stype == "string" then json:AddStr(idx, t);
        elseif stype == "boolean" then json:AddBool(idx, t);
        elseif stype == "function" then --do nothing
        elseif stype == "table" then json:AddTable(idx, t);
        else core.host:trace(tostring(idx) .. " " .. tostring(stype));
        end
    end
    return json:ToString();
end

function trading:CreateEntryOrderSuccessResult(msg)
    local res = {};
    if msg ~= nil then res.Finished = false; else res.Finished = true; end
    res.RequestID = msg;
    function res:IsOrderExecuted()
        return self.FixStatus ~= nil and self.FixStatus == "F";
    end
    function res:GetOrder()
        if self._order == nil and self.RequestID ~= nil then
            self._order = core.host:findTable("orders"):find("RequestID", self.RequestID);
            if self._order == nil then
                return nil;
            end
        end
        if not self._order:refresh() then return nil; end
        return self._order;
    end
    function res:GetTrade()
        if self._trade == nil and self.RequestID ~= nil then
            self._trade = core.host:findTable("trades"):find("OpenOrderReqID", self.RequestID);
            if self._trade == nil then
                return nil;
            end
        end
        if not self._trade:refresh() then return nil; end
        return self._trade;
    end
    function res:GetClosedTrade()
        if self._closed_trade == nil and self.RequestID ~= nil then
            self._closed_trade = core.host:findTable("closed trades"):find("OpenOrderReqID", self.RequestID);
            if self._closed_trade == nil then return nil; end
        end
        if not self._closed_trade:refresh() then return nil; end
        return self._closed_trade;
    end
    function res:ToJSON()
        return trading:ObjectToJson(self);
    end
    return res;
end
function trading:CreateEntryOrderFailResult(message)
    local res = {};
    res.Finished = true;
    res.Success = false;
    res.Error = message;
    function res:GetOrder() return nil; end
    function res:GetTrade() return nil; end
    function res:GetClosedTrade() return nil; end
    function res:IsOrderExecuted() return false; end
    return res;
end

function trading:EntryOrder(instrument)
    local builder = {};
    builder.Offer = core.host:findTable("offers"):find("Instrument", instrument);
    builder.Instrument = instrument;
    builder.Parent = self;
    builder.valuemap = core.valuemap();
    builder.valuemap.Command = "CreateOrder";
    builder.valuemap.OfferID = builder.Offer.OfferID;
    builder.valuemap.AcctID = self._account;
    function builder:_GetBaseUnitSize() if self._base_size == nil then self._base_size = core.host:execute("getTradingProperty", "baseUnitSize", self.Instrument, self.valuemap.AcctID); end return self._base_size; end

    function builder:SetAccountID(accountID) self.valuemap.AcctID = accountID; return self; end
    function builder:SetAmount(amount) self.amount = amount; return self; end
    function builder:SetRiskPercentOfEquityAmount(percent) self._RiskPercentOfEquityAmount = percent; return self; end
    function builder:SetPercentOfEquityAmount(percent) self._PercentOfEquityAmount = percent; return self; end
    function builder:SetPercentOfMarginAmount(percent) self._PercentOfMarginAmount = percent; return self; end
    function builder:SetExecutionType(type) self.valuemap.GTC = type; return self; end
    function builder:UpdateOrderType()
        if self.valuemap.BuySell == nil or self.valuemap.Rate == nil then
            return;
        end
        if self.valuemap.BuySell == "B" then 
            self.valuemap.OrderType = self.Offer.Ask > self.valuemap.Rate and "LE" or "SE"; 
        else 
            self.valuemap.OrderType = self.Offer.Bid > self.valuemap.Rate and "SE" or "LE"; 
        end 
    end
    function builder:SetSide(buy_sell) 
        self.valuemap.BuySell = buy_sell; 
        self:UpdateOrderType();
        return self; 
    end
    function builder:SetRate(rate) 
        self.valuemap.Rate = rate; 
        self:UpdateOrderType();
        return self; 
    end
    function builder:SetPipLimit(limit_type, limit) self.valuemap.PegTypeLimit = limit_type or "M"; self.valuemap.PegPriceOffsetPipsLimit = self.valuemap.BuySell == "B" and limit or -limit; return self; end
    function builder:SetLimit(limit) self.valuemap.RateLimit = limit; return self; end
    function builder:SetLimitByEquityTargetPercent(limit) self.EquityTargetPercent = limit; return self; end
    function builder:SetPipStop(stop_type, stop, trailing_stop) self.valuemap.PegTypeStop = stop_type or "O"; self.valuemap.PegPriceOffsetPipsStop = self.valuemap.BuySell == "B" and -stop or stop; self.valuemap.TrailStepStop = trailing_stop; return self; end
    function builder:SetStop(stop, trailing_stop) self.valuemap.RateStop = stop; self.valuemap.TrailStepStop = trailing_stop; return self; end
    function builder:UseDefaultCustomId() self.valuemap.CustomID = self.Parent.CustomID; return self; end
    function builder:SetCustomID(custom_id) self.valuemap.CustomID = custom_id; return self; end
    function builder:GetValueMap() return self.valuemap; end
    function builder:AddMetadata(id, val) if self._metadata == nil then self._metadata = {}; end self._metadata[id] = val; return self; end
    function builder:BuildValueMap()
        if self._metadata ~= nil then
            self._metadata.CustomID = self.valuemap.CustomID;
            self.valuemap.CustomID = trading:ObjectToJson(self._metadata);
        end
        if self._PercentOfEquityAmount ~= nil then
            local equity = core.host:findTable("accounts"):find("AccountID", self.valuemap.AcctID).Equity;
            local used_equity = equity * self._PercentOfEquityAmount / 100.0;
            local emr = core.host:getTradingProperty("EMR", self.Offer.Instrument, self.valuemap.AcctID);
            self.valuemap.Quantity = math.floor(used_equity / emr) * self:_GetBaseUnitSize();
        elseif self._RiskPercentOfEquityAmount ~= nil then
            local equity = core.host:findTable("accounts"):find("AccountID", self.valuemap.AcctID).Equity;
            local affordable_loss = equity * self._RiskPercentOfEquityAmount / 100.0;
            assert(self.valuemap.RateStop ~= nil, "Only absolute stop is supported");
            local stop = math.abs(self.valuemap.RateStop - self.valuemap.Rate) / self.Offer.PointSize;
            local possible_loss = self.Offer.PipCost * stop;
            self.valuemap.Quantity = math.floor(affordable_loss / possible_loss) * self:_GetBaseUnitSize();
        elseif self._PercentOfEquityAmount ~= nil then
            local equity = core.host:findTable("accounts"):find("AccountID", self.valuemap.AcctID).UsableMargin;
            local used_equity = equity * self._PercentOfMarginAmount / 100.0;
            local emr = core.host:getTradingProperty("EMR", self.Offer.Instrument, self.valuemap.AcctID);
            self.valuemap.Quantity = math.floor(used_equity / emr) * self:_GetBaseUnitSize();
        else
            self.valuemap.Quantity = self.amount * self:_GetBaseUnitSize();
        end
        if self.EquityTargetPercent ~= nil then
            self.valuemap.PegTypeLimit = "M";
            local gained_per_pip = self.Offer.PipCost * (self.valuemap.Quantity / self:_GetBaseUnitSize());
            local equity = core.host:findTable("accounts"):find("AccountID", self.valuemap.AcctID).Equity;
            local target_profit = equity * (self.EquityTargetPercent  / 100.0);
            local limit = target_profit / gained_per_pip;
            self.valuemap.PegPriceOffsetPipsLimit = self.valuemap.BuySell == "B" and limit or -limit;
        end
        return self.valuemap;
    end
    function builder:Execute()
        self:BuildValueMap();
        local id = self.Parent:getId();
        local success, msg = terminal:execute(id, self.valuemap);
        if not(success) then
            local message = "Open order failed: " .. msg;
            self.Parent:trace(message);
            if self.Parent._signaler ~= nil then self.Parent._signaler:Signal(message); end
            return trading:CreateEntryOrderFailResult(message);
        end
        local res = trading:CreateEntryOrderSuccessResult(msg);
        self.Parent._waiting_requests[id] = res;
        return res;
    end
    return builder;
end

function trading:StoreMarketOrderResults(res)
    local str = "[";
    for i, t in ipairs(res) do
        local json = t:ToJSON();
        if str == "[" then str = str .. json; else str = str .. "," .. json; end
    end
    return str .. "]";
end
function trading:RestoreMarketOrderResults(str)
    local results = {};
    local position = 2;
    local result;
    while (position < str:len()) do
        local ch = string.sub(str, position, position);
        if ch == "{" then
            result = trading:CreateMarketOrderSuccessResult();
            position = position + 1;
        elseif ch == "}" then
            results[#results + 1] = result;
            result = nil;
            position = position + 1;
        elseif ch == "," then
            position = position + 1;
        else
            local name, value = string.match(str, '"([^"]+)":("?[^,}]+"?)', position);
            if value == "false" then
                result[name] = false;
                position = position + name:len() + 8;
            elseif value == "true" then
                result[name] = true;
                position = position + name:len() + 7;
            else
                if string.sub(value, 1, 1) == "\"" then
                    result[name] = value;
                    value:sub(2, value:len() - 1);
                    position = position + name:len() + 3 + value:len();
                else
                    result[name] = tonumber(value);
                    position = position + name:len() + 3 + value:len();
                end
            end
        end
    end
    return results;
end
function trading:CreateMarketOrderSuccessResult(msg)
    local res = {};
    if msg ~= nil then res.Finished = false; else res.Finished = true; end
    res.RequestID = msg;
    function res:GetTrade()
        if self._trade == nil then
            self._trade = core.host:findTable("trades"):find("OpenOrderReqID", self.RequestID);
            if self._trade == nil then return nil; end
        end
        if not self._trade:refresh() then return nil; end
        return self._trade;
    end
    function res:GetClosedTrade()
        if self._closed_trade == nil and self.RequestID ~= nil then
            self._closed_trade = core.host:findTable("closed trades"):find("OpenOrderReqID", self.RequestID);
            if self._closed_trade == nil then return nil; end
        end
        if not self._closed_trade:refresh() then return nil; end
        return self._closed_trade;
    end
    function res:ToJSON()
        local json = {};
        function json:AddStr(name, value)
            local separator = "";
            if self.str ~= nil then separator = ","; else self.str = ""; end
            self.str = self.str .. string.format("%s\"%s\":\"%s\"", separator, tostring(name), tostring(value));
        end
        function json:AddNumber(name, value)
            local separator = "";
            if self.str ~= nil then separator = ","; else self.str = ""; end
            self.str = self.str .. string.format("%s\"%s\":%f", separator, tostring(name), value or 0);
        end
        function json:AddBool(name, value)
            local separator = "";
            if self.str ~= nil then separator = ","; else self.str = ""; end
            self.str = self.str .. string.format("%s\"%s\":%s", separator, tostring(name), value and "true" or "false");
        end
        function json:ToString() return "{" .. (self.str or "") .. "}"; end
        
        local first = true;
        for idx,t in pairs(self) do
            local stype = type(t)
            if stype == "number" then json:AddNumber(idx, t);
            elseif stype == "string" then json:AddStr(idx, t);
            elseif stype == "boolean" then json:AddBool(idx, t);
            elseif stype == "function" or stype == "table" then --do nothing
            else core.host:trace(tostring(idx) .. " " .. tostring(stype));
            end
        end
        return json:ToString();
    end
    return res;
end
function trading:CreateMarketOrderFailResult(message)
    local res = {};
    res.Finished = true;
    res.Success = false;
    res.Error = message;
    function res:GetTrade() return nil; end
    return res;
end

function trading:MarketOrder(instrument)
    local builder = {};
    local offer = core.host:findTable("offers"):find("Instrument", instrument);
    builder.Instrument = instrument;
    builder.Offer = offer;
    builder.Parent = self;
    builder.valuemap = core.valuemap();
    builder.valuemap.Command = "CreateOrder";
    builder.valuemap.OrderType = "OM";
    builder.valuemap.OfferID = offer.OfferID;
    builder.valuemap.AcctID = self._account;
    function builder:_GetBaseUnitSize() if self._base_size == nil then self._base_size = core.host:execute("getTradingProperty", "baseUnitSize", self.Instrument, self.valuemap.AcctID); end return self._base_size; end
    function builder:SetAccountID(accountID) self.valuemap.AcctID = accountID; return self; end
    function builder:SetAmount(amount) self._amount = amount; return self; end
    function builder:SetRiskPercentOfEquityAmount(percent) self._RiskPercentOfEquityAmount = percent; return self; end
    function builder:SetPercentOfEquityAmount(percent) self._PercentOfEquityAmount = percent; return self; end
    function builder:SetPercentOfMarginAmount(percent) self._PercentOfMarginAmount = percent; return self; end
    function builder:SetSide(buy_sell) self.valuemap.BuySell = buy_sell; return self; end
    function builder:SetExecutionType(type) self.valuemap.GTC = type; return self; end
    function builder:SetPipLimit(limit_type, limit)
        self.valuemap.PegTypeLimit = limit_type or "O";
        self.valuemap.PegPriceOffsetPipsLimit = self.valuemap.BuySell == "B" and limit or -limit;
        return self;
    end
    function builder:SetLimit(limit) self.valuemap.RateLimit = limit; return self; end
    function builder:SetPipStop(stop_type, stop, trailing_stop)
        self.valuemap.PegTypeStop = stop_type or "O";
        self.valuemap.PegPriceOffsetPipsStop = self.valuemap.BuySell == "B" and -stop or stop;
        self.valuemap.TrailStepStop = trailing_stop;
        return self;
    end
    function builder:SetStop(stop, trailing_stop) self.valuemap.RateStop = stop; self.valuemap.TrailStepStop = trailing_stop; return self; end
    function builder:SetCustomID(custom_id) self.valuemap.CustomID = custom_id; return self; end
    function builder:GetValueMap() return self.valuemap; end
    function builder:AddMetadata(id, val) if self._metadata == nil then self._metadata = {}; end self._metadata[id] = val; return self; end
    function builder:FillFields()
        local base_size = self:_GetBaseUnitSize();
        if self._metadata ~= nil then
            self._metadata.CustomID = self.valuemap.CustomID;
            self.valuemap.CustomID = trading:ObjectToJson(self._metadata);
        end
        if self._PercentOfEquityAmount ~= nil then
            local equity = core.host:findTable("accounts"):find("AccountID", self.valuemap.AcctID).Equity;
            local used_equity = equity * self._PercentOfEquityAmount / 100.0;
            local emr = core.host:getTradingProperty("EMR", self.Offer.Instrument, self.valuemap.AcctID);
            self.valuemap.Quantity = math.floor(used_equity / emr) * base_size;
        elseif self._RiskPercentOfEquityAmount ~= nil then
            local equity = core.host:findTable("accounts"):find("AccountID", self.valuemap.AcctID).Equity;
            local affordable_loss = equity * self._RiskPercentOfEquityAmount / 100.0;
            assert(self.valuemap.PegPriceOffsetPipsStop ~= nil, "Only pip stop are supported");
            local possible_loss = self.Offer.PipCost * self.valuemap.PegPriceOffsetPipsStop;
            self.valuemap.Quantity = math.floor(affordable_loss / possible_loss) * base_size;
        elseif self._PercentOfEquityAmount ~= nil then
            local equity = core.host:findTable("accounts"):find("AccountID", self.valuemap.AcctID).UsableMargin;
            local used_equity = equity * self._PercentOfMarginAmount / 100.0;
            local emr = core.host:getTradingProperty("EMR", self.Offer.Instrument, self.valuemap.AcctID);
            self.valuemap.Quantity = math.floor(used_equity / emr) * self:_GetBaseUnitSize();
        else
            self.valuemap.Quantity = self._amount * base_size;
        end
    end
    function builder:Execute()
        self.Parent:trace(string.format("Creating %s OM for %s", self.valuemap.BuySell, self.Instrument));
        self:FillFields();
        local id = self.Parent:getId();
        local success, msg = terminal:execute(id, self.valuemap);
        if not(success) then
            local message = "Open order failed: " .. msg;
            self.Parent:trace(message);
            if self.Parent._signaler ~= nil then
                self.Parent._signaler:Signal(message);
            end
            return trading:CreateMarketOrderFailResult(message);
        end
        local res = trading:CreateMarketOrderSuccessResult(msg);
        self.Parent._waiting_requests[id] = res;
        return res;
    end
    return builder;
end

function trading:ReadValue(json, position)
    local whaitFor = "";
    local start = position;
    while (position < json:len() + 1) do
        local ch = string.sub(json, position, position);
        position = position + 1;
        if ch == "\"" then
            start = position - 1;
            whaitFor = ch;
            break;
        elseif ch == "{" then
            start = position - 1;
            whaitFor = "}";
            break;
        elseif ch == "," or ch == "}" then
            return string.sub(json, start, position - 2), position - 1;
        end
    end
    while (position < json:len() + 1) do
        local ch = string.sub(json, position, position);
        position = position + 1;
        if ch == whaitFor then
            return string.sub(json, start, position - 1), position;
        end
    end
    return "", position;
end
function trading:JsonToObject(json)
    local position = 1;
    local result;
    local results;
    while (position < json:len() + 1) do
        local ch = string.sub(json, position, position);
        if ch == "{" then
            result = {};
            position = position + 1;
        elseif ch == "}" then
            if results ~= nil then
                position = position + 1;
                results[#results + 1] = result;
            else
                return result;
            end
        elseif ch == "," then
            position = position + 1;
        elseif ch == "[" then
            position = position + 1;
            results = {};
        elseif ch == "]" then
            return results;
        else
            if result == nil then
                return nil;
            end
            local name = string.match(json, '"([^"]+)":', position);
            local value, new_pos = trading:ReadValue(json, position + name:len() + 3);
            position = new_pos;
            if value == "false" then
                result[name] = false;
            elseif value == "true" then
                result[name] = true;
            else
                if string.sub(value, 1, 1) == "\"" then
                    result[name] = value:sub(2, value:len() - 1);
                elseif string.sub(value, 1, 1) == "{" then
                    result[name] = trading:JsonToObject(value);
                else
                    result[name] = tonumber(value);
                end
            end
        end
    end
    return nil;
end

function trading:GetMetadata(qtxt)
    if qtxt == "" then
        return nil;
    end
    local position = 1;
    local result;
    while (position < qtxt:len() + 1) do
        local ch = string.sub(qtxt, position, position);
        if ch == "{" then
            result = {};
            position = position + 1;
        elseif ch == "}" then
            return result;
        elseif ch == "," then
            position = position + 1;
        else
            if result == nil then
                return nil;
            end
            local name, value = string.match(qtxt, '"([^"]+)":("?[^,}]+"?)', position);
            if value == "false" then
                result[name] = false;
                position = position + name:len() + 8;
            elseif value == "true" then
                result[name] = true;
                position = position + name:len() + 7;
            else
                if string.sub(value, 1, 1) == "\"" then
                    result[name] = value;
                    value:sub(2, value:len() - 1);
                    position = position + name:len() + 3 + value:len();
                else
                    result[name] = tonumber(value);
                    position = position + name:len() + 3 + value:len();
                end
            end
        end
    end
    return nil;
end

function trading:GetTradeMetadata(trade)
    return self:GetMetadata(trade.QTXT);
end
if Modules ~= nil then
    trading:RegisterModule(Modules);
end
breakeven = {};
-- public fields
breakeven.Name = "Breakeven";
breakeven.Version = "5.0";
breakeven.Debug = false;
--private fields
breakeven._moved_stops = {};
breakeven._request_id = nil;
breakeven._used_stop_orders = {};
breakeven._ids_start = 200;
breakeven._trading = nil;
breakeven._controllers = {};

function breakeven:trace(str) if not self.Debug then return; end core.host:trace(self.Name .. ": " .. str); end
function breakeven:OnNewModule(module)
    if module.Name == "Trading" then 
        self._trading = module;
    elseif module.Name == "Tables monitor" then
        module:ListenCloseTrade(BreakevenOnClosedTrade);
    elseif module.Name == "Signaler" then
        self._signaler = module;
    elseif module.Name == "Storage" then
        self._storage = module;
    end
end
function BreakevenOnClosedTrade(closed_trade)
    for _, controller in ipairs(breakeven._controllers) do
        if controller.TradeID == closed_trade.TradeID then
            controller._trade = core.host:findTable("trades"):find("TradeID", closed_trade.TradeIDRemain);
            controller.TradeID = closed_trade.TradeIDRemain;
        elseif controller.TradeID == closed_trade.TradeIDRemain then
            controller._executed = true;
            controller._close_percent = nil;
        end
    end
end
function breakeven:RegisterModule(modules) for _, module in pairs(modules) do self:OnNewModule(module); module:OnNewModule(self); end modules[#modules + 1] = self; self._ids_start = (#modules) * 100; end

function breakeven:Init(parameters)
end

function breakeven:Prepare(nameOnly)
    if breakeven._storage == nil then
        return;
    end
    local enum = core.host:findTable("trades"):enumerator();
    local trade = enum:next();
    while trade ~= nil do
        if breakeven._storage:ReadNumber("BE_" .. trade.TradeID) == 1 then
            self:CreateBreakeven():Restore(trade.TradeID);
        end
        trade = enum:next();
    end
end

function breakeven:ReleaseInstance()
    for _, controller in ipairs(self._controllers) do
        if controller.Save ~= nil then
            controller:Save();
        end
    end
end

function breakeven:ExtUpdate(id, source, period)
    for _, controller in ipairs(self._controllers) do
        controller:DoBreakeven();
    end
end

function breakeven:round(num, idp)
    if idp and idp > 0 then
        local mult = 10 ^ idp
        return math.floor(num * mult + 0.5) / mult
    end
    return math.floor(num + 0.5)
end

function breakeven:CreatePLGTCondition(pl)
    local condition = {};
    condition._pl = pl;
    function condition:IsPass(data)
        local trade = data:GetTrade();
        if trade == nil then
            return false;
        end
        if not trade:refresh() then
            return true;
        end
        return trade.PL >= self._pl;
    end
    return condition;
end

function breakeven:CreateMoveStopAction(to, trailing)
    local action = {};
    function action:Execute(data)
        if self._command ~= nil then
            return self._command.Finished;
        end
        local trade = data:GetTrade();
        if trade == nil then
            return false;
        end
        if not trade:refresh() then
            return true;
        end
        self._command = self._parent._trading:MoveStop(trade, self:getTo(), self._trailing);
        return false;
    end
    -- private start
    action._to = to;
    action._trailing = trailing;
    function action:getTo()
        local trade = self:GetTrade();
        local offer = self:GetOffer();
        if trade.BS == "B" then
            return offer.Bid - (trade.PL - self._to) * offer.PointSize;
        else
            return offer.Ask + (trade.PL - self._to) * offer.PointSize;
        end
    end
    -- private end
    return action;
end

function breakeven:CreatePartialClose(toClosePercent, mode)
    local action = {};
    function action:Execute(data)
        if self._command ~= nil then
            if not self._command.Finished then
                return false;
            end
            if self._command.Success then
                return true;
            end
            return self:doClose(data);
        end
        return self:doClose(data);
    end
    function action:getLots(data, trade)
        if self._mode == "initial_lots" then
            return data:GetInitialLots();
        end
        return trade.Lot;
    end
    -- private start
    function action:doClose(data)
        local trade = data:GetTrade();
        if trade == nil then
            return false;
        end
        if not trade:refresh() then
            return true;
        end
        local base_size = core.host:execute("getTradingProperty", "baseUnitSize", trade.Instrument, trade.AccountID);
        local to_close = breakeven:round(self:getLots(data, trade) * self._toClosePercent / 100.0 / base_size) * base_size;
        self._command = trading:PartialClose(trade, to_close);
        return false;
    end
    action._toClosePercent = toClosePercent;
    action._mode = mode;
    -- private end
    return action;
end

function breakeven:CreateController(condition)
    local controller = {};
    function controller:AddAction(action)
        self._actions[#self._actions + 1] = action;
    end
    function controller:DoLogic()
        if #self._actions == 0 then
            return;
        end
        if self._condition:IsPass(self) then
            if self._actions:Execute(self) then
                table.remove(self._actions, 1);
            end
        end
    end
    function controller:GetInitialLots()
    end
    function controller:Save()
    end
    function controller:Restore(tradeID)
    end
    function controller:SetTrade(trade)
        self._trade = trade;
        self.TradeID = trade.TradeID;
        self._initial_limit = self._trade.Limit;
        self._initial_stop = self._trade.Stop;
        self._initial_lot = self._trade.Lot;
        return self;
    end
    function controller:GetOffer()
        if self._offer == nil then
            local order = self:GetOrder();
            if order == nil then
                order = self:GetTrade();
            end
            self._offer = core.host:findTable("offers"):find("Instrument", order.Instrument);
        end
        return self._offer;
    end
    function controller:SetRequestID(trade_request_id)
        self._request_id = trade_request_id;
        return self;
    end
    function controller:GetOrder()
        if self._order == nil and self._request_id ~= nil then
            self._order = core.host:findTable("orders"):find("RequestID", self._request_id);
            if self._order ~= nil then
                self.OrderID = self._order.OrderID;
            end
        end
        return self._order;
    end
    function controller:GetTrade()
        if self._trade == nil and self._request_id ~= nil then
            self:SetTrade(core.host:findTable("trades"):find("OpenOrderReqID", self._request_id));
            if self._trade == nil then
                return nil;
            end
        end
        return self._trade;
    end
    -- private start
    controller._parent = self;
    controller._executed = false;
    controller._actions = {};
    controller._condition = condition;
    -- private end
    return controller;
end

function breakeven:CreateBaseController()
    local controller = {};
    controller._parent = self;
    controller._executed = false;
    function controller:SetTrade(trade)
        self._trade = trade;
        self.TradeID = trade.TradeID;
        return self;
    end
    function controller:GetOffer()
        if self._offer == nil then
            local order = self:GetOrder();
            if order == nil then
                order = self:GetTrade();
            end
            self._offer = core.host:findTable("offers"):find("Instrument", order.Instrument);
        end
        return self._offer;
    end
    function controller:SetRequestID(trade_request_id)
        self._request_id = trade_request_id;
        return self;
    end
    function controller:GetOrder()
        if self._order == nil and self._request_id ~= nil then
            self._order = core.host:findTable("orders"):find("RequestID", self._request_id);
            if self._order ~= nil then
                self.OrderID = self._order.OrderID;
            end
        end
        return self._order;
    end
    function controller:GetTrade()
        if self._trade == nil and self._request_id ~= nil then
            self._trade = core.host:findTable("trades"):find("OpenOrderReqID", self._request_id);
            if self._trade == nil then
                return nil;
            end
            self.TradeID = self._trade.TradeID;
            self._initial_limit = self._trade.Limit;
            self._initial_stop = self._trade.Stop;
        end
        return self._trade;
    end
    return controller;
end

function breakeven:CreateMartingale(openFunction)
    local controller = self:CreateBaseController();
    controller.OpenFunction = openFunction;
    function controller:SetStep(step)
        self._step = step;
        return self;
    end
    function controller:SetLotSizingValue(martingale_lot_sizing_val)
        self._martingale_lot_sizing_val = martingale_lot_sizing_val;
        return self;
    end
    function controller:SetStop(Stop)
        self._martingale_stop = Stop;
        return self;
    end
    function controller:SetLimit(Limit)
        self._martingale_limit = Limit;
        return self;
    end
    function controller:DoBreakeven()
        if self._executed then
            return false;
        end
        local trade = self:GetTrade();
        if trade == nil then
            return true;
        end
        if not trade:refresh() then
            self._executed = true;
            return false;
        end
        if self._current_lot == nil then
            self._current_lot = trade.AmountK;
        end
        local pipSize = self:GetOffer().PointSize;
        if trade.BS == "B" then
            local movement = (trade.Close - trade.Open) / pipSize;
            local enoughtMovement = false;
            if self._step >= 0 then
                enoughtMovement = movement <= -self._step;
            else
                enoughtMovement = movement >= -self._step;
            end
            if enoughtMovement then
                self._current_lot = self._current_lot * self._martingale_lot_sizing_val;
                local result = self.OpenFunction("B", math.floor(self._current_lot + 0.5), trade);
                self._trade = nil;
                self:SetRequestID(result.RequestID);
                if self._signaler ~= nil then
                    local command = string.format("action=create symbol=%s side=buy quantity=%s"
                        , trade.Instrument
                        , tostring(math.floor(self._current_lot + 0.5)));
                    self._signaler:SendCommand(command);
                end
                return true;
            end
        else
            local movement = (trade.Open - trade.Close) / pipSize;
            if self._step >= 0 then
                enoughtMovement = movement <= -self._step;
            else
                enoughtMovement = movement >= -self._step;
            end
            if enoughtMovement then
                self._current_lot = self._current_lot * self._martingale_lot_sizing_val;
                local result = self.OpenFunction("S", math.floor(self._current_lot + 0.5), trade);
                self._trade = nil;
                self:SetRequestID(result.RequestID);
                if self._signaler ~= nil then
                    local command = string.format("action=create symbol=%s side=sell quantity=%s"
                        , trade.Instrument
                        , tostring(math.floor(self._current_lot + 0.5)));
                    self._signaler:SendCommand(command);
                end
                return true;
            end
        end
        self:UpdateStopLimits();
        return true;
    end
    function controller:CloseAll()
        core.host:trace("Closing all positions");
        local it = trading:FindTrade():WhenCustomID(CustomID)
        it:Do(function (trade) trading:Close(trade); end);
        if self._signaler ~= nil then
            signaler:SendCommand("action=close");
        end
        self._executed = true;
    end
    function controller:UpdateStopLimits()
        local trade = self:GetTrade();
        if trade == nil then
            return;
        end
        local offer = self:GetOffer();
        local bAmount = 0;
        local bPriceSumm = 0;
        local sAmount = 0;
        local sPriceSumm = 0;
        trading:FindTrade()
            :WhenCustomID(CustomID)
            :Do(function (trade)
                if trade.BS == "B" then
                    bAmount = bAmount + trade.AmountK
                    bPriceSumm = bPriceSumm + trade.Open * trade.AmountK;
                else
                    sAmount = sAmount + trade.AmountK
                    sPriceSumm = sPriceSumm + trade.Open * trade.AmountK;
                end
            end);
        local avgBPrice = bPriceSumm / bAmount;
        local avgSPrice = sPriceSumm / sAmount;
        local totalAmount = bAmount + sAmount;
        local avgPrice = avgBPrice * (bAmount / totalAmount) + avgSPrice * (sAmount / totalAmount);
        local stopPrice, limitPrice;
        if trade.BS == "B" then
            if self._martingale_stop ~= nil then
                stopPrice = avgPrice - self._martingale_stop * offer.PointSize;
                if trade.Close <= stopPrice then
                    self:CloseAll();
                end
                return;
            end
            if self._martingale_limit ~= nil then
                limitPrice = avgPrice + self._martingale_limit * offer.PointSize;
                if trade.Close >= limitPrice then
                    self:CloseAll();
                end
            end
            return;
        end
        if self._martingale_stop ~= nil then
            stopPrice = avgPrice + self._martingale_stop * offer.PointSize;
            if trade.Close >= stopPrice then
                self:CloseAll();
                return;
            end
        end
        if self._martingale_limit ~= nil then
            limitPrice = avgPrice - self._martingale_limit * offer.PointSize;
            if trade.Close <= limitPrice then
                self:CloseAll();
            end
        end
    end
    self._controllers[#self._controllers + 1] = controller;
    return controller;
end

breakeven.STOP_ID = 1;
breakeven.LIMIT_ID = 2;

function breakeven:CreateOrderTrailingController()
    local controller = self:CreateBaseController();
    function controller:SetTrailingTarget(id)
        self._target_id = id;
        return self;
    end
    function controller:MoveUpOnly()
        self._up_only = true;
        return self;
    end
    function controller:SetIndicatorStream(stream, multiplicator, is_distance)
        self._stream = stream;
        self._stream_in_distance = is_distance;
        self._stream_multiplicator = multiplicator;
        return self;
    end
    function controller:SetIndicatorStreamShift(x, y)
        self._stream_x_shift = x;
        self._stream_y_shift = y;
        return self;
    end
    function controller:DoBreakeven()
        if self._executed then
            return false;
        end
        local order = self:GetOrder();
        if order == nil or (self._move_command ~= nil and not self._move_command.Finished) then
            return true;
        end
        if not order:refresh() then
            self._executed = true;
            return false;
        end
        local streamPeriod = NOW;
        if self._stream_x_shift ~= nil then
            streamPeriod = streamPeriod - self._stream_x_shift;
        end
        if not self._stream:hasData(streamPeriod) then
            return true;
        end
        return self:DoOrderTrailing(order, streamPeriod);
    end
    function controller:DoOrderTrailing(order, streamPeriod)
        local new_level;
        local offer = self:GetOffer();
        if self._stream_in_distance then
            local tick = self._stream:tick(streamPeriod) * self._stream_multiplicator;
            if self._stream_y_shift ~= nil then
                tick = tick + self._stream_y_shift * offer.PointSize;
            end
            if order.BS == "B" then
                new_level = breakeven:round(offer.Bid + tick, offer.Digits);
            else
                new_level = breakeven:round(offer.Ask - tick, offer.Digits);
            end
        else
            local tick = self._stream:tick(streamPeriod);
            if self._stream_y_shift ~= nil then
                if order.BS == "B" then
                    tick = tick - self._stream_y_shift * offer.PointSize;
                else
                    tick = tick + self._stream_y_shift * offer.PointSize;
                end
            end
            new_level = breakeven:round(tick, offer.Digits);
        end
        if self._up_only then
            if order.BS == "B" then
                if order.Rate >= new_level then
                    return true;
                end
            else
                if order.Rate <= new_level then
                    return true;
                end
            end
        end
        if self._min_profit ~= nil then
            if order.BS == "B" then
                if (offer.Bid - new_level) / offer.PointSize < self._min_profit then
                    return true;
                end
            else
                if (new_level - offer.Ask) / offer.PointSize < self._min_profit then
                    return true;
                end
            end
        end
        if order.Rate ~= new_level then
            self._move_command = self._parent._trading:ChangeOrder(order, new_level, order.TrlMinMove);
        end
    end
    self._controllers[#self._controllers + 1] = controller;
    return controller;
end

function breakeven:CreateIndicatorTrailingController()
    local controller = self:CreateBaseController();
    function controller:SetTrailingTarget(id)
        self._target_id = id;
        return self;
    end
    function controller:MoveUpOnly()
        self._up_only = true;
        return self;
    end
    function controller:SetMinProfit(min_profit)
        self._min_profit = min_profit;
        return self;
    end
    function controller:SetIndicatorStream(stream, multiplicator, is_distance)
        self._stream = stream;
        self._stream_in_distance = is_distance;
        self._stream_multiplicator = multiplicator;
        return self;
    end
    function controller:SetIndicatorStreamShift(x, y)
        self._stream_x_shift = x;
        self._stream_y_shift = y;
        return self;
    end
    function controller:DoBreakeven()
        if self._executed then
            return false;
        end
        local trade = self:GetTrade();
        if trade == nil or (self._move_command ~= nil and not self._move_command.Finished) then
            return true;
        end
        if not trade:refresh() then
            self._executed = true;
            return false;
        end
        local streamPeriod = NOW;
        if self._stream_x_shift ~= nil then
            streamPeriod = streamPeriod - self._stream_x_shift;
        end
        if not self._stream:hasData(streamPeriod) then
            return true;
        end
        if self._target_id == breakeven.STOP_ID then
            return self:DoStopTrailing(trade, streamPeriod);
        elseif self._target_id == breakeven.LIMIT_ID then
            return self:DoLimitTrailing(trade, streamPeriod);
        end
        return self:DoOrderTrailing(trade, streamPeriod);
    end
    function controller:DoStopTrailing(trade, streamPeriod)
        local new_level;
        local offer = self:GetOffer();
        if self._stream_in_distance then
            local tick = self._stream:tick(streamPeriod) * self._stream_multiplicator;
            if self._stream_y_shift ~= nil then
                tick = tick + self._stream_y_shift * offer.PointSize;
            end
            if trade.BS == "B" then
                new_level = breakeven:round(trade.Open - tick, offer.Digits);
            else
                new_level = breakeven:round(trade.Open + tick, offer.Digits);
            end
        else
            local tick = self._stream:tick(streamPeriod);
            if self._stream_y_shift ~= nil then
                if trade.BS == "B" then
                    tick = tick + self._stream_y_shift * offer.PointSize;
                else
                    tick = tick - self._stream_y_shift * offer.PointSize;
                end
            end
            new_level = breakeven:round(self._stream:tick(streamPeriod), offer.Digits);
        end
        if self._min_profit ~= nil then
            if trade.BS == "B" then
                if (new_level - trade.Open) / offer.PointSize < self._min_profit then
                    return true;
                end
            else
                if (trade.Open - new_level) / offer.PointSize < self._min_profit then
                    return true;
                end
            end
        end
        if self._up_only then
            if trade.BS == "B" then
                if trade.Stop >= new_level then
                    return true;
                end
            else
                if trade.Stop <= new_level then
                    return true;
                end
            end
            return true;
        end
        if trade.Stop ~= new_level then
            self._move_command = self._parent._trading:MoveStop(trade, new_level);
        end
        return true;
    end
    function controller:DoLimitTrailing(trade, streamPeriod)
        assert(self._up_only == nil, "Not implemented!!!");
        local new_level;
        local offer = self:GetOffer();
        if self._stream_in_distance then
            local tick = self._stream:tick(streamPeriod) * self._stream_multiplicator;
            if self._stream_y_shift ~= nil then
                tick = tick + self._stream_y_shift * offer.PointSize;
            end
            if trade.BS == "B" then
                new_level = breakeven:round(trade.Open + tick, offer.Digits);
            else
                new_level = breakeven:round(trade.Open - tick, offer.Digits);
            end
        else
            local tick = self._stream:tick(streamPeriod);
            if self._stream_y_shift ~= nil then
                if trade.BS == "B" then
                    tick = tick - self._stream_y_shift * offer.PointSize;
                else
                    tick = tick + self._stream_y_shift * offer.PointSize;
                end
            end
            new_level = breakeven:round(tick, offer.Digits);
        end
        if self._min_profit ~= nil then
            if trade.BS == "B" then
                if (trade.Open - new_level) / offer.PointSize < self._min_profit then
                    return true;
                end
            else
                if (new_level - trade.Open) / offer.PointSize < self._min_profit then
                    return true;
                end
            end
        end
        if trade.Limit ~= new_level then
            self._move_command = self._parent._trading:MoveLimit(trade, new_level);
        end
    end
    self._controllers[#self._controllers + 1] = controller;
    return controller;
end

function breakeven:CreateTrailingLimitController()
    local controller = self:CreateBaseController();
    function controller:SetDirection(direction)
        self._direction = direction;
        return self;
    end
    function controller:SetTrigger(trigger)
        self._trigger = trigger;
        return self;
    end
    function controller:SetStep(step)
        self._step = step;
        return self;
    end
    function controller:DoBreakeven()
        if self._executed then
            return false;
        end
        local trade = self:GetTrade();
        if trade == nil or (self._move_command ~= nil and not self._move_command.Finished) then
            return true;
        end
        if not trade:refresh() then
            self._executed = true;
            return false;
        end
        if self._direction == 1 then
            if trade.PL >= self._trigger then
                local offer = self:GetOffer();
                local target_limit;
                if trade.BS == "B" then
                    target_limit = self._initial_limit + self._step * offer.PointSize; 
                else
                    target_limit = self._initial_limit - self._step * offer.PointSize; 
                end
                self._initial_limit = target_limit;
                self._trigger = self._trigger + self._step;
                self._move_command = self._parent._trading:MoveLimit(trade, target_limit);
                return true;
            end
        elseif self._direction == -1 then
            if trade.PL <= -self._trigger then
                local offer = self:GetOffer();
                local target_limit;
                if trade.BS == "B" then
                    target_limit = self._initial_limit - self._step * offer.PointSize; 
                else
                    target_limit = self._initial_limit + self._step * offer.PointSize; 
                end
                self._initial_limit = target_limit;
                self._trigger = self._trigger + self._step;
                self._move_command = self._parent._trading:MoveLimit(trade, target_limit);
                return true;
            end
        else
            core.host:trace("No direction is set for the trailing limit");
        end
        return true;
    end
    self._controllers[#self._controllers + 1] = controller;
    return controller;
end

function breakeven:ActionOnTrade(action)
    local controller = self:CreateBaseController();
    controller._action = action;
    function controller:DoBreakeven()
        if self._executed then
            return false;
        end
        local trade = self:GetTrade();
        if trade == nil then
            return true;
        end
        if not trade:refresh() then
            self._executed = true;
            return false;
        end
        self._action(trade, self);
        self._executed = true;
        return true;
    end
    self._controllers[#self._controllers + 1] = controller;
    return controller;
end

function breakeven:CreateOnCandleClose()
    local controller = self:CreateBaseController();
    controller._trailing = 0;
    function controller:SetSource(source)
        self._source = source;
        return self;
    end
    function controller:SetBarsToLive(bars_to_live)
        self._bars_to_live = bars_to_live;
        return self;
    end
    function controller:DoBreakeven()
        if self._executed then
            return true;
        end
        local trade = self:GetTrade();
        if trade == nil then
            return true;
        end
        if not trade:refresh() then
            self._executed = true;
            return false;
        end
        local index = core.findDate(self._source, trade.Time, false);
        if self._source:size() - 1 - index >= self._bars_to_live then
            self._command = self._parent._trading:Close(trade);
            self._executed = true;
            return false;
        end
        return true;
    end
    self._controllers[#self._controllers + 1] = controller;
    return controller;
end


function breakeven:CreateBreakeven()
    local controller = self:CreateBaseController();
    function controller:Save()
        if self._executed or breakeven._storage == nil then
            return;
        end
        local trade = self:GetTrade();
        if trade ~= nil then
            breakeven._storage.SaveNumber("BE_" .. trade.TradeID, 1);
            breakeven._storage:SaveNumber("BE_" .. trade.TradeID .. "_when", self._when);
            breakeven._storage:SaveNumber("BE_" .. trade.TradeID .. "_to", self._to);
            breakeven._storage:SaveNumber("BE_" .. trade.TradeID .. "_trailing", self._trailing);
            breakeven._storage:SaveNumber("BE_" .. trade.TradeID .. "_close_percent", self._close_percent);
        end
    end
    function controller:Restore(tradeID)
        self._when = breakeven._storage:ReadNumber("BE_" .. trade.TradeID .. "_when");
        self._to = breakeven._storage:ReadNumber("BE_" .. trade.TradeID .. "_to");
        self._trailing = breakeven._storage:ReadNumber("BE_" .. trade.TradeID .. "_trailing");
        self._close_percent = breakeven._storage:ReadNumber("BE_" .. trade.TradeID .. "_close_percent");
    end
    self._controllers[#self._controllers + 1] = controller;
    return controller;
end

function breakeven:RestoreTrailingOnProfitController(controller)
    controller._parent = self;
    function controller:SetProfitPercentage(profit_pr, min_profit)
        self._profit_pr = profit_pr;
        self._min_profit = min_profit;
        return self;
    end
    function controller:GetClosedTrade()
        if self._closed_trade == nil and self.RequestID ~= nil then
            self._closed_trade = core.host:findTable("closed trades"):find("OpenOrderReqID", self._request_id);
            if self._closed_trade == nil then return nil; end
        end
        if not self._closed_trade:refresh() then return nil; end
        return self._closed_trade;
    end
    function controller:getStopPips(trade)
        local stop = trading:FindStopOrder(trade);
        if stop == nil then
            return nil;
        end
        local offer = self:GetOffer();
        if trade.BS == "B" then
            return (stop.Rate - trade.Open) / offer.PointSize;
        else
            return (trade.Open - stop.Rate) / offer.PointSize;
        end
    end
    function controller:DoBreakeven()
        if self._executed then
            return false;
        end
        if self._move_command ~= nil and not self._move_command.Finished then
            return true;
        end
        local trade = self:GetTrade();
        if trade == nil then
            if self:GetClosedTrade() ~= nil then
                self._executed = true;
            end
            return true;
        end
        if not trade:refresh() then
            self._executed = true;
            return false;
        end
        if trade.PL < self._min_profit then
            return true;
        end
        local new_stop = trade.PL * (self._profit_pr / 100);
        local current_stop = self:getStopPips(trade);
        if current_stop == nil or current_stop < new_stop then
            local offer = self:GetOffer();
            if trade.BS == "B" then
                if not trailing_mark:hasData(NOW) then
                    trailing_mark[NOW] = trade.Close;
                end
                self._move_command = self._parent._trading:MoveStop(trade, trade.Open + new_stop * offer.PointSize);
            else
                if not trailing_mark:hasData(NOW) then
                    trailing_mark[NOW] = trade.Close;
                end
                self._move_command = self._parent._trading:MoveStop(trade, trade.Open - new_stop * offer.PointSize);
            end
            return true;
        end
        return true;
    end
end

function breakeven:CreateTrailingOnProfitController()
    local controller = self:CreateBaseController();
    controller._trailing = 0;
    self:RestoreTrailingOnProfitController(controller);
    self._controllers[#self._controllers + 1] = controller;
    return controller;
end
if Modules ~= nil then
    breakeven:RegisterModule(Modules);
end
-- Available @ https://fxcodebase.com/code/viewtopic.php?f=17&t=76018

-- +------------------------------------------------------------------------------------------------+
-- |                                                              Copyright 2025, Gehtsoft USA LLC  | 
-- |                                                                         http://fxcodebase.com  |
-- |                                                               Paypal:  https://goo.gl/9Rj74e   |
-- +------------------------------------------------------------------------------------------------+
-- |                                                                   Developed by : Mario Jemic   |                    
-- |                                                                       mario.jemic@gmail.com    |
-- |                                                                       https://mario-jemic.com/ | 
-- |                                                             Patreon :  http://tiny.cc/1ybwxz   |   
-- |                                                      Buy Me a Coffee:  http://tiny.cc/bj7vxz   |  
-- +-----------------+----------------------+-------------------------------------------------------+
-- |  Cryptocurrency |  Network             |  Address                                              |
-- +-----------------+----------------------+-------------------------------------------------------+
-- |  BTC            |  BTC                 |  16F5k43RXibTmna4np8bPVgmXM1CzjXFJJ                   | 
-- |  SOL            |  SOL                 |  3nh5rpUKopcYLNU4zGCdUFAkM3iRQq8VVUmuzVG6VDf2         | 
-- |  ETH            |  ERC20               |  0xe53aab6bc468a963a02d1319660ee60cf80fc8e7           |
-- |  BNB            |  BEP20               |  0xe53aab6bc468a963a02d1319660ee60cf80fc8e7           | 
-- |  USDT           |  BEP20               |  0xe53aab6bc468a963a02d1319660ee60cf80fc8e7           | 
-- |  XRP            |  BEP20               |  0xe53aab6bc468a963a02d1319660ee60cf80fc8e7           | 
-- +-----------------+----------------------+-------------------------------------------------------+ 