-- Id: 4362
-- More information about this indicator can be found at:
-- http://fxcodebase.com/code/viewtopic.php?f=31&t=5440

--+------------------------------------------------------------------+
--|                               Copyright © 2018, Gehtsoft USA LLC |
--|                                            http://fxcodebase.com |
--+------------------------------------------------------------------+
--|                                 Support our efforts by donating  |
--|                                    Paypal: https://goo.gl/9Rj74e |
--+------------------------------------------------------------------+
--|                                Patreon :  https://goo.gl/GdXWeN  |
--|                    BitCoin : 15VCJTLaz12Amr7adHSBtL9v8XomURo9RF  |
--|                BitCoin Cash: 1BEtS465S3Su438Kc58h2sqvVvHK9Mijtg  |
--|           Ethereum : 0x8C110cD61538fb6d7A2B47858F0c0AaBd663068D  |
--|                   LiteCoin : LLU8PSY2vsq7B9kRELLZQcKf5nJQrdeqwD  |
--+------------------------------------------------------------------+

function Init() --The strategy profile initialization
    strategy:name("Sathiam strategy 2")
    strategy:description("Sathiam strategy 2")
    strategy:setTag(
        "NonOptimizableParameters",
        "Email,SendEmail,SoundFile,RecurrentSound,PlaySound,ShowAlert,Account,CanTrade,Email,SendEmail"
    )
    strategy:setTag("Version", "1.1")

    strategy.parameters:addGroup("Parameters")
    strategy.parameters:addInteger("EMA_Period", "EMA_Period", "", 5)
    strategy.parameters:addInteger("SMA_Period", "SMA_Period", "", 8)
    strategy.parameters:addInteger("MACD_FastPeriod", "MACD_FastPeriod", "", 21)
    strategy.parameters:addInteger("MACD_SlowPeriod", "MACD_SlowPeriod", "", 55)
    strategy.parameters:addInteger("MACD_SignalPeriod", "MACD_SignalPeriod", "", 8)
    strategy.parameters:addInteger("SSD_PeriodK", "SSD_PeriodK", "", 8)
    strategy.parameters:addInteger("SSD_SlowPeriodD", "SSD_SlowPeriodD", "", 5)
    strategy.parameters:addInteger("SSD_PeriodD", "SSD_PeriodD", "", 3)
    strategy.parameters:addString("Price", "Price", "", "close")
    strategy.parameters:addStringAlternative("Price", "close", "", "close")
    strategy.parameters:addStringAlternative("Price", "open", "", "open")
    strategy.parameters:addStringAlternative("Price", "high", "", "high")
    strategy.parameters:addStringAlternative("Price", "low", "", "low")
    strategy.parameters:addStringAlternative("Price", "median", "", "median")
    strategy.parameters:addStringAlternative("Price", "typical", "", "typical")
    strategy.parameters:addStringAlternative("Price", "weighted", "", "weighted")

    strategy.parameters:addGroup("Strategy Parameters")
    strategy.parameters:addDouble("Level", "Level", "Level", 50)
    strategy.parameters:addString("TypeSignal", "Type of signal", "", "direct")
    strategy.parameters:addStringAlternative("TypeSignal", "direct", "", "direct")
    strategy.parameters:addStringAlternative("TypeSignal", "reverse", "", "reverse")

    strategy.parameters:addGroup("Price Parameters")
    strategy.parameters:addString("TF", "Time Frame", "", "m15")
    strategy.parameters:setFlag("TF", core.FLAG_PERIODS)

    strategy.parameters:addGroup("Trading Parameters")
    strategy.parameters:addBoolean("AllowTrade", "Allow strategy to trade", "", false)
    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:addBoolean("SetLimit", "Set Limit Orders", "", false)
    strategy.parameters:addInteger("Limit", "Limit Order in pips", "", 30, 1, 10000)
    strategy.parameters:addBoolean("SetStop", "Set Stop Orders", "", false)
    strategy.parameters:addInteger("Stop", "Stop Order in pips", "", 30, 1, 10000)
    strategy.parameters:addBoolean("TrailingStop", "Trailing stop order", "", false)
    strategy.parameters:addString("AllowDirection", "Allow direction for positions", "", "Both")
    strategy.parameters:addStringAlternative("AllowDirection", "Both", "", "Both")
    strategy.parameters:addStringAlternative("AllowDirection", "Long", "", "Long")
    strategy.parameters:addStringAlternative("AllowDirection", "Short", "", "Short")

    strategy.parameters:addGroup("Signal Parameters")
    strategy.parameters:addBoolean("ShowAlert", "Show Alert", "", true)
    strategy.parameters:addBoolean("PlaySound", "Play Sound", "", false)
    strategy.parameters:addFile("SoundFile", "Sound File", "", "")
    strategy.parameters:setFlag("SoundFile", core.FLAG_SOUND)
    strategy.parameters:addBoolean("Recurrent", "RecurrentSound", "", false)

    strategy.parameters:addGroup("Email Parameters")
    strategy.parameters:addBoolean("SendEmail", "Send email", "", false)
    strategy.parameters:addString("Email", "Email address", "", "")
    strategy.parameters:setFlag("Email", core.FLAG_EMAIL)
