----------------------------------------------------------- -- VENDETTA UP ----------------------------------------------------------- local _gSubscription = {}; local _gUpdatePeriods = {}; local _gLastTime; ----------------------------------------------------------- -- Standard strategy init handler for marketscope strategy ----------------------------------------------------------- function Init() strategy:name("VENDETTA UP"); strategy:type(core.Both); strategy.parameters:addString("Account", "Account", "", ""); strategy.parameters:setFlag("Account", core.FLAG_ACCOUNT); strategy.parameters:addBoolean("AllowTrade", "Allow trade", "", false); strategy.parameters:setFlag("AllowTrade", core.FLAG_ALLOW_TRADE); 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:addBoolean("AllowMultiplePositions", "Allow multiple positions", "", true); strategy.parameters:addString("tf", "tf", "The used timeframe", "m1"); strategy.parameters:setFlag("tf", core.FLAG_PERIODS); strategy.parameters:addString("tf2", "tf2", "The used timeframe", "m1"); strategy.parameters:setFlag("tf2", core.FLAG_PERIODS); strategy.parameters:addInteger("lot", "lot", "", 0); strategy.parameters:addInteger("STOP", resources:get("R_TR_S"), resources:get("R_TR_S_D"), 0, 0, 300); strategy.parameters:addBoolean("USE_TRAILING_STOP", resources:get("R_USE_TRAILING_STOP"), "", true); strategy.parameters:addString("TRAILING_STOP_TYPE", resources:get("R_TRAILING_STOP_TYPE"), resources:get("R_TRAILING_STOP_TYPE_D"), "Dynamic"); strategy.parameters:addStringAlternative("TRAILING_STOP_TYPE", resources:get("R_DYNAMIC"), "", "Dynamic"); strategy.parameters:addStringAlternative("TRAILING_STOP_TYPE", resources:get("R_FIXED"), "", "Fixed"); strategy.parameters:addInteger("FIXED_TRAILING_STOP", resources:get("R_FIXED_TRAILING_STOP"), resources:get("R_FIXED_TRAILING_STOP_D"), 10, 10, 300); strategy.parameters:addDouble("limit", "limit", "", 0); strategy.parameters:addGroup("DMI parameters") strategy.parameters:addInteger("DMI_N", "Number of periods", "The number of periods.", 14, 1, 1000); strategy.parameters:addGroup("SAR parameters") strategy.parameters:addDouble("SAR_Step", "Step", "The sensitivity of SAR.", 0.02, 0.001, 1); strategy.parameters:addDouble("SAR_Max", "Max", "The maximum value of Step.", 0.2, 0.001, 10); strategy.parameters:addGroup("MACD parameters") strategy.parameters:addInteger("MACD_SN", "Fast MA periods", "The number of periods to calculate the fast moving average.", 12, 2, 1000); strategy.parameters:addInteger("MACD_LN", "Slow MA periods", "The number of periods to calculate the slow moving average.", 26, 2, 1000); strategy.parameters:addInteger("MACD_IN", "Signal line", "The number of periods for the signal line.", 9, 2, 1000); strategy.parameters:addGroup("ICH parameters") strategy.parameters:addInteger("ICH_X", "Tenkan-sen period", "The signal line period.", 9, 1, 10000); strategy.parameters:addInteger("ICH_Y", "Kijun-sen period", "The trend line period.", 26, 1, 10000); strategy.parameters:addInteger("ICH_Z", "Senkou Span B period", "The leading span 2 period.", 52, 1, 10000); strategy.parameters:addInteger("ICH_transp", "Cloud transparency, %", "The transparency of the SA-SB cloud. Must be in the range 0-100.", 80, 0, 100); end local mCID= "FXSW_STRATEGY"; local mAccount; local mLotSize; local mAllowTrade = false; local mAllowedSide = "Both"; local mPlaySound = false; local mReccurentSound = false; local mSendEmail = false; local mShowAlert = false; local mEmail; local mAllowMultiplePositions = true; local tf; local tf2; local symbol; local source; local id_source = 101; local source2; local id_source2 = 102; local lot; local stop; local USE_TRAILING_STOP; local TRAILING_STOP_SIZE; local limit; local DMI_N; local DMI; local SAR_Step; local SAR_Max; local SAR; local MACD_SN; local MACD_LN; local MACD_IN; local MACD; local ICH_X; local ICH_Y; local ICH_Z; local ICH_transp; local ICH; local expr = false; local expr3 = false; local expr2 = false; ----------------------------------------------------------- -- Standard prepare handler for marketscope strategy ----------------------------------------------------------- function Prepare(onlyName) assert(instance.parameters.tf ~= "t1", "Tick is not allowed for tf parameter"); assert(instance.parameters.tf2 ~= "t1", "Tick is not allowed for tf2 parameter"); -- collect parameters mAccount = instance.parameters.Account; mLotSize = core.host:execute("getTradingProperty", "baseUnitSize", instance.bid:instrument(), mAccount); mAllowTrade = instance.parameters.AllowTrade; mAllowedSide = instance.parameters.AllowedSide; mAllowMultiplePositions = instance.parameters.AllowMultiplePositions; tf = instance.parameters.tf; tf2 = instance.parameters.tf2; symbol = instance.bid:instrument(); lot = instance.parameters.lot; stop = instance.parameters.stop; limit = instance.parameters.limit; DMI_N = instance.parameters.DMI_N; SAR_Step = instance.parameters.SAR_Step; SAR_Max = instance.parameters.SAR_Max; MACD_SN = instance.parameters.MACD_SN; MACD_LN = instance.parameters.MACD_LN; MACD_IN = instance.parameters.MACD_IN; ICH_X = instance.parameters.ICH_X; ICH_Y = instance.parameters.ICH_Y; ICH_Z = instance.parameters.ICH_Z; ICH_transp = instance.parameters.ICH_transp; --set name instance:name(profile:id() .. "(" .. instance.bid:instrument() .. "(" .. tf .. "))"); if onlyName then return; end --datasources source = ExtSubscribe(id_source, symbol, tf, true, "bar"); source2 = ExtSubscribe(id_source2, symbol, tf2, true, "bar"); --indicators DMI = core.indicators:create("DMI", source, DMI_N, 65280, 1, 1, 255, 1, 1); _gUpdatePeriods[DMI.DATA] = _gUpdatePeriods[source]; SAR = core.indicators:create("SAR", source, SAR_Step, SAR_Max, 255, 1, 65280, 1); _gUpdatePeriods[SAR.DATA] = _gUpdatePeriods[source]; MACD = core.indicators:create("MACD", source.close, MACD_SN, MACD_LN, MACD_IN, 255, 1, 1, 16711680, 1, 1, 65280); _gUpdatePeriods[MACD.DATA] = _gUpdatePeriods[source.close]; ICH = core.indicators:create("ICH", source2, ICH_X, ICH_Y, ICH_Z, 65535, 1, 1, 16776960, 1, 1, 65280, 1, 1, 255, 1, 1, 16711680, 1, 1, ICH_transp); _gUpdatePeriods[ICH.DATA] = _gUpdatePeriods[source2]; end ----------------------------------------------------------- -- 'Event handler' that is called when a datasource is updated ----------------------------------------------------------- function ExtUpdate(id, updatedSource, period) if not checkReady("trades") or not checkReady("summary") then return; end -- update indicators values DMI:update(core.UpdateLast); SAR:update(core.UpdateLast); MACD:update(core.UpdateLast); ICH:update(core.UpdateLast); -- update expressions if id == id_source then --Updates handler of 'source' datasource ('tf' timeframe) --Check that all data of used datasources is available if canCalculate( DMI.DIP , DMI.DIP:size() - 1 - 2) and canCalculate( DMI.DIM , DMI.DIM:size() - 1 - 2) and canCalculate( DMI.DIP , DMI.DIP:size() - 1 - 1) and canCalculate( DMI.DIM , DMI.DIM:size() - 1 - 1) and canCalculate( source.close , getClosedPeriod(source.close, source.close:size() - 1) ) and canCalculate( SAR.DN , SAR.DN:size() - 1 ) and canCalculate( MACD.MACD , MACD.MACD:size() - 1 ) then expr = DMI.DIP[getClosedPeriod(DMI.DIP, DMI.DIP:size() - 1 - 2)] < DMI.DIM[getClosedPeriod(DMI.DIM, DMI.DIM:size() - 1 - 2)] and DMI.DIP[getClosedPeriod(DMI.DIP, DMI.DIP:size() - 1 - 1)] > DMI.DIM[getClosedPeriod(DMI.DIM, DMI.DIM:size() - 1 - 1)] and source.close[getClosedPeriod(source.close, source.close:size() - 1) ] > SAR.DN[getClosedPeriod(SAR.DN, SAR.DN:size() - 1) ] and MACD.MACD[getClosedPeriod(MACD.MACD, MACD.MACD:size() - 1) ] < 0; end --Check that all data of used datasources is available if canCalculate( DMI.DIP , DMI.DIP:size() - 1 - 2) and canCalculate( DMI.DIM , DMI.DIM:size() - 1 - 2) and canCalculate( DMI.DIP , DMI.DIP:size() - 1 - 1) and canCalculate( DMI.DIM , DMI.DIM:size() - 1 - 1) and canCalculate( source.close , getClosedPeriod(source.close, source.close:size() - 1) ) and canCalculate( SAR.UP , SAR.UP:size() - 1 ) then expr3 = DMI.DIP[getClosedPeriod(DMI.DIP, DMI.DIP:size() - 1 - 2)] > DMI.DIM[getClosedPeriod(DMI.DIM, DMI.DIM:size() - 1 - 2)] and DMI.DIP[getClosedPeriod(DMI.DIP, DMI.DIP:size() - 1 - 1)] < DMI.DIM[getClosedPeriod(DMI.DIM, DMI.DIM:size() - 1 - 1)] and source.close[getClosedPeriod(source.close, source.close:size() - 1) ] < SAR.UP[getClosedPeriod(SAR.UP, SAR.UP:size() - 1) ]; end end if id == id_source2 then --Updates handler of 'source2' datasource ('tf2' timeframe) --Check that all data of used datasources is available if canCalculate( ICH.TL , ICH.TL:size() - 1 ) and canCalculate( ICH.SB , ICH.SB:size() - 1 - 26) then expr2 = ICH.TL[getClosedPeriod(ICH.TL, ICH.TL:size() - 1) ] > ICH.SB[getClosedPeriod(ICH.SB, ICH.SB:size() - 1 - 26)]; end end -- processing of Activation points if id==id_source then --Updates handler of 'source' datasource ('tf' timeframe) --'point' activation point logic if expr and expr2 then if mAllowTrade then createTrueMarketOrder("B", 1, symbol, stop, false, limit); end end --'point2' activation point logic if expr3 then if mAllowTrade then close("B", core.host:findTable("offers"):find("Instrument", symbol).OfferID); end end end end function checkReady(tableName) return core.host:execute("isTableFilled", tableName); end ----------------------------------------------------------- --Enters to the market -- side: B - BUY or S - SELL -- amount: order amount -- instrumentName: instrument of order -- stop: 0, 1 or greater value -- isTrailingStop: true/false -- limit: 0, 1 or greater value ----------------------------------------------------------- function createTrueMarketOrder(side, amount, instrumentName, stop, isTrailingStop, limit) if not mAllowTrade then return; end local offerId = core.host:findTable("offers"):find("Instrument", instrumentName).OfferID; if not (mAllowedSide == "Both" or (mAllowedSide == "Buy" and side == "B") or (mAllowedSide == "Sell" and side == "S")) then return; end if not mAllowMultiplePositions then if (side == 'B' and countLongPositions(instrumentName) > 0) then return; elseif (side == 'S' and countShortPositions(instrumentName) > 0) then return; end end local valuemap; valuemap = core.valuemap(); valuemap.Command = "CreateOrder"; valuemap.OrderType = "OM"; valuemap.OfferID = offerId; valuemap.AcctID = mAccount; valuemap.GTC = "FOK"; --Fill or Kill order to avoid partial execution valuemap.Quantity = amount * mLotSize; valuemap.BuySell = side; valuemap.CustomID = mCID; if stop >= 1 then valuemap.PegTypeStop = "M"; if side == "B" then valuemap.PegPriceOffsetPipsStop = - stop; else valuemap.PegPriceOffsetPipsStop = stop; end if isTrailingStop then valuemap.TrailStepStop = 1; end end if limit >= 1 then valuemap.PegTypeLimit = "M"; if side == "B" then valuemap.PegPriceOffsetPipsLimit = limit; else valuemap.PegPriceOffsetPipsLimit = -limit; end end if (not canClose(instrumentName)) and (stop >= 1 or limit >= 1) then valuemap.EntryLimitStop = 'Y' end success, msg = terminal:execute(200, valuemap); assert(success, msg); end ----------------------------------------------------------- -- closes all positions of the specified direction (B for buy, S for sell) ----------------------------------------------------------- function canClose(instrumentName) return core.host:execute("getTradingProperty", "canCreateMarketClose", instrumentName, mAccount); end function close(side, offer) 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 == mAccount and row.OfferID == offer and row.BS == side and row.QTXT == mCID then -- if trade has to be closed if canClose(row.Instrument) then -- create a close market order when hedging is allowed valuemap = core.valuemap(); valuemap.OrderType = "CM"; valuemap.OfferID = offer; valuemap.AcctID = mAccount; valuemap.Quantity = row.Lot; valuemap.TradeID = row.TradeID; valuemap.CustomID = mCID; if row.BS == "B" then valuemap.BuySell = "S"; else valuemap.BuySell = "B"; end success, msg = terminal:execute(200, valuemap); assert(success, msg); else -- create an opposite market order when FIFO valuemap = core.valuemap(); valuemap.OrderType = "OM"; valuemap.OfferID = offer; valuemap.AcctID = mAccount; valuemap.Quantity = row.Lot; valuemap.CustomID = mCID; if row.BS == "B" then valuemap.BuySell = "S"; else valuemap.BuySell = "B"; end success, msg = terminal:execute(200, valuemap); assert(success, msg); end end end end ----------------------------------------------------------- --Handle command execution result ----------------------------------------------------------- function ExtAsyncOperationFinished(cookie, success, message) if cookie == 1 then loaded = true; elseif cookie == 200 then assert(success, message); end end ----------------------------------------------------------- -- Helper functions ----------------------------------------------------------- function getOppositeSide(side) if(side == "B") then return "S"; else return "B"; end end function getDSPeriod(ds, updatedSource, period) local p; if ds:isBar() then p = core.findDate(ds.open, updatedSource:date(period), false); else p = core.findDate(ds, updatedSource:date(period), false); end if (p > ds:size() - 1) then p = ds:size() - 1; elseif (p < ds:first()) then p = ds:first(); end return p; end ----------------------------------------------------------- -- Allow to calculate last closed bar period for ds datasource -- which has not tick frequency updates ----------------------------------------------------------- function getClosedPeriod(ds, supposedPeriod) --Check if datasource lastdate is closed on updatePeriod or shift supposedPeriod to -1 if _gUpdatePeriods[ds] == 't1' or _gUpdatePeriods[ds] == nil then return supposedPeriod; else return supposedPeriod - 1; end end ----------------------------------------------------------- --Helper functions to wrap the table's method call into the simple function call ----------------------------------------------------------- function streamSize(stream) return stream:size(); end function streamHasData(stream, period) return stream:hasData(period); end function canCalculate(stream, period) return (period >= 0) and (period > stream:first()) and streamHasData(stream, period); end ----------------------------------------------------------- -- Helper functions to be sure that you work with a tick stream ----------------------------------------------------------- function getTickStreamOfPriceType(stream, priceType) if stream:isBar() then if priceType == "open" then return stream.open; elseif priceType == "high" then return stream.high; elseif priceType == "low" then return stream.low; elseif priceType == "close" then return stream.close; elseif priceType == "typical" then return stream.typical; elseif priceType == "weighted" then return stream.weighted; elseif priceType == "volume" then return stream.volume; else return stream.close; end else return stream; end end function selectStream(safeStream, subStream) if safeStream:isBar() then return subStream; else return safeStream; end end --------------------------------------------------------- -- Subscription for updates by datasource timeframe --------------------------------------------------------- -- subscribe for the price data function ExtSubscribe(id, instrument, period, bid, type) local sub = {}; if instrument == nil and period == "t1" then if bid then sub.stream = instance.bid; else sub.stream = instance.ask; end sub.tick = true; sub.loaded = true; sub.lastSerial = -1; _gSubscription[id] = sub; elseif instrument == nil then sub.stream = core.host:execute("getHistory", id, instance.bid:instrument(), period, 0, 0, bid); sub.tick = false; sub.loaded = false; sub.lastSerial = -1; _gSubscription[id] = sub; else sub.stream = core.host:execute("getHistory", id, instrument, period, 0, 0, bid); sub.tick = (period == "t1"); sub.loaded = false; sub.lastSerial = -1; _gSubscription[id] = sub; end _gUpdatePeriods[sub.stream] = period; if sub.tick then return sub.stream; else if type == "open" then _gUpdatePeriods[sub.stream.open] = period; return sub.stream.open; elseif type == "high" then _gUpdatePeriods[sub.stream.high] = period; return sub.stream.high; elseif type == "low" then _gUpdatePeriods[sub.stream.low] = period; return sub.stream.low; elseif type == "close" then _gUpdatePeriods[sub.stream.close] = period; return sub.stream.close; elseif type == "bar" then _gUpdatePeriods[sub.stream.open] = period; _gUpdatePeriods[sub.stream.high] = period; _gUpdatePeriods[sub.stream.low] = period; _gUpdatePeriods[sub.stream.close] = period; _gUpdatePeriods[sub.stream.median] = period; _gUpdatePeriods[sub.stream.typical] = period; _gUpdatePeriods[sub.stream.volume] = period; _gUpdatePeriods[sub.stream.weighted] = period; return sub.stream; else assert(false, type .. " is unknown"); end end end function AsyncOperationFinished(cookie, success, message) local sub; sub = _gSubscription[cookie]; if sub ~= nil then sub.loaded = true; if sub.stream:size() > 1 then sub.lastSerial = sub.stream:serial(sub.stream:size() - 1); end else -- unknown cookie if ExtAsyncOperationFinished ~= nil then ExtAsyncOperationFinished(cookie, success, message) end end end function Update() if instance.bid:size() > 0 then _gLastTime = instance.bid:date(instance.bid:size() - 1); end for k, v in pairs(_gSubscription) do if v.loaded and v.stream:size() > 1 then local s = v.stream:serial(v.stream:size() - 1); local p; if s ~= v.lastSerial then if v.tick then p = v.stream:size() - 1; -- the last tick else p = v.stream:size() - 2; -- the previous candle end ExtUpdate(k, v.stream, p); v.lastSerial = s; end end end end --------------------------------------------------------- -- Additional functions --------------------------------------------------------- local mAccountRow = nil; local mSummaries = {}; function checkAccountRow() if mAccountRow == nil then mAccountRow = core.host:findTable("accounts"):find("AccountID", mAccount); else mAccountRow:refresh(); end end function checkSummaryRow(sInstrument) local sOfferID, summaryIter, summaryRow; if mSummaries[sInstrument] ~= nil then --try refresh if not (mSummaries[sInstrument]:refresh()) then mSummaries[sInstrument] = nil; end end --re-read all cache if mSummaries[sInstrument] == nil then sOfferID = core.host:findTable("offers"):find("Instrument", sInstrument).OfferID; summaryIter = core.host:findTable("summary"):enumerator(); summaryRow = summaryIter:next(); while summaryRow ~= nil do if summaryRow.OfferID == sOfferID then mSummaries[sInstrument] = summaryRow; break; end summaryRow = summaryIter:next(); end end end function getEquity() checkAccountRow(); return mAccountRow.Equity; end function getBalance() checkAccountRow(); return mAccountRow.Balance; end function getProfit() checkAccountRow(); return mAccountRow.Equity - mAccountRow.Balance; end function getAmountK(sInstrument) local res; checkSummaryRow(sInstrument); res = mSummaries[sInstrument]; if res == nil then return 0; else return res.AmountK; end end function getGrossPL(sInstrument) local res; checkSummaryRow(sInstrument); res = mSummaries[sInstrument]; if res == nil then return 0; else return res.GrossPL; end end function getNetPL(sInstrument) local res; checkSummaryRow(sInstrument); res = mSummaries[sInstrument]; if res == nil then return 0; else return res.NetPL; end end function getSellAmountK(sInstrument) local res; checkSummaryRow(sInstrument); res = mSummaries[sInstrument]; if res == nil then return 0; else return res.SellAmountK; end end function getBuyAmountK(sInstrument) local res; checkSummaryRow(sInstrument); res = mSummaries[sInstrument]; if res == nil then return 0; else return res.BuyAmountK; end end function getBuyNetPLPip(sInstrument) local res; checkSummaryRow(sInstrument); res = mSummaries[sInstrument]; if res == nil then return 0; else return res.BuyNetPLPip; end end function getSellNetPLPip(sInstrument) local res; checkSummaryRow(sInstrument); res = mSummaries[sInstrument]; if res == nil then return 0; else return res.SellNetPLPip; end end function getBuyNetPL(sInstrument) local res; checkSummaryRow(sInstrument); res = mSummaries[sInstrument]; if res == nil then return 0; else return res.BuyNetPL; end end function getSellNetPL(sInstrument) local res; checkSummaryRow(sInstrument); res = mSummaries[sInstrument]; if res == nil then return 0; else return res.SellNetPL; end end function countPositions(sInstrument) local tradesIter, tradeRow, count, sOfferID; count = 0; if sInstrument ~= nil then sOfferID = core.host:findTable("offers"):find("Instrument", sInstrument).OfferID; end tradesIter = core.host:findTable("trades"):enumerator(); tradeRow = tradesIter:next(); while tradeRow ~= nil do if (sInstrument == nil or tradeRow.OfferID == sOfferID) then count = count + 1; end tradeRow = tradesIter:next(); end return count; end function countLongPositions(sInstrument) local tradesIter, tradeRow, count, sOfferID; count = 0; if sInstrument ~= nil then sOfferID = core.host:findTable("offers"):find("Instrument", sInstrument).OfferID; end tradesIter = core.host:findTable("trades"):enumerator(); tradeRow = tradesIter:next(); while tradeRow ~= nil do if ((sInstrument==nil or tradeRow.OfferID == sOfferID) and tradeRow.BS == "B") then count = count + 1; end tradeRow = tradesIter:next(); end return count; end function countShortPositions(sInstrument) local tradesIter, tradeRow, count, sOfferID; count = 0; if sInstrument ~= nil then sOfferID = core.host:findTable("offers"):find("Instrument", sInstrument).OfferID; end tradesIter = core.host:findTable("trades"):enumerator(); tradeRow = tradesIter:next(); while tradeRow ~= nil do if ((sInstrument == nil or tradeRow.OfferID == sOfferID) and tradeRow.BS == "S") then count = count + 1; end tradeRow = tradesIter:next(); end return count; end function getLastUpdateTime() if (_gLastTime == nil) then return 0; else return _gLastTime; end end function time(hours, minutes, seconds) local dtLast; dtLast = core.dateToTable(_gLastTime); if seconds == nil then seconds = 0; end return core.datetime(dtLast.year, dtLast.month, dtLast.day, hours, minutes, seconds); end function isValidDate(checkDate) if (checkDate < 1) then return false; else return true; end end function parseTime(sTime) local iDelimHMPos = string.find(sTime, ":"); local h = tonumber(string.sub(sTime, 1, iDelimHMPos - 1)); local sTimeTile = string.sub(sTime, iDelimHMPos + 1); local iDelimMSPos = string.find(sTimeTile, ":"); local m, s; s = 0; if iDelimMSPos == nil then m = tonumber(sTimeTile); else m = tonumber(string.sub(sTimeTile, 1, iDelimMSPos - 1)); s = tonumber(string.sub(sTimeTile, iDelimMSPos + 1)); end return time(h, m, s); end function getPipSize(sInstrument) return core.host:findTable("offers"):find("Instrument", sInstrument).PointSize; end