/// <summary> /// Метод под флаг TemplateTypes.INTERACTIVESPLINE /// </summary> public InteractiveSeries Execute(InteractiveSeries smile, IOptionSeries optSer, double scaleMult, int barNum) { if ((smile == null) || (optSer == null)) { return(Constants.EmptySeries); } int barsCount = m_context.BarsCount; if (!m_context.IsLastBarUsed) { barsCount--; } if (barNum < barsCount - 1) { return(Constants.EmptySeries); } SmileInfo oldInfo = smile.GetTag <SmileInfo>(); if ((oldInfo == null) || (oldInfo.ContinuousFunction == null)) { return(Constants.EmptySeries); } double futPx = oldInfo.F; double dT = oldInfo.dT; if (m_executeCommand) { string msg = String.Format("[{0}.StartButton] Strike: {1}", GetType().Name, m_strike); m_context.Log(msg, MessageType.Info, false); } #region 1. Список страйков HashSet <string> serList = StrikeList; serList.Clear(); IOptionStrikePair[] pairs; if (Double.IsNaN(m_strikeStep) || (m_strikeStep <= Double.Epsilon)) { pairs = optSer.GetStrikePairs().ToArray(); } else { // Выделяем страйки, которые нацело делятся на StrikeStep pairs = (from p in optSer.GetStrikePairs() let test = m_strikeStep * Math.Round(p.Strike / m_strikeStep) where DoubleUtil.AreClose(p.Strike, test) select p).ToArray(); // [2015-12-24] Если шаг страйков по ошибке задан совершенно неправильно, // то в коллекцию ставим все имеющиеся страйки. // Пользователь потом разберется if (pairs.Length <= 0) { pairs = optSer.GetStrikePairs().ToArray(); } } //if (pairs.Length < 2) // return Constants.EmptyListDouble; foreach (IOptionStrikePair pair in pairs) { double k = pair.Strike; serList.Add(k.ToString(StrikeFormat, CultureInfo.InvariantCulture)); } #endregion 1. Список страйков InteractiveSeries res = Constants.EmptySeries; List <InteractiveObject> controlPoints = new List <InteractiveObject>(); #region 2. Формируем улыбку просто для отображения текущего положения потенциальной котировки // При нулевом рабочем объёме не утруждаемся рисованием лишних линий if (!DoubleUtil.IsZero(m_qty)) { for (int j = 0; j < pairs.Length; j++) { var pair = pairs[j]; double sigma = oldInfo.ContinuousFunction.Value(pair.Strike) + m_shiftIv; if (!DoubleUtil.IsPositive(sigma)) { //string msg = String.Format("[DEBUG:{0}] Invalid sigma:{1} for strike:{2}", GetType().Name, sigma, nodeInfo.Strike); //m_context.Log(msg, MessageType.Warning, true); continue; } //bool isCall = (futPx <= pair.Strike); bool isCall; if (m_optionType == StrikeType.Call) { isCall = true; } else if (m_optionType == StrikeType.Put) { isCall = false; } else { isCall = (futPx <= pair.Strike); } StrikeType optionType = isCall ? StrikeType.Call : StrikeType.Put; Contract.Assert(pair.Tick < 1, $"#1 На тестовом контуре Дерибит присылает неправильный шаг цены! Tick:{pair.Tick}; Decimals:{pair.Put.Security.Decimals}"); double theorOptPxDollars = FinMath.GetOptionPrice(futPx, pair.Strike, dT, sigma, oldInfo.RiskFreeRate, isCall); // Сразу(!!!) переводим котировку из баксов в битки double theorOptPxBitcoins = theorOptPxDollars / scaleMult; // Сдвигаем цену в долларах (с учетом ш.ц. в баксах) theorOptPxDollars += m_shiftPriceStep * pair.Tick * scaleMult; theorOptPxDollars = Math.Round(theorOptPxDollars / (pair.Tick * scaleMult)) * (pair.Tick * scaleMult); // Сдвигаем цену в биткойнах (с учетом ш.ц. в битках) theorOptPxBitcoins += m_shiftPriceStep * pair.Tick; theorOptPxBitcoins = Math.Round(theorOptPxBitcoins / pair.Tick) * pair.Tick; if ((!DoubleUtil.IsPositive(theorOptPxBitcoins)) || (!DoubleUtil.IsPositive(theorOptPxDollars))) { //string msg = String.Format("[DEBUG:{0}] Invalid theorOptPx:{1} for strike:{2}", GetType().Name, theorOptPx, nodeInfo.Strike); //m_context.Log(msg, MessageType.Warning, true); continue; } // Пересчитываем сигму обратно, ЕСЛИ мы применили сдвиг цены в абсолютном выражении if (m_shiftPriceStep != 0) { // Обратный пересчет в волатильность sigma = FinMath.GetOptionSigma(futPx, pair.Strike, dT, theorOptPxDollars, oldInfo.RiskFreeRate, isCall); if (!DoubleUtil.IsPositive(sigma)) { //string msg = String.Format("[DEBUG:{0}] Invalid sigma:{1} for strike:{2}", GetType().Name, sigma, nodeInfo.Strike); //m_context.Log(msg, MessageType.Warning, true); continue; } } // ReSharper disable once UseObjectOrCollectionInitializer SmileNodeInfo nodeInfo = new SmileNodeInfo(); var secDesc = isCall ? pair.CallFinInfo.Security : pair.PutFinInfo.Security; nodeInfo.F = oldInfo.F; nodeInfo.dT = oldInfo.dT; nodeInfo.RiskFreeRate = oldInfo.RiskFreeRate; nodeInfo.Strike = pair.Strike; nodeInfo.Sigma = sigma; nodeInfo.OptPx = theorOptPxBitcoins; nodeInfo.OptionType = isCall ? StrikeType.Call : StrikeType.Put; nodeInfo.Pair = pair; nodeInfo.Symbol = secDesc.Name; nodeInfo.DSName = secDesc.DSName; nodeInfo.Expired = secDesc.Expired; nodeInfo.FullName = secDesc.FullName; // ReSharper disable once UseObjectOrCollectionInitializer InteractivePointActive tmp = new InteractivePointActive(); tmp.IsActive = true; tmp.ValueX = pair.Strike; tmp.ValueY = sigma; tmp.DragableMode = DragableMode.Yonly; tmp.Tooltip = String.Format(CultureInfo.InvariantCulture, " F: {0}\r\n K: {1}; IV: {2:P2}\r\n {3} px {4}", futPx, pair.Strike, sigma, optionType, theorOptPxBitcoins); tmp.Tag = nodeInfo; //tmp.Color = Colors.White; if (m_qty > 0) { tmp.Geometry = Geometries.Triangle; } else if (m_qty < 0) { tmp.Geometry = Geometries.TriangleDown; } else { tmp.Geometry = Geometries.None; } InteractiveObject obj = new InteractiveObject(); obj.Anchor = tmp; controlPoints.Add(obj); } // ReSharper disable once UseObjectOrCollectionInitializer res = new InteractiveSeries(); // Здесь так надо -- мы делаем новую улыбку res.ControlPoints = new ReadOnlyCollection <InteractiveObject>(controlPoints); // ReSharper disable once UseObjectOrCollectionInitializer SmileInfo sInfo = new SmileInfo(); sInfo.F = futPx; sInfo.dT = dT; sInfo.Expiry = oldInfo.Expiry; sInfo.ScriptTime = oldInfo.ScriptTime; sInfo.RiskFreeRate = oldInfo.RiskFreeRate; sInfo.BaseTicker = oldInfo.BaseTicker; res.Tag = sInfo; if (controlPoints.Count > 0) { res.ClickEvent -= InteractiveSplineOnQuoteIvEvent; res.ClickEvent += InteractiveSplineOnQuoteIvEvent; m_clickableSeries = res; } } #endregion 2. Формируем улыбку просто для отображения текущего положения потенциальной котировки PositionsManager posMan = PositionsManager.GetManager(m_context); if (m_cancelAllLong) { posMan.DropAllLongIvTargets(m_context); } if (m_cancelAllShort) { posMan.DropAllShortIvTargets(m_context); } #region 4. Котирование { var longTargets = posMan.GetIvTargets(true); var shortTargets = posMan.GetIvTargets(false); var ivTargets = longTargets.Union(shortTargets).ToList(); for (int j = 0; j < ivTargets.Count; j++) { var ivTarget = ivTargets[j]; // PROD-6102 - Требуется точное совпадение опционной серии if (optSer.ExpirationDate.Date != ivTarget.SecInfo.Expiry.Date) { // Вывести предупреждение??? continue; } IOptionStrikePair pair; double k = ivTarget.SecInfo.Strike; if (!optSer.TryGetStrikePair(k, out pair)) { // Вывести предупреждение??? continue; } double sigma; QuoteIvMode quoteMode = ivTarget.QuoteMode; if (quoteMode == QuoteIvMode.Absolute) { sigma = ivTarget.EntryIv; } else { sigma = oldInfo.ContinuousFunction.Value(k) + ivTarget.EntryIv; if (!DoubleUtil.IsPositive(sigma)) { //string msg = String.Format("[DEBUG:{0}] Invalid sigma:{1} for strike:{2}", GetType().Name, sigma, nodeInfo.Strike); //m_context.Log(msg, MessageType.Warning, true); continue; } } //bool isCall = (futPx <= pair.Strike); // Определяю тип опциона на основании информации в Задаче StrikeType taskOptionType = StrikeType.Any; if (ivTarget.SecInfo.StrikeType.HasValue) { taskOptionType = ivTarget.SecInfo.StrikeType.Value; } bool isCall; if (taskOptionType == StrikeType.Call) { isCall = true; } else if (taskOptionType == StrikeType.Put) { isCall = false; } else { isCall = (futPx <= pair.Strike); // Это аварийная ситуация? } StrikeType optionType = isCall ? StrikeType.Call : StrikeType.Put; Contract.Assert(pair.Tick < 1, $"#3 На тестовом контуре Дерибит присылает неправильный шаг цены! Tick:{pair.Tick}; Decimals:{pair.Put.Security.Decimals}"); double theorOptPxDollars = FinMath.GetOptionPrice(futPx, pair.Strike, dT, sigma, oldInfo.RiskFreeRate, isCall); // Сразу(!!!) переводим котировку из баксов в битки double theorOptPxBitcoins = theorOptPxDollars / scaleMult; // Сдвигаем цену в долларах (с учетом ш.ц. в баксах) theorOptPxDollars += ivTarget.EntryShiftPrice * pair.Tick * scaleMult; theorOptPxDollars = Math.Round(theorOptPxDollars / (pair.Tick * scaleMult)) * (pair.Tick * scaleMult); // Сдвигаем цену в биткойнах (с учетом ш.ц. в битках) theorOptPxBitcoins += ivTarget.EntryShiftPrice * pair.Tick; theorOptPxBitcoins = Math.Round(theorOptPxBitcoins / pair.Tick) * pair.Tick; if ((!DoubleUtil.IsPositive(theorOptPxBitcoins)) || (!DoubleUtil.IsPositive(theorOptPxDollars))) { //string msg = String.Format("[DEBUG:{0}] Invalid theorOptPx:{1} for strike:{2}", GetType().Name, theorOptPx, nodeInfo.Strike); //m_context.Log(msg, MessageType.Warning, true); continue; } IOptionStrike optStrike = isCall ? pair.Call : pair.Put; ISecurity sec = optStrike.Security; double totalQty = posMan.GetTotalQty(sec, m_context.BarsCount, TotalProfitAlgo.AllPositions, ivTarget.IsLong); // Поскольку котирование страйка по волатильности -- это вопрос набора нужного количества СТРЕДДЛОВ, // то учитывать надо суммарный объём опционов как в колах, так и в путах. // НО ЗАДАЧУ-ТО Я СТАВЛЮ ДЛЯ КОНКРЕТНОГО ИНСТРУМЕНТА! // Как быть? //double totalQty = posMan.GetTotalQty(pair.Put.Security, m_context.BarsCount, TotalProfitAlgo.AllPositions, ivTarget.IsLong); //totalQty += posMan.GetTotalQty(pair.Call.Security, m_context.BarsCount, TotalProfitAlgo.AllPositions, ivTarget.IsLong); double targetQty = Math.Abs(ivTarget.TargetShares) - totalQty; // Если имеется дробный LotTick (как в Дерибит к примеру), то надо предварительно округлить targetQty = sec.RoundShares(targetQty); if (targetQty > 0) { string note = String.Format(CultureInfo.InvariantCulture, "{0}; ActQty:{1}; Px:{2}; IV:{3:P2}", ivTarget.EntryNotes, targetQty, theorOptPxBitcoins, sigma); if (ivTarget.IsLong) { posMan.BuyAtPrice(m_context, sec, targetQty, theorOptPxBitcoins, ivTarget.EntrySignalName, note); } else { posMan.SellAtPrice(m_context, sec, targetQty, theorOptPxBitcoins, ivTarget.EntrySignalName, note); } } else { string msg = String.Format(CultureInfo.InvariantCulture, "IvTarget cancelled. SignalName:{0}; Notes:{1}", ivTarget.EntrySignalName, ivTarget.EntryNotes); posMan.CancelVolatility(m_context, ivTarget, msg); // TODO: потом убрать из ГЛ m_context.Log(msg, MessageType.Info, true, new Dictionary <string, object> { { "VOLATILITY_ORDER_CANCELLED", msg } }); } } } #endregion 4. Котирование #region 5. Торговля if (m_executeCommand && (!DoubleUtil.IsZero(m_qty))) { double k; if ((!Double.TryParse(m_strike, out k)) && (!Double.TryParse(m_strike, NumberStyles.Any, CultureInfo.InvariantCulture, out k))) { return(res); } var pair = (from p in pairs where DoubleUtil.AreClose(k, p.Strike) select p).SingleOrDefault(); if (pair == null) { return(res); } InteractiveObject obj = (from o in controlPoints where DoubleUtil.AreClose(k, o.Anchor.ValueX) select o).SingleOrDefault(); if (obj == null) { return(res); } // TODO: для режима котирования в абсолютных числах сделать отдельную ветку //double iv = obj.Anchor.ValueY; const QuoteIvMode QuoteMode = QuoteIvMode.Relative; if (posMan.BlockTrading) { string msg = String.Format(RM.GetString("OptHandlerMsg.PositionsManager.TradingBlocked"), m_context.Runtime.TradeName + ":QuoteIv"); m_context.Log(msg, MessageType.Warning, true); return(res); } // Выбираю тип инструмента пут или колл? bool isCall; if (m_optionType == StrikeType.Call) { isCall = true; } else if (m_optionType == StrikeType.Put) { isCall = false; } else { isCall = (futPx <= k); } double iv = m_shiftIv; int shift = m_shiftPriceStep; var option = isCall ? pair.Call : pair.Put; if (m_qty > 0) { // Пересчитываю целочисленный параметр Qty в фактические лоты конкретного инструмента double actQty = m_qty * option.LotTick; string sigName = String.Format(CultureInfo.InvariantCulture, "Qty:{0}; IV:{1:P2}+{2}; dT:{3}; Mode:{4}", actQty, iv, shift, dT, QuoteMode); posMan.BuyVolatility(m_context, option, Math.Abs(actQty), QuoteMode, iv, shift, "BuyVola", sigName); m_context.Log(sigName, MessageType.Info, false); } else if (m_qty < 0) { // Пересчитываю целочисленный параметр Qty в фактические лоты конкретного инструмента double actQty = m_qty * option.LotTick; string sigName = String.Format(CultureInfo.InvariantCulture, "Qty:{0}; IV:{1:P2}+{2}; dT:{3}; Mode:{4}", actQty, iv, shift, dT, QuoteMode); posMan.SellVolatility(m_context, option, Math.Abs(actQty), QuoteMode, iv, shift, "SellVola", sigName); m_context.Log(sigName, MessageType.Info, false); } } #endregion 5. Торговля return(res); }
/// <summary> /// Основной метод, который выполняет всю торговую логику по котированию и следит за риском /// </summary> protected double Process(double entryPermission, double centralStrike, double risk, double maxRisk, InteractiveSeries smile, IOptionSeries optSer, double callRisk, double putRisk, int barNum) { int barsCount = m_context.BarsCount; if (!m_context.IsLastBarUsed) { barsCount--; } if ((barNum < barsCount - 1) || (optSer == null) || (smile == null)) { return(Constants.NaN); } { IOptionStrikePair testPair; if (!optSer.TryGetStrikePair(centralStrike, out testPair)) { return(Constants.NaN); } } // Если риск не был измерен говорить вообще не о чем! if (Double.IsNaN(risk)) { return(Constants.NaN); } // Если риск разумен и условие входа НЕ ВЫПОЛНЕНО -- отдыхаем. // А вот если риск превышен -- тогда по идее надо бы его подсократить! if ((risk < maxRisk) && (entryPermission <= 0)) { return(Constants.NaN); } PositionsManager posMan = PositionsManager.GetManager(m_context); if (posMan.BlockTrading) { //string msg = String.Format("Trading is blocked. Please, change 'Block Trading' parameter."); //m_context.Log(msg, MessageType.Info, true); return(Constants.NaN); } SmileInfo sInfo = smile.GetTag <SmileInfo>(); if ((sInfo == null) || (sInfo.ContinuousFunction == null)) { return(Constants.NaN); } double dT = sInfo.dT; double futPx = sInfo.F; // Набираем риск if (risk < maxRisk) { // Надо взять пары, начиная от центральной и далее по возрастанию расстояния с учетом шага страйков IOptionStrikePair[] orderedPairs; if (m_strikeStep < Double.Epsilon) { // Просто сортируем страйки по расстоянию до Центра orderedPairs = (from p in optSer.GetStrikePairs() orderby Math.Abs(p.Strike - centralStrike) ascending select p).ToArray(); } else { // Сортировка по возрастанию до Центра + обязательно условие кратности параметру m_strikeStep orderedPairs = (from p in optSer.GetStrikePairs() let dK = Math.Abs(p.Strike - centralStrike) let dKStep = (int)Math.Round(dK / m_strikeStep) where DoubleUtil.AreClose(dK, m_strikeStep * dKStep) // проверяем, что расстояние от страйка до центра кратно m_strikeStep orderby Math.Abs(p.Strike - centralStrike) ascending select p).ToArray(); } Contract.Assert(m_strikeAmount >= 0, "Как получился отрицательный m_strikeAmount??? m_strikeAmount: " + m_strikeAmount); // Защита от дурака? Или не надо париться? m_strikeAmount = Math.Max(0, m_strikeAmount); // Котируем либо 1 центральный страйк либо центр + четное число соседей int maxStrikeCount = 2 * m_strikeAmount + 1; int strikeCounter = 0; // Сколько лотов уже выставлено в рынок double pendingQty = 0; if (orderedPairs.Length > 0) { foreach (IOptionStrikePair candidPair in orderedPairs) { if (strikeCounter >= maxStrikeCount) { // Все, выходим. Цикл завершен. break; } double ivAtm; double strike = candidPair.Strike; if ((!sInfo.ContinuousFunction.TryGetValue(strike, out ivAtm)) || Double.IsNaN(ivAtm) || (ivAtm < Double.Epsilon)) { string msg = String.Format("[{0}.{1}] Unable to get IV at strike {2}. ivAtm:{3}", Context.Runtime.TradeName, GetType().Name, candidPair.Strike, ivAtm); m_context.Log(msg, MessageType.Error, true); return(Constants.NaN); } double theorPutPx = FinMath.GetOptionPrice(futPx, strike, dT, ivAtm, 0, false); double theorCallPx = FinMath.GetOptionPrice(futPx, strike, dT, ivAtm, 0, true); #region Набираем риск double putPx, callPx; { double putQty, callQty; DateTime putTime, callTime; putPx = IvSmile.GetOptPrice(m_context, futPx, candidPair.Put, OptionPxMode.Bid, 0, 0, out putQty, out putTime); callPx = IvSmile.GetOptPrice(m_context, futPx, candidPair.Call, OptionPxMode.Bid, 0, 0, out callQty, out callTime); } if ((m_optionType == StrikeType.Put) || (m_optionType == StrikeType.Any) && (strike <= futPx)) { #region В путах ISecurity sec = candidPair.Put.Security; double qty = Math.Abs(m_fixedQty); // TODO: Немного грубая оценка, но пока сойдет qty = BuyOptions.GetSafeQty(risk + pendingQty * putRisk, maxRisk, qty, putRisk); if (qty > 0) { double px = SellOptions.SafeMaxPrice(theorPutPx + m_entryShift * sec.Tick, putPx, sec); double iv = FinMath.GetOptionSigma(futPx, candidPair.Strike, dT, px, 0, false); string sigName = String.Format("Risk:{0}; MaxRisk:{1}; Px:{2}; Qty:{3}; IV:{4:P2}; dT:{5}", risk, maxRisk, px, qty, iv, dT); posMan.SellAtPrice(m_context, sec, qty, px, "Open SELL", sigName); pendingQty += qty; m_context.Log(sigName, MessageType.Info, false); } #endregion В путах } else if ((m_optionType == StrikeType.Call) || (m_optionType == StrikeType.Any) && (futPx <= strike)) { #region В колах ISecurity sec = candidPair.Call.Security; double qty = Math.Abs(m_fixedQty); // TODO: Немного грубая оценка, но пока сойдет qty = BuyOptions.GetSafeQty(risk + pendingQty * callRisk, maxRisk, qty, callRisk); if (qty > 0) { double px = SellOptions.SafeMaxPrice(theorCallPx + m_entryShift * sec.Tick, callPx, sec); double iv = FinMath.GetOptionSigma(futPx, candidPair.Strike, dT, px, 0, true); string sigName = String.Format("Risk:{0}; MaxRisk:{1}; Px:{2}; Qty:{3}; IV:{4:P2}; dT:{5}", risk, maxRisk, px, qty, iv, dT); posMan.SellAtPrice(m_context, sec, qty, px, "Open SELL", sigName); pendingQty += qty; m_context.Log(sigName, MessageType.Info, false); } #endregion В колах } else { // Вроде бы, сюда не должны приходить никогда?.. #region В оба вида опционов сразу встаю int executedQty = 0; { ISecurity sec = candidPair.Put.Security; double px = SellOptions.SafeMaxPrice(theorPutPx + m_entryShift * sec.Tick, putPx, sec); double iv = FinMath.GetOptionSigma(futPx, candidPair.Strike, dT, px, 0, false); double qty = Math.Max(1, Math.Abs(m_fixedQty / 2)); // TODO: Немного грубая оценка, но пока сойдет qty = BuyOptions.GetSafeQty(risk + pendingQty * putRisk, maxRisk, qty, putRisk); if (qty > 0) { string sigName = String.Format("Risk:{0}; MaxRisk:{1}; Px:{2}; Qty:{3}; IV:{4:P2}; dT:{5}", risk, maxRisk, px, qty, iv, dT); posMan.SellAtPrice(m_context, sec, qty, px, "Open SELL", sigName); pendingQty += qty; m_context.Log(sigName, MessageType.Info, false); executedQty += (int)qty; } } if (Math.Abs(executedQty) < Math.Abs(m_fixedQty)) { ISecurity sec = candidPair.Call.Security; double px = SellOptions.SafeMaxPrice(theorCallPx + m_entryShift * sec.Tick, callPx, sec); double iv = FinMath.GetOptionSigma(futPx, candidPair.Strike, dT, px, 0, true); double qty = Math.Abs(m_fixedQty) - Math.Abs(executedQty); // TODO: Немного грубая оценка, но пока сойдет // Делаю оценку изменения текущего риска, если нам зафилят заявку в путах //qty = BuyOptions.GetSafeQty(risk + Math.Abs(executedQty) * putRisk, maxRisk, qty, callRisk); // Причем здесь уже не нужно отдельно учитывать executedQty, потому что он входит в pendingQty qty = BuyOptions.GetSafeQty(risk + pendingQty * callRisk, maxRisk, qty, callRisk); if (qty > 0) { string sigName = String.Format("Risk:{0}; MaxRisk:{1}; Px:{2}; Qty:{3}; IV:{4:P2}; dT:{5}", risk, maxRisk, px, qty, iv, dT); posMan.SellAtPrice(m_context, sec, qty, px, "Open SELL", sigName); pendingQty += qty; m_context.Log(sigName, MessageType.Info, false); //executedQty += (int)qty; } } #endregion В оба вида опционов сразу встаю } #endregion Набираем риск strikeCounter++; } // End foreach (IOptionStrikePair candidPair in orderedPairs) } else { string msg = String.Format("[{0}] Strike not found. risk:{1}; maxRisk:{2}; orderedPairs.Length:{3}", Context.Runtime.TradeName, risk, maxRisk, orderedPairs.Length); m_context.Log(msg, MessageType.Warning, true); } } else if (risk > maxRisk) { string msg; //string msg = String.Format("[DEBUG:{0}] risk:{1}; maxRisk:{2}", Context.Runtime.TradeName, risk, maxRisk); //m_context.Log(msg, MessageType.Info, true); // Надо взять пары, начиная от центральной и далее по возрастанию расстояния var orderedPairs = (from p in optSer.GetStrikePairs() orderby Math.Abs(p.Strike - centralStrike) ascending select p).ToArray(); if (orderedPairs.Length > 0) { foreach (IOptionStrikePair candidPair in orderedPairs) { #region Проверяю, что в страйке есть КОРОТКАЯ позиция double putOpenQty = posMan.GetTotalQty(candidPair.Put.Security, barNum); double callOpenQty = posMan.GetTotalQty(candidPair.Call.Security, barNum); if ((putOpenQty >= 0) && (callOpenQty >= 0)) { continue; } if (DoubleUtil.IsZero(putOpenQty) && DoubleUtil.IsZero(callOpenQty)) { continue; } { msg = String.Format("[{0}:{1}] Strike:{2}; putOpenQty:{3}; callOpenQty:{4}", Context.Runtime.TradeName, GetType().Name, candidPair.Strike, putOpenQty, callOpenQty); m_context.Log(msg, MessageType.Info, true); } #endregion Проверяю, что в страйке есть КОРОТКАЯ позиция double theorPutPx, theorCallPx; { double iv; if ((!sInfo.ContinuousFunction.TryGetValue(candidPair.Strike, out iv)) || Double.IsNaN(iv) || (iv < Double.Epsilon)) { msg = String.Format("[{0}.{1}] Unable to get IV at strike {2}. IV:{3}", Context.Runtime.TradeName, GetType().Name, candidPair.Strike, iv); m_context.Log(msg, MessageType.Error, true); continue; } theorPutPx = FinMath.GetOptionPrice(futPx, candidPair.Strike, dT, iv, 0, false); theorCallPx = FinMath.GetOptionPrice(futPx, candidPair.Strike, dT, iv, 0, true); } #region Сдаём риск (один квант объёма за раз) double putPx, callPx; { DateTime putTime, callTime; double putAskQty, callAskQty; putPx = IvSmile.GetOptPrice(m_context, futPx, candidPair.Put, OptionPxMode.Ask, 0, 0, out putAskQty, out putTime); callPx = IvSmile.GetOptPrice(m_context, futPx, candidPair.Call, OptionPxMode.Ask, 0, 0, out callAskQty, out callTime); } if (m_optionType == StrikeType.Put) { #region В путах if (putOpenQty < 0) // Это означает, что в страйке есть короткие путы { ISecurity sec = candidPair.Put.Security; double px = SellOptions.SafeMinPrice(theorPutPx + m_exitShift * sec.Tick, putPx, sec); double iv = FinMath.GetOptionSigma(futPx, candidPair.Strike, dT, px, 0, false); double qty = Math.Min(Math.Abs(m_fixedQty), Math.Abs(putOpenQty)); string sigName = String.Format("Risk:{0}; MaxRisk:{1}; Px:{2}; Qty:{3}; IV:{4:P2}; dT:{5}", risk, maxRisk, px, qty, iv, dT); posMan.BuyAtPrice(m_context, sec, qty, px, "Close BUY", sigName); m_context.Log(sigName, MessageType.Info, false); // Выход из foreach (IOptionStrikePair candidPair in orderedPairs) break; } #endregion В путах } else if (m_optionType == StrikeType.Call) { #region В колах if (callOpenQty < 0) // Это означает, что в страйке есть короткие колы { ISecurity sec = candidPair.Call.Security; double px = SellOptions.SafeMinPrice(theorCallPx + m_exitShift * sec.Tick, callPx, sec); double iv = FinMath.GetOptionSigma(futPx, candidPair.Strike, dT, px, 0, true); double qty = Math.Min(Math.Abs(m_fixedQty), Math.Abs(callOpenQty)); string sigName = String.Format("Risk:{0}; MaxRisk:{1}; Px:{2}; Qty:{3}; IV:{4:P2}; dT:{5}", risk, maxRisk, px, qty, iv, dT); posMan.BuyAtPrice(m_context, sec, qty, px, "Close BUY", sigName); m_context.Log(sigName, MessageType.Info, false); // Выход из foreach (IOptionStrikePair candidPair in orderedPairs) break; } #endregion В колах } else { #region В оба вида опционов сразу встаю int executedQty = 0; if (putOpenQty < 0) // Это означает, что в страйке есть короткие путы { ISecurity sec = candidPair.Put.Security; double px = SellOptions.SafeMinPrice(theorPutPx + m_exitShift * sec.Tick, putPx, sec); double iv = FinMath.GetOptionSigma(futPx, candidPair.Strike, dT, px, 0, false); double qty = Math.Min(Math.Abs(m_fixedQty), Math.Abs(putOpenQty)); string sigName = String.Format("Risk:{0}; MaxRisk:{1}; Px:{2}; Qty:{3}; IV:{4:P2}; dT:{5}", risk, maxRisk, px, qty, iv, dT); posMan.BuyAtPrice(m_context, sec, qty, px, "Close BUY", sigName); m_context.Log(sigName, MessageType.Info, false); executedQty += (int)qty; } if ((callOpenQty < 0) && // Это означает, что в страйке есть короткие колы (Math.Abs(executedQty) < Math.Abs(m_fixedQty))) { ISecurity sec = candidPair.Call.Security; double px = SellOptions.SafeMinPrice(theorCallPx + m_exitShift * sec.Tick, callPx, sec); double iv = FinMath.GetOptionSigma(futPx, candidPair.Strike, dT, px, 0, true); double qty = Math.Min(Math.Abs(m_fixedQty) - Math.Abs(executedQty), Math.Abs(callOpenQty)); string sigName = String.Format("Risk:{0}; MaxRisk:{1}; Px:{2}; Qty:{3}; IV:{4:P2}; dT:{5}", risk, maxRisk, px, qty, iv, dT); posMan.BuyAtPrice(m_context, sec, qty, px, "Close BUY", sigName); m_context.Log(sigName, MessageType.Info, false); executedQty += (int)qty; } if (executedQty > 0) { // Выход из foreach (IOptionStrikePair candidPair in orderedPairs) break; } #endregion В оба вида опционов сразу встаю } #endregion Сдаём риск (один квант объёма за раз) } } else { msg = String.Format("[{0}.{1}] risk:{2}; maxRisk:{3}; orderedPairs.Length:{4}", Context.Runtime.TradeName, GetType().Name, risk, maxRisk, orderedPairs.Length); m_context.Log(msg, MessageType.Warning, true); } } return(Constants.NaN); }
public double Execute(double price, double time, InteractiveSeries smile, IOptionSeries optSer, int barNum) { int barsCount = m_context.BarsCount; if (!m_context.IsLastBarUsed) { barsCount--; } if ((barNum < barsCount - 1) || (optSer == null)) { return(Constants.NaN); } int lastBarIndex = optSer.UnderlyingAsset.Bars.Count - 1; DateTime now = optSer.UnderlyingAsset.Bars[Math.Min(barNum, lastBarIndex)].Date; bool wasInitialized = HandlerInitializedToday(now); double f = price; double dT = time; if (!DoubleUtil.IsPositive(dT)) { // [{0}] Time to expiry must be positive value. dT:{1} string msg = RM.GetStringFormat("OptHandlerMsg.TimeMustBePositive", GetType().Name, dT); if (wasInitialized) { m_context.Log(msg, MessageType.Error, true); } return(Constants.NaN); } if (!DoubleUtil.IsPositive(f)) { // [{0}] Base asset price must be positive value. F:{1} string msg = RM.GetStringFormat("OptHandlerMsg.FutPxMustBePositive", GetType().Name, f); if (wasInitialized) { m_context.Log(msg, MessageType.Error, true); } return(Constants.NaN); } if (smile == null) { string msg = String.Format("[{0}] Argument 'smile' must be filled with InteractiveSeries.", GetType().Name); if (wasInitialized) { m_context.Log(msg, MessageType.Error, true); } return(Constants.NaN); } SmileInfo oldInfo = smile.GetTag <SmileInfo>(); if (oldInfo == null) { string msg = String.Format("[{0}] Property Tag of object smile must be filled with SmileInfo. Tag:{1}", GetType().Name, smile.Tag); if (wasInitialized) { m_context.Log(msg, MessageType.Error, true); } return(Constants.NaN); } double rawDelta, res; double dF = optSer.UnderlyingAsset.Tick; IOptionStrikePair[] pairs = optSer.GetStrikePairs().ToArray(); PositionsManager posMan = PositionsManager.GetManager(m_context); if (SingleSeriesNumericalDelta.TryEstimateDelta(posMan, optSer, pairs, smile, m_greekAlgo, f, dF, dT, out rawDelta)) { res = rawDelta; } else { res = Constants.NaN; } SmileInfo sInfo = smile.GetTag <SmileInfo>(); if ((sInfo != null) && (sInfo.ContinuousFunction != null)) { double iv = sInfo.ContinuousFunction.Value(sInfo.F); string msg = String.Format("[{0}] F:{1}; dT:{2}; smile.F:{3}; smile.dT:{4}; smile.IV:{5}", GetType().Name, f, dT, sInfo.F, sInfo.dT, iv); m_context.Log(msg, MessageType.Info, false); } m_delta.Value = res; //context.Log(String.Format("[{0}] Delta: {1}; rawDelta:{2}", MsgId, delta.Value, rawDelta), logColor, true); if (m_hedgeDelta) { #region Hedge logic try { int rounded = Math.Sign(rawDelta) * ((int)Math.Floor(Math.Abs(rawDelta))); if (rounded == 0) { string msg = String.Format("[{0}] Delta is too low to hedge. Delta: {1}", MsgId, rawDelta); m_context.Log(msg, MessageType.Info, true); } else { int len = optSer.UnderlyingAsset.Bars.Count; ISecurity sec = (from s in m_context.Runtime.Securities where (s.SecurityDescription.Equals(optSer.UnderlyingAsset.SecurityDescription)) select s).SingleOrDefault(); if (sec == null) { string msg = String.Format("[{0}] There is no security. Symbol: {1}", MsgId, optSer.UnderlyingAsset.Symbol); m_context.Log(msg, MessageType.Warning, true); } else { if (rounded < 0) { string signalName = String.Format("\r\nDelta BUY\r\nF:{0}; dT:{1}; Delta:{2}\r\n", f, dT, rawDelta); m_context.Log(signalName, MessageType.Warning, true); posMan.BuyAtPrice(m_context, sec, Math.Abs(rounded), f, signalName, null); } else if (rounded > 0) { string signalName = String.Format("\r\nDelta SELL\r\nF:{0}; dT:{1}; Delta:+{2}\r\n", f, dT, rawDelta); m_context.Log(signalName, MessageType.Warning, true); posMan.SellAtPrice(m_context, sec, Math.Abs(rounded), f, signalName, null); } } } } finally { m_hedgeDelta = false; } #endregion Hedge logic } SetHandlerInitialized(now); return(res); }
/// <summary> /// Основной метод, который выполняет всю торговую логику по котированию и следит за риском /// </summary> protected double Process(double entryPermission, double centralStrike, double risk, double maxRisk, InteractiveSeries smile, IOptionSeries optSer, InteractiveSeries callDelta, double callRisk, double putRisk, int barNum) { int barsCount = m_context.BarsCount; if (!m_context.IsLastBarUsed) { barsCount--; } if ((barNum < barsCount - 1) || (optSer == null) || (smile == null)) { return(Constants.NaN); } { IOptionStrikePair testPair; if (!optSer.TryGetStrikePair(centralStrike, out testPair)) { return(Constants.NaN); } } // Если риск не был измерен говорить вообще не о чем! if (Double.IsNaN(risk)) { return(Constants.NaN); } // Если риск разумен и условие входа НЕ ВЫПОЛНЕНО -- отдыхаем. // А вот если риск превышен -- тогда по идее надо бы его подсократить! if ((risk < maxRisk) && (entryPermission <= 0)) { return(Constants.NaN); } PositionsManager posMan = PositionsManager.GetManager(m_context); if (posMan.BlockTrading) { //string msg = String.Format("Trading is blocked. Please, change 'Block Trading' parameter."); //m_context.Log(msg, MessageType.Info, true); return(Constants.NaN); } SmileInfo sInfo = smile.GetTag <SmileInfo>(); if ((sInfo == null) || (sInfo.ContinuousFunction == null)) { return(Constants.NaN); } double dT = sInfo.dT; double futPx = sInfo.F; SmileInfo callDeltaInfo = callDelta.GetTag <SmileInfo>(); if ((callDeltaInfo == null) || (callDeltaInfo.ContinuousFunction == null)) { return(Constants.NaN); } // Функция для вычисления дельты кола IFunction cDf = callDeltaInfo.ContinuousFunction; // Набираем риск if (risk < maxRisk) { List <IOptionStrikePair> orderedPairs = BuyOptionGroupDelta.GetFilteredPairs(optSer, centralStrike, cDf, m_strikeStep, m_minDelta, m_maxDelta, m_checkAbsDelta); // Сколько лотов уже выставлено в рынок double pendingQty = 0; if (orderedPairs.Count > 0) { foreach (IOptionStrikePair candidPair in orderedPairs) { double ivAtm; double strike = candidPair.Strike; if ((!sInfo.ContinuousFunction.TryGetValue(strike, out ivAtm)) || Double.IsNaN(ivAtm) || (ivAtm < Double.Epsilon)) { string msg = String.Format("[{0}.{1}] Unable to get IV at strike {2}. ivAtm:{3}", Context.Runtime.TradeName, GetType().Name, candidPair.Strike, ivAtm); m_context.Log(msg, MessageType.Error, true); return(Constants.NaN); } double theorPutPx = FinMath.GetOptionPrice(futPx, strike, dT, ivAtm, 0, false); double theorCallPx = FinMath.GetOptionPrice(futPx, strike, dT, ivAtm, 0, true); double cd, pd; // Вычисляю дельту кола и с ее помощью -- дельту пута if (!cDf.TryGetValue(strike, out cd)) { // Этого не может быть по правилу отбора страйков! Contract.Assert(false, "Почему мы не смогли вычислить дельту кола???"); continue; } // Типа, колл-пут паритет для вычисления дельты путов pd = 1 - cd; if (m_checkAbsDelta) { // Берем дельты по модулю cd = Math.Abs(cd); pd = Math.Abs(pd); } double putPx, callPx; { double putQty, callQty; DateTime putTime, callTime; putPx = IvSmile.GetOptPrice(m_context, futPx, candidPair.Put, OptionPxMode.Bid, 0, 0, out putQty, out putTime); callPx = IvSmile.GetOptPrice(m_context, futPx, candidPair.Call, OptionPxMode.Bid, 0, 0, out callQty, out callTime); } #region Набираем риск int executedQty = 0; // Если дельта пута влезает в диапазон -- выставляем котировку в путы if ((m_minDelta <= pd) && (pd <= m_maxDelta)) { #region Набираем риск в путах ISecurity sec = candidPair.Put.Security; double px = SellOptions.SafeMaxPrice(theorPutPx + m_entryShift * sec.Tick, putPx, sec); double iv = FinMath.GetOptionSigma(futPx, candidPair.Strike, dT, px, 0, false); double qty = Math.Abs(m_fixedQty); // TODO: Немного грубая оценка, но пока сойдет qty = BuyOptions.GetSafeQty(risk + pendingQty * putRisk, maxRisk, qty, putRisk); if (qty > 0) { string sigName = String.Format("Risk:{0}; MaxRisk:{1}; Px:{2}; Qty:{3}; IV:{4:P2}; dT:{5}", risk, maxRisk, px, qty, iv, dT); posMan.SellAtPrice(m_context, sec, qty, px, "Open SELL", sigName); pendingQty += qty; m_context.Log(sigName, MessageType.Info, false); executedQty += (int)qty; } #endregion Набираем риск в путах } // Если дельта кола влезает в диапазон -- выставляем котировку в колы if (Math.Abs(executedQty) < Math.Abs(m_fixedQty) && (m_minDelta <= cd) && (cd <= m_maxDelta)) { #region Набираем риск в колах ISecurity sec = candidPair.Call.Security; double px = SellOptions.SafeMaxPrice(theorCallPx + m_entryShift * sec.Tick, callPx, sec); double iv = FinMath.GetOptionSigma(futPx, candidPair.Strike, dT, px, 0, true); double qty = Math.Abs(m_fixedQty) - Math.Abs(executedQty); // TODO: Немного грубая оценка, но пока сойдет // Делаю оценку изменения текущего риска, если нам зафилят заявку в путах //qty = BuyOptions.GetSafeQty(risk + Math.Abs(executedQty) * putRisk, maxRisk, qty, callRisk); // Причем здесь уже не нужно отдельно учитывать executedQty, потому что он входит в pendingQty qty = BuyOptions.GetSafeQty(risk + pendingQty * callRisk, maxRisk, qty, callRisk); if (qty > 0) { string sigName = String.Format("Risk:{0}; MaxRisk:{1}; Px:{2}; Qty:{3}; IV:{4:P2}; dT:{5}", risk, maxRisk, px, qty, iv, dT); posMan.SellAtPrice(m_context, sec, qty, px, "Open SELL", sigName); pendingQty += qty; m_context.Log(sigName, MessageType.Info, false); //executedQty += (int)qty; } #endregion Набираем риск в колах } #endregion Набираем риск } // End foreach (IOptionStrikePair candidPair in orderedPairs) } else { string msg = String.Format("[{0}] Strike not found. risk:{1}; maxRisk:{2}; orderedPairs.Count:{3}", Context.Runtime.TradeName, risk, maxRisk, orderedPairs.Count); m_context.Log(msg, MessageType.Warning, true); } } else if (risk > maxRisk) { string msg; //string msg = String.Format("[DEBUG:{0}] risk:{1}; maxRisk:{2}", Context.Runtime.TradeName, risk, maxRisk); //m_context.Log(msg, MessageType.Info, true); // Надо взять пары, начиная от центральной и далее по возрастанию расстояния var orderedPairs = (from p in optSer.GetStrikePairs() orderby Math.Abs(p.Strike - centralStrike) ascending select p).ToArray(); if (orderedPairs.Length > 0) { foreach (IOptionStrikePair candidPair in orderedPairs) { #region Проверяю, что в страйке есть КОРОТКАЯ позиция double putOpenQty = posMan.GetTotalQty(candidPair.Put.Security, barNum); double callOpenQty = posMan.GetTotalQty(candidPair.Call.Security, barNum); if ((putOpenQty >= 0) && (callOpenQty >= 0)) { continue; } if (DoubleUtil.IsZero(putOpenQty) && DoubleUtil.IsZero(callOpenQty)) { continue; } { msg = String.Format("[{0}:{1}] Strike:{2}; putOpenQty:{3}; callOpenQty:{4}", Context.Runtime.TradeName, GetType().Name, candidPair.Strike, putOpenQty, callOpenQty); m_context.Log(msg, MessageType.Info, true); } #endregion Проверяю, что в страйке есть КОРОТКАЯ позиция double theorPutPx, theorCallPx; { double iv; if ((!sInfo.ContinuousFunction.TryGetValue(candidPair.Strike, out iv)) || Double.IsNaN(iv) || (iv < Double.Epsilon)) { msg = String.Format("[{0}.{1}] Unable to get IV at strike {2}. IV:{3}", Context.Runtime.TradeName, GetType().Name, candidPair.Strike, iv); m_context.Log(msg, MessageType.Error, true); continue; } theorPutPx = FinMath.GetOptionPrice(futPx, candidPair.Strike, dT, iv, 0, false); theorCallPx = FinMath.GetOptionPrice(futPx, candidPair.Strike, dT, iv, 0, true); } #region Сдаём риск (один квант объёма за раз) double putPx, callPx; { DateTime putTime, callTime; double putAskQty, callAskQty; putPx = IvSmile.GetOptPrice(m_context, futPx, candidPair.Put, OptionPxMode.Ask, 0, 0, out putAskQty, out putTime); callPx = IvSmile.GetOptPrice(m_context, futPx, candidPair.Call, OptionPxMode.Ask, 0, 0, out callAskQty, out callTime); } //if (m_optionType == StrikeType.Put) //{ // #region В путах // if (putOpenQty < 0) // Это означает, что в страйке есть короткие путы // { // ISecurity sec = candidPair.Put.Security; // double px = SafeMinPrice(theorPutPx + m_exitShift * sec.Tick, putPx, sec); // double iv = FinMath.GetOptionSigma(futPx, candidPair.Strike, dT, px, 0, false); // double qty = Math.Min(Math.Abs(m_fixedQty), Math.Abs(putOpenQty)); // string sigName = String.Format("Risk:{0}; MaxRisk:{1}; Px:{2}; Qty:{3}; IV:{4:P2}; dT:{5}", risk, maxRisk, px, qty, iv, dT); // posMan.BuyAtPrice(m_context, sec, qty, px, "Close BUY", sigName); // m_context.Log(sigName, MessageType.Info, false); // // Выход из foreach (IOptionStrikePair candidPair in orderedPairs) // break; // } // #endregion В путах //} //else if (m_optionType == StrikeType.Call) //{ // #region В колах // if (callOpenQty < 0) // Это означает, что в страйке есть короткие колы // { // ISecurity sec = candidPair.Call.Security; // double px = SafeMinPrice(theorCallPx + m_exitShift * sec.Tick, callPx, sec); // double iv = FinMath.GetOptionSigma(futPx, candidPair.Strike, dT, px, 0, true); // double qty = Math.Min(Math.Abs(m_fixedQty), Math.Abs(callOpenQty)); // string sigName = String.Format("Risk:{0}; MaxRisk:{1}; Px:{2}; Qty:{3}; IV:{4:P2}; dT:{5}", risk, maxRisk, px, qty, iv, dT); // posMan.BuyAtPrice(m_context, sec, qty, px, "Close BUY", sigName); // m_context.Log(sigName, MessageType.Info, false); // // Выход из foreach (IOptionStrikePair candidPair in orderedPairs) // break; // } // #endregion В колах //} //else { #region В оба вида опционов сразу встаю int executedQty = 0; if (putOpenQty < 0) // Это означает, что в страйке есть короткие путы { ISecurity sec = candidPair.Put.Security; double px = SellOptions.SafeMinPrice(theorPutPx + m_exitShift * sec.Tick, putPx, sec); double iv = FinMath.GetOptionSigma(futPx, candidPair.Strike, dT, px, 0, false); double qty = Math.Min(Math.Abs(m_fixedQty), Math.Abs(putOpenQty)); string sigName = String.Format("Risk:{0}; MaxRisk:{1}; Px:{2}; Qty:{3}; IV:{4:P2}; dT:{5}", risk, maxRisk, px, qty, iv, dT); posMan.BuyAtPrice(m_context, sec, qty, px, "Close BUY", sigName); m_context.Log(sigName, MessageType.Info, false); executedQty += (int)qty; } if ((callOpenQty < 0) && // Это означает, что в страйке есть короткие колы (Math.Abs(executedQty) < Math.Abs(m_fixedQty))) { ISecurity sec = candidPair.Call.Security; double px = SellOptions.SafeMinPrice(theorCallPx + m_exitShift * sec.Tick, callPx, sec); double iv = FinMath.GetOptionSigma(futPx, candidPair.Strike, dT, px, 0, true); double qty = Math.Min(Math.Abs(m_fixedQty) - Math.Abs(executedQty), Math.Abs(callOpenQty)); string sigName = String.Format("Risk:{0}; MaxRisk:{1}; Px:{2}; Qty:{3}; IV:{4:P2}; dT:{5}", risk, maxRisk, px, qty, iv, dT); posMan.BuyAtPrice(m_context, sec, qty, px, "Close BUY", sigName); m_context.Log(sigName, MessageType.Info, false); executedQty += (int)qty; } if (executedQty > 0) { // Выход из foreach (IOptionStrikePair candidPair in orderedPairs) break; } #endregion В оба вида опционов сразу встаю } #endregion Сдаём риск (один квант объёма за раз) } } else { msg = String.Format("[{0}.{1}] risk:{2}; maxRisk:{3}; orderedPairs.Length:{4}", Context.Runtime.TradeName, GetType().Name, risk, maxRisk, orderedPairs.Length); m_context.Log(msg, MessageType.Warning, true); } } return(Constants.NaN); }
private void RouteOnClickEvents(List <InteractiveActionEventArgs> eventArgs) { if (eventArgs == null) { return; } int argLen = eventArgs.Count; PositionsManager posMan = PositionsManager.GetManager(m_context); if (posMan.BlockTrading && (argLen > 0)) { eventArgs.Clear(); string msg = String.Format("[{0}] ERROR! Trading is blocked. Should NOT be here. All events were removed.", m_optionPxMode); m_context.Log(msg, MessageType.Warning, true); return; } bool recalc = false; for (int j = argLen - 1; j >= 0; j--) { InteractiveActionEventArgs eventArg = eventArgs[j]; eventArgs.RemoveAt(j); try { SmileNodeInfo nodeInfo = eventArg.Point.Tag as SmileNodeInfo; if (nodeInfo == null) { //string msg = String.Format("[{0}] There is no nodeInfo. Quote type: {1}; Strike: {2}", m_optionPxMode); string msg = RM.GetStringFormat(CultureInfo.InvariantCulture, "OptHandlerMsg.ThereIsNoNodeInfo", m_context.Runtime.TradeName, m_optionPxMode, eventArg.Point.ValueX); m_context.Log(msg, MessageType.Error, true); continue; } if ((!posMan.UseVirtualPositions) && nodeInfo.Expired) { string msg = String.Format("[{0}] Security {1} is already expired. Expiry: {2}", m_optionPxMode, nodeInfo.Symbol, nodeInfo.Security.SecurityDescription.ExpirationDate); m_context.Log(msg, MessageType.Error, true); continue; } ISecurity sec = (from s in m_context.Runtime.Securities where (s.Symbol.Equals(nodeInfo.Symbol, StringComparison.InvariantCultureIgnoreCase)) && (String.IsNullOrWhiteSpace(nodeInfo.DSName) || (s.SecurityDescription.DSName == nodeInfo.DSName)) && (String.IsNullOrWhiteSpace(nodeInfo.FullName) || (s.SecurityDescription.FullName == nodeInfo.FullName)) select s).SingleOrDefault(); if (sec == null) { string msg = String.Format("[{0}] There is no security. Symbol: {1}", m_optionPxMode, nodeInfo.Symbol); m_context.Log(msg, MessageType.Error, true); continue; } double actualPx = nodeInfo.OptPx; if (m_optionType != StrikeType.Any) { // Заменяю инструмент в соответствии с настройками кубика switch (m_optionType) { case StrikeType.Put: if (sec.SecurityDescription.ActiveType != DataSource.ActiveType.OptionPut) { var oldCall = sec; sec = nodeInfo.Pair.Put.Security; // Теперь еще нужно заменить ЦЕНУ. Проще всего это сделать через паритет. // C - P = F - K ==> P = C + K - F double putPx = nodeInfo.OptPx + nodeInfo.Strike - nodeInfo.F; // Но не меньше 1 ш.ц.! actualPx = Math.Max(putPx, sec.Tick); string txt = String.Format(CultureInfo.InvariantCulture, "[{0}] Symbol '{1}' has been replaced to '{2}' and changed price from {3} to {4}", m_optionPxMode, nodeInfo.Symbol, sec.Symbol, nodeInfo.OptPx, actualPx); m_context.Log(txt, MessageType.Warning, true); } break; case StrikeType.Call: if (sec.SecurityDescription.ActiveType != DataSource.ActiveType.OptionCall) { var oldPut = sec; sec = nodeInfo.Pair.Call.Security; // Теперь еще нужно заменить ЦЕНУ. Проще всего это сделать через паритет. // C - P = F - K ==> C = P + F - K double callPx = nodeInfo.OptPx + nodeInfo.F - nodeInfo.Strike; // Но не меньше 1 ш.ц.! actualPx = Math.Max(callPx, sec.Tick); string txt = String.Format(CultureInfo.InvariantCulture, "[{0}] Symbol '{1}' has been replaced to '{2}' and changed price from {3} to {4}", m_optionPxMode, nodeInfo.Symbol, sec.Symbol, nodeInfo.OptPx, actualPx); m_context.Log(txt, MessageType.Warning, true); } break; } } int len = sec.Bars.Count; // Валидирую наличие правильной котировки if (sec.FinInfo != null) { Debug.WriteLine("AskPx: " + sec.FinInfo.Ask); Debug.WriteLine("BidPx: " + sec.FinInfo.Bid); } recalc = true; if ((m_optionPxMode == OptionPxMode.Ask) && (m_qty > 0) || (m_optionPxMode == OptionPxMode.Bid) && (m_qty < 0)) { string signalName = "\r\nLeft-Click BUY \r\n" + eventArg.Point.Tooltip + "\r\n"; posMan.BuyAtPrice(m_context, sec, Math.Abs(m_qty), actualPx, signalName, null); } else if ((m_optionPxMode == OptionPxMode.Bid) && (m_qty > 0) || (m_optionPxMode == OptionPxMode.Ask) && (m_qty < 0)) { string signalName = "\r\nLeft-Click SELL \r\n" + eventArg.Point.Tooltip + "\r\n"; posMan.SellAtPrice(m_context, sec, Math.Abs(m_qty), actualPx, signalName, null); } else if (m_optionPxMode == OptionPxMode.Mid) { string msg = String.Format("[{0}] OptionPxMode.Mid is not implemented.", m_optionPxMode); m_context.Log(msg, MessageType.Warning, true); } else if (m_qty == 0) { string msg = String.Format("[{0}] Qty: {1}. Trading is blocked.", m_optionPxMode, m_qty); m_context.Log(msg, MessageType.Warning, true); } } catch (Exception ex) { string msg = String.Format("[{0}] {1} in RouteOnClickEvents. Message: {2}\r\n{3}", m_optionPxMode, ex.GetType().FullName, ex.Message, ex); m_context.Log(msg, MessageType.Warning, true); } } if (recalc) { m_context.Recalc(RecalcReasons.ChartTrading, null); } }
/// <summary> /// Основной метод, который выполняет всю торговую логику по котированию и следит за риском /// </summary> protected double Process(double entryPermission, double strike, double risk, double maxRisk, InteractiveSeries smile, IOptionSeries optSer, double callRisk, double putRisk, int barNum) { int barsCount = m_context.BarsCount; if (!m_context.IsLastBarUsed) { barsCount--; } if ((barNum < barsCount - 1) || (optSer == null) || (smile == null)) { return(Constants.NaN); } IOptionStrikePair pair; if (!optSer.TryGetStrikePair(strike, out pair)) { return(Constants.NaN); } // Если риск не был измерен говорить вообще не о чем! if (Double.IsNaN(risk) || Double.IsInfinity(risk)) { return(Constants.NaN); } // Если риск разумен и условие входа НЕ ВЫПОЛНЕНО -- отдыхаем. // А вот если риск превышен -- тогда по идее надо бы его подсократить! if ((risk < maxRisk) && (entryPermission <= 0)) { return(Constants.NaN); } PositionsManager posMan = PositionsManager.GetManager(m_context); if (posMan.BlockTrading) { //string msg = String.Format("Trading is blocked. Please, change 'Block Trading' parameter."); //m_context.Log(msg, MessageType.Info, true); return(Constants.NaN); } SmileInfo sInfo = smile.GetTag <SmileInfo>(); if ((sInfo == null) || (sInfo.ContinuousFunction == null)) { return(Constants.NaN); } double dT = sInfo.dT; double futPx = sInfo.F; // Набираем риск if (risk < maxRisk) { double ivAtm; if ((!sInfo.ContinuousFunction.TryGetValue(strike, out ivAtm)) || Double.IsNaN(ivAtm) || (ivAtm < Double.Epsilon)) { string msg = String.Format("[{0}.{1}] Unable to get IV at strike {2}. ivAtm:{3}", Context.Runtime.TradeName, GetType().Name, pair.Strike, ivAtm); m_context.Log(msg, MessageType.Error, true); return(Constants.NaN); } double theorPutPx = FinMath.GetOptionPrice(futPx, strike, dT, ivAtm, 0, false); double theorCallPx = FinMath.GetOptionPrice(futPx, strike, dT, ivAtm, 0, true); #region Набираем риск double putPx, callPx; { double putQty, callQty; DateTime putTime, callTime; putPx = IvSmile.GetOptPrice(m_context, futPx, pair.Put, OptionPxMode.Ask, 0, 0, out putQty, out putTime); callPx = IvSmile.GetOptPrice(m_context, futPx, pair.Call, OptionPxMode.Ask, 0, 0, out callQty, out callTime); } if (m_optionType == StrikeType.Put) { #region В путах ISecurity sec = pair.Put.Security; double qty = Math.Abs(m_fixedQty); qty = GetSafeQty(risk, maxRisk, qty, putRisk); if (qty > 0) { double px = SellOptions.SafeMinPrice(theorPutPx + m_entryShift * sec.Tick, putPx, sec); double iv = FinMath.GetOptionSigma(futPx, pair.Strike, dT, px, 0, false); string sigName = String.Format("Risk:{0}; MaxRisk:{1}; Px:{2}; Qty:{3}; IV:{4:P2}; dT:{5}", risk, maxRisk, px, qty, iv, dT); posMan.BuyAtPrice(m_context, sec, qty, px, "Open BUY", sigName); m_context.Log(sigName, MessageType.Info, false); } #endregion В путах } else if (m_optionType == StrikeType.Call) { #region В колах ISecurity sec = pair.Call.Security; double qty = Math.Abs(m_fixedQty); qty = GetSafeQty(risk, maxRisk, qty, callRisk); if (qty > 0) { double px = SellOptions.SafeMinPrice(theorCallPx + m_entryShift * sec.Tick, callPx, sec); double iv = FinMath.GetOptionSigma(futPx, pair.Strike, dT, px, 0, true); string sigName = String.Format("Risk:{0}; MaxRisk:{1}; Px:{2}; Qty:{3}; IV:{4:P2}; dT:{5}", risk, maxRisk, px, qty, iv, dT); posMan.BuyAtPrice(m_context, sec, qty, px, "Open BUY", sigName); m_context.Log(sigName, MessageType.Info, false); } #endregion В колах } else { #region В оба вида опционов сразу встаю int executedQty = 0; { ISecurity sec = pair.Put.Security; double px = SellOptions.SafeMinPrice(theorPutPx + m_entryShift * sec.Tick, putPx, sec); double iv = FinMath.GetOptionSigma(futPx, pair.Strike, dT, px, 0, false); double qty = Math.Max(1, Math.Abs(m_fixedQty / 2)); qty = GetSafeQty(risk, maxRisk, qty, putRisk); if (qty > 0) { string sigName = String.Format("Risk:{0}; MaxRisk:{1}; Px:{2}; Qty:{3}; IV:{4:P2}; dT:{5}", risk, maxRisk, px, qty, iv, dT); posMan.BuyAtPrice(m_context, sec, qty, px, "Open BUY", sigName); m_context.Log(sigName, MessageType.Info, false); executedQty += (int)qty; } } if (Math.Abs(executedQty) < Math.Abs(m_fixedQty)) { ISecurity sec = pair.Call.Security; double px = SellOptions.SafeMinPrice(theorCallPx + m_entryShift * sec.Tick, callPx, sec); double iv = FinMath.GetOptionSigma(futPx, pair.Strike, dT, px, 0, true); double qty = Math.Abs(m_fixedQty) - Math.Abs(executedQty); // Делаю оценку изменения текущего риска, если нам зафилят заявку в путах qty = GetSafeQty(risk + Math.Abs(executedQty) * putRisk, maxRisk, qty, callRisk); if (qty > 0) { string sigName = String.Format("Risk:{0}; MaxRisk:{1}; Px:{2}; Qty:{3}; IV:{4:P2}; dT:{5}", risk, maxRisk, px, qty, iv, dT); posMan.BuyAtPrice(m_context, sec, qty, px, "Open BUY", sigName); m_context.Log(sigName, MessageType.Info, false); //executedQty += (int)qty; } } #endregion В оба вида опционов сразу встаю } #endregion Набираем риск } else if (risk > maxRisk) { string msg; //string msg = String.Format("[DEBUG:{0}] risk:{1}; maxRisk:{2}", Context.Runtime.TradeName, risk, maxRisk); //m_context.Log(msg, MessageType.Info, true); // Надо взять пары, начиная от центральной и далее по возрастанию расстояния var orderedPairs = (from p in optSer.GetStrikePairs() orderby Math.Abs(p.Strike - strike) ascending select p).ToArray(); if (orderedPairs.Length > 0) { foreach (IOptionStrikePair candidPair in orderedPairs) { #region Проверяю, что в страйке есть ДЛИННАЯ позиция double putOpenQty = posMan.GetTotalQty(candidPair.Put.Security, barNum); double callOpenQty = posMan.GetTotalQty(candidPair.Call.Security, barNum); if ((putOpenQty <= 0) && (callOpenQty <= 0)) { continue; } if (DoubleUtil.IsZero(putOpenQty) && DoubleUtil.IsZero(callOpenQty)) { continue; } { msg = String.Format("[{0}:{1}] Strike:{2}; putOpenQty:{3}; callOpenQty:{4}", Context.Runtime.TradeName, GetType().Name, candidPair.Strike, putOpenQty, callOpenQty); m_context.Log(msg, MessageType.Info, true); } #endregion Проверяю, что в страйке есть ДЛИННАЯ позиция double theorPutPx, theorCallPx; { double iv; if ((!sInfo.ContinuousFunction.TryGetValue(candidPair.Strike, out iv)) || Double.IsNaN(iv) || (iv < Double.Epsilon)) { msg = String.Format("[{0}.{1}] Unable to get IV at strike {2}. IV:{3}", Context.Runtime.TradeName, GetType().Name, candidPair.Strike, iv); m_context.Log(msg, MessageType.Error, true); continue; } theorPutPx = FinMath.GetOptionPrice(futPx, candidPair.Strike, dT, iv, 0, false); theorCallPx = FinMath.GetOptionPrice(futPx, candidPair.Strike, dT, iv, 0, true); } #region Сдаём риск (один квант объёма за раз) double putPx, callPx; { double putQty, callQty; DateTime putTime, callTime; putPx = IvSmile.GetOptPrice(m_context, futPx, candidPair.Put, OptionPxMode.Bid, 0, 0, out putQty, out putTime); callPx = IvSmile.GetOptPrice(m_context, futPx, candidPair.Call, OptionPxMode.Bid, 0, 0, out callQty, out callTime); } if (m_optionType == StrikeType.Put) { #region В путах if (putOpenQty > 0) // Это означает, что в страйке есть длинные путы { ISecurity sec = candidPair.Put.Security; double px = SellOptions.SafeMaxPrice(theorPutPx + m_exitShift * sec.Tick, putPx, sec); double iv = FinMath.GetOptionSigma(futPx, candidPair.Strike, dT, px, 0, false); double qty = Math.Min(Math.Abs(m_fixedQty), Math.Abs(putOpenQty)); string sigName = String.Format("Risk:{0}; MaxRisk:{1}; Px:{2}; Qty:{3}; IV:{4:P2}; dT:{5}", risk, maxRisk, px, qty, iv, dT); posMan.SellAtPrice(m_context, sec, qty, px, "Close SELL", sigName); m_context.Log(sigName, MessageType.Info, false); // Выход из foreach (IOptionStrikePair candidPair in orderedPairs) break; } #endregion В путах } else if (m_optionType == StrikeType.Call) { #region В колах if (callOpenQty > 0) // Это означает, что в страйке есть длинные колы { ISecurity sec = candidPair.Call.Security; double px = SellOptions.SafeMaxPrice(theorCallPx + m_exitShift * sec.Tick, callPx, sec); double iv = FinMath.GetOptionSigma(futPx, candidPair.Strike, dT, px, 0, true); double qty = Math.Min(Math.Abs(m_fixedQty), Math.Abs(callOpenQty)); string sigName = String.Format("Risk:{0}; MaxRisk:{1}; Px:{2}; Qty:{3}; IV:{4:P2}; dT:{5}", risk, maxRisk, px, qty, iv, dT); posMan.SellAtPrice(m_context, sec, qty, px, "Close SELL", sigName); m_context.Log(sigName, MessageType.Info, false); // Выход из foreach (IOptionStrikePair candidPair in orderedPairs) break; } #endregion В колах } else { #region В оба вида опционов сразу встаю int executedQty = 0; if (putOpenQty > 0) // Это означает, что в страйке есть длинные путы { ISecurity sec = candidPair.Put.Security; double px = SellOptions.SafeMaxPrice(theorPutPx + m_exitShift * sec.Tick, putPx, sec); double iv = FinMath.GetOptionSigma(futPx, candidPair.Strike, dT, px, 0, false); double qty = Math.Min(Math.Abs(m_fixedQty), Math.Abs(putOpenQty)); string sigName = String.Format("Risk:{0}; MaxRisk:{1}; Px:{2}; Qty:{3}; IV:{4:P2}; dT:{5}", risk, maxRisk, px, qty, iv, dT); posMan.SellAtPrice(m_context, sec, qty, px, "Close SELL", sigName); m_context.Log(sigName, MessageType.Info, false); executedQty += (int)qty; } if ((callOpenQty > 0) && // Это означает, что в страйке есть длинные колы (Math.Abs(executedQty) < Math.Abs(m_fixedQty))) { ISecurity sec = candidPair.Call.Security; double px = SellOptions.SafeMaxPrice(theorCallPx + m_exitShift * sec.Tick, callPx, sec); double iv = FinMath.GetOptionSigma(futPx, candidPair.Strike, dT, px, 0, true); double qty = Math.Min(Math.Abs(m_fixedQty) - Math.Abs(executedQty), Math.Abs(callOpenQty)); string sigName = String.Format("Risk:{0}; MaxRisk:{1}; Px:{2}; Qty:{3}; IV:{4:P2}; dT:{5}", risk, maxRisk, px, qty, iv, dT); posMan.SellAtPrice(m_context, sec, qty, px, "Close SELL", sigName); m_context.Log(sigName, MessageType.Info, false); executedQty += (int)qty; } if (executedQty > 0) { // Выход из foreach (IOptionStrikePair candidPair in orderedPairs) break; } #endregion В оба вида опционов сразу встаю } #endregion Сдаём риск (один квант объёма за раз) } } else { msg = String.Format("[{0}.{1}] risk:{2}; maxRisk:{3}; orderedPairs.Length:{4}", Context.Runtime.TradeName, GetType().Name, risk, maxRisk, orderedPairs.Length); m_context.Log(msg, MessageType.Warning, true); } } return(Constants.NaN); }
public IList <double> Execute(IList <double> prices, IList <double> times, InteractiveSeries smile, IOptionSeries optSer) { List <double> positionDeltas = m_context.LoadObject(VariableId + "positionDeltas") as List <double>; if (positionDeltas == null) { positionDeltas = new List <double>(); m_context.StoreObject(VariableId + "positionDeltas", positionDeltas); } int len = optSer.UnderlyingAsset.Bars.Count; for (int j = positionDeltas.Count; j < len; j++) { positionDeltas.Add(Constants.NaN); } double f = prices[prices.Count - 1]; double dT = times[times.Count - 1]; if ((dT < Double.Epsilon) || Double.IsNaN(dT) || Double.IsNaN(f)) { return(positionDeltas); } double rawDelta, res; double dF = optSer.UnderlyingAsset.Tick; IOptionStrikePair[] pairs = optSer.GetStrikePairs().ToArray(); PositionsManager posMan = PositionsManager.GetManager(m_context); if (SingleSeriesNumericalDelta.TryEstimateDelta(posMan, optSer, pairs, smile, m_greekAlgo, f, dF, dT, out rawDelta)) { res = rawDelta; } else { res = Constants.NaN; } positionDeltas[positionDeltas.Count - 1] = res; m_delta.Value = res; //context.Log(String.Format("[{0}] Delta: {1}; rawDelta:{2}", MsgId, delta.Value, rawDelta), logColor, true); if (m_hedgeDelta) { #region Hedge logic try { int rounded = Math.Sign(rawDelta) * ((int)Math.Floor(Math.Abs(rawDelta))); if (rounded == 0) { string msg = String.Format("[{0}] Delta is too low to hedge. Delta: {1}", MsgId, rawDelta); m_context.Log(msg, MessageType.Info, true); } else { ISecurity sec = (from s in m_context.Runtime.Securities where (s.SecurityDescription.Equals(optSer.UnderlyingAsset.SecurityDescription)) select s).SingleOrDefault(); if (sec == null) { string msg = String.Format("[{0}] There is no security. Symbol: {1}", MsgId, optSer.UnderlyingAsset.Symbol); m_context.Log(msg, MessageType.Warning, true); } else { if (rounded < 0) { string signalName = String.Format("\r\nDelta BUY\r\nF:{0}; dT:{1}; Delta:{2}\r\n", f, dT, rawDelta); m_context.Log(signalName, MessageType.Warning, true); posMan.BuyAtPrice(m_context, sec, Math.Abs(rounded), f, signalName, null); } else if (rounded > 0) { string signalName = String.Format("\r\nDelta SELL\r\nF:{0}; dT:{1}; Delta:+{2}\r\n", f, dT, rawDelta); m_context.Log(signalName, MessageType.Warning, true); posMan.SellAtPrice(m_context, sec, Math.Abs(rounded), f, signalName, null); } } } } finally { m_hedgeDelta = false; } #endregion Hedge logic } return(positionDeltas); }
private void RouteOnClickEvents(List <InteractiveActionEventArgs> eventArgs) { if (eventArgs == null) { return; } int argLen = eventArgs.Count; PositionsManager posMan = PositionsManager.GetManager(m_context); if (posMan.BlockTrading && (argLen > 0)) { eventArgs.Clear(); string msg = String.Format("[{0}] ERROR! Trading is blocked. Should NOT be here. All events were removed.", m_optionPxMode); m_context.Log(msg, MessageType.Warning, true); return; } bool recalc = false; for (int j = argLen - 1; j >= 0; j--) { InteractiveActionEventArgs eventArg = eventArgs[j]; eventArgs.RemoveAt(j); try { SmileNodeInfo nodeInfo = eventArg.Point.Tag as SmileNodeInfo; if (nodeInfo == null) { //string msg = String.Format("[{0}] There is no nodeInfo. Quote type: {1}; Strike: {2}", m_optionPxMode); string msg = RM.GetStringFormat(CultureInfo.InvariantCulture, "OptHandlerMsg.ThereIsNoNodeInfo", m_context.Runtime.TradeName, m_optionPxMode, eventArg.Point.ValueX); m_context.Log(msg, MessageType.Error, true); continue; } if ((!posMan.UseVirtualPositions) && nodeInfo.Expired) { string msg = String.Format("[{0}] Security {1} is already expired. Expiry: {2}", m_optionPxMode, nodeInfo.Symbol, nodeInfo.Security.SecurityDescription.ExpirationDate); m_context.Log(msg, MessageType.Error, true); continue; } ISecurity sec = (from s in m_context.Runtime.Securities where (s.Symbol.Equals(nodeInfo.Symbol, StringComparison.InvariantCultureIgnoreCase)) && (String.IsNullOrWhiteSpace(nodeInfo.DSName) || (s.SecurityDescription.DSName == nodeInfo.DSName)) && (String.IsNullOrWhiteSpace(nodeInfo.FullName) || (s.SecurityDescription.FullName == nodeInfo.FullName)) select s).SingleOrDefault(); if (sec == null) { string msg = String.Format("[{0}] There is no security. Symbol: {1}", m_optionPxMode, nodeInfo.Symbol); m_context.Log(msg, MessageType.Error, true); continue; } int len = sec.Bars.Count; // Валидирую наличие правильной котировки if (sec.FinInfo != null) { Debug.WriteLine("AskPx: " + sec.FinInfo.Ask); Debug.WriteLine("BidPx: " + sec.FinInfo.Bid); } recalc = true; if ((m_optionPxMode == OptionPxMode.Ask) && (m_qty > 0) || (m_optionPxMode == OptionPxMode.Bid) && (m_qty < 0)) { string signalName = "\r\nLeft-Click BUY \r\n" + eventArg.Point.Tooltip + "\r\n"; posMan.BuyAtPrice(m_context, sec, Math.Abs(m_qty), nodeInfo.OptPx, signalName, null); } else if ((m_optionPxMode == OptionPxMode.Bid) && (m_qty > 0) || (m_optionPxMode == OptionPxMode.Ask) && (m_qty < 0)) { string signalName = "\r\nLeft-Click SELL \r\n" + eventArg.Point.Tooltip + "\r\n"; posMan.SellAtPrice(m_context, sec, Math.Abs(m_qty), nodeInfo.OptPx, signalName, null); } else if (m_optionPxMode == OptionPxMode.Mid) { string msg = String.Format("[{0}] OptionPxMode.Mid is not implemented.", m_optionPxMode); m_context.Log(msg, MessageType.Alert, true); } else if (m_qty == 0) { string msg = String.Format("[{0}] Qty: {1}. Trading is blocked.", m_optionPxMode, m_qty); m_context.Log(msg, MessageType.Warning, true); } } catch (Exception ex) { string msg = String.Format("[{0}] {1} in RouteOnClickEvents. Message: {2}\r\n{3}", m_optionPxMode, ex.GetType().FullName, ex.Message, ex); m_context.Log(msg, MessageType.Alert, true); } } if (recalc) { m_context.Recalc(); } }
public double Execute(InteractiveSeries deltaProfile, IOptionSeries optSer, int barNum) { List <double> positionDeltas = m_context.LoadObject(VariableId + "positionDeltas") as List <double>; if (positionDeltas == null) { positionDeltas = new List <double>(); m_context.StoreObject(VariableId + "positionDeltas", positionDeltas); } int len = m_context.BarsCount; if (len <= barNum) { string msg = String.Format("[{0}] (BarsCount <= barNum)! BarsCount:{1}; barNum:{2}", GetType().Name, m_context.BarsCount, barNum); m_context.Log(msg, MessageType.Info, true); } for (int j = positionDeltas.Count; j < Math.Max(len, barNum + 1); j++) { positionDeltas.Add(Constants.NaN); } { int barsCount = m_context.BarsCount; if (!m_context.IsLastBarUsed) { barsCount--; } if ((barNum < barsCount - 1) || (optSer == null)) { return(positionDeltas[barNum]); } } if (deltaProfile == null) { return(Constants.NaN); } SmileInfo deltaInfo = deltaProfile.GetTag <SmileInfo>(); if ((deltaInfo == null) || (deltaInfo.ContinuousFunction == null)) { positionDeltas[barNum] = Double.NaN; // заполняю индекс barNumber return(Constants.NaN); } int lastBarIndex = optSer.UnderlyingAsset.Bars.Count - 1; DateTime now = optSer.UnderlyingAsset.Bars[Math.Min(barNum, lastBarIndex)].Date; bool wasInitialized = HandlerInitializedToday(now); double f = deltaInfo.F; double dT = deltaInfo.dT; if (!DoubleUtil.IsPositive(dT)) { // [{0}] Time to expiry must be positive value. dT:{1} string msg = RM.GetStringFormat("OptHandlerMsg.TimeMustBePositive", GetType().Name, dT); if (wasInitialized) { m_context.Log(msg, MessageType.Error, true); } return(Constants.NaN); } if (!DoubleUtil.IsPositive(f)) { // [{0}] Base asset price must be positive value. F:{1} string msg = RM.GetStringFormat("OptHandlerMsg.FutPxMustBePositive", GetType().Name, f); if (wasInitialized) { m_context.Log(msg, MessageType.Error, true); } return(Constants.NaN); } double rawDelta; if (!deltaInfo.ContinuousFunction.TryGetValue(f, out rawDelta)) { rawDelta = Constants.NaN; } positionDeltas[barNum] = rawDelta; // заполняю индекс barNumber m_delta.Value = rawDelta; if (PrintDeltaInLog) { m_context.Log(MsgId + ": " + m_delta.Value, MessageType.Info, PrintDeltaInLog); } if (m_hedgeDelta) { #region Hedge logic try { int rounded = Math.Sign(rawDelta) * ((int)Math.Floor(Math.Abs(rawDelta))); if (rounded == 0) { string msg = String.Format("[{0}] Delta is too low to hedge. Delta: {1}", MsgId, rawDelta); m_context.Log(msg, MessageType.Info, true); } else { len = optSer.UnderlyingAsset.Bars.Count; ISecurity sec = (from s in m_context.Runtime.Securities where (s.Symbol == optSer.UnderlyingAsset.Symbol) select s).SingleOrDefault(); if (sec == null) { string msg = String.Format("[{0}] There is no security. Symbol: {1}", MsgId, optSer.UnderlyingAsset.Symbol); m_context.Log(msg, MessageType.Warning, true); } else { if (rounded < 0) { PositionsManager posMan = PositionsManager.GetManager(m_context); string signalName = String.Format("\r\nDelta BUY\r\nF:{0}; dT:{1}; Delta:{2}\r\n", f, dT, rawDelta); m_context.Log(signalName, MessageType.Warning, true); posMan.BuyAtPrice(m_context, sec, Math.Abs(rounded), f, signalName, null); } else if (rounded > 0) { PositionsManager posMan = PositionsManager.GetManager(m_context); string signalName = String.Format("\r\nDelta SELL\r\nF:{0}; dT:{1}; Delta:+{2}\r\n", f, dT, rawDelta); m_context.Log(signalName, MessageType.Warning, true); posMan.SellAtPrice(m_context, sec, Math.Abs(rounded), f, signalName, null); } } } } finally { m_hedgeDelta = false; } #endregion Hedge logic } SetHandlerInitialized(now); return(rawDelta); }
protected override bool TryCalculate(Dictionary <DateTime, double> history, DateTime now, int barNum, object[] args, out double val) { int col = 0; double price = (double)args[col++]; double rawDelta = (double)args[col++]; //IOptionSeries optSer = (IOptionSeries)args[col++]; double priceStep = (double)args[col++]; DateTime expirationDate = (DateTime)args[col++]; string underlyingSymbol = (string)args[col++]; DateTime lastUpdate = (DateTime)args[col++]; var baseSecDesc = (DataSource.IDataSourceSecurity)args[col++]; bool permissionToWork = (bool)args[col++]; // Здесь работаем не с парами, а со списком, чтобы потом было легче обобщить на весь опцион целиком. var strikes = (IEnumerable <IOptionStrike>)args[col++]; // Шаг лотности БАЗОВОГО АКТИВА (нужен для Дерибит в первую очередь) double lotTick = (double)args[col++]; val = rawDelta; #region Validate args double futPx = price; // Входные параметры не проинициализированы if (Double.IsNaN(rawDelta) || (!DoubleUtil.IsPositive(futPx))) { return(true); } // Входные параметры не проинициализированы //if ((optSer == null) || (optSer.UnderlyingAsset == null) || (optSer.UnderlyingAsset.SecurityDescription == null)) // return true; if (!DoubleUtil.IsPositive(priceStep)) { return(true); } if (!DoubleUtil.IsPositive(lotTick)) { return(true); } m_buyPrice.Value = futPx + m_buyShift * priceStep; m_sellPrice.Value = futPx + m_sellShift * priceStep; // PROD-3687: Если выйти из метода слишком рано, то не будут заполнены контролы на UI if (!m_hedgeDelta) { return(true); } if (!permissionToWork) { // PROD-4568 - Если параметр блока говорит "можно", а на вход блока из скрипта подан запрет, // то нужно записать в лог значения этих параметров, чтобы не было вопросов потом. // См. также обсуждение на форуме: http://forum.tslab.ru/ubb/ubbthreads.php?ubb=showflat&Number=80364#Post80364 if (Context.Runtime.IsAgentMode) { string tName = (Context.Runtime.TradeName ?? "").Replace(Constants.HtmlDot, "."); string expiry = expirationDate.ToString(DateTimeFormatWithMs, CultureInfo.InvariantCulture); string msg = String.Format("[{0}.{1}] Execution is blocked. Symbol:{2}; Expiry:{3}; HedgeDelta:{4}; Permission:{5}", MsgId, tName, underlyingSymbol, expiry, m_hedgeDelta, permissionToWork); m_context.Log(msg, MessageType.Warning, false); } return(true); } DateTime lastExec = LastExecution; //DateTime lastUpdate = optSer.UnderlyingAsset.FinInfo.LastUpdate; // В некоторых случаях слишком частое исполнение нам не подходит if ((lastUpdate - lastExec).TotalSeconds < m_minPeriod) { // Но надо проверять изменения в составе портфеля, чтобы вовремя ровнять дельту // НО КАК??? return(true); } #endregion Validate args // Если дельта уже лежит в заданных границах - просто выходим double dnEdge = m_targetDelta.Value - Math.Abs(m_doDelta.Value) + (1.0 - m_sensitivity); double upEdge = m_targetDelta.Value + Math.Abs(m_upDelta.Value) - (1.0 - m_sensitivity); // Пересчитываем пороги срабатывания в истинные лоты dnEdge *= lotTick; upEdge *= lotTick; if ((dnEdge < rawDelta) && (rawDelta < upEdge)) { //string msg = String.Format(CultureInfo.InvariantCulture, "[{0}] Delta is too low to hedge. Delta: {1}; TargetDelta: {2}; Sensitivity: {3}", // MsgId, rawDelta, m_targetDelta.Value, m_sensitivity); //context.Log(msg, logColor, true); return(true); } //var baseSecDesc = optSer.UnderlyingAsset.SecurityDescription; PositionsManager posMan = PositionsManager.GetManager(m_context); ISecurity sec = (from s in m_context.Runtime.Securities where (s.SecurityDescription != null) && baseSecDesc.Equals(s.SecurityDescription) select s).SingleOrDefault(); if (sec == null) { string msg = String.Format("[{0}] There is no security. Symbol: {1}", MsgId, underlyingSymbol); m_context.Log(msg, MessageType.Warning, true); return(true); } // PROD-6000 - Если в позиции вообще нет опционов, значит произошла какая-то ошибка if (!m_workWithoutOptions) { bool hasAnyOption = HasAnyOptionPosition(posMan, strikes); if (!hasAnyOption) { // В режиме агента обязательно логгируем, чтобы потом вопросов не было if (Context.Runtime.IsAgentMode) { string tName = (Context.Runtime.TradeName ?? "").Replace(Constants.HtmlDot, "."); string expiry = expirationDate.ToString(DateTimeFormatWithMs, CultureInfo.InvariantCulture); string msg = String.Format("[{0}.{1}] Execution is blocked. Symbol:{2}; Expiry:{3}; WorkWithoutOptions:{4}; HasAnyOption:{5}", MsgId, tName, underlyingSymbol, expiry, m_workWithoutOptions, hasAnyOption); m_context.Log(msg, MessageType.Warning, false); } return(true); } } // Здесь rawDelta и dnEdge ОБА в истинных лотах if (rawDelta <= dnEdge) { double diffLots = m_targetDelta.Value * lotTick - rawDelta; // Переводим в штуки LotTick-ов double diffLotTicks = diffLots / lotTick; int roundedLo = Math.Sign(diffLotTicks) * (int)Math.Floor(Math.Abs(diffLotTicks)); int roundedHi = Math.Sign(diffLotTicks) * (1 + (int)Math.Floor(Math.Abs(diffLotTicks))); int rounded; //if (Math.Abs(rawDelta + roundedLo - m_targetDelta.Value) <= Math.Abs(rawDelta + roundedHi - m_targetDelta.Value)) if (Math.Abs(roundedLo - diffLotTicks) <= Math.Abs(roundedHi - diffLotTicks)) { rounded = roundedLo; } else { rounded = roundedHi; } if (rounded > 0) { // [2015-07-15] Сдвиг заявки хеджа относительно рынка на заданную величину double px = futPx + m_buyShift * sec.Tick; string signalName; if (DoubleUtil.IsOne(lotTick)) { signalName = String.Format(CultureInfo.InvariantCulture, "F:{0}; Delta:{1}; TargetDelta:{2}", px, rawDelta, m_targetDelta.Value); } else { signalName = String.Format(CultureInfo.InvariantCulture, "F:{0}; Delta:{1}; TargetDelta:{2}; LotTick:{3}", px, rawDelta, m_targetDelta.Value * lotTick, lotTick); } m_context.Log(signalName, MessageType.Warning, false); posMan.BuyAtPrice(m_context, sec, Math.Abs(rounded * lotTick), px, "Delta BUY", signalName); m_context.StoreObject(VariableId + "_" + LastExecutionKey, now); } } // Здесь upEdge и rawDelta ОБА в истинных лотах else if (upEdge <= rawDelta) { double diffLots = m_targetDelta.Value * lotTick - rawDelta; // в этой ветке diff обычно отрицательный... // Переводим в штуки LotTick-ов double diffLotTicks = diffLots / lotTick; int roundedLo = Math.Sign(diffLotTicks) * (int)Math.Floor(Math.Abs(diffLotTicks)); int roundedHi = Math.Sign(diffLotTicks) * (1 + (int)Math.Floor(Math.Abs(diffLotTicks))); int rounded; if (Math.Abs(roundedLo - diffLotTicks) <= Math.Abs(roundedHi - diffLotTicks)) { rounded = roundedLo; } else { rounded = roundedHi; } if (rounded < 0) { // [2015-07-15] Сдвиг заявки хеджа относительно рынка на заданную величину double px = futPx + m_sellShift * sec.Tick; string signalName; if (DoubleUtil.IsOne(lotTick)) { signalName = String.Format(CultureInfo.InvariantCulture, "F:{0}; Delta:{1}; TargetDelta:{2}", px, rawDelta, m_targetDelta.Value); } else { signalName = String.Format(CultureInfo.InvariantCulture, "F:{0}; Delta:{1}; TargetDelta:{2}; LotTick:{3}", px, rawDelta, m_targetDelta.Value * lotTick, lotTick); } m_context.Log(signalName, MessageType.Warning, false); posMan.SellAtPrice(m_context, sec, Math.Abs(rounded * lotTick), px, "Delta SELL", signalName); m_context.StoreObject(VariableId + "_" + LastExecutionKey, now); } } return(true); }