end

-- Signal Parameters
local ShowAlert
local SoundFile
local RecurrentSound
local SendEmail, Email

-- Internal indicators
local EMA = nil
local SMA = nil
local MACD = nil
local SSD = nil

-- Strategy parameters
local openLevel = 0
local closeLevel = 0
local confirmTrend

-- Trading parameters
local AllowTrade = nil
local Account = nil
local Amount = nil
local BaseSize = nil
local PipSize
local SetLimit = nil
local Limit = nil
local SetStop = nil
local Stop = nil
local TrailingStop = nil
local CanClose = nil
local AllowDirection

--
--
--
function Prepare(nameOnly)
    ShowAlert = instance.parameters.ShowAlert
    AllowDirection = instance.parameters.AllowDirection
    local PlaySound = instance.parameters.PlaySound
    if PlaySound then
        SoundFile = instance.parameters.SoundFile
    else
        SoundFile = nil
    end
    assert(not (PlaySound) or SoundFile ~= "", "Sound file must be chosen")
    RecurrentSound = instance.parameters.Recurrent

    local SendEmail = instance.parameters.SendEmail
    if SendEmail then
        Email = instance.parameters.Email
    else
        Email = nil
    end
    assert(not (SendEmail) or Email ~= "", "Email address must be specified")
    assert(instance.parameters.TF ~= "t1", "The time frame must not be tick")

    local name
    name =
        profile:id() ..
        "(" ..
            instance.bid:name() ..
                "." ..
                    instance.parameters.TF ..
                        "," ..
                            "EMA(" ..
                                instance.parameters.EMA_Period ..
                                    "), SMA(" ..
                                        instance.parameters.SMA_Period ..
                                            "), MACD(" ..
                                                instance.parameters.MACD_FastPeriod ..
                                                    ", " ..
                                                        instance.parameters.MACD_SlowPeriod ..
                                                            ", " ..
                                                                instance.parameters.MACD_SignalPeriod ..
                                                                    "), SSD(" ..
                                                                        instance.parameters.SSD_PeriodK ..
                                                                            ", " ..
                                                                                instance.parameters.SSD_SlowPeriodD ..
                                                                                    ", " ..
                                                                                        instance.parameters.SSD_PeriodD ..
                                                                                            "))"
    instance:name(name)
    if nameOnly then
        return
    end

    AllowTrade = instance.parameters.AllowTrade
    if AllowTrade then
        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
        CanClose = core.host:execute("getTradingProperty", "canCreateMarketClose", instance.bid:instrument(), Account)
        PipSize = instance.bid:pipSize()
        SetLimit = instance.parameters.SetLimit
        Limit = instance.parameters.Limit
        SetStop = instance.parameters.SetStop
        Stop = instance.parameters.Stop
        TrailingStop = instance.parameters.TrailingStop
    end

    Source = ExtSubscribe(2, nil, instance.parameters.TF, true, "bar")
    Price = instance.parameters.Price
    if Price == "close" then
        Source_ = Source.close
    elseif Price == "open" then
        Source_ = Source.open
    elseif Price == "high" then
        Source_ = Source.high
    elseif Price == "low" then
        Source_ = Source.low
    elseif Price == "typical" then
        Source_ = Source.typical
    elseif Price == "median" then
        Source_ = Source.median
    else
        Source_ = Source.weighted
    end
    EMA = core.indicators:create("EMA", Source_, instance.parameters.EMA_Period)
    SMA = core.indicators:create("MVA", Source_, instance.parameters.SMA_Period)
    MACD =
        core.indicators:create(
        "MACD",
        Source_,
        instance.parameters.MACD_FastPeriod,
        instance.parameters.MACD_SlowPeriod,
        instance.parameters.MACD_SignalPeriod
    )
    SSD =
        core.indicators:create(
        "SSD",
        Source,
        instance.parameters.SSD_PeriodK,
        instance.parameters.SSD_SlowPeriodD,
        instance.parameters.SSD_PeriodD
    )

    ExtSetupSignal(profile:id() .. ":", ShowAlert)
    ExtSetupSignalMail(name)
