Блог им. kurd
using System; using System.Collections.Generic; using System.Text; using System.Drawing; using System.Windows.Forms; // DialogResult using WealthLab; using WealthLab.Indicators; //using mw = MyWealth; //using mwu = MyWealth.Utils; namespace WealthLab.Strategies { public struct Arg { public static double MinMoney = 30; // Минимальный выигрыш в рублях public static double ProfitFactor = 1;// Доля MinMoney для выхода из позиции public static int PeriodMins = 10; // Период SMA, минуты public static TimeSpan TimeIni = new TimeSpan (10, 10, 0); // Начало игры public static TimeSpan TimeBan = new TimeSpan (18, 00, 0); // Запрет входа public static TimeSpan TimeFin = new TimeSpan (18, 40, 0); // Конец игры } public struct Data { public static double SecPriceStep;// Шаг цены в пунктах фьючерса public static double DealFee = 4; // Комиссия за сделку в рублях public static double MinProfit; // Минимальный выигрыш в пунктах public static int PeriodBars; // Период SMA, бары } public class CalendarSpreadTwo : WealthScript { DataSeries SpreadLong, SpreadShrt, UprLong, LwrShrt; DataSeries SpreadMid, UprMid, LwrMid; protected override void Execute() { ClearDebug(); if (! Bars.IsIntraday || ! Bars.Scale.Equals (BarScale.Minute)) { PrintDebug ("Bar.Scale Minute only!"); return; } double stepPrice // Рублёвая цена для Data.SecPriceStep ,moneyMargin // Среднее ГО контракта, руб ,moneyFct; // Единица контракта, руб Prepare (out stepPrice, out moneyMargin, out moneyFct); //*** Pos pos = new Pos(); double win = 0; double ttlWin = 0; double minWin = Double.MaxValue; double maxWin = Double.MinValue; int bar = Data.PeriodBars; TimeSpan curTime = new TimeSpan (0); for ( ; ; ++bar) { curTime = new TimeSpan(Date[bar].Hour, Date[bar].Minute, 0); if (curTime < Arg.TimeIni) goto Next; //if (pos.Type == 1 && SpreadShrt[bar] >= UprShrt[bar] || // pos.Type == -1 && SpreadLong[bar] <= LwrLong[bar]) if (ExitPos ( bar, false, pos, ref win, ref minWin, ref maxWin, ref ttlWin)) goto Next; if (curTime >= Arg.TimeBan || pos.Type != 0) goto Next; if (SpreadShrt[bar] < LwrShrt[bar]) { // Покупаем //if (SpreadMid[bar] < LwrMid[bar]) { // Покупаем EnterPos (1, bar, pos); } else if (SpreadLong[bar] > UprLong[bar]) { // Продаём //} else if (SpreadMid[bar] > UprMid[bar]) { // Продаём EnterPos (-1, bar, pos); } Next: if (curTime >= Arg.TimeFin) break; } // for ( ; ; ++ bar ExitPos (bar, true, pos, ref win, ref minWin, ref maxWin, ref ttlWin); //*** if (pos.PosNo > 0) { double margin = 2 * moneyMargin / moneyFct; double fee = Data.DealFee / moneyFct * pos.PosNo; double pct = (ttlWin - fee) / margin * 100; PrintDebug (String.Format("{0,-7};{1,-6};{2,-5};{3,-7};{4,-6};{5,-5}" ,"min", "max", "avr", "ttl", "fee", "pct")); PrintDebug (String.Format ( "{0,7:F2};{1,6:F2};{2,5:F2};{3,7:F2};{4,6:F2};{5,5:F2}" ,minWin, maxWin, ttlWin / pos.PosNo, ttlWin, fee, pct)); } } // Execute() bool EnterPos (int type, int bar, Pos pos) { pos.Type = type; pos.EntryBar = bar; pos.EntryPrice = SpreadPrice (type, bar); pos.PosNo = pos.PosNo + 1; return true; } // EnterPos() double SpreadPrice (int buySell, int bar) { if (buySell == 1) { return SpreadLong[bar]; //double ask1 = High[bar]; double bid2 = Low[bar]; //return ask1 - bid2; // Для покупки спред дорог } else { return SpreadShrt[bar]; //double bid1 = Open[bar]; double ask2 = Close[bar]; //return bid1 - ask2; // Для продажи спред дёшев } } bool ExitPos (int bar, bool atOnce, Pos pos, ref double win ,ref double minWin, ref double maxWin, ref double ttlWin) { if (pos.Type == 0) return false; int newType = pos.Type == 1 ? -1 : 1; double exitPrice = SpreadPrice (newType, bar); bool mustExit = pos.Type == 1 ? exitPrice >= pos.EntryPrice + Data.MinProfit * Arg.ProfitFactor : exitPrice + Data.MinProfit * Arg.ProfitFactor <= pos.EntryPrice; if (! mustExit && ! atOnce) return false; pos.ExitBar = bar; pos.ExitPrice = exitPrice; win = 0; if (pos.Type == 1) // Продаём лонг win = pos.ExitPrice - pos.EntryPrice; else // Откупаем шорт win = pos.EntryPrice - pos.ExitPrice; minWin = Math.Min (minWin, win); maxWin = Math.Max (maxWin, win); ttlWin += win; if (pos.PosNo == 1) PrintDebug ( String.Format ("{0,-4};{1,1}:{2,-6};{3,-6};{4,-7};{5,-7};{6,-7}" ,"nn","*", "nBar", "xBar", "nPrc", "xPrc", "win")); PrintDebug (String.Format ( "{0,4};{1,1};{2,6};{3,6};{4,7:F2};{5,7:F2};{6,7:F2}" ,pos.PosNo, pos.Type == -1 ? "-" : "+", pos.EntryBar, pos.ExitBar ,pos.EntryPrice, pos.ExitPrice, win)); pos.Type = 0; return true; } // ExitPos() void Prepare (out double stepPrice, out double moneyMargin ,out double moneyFct) { PrintDebug (StrategyName + " " + Bars.Symbol); Data.SecPriceStep = stepPrice = moneyMargin = moneyFct = 0; if (Bars.Symbol.Contains ("_OneOfBR")) { Data.SecPriceStep = 0.01; stepPrice = 6.14629; moneyMargin = 4700; } else if (Bars.Symbol.Contains ("_OneOfGD")) { Data.SecPriceStep = 0.1; stepPrice = 6.29685; moneyMargin = (7756.08+790879)/2; } else if (Bars.Symbol.Contains ("_OneOfRI")) { Data.SecPriceStep = 10; stepPrice = 12.29258; moneyMargin = 24000; } else if (Bars.Symbol.Contains ("_OneOfSi") || Bars.Symbol.Contains ("_OneOfGZ")) { Data.SecPriceStep = 1; stepPrice = 1; moneyMargin = 4500; Data.DealFee = 3; } else if (Bars.Symbol.Contains ("_OneOfSR")) { Data.SecPriceStep = 1; stepPrice = 1; moneyMargin = 4500; Data.DealFee = 2.50; } else return; moneyFct = stepPrice / Data.SecPriceStep; Data.MinProfit = Arg.MinMoney / moneyFct; SetPeriodBars(); SpreadLong = High - Low; SpreadShrt = Open - Close; SpreadLong.Description = "SpeadLong"; SpreadShrt.Description = "SpeadShrt"; int m = Bars.Count-1; PrintDebug ("Spreads " + SpreadLong[m] + " " + SpreadShrt[m]); PrintDebug ("HighLows " + High[m] + " " + Low[m]); DataSeries smaLong = SMA.Series (SpreadLong, Data.PeriodBars); smaLong.Description = "SmaLong"; DataSeries smaShrt = SMA.Series (SpreadShrt, Data.PeriodBars); smaShrt.Description = "SmaShrt"; UprLong = smaLong + Data.MinProfit; UprLong.Description = "UprLong"; LwrShrt = smaShrt - Data.MinProfit; LwrShrt.Description = "LwrShrt"; double avrLong = 0; double avrShrt = 0; double ofrbid = 0; for (int i = 0; i < Bars.Count; ++i) { avrLong += SpreadLong[i]; avrShrt += SpreadShrt[i]; ofrbid += Close[i] - Low[i]; } avrLong /= Bars.Count; avrShrt /= Bars.Count; ofrbid /= Bars.Count; int k = -23; PrintDebug (String.Format ("{0,"+k+"}{1,7:F2} ({2:F0} руб)" ,"Мин.выигрыш", Data.MinProfit, Arg.MinMoney)); PrintDebug (String.Format ("{0,"+k+"}{1,7:F2}" ,"Профит-фактор", Arg.ProfitFactor)); PrintDebug (String.Format ("{0,"+k+"}{1,7:F2}, пункты" ,"Шаг цены фьючерса", Data.SecPriceStep)); PrintDebug (String.Format ("{0,"+(k+3)+"}{1,7}" ,"Период в минутах", Arg.PeriodMins)); PrintDebug (String.Format ("{0,"+(k+3)+"}{1,7}" ,"Период в барах", Data.PeriodBars)); PrintDebug (String.Format ("{0,"+k+"}{1,7:F2}" ,"Максимум лонг-спреда", SpreadLong.MaxValue)); PrintDebug (String.Format ("{0,"+k+"}{1,7:F2}" ,"Минимум лонг-спреда", SpreadLong.MinValue)); PrintDebug (String.Format ("{0,"+k+"}{1,7:F2}" ,"Максимум шорт-спреда", SpreadShrt.MaxValue)); PrintDebug (String.Format ("{0,"+k+"}{1,7:F2}" ,"Минимум шорт-спреда", SpreadShrt.MinValue)); PrintDebug (String.Format ("{0,"+k+"}{1,7:F2}" ,"Коридор лонг-спреда", SpreadLong.MaxValue - SpreadLong.MinValue)); PrintDebug (String.Format ("{0,"+k+"}{1,7:F2}" ,"Коридор шорт-спреда", SpreadShrt.MaxValue - SpreadShrt.MinValue)); PrintDebug (String.Format ("{0,"+k+"}{1,7:F2}" ,"Средний лонг-спред", avrLong)); PrintDebug (String.Format ("{0,"+k+"}{1,7:F2}" ,"Средний шорт-спред", avrShrt)); PrintDebug (String.Format ("{0,"+k+"}{1,7:F2}" ,"Средний офер-бид", ofrbid)); ChartPane cpSpread = CreatePane (50, true, true); DrawLabel (cpSpread, "Spread"); PlotSeries (cpSpread, smaLong, Color.Black, LineStyle.Solid, 1); PlotSeries (cpSpread, smaShrt, Color.Black, LineStyle.Solid, 1); if (Bars.BarInterval > 1) { DataSeries diff = smaLong - smaShrt; ChartPane cpDiff = CreatePane (25, true, true); PlotSeries (cpDiff, diff, Color.Gray, LineStyle.Histogram, 3); return; } PlotSeries (cpSpread, SpreadLong, Color.Green, LineStyle.Dots, 3); PlotSeries (cpSpread, SpreadShrt, Color.Red, LineStyle.Dots, 3); PlotSeries (cpSpread, UprLong, Color.Green, LineStyle.Solid, 1); PlotSeries (cpSpread, LwrShrt, Color.Red, LineStyle.Solid, 1); for (int i = 0; i < Data.PeriodBars-1; ++i) { UprLong[i] = UprLong[Data.PeriodBars-1]; LwrShrt[i] = LwrShrt[Data.PeriodBars-1]; SetSeriesBarColor (i, UprLong, Color.Empty); SetSeriesBarColor (i, LwrShrt, Color.Empty); } int upr = 0; int lwr = 0; int uprlwr = 0; for (int i = Data.PeriodBars-1; i < Bars.Count; ++i) { if (SpreadLong[i] >= UprLong[i] && SpreadShrt[i] > LwrShrt[i]) { SetPaneBackgroundColor (cpSpread, i, Color.LightGreen); ++upr; } else if (SpreadShrt[i] <= LwrShrt[i] && SpreadLong[i] < UprLong[i]) { SetPaneBackgroundColor (cpSpread, i, Color.Pink); ++lwr; } else if (SpreadLong[i] >= UprLong[i] && SpreadShrt[i]<= LwrShrt[i]) { SetPaneBackgroundColor (cpSpread, i, Color.LightYellow); ++uprlwr; } } int j = Bars.Count-10; AnnotateChart(cpSpread, SpreadLong[j].ToString() ,j, SpreadLong[j]+17, Color.Black); AnnotateChart(cpSpread, SpreadShrt[j].ToString() ,j, SpreadShrt[j]-7, Color.Red); k = -20; PrintDebug (String.Format ("{0,"+k+"}{1,7} ({2,5:F2}%)" ,"Пробоев вверх", upr ,100.0 * upr / (Bars.Count - Data.PeriodBars))); PrintDebug (String.Format ("{0,"+k+"}{1,7} ({2,5:F2}%)" ,"Пробоев вниз", lwr ,100.0 * lwr / (Bars.Count - Data.PeriodBars))); PrintDebug (String.Format ("{0,"+k+"}{1,7} ({2,5:F2}%)" ,"Пробоев вверх и вниз", uprlwr ,100.0 * uprlwr / (Bars.Count - Data.PeriodBars))); SpreadMid = ((High + Open) - (Close + Low)) / 2; SpreadMid.Description = "SpreadMid"; DataSeries smaMid = SMA.Series (SpreadMid, Data.PeriodBars); smaMid.Description = "SmaMid"; ChartPane cpMid = CreatePane (25, true, true); DrawLabel (cpMid, "Mid"); PlotSeries (cpMid, SpreadMid, Color.Black, LineStyle.Solid, 1); PlotSeries (cpMid, smaMid, Color.Blue, LineStyle.Solid, 1); UprMid = smaMid + Data.MinProfit; UprMid.Description = "UptMid"; LwrMid = smaMid - Data.MinProfit; LwrMid.Description = "LwrMid"; PlotSeries (cpMid, UprMid, Color.Green, LineStyle.Solid, 1); PlotSeries (cpMid, LwrMid, Color.Red, LineStyle.Solid, 1); for (int i = 0; i < Data.PeriodBars-1; ++i) { UprMid[i] = UprMid[Data.PeriodBars-1]; LwrMid[i] = LwrMid[Data.PeriodBars-1]; SetSeriesBarColor (i, UprMid, Color.Empty); SetSeriesBarColor (i, LwrMid, Color.Empty); } for (int i = Data.PeriodBars-1; i < Bars.Count; ++i) { if (SpreadMid[i] > UprMid[i]) SetPaneBackgroundColor (cpMid, i, Color.LightGreen); else if (SpreadMid[i] < LwrMid[i]) SetPaneBackgroundColor (cpMid, i, Color.Pink); } } // Prepare() void SetPeriodBars() { if (Bars.BarInterval > 1) { // Arg.PeriodMins) { Arg.PeriodMins = Bars.BarInterval; Data.PeriodBars = 1; return; } int barsPerMin = 60 * Int32.Parse ( Bars.Symbol [Bars.Symbol.Length-1].ToString()); Data.PeriodBars = Arg.PeriodMins * barsPerMin / Bars.BarInterval; } } // class CalendarSpreadTwo class Pos { public int Type = 0; // 1 - long, -1 - short public int EntryBar, ExitBar; public double EntryPrice, ExitPrice; public int PosNo = 0; } } // namespace WealthLab.StrategiesЭто головной скрипт монитора на QLua для Quik:
-- Мониторим котировки ближнего и дальнего фьючерсов по тикам Data = { "RIH0", "RIM0" -- дорогой и дешёвый фьючерсы ,MsDlt = 200 -- период опроса очереди заявок, мсек } function OnInit (scriptPath) dofile ("D:\\BAT\\Lua\\SetPaths64.lua") Require { qu = "QuikUtil(qu)" } -- lu, qc, tu, wau ScriptDir, ScriptName = lu.SplitPath (scriptPath) end -- OnInit() function OnStop (signal) -- 1 по кнопке Остановить StopFlag = true -- 2 при закрытии Quik'а return 3000 -- Вместо стандартных 5 сек end -- OnStop() function main() dofile (ScriptDir .."OneOf_Lib.lua") local frame = MakeFrame() -- Неуклюжая идея. Исправить!!! message (ScriptName ..": Start") while not StopFlag and frame.HandleTick() do end message (ScriptName ..": Quit") end -- main()А это его вспомогательный dofile:
-- dofile монитора котировок ближнего и дальнего фьючерсов по тикам -- Константы local Header = "<TICKER>;<PER>;<DATE>;<TIME>;<OPEN>;<HIGH>;<LOW>;<CLOSE>;<VOL>" local per = "1" local tmBeg, tmEnd = "10000", "184500" --local tmBeg, tmEnd = "00000", "235000" -- Параметры: Data.MsDlt -- Data[1],[2] -- дорогой и дешёвый фьючерсы, "BRG0", "BRH0" и т.п. local ticker = Data[1] .."-".. Data[2] -- Общие: Log, ScriptDir С конечным "\", ScriptName Без расширения local ymd = os.date ("%Y%m%d", os.time()) local sfx = Data.MsDlt == 200 and 5 or Data.MsDlt == 500 and 2 or nil if not sfx then error ("Invalid MsDlt", 2) end Log = io.open (ScriptDir .."Log\\".. ymd .."_".. ScriptName .. sfx ..".csv", "w") Log:write (Header) function MakeFrame () local ask1, bid1, ask2, bid2, sprd local frm = Data[1]:sub (1, 2) == "GD" and "%s;%s;%s;%s;%.1f;%.1f;%.1f;%.1f;%.2f" or Data[1]:sub (1, 2) == "BR" and "%s;%s;%s;%s;%.2f;%.2f;%.2f;%.2f;%.3f" or "%s;%s;%s;%s;%d;%d;%d;%d;%d" -- GZ, RI, Si, SR local pre, no = os.time(), 0 -- Ловим начало секунды local cur = pre while cur == pre do sleep (1); cur = os.time() end pre = cur local m = {} m.HandleTick = function() local tm = os.date ("%H%M%S", cur) if tm >= tmEnd then return false end if tm >= tmBeg then no = no + 1 ask1 = qu.GetParamNum (qc.SPBFUT, Data[1], qc.OFFER) ask2 = qu.GetParamNum (qc.SPBFUT, Data[2], qc.OFFER) bid1 = qu.GetParamNum (qc.SPBFUT, Data[1], qc.BID) bid2 = qu.GetParamNum (qc.SPBFUT, Data[2], qc.BID) sprd = (ask1 + bid1) / 2 - (ask2 + bid2) / 2 local msg = string.format (frm, ticker, per ,os.date ("%Y%m%d", cur), tm ,bid1, ask1, bid2, ask2, no) Log:write ("\n".. msg) end -- if tm >= tmBeg sleep (Data.MsDlt) cur = os.time() if cur > pre then Log:flush() pre, no = cur, 0 end return true end -- m.HandleTick() return m end -- MakeFrame()
У Вас есть положительный опыт торговли этого календарника? Или это тоже только в теории на минутках?
В опционах ликвидность похуже, но ведь торгуют и не жалуются.
upd. Хотя прошу прощение. Если ставка по маркету плавает, то между фучами да, ставка тоже поплывет
Боюсь, мне это недоступно. Я играю через домашний ПК.
Оосновная проблема в том, что внутридневный размах движения спреда фьючерса (и в самые мелкие мксек, и за часы) сопоставим со спредом бидов-оферов дальнего фьючерса.
К тому же, чтобы не застревать надолго в позиции, надо угадывать направление движения спреда фьчерсов, что мне тоже не дано.
Характерная ситуация. В конце дня позиция застряла в убытке. Что делать? Переносить на следующий день и получить углубление убытка или рубить позицию с потерей дневной прибыли?
привожу график по RTS-3.20 за 1 секунду, за которую было огромное количество возможностей, в том числе и календарного арбитража с RTS-6.20.
По RTS-3.20 за эту секунду проторговано более 5 тысяч лотов.
По RTS-6.20 за этот же период проторговано 11 (одиннадцать!) лотов.
Где все арбитражеры то?
это не просто секунда. Это уникальная секунда. Если вы этого не видите на картинке, мне жаль.