-- Id: 21922 -- More information about this indicator can be found at: -- http://fxcodebase.com/code/viewtopic.php?f=31&t=66167 --+------------------------------------------------------------------+ --| Copyright © 2018, Gehtsoft USA LLC | --| http://fxcodebase.com | --+------------------------------------------------------------------+ --| Developed by : Mario Jemic | --| mario.jemic@gmail.com | --+------------------------------------------------------------------+ --| Support our efforts by donating | --| Paypal: https://goo.gl/9Rj74e | --+------------------------------------------------------------------+ --| BitCoin : 15VCJTLaz12Amr7adHSBtL9v8XomURo9RF | --| BitCoin Cash: 1BEtS465S3Su438Kc58h2sqvVvHK9Mijtg | --| Ethereum : 0x8C110cD61538fb6d7A2B47858F0c0AaBd663068D | --| LiteCoin : LLU8PSY2vsq7B9kRELLZQcKf5nJQrdeqwD | --+------------------------------------------------------------------+ local Modules = {}; function Init() --The strategy profile initialization strategy:name("VWAP Stratagy") strategy:description("MC. Trend Trader v1 2018-08-07") strategy:setTag("NonOptimizableParameters", "Email,SendEmail,SoundFile,RecurrentSound,PlaySound, ShowAlert") strategy.parameters:addInteger("ema_period", "EMA Period", "", 10); strategy.parameters:addGroup("Price") strategy.parameters:addString("Type", "Price Type", "", "Bid") strategy.parameters:addStringAlternative("Type", "Bid", "", "Bid") strategy.parameters:addStringAlternative("Type", "Ask", "", "Ask") strategy.parameters:addString("TF", "Time frame", "", "m1") strategy.parameters:setFlag("TF", core.FLAG_PERIODS) strategy.parameters:addGroup("Calculate") strategy.parameters:addString("Mode", "Mode", "", "Daily") strategy.parameters:addStringAlternative("Mode", "Daily", "", "Daily") strategy.parameters:addStringAlternative("Mode", "Weekly", "", "Weekly") strategy.parameters:addStringAlternative("Mode", "Monthly", "", "Monthly") CreateTradingParameters() end function AddTrade(i) strategy.parameters:addGroup("Trade " .. i); strategy.parameters:addBoolean("open_" .. i, "Open trade " .. i, "", false); strategy.parameters:addInteger("amount_" .. i, "Trade " .. i, "", 1, 1, 100); strategy.parameters:addBoolean("set_limit_" .. i, "Set Limit for Trade " .. i, "", false); strategy.parameters:addInteger("limit_" .. i, "Limit for Trade " .. i .. ", in pips", "", 30, 1, 10000); strategy.parameters:addBoolean("set_stop_" .. i, "Set Stop for Trade " .. i, "", false); strategy.parameters:addInteger("stop_" .. i, "Stop for Trade " .. i, "", 30, 1, 10000); strategy.parameters:addBoolean("use_trailing_" .. i, "Trailing Stop order for Trade " .. i, "", false); strategy.parameters:addInteger("trailing_" .. i, "Trailing for Trade " .. i .. ", in pips", "", 10); strategy.parameters:addBoolean("use_breakeven_" .. i, "Breakeaven for Trade " .. i, "", false); strategy.parameters:addInteger("breakeven_when_" .. i, "Min Profit for Trade " .. i, "", 10); end function CreateTradingParameters() strategy.parameters:addGroup("Execution Parameters") strategy.parameters:addBoolean("AllowTrade", "Allow strategy to trade", "", true) strategy.parameters:setFlag("AllowTrade", core.FLAG_ALLOW_TRADE) strategy.parameters:addString("AccountType", "Account Type", "", "Automatic") strategy.parameters:addStringAlternative("AccountType", "FIFO", "", "FIFO") strategy.parameters:addStringAlternative("AccountType", "non FIFO", "", "NON") strategy.parameters:addStringAlternative("AccountType", "Automatic", "", "Automatic") strategy.parameters:addString("EntryExecutionType", "Entry Execution Type", "", "EndOfTurn") strategy.parameters:addStringAlternative("EntryExecutionType", "End of Turn", "", "EndOfTurn") strategy.parameters:addStringAlternative("EntryExecutionType", "Live", "", "Live") strategy.parameters:addGroup("Trade Parameters") strategy.parameters:addBoolean("CloseOnOpposite", "Close On Opposite", "", true) strategy.parameters:addString( "CustomID", "Custom Identifier", "The identifier that can be used to distinguish strategy instances", "VWAPS" ) strategy.parameters:addBoolean("PositionCap", "Use Position Cap", "", false) strategy.parameters:addInteger( "MaxNumberOfPositionInAnyDirection", "Max Number Of Open Position In Any Direction", "", 2 ) strategy.parameters:addInteger("MaxNumberOfPosition", "Max Number Of Position In One Direction", "", 1) strategy.parameters:addString( "ALLOWEDSIDE", "Allowed side", "Allowed side for trading or signaling, can be Sell, Buy or Both", "Both" ) strategy.parameters:addStringAlternative("ALLOWEDSIDE", "Both", "", "Both") strategy.parameters:addStringAlternative("ALLOWEDSIDE", "Buy", "", "Buy") strategy.parameters:addStringAlternative("ALLOWEDSIDE", "Sell", "", "Sell") strategy.parameters:addString("Direction", "Type of Signal / Trade", "", "direct") strategy.parameters:addStringAlternative("Direction", "Direct", "", "direct") strategy.parameters:addStringAlternative("Direction", "Reverse", "", "reverse") strategy.parameters:addString("Account", "Account to trade on", "", "") strategy.parameters:setFlag("Account", core.FLAG_ACCOUNT) AddTrade(1); AddTrade(2); AddTrade(3); AddTrade(4); AddTrade(5); strategy.parameters:addGroup("Alerts") signaler:Init(strategy.parameters); strategy.parameters:addGroup("Time Parameters") strategy.parameters:addInteger("ToTime", "Convert the date to", "", 6) strategy.parameters:addIntegerAlternative("ToTime", "EST", "", 1) strategy.parameters:addIntegerAlternative("ToTime", "UTC", "", 2) strategy.parameters:addIntegerAlternative("ToTime", "Local", "", 3) strategy.parameters:addIntegerAlternative("ToTime", "Server", "", 4) strategy.parameters:addIntegerAlternative("ToTime", "Financial", "", 5) strategy.parameters:addIntegerAlternative("ToTime", "Display", "", 6) strategy.parameters:addString("StartTime", "Start Time for Trading", "", "00:00:00") strategy.parameters:addString("StopTime", "Stop Time for Trading", "", "24:00:00") strategy.parameters:addBoolean("UseMandatoryClosing", "Use Mandatory Closing", "", false) strategy.parameters:addString("ExitTime", "Mandatory Closing Time", "", "23:59:00") strategy.parameters:addInteger("ValidInterval", "Valid interval for operation in second", "", 60) end local AccountType local Source, TickSource local MaxNumberOfPositionInAnyDirection, MaxNumberOfPosition local ALLOWEDSIDE local AllowTrade local Offer local CanClose local Account local BaseSize local EntyExecutionType, ExitExecutionType local CloseOnOpposite local first local Direction local CustomID local PositionCap local TF local OpenTime, CloseTime, ExitTime local LastEntry, LastExit local ToTime local ValidInterval, UseMandatoryClosing --Indicator parameters local Indicator local stream; local Mode local ema; local trades = {}; function Prepare(nameOnly) for _, module in pairs(Modules) do module:Prepare(nameOnly); end CustomID = instance.parameters.CustomID Mode = instance.parameters.Mode name = profile:id() .. ", " .. instance.bid:name() .. ", " .. CustomID instance:name(name) if nameOnly then return end AccountType = instance.parameters.AccountType EntryExecutionType = instance.parameters.EntryExecutionType ExitExecutionType = instance.parameters.ExitExecutionType CloseOnOpposite = instance.parameters.CloseOnOpposite MaxNumberOfPositionInAnyDirection = instance.parameters.MaxNumberOfPositionInAnyDirection MaxNumberOfPosition = instance.parameters.MaxNumberOfPosition Direction = instance.parameters.Direction == "direct" TF = instance.parameters.TF ToTime = instance.parameters.ToTime if ToTime == 1 then ToTime = core.TZ_EST elseif ToTime == 2 then ToTime = core.TZ_UTC elseif ToTime == 3 then ToTime = core.TZ_LOCAL elseif ToTime == 4 then ToTime = core.TZ_SERVER elseif ToTime == 5 then ToTime = core.TZ_FINANCIAL elseif ToTime == 6 then ToTime = core.TZ_TS end PositionCap = instance.parameters.PositionCap ValidInterval = instance.parameters.ValidInterval UseMandatoryClosing = instance.parameters.UseMandatoryClosing LastEntry = nil LastExit = nil --Indicator parameters OpenLong = instance.parameters.OpenLong OpenShort = instance.parameters.OpenShort CloseLong = instance.parameters.CloseLong CloseShort = instance.parameters.CloseShort HedgeLong = instance.parameters.HedgeLong HedgeShort = instance.parameters.HedgeShort assert(TF ~= "t1", "The time frame must not be tick") PrepareTrading() assert(core.indicators:findIndicator("VWAP") ~= nil, "Please, download and install VWAP.LUA indicator") if EntryExecutionType == "Live" then TickSource = ExtSubscribe(1, nil, "t1", instance.parameters.Type == "Bid", "close") end Source = ExtSubscribe(2, nil, TF, instance.parameters.Type == "Bid", "bar") if Mode == "Daily" then Indicator = core.indicators:create("VWAP", Source, true,1,1,1, false,1,1,1, false,1,1,1) stream = Indicator.Daily elseif Mode == "Weekly" then Indicator = core.indicators:create("VWAP", Source, false,1,1,1, true,1,1,1, false,1,1,1) stream = Indicator.Weekly elseif Mode == "Monthly" then Indicator = core.indicators:create("VWAP", Source, false,1,1,1, false,1,1,1, true,1,1,1) stream = Indicator.Monthly end ema = core.indicators:create("EMA", Source, instance.parameters.ema_period); first = Indicator.DATA:first() ValidInterval = instance.parameters.ValidInterval UseMandatoryClosing = instance.parameters.UseMandatoryClosing for i = 1, 5 do local trade = {}; trade.open = instance.parameters:getBoolean("open_" .. i); trade.amount = instance.parameters:getInteger("amount_" .. i); trade.set_limit = instance.parameters:getBoolean("set_limit_" .. i); trade.limit = instance.parameters:getInteger("limit_" .. i); trade.set_stop = instance.parameters:getBoolean("set_stop_" .. i); trade.stop = instance.parameters:getInteger("stop_" .. i); trade.use_trailing = instance.parameters:getBoolean("use_trailing_" .. i); trade.trailing = instance.parameters:getInteger("trailing_" .. i); trade.use_breakeven = instance.parameters:getBoolean("use_breakeven_" .. i); trade.breakeven_when = instance.parameters:getInteger("breakeven_when_" .. i); trades[#trades + 1] = trade; end local valid OpenTime, valid = ParseTime(instance.parameters.StartTime) assert(valid, "Time " .. instance.parameters.StartTime .. " is invalid") CloseTime, valid = ParseTime(instance.parameters.StopTime) assert(valid, "Time " .. instance.parameters.StopTime .. " is invalid") ExitTime, valid = ParseTime(instance.parameters.ExitTime) assert(valid, "Time " .. instance.parameters.ExitTime .. " is invalid") if UseMandatoryClosing then core.host:execute("setTimer", 100, math.max(ValidInterval / 2, 1)) end end function ReleaseInstance() core.host:execute("killTimer", 100) for _, module in pairs(Modules) do if module.ReleaseInstance ~= nil then module:ReleaseInstance(); end end end function ParseTime(time) local Pos = string.find(time, ":") if Pos == nil then return nil, false end local h = tonumber(string.sub(time, 1, Pos - 1)) time = string.sub(time, Pos + 1) Pos = string.find(time, ":") if Pos == nil then return nil, false end local m = tonumber(string.sub(time, 1, Pos - 1)) local s = tonumber(string.sub(time, Pos + 1)) return (h / 24.0 + m / 1440.0 + s / 86400.0), ((h >= 0 and h < 24 and m >= 0 and m < 60 and s >= 0 and s < 60) or -- time in ole format (h == 24 and m == 0 and s == 0)) -- validity flag end function PrepareTrading() ALLOWEDSIDE = instance.parameters.ALLOWEDSIDE AllowTrade = instance.parameters.AllowTrade Account = instance.parameters.Account BaseSize = core.host:execute("getTradingProperty", "baseUnitSize", instance.bid:instrument(), Account) Offer = core.host:findTable("offers"):find("Instrument", instance.bid:instrument()).OfferID if AccountType == "FIFO" then CanClose = false elseif AccountType == "NON" then CanClose = true else CanClose = core.host:execute("getTradingProperty", "canCreateMarketClose", instance.bid:instrument(), Account) end end function InRange(now, openTime, closeTime) if openTime < closeTime then return now >= openTime and now <= closeTime; end if openTime > closeTime then return now > openTime or now < closeTime; end return now == openTime; end function ExtUpdate(id, source, period) -- The method called every time when a new bid or ask price appears. 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 if AllowTrade then if not (checkReady("trades")) or not (checkReady("orders")) then return end end if period < 0 then return end if EntryExecutionType == "Live" then if id ~= 1 then return end period = core.findDate(Source, TickSource:date(period), false) else if id ~= 2 then return end end now = core.host:execute("getServerTime") now = core.host:execute("convertTime", core.TZ_EST, ToTime, now) -- get only time now = now - math.floor(now) -- update indicators. Indicator:update(core.UpdateLast) ema:update(core.UpdateLast); if not ema.DATA:hasData(period) or period < first then return end if EntryExecutionType == "Live" and id == 1 or EntryExecutionType ~= "Live" and id ~= 1 then EntryFunction(now, period) end end function EntryFunction(now, period) if not InRange(now, OpenTime, CloseTime) then return ; end if (LastEntry == Source:serial(period)) then return false end -- only buy if we have a fast cross over slow and the price is above the moving averages. if ema.DATA[period] > stream[period] and ema.DATA[period - 1] <= stream[period - 1] then if Direction then BUY(period) else SELL(period) end LastEntry = Source:serial(period) return true elseif ema.DATA[period] < stream[period] and ema.DATA[period - 1] >= stream[period - 1] then if Direction then SELL(period) else BUY(period) end LastEntry = Source:serial(period) return true end return false end -- NG: Introduce async function for timer/monitoring for the order results 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 if cookie == 100 then -- timer if UseMandatoryClosing and AllowTrade then now = core.host:execute("getServerTime") now = core.host:execute("convertTime", core.TZ_EST, ToTime, now) -- get only time now = now - math.floor(now) -- check whether the time is in the exit time period if now >= ExitTime and now < ExitTime + (ValidInterval / 86400.0) then if not checkReady("trades") then return end if haveTrades("B") then exitSpecific("B") Signal("Close Long") end if haveTrades("S") then exitSpecific("S") Signal("Close Short") end end end elseif cookie == 200 and not success then terminal:alertMessage( instance.bid:instrument(), instance.bid[instance.bid:size() - 1], "Open order failed" .. message, instance.bid:date(instance.bid:size() - 1) ) elseif cookie == 201 and not success then terminal:alertMessage( instance.bid:instrument(), instance.bid[instance.bid:size() - 1], "Close order failed" .. message, instance.bid:date(instance.bid:size() - 1) ) end end --===========================================================================-- -- TRADING UTILITY FUNCTIONS -- --============================================================================-- function BUY(period) if AllowTrade then --if CanClose and CloseOnOpposite and haveTrades("S") then if (CloseOnOpposite or Hedge) and haveTrades("S") then -- close on opposite signal exitSpecific("S") Signal("Close Short") end if ALLOWEDSIDE == "Sell" then -- we are not allowed buys. return end enter("B", 0, period) else Signal("Buy Signal") end end function HEDGELONG() if ALLOWEDSIDE == "Buy" and haveTrades("B") then -- we are not allowed sells. return end if not haveTrades("B") then return end if AllowTrade then local bCount = tradesCount("B") if bCount > 0 then exitSpecific("B") Signal("Hedge Long") enter("S", bCount) end else Signal("Hedge Long") end end function HEDGESHORT() if ALLOWEDSIDE == "Sell" and haveTrades("S") then -- we are not allowed buys. return end if not haveTrades("S") then return end if AllowTrade then local sCount = tradesCount("S") if sCount > 0 then exitSpecific("S") Signal("Hedge Short") enter("B", sCount) end else Signal("Hedge Short") end end function SELL(period) if AllowTrade then --if CanClose and CloseOnOpposite and haveTrades("B") then if (CloseOnOpposite or Hedge) and haveTrades("B") then -- close on opposite signal exitSpecific("B") Signal("Close Long") end if ALLOWEDSIDE == "Buy" then -- we are not allowed sells. return end enter("S", 0, period) else Signal("Sell Signal") end end function Signal(Label) signaler:Signal(Label, Source); end function checkReady(table) local rc if Account == "TESTACC_ID" then -- run under debugger/simulator rc = true else rc = core.host:execute("isTableFilled", table) end return rc end function tradesCount(BuySell) local enum, row local count = 0 enum = core.host:findTable("trades"):enumerator() row = enum:next() while row ~= nil do if row.AccountID == Account and row.OfferID == Offer and row.QTXT == CustomID and (row.BS == BuySell or BuySell == nil) then count = count + 1 end row = enum:next() end return count end function haveTrades(BuySell) local enum, row local found = false enum = core.host:findTable("trades"):enumerator() row = enum:next() while (row ~= nil) do if row.AccountID == Account and row.OfferID == Offer and row.QTXT == CustomID and (row.BS == BuySell or BuySell == nil) then found = true break end row = enum:next() end return found end -- enter into the specified direction function enter(BuySell) -- do not enter if position in the specified direction already exists if (tradesCount(BuySell) >= MaxNumberOfPosition or (tradesCount(nil) >= MaxNumberOfPositionInAnyDirection)) and PositionCap then return true end -- send the alert after the checks to see if we can trade. if (BuySell == "S") then Signal("Sell Signal") else Signal("Buy Signal") end for i, trade in ipairs(trades) do MarketOrder(BuySell, trade); end end -- enter into the specified direction function MarketOrder(BuySell, trade) if not trade.open then return nil; end local command = trading:MarketOrder(instance.bid:instrument()) :SetAccountID(Account) :SetAmount(trade.amount) :SetSide(BuySell) :SetCustomID(CustomID); if trade.set_stop then command = command:SetPipStop(nil, trade.stop, trade.use_trailing and trade.trailing or nil); end if trade.set_limit then command = command:SetPipLimit(nil, trade.limit); end local res = command:Execute(); if trade.use_breakeven then breakeven:CreateController() :SetWhen(trade.breakeven_when) :SetTo(0) :SetRequestID(res.RequestID); end return command; end function exitSpecific(BuySell) if not AllowTrade then return end --side -- closes all positions of the specified direction (B for buy, S for sell) local enum, row, valuemap enum = core.host:findTable("trades"):enumerator() while true do row = enum:next() if row == nil then break end if row.AccountID == Account and row.OfferID == Offer and row.BS == BuySell and row.QTXT == CustomID then -- if trade has to be closed if CanClose then -- non-FIFO account, create a close market order valuemap = core.valuemap() valuemap.OrderType = "CM" valuemap.OfferID = Offer valuemap.AcctID = Account valuemap.Quantity = row.Lot valuemap.TradeID = row.TradeID valuemap.CustomID = CustomID if row.BS == "B" then valuemap.BuySell = "S" else valuemap.BuySell = "B" end success, msg = terminal:execute(201, valuemap) if not (success) then terminal:alertMessage( instance.bid:instrument(), instance.bid[instance.bid:size() - 1], "Close order failed" .. msg, instance.bid:date(instance.bid:size() - 1) ) return false end else -- FIFO account, create an opposite market order valuemap = core.valuemap() valuemap.OrderType = "OM" valuemap.OfferID = Offer valuemap.AcctID = Account --valuemap.Quantity = Amount*BaseSize; valuemap.Quantity = row.Lot valuemap.CustomID = CustomID if row.BS == "B" then valuemap.BuySell = "S" else valuemap.BuySell = "B" end success, msg = terminal:execute(201, valuemap) if not (success) then terminal:alertMessage( instance.bid:instrument(), instance.bid[instance.bid:size() - 1], "Close order failed" .. msg, instance.bid:date(instance.bid:size() - 1) ) return false end end end end end dofile(core.app_path() .. "\\strategies\\standard\\include\\helper.lua") signaler = {}; signaler.Name = "Signaler"; signaler.Debug = false; signaler.Version = "1.2.1"; signaler._show_alert = nil; signaler._sound_file = nil; signaler._recurrent_sound = nil; signaler._email = nil; signaler._ids_start = nil; signaler._telegram_timer = nil; signaler._tz = nil; signaler._alerts = {}; function signaler:trace(str) if not self.Debug then return; end core.host:trace(self.Name .. ": " .. str); end function signaler:OnNewModule(module) end function signaler: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 signaler:ToJSON(item) 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(item) 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 function signaler:ArrayToJSON(arr) local str = "["; for i, t in ipairs(self._alerts) do local json = self:ToJSON(t); if str == "[" then str = str .. json; else str = str .. "," .. json; end end return str .. "]"; end function signaler:AsyncOperationFinished(cookie, success, message, message1, message2) if cookie == self._telegram_timer and #self._alerts > 0 and (self.last_req == nil or not self.last_req:loading()) then if self._external_service_key == nil then return; end local data = self:ArrayToJSON(self._alerts); self._alerts = {}; self.last_req = http_lua.createRequest(); local query = string.format('{"Key":"%s","StrategyName":"%s","Platform":"FXTS2","Notifications":%s}', self._external_service_key, string.gsub(self.StrategyName or "", '"', '\\"'), data); self.last_req:setRequestHeader("Content-Type", "application/json"); self.last_req:setRequestHeader("Content-Length", tostring(string.len(query))); self.last_req:start("http://profitrobots.com/api/v1/notification", "POST", query); end end function signaler:FormatEmail(source, period, message) --format email subject local subject = message .. "(" .. source:instrument() .. ")"; --format email text local delim = "\013\010"; local signalDescr = "Signal: " .. (self.StrategyName or ""); local symbolDescr = "Symbol: " .. source:instrument(); local messageDescr = "Message: " .. message; local ttime = core.dateToTable(core.host:execute("convertTime", 1, 4, source:date(period))); local dateDescr = string.format("Time: %02i/%02i %02i:%02i", ttime.month, ttime.day, ttime.hour, ttime.min); local priceDescr = "Price: " .. source[period]; local text = "You have received this message because the following signal alert was received:" .. delim .. signalDescr .. delim .. symbolDescr .. delim .. messageDescr .. delim .. dateDescr .. delim .. priceDescr; return subject, text; end function signaler:Signal(label, source) if source == nil then source = instance.bid; if instance.bid == nil then local pane = core.host.Window.CurrentPane; source = pane.Data:getStream(0); else source = instance.bid; end end if self._show_alert then terminal:alertMessage(source:instrument(), source[NOW], label, source:date(NOW)); end if self._sound_file ~= nil then terminal:alertSound(self._sound_file, self._recurrent_sound); end if self._email ~= nil then terminal:alertEmail(self._email, profile:id().. " : " .. label, self:FormatEmail(source, NOW, label)); end if self._external_service_key ~= nil then self:AlertTelegram(label, source:instrument(), source:barSize()); end end function signaler:AlertTelegram(message, instrument, timeframe) if core.host.Trading:getTradingProperty("isSimulation") then return; end local alert = {}; alert.Text = message or ""; alert.Instrument = instrument or ""; alert.TimeFrame = timeframe or ""; self._alerts[#self._alerts + 1] = alert; end function signaler:Init(parameters) parameters:addBoolean("signaler_show_alert", "Show Alert", "", true); parameters:addBoolean("signaler_play_sound", "Play Sound", "", false); parameters:addFile("signaler_sound_file", "Sound File", "", ""); parameters:setFlag("signaler_sound_file", core.FLAG_SOUND); parameters:addBoolean("signaler_recurrent_sound", "Recurrent Sound", "", true); parameters:addBoolean("signaler_send_email", "Send Email", "", false); parameters:addString("signaler_email", "Email", "", ""); parameters:setFlag("signaler_email", core.FLAG_EMAIL); parameters:addBoolean("use_external_service", "Send to external service", "Telegram message or Channel post", false); parameters:addString("external_service_key", "External service Key", "You can get it via @profit_robots_bot Telegram bot", ""); end function signaler:Prepare(name_only) if instance.parameters.signaler_play_sound then self._sound_file = instance.parameters.signaler_sound_file; assert(self._sound_file ~= "", "Sound file must be chosen"); end self._show_alert = instance.parameters.signaler_show_alert; self._recurrent_sound = instance.parameters.signaler_recurrent_sound; if instance.parameters.signaler_send_email then self._email = instance.parameters.signaler_email; assert(self._email ~= "", "E-mail address must be specified"); end --do what you usually do in prepare if name_only then return; end if instance.parameters.external_service_key ~= "" and instance.parameters.use_external_service then self._external_service_key = instance.parameters.external_service_key; require("http_lua"); self._telegram_timer = self._ids_start + 1; core.host:execute("setTimer", self._telegram_timer, 1); end end signaler:RegisterModule(Modules); breakeven = {}; -- public fields breakeven.Name = "Breakeven"; breakeven.Version = "1.5.0"; breakeven.Debug = false; breakeven.Default_breakeven_when = 10; breakeven.Default_use_breakeven = false; breakeven.Default_breaeven_to = 1; --private fields breakeven._breakeven_when = 10; breakeven._use_breakeven = false; breakeven._breaeven_to = 1; breakeven._moved_stops = {}; breakeven._request_id = nil; breakeven._used_stop_orders = {}; breakeven._ids_start = nil; breakeven._source_id = nil; breakeven._trading = nil; breakeven._controllers = {}; function breakeven:trace(str) if not self.Debug then return; end core.host:trace(self.Name .. ": " .. str); end function breakeven:OnNewModule(module) if module.Name == "Trading" then self._trading = module; end end function breakeven:RegisterModule(modules) for _, module in pairs(modules) do self:OnNewModule(module); module:OnNewModule(self); end modules[#modules + 1] = self; self._ids_start = (#modules) * 100; end function breakeven:Init(parameters) parameters:addBoolean("use_breakeven", "Use Breakeven", "", self.Default_use_breakeven); parameters:addDouble("breakeven_when", "Breakeven Activation Value, in pips", "", self.Default_breakeven_when); parameters:addDouble("breakeven_to", "Breakeven To, in pips", "", self.Default_breaeven_to); end function breakeven:Prepare(nameOnly) self._breakeven_when = instance.parameters.breakeven_when; self._use_breakeven = instance.parameters.use_breakeven; self._breaeven_to = instance.parameters.breakeven_to; if self._use_breakeven == nil or self._use_breakeven == true then self._source_id = self._ids_start + 1; ExtSubscribe(self._source_id, nil, "t1", true, "tick"); end self:trace(string.format("Use breakeven: %s. Profit for trigger breakeven: %s. Breakeven target: %s", tostring(self._use_breakeven), tostring(self._breakeven_when), tostring(self._breaeven_to))); end function breakeven:ExtUpdate(id, source, period) if id ~= self._source_id then return; end for _, controller in ipairs(self._controllers) do controller:DoBreakeven(); end end function breakeven:CreateController() local controller = {}; controller._parent = self; controller._executed = false; controller._trailing = 0; function controller:SetWhen(when) self._when = when; return self; end function controller:SetTrade(trade) self._trade = trade; return self; end function controller:SetDynamicTo(dynamicTo) self._dynamicTo = dynamicTo; return self; end function controller:SetTo(to) self._to = to; return self; end function controller:SetTrailing(trailing) self._trailing = trailing return self; end function controller:SetRequestID(trade_request_id) self._request_id = trade_request_id; return self; end function controller:GetTrade() if self._trade == nil then self._trade = core.host:findTable("trades"):find("OpenOrderReqID", self._request_id); if self._trade == nil then return nil; end end return self._trade; end function controller:getTo() local trade = self:GetTrade(); if self._dynamicTo ~= nil then return self._dynamicTo(trade); end local offer = core.host:findTable("offers"):find("Instrument", trade.Instrument); if trade.BS == "B" then return offer.Bid - (trade.PL - self._to) * offer.PointSize; else return offer.Ask + (trade.PL - self._to) * offer.PointSize; end end function controller:DoBreakeven() if self._executed then return false; end local trade = self:GetTrade(); if trade == nil then return true; end if not trade:refresh() then self._executed = true; return false; end if trade.PL >= self._when then self._parent._trading:MoveStop(trade, self:getTo(), self._trailing); self._executed = true; return false; end return true; end self._controllers[#self._controllers + 1] = controller; return controller; end function breakeven:CreateTrailingOnProfitController() local controller = {}; controller._parent = self; controller._executed = false; controller._trailing = 0; function controller:SetTrade(trade) self._trade = trade; return self; end function controller:SetProfitPercentage(profit_pr, min_profit) self._profit_pr = profit_pr; self._min_profit = min_profit; return self; end function controller:SetRequestID(trade_request_id) self._request_id = trade_request_id; return self; end function controller:GetTrade() if self._trade == nil then self._trade = core.host:findTable("trades"):find("OpenOrderReqID", self._request_id); if self._trade == nil then return nil; end end return self._trade; end function controller:GetClosedTrade() if self._closed_trade == nil then self._closed_trade = core.host:findTable("closed trades"):find("OpenOrderReqID", self._request_id); if self._closed_trade == nil then return nil; end end if not self._closed_trade:refresh() then return nil; end return self._closed_trade; end function controller:getStopPips(trade) local stop = trading:FindStopOrder(trade); if stop == nil then return nil; end local offer = core.host:findTable("offers"):find("Instrument", trade.Instrument); if trade.BS == "B" then return (stop.Rate - trade.Open) / offer.PointSize; else return (trade.Open - stop.Rate) / offer.PointSize; end end function controller:DoBreakeven() if self._executed then return false; end if self._move_command ~= nil and not self._move_command.Finished then return true; end local trade = self:GetTrade(); if trade == nil then if self:GetClosedTrade() ~= nil then self._executed = true; end return true; end if not trade:refresh() then self._executed = true; return false; end if trade.PL < self._min_profit then return true; end local new_stop = trade.PL * (self._profit_pr / 100); local current_stop = self:getStopPips(trade); if current_stop == nil or current_stop < new_stop then local offer = core.host:findTable("offers"):find("Instrument", trade.Instrument); if trade.BS == "B" then self._move_command = self._parent._trading:MoveStop(trade, trade.Open + new_stop * offer.PointSize); else self._move_command = self._parent._trading:MoveStop(trade, trade.Open - new_stop * offer.PointSize); end return true; end return true; end self._controllers[#self._controllers + 1] = controller; return controller; end breakeven:RegisterModule(Modules); trading = {}; trading.Name = "Trading"; trading.Version = "4.4.0"; trading.Debug = false; trading.AddAmountParameter = true; trading.AddStopParameter = true; trading.AddLimitParameter = true; 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 parameters:addBoolean("close_on_opposite", "Close on Opposite", "", true); parameters:addBoolean("position_cap", "Position Cap", "", false); parameters:addInteger("no_of_positions", "No of open positions", "", 1); 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.AllowTrade; 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 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(trade, stop_rate, trailing) local valuemap = core.valuemap(); valuemap.Command = "CreateOrder"; valuemap.OfferID = trade.OfferID; valuemap.Rate = stop_rate; if trade.BS == "B" then valuemap.BuySell = "S"; else valuemap.BuySell = "B"; end local can_close = core.host:execute("getTradingProperty", "canCreateMarketClose", trade.Instrument, trade.AccountID); if can_close then valuemap.OrderType = "S"; valuemap.AcctID = trade.AccountID; valuemap.TradeID = trade.TradeID; valuemap.Quantity = trade.Lot; valuemap.TrailUpdatePips = trailing; else valuemap.OrderType = "SE" valuemap.AcctID = trade.AccountID; valuemap.NetQtyFlag = "Y" end local id = self:getId(); local success, msg = terminal:execute(id, valuemap); if not(success) then local message = "Failed create stop " .. msg; self:trace(message); if self._signaler ~= nil then self._signaler:Signal(message); end local res = {}; res.Finished = true; res.Success = false; res.Error = message; return res; end local res = {}; res.Finished = false; res.RequestID = msg; self._waiting_requests[id] = res; self._request_id[trade.TradeID] = msg; return res; end function trading:CreateLimitOrder(trade, limit_rate) local valuemap = core.valuemap(); valuemap.Command = "CreateOrder"; valuemap.OfferID = trade.OfferID; valuemap.Rate = limit_rate; if trade.BS == "B" then valuemap.BuySell = "S"; else valuemap.BuySell = "B"; end local can_close = core.host:execute("getTradingProperty", "canCreateMarketClose", trade.Instrument, trade.AccountID); if can_close then valuemap.OrderType = "L"; valuemap.AcctID = trade.AccountID; valuemap.TradeID = trade.TradeID; valuemap.Quantity = trade.Lot; else valuemap.OrderType = "LE" valuemap.AcctID = trade.AccountID; valuemap.NetQtyFlag = "Y" end local success, msg = terminal:execute(200, valuemap); if not(success) then terminal:alertMessage(trade.Instrument, limit_rate, "Failed create limit " .. msg, core.now()); else self._request_id[trade.TradeID] = msg; end end function trading:ChangeOrder(order, rate, trailing) 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 = trailing; valuemap.Rate = rate; local id = self:getId(); local success, msg = terminal:execute(id, valuemap); if not(success) then local message = "Failed change order " .. msg; self:trace(message); if self._signaler ~= nil then self._signaler:Signal(message); end local res = {}; res.Finished = true; res.Success = false; res.Error = message; return res; end local res = {}; res.Finished = false; res.RequestID = msg; self._waiting_requests[id] = res; return res; end local res = {}; res.Finished = true; res.Success = true; return res; end function trading: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(trade, stop_rate, trailing) local order = self:FindStopOrder(trade); if order == nil then if trailing == 0 then trailing = nil; end return self:CreateStopOrder(trade, stop_rate, trailing); else if trailing == 0 then if order.TrlMinMove ~= 0 then trailing = order.TrlMinMove else trailing = nil; end end return self:ChangeOrder(order, stop_rate, trailing); end end function trading:MoveLimit(trade, limit_rate) self:trace("Searching for a limit"); local order = self:FindLimitOrder(trade); if order == nil then self:trace("Limit order not found, creating a new one"); return self:CreateLimitOrder(trade, limit_rate); else return self:ChangeOrder(order, limit_rate); end end function trading:RemoveStop(trade) self:trace("Searching for a stop"); local order = self:FindStopOrder(trade); if order == nil then self:trace("No stop"); return nil; end self:trace("Deleting order"); return self:DeleteOrder(order); end function trading:RemoveLimit(trade) self:trace("Searching for a limit"); local order = self:FindLimitOrder(trade); if order == nil then self:trace("No limit"); return nil; end self:trace("Deleting order"); return self:DeleteOrder(order); end function trading:DeleteOrder(order) self:trace(string.format("Deleting order %s", order.OrderID)); local valuemap = core.valuemap(); valuemap.Command = "DeleteOrder"; valuemap.OrderID = order.OrderID; local id = self:getId(); local success, msg = terminal:execute(id, valuemap); if not(success) then local message = "Delete order failed: " .. msg; self:trace(message); if self._signaler ~= nil then self._signaler:Signal(message); end local res = {}; res.Finished = true; res.Success = false; res.Error = message; return res; end local res = {}; res.Finished = false; res.RequestID = msg; self._waiting_requests[id] = res; return res; end function trading:FindOrder() local search = {}; function search:WhenCustomID(custom_id) self.CustomID = custom_id; return self; end function search:WhenSide(bs) self.Side = bs; return self; end function search:WhenInstrument(instrument) self.Instrument = instrument; return self; end function search:WhenAccountID(account_id) self.AccountID = account_id; return self; end function search:WhenRate(rate) self.Rate = rate; return self; end function search:WhenOrderType(orderType) self.OrderType = orderType; return self; end function search:Do(action) local enum = core.host:findTable("orders"):enumerator(); local row = enum:next(); while (row ~= nil) do if self:PassFilter(row) then action(row); end row = enum:next(); end 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:Do(action) local enum = core.host:findTable("trades"):enumerator(); local row = enum:next(); while (row ~= nil) do if self:PassFilter(row) then action(row); end row = enum:next(); end 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:Any() local enum = core.host:findTable("trades"):enumerator(); local row = enum:next(); while (row ~= nil) do if self:PassFilter(row) then return true; end row = enum:next(); end return false; end function search: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); local id = self:getId(); if account.Hedging == "Y" then local valuemap = core.valuemap(); valuemap.BuySell = trade.BS == "B" and "S" or "B"; valuemap.OrderType = "CM"; valuemap.OfferID = trade.OfferID; valuemap.AcctID = trade.AccountID; valuemap.TradeID = trade.TradeID; valuemap.Quantity = math.min(amount, trade.Lot); local success, msg = terminal:execute(id, valuemap); if success then local res = trading:ClosePartialSuccessResult(msg); self._waiting_requests[id] = res; return res; end return trading:ClosePartialFailResult(msg); end local valuemap = core.valuemap(); valuemap.OrderType = "OM"; valuemap.OfferID = trade.OfferID; valuemap.AcctID = trade.AccountID; valuemap.Quantity = math.min(amount, trade.Lot); valuemap.BuySell = trading:getOppositeSide(trade.BS); local success, msg = terminal:execute(id, valuemap); if success then local res = trading:ClosePartialSuccessResult(msg); self._waiting_requests[id] = res; return res; end return trading:ClosePartialFailResult(msg); end function trading:ClosePartialSuccessResult(msg) local res = {}; if msg ~= nil then res.Finished = false; else res.Finished = true; end res.RequestID = msg; function res:ToJSON() return trading:ObjectToJson(self); end return res; end function trading:ClosePartialFailResult(message) local res = {}; res.Finished = true; res.Success = false; res.Error = message; return res; end function trading:Close(trade) local valuemap = core.valuemap(); valuemap.BuySell = trade.BS == "B" and "S" or "B"; valuemap.OrderType = "CM"; valuemap.OfferID = trade.OfferID; valuemap.AcctID = trade.AccountID; valuemap.TradeID = trade.TradeID; valuemap.Quantity = trade.Lot; local success, msg = terminal:execute(self._ids_start + 3, valuemap); if not(success) then if self._signaler ~= nil then self._signaler:Signal("Close failed: " .. msg); end return false; end return true; end function trading:ObjectToJson(obj) local json = {}; function json:AddStr(name, value) local separator = ""; if self.str ~= nil then separator = ","; else self.str = ""; end self.str = self.str .. string.format("%s\"%s\":\"%s\"", separator, tostring(name), tostring(value)); end function json:AddNumber(name, value) local separator = ""; if self.str ~= nil then separator = ","; else self.str = ""; end self.str = self.str .. string.format("%s\"%s\":%f", separator, tostring(name), value or 0); end function json:AddBool(name, value) local separator = ""; if self.str ~= nil then separator = ","; else self.str = ""; end self.str = self.str .. string.format("%s\"%s\":%s", separator, tostring(name), value and "true" or "false"); end function json:ToString() return "{" .. (self.str or "") .. "}"; end local first = true; for idx,t in pairs(obj) do local stype = type(t) if stype == "number" then json:AddNumber(idx, t); elseif stype == "string" then json:AddStr(idx, t); elseif stype == "boolean" then json:AddBool(idx, t); elseif stype == "function" or stype == "table" then --do nothing else core.host:trace(tostring(idx) .. " " .. tostring(stype)); end end return json:ToString(); end function trading:CreateEntryOrderSuccessResult(msg) local res = {}; if msg ~= nil then res.Finished = false; else res.Finished = true; end res.RequestID = msg; function res:IsOrderExecuted() return self.FixStatus ~= nil and self.FixStatus == "F"; end function res:GetOrder() if self._order == nil 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() return trading:ObjectToJson(self); end return res; end function trading:CreateEntryOrderFailResult(message) local res = {}; res.Finished = true; res.Success = false; res.Error = message; function res:GetOrder() return nil; end function res: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:AddMetadata(id, val) if self._metadata == nil then self._metadata = {}; end self._metadata[id] = val; return self; 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._metadata ~= nil then self._metadata.CustomID = self.valuemap.CustomID; self.valuemap.CustomID = trading:ObjectToJson(self._metadata); end 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:AddMetadata(id, val) if self._metadata == nil then self._metadata = {}; end self._metadata[id] = val; return self; end function builder:Execute() self.Parent:trace(string.format("Creating %s OM for %s", self.valuemap.BuySell, self.Instrument)); if self._metadata ~= nil then self._metadata.CustomID = self.valuemap.CustomID; self.valuemap.CustomID = trading:ObjectToJson(self._metadata); 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: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:JsonToObject(json) local position = 1; local result; while (position < json:len() + 1) do local ch = string.sub(json, position, position); if ch == "{" then result = {}; position = position + 1; elseif ch == "}" then return result; elseif ch == "," then position = position + 1; else if result == nil then return nil; end local name, value = string.match(json, '"([^"]+)":("?[^,}]+"?)', position); if value == "false" then result[name] = false; position = position + name:len() + 8; elseif value == "true" then result[name] = true; position = position + name:len() + 7; else if string.sub(value, 1, 1) == "\"" then result[name] = value; value:sub(2, value:len() - 1); position = position + name:len() + 3 + value:len(); else result[name] = tonumber(value); position = position + name:len() + 3 + value:len(); end end end end return nil; end function trading:GetTradeMetadata(trade) local qtxt = trade.QTXT; if qtxt == "" then return nil; end local position = 1; local result; while (position < qtxt:len() + 1) do local ch = string.sub(qtxt, position, position); if ch == "{" then result = {}; position = position + 1; elseif ch == "}" then return result; elseif ch == "," then position = position + 1; else if result == nil then return nil; end local name, value = string.match(qtxt, '"([^"]+)":("?[^,}]+"?)', position); if value == "false" then result[name] = false; position = position + name:len() + 8; elseif value == "true" then result[name] = true; position = position + name:len() + 7; else if string.sub(value, 1, 1) == "\"" then result[name] = value; value:sub(2, value:len() - 1); position = position + name:len() + 3 + value:len(); else result[name] = tonumber(value); position = position + name:len() + 3 + value:len(); end end end end return nil; end trading:RegisterModule(Modules);