-- 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)