public static double GetOptPx(this double strikePrice, double strikeStep, bool isCall, double price, double dT, double sigma) { var optPx = FinMath.GetOptionPrice(price, strikePrice, dT, sigma, 0, isCall); optPx /= price; optPx = Math.Truncate(optPx / strikeStep + 1) * strikeStep; return(Math.Max(optPx, strikeStep)); }
internal static void FillNodeInfo(InteractivePointActive ip, double f, double dT, IOptionStrikePair sInfo, StrikeType optionType, OptionPxMode optPxMode, double optSigma, bool returnPct, bool isVisiblePoints, double scaleMult, double riskfreeRatePct) { if (optionType == StrikeType.Any) { throw new ArgumentException("Option type 'Any' is not supported.", "optionType"); } bool isCall = (optionType == StrikeType.Call); double optPx = FinMath.GetOptionPrice(f, sInfo.Strike, dT, optSigma, 0, isCall); // Сразу переводим котировку из баксов в битки optPx /= scaleMult; // ReSharper disable once UseObjectOrCollectionInitializer SmileNodeInfo nodeInfo = new SmileNodeInfo(); nodeInfo.F = f; nodeInfo.dT = dT; nodeInfo.RiskFreeRate = riskfreeRatePct; nodeInfo.Sigma = returnPct ? optSigma * Constants.PctMult : optSigma; nodeInfo.OptPx = optPx; nodeInfo.Strike = sInfo.Strike; // Сюда мы приходим когда хотим торговать, поэтому обращение к Security уместно nodeInfo.Security = (optionType == StrikeType.Put) ? sInfo.Put.Security : sInfo.Call.Security; nodeInfo.PxMode = optPxMode; nodeInfo.OptionType = optionType; nodeInfo.Pair = sInfo; //nodeInfo.ScriptTime = optTime; nodeInfo.CalendarTime = DateTime.Now; nodeInfo.Symbol = nodeInfo.Security.Symbol; nodeInfo.DSName = nodeInfo.Security.SecurityDescription.DSName; nodeInfo.Expired = nodeInfo.Security.SecurityDescription.Expired; nodeInfo.FullName = nodeInfo.Security.SecurityDescription.FullName; ip.Tag = nodeInfo; //ip.Color = Colors.Yellow; ip.IsActive = isVisiblePoints; ip.Geometry = Geometries.Ellipse; ip.DragableMode = DragableMode.None; //ip.Value = new System.Windows.Point(sInfo.Strike, nodeInfo.Sigma); // Поскольку цены скорее всего расчетные, показываю на 1 знак болььше, чем объявлена точность в инструменте int decim = Math.Max(0, nodeInfo.Security.Decimals + 1); string optPxStr = optPx.ToString("N" + decim, CultureInfo.InvariantCulture); ip.Tooltip = String.Format(CultureInfo.InvariantCulture, " K: {0}; IV: {1:#0.00}%\r\n {2} px {3}", sInfo.Strike, optSigma * Constants.PctMult, optionType, optPx); }
internal static void FillNodeInfo(InteractivePointActive ip, double f, double dT, IOptionStrikePair sInfo, StrikeType optionType, OptionPxMode optPxMode, double optSigma, bool returnPct, bool isVisiblePoints, double riskfreeRatePct) { if (optionType == StrikeType.Any) { throw new ArgumentException("Option type 'Any' is not supported.", "optionType"); } bool isCall = (optionType == StrikeType.Call); double optPx = FinMath.GetOptionPrice(f, sInfo.Strike, dT, optSigma, 0, isCall); // ReSharper disable once UseObjectOrCollectionInitializer SmileNodeInfo nodeInfo = new SmileNodeInfo(); nodeInfo.F = f; nodeInfo.dT = dT; nodeInfo.RiskFreeRate = riskfreeRatePct; nodeInfo.Sigma = returnPct ? optSigma * Constants.PctMult : optSigma; nodeInfo.OptPx = optPx; nodeInfo.Strike = sInfo.Strike; // Сюда мы приходим когда хотим торговать, поэтому обращение к Security уместно nodeInfo.Security = (optionType == StrikeType.Put) ? sInfo.Put.Security : sInfo.Call.Security; nodeInfo.PxMode = optPxMode; nodeInfo.OptionType = optionType; nodeInfo.Pair = sInfo; //nodeInfo.ScriptTime = optTime; nodeInfo.CalendarTime = DateTime.Now; nodeInfo.Symbol = nodeInfo.Security.Symbol; nodeInfo.DSName = nodeInfo.Security.SecurityDescription.DSName; nodeInfo.Expired = nodeInfo.Security.SecurityDescription.Expired; nodeInfo.FullName = nodeInfo.Security.SecurityDescription.FullName; ip.Tag = nodeInfo; ip.Color = AlphaColors.Yellow; ip.IsActive = isVisiblePoints; ip.Geometry = Geometries.Ellipse; ip.DragableMode = DragableMode.None; //ip.Value = new System.Windows.Point(sInfo.Strike, nodeInfo.Sigma); ip.Tooltip = String.Format(CultureInfo.InvariantCulture, "F:{0}; K:{1}; IV:{2:#0.00}%\r\n{3} px {4}", f, sInfo.Strike, optSigma * Constants.PctMult, optionType, optPx); }
protected static void FillNodeInfo(InteractivePointLight ip, double f, double dT, IOptionStrikePair sInfo, StrikeType optionType, OptionPxMode optPxMode, double optSigma, bool returnPct, double pctRate) { if (optionType == StrikeType.Any) { throw new ArgumentException("Option type 'Any' is not supported.", "optionType"); } bool isCall = (optionType == StrikeType.Call); double optPx = FinMath.GetOptionPrice(f, sInfo.Strike, dT, optSigma, pctRate, isCall); // ReSharper disable once UseObjectOrCollectionInitializer SmileNodeInfo nodeInfo = new SmileNodeInfo(); nodeInfo.F = f; nodeInfo.dT = dT; nodeInfo.RiskFreeRate = pctRate; nodeInfo.Sigma = returnPct ? optSigma * Constants.PctMult : optSigma; nodeInfo.OptPx = optPx; nodeInfo.Strike = sInfo.Strike; nodeInfo.Security = (optionType == StrikeType.Put) ? sInfo.Put.Security : sInfo.Call.Security; nodeInfo.PxMode = optPxMode; nodeInfo.OptionType = optionType; nodeInfo.Pair = sInfo; //nodeInfo.ScriptTime = optTime; nodeInfo.CalendarTime = DateTime.Now; nodeInfo.Symbol = nodeInfo.Security.Symbol; nodeInfo.DSName = nodeInfo.Security.SecurityDescription.DSName; nodeInfo.FullName = nodeInfo.Security.SecurityDescription.FullName; nodeInfo.Expired = nodeInfo.Security.SecurityDescription.Expired; ip.Tag = nodeInfo; ip.Value = new Point(sInfo.Strike, nodeInfo.Sigma); if (ip is InteractivePointActive) { InteractivePointActive ipa = (InteractivePointActive)ip; ipa.Tooltip = String.Format("K:{0}; IV:{1:#0.00}%\r\n{2} {3} @ {4}", sInfo.Strike, optSigma * Constants.PctMult, optionType, optPx, DefaultOptQty); } }
protected static void FillNodeInfo(InteractivePointActive ip, double f, double dT, IOptionStrikePair sInfo, StrikeType optionType, OptionPxMode optPxMode, double optSigma, bool returnPct, bool isVisiblePoints) { if (optionType == StrikeType.Any) { throw new ArgumentException("Option type 'Any' is not supported.", "optionType"); } bool isCall = (optionType == StrikeType.Call); double optPx = FinMath.GetOptionPrice(f, sInfo.Strike, dT, optSigma, 0, isCall); SmileNodeInfo nodeInfo = new SmileNodeInfo(); nodeInfo.F = f; nodeInfo.dT = dT; nodeInfo.Sigma = returnPct ? optSigma * Constants.PctMult : optSigma; nodeInfo.OptPx = optPx; nodeInfo.Strike = sInfo.Strike; nodeInfo.Security = (optionType == StrikeType.Put) ? sInfo.Put.Security : sInfo.Call.Security; nodeInfo.PxMode = optPxMode; nodeInfo.OptionType = optionType; nodeInfo.Pair = sInfo; //nodeInfo.ScriptTime = optTime; nodeInfo.CalendarTime = DateTime.Now; nodeInfo.Symbol = nodeInfo.Security.Symbol; nodeInfo.Expired = nodeInfo.Security.SecurityDescription.Expired; ip.Tag = nodeInfo; ip.Color = AlphaColors.Yellow; ip.IsActive = isVisiblePoints; ip.Geometry = Geometries.Ellipse; ip.DragableMode = DragableMode.None; //ip.Value = new System.Windows.Point(sInfo.Strike, nodeInfo.Sigma); ip.Tooltip = String.Format("F:{0}; K:{1}; IV:{2:#0.00}%\r\n{3} {4} @ {5}", f, sInfo.Strike, optSigma * Constants.PctMult, optionType, optPx, DefaultOptQty); }
/// <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> /// Метод под флаг TemplateTypes.INTERACTIVESPLINE /// </summary> public InteractiveSeries Execute(InteractiveSeries smile, IOptionSeries optSer, InteractiveSeries quoteIv, 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; // 1. Формируем маркеры заявок List <InteractiveObject> controlPoints = new List <InteractiveObject>(); PositionsManager posMan = PositionsManager.GetManager(m_context); IList <PositionsManager.IvTargetInfo> ivTargets = posMan.GetIvTargets(m_isLong, true); 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 <= k); if ((ivTarget.SecInfo.StrikeType != null) && (ivTarget.SecInfo.StrikeType.Value != StrikeType.Any)) { isCall = (ivTarget.SecInfo.StrikeType.Value == StrikeType.Call); } StrikeType optionType = isCall ? StrikeType.Call : StrikeType.Put; Contract.Assert(pair.Tick < 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 += 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; } // Пересчитываем сигму обратно, ЕСЛИ мы применили сдвиг цены в абсолютном выражении if (ivTarget.EntryShiftPrice != 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; } } double totalQty; if (isCall) { totalQty = posMan.GetTotalQty(pair.Call.Security, m_context.BarsCount, TotalProfitAlgo.AllPositions, ivTarget.IsLong); } else { totalQty = posMan.GetTotalQty(pair.Put.Security, m_context.BarsCount, TotalProfitAlgo.AllPositions, ivTarget.IsLong); } double targetQty = Math.Abs(ivTarget.TargetShares) - totalQty; // ReSharper disable once UseObjectOrCollectionInitializer InteractivePointActive tmp = new InteractivePointActive(); // Попробуем по-простому? tmp.Tag = ivTarget; tmp.IsActive = true; tmp.ValueX = k; tmp.ValueY = sigma; tmp.DragableMode = DragableMode.None; if (ivTarget.EntryShiftPrice == 0) { tmp.Tooltip = String.Format(CultureInfo.InvariantCulture, " F: {0}\r\n K: {1}; IV: {2:P2}\r\n {3} px {4} rIV {5:P2} @ {6}", futPx, k, sigma, optionType, theorOptPxBitcoins, ivTarget.EntryIv, targetQty); } else { string shiftStr = (ivTarget.EntryShiftPrice > 0) ? "+" : "-"; shiftStr = shiftStr + Math.Abs(ivTarget.EntryShiftPrice) + "ps"; tmp.Tooltip = String.Format(CultureInfo.InvariantCulture, " F: {0}\r\n K: {1}; IV: {2:P2}\r\n {3} px {4} rIV {5:P2} {6} @ {7}", futPx, k, sigma, optionType, theorOptPxBitcoins, ivTarget.EntryIv, shiftStr, targetQty); } //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 InteractiveSeries res = new InteractiveSeries(); // Здесь так надо -- мы делаем новую улыбку res.ControlPoints = new ReadOnlyCollection <InteractiveObject>(controlPoints); if (controlPoints.Count > 0) { res.ClickEvent -= InteractiveSplineOnClickEvent; res.ClickEvent += InteractiveSplineOnClickEvent; m_clickableSeries = res; } return(res); }
/// <summary> /// Метод под флаг TemplateTypes.INTERACTIVESPLINE /// </summary> public InteractiveSeries Execute(InteractiveSeries quotes, InteractiveSeries smile, int barNum) { if ((quotes == null) || (smile == null)) { return(Constants.EmptySeries); } int barsCount = m_context.BarsCount; if (!m_context.IsLastBarUsed) { barsCount--; } if (barNum < barsCount - 1) { return(quotes); } SmileInfo oldInfo = smile.GetTag <SmileInfo>(); if ((oldInfo == null) || (oldInfo.ContinuousFunction == null)) { return(Constants.EmptySeries); } InteractiveSeries res = quotes; double futPx = oldInfo.F; double dT = oldInfo.dT; foreach (InteractiveObject obj in res.ControlPoints) { SmileNodeInfo nodeInfo = obj.Anchor.Tag as SmileNodeInfo; if (nodeInfo == null) { continue; } double realOptPx = nodeInfo.OptPx; bool isCall = (nodeInfo.OptionType == StrikeType.Call); double sigma = oldInfo.ContinuousFunction.Value(nodeInfo.Strike); if (Double.IsNaN(sigma) || (sigma < Double.Epsilon)) { //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; } double theorOptPx = FinMath.GetOptionPrice(futPx, nodeInfo.Strike, dT, sigma, oldInfo.RiskFreeRate, isCall); if (Double.IsNaN(theorOptPx) || (theorOptPx < Double.Epsilon)) { //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 (nodeInfo.PxMode == OptionPxMode.Ask) { double doLevel = theorOptPx - m_widthPx * nodeInfo.Pair.Tick; if (realOptPx <= doLevel) { var anchor = (InteractivePointActive)obj.Anchor; // ReSharper disable once UseObjectOrCollectionInitializer var tmp = new InteractivePointActive(); tmp.IsActive = true; tmp.Tag = anchor.Tag; tmp.Tooltip = null; // anchor.Tooltip; int decimals = (nodeInfo.Security != null) ? nodeInfo.Security.Decimals : 0; string pot = Math.Abs(theorOptPx - realOptPx).ToString("N" + decimals, CultureInfo.InvariantCulture); tmp.Label = anchor.Tooltip + " (" + pot + ")"; tmp.ValueX = anchor.ValueX; tmp.ValueY = anchor.ValueY + m_outlet; tmp.DragableMode = DragableMode.None; tmp.Size = m_outletSize; //tmp.Color = Colors.White; //tmp.Geometry = m_outletGeometry; // Geometries.Ellipse; obj.ControlPoint1 = tmp; } } if (nodeInfo.PxMode == OptionPxMode.Bid) { double upLevel = theorOptPx + m_widthPx * nodeInfo.Pair.Tick; if (realOptPx >= upLevel) { var anchor = (InteractivePointActive)obj.Anchor; // ReSharper disable once UseObjectOrCollectionInitializer var tmp = new InteractivePointActive(); tmp.IsActive = true; tmp.Tag = anchor.Tag; tmp.Tooltip = null; // anchor.Tooltip; int decimals = (nodeInfo.Security != null) ? nodeInfo.Security.Decimals : 0; string pot = Math.Abs(theorOptPx - realOptPx).ToString("N" + decimals, CultureInfo.InvariantCulture); tmp.Label = anchor.Tooltip + " (" + pot + ")"; tmp.ValueX = anchor.ValueX; tmp.ValueY = anchor.ValueY - m_outlet; tmp.DragableMode = DragableMode.None; tmp.Size = m_outletSize; //tmp.Color = Colors.White; //tmp.Geometry = m_outletGeometry; // Geometries.Ellipse; obj.ControlPoint1 = tmp; } } } res.ClickEvent -= InteractiveSplineOnClickEvent; res.ClickEvent += InteractiveSplineOnClickEvent; m_clickableSeries = res; return(res); }
/// <summary> /// Получить финансовые параметры опционной позиции (один опцион) /// </summary> /// <param name="positions">список закрытых и открытых позиций</param> /// <param name="curBar">номер рабочего бара</param> /// <param name="f">текущая цена БА</param> /// <param name="k">страйк</param> /// <param name="dT">время до экспирации</param> /// <param name="sigma">волатильность</param> /// <param name="r">процентная ставка</param> /// <param name="isCall">put-false; call-true</param> /// <param name="cash">денежные затраты на формирование позы (могут быть отрицательными)</param> /// <param name="pnl">текущая цена позиции</param> public static void GetOptPnl(IList <IPosition> positions, int curBar, double f, double k, double dT, double sigma, double r, bool isCall, double btcUsdInd, out CashPnlUsd cashPnlUsd, out CashPnlBtc cashPnlBtc) { if (positions.Count == 0) { cashPnlUsd = new CashPnlUsd(); cashPnlBtc = new CashPnlBtc(); return; } { var msg = $"Как получился отрицательный курс BTC/USD? btcUsdInd:{btcUsdInd}"; Contract.Assert(DoubleUtil.IsPositive(btcUsdInd), msg); if (!DoubleUtil.IsPositive(btcUsdInd)) { throw new ArgumentException(msg, nameof(btcUsdInd)); } } double pnlBtc = 0; double pnlUsd = 0; double cashBtc = 0; double cashUsd = 0; foreach (IPosition pos in positions) { int sign = pos.IsLong ? 1 : -1; double qty = Math.Abs(pos.Shares); // Знак "минус" стоит в честь того, что при покупке инструмента наличные средства уменьшаются double locCashBtcEntry = sign * pos.GetBalancePrice(curBar) * qty; cashBtc -= locCashBtcEntry; cashUsd -= locCashBtcEntry * btcUsdInd; double optPxUsd = FinMath.GetOptionPrice(f, k, dT, sigma, r, isCall); double locPnlUsdEntry = sign * optPxUsd * qty; pnlUsd += locPnlUsdEntry; pnlBtc += locPnlUsdEntry / btcUsdInd; // Учет комиссии (комиссия в битках по идее) cashBtc -= pos.EntryCommission; cashUsd -= pos.EntryCommission * btcUsdInd; if (!pos.IsActiveForBar(curBar)) { // Знак "ПЛЮС" стоит в честь того, что при ЗАКРЫТИИ ЛОНГА наличные средства УВЕЛИЧИВАЮТСЯ double locCashBtcExit = sign * pos.ExitPrice * qty; cashBtc += locCashBtcExit; cashUsd += locCashBtcExit * btcUsdInd; double locPnlUsdExit = sign * optPxUsd * qty; pnlUsd -= locPnlUsdExit; pnlBtc -= locPnlUsdExit / btcUsdInd; // Учет комиссии (комиссия в битках по идее) cashBtc -= pos.ExitCommission; cashUsd -= pos.ExitCommission * btcUsdInd; } } // End foreach (IPosition pos in positions) cashPnlUsd = new CashPnlUsd(cashUsd, pnlUsd); cashPnlBtc = new CashPnlBtc(cashBtc, pnlBtc); }
/// <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); }
/// <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); }
/// <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 InteractiveSeries Execute(double price, double time, InteractiveSeries smile, IOptionSeries optSer, double riskFreeRatePct, int barNum) { int barsCount = ContextBarsCount; if ((barNum < barsCount - 1) || (optSer == null)) { return(Constants.EmptySeries); } // В оптимизации ничего рисовать не надо if (Context.IsOptimization) { return(Constants.EmptySeries); } int lastBarIndex = optSer.UnderlyingAsset.Bars.Count - 1; DateTime now = optSer.UnderlyingAsset.Bars[Math.Min(barNum, lastBarIndex)].Date; bool wasInitialized = HandlerInitializedToday(now); double futPx = 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); m_context.Log(msg, MessageType.Error, true); return(Constants.EmptySeries); } if (!DoubleUtil.IsPositive(futPx)) { // [{0}] Base asset price must be positive value. F:{1} string msg = RM.GetStringFormat("OptHandlerMsg.FutPxMustBePositive", GetType().Name, futPx); m_context.Log(msg, MessageType.Error, true); return(Constants.EmptySeries); } 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, false); } return(Constants.EmptySeries); } 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, false); } return(Constants.EmptySeries); } if (!oldInfo.IsValidSmileParams) { string msg = String.Format("[{0}] SmileInfo must have valid smile params. IsValidSmileParams:{1}", GetType().Name, oldInfo.IsValidSmileParams); if (wasInitialized) { m_context.Log(msg, MessageType.Error, false); } return(Constants.EmptySeries); } double ivAtm; if (!oldInfo.ContinuousFunction.TryGetValue(futPx, out ivAtm)) { return(Constants.EmptySeries); } if (!DoubleUtil.IsPositive(ivAtm)) { // [{0}] ivAtm must be positive value. ivAtm:{1} string msg = RM.GetStringFormat("OptHandlerMsg.IvAtmMustBePositive", GetType().Name, ivAtm); if (wasInitialized) { m_context.Log(msg, MessageType.Error, true); } return(Constants.EmptySeries); } // TODO: Нужно ли писать отдельный код для лаборатории? Чтобы показывать позиции из симуляции? // if (!Context.Runtime.IsAgentMode) IOptionStrikePair[] pairs = optSer.GetStrikePairs().ToArray(); if (pairs.Length < 2) { string msg = String.Format("[{0}] optSer must contain few strike pairs. pairs.Length:{1}", GetType().Name, pairs.Length); if (wasInitialized) { m_context.Log(msg, MessageType.Warning, true); } return(Constants.EmptySeries); } double futStep = optSer.UnderlyingAsset.Tick; PositionsManager posMan = PositionsManager.GetManager(m_context); // Вытаскиваем ВСЕ позиции фьючерса ReadOnlyCollection <IPosition> basePositions = posMan.GetClosedOrActiveForBar(optSer.UnderlyingAsset); // Вытаскиваем ВСЕ позиции опционов var optPositions = SingleSeriesProfile.GetAllOptionPositions(posMan, pairs); // 1. Если в позиции вообще нет опционов -- сразу выходим. Эффективную волатильность построить нельзя. int posAmount = (from t in optPositions select(t.Item1.Count + t.Item2.Count)).Sum(); if (posAmount <= 0) { return(Constants.EmptySeries); } // 2. TODO: посчитать позиции без учета синтетических фьючерсов. Если позиция только из синтетики -- выходим. // 3. Вычисляем эффективную волатильность и заодно разбиваем позицию на длинные и короткие // Но это имеет смысл только если сразу рисовать позу!!! double effectiveLongIvAtm, effectiveShortIvAtm; Tuple <ReadOnlyCollection <IPosition>, ReadOnlyCollection <IPosition> >[] longPositions; Tuple <ReadOnlyCollection <IPosition>, ReadOnlyCollection <IPosition> >[] shortPositions; bool ok = posMan.TryEstimateEffectiveIv(oldInfo, optSer, lastBarIndex, out effectiveLongIvAtm, out effectiveShortIvAtm, out longPositions, out shortPositions); if (!ok) { // Мы не смогли завершить алгоритм, но получили какую-то оценку волатильностей. Нарисуем ее? if ((!DoubleUtil.IsPositive(effectiveLongIvAtm)) || (!DoubleUtil.IsPositive(effectiveShortIvAtm))) { return(Constants.EmptySeries); } } Contract.Assert(longPositions != null, "longPositions==null ???"); Contract.Assert(shortPositions != null, "shortPositions==null ???"); double actualIv = ShowLongPositions ? effectiveLongIvAtm : effectiveShortIvAtm; double displayValue = FixedValue.ConvertToDisplayUnits(m_valueMode, actualIv); m_displayIv.Value = displayValue; Contract.Assert(DoubleUtil.IsPositive(actualIv), $"Это вообще что-то странное. Почему плохой айви? actualIv:{actualIv}"); // Это вообще что-то странное. Как так? if (!DoubleUtil.IsPositive(actualIv)) { return(Constants.EmptySeries); } // 5. Подготавливаю улыбку (достаточно функции, без обвязки) var lowSmileFunc = new SmileFunctionExtended( SmileFunction5.TemplateFuncRiz4Nov1, actualIv, oldInfo.SkewAtm, oldInfo.Shape, futPx, dT); // 7. Подготавливаем графическое отображение позиции. Причем нам даже сплайн не нужен. var actualPositions = ShowLongPositions ? longPositions : shortPositions; List <InteractiveObject> controlPoints = new List <InteractiveObject>(); for (int j = 0; j < pairs.Length; j++) { var pair = pairs[j]; var tuple = actualPositions[j]; // На данном страйке позиций нет? Идем дальше. if ((tuple.Item1.Count <= 0) && (tuple.Item2.Count <= 0)) { continue; } double sigma; if ((!lowSmileFunc.TryGetValue(pair.Strike, out sigma)) || (!DoubleUtil.IsPositive(sigma))) { // TODO: Это очень странно. Вывести в лог? Проигнорировать страйк? sigma = actualIv; } var putPositions = tuple.Item1; var callPositions = tuple.Item2; double putQty = PositionsManager.GetTotalQty(putPositions); double callQty = PositionsManager.GetTotalQty(callPositions); int decimals = optSer.UnderlyingAsset.Decimals + 1; double putPx = FinMath.GetOptionPrice(futPx, pair.Strike, dT, sigma, riskFreeRatePct, false); double callPx = FinMath.GetOptionPrice(futPx, pair.Strike, dT, sigma, riskFreeRatePct, true); string putPxStr = putPx.ToString("N" + decimals, CultureInfo.InvariantCulture); string callPxStr = callPx.ToString("N" + decimals, CultureInfo.InvariantCulture); // ReSharper disable once UseObjectOrCollectionInitializer InteractivePointActive ip = new InteractivePointActive(); // TODO: вывести в тултип дополнительные подробности о составе позиции на этом страйке ip.Tooltip = String.Format(CultureInfo.InvariantCulture, " K: {0}; IV: {1:#0.00}%\r\n PutQty: {2}; CallQty: {3}\r\n PutPx: {4}; CallPx: {5}\r\n Total: {6}", pair.Strike, sigma * Constants.PctMult, putQty, callQty, putPxStr, callPxStr, putQty + callQty); ip.Value = new Point(pair.Strike, sigma); controlPoints.Add(new InteractiveObject(ip)); } // ReSharper disable once UseObjectOrCollectionInitializer InteractiveSeries res = new InteractiveSeries(); // Здесь так надо -- мы делаем новую улыбку res.ControlPoints = new ReadOnlyCollection <InteractiveObject>(controlPoints); SetHandlerInitialized(now, true); return(res); }
/// <summary> /// Метод под флаг TemplateTypes.INTERACTIVESPLINE /// </summary> public InteractiveSeries Execute(InteractiveSeries smile, IOptionSeries optSer, 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; IOptionStrikePair[] pairs = (from strike in optSer.GetStrikePairs() //orderby strike.Strike ascending -- уже отсортировано select strike).ToArray(); List <InteractiveObject> controlPoints = new List <InteractiveObject>(); for (int j = 0; j < pairs.Length; j++) { var pair = pairs[j]; double sigma = oldInfo.ContinuousFunction.Value(pair.Strike) + m_shiftIv; if (Double.IsNaN(sigma) || (sigma < Double.Epsilon)) { //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); double theorOptPx = FinMath.GetOptionPrice(futPx, pair.Strike, dT, sigma, oldInfo.RiskFreeRate, isCall); theorOptPx = Math.Round(theorOptPx / pair.Tick) * pair.Tick; if (Double.IsNaN(theorOptPx) || (theorOptPx < Double.Epsilon)) { //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; } // ReSharper disable once UseObjectOrCollectionInitializer InteractivePointActive tmp = new InteractivePointActive(); tmp.IsActive = true; //tmp.Tag = anchor.Tag; tmp.Tooltip = null; // anchor.Tooltip; //tmp.Label = anchor.Tooltip; tmp.ValueX = pair.Strike; tmp.ValueY = sigma; tmp.DragableMode = DragableMode.Yonly; //tmp.Size = m_outletSize; //tmp.Color = Colors.White; //tmp.Geometry = m_outletGeometry; // Geometries.Ellipse; InteractiveObject obj = new InteractiveObject(); obj.Anchor = tmp; controlPoints.Add(obj); } // Это надо ОБЯЗАТЕЛЬНО делать ПОСЛЕ восстановления виртуальных позиций #region Process clicks //{ // var onClickEvents = m_context.LoadObject(VariableId + "OnClickEvent") as List<InteractiveActionEventArgs>; // if (onClickEvents == null) // { // onClickEvents = new List<InteractiveActionEventArgs>(); // m_context.StoreObject(VariableId + "OnClickEvent", onClickEvents); // } // if (onClickEvents.Count > 0) // { // RouteOnClickEvents(onClickEvents); // } //} #endregion Process clicks // ReSharper disable once UseObjectOrCollectionInitializer InteractiveSeries res = new InteractiveSeries(); // Здесь так надо -- мы делаем новую улыбку res.ControlPoints = new ReadOnlyCollection <InteractiveObject>(controlPoints); //res.ClickEvent -= InteractiveSplineOnClickEvent; //res.ClickEvent += InteractiveSplineOnClickEvent; res.EndDragEvent -= res_EndDragEvent; res.EndDragEvent += res_EndDragEvent; m_clickableSeries = res; return(res); }