-- More information about this indicator can be found at:
-- http://fxcodebase.com/code/viewtopic.php?f=31&t=65822

--+------------------------------------------------------------------+
--|                               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 |
--+------------------------------------------------------------------+
--|                                Patreon :  https://goo.gl/GdXWeN  |
--|                    BitCoin : 15VCJTLaz12Amr7adHSBtL9v8XomURo9RF  |
--|                BitCoin Cash: 1BEtS465S3Su438Kc58h2sqvVvHK9Mijtg  |
--|           Ethereum : 0x8C110cD61538fb6d7A2B47858F0c0AaBd663068D  |
--|                   LiteCoin : LLU8PSY2vsq7B9kRELLZQcKf5nJQrdeqwD  |
--+------------------------------------------------------------------+

local Modules = {}
function Init()
    strategy:name("Pullback Strategy")
    strategy:description("")
    strategy.parameters:addInteger("ema_short_period", "EMA Short Period", "", 50)
    strategy.parameters:addInteger("ema_long_period", "EMA Long Period", "", 200)
    trading_logic:Init(strategy.parameters)
    trading:Init(strategy.parameters)
end
local ema_short = nil
local ema_long = nil
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
    assert(core.indicators:findIndicator("EMA") ~= nil, "Please, download and install EMA indicator")
    ema_short = core.indicators:create("EMA", trading_logic.MainSource, 50)
    ema_long = core.indicators:create("EMA", trading_logic.MainSource, 200)
    trading_logic.DoTrading = EntryFunction
end
function CalculateHH(source, period)
    local high = source.high[period]
    local i = period - 1
    while (i > 1) do
        if high < source.high[i] then
            high = source.high[i]
        end
        if not ema_short.DATA:hasData(i - 1) or not ema_long.DATA:hasData(i - 1) or core.crossesOver(ema_short.DATA, ema_long.DATA, i) then
            return high
        end
        i = i - 1
    end
    return high
end
function CalculateLL(source, period)
    local low = source.low[period]
    local i = period - 1
    while (i > 1) do
        if low > source.low[i] then
            low = source.low[i]
        end
        if not ema_short.DATA:hasData(i - 1) or not ema_long.DATA:hasData(i - 1) or core.crossesUnder(ema_short.DATA, ema_long.DATA, i) then
            return low
        end
        i = i - 1
    end
    return low
end
local hh
local ll
local last_serial
function EntryFunction(source, period)
    ema_short:update(core.UpdateLast)
    ema_long:update(core.UpdateLast)
    if period < 2 then
        return
    end
    if last_serial == source:serial(period) then
        return
    end

    if trading:TradesCount(nil) > (trading_logic.MaxNumberOfPositionInAnyDirection) then
        return
    end
    if not ema_short.DATA:hasData(period - 1) or not ema_long.DATA:hasData(period - 1) then
        return;
    end

    if ema_short.DATA[period] > ema_long.DATA[period] then
        if core.crossesUnder(source.close, ema_short.DATA, period) then
            hh = CalculateHH(source, period)
        end
        
        ll = nil
        if hh ~= nil and core.crossesOver(source.close, hh, period) then
            local command = trading:MarketOrder(source:instrument()):SetSide("B"):SetDefaultAmount()
            if instance.parameters.set_stop then
                command =
                    command:SetPipStop(
                    nil,
                    instance.parameters.stop,
                    instance.parameters.use_trailing and instance.parameters.trailing or nil
                )
            end
            if instance.parameters.set_limit then
                command = command:SetPipLimit(nil, instance.parameters.limit)
            end

            if trading:TradesCount("B") >= trading_logic.MaxNumberOfPosition then
                return
            end

            command:Execute()
            last_serial = source:serial(period)
        end
    elseif ema_short.DATA[period] < ema_long.DATA[period] then
        if core.crossesOver(source.close, ema_short.DATA, period) then
            ll = CalculateLL(source, period)
        end
        hh = nil
        if ll ~= nil and core.crossesUnder(source.close, ll, period) then
            local command = trading:MarketOrder(source:instrument()):SetSide("S"):SetDefaultAmount()
            if instance.parameters.set_stop then
                command =
                    command:SetPipStop(
                    nil,
                    instance.parameters.stop,
                    instance.parameters.use_trailing and instance.parameters.trailing or nil
                )
            end
            if instance.parameters.set_limit then
                command = command:SetPipLimit(nil, instance.parameters.limit)
            end
            if trading:TradesCount("S") >= trading_logic.MaxNumberOfPosition then
                return
            end

            command:Execute()
            last_serial = source:serial(period)
        end
    end
end
function ExtUpdate(id, source, period)
    for _, module in pairs(Modules) do
        if module.BlockTrading ~= nil and module:BlockTrading(id, source, period) then
            return
        end
    end
    for _, module in pairs(Modules) do
        if module.ExtUpdate ~= nil then
            module:ExtUpdate(id, source, period)
        end
    end