end

function haveTrades(BuySell)
    local enum = core.host:findTable("trades"):enumerator()
    local row = enum:next()
    while (not found) and (row ~= nil) do
        if row.AccountID == Account and (row.BS == BuySell or BuySell == nil) then
            return true;
        end
        row = enum:next()
    end
    return false
end

function ExtUpdate(id, source, period) -- The method called every time when a new bid or ask price appears.
    EMA:update(core.UpdateLast)
    SMA:update(core.UpdateLast)
    MACD:update(core.UpdateLast)
    SSD:update(core.UpdateLast)

    -- Check that we have enough data
    if (EMA.DATA:first() > (period - 1)) then
        return
    end
    if (SMA.DATA:first() > (period - 1)) then
        return
    end
    if (MACD.DATA:first() > (period - 1)) then
        return
    end
    if (SSD.DATA:first() > (period - 1)) then
        return
    end

    local pipSize = instance.bid:pipSize()

    local trades = core.host:findTable("trades")

    local MustOpenB = false
    local MustOpenS = false
    local MustCloseB = false
    local MustCloseS = false

    if
        SSD.K[period - 1] < SSD.D[period - 1] and SSD.K[period] > SSD.D[period] and
            SSD.K[period] < instance.parameters.Level and
            MACD.MACD[period] > MACD.SIGNAL[period]
     then
        if instance.parameters.TypeSignal == "direct" then
            MustOpenB = true
        else
            MustOpenS = true
        end
    end

    if
        SSD.K[period - 1] > SSD.D[period - 1] and SSD.K[period] < SSD.D[period] and
            SSD.K[period] > instance.parameters.Level and
            MACD.MACD[period] < MACD.SIGNAL[period]
     then
        if instance.parameters.TypeSignal == "direct" then
            MustOpenS = true
        else
            MustOpenB = true
        end
    end

    if EMA.DATA[period - 1] > SMA.DATA[period - 1] and EMA.DATA[period] < SMA.DATA[period] then
        if instance.parameters.TypeSignal == "direct" then
            MustCloseB = true
        else
            MustCloseS = true
        end
    end

    if EMA.DATA[period - 1] < SMA.DATA[period - 1] and EMA.DATA[period] > SMA.DATA[period] then
        if instance.parameters.TypeSignal == "direct" then
            MustCloseS = true
        else
            MustCloseB = true
        end
    end

    if instance.parameters.AllowDirection == "Short" then
        MustOpenB = false
    end
    if instance.parameters.AllowDirection == "Long" then
        MustOpenS = false
    end

    if (haveTrades()) then
        local enum = trades:enumerator()
        while true do
            local row = enum:next()
            if row == nil then
                break
            end

            if row.AccountID == Account and row.OfferID == Offer then
                -- Close position if we have corresponding closing conditions.
                if row.BS == "B" then
                    if MustCloseB then
                        if ShowAlert then
                            ExtSignal(source, period, "Close BUY", SoundFile, Email, RecurrentSound)
                        end

                        if AllowTrade then
                            Close(row)
                        end
                    end
                    if MustOpenS then
                        if ShowAlert then
                            ExtSignal(source, period, "Open SELL", SoundFile, Email, RecurrentSound)
                        end

                        if AllowTrade then
                            Open("S")
                        end
                    end
                elseif row.BS == "S" then
                    if MustCloseS then
                        if ShowAlert then
                            ExtSignal(source, period, "Close SELL", SoundFile, Email, RecurrentSound)
                        end

                        if AllowTrade then
                            Close(row)
                        end
                    end
                    if MustOpenB then
                        if ShowAlert then
                            ExtSignal(source, period, "Open BUY", SoundFile, Email, RecurrentSound)
                        end

                        if AllowTrade then
                            Open("B")
                        end
                    end
                end
            end
        end
    else
        if MustOpenB == true then
            if ShowAlert then
                ExtSignal(source, period, "BUY", SoundFile, Email, RecurrentSound)
            end

            if AllowTrade then
                Open("B")
            end
        end

        if MustOpenS == true then
            if ShowAlert then
                ExtSignal(source, period, "SELL", SoundFile, Email, RecurrentSound)
            end

            if AllowTrade then
                Open("S")
            end
        end
    end
