· 9 years ago · Jan 17, 2017, 11:48 PM
1--+------------------------------------------------------------------+
2--| Price MA Strategy with Stochastic Filters.lua |
3--| Copyright © 2015, Gehtsoft USA LLC |
4--| http://fxcodebase.com |
5--| Support our efforts by donating |
6--| Paypal: http://goo.gl/cEP5h5 |
7--+------------------------------------------------------------------+
8--| Developed by : Mario Jemic |
9--| mario.jemic@gmail.com |
10--| BitCoin : 1MfUHS3h86MBTeonJzWdszdzF2iuKESCKU |
11--+------------------------------------------------------------------+
12
13function Init() --The strategy profile initialization
14 strategy:name("Price MA Strategy with Stochastic Filters");
15 strategy:description("");
16
17 strategy.parameters:addGroup("Price");
18 strategy.parameters:addString("Type", "Price Type", "", "Bid");
19 strategy.parameters:addStringAlternative("Type", "Bid", "", "Bid");
20 strategy.parameters:addStringAlternative("Type", "Ask", "", "Ask");
21
22 strategy.parameters:addString("TF", "Time frame", "", "H1");
23 strategy.parameters:setFlag("TF", core.FLAG_PERIODS);
24
25 strategy.parameters:addGroup("Strategy Parameters");
26 strategy.parameters:addInteger("K", "%K Period", "", 5, 2, 1000);
27 strategy.parameters:addInteger("D", "%D Period", "", 3, 1, 1000);
28 strategy.parameters:addInteger("SD", "%D slowing periods", "", 3, 1, 1000);
29
30 strategy.parameters:addString("A1", "Smoothing method for %K", "", "MVA");
31 strategy.parameters:addStringAlternative("A1", "MVA", "", "MVA");
32 strategy.parameters:addStringAlternative("A1", "EMA", "", "EMA");
33 strategy.parameters:addStringAlternative("A1", "MetaTrader", "", "MT");
34
35 strategy.parameters:addString("A2", "Smoothing method for %D", "", "MVA");
36 strategy.parameters:addStringAlternative("A2", "MVA", "", "MVA");
37 strategy.parameters:addStringAlternative("A2", "EMA", "", "EMA");
38
39 strategy.parameters:addString("L", "Signal Type", "", "K");
40 strategy.parameters:addStringAlternative("L", "%K Overbought/ Oversold", "", "K");
41 strategy.parameters:addStringAlternative("L", "%K / %D Position", "", "C");
42
43 strategy.parameters:addInteger("OS", "Oversold level", "", 20, 1, 100);
44 strategy.parameters:addInteger("OB", "Overbought level", "", 80, 1, 100);
45
46
47
48 strategy.parameters:addString("MAMethod", "MA Method", "Method" , "EMA");
49 strategy.parameters:addStringAlternative("MAMethod", "MVA", "MVA" , "MVA");
50 strategy.parameters:addStringAlternative("MAMethod", "EMA", "EMA" , "EMA");
51 strategy.parameters:addStringAlternative("MAMethod", "LWMA", "LWMA" , "LWMA");
52 strategy.parameters:addStringAlternative("MAMethod", "TMA", "TMA" , "TMA");
53 strategy.parameters:addStringAlternative("MAMethod", "SMMA", "SMMA" , "SMMA");
54 strategy.parameters:addStringAlternative("MAMethod", "KAMA", "KAMA" , "KAMA");
55 strategy.parameters:addStringAlternative("MAMethod", "VIDYA", "VIDYA" , "VIDYA");
56 strategy.parameters:addStringAlternative("MAMethod", "WMA", "WMA" , "WMA");
57
58 strategy.parameters:addInteger("MAPeriod", "MAPeriod", "MAPeriod" ,50, 2, 2000);
59
60
61 strategy.parameters:addGroup("ADX Filter Selector");
62 strategy.parameters:addBoolean("ADX", "USE ADX Filter", "", false);
63
64 strategy.parameters:addInteger("ADXPeriod", "ADX Period", "", 14, 2, 2000);
65 strategy.parameters:addDouble("ADXLevel", "ADX Level", "", 25, 0, 100);
66
67 CreateTradingParameters();
68end
69
70function CreateTradingParameters()
71 strategy.parameters:addGroup("Trading Parameters");
72
73 strategy.parameters:addBoolean("AllowTrade", "Allow strategy to trade", "", false);
74 strategy.parameters:setFlag("AllowTrade", core.FLAG_ALLOW_TRADE);
75
76
77 strategy.parameters:addString("ExecutionType", "End of Turn / Live", "", "End of Turn");
78 strategy.parameters:addStringAlternative("ExecutionType", "End of Turn", "", "End of Turn");
79 strategy.parameters:addStringAlternative("ExecutionType", "Live", "", "Live");
80
81 strategy.parameters:addBoolean("CloseOnOpposite", "Close On Opposite", "", true);
82 strategy.parameters:addString("CustomID", "Custom Identifier", "The identifier that can be used to distinguish strategy instances", "PMSSF");
83
84 strategy.parameters:addInteger("MaxNumberOfPositionInAnyDirection", "Max Number Of Open Position In Any Direction", "", 2, 1, 100);
85 strategy.parameters:addInteger("MaxNumberOfPosition", "Max Number Of Position In One Direction", "", 1, 1, 100);
86
87 strategy.parameters:addString("ALLOWEDSIDE", "Allowed side", "Allowed side for trading or signaling, can be Sell, Buy or Both", "Both");
88 strategy.parameters:addStringAlternative("ALLOWEDSIDE", "Both", "", "Both");
89 strategy.parameters:addStringAlternative("ALLOWEDSIDE", "Buy", "", "Buy");
90 strategy.parameters:addStringAlternative("ALLOWEDSIDE", "Sell", "", "Sell");
91
92 strategy.parameters:addString("Account", "Account to trade on", "", "");
93 strategy.parameters:setFlag("Account", core.FLAG_ACCOUNT);
94 strategy.parameters:addInteger("Amount", "Trade Amount in Lots", "", 1, 1, 100);
95 strategy.parameters:addBoolean("SetLimit", "Set Limit Orders", "", false);
96 strategy.parameters:addInteger("Limit", "Limit Order in pips", "", 30, 1, 10000);
97 strategy.parameters:addBoolean("SetStop", "Set Stop Orders", "", false);
98 strategy.parameters:addInteger("Stop", "Stop Order in pips", "", 30, 1, 10000);
99 strategy.parameters:addBoolean("TrailingStop", "Trailing stop order", "", false);
100
101 strategy.parameters:addGroup("Alerts");
102 strategy.parameters:addBoolean("ShowAlert", "ShowAlert", "", true);
103 strategy.parameters:addBoolean("PlaySound", "Play Sound", "", false);
104 strategy.parameters:addFile("SoundFile", "Sound File", "", "");
105 strategy.parameters:setFlag("SoundFile", core.FLAG_SOUND);
106 strategy.parameters:addBoolean("RecurrentSound", "Recurrent Sound", "", true);
107 strategy.parameters:addBoolean("SendEmail", "Send Email", "", false);
108 strategy.parameters:addString("Email", "Email", "", "");
109 strategy.parameters:setFlag("Email", core.FLAG_EMAIL);
110
111 strategy.parameters:addGroup("Time Parameters");
112 strategy.parameters:addString("StartTime", "Start Time for Trading", "", "00:00:00");
113 strategy.parameters:addString("StopTime", "Stop Time for Trading", "", "24:00:00");
114
115 strategy.parameters:addBoolean("UseMandatoryClosing", "Use Mandatory Closing", "", false);
116 strategy.parameters:addString("ExitTime", "Mandatory Closing Time", "", "23:59:00");
117 strategy.parameters:addInteger("ValidInterval", "Valid interval for operation in second", "", 60);
118
119
120end
121local OpenTime, CloseTime, ExitTime,ValidInterval;
122local Source,TickSource;
123local MaxNumberOfPositionInAnyDirection, MaxNumberOfPosition;
124local SoundFile = nil;
125local RecurrentSound = false;
126local ALLOWEDSIDE;
127local AllowTrade;
128local Offer;
129local CanClose;
130local Account;
131local Amount;
132local SetLimit;
133local Limit;
134local SetStop;
135local Stop;
136local TrailingStop;
137local ShowAlert;
138local Email;
139local SendEmail;
140local BaseSize;
141local ExecutionType;
142local CloseOnOpposite
143local first;
144local ADX ;
145local indicator = nil;
146
147local Line1, Line2;
148local OS, OB;
149local PARAMETARS;
150local ma, adx,MAPeriod, MAMethod;
151local ADXLevel, ADXPariod;
152
153local CustomID;
154
155-- Don't need to store hour + minute + second for each time
156local OpenTime, CloseTime, ExitTime;
157--
158function Prepare( nameOnly)
159 CustomID = instance.parameters.CustomID;
160 ExecutionType = instance.parameters.ExecutionType;
161 CloseOnOpposite = instance.parameters.CloseOnOpposite;
162 MaxNumberOfPositionInAnyDirection = instance.parameters.MaxNumberOfPositionInAnyDirection;
163 MaxNumberOfPosition = instance.parameters.MaxNumberOfPosition;
164 MAMethod = instance.parameters.MAMethod;
165 MAPeriod = instance.parameters.MAPeriod;
166
167 ADXLevel = instance.parameters.ADXLevel;
168 ADXPeriod = instance.parameters.ADXPeriod;
169
170 ADX= instance.parameters.ADX;
171
172 ValidInterval = instance.parameters.ValidInterval;
173 UseMandatoryClosing = instance.parameters.UseMandatoryClosing;
174
175 OS = instance.parameters.OS;
176 OB = instance.parameters.OB;
177
178
179 local PARAMETARS = instance.parameters.K .. "," .. instance.parameters.D .. "," .. instance.parameters.SD .. "," .. instance.parameters.A1 .. "," .. instance.parameters.A2 .. "," .. OS .. "," .. OB;
180
181 assert(instance.parameters.TF ~= "t1", "The time frame must not be tick");
182
183 local name;
184 name = profile:id() .. "( " .. instance.bid:name() .. "," .. PARAMETARS .. "," .. CustomID .. " )";
185 instance:name(name);
186
187 PrepareTrading();
188
189 if nameOnly then
190 return ;
191 end
192
193 if ExecutionType== "Live" then
194 TickSource = ExtSubscribe(1, nil, "t1", instance.parameters.Type == "Bid", "close");
195 end
196
197 Source = ExtSubscribe(2, nil, instance.parameters.TF, instance.parameters.Type == "Bid", "bar");
198
199
200 local iprofile = core.indicators:findIndicator("STOCHASTIC");
201 local iparams = iprofile:parameters();
202 iparams:setInteger("K", instance.parameters:getInteger("K"));
203 iparams:setInteger("SD", instance.parameters:getInteger("SD"));
204 iparams:setInteger("D", instance.parameters:getInteger("D"));
205 iparams:setString("MVAT_K", instance.parameters:getString("A1"));
206 iparams:setString("MVAT_D", instance.parameters:getString("A2"));
207 indicator = iprofile:createInstance(Source, iparams);
208
209 Line1 = indicator:getStream(0); -- %K
210 Line2 = indicator:getStream(1); -- %D
211
212 ma = core.indicators:create(MAMethod, Source.close, MAPeriod);
213 adx = core.indicators:create("ADX", Source , ADXPeriod);
214
215 first=math.max( Line2:first(), ma.DATA:first(), adx.DATA:first())+1;
216
217 local valid;
218 OpenTime, valid = ParseTime(instance.parameters.StartTime);
219 assert(valid, "Time " .. instance.parameters.StartTime .. " is invalid");
220 CloseTime, valid = ParseTime(instance.parameters.StopTime);
221 assert(valid, "Time " .. instance.parameters.StopTime .. " is invalid");
222 ExitTime, valid = ParseTime(instance.parameters.ExitTime);
223 assert(valid, "Time " .. instance.parameters.ExitTime .. " is invalid");
224
225 if UseMandatoryClosing then
226 core.host:execute("setTimer", 100, math.max(ValidInterval / 2, 1));
227 end
228
229
230end
231
232
233-- NG: create a function to parse time
234function ParseTime(time)
235 local Pos = string.find(time, ":");
236 local h = tonumber(string.sub(time, 1, Pos - 1));
237 time = string.sub(time, Pos + 1);
238 Pos = string.find(time, ":");
239 local m = tonumber(string.sub(time, 1, Pos - 1));
240 local s = tonumber(string.sub(time, Pos + 1));
241 return (h / 24.0 + m / 1440.0 + s / 86400.0), -- time in ole format
242 ((h >= 0 and h < 24 and m >= 0 and m < 60 and s >= 0 and s < 60) or (h == 24 and m == 0 and s == 0)); -- validity flag
243end
244
245function PrepareTrading()
246 ALLOWEDSIDE = instance.parameters.ALLOWEDSIDE;
247
248 local PlaySound = instance.parameters.PlaySound;
249 if PlaySound then
250 SoundFile = instance.parameters.SoundFile;
251 else
252 SoundFile = nil;
253 end
254 assert(not(PlaySound) or (PlaySound and SoundFile ~= ""), "Sound file must be chosen");
255
256 ShowAlert = instance.parameters.ShowAlert;
257 RecurrentSound = instance.parameters.RecurrentSound;
258
259 SendEmail = instance.parameters.SendEmail;
260
261 if SendEmail then
262 Email = instance.parameters.Email;
263 else
264 Email = nil;
265 end
266 assert(not(SendEmail) or (SendEmail and Email ~= ""), "E-mail address must be specified");
267
268 AllowTrade = instance.parameters.AllowTrade;
269 Account = instance.parameters.Account;
270 Amount = instance.parameters.Amount;
271 BaseSize = core.host:execute("getTradingProperty", "baseUnitSize", instance.bid:instrument(), Account);
272 Offer = core.host:findTable("offers"):find("Instrument", instance.bid:instrument()).OfferID;
273 CanClose = core.host:execute("getTradingProperty", "canCreateMarketClose", instance.bid:instrument(), Account);
274 SetLimit = instance.parameters.SetLimit;
275 Limit = instance.parameters.Limit;
276 SetStop = instance.parameters.SetStop;
277 Stop = instance.parameters.Stop;
278 TrailingStop = instance.parameters.TrailingStop;
279end
280
281local Last;
282local LAST;
283local ONE;
284
285
286function ExtUpdate(id, source, period) -- The method called every time when a new bid or ask price appears.
287 if AllowTrade then
288 if not(checkReady("trades")) or not(checkReady("orders")) then
289 return ;
290 end
291 end
292
293
294 if ExecutionType == "Live" and id == 1 then
295
296 period= core.findDate (Source.close, TickSource:date(period), false );
297
298 end
299
300 if ExecutionType == "Live" then
301
302 if ONE == Source:serial(period) then
303 return;
304 end
305
306 if id == 2 then
307 return;
308 end
309
310
311
312 else
313 if id ~= 2 then
314 return;
315 end
316 end
317
318
319
320 -- update indicators.
321 indicator:update(core.UpdateLast);
322 ma:update(core.UpdateLast);
323 adx:update(core.UpdateLast);
324
325 if period < first
326 or ( ADX and adx.DATA[period] < ADXLevel )
327 then
328 return;
329 end
330
331 if instance.parameters.L =="K" then
332 if core.crossesOver(Source.close, ma.DATA, period)
333 and ( Line1 [period]> OS )
334 then
335 BUY();
336 ONE= Source:serial(period);
337 elseif core.crossesUnder(Source.close, ma.DATA, period)
338 and ( Line1 [period]< OB )
339 then
340 SELL();
341 ONE= Source:serial(period);
342 end
343 end
344
345
346
347 if (instance.parameters.L =="C") then
348
349 if core.crossesOver( Source.close, ma.DATA, period)
350 and Line1[period] > Line2[period]
351 then
352 BUY();
353 ONE= Source:serial(period);
354 end
355 if core.crossesUnder( Source.close, ma.DATA, period)
356 and Line1[period] < Line2[period]
357 then
358 SELL();
359 ONE= Source:serial(period);
360 end
361
362 end
363
364
365
366
367
368end
369
370-- NG: Introduce async function for timer/monitoring for the order results
371function ExtAsyncOperationFinished(cookie, success, message)
372
373 if cookie == 100 then
374 -- timer
375 if UseMandatoryClosing and AllowTrade then
376 now = core.host:execute("getServerTime");
377 -- get only time
378 now = now - math.floor(now);
379 -- check whether the time is in the exit time period
380 if now >= ExitTime and now < ExitTime + ValidInterval then
381 if not(checkReady("trades")) or not(checkReady("orders")) then
382 return ;
383 end
384 if haveTrades("S") then
385 exitSpecific("S");
386 Signal ("Close Short");
387 end
388 if haveTrades("B") then
389 exitSpecific("B");
390 Signal ("Close Long");
391 end
392 end
393 end
394 elseif cookie == 200 and not success then
395 terminal:alertMessage(instance.bid:instrument(), instance.bid[instance.bid:size() - 1], "Open order failed" .. message, instance.bid:date(instance.bid:size() - 1));
396 elseif cookie == 201 and not success then
397 terminal:alertMessage(instance.bid:instrument(), instance.bid[instance.bid:size() - 1], "Close order failed" .. message, instance.bid:date(instance.bid:size() - 1));
398 end
399end
400
401--===========================================================================--
402-- TRADING UTILITY FUNCTIONS --
403--============================================================================--
404function BUY()
405 if AllowTrade then
406 if CloseOnOpposite and haveTrades("S") then
407 -- close on opposite signal
408 exitSpecific("S");
409 Signal ("Close Short");
410 end
411
412 if ALLOWEDSIDE == "Sell" then
413 -- we are not allowed buys.
414 return;
415 end
416
417 enter("B");
418 else
419 Signal ("Buy Signal");
420 end
421end
422
423function SELL ()
424 if AllowTrade then
425 if CloseOnOpposite and haveTrades("B") then
426 -- close on opposite signal
427 exitSpecific("B");
428 Signal ("Close Long");
429 end
430
431 if ALLOWEDSIDE == "Buy" then
432 -- we are not allowed sells.
433 return;
434 end
435
436 enter("S");
437 else
438 Signal ("Sell Signal");
439 end
440end
441
442function Signal (Label)
443 if ShowAlert then
444 terminal:alertMessage(instance.bid:instrument(), instance.bid[NOW], Label, instance.bid:date(NOW));
445 end
446
447 if SoundFile ~= nil then
448 terminal:alertSound(SoundFile, RecurrentSound);
449 end
450
451 if Email ~= nil then
452 terminal:alertEmail(Email, Label, profile:id() .. "(" .. instance.bid:instrument() .. ")" .. instance.bid[NOW]..", " .. Label..", " .. instance.bid:date(NOW));
453 end
454end
455
456function checkReady(table)
457 local rc;
458 if Account == "TESTACC_ID" then
459 -- run under debugger/simulator
460 rc = true;
461 else
462 rc = core.host:execute("isTableFilled", table);
463 end
464
465 return rc;
466end
467
468function tradesCount(BuySell)
469 local enum, row;
470 local count = 0;
471 enum = core.host:findTable("trades"):enumerator();
472 row = enum:next();
473 while row ~= nil do
474 if row.AccountID == Account and row.OfferID == Offer and row.QTXT == CustomID and (row.BS == BuySell or BuySell == nil) then
475 count = count + 1;
476 end
477
478 row = enum:next();
479 end
480
481 return count;
482end
483
484function haveTrades(BuySell)
485 local enum, row;
486 local found = false;
487 enum = core.host:findTable("trades"):enumerator();
488 row = enum:next();
489 while (row ~= nil) do
490 if row.AccountID == Account and row.OfferID == Offer and row.QTXT == CustomID and (row.BS == BuySell or BuySell == nil) then
491 found = true;
492 break;
493 end
494
495 row = enum:next();
496 end
497
498 return found;
499end
500
501-- enter into the specified direction
502function enter(BuySell)
503 -- do not enter if position in the specified direction already exists
504 if tradesCount(BuySell) >= MaxNumberOfPosition
505 or ((tradesCount(nil)) >= MaxNumberOfPositionInAnyDirection)
506 then
507 return true;
508 end
509
510 -- send the alert after the checks to see if we can trade.
511 if (BuySell == "S") then
512 Signal ("Sell Signal");
513 else
514 Signal ("Buy Signal");
515 end
516
517 return MarketOrder(BuySell);
518end
519
520
521-- enter into the specified direction
522function MarketOrder(BuySell)
523 local valuemap, success, msg;
524 valuemap = core.valuemap();
525
526 valuemap.Command = "CreateOrder";
527 valuemap.OrderType = "OM";
528 valuemap.OfferID = Offer;
529 valuemap.AcctID = Account;
530 valuemap.Quantity = Amount * BaseSize;
531 valuemap.BuySell = BuySell;
532 valuemap.CustomID = CustomID;
533
534 -- add stop/limit
535 valuemap.PegTypeStop = "O";
536 if SetStop then
537 if BuySell == "B" then
538 valuemap.PegPriceOffsetPipsStop = -Stop;
539 else
540 valuemap.PegPriceOffsetPipsStop = Stop;
541 end
542 end
543 if TrailingStop then
544 valuemap.TrailStepStop = 1;
545 end
546
547 valuemap.PegTypeLimit = "O";
548 if SetLimit then
549 if BuySell == "B" then
550 valuemap.PegPriceOffsetPipsLimit = Limit;
551 else
552 valuemap.PegPriceOffsetPipsLimit = -Limit;
553 end
554 end
555
556 if (not CanClose) then
557 valuemap.EntryLimitStop = 'Y'
558 end
559
560 success, msg = terminal:execute(200, valuemap);
561
562 if not(success) then
563 terminal:alertMessage(instance.bid:instrument(), instance.bid[instance.bid:size() - 1], "Open order failed" .. msg, instance.bid:date(instance.bid:size() - 1));
564 return false;
565 end
566
567 return true;
568end
569
570-- exit from the specified trade using the direction as a key
571function exitSpecific(BuySell)
572 -- we have to loop through to exit all trades in each direction instead
573 -- of using the net qty flag because we may be running multiple strategies on the same account.
574 local enum, row;
575 local found = false;
576 enum = core.host:findTable("trades"):enumerator();
577 row = enum:next();
578 while (not found) and (row ~= nil) do
579 -- for every trade for this instance.
580 if row.AccountID == Account and row.OfferID == Offer and row.QTXT == CustomID and (row.BS == BuySell or BuySell == nil) then
581 exitTrade(row);
582 end
583
584 row = enum:next();
585 end
586end
587
588-- exit from the specified direction
589function exitTrade(tradeRow)
590 if not(AllowTrade) then
591 return true;
592 end
593
594 local valuemap, success, msg;
595 valuemap = core.valuemap();
596
597 -- switch the direction since the order must be in oppsite direction
598 if tradeRow.BS == "B" then
599 BuySell = "S";
600 else
601 BuySell = "B";
602 end
603 valuemap.OrderType = "CM";
604 valuemap.OfferID = Offer;
605 valuemap.AcctID = Account;
606 if (CanClose) then
607 -- Non-FIFO can close each trade independantly.
608 valuemap.TradeID = tradeRow.TradeID;
609 valuemap.Quantity = tradeRow.Lot;
610 else
611 -- FIFO.
612 valuemap.NetQtyFlag = "Y"; -- this forces all trades to close in the opposite direction.
613 end
614 valuemap.BuySell = BuySell;
615 valuemap.CustomID = CustomID;
616 success, msg = terminal:execute(201, valuemap);
617
618 if not(success) then
619 terminal:alertMessage(instance.bid:instrument(), instance.bid[instance.bid:size() - 1], "Close order failed" .. msg, instance.bid:date(instance.bid:size() - 1));
620 return false;
621 end
622
623 return true;
624end
625
626dofile(core.app_path() .. "\\strategies\\standard\\include\\helper.lua");