end
function ReleaseInstance()
    for _, module in pairs(Modules) do
        if module.ReleaseInstance ~= nil then
            module:ReleaseInstance()
        end
    end
end
function ExtAsyncOperationFinished(cookie, success, message, message1, message2)
    for _, module in pairs(Modules) do
        if module.AsyncOperationFinished ~= nil then
            module:AsyncOperationFinished(cookie, success, message, message1, message2)
        end
    end
end
dofile(core.app_path() .. "\\strategies\\standard\\include\\helper.lua")

trading_logic = {}
-- public fields
trading_logic.Name = "Trading logic"
trading_logic.Version = "1.2.1"
trading_logic.Debug = false
trading_logic.DoTrading = nil
trading_logic.DoExit = nil
trading_logic.MainSource = nil
trading_logic.MainSource = nil
trading_logic.MainSource = nil
trading_logic.MaxNumberOfPositionInAnyDirection = nil
trading_logic.MaxNumberOfPosition = nil

--private fields
trading_logic._ids_start = nil
trading_logic._trading_source_id = nil
function trading_logic:trace(str)
    if not self.Debug then
        return
    end
    core.host:trace(self.Name .. ": " .. str)
end
function trading_logic:OnNewModule(module)
end
function trading_logic: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_logic:Init(parameters)
    parameters:addGroup("Price")
    parameters:addBoolean("is_bid", "Price Type", "", true)
    parameters:setFlag("is_bid", core.FLAG_BIDASK)
    parameters:addString("timeframe", "Time frame", "", "m5")
    parameters:setFlag("timeframe", core.FLAG_PERIODS)
    parameters:addString("entry_execution_type", "Entry Execution Type", "", "Live")
    parameters:addStringAlternative("entry_execution_type", "End of Turn", "", "EndOfTurn")
    parameters:addStringAlternative("entry_execution_type", "Live", "", "Live")
    parameters:addInteger(
        "max_number_of_position_in_any_direction",
        "Max Number Of Open Position In Any Direction",
        "",
        2,
        1,
        100
    )
    parameters:addInteger("max_number_of_position", "Max Number Of Position In One Direction", "", 1, 1, 100)

    --parameters:addString("exit_execution_type", "Entry Execution Type", "", "Live");
    --parameters:addStringAlternative("exit_execution_type", "End of Turn", "", "EndOfTurn");
    --parameters:addStringAlternative("exit_execution_type", "Live", "", "Live");
end
function trading_logic:GetLastPeriod(source_period, source, target)
    if source_period < 0 or target:size() < 2 then
        return nil
    end
    local s1, e1 = core.getcandle(source:barSize(), source:date(source_period), -7, 0)
    local s2, e2 = core.getcandle(target:barSize(), target:date(NOW - 1), -7, 0)
    if e1 == e2 then
        return target:size() - 2
    else
        return target:size() - 1
    end
end
function trading_logic:GetPeriod(source_period, source, target)
    if source_period < 0 then
        return nil
    end
    local source_date = source:date(source_period)
    local index = core.findDate(target, source_date, false)
    if index == -1 then
        return nil
    end
    return index
end
function trading_logic:Prepare(name_only)
    if name_only then
        return
    end
    if instance.parameters.entry_execution_type == "Live" then
        self._trading_source_id = self._ids_start + 1
        ExtSubscribe(self._trading_source_id, nil, "t1", instance.parameters.is_bid, "close")
        self:trace("Trading on tick (live data)")
    else
        self._trading_source_id = self._ids_start + 2
        self:trace(string.format("Trading on %s bar", instance.parameters.timeframe))
    end
    if instance.parameters.exit_execution_type == "Live" then
        self._exit_source_id = self._ids_start + 1
        if self._trading_source_id ~= self._exit_source_id then
            ExtSubscribe(self._exit_source_id, nil, "t1", instance.parameters.is_bid, "close")
        end
        self:trace("Exit on tick (live data)")
    else
        self._exit_source_id = self._ids_start + 2
        self:trace(string.format("Trading on %s bar", instance.parameters.timeframe))
    end
    self.MainSource =
        ExtSubscribe(self._ids_start + 2, nil, instance.parameters.timeframe, instance.parameters.is_bid, "bar")
    self.MaxNumberOfPositionInAnyDirection = instance.parameters.max_number_of_position_in_any_direction
    self.MaxNumberOfPosition = instance.parameters.max_number_of_position
