-- More information about this indicator can be found at:
-- http://fxcodebase.com/code/viewtopic.php?f=31&t=65680

--+------------------------------------------------------------------+
--|                               Copyright © 2018, Gehtsoft USA LLC | 
--|                                            http://fxcodebase.com |
--+------------------------------------------------------------------+
--|                                      Developed by : Mario Jemic  |
--|                                          mario.jemic@gmail.com   |
--+------------------------------------------------------------------+
--|                                 Support our efforts by donating  | 
--|                                    Paypal: https://goo.gl/9Rj74e |
--|                    BitCoin : 15VCJTLaz12Amr7adHSBtL9v8XomURo9RF  |
--|                BitCoin Cash: 1BEtS465S3Su438Kc58h2sqvVvHK9Mijtg  |
--|           Ethereum : 0x8C110cD61538fb6d7A2B47858F0c0AaBd663068D  |
--|                   LiteCoin : LLU8PSY2vsq7B9kRELLZQcKf5nJQrdeqwD  |
--+------------------------------------------------------------------+

local Modules = {};

function Init()
    strategy:name("Conjure Strategy");
    strategy:description("");

    strategy.parameters:addBoolean("is_buy", "Is Buy", "", true);
    strategy.parameters:addDouble("step", "Step", "", 50);
    strategy.parameters:addBoolean("AllowTrade", "Allow strategy to trade", "", true);   
    strategy.parameters:setFlag("AllowTrade", core.FLAG_ALLOW_TRADE);
    strategy.parameters:addString("Account", "Account to trade on", "", "");
    strategy.parameters:setFlag("Account", core.FLAG_ACCOUNT);
    strategy.parameters:addInteger("Amount", "Trade Amount in Lots", "", 1, 1, 100);
    strategy.parameters:addDouble("ProfitLevel", "Profit level for close orders in the account currency", "", 0);
    strategy.parameters:addString("ProfitType", "Profit Type of level for close orders", "", "Fixed");
    strategy.parameters:addStringAlternative("ProfitType", "Fixed", "", "Fixed");
    strategy.parameters:addStringAlternative("ProfitType", "Percentage of balance", "", "Balance");
    
    strategy.parameters:addString("LevelType", "Type of profit level", "", "ABOVE");
    strategy.parameters:addStringAlternative("LevelType", "ABOVE", "", "ABOVE");
    strategy.parameters:addStringAlternative("LevelType", "BELOW", "", "BELOW");
end

local is_buy;
local step;
local AllowTrade;
local Account;
local Amount;
local Offer;

function Prepare(name_only)
    for _, module in pairs(Modules) do module:Prepare(nameOnly); end
    instance:name(profile:id() .. "(" .. instance.bid:name() ..  ")");
    if name_only then return ; end

    local profitlevel = instance.parameters.ProfitLevel
    assert(instance.parameters.ProfitType ~= "Balance" or (profitlevel >= 0 and profitlevel <= 100 ), "Incorrect value of the Profit Level. 0 <= Profit Level <= 100");

    is_buy = instance.parameters.is_buy;
    step = instance.parameters.step * instance.bid:pipSize();
    AllowTrade = instance.parameters.AllowTrade;
    Account = instance.parameters.Account;
    Amount = instance.parameters.Amount;
    BaseSize = core.host:execute("getTradingProperty", "baseUnitSize", instance.bid:instrument(), Account);
    Offer = core.host:findTable("offers"):find("Instrument", instance.bid:instrument()).OfferID;

    ExtSubscribe(1, nil, "t1", not is_buy, "tick");
end

local initial_price;
function ExtUpdate(id, source, period)
    if initial_price == nil then
        initial_price = source:tick(NOW);
    end
    if is_buy then
        if source:tick(NOW) - initial_price >= step then
            initial_price = initial_price + step;
            MarketOrder("B");
        end
    else
        if initial_price - source:tick(NOW) >= step then
            initial_price = initial_price - step;
            MarketOrder("S");
        end
    end
    local account = core.host:findTable("accounts"):find("AccountID", Account);
    local mmr = core.host:execute("getTradingProperty", "MMR", instance.bid:instrument(), Account);
    local emr = core.host:execute("getTradingProperty", "EMR", instance.bid:instrument(), Account);
    local margin_needed = math.max(emr, mmr) * Amount;
    if account.UsedMargin >= account.Balance or account.UsableMargin < margin_needed then
        local offer_id = core.host:findTable("offers"):find("Instrument", instance.bid:instrument()).OfferID;
        CloseAll(offer_id, Account, "B");
        CloseAll(offer_id, Account, "S");
    end
    ControlProfit(id, source, period);
end

-- -----------------------------------------------------------------------
-- Return count of opened trades for spicific direction 
-- (both directions if BuySell parameters is 'nil')
-- -----------------------------------------------------------------------
function haveTrades(BuySell) 
    local enum, row;
    local found = false;
    enum = core.host:findTable("trades"):enumerator();
    row = enum:next();
    while (not found) and (row ~= nil) do
        if row.AccountID == Account and
           row.Instrument == instance.bid:instrument() and
           (row.BS == BuySell or BuySell == nil) then
           found = true;
        end
        row = enum:next();
    end

    return found
end

-- -----------------------------------------------------------------------
--  Exit from the specified direction
-- -----------------------------------------------------------------------
function exit(BuySell, row)
    if not(AllowTrade) then
        return ;
    end

    local valuemap, success, msg;
    if haveTrades(BuySell) then
        valuemap = core.valuemap();

        -- switch the direction since the order must be in oppsite direction
        if BuySell == "B" then
            BuySell = "S";
        else
            BuySell = "B";
        end
        valuemap.OrderType = "CM";
        valuemap.OfferID=row.OfferID;
        valuemap.AcctID = Account;
        valuemap.NetQtyFlag = "N";
        valuemap.TradeID=row.TradeID;
        valuemap.Quantity=row.Lot;
        valuemap.BuySell = BuySell;
        success, msg = terminal:execute(101, valuemap);

        if not(success) then
            terminal:alertMessage(instance.bid:instrument(), instance.bid[instance.bid:size() - 1], "Open order failed" .. msg, instance.bid:date(instance.bid:size() - 1));
        end
    end
end

function ControlProfit(id, source, period)
    if haveTrades() then
        local trades = core.host:findTable("trades");
        local enum = trades:enumerator();
        local PL=0;
        while true do
            local row = enum:next();
            if row == nil then break end

            if row.AccountID == Account and row.Instrument==instance.bid:instrument() then
                if row.BS == 'B' then
                    PL=PL+row.GrossPL; 
                elseif row.BS == 'S' then
                    PL=PL+row.GrossPL; 
                end
            end
        end
        local profitLevel =  instance.parameters.ProfitLevel;
        local currentBalance = core.host:findTable("accounts"):find("AccountID", Account).Balance;
        if (instance.parameters.ProfitType == "Balance") then
            profitLevel = currentBalance * instance.parameters.ProfitLevel / 100;
        end

        if (PL >= profitLevel and instance.parameters.LevelType=="ABOVE") or (PL<=profitLevel and instance.parameters.LevelType=="BELOW") then
            local enum = trades:enumerator();
            while true do
                local row = enum:next();
                if row == nil then break end
                if row.AccountID == Account and row.Instrument==instance.bid:instrument() then
                    if row.BS == 'B' then
                        if AllowTrade then
                            exit('B',row);
                        end
                    elseif row.BS == 'S' then
                        if AllowTrade then
                            exit('S',row);
                        end
                    end
                end
            end
        end
    end
end

function CloseAll(offer_id, account_id, bs)
    local valuemap;
    valuemap = core.valuemap();
    valuemap.Command = "CreateOrder";
    valuemap.OrderType = "CM";
    valuemap.OfferID = offer_id;
    valuemap.BuySell = bs;
    valuemap.AcctID = account_id;
    valuemap.NetQtyFlag = "y";
    local success, msg;
    success, msg = terminal:execute(100, valuemap);
    assert(success, msg);
end

function MarketOrder(BuySell)
    local valuemap = core.valuemap();
    valuemap.Command = "CreateOrder";
    valuemap.OrderType = "OM";
    valuemap.OfferID = Offer;
    valuemap.AcctID = Account;
    valuemap.Quantity = Amount * BaseSize;
    valuemap.BuySell = BuySell;

    local success, msg = terminal:execute(200, valuemap);
    if not(success) then
        terminal:alertMessage(instance.bid:instrument(), instance.bid[NOW], "Open order failed" .. msg, instance.bid:date(NOW));
        return false;
    end

    return true;
end

dofile(core.app_path() .. "\\strategies\\standard\\include\\helper.lua");