end

-- The strategy instance finalization.
function ReleaseInstance()
end

-- The method enters to the market
function Open(side)
    local valuemap

    valuemap = core.valuemap()
    valuemap.OrderType = "OM"
    valuemap.OfferID = Offer
    valuemap.AcctID = Account
    valuemap.Quantity = Amount * BaseSize
    valuemap.CustomID = CID
    valuemap.BuySell = side
    valuemap.QTXT = "1"
    if SetStop and CanClose then
        valuemap.PegTypeStop = "O"
        if side == "B" then
            valuemap.PegPriceOffsetPipsStop = -Stop
        else
            valuemap.PegPriceOffsetPipsStop = Stop
        end
        if TrailingStop then
            valuemap.TrailStepStop = 1
        end
    end
    if SetLimit and CanClose then
        valuemap.PegTypeLimit = "O"
        if side == "B" then
            valuemap.PegPriceOffsetPipsLimit = Limit
        else
            valuemap.PegPriceOffsetPipsLimit = -Limit
        end
    end
    success, msg = terminal:execute(200, valuemap)
    assert(success, msg)

    -- FIFO Account, in that case we have to open Net Limit and Stop Orders
    if not (CanClose) then
        if SetStop then
            valuemap = core.valuemap()
            valuemap.OrderType = "SE"
            valuemap.OfferID = Offer
            valuemap.AcctID = Account
            valuemap.NetQtyFlag = "y"
            if side == "B" then
                valuemap.BuySell = "S"
                rate = instance.ask[NOW] - Stop * PipSize
                valuemap.Rate = rate
            elseif side == "S" then
                valuemap.BuySell = "B"
                rate = instance.bid[NOW] + Stop * PipSize
                valuemap.Rate = rate
            end
            if TrailingStop then
                valuemap.TrailUpdatePips = 1
            end
            success, msg = terminal:execute(200, valuemap)
            --core.host:trace('Set stop @ ' .. rate);
            assert(success, msg)
        end
        if SetLimit then
            valuemap = core.valuemap()
            valuemap.OrderType = "LE"
            valuemap.OfferID = Offer
            valuemap.AcctID = Account
            valuemap.NetQtyFlag = "y"
            if side == "B" then
                valuemap.BuySell = "S"
                rate = instance.ask[NOW] + Limit * PipSize
                valuemap.Rate = rate
            elseif side == "S" then
                valuemap.BuySell = "B"
                rate = instance.bid[NOW] - Limit * PipSize
                valuemap.Rate = rate
            end
            success, msg = terminal:execute(200, valuemap)
            --core.host:trace('Set limit @ ' .. rate);
            assert(success, msg)
        end
    end
end

-- Closes specific position
function Close(trade)
    local valuemap
    valuemap = core.valuemap()

    if CanClose then
        -- non-FIFO account, create a close market order
        valuemap.OrderType = "CM"
        valuemap.TradeID = trade.TradeID
    else
        -- FIFO account, create an opposite market order
        valuemap.OrderType = "OM"
    end

    valuemap.OfferID = trade.OfferID
    valuemap.AcctID = trade.AccountID
    valuemap.Quantity = trade.Lot
    valuemap.CustomID = trade.QTXT
    if trade.BS == "B" then
        valuemap.BuySell = "S"
    else
        valuemap.BuySell = "B"
    end
    success, msg = terminal:execute(200, valuemap)
    assert(success, msg)
end

function AsyncOperationFinished(cookie, successful, message)
    if not successful then
        core.host:trace("Error: " .. message)
    end
end

dofile(core.app_path() .. "\\strategies\\standard\\include\\helper.lua")