end
function trading_logic:ExtUpdate(id, source, period)
    self:trace(string.format("Trading on %s bar", instance.parameters.timeframe))

    if id == self._trading_source_id and self.DoTrading ~= nil then
        local period2 = period
        if source ~= self.MainSource then
            period2 = core.findDate(self.MainSource, source:date(period), false)
            if period2 == -1 then
                return
            end
        end
        self.DoTrading(self.MainSource, period2)
    end
    if id == self._exit_source_id and self.DoExit ~= nil then
        local period2 = period
        if source ~= self.MainSource then
            period2 = core.findDate(self.MainSource, source:date(period), false)
            if period2 == -1 then
                return
            end
        end
        self.DoExit(self.MainSource, period2)
    end
end
trading_logic:RegisterModule(Modules)
trading = {}
trading.Name = "Trading"
trading.Version = "3.0.2"
trading.Debug = false
trading.AddAmountParameter = true
trading.AddStopParameter = true
trading.AddLimitParameter = true
trading.CustomID = nil
trading.Offer = nil
trading._ids_start = nil
trading._signaler = nil
trading._allow_trade = false
trading._account = nil
trading._amount = 1
trading._all_modules = {}
trading._limit = nil
trading._stop = nil
trading._trailing_stop = nil
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:Init(parameters)
    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)
    if self.AddAmountParameter then
        parameters:addInteger("amount", "Trade Amount in Lots", "", 1)
    end
    if self.AddStopParameter then
        parameters:addBoolean("set_stop", "Set Stop Orders", "", false)
        parameters:addInteger("stop", "Stop Order in pips", "", 30)
        parameters:addBoolean("use_trailing", "Trailing stop order", "", false)
        parameters:addInteger(
            "trailing",
            "Trailing in pips",
            "Use 1 for dynamic and 10 or greater for the fixed trailing",
            1
        )
    end
    if self.AddLimitParameter then
        parameters:addBoolean("set_limit", "Set Limit Orders", "", false)
        parameters:addInteger("limit", "Limit Order in pips", "", 30)
    end
end
function trading:Prepare(name_only)
    --do what you usually do in prepare
    if name_only then
        return
    end
    self._account = instance.parameters.account
    if self.AddAmountParameter then
        self._amount = instance.parameters.amount
    end
    self._allow_trade = instance.parameters.allow_trade
    if instance.parameters.set_limit then
        self._limit = instance.parameters.limit
    end
    if instance.parameters.set_stop then
        self._stop = instance.parameters.stop
        if instance.parameters.use_trailing then
            self._trailing_stop = instance.parameters.trailing
        end
    end
    self.Offer = core.host:findTable("offers"):find("Instrument", instance.bid:instrument()).OfferID
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
        res.Error = not success and message or nil
        if not success then
            self:trace(string.format("Failed request %s", tostring(message)))
        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:calculateAmount()
    return self._amount
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(stop_rate, trade, 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(limit_rate, trade)
    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:ChangeOrder(rate, order)
    local min_change = core.host:findTable("offers"):find("Instrument", order.Instrument).PointSize
    if math.abs(rate - order.Rate) > min_change then
        self:trace(string.format("Changing an order to %s", tostring(rate)))
        -- stop exists
        local valuemap = core.valuemap()
        valuemap.Command = "EditOrder"
        valuemap.AcctID = order.AccountID
        valuemap.OrderID = order.OrderID
        valuemap.TrailUpdatePips = order.TrlMinMove ~= 0 and order.TrlMinMove or nil
        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: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 row.ContingencyType == 3 and IsLimitOrderType(row.Type) 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
                row.ContingencyType == 3 and self:IsStopOrderType(row.Type) 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(stop_rate, trade)
    local order = self:FindStopOrder(trade)
    if order == nil then
        self:trace("Stop order not found, creating a new one")
        return self:CreateStopOrder(stop_rate, trade)
    else
        return self:ChangeOrder(stop_rate, order)
    end
end
function trading:MoveLimit(limit_rate, trade)
    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(limit_rate, trade)
    else
        return self:ChangeOrder(limit_rate, order)
    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:MoveOrder(order, new_rate)
    local min_change = core.host:findTable("offers"):find("Instrument", order.Instrument).PointSize
    if math.abs(new_rate - order.Rate) > min_change then
        self:trace(string.format("Changing an order to %s", tostring(new_rate)))
        -- stop exists
        local valuemap = core.valuemap()
        valuemap.Command = "EditOrder"
        valuemap.AcctID = order.AccountID
        valuemap.OrderID = order.OrderID
        valuemap.Rate = new_rate
        local success, msg = terminal:execute(200, valuemap)
        if not (success) then
            terminal:alertMessage(order.Instrument, new_rate, "Failed change stop " .. msg, core.now())
        end
    end
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: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: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
            (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
    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: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
            (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: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: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
            (row.QTXT == self.CustomID or not self.CustomID) 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: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:ParialClose(trade, amount)
    -- not finished
    local account = core.host:findTable("accounts"):find("AccountID", trade.AccountID)
    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(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
    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(200, valuemap)
    assert(success, msg)
    return true
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: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 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 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 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:CreateEntryOrderFailResult(message)
    local res = {}
    res.Finished = true
    res.Success = false
    res.Error = message
    function res:GetOrder()
        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:SetDefaultAmount()
        self.valuemap.Quantity = self.Parent:calculateAmount() * self:_GetBaseUnitSize()
        return self
    end
    function builder:SetAmount(amount)
        self.valuemap.Quantity = amount * self:_GetBaseUnitSize()
        return self
    end
    function builder:SetPercentOfEquityAmount(percent)
        self._PercentOfEquityAmount = percent
        return self
    end
    function builder:SetSide(buy_sell)
        self.valuemap.BuySell = buy_sell
        return self
    end
    function builder:SetRate(rate)
        if self.valuemap.BuySell == "B" then
            self.valuemap.OrderType = self.Offer.Ask > rate and "LE" or "SE"
        else
            self.valuemap.OrderType = self.Offer.Bid > rate and "SE" or "LE"
        end
        self.valuemap.Rate = rate
        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: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:Execute()
        local desc =
            string.format(
            "Creating %s %s for %s at %f",
            self.valuemap.BuySell,
            self.valuemap.OrderType,
            self.Instrument,
            self.valuemap.Rate
        )
        if self.valuemap.RateStop ~= nil then
            desc = desc .. " stop " .. self.valuemap.RateStop
        end
        if self.valuemap.RateLimit ~= nil then
            desc = desc .. " limit " .. self.valuemap.RateLimit
        end
        self.Parent:trace(desc)
        if self._PercentOfEquityAmount ~= nil then
            local equity = core.host:findTable("accounts"):find("AccountID", self.valuemap.AcctID).Equity
            local affordable_loss = equity * self._PercentOfEquityAmount / 100.0
            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()
        end
        for _, module in pairs(self.Parent._all_modules) do
            if module.BlockOrder ~= nil and module:BlockOrder(self.valuemap) then
                self.Parent:trace("Creation of order blocked by " .. module.Name)
                return trading:CreateEntryOrderFailResult("Creation of order blocked by " .. module.Name)
            end
        end
        if not self.Parent._allow_trade then
            local message = string.format("%s signal for %s", self.valuemap.BuySell, self.Instrument)
            self.Parent:trace(message)
            if self.Parent._signaler ~= nil then
                self.Parent._signaler:Signal(message)
            end
            return trading:CreateEntryOrderSuccessResult()
        end
        for _, module in pairs(self.Parent._all_modules) do
            if module.OnOrder ~= nil then
                module:OnOrder(self.valuemap)
            end
        end
        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 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.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:SetAccountID(accountID)
        self.valuemap.AcctID = accountID
        return self
    end
    function builder:SetAmount(amount)
        local base_size = core.host:execute("getTradingProperty", "baseUnitSize", self.Instrument, self.valuemap.AcctID)
        self.valuemap.Quantity = amount * base_size
        return self
    end
    function builder:SetDefaultAmount()
        local base_size = core.host:execute("getTradingProperty", "baseUnitSize", self.Instrument, self.Parent._account)
        self.valuemap.Quantity = self.Parent:calculateAmount() * base_size
        return self
    end
    function builder:SetSide(buy_sell)
        self.valuemap.BuySell = buy_sell
        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:Execute()
        self.Parent:trace(string.format("Creating %s OM for %s", self.valuemap.BuySell, self.Instrument))
        for _, module in pairs(self.Parent._all_modules) do
            if module.BlockOrder ~= nil and module:BlockOrder(self.valuemap) then
                self.Parent:trace("Creation of order blocked by " .. module.Name)
                return trading:CreateMarketOrderFailResult("Creation of order blocked by " .. module.Name)
            end
        end
        if not self.Parent._allow_trade then
            local message = string.format("%s signal for %s", self.valuemap.BuySell, self.Instrument)
            self.Parent:trace(message)
            if self.Parent._signaler ~= nil then
                self.Parent._signaler:Signal(message)
            end
            return trading:CreateMarketOrderSuccessResult()
        end
        for _, module in pairs(self.Parent._all_modules) do
            if module.OnOrder ~= nil then
                module:OnOrder(self.valuemap)
            end
        end
        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:TradesCount(BuySell)
    local enum, row
    local count = 0
    enum = core.host:findTable("trades"):enumerator()
    row = enum:next()
    while row ~= nil do
        if
            row.AccountID == instance.parameters.account and row.OfferID == trading.Offer and
                (row.BS == BuySell or BuySell == nil)
         then
            count = count + 1
        end

        row = enum:next()
    end

    return count
end

trading:RegisterModule(Modules)
