private IList <double> ExecuteAll(ISecurity sec, IOptionSeries optSer) { if (sec == null) { return(Constants.EmptyListDouble); } IList <double> basePrices = CommonStreamExecute(m_variableId + "_basePrices", m_variableId + "_basePriceHistory", sec, m_repeatLastPx, true, false, new object[] { sec, optSer }); if (basePrices.Count > 0) { double px = basePrices[basePrices.Count - 1]; double displayValue = FixedValue.ConvertToDisplayUnits(m_valueMode, px); m_displayPrice.Value = displayValue; } return(new ReadOnlyCollection <double>(basePrices)); }
/// <summary> /// Обработчик под тип входных данных OPTION /// </summary> public double Execute(IOption opt, int barNum) { if ((opt == null) || (opt.UnderlyingAsset == null)) { return(Double.NaN); // В данном случае намеренно возвращаю Double.NaN } int len = m_context.BarsCount; if (len <= 0) { return(Double.NaN); // В данном случае намеренно возвращаю Double.NaN } if (len <= barNum) { string msg = String.Format("[{0}:{1}] (BarsCount <= barNum)! BarsCount:{2}; barNum:{3}", m_context.Runtime.TradeName, GetType().Name, m_context.BarsCount, barNum); m_context.Log(msg, MessageType.Warning, true); barNum = len - 1; } DateTime now = opt.UnderlyingAsset.Bars[barNum].Date; double risk = CommonExecute(m_variableId + "_RiskN2", now, true, true, false, barNum, new object[] { opt }); //// [2015-07-15] Отключаю вывод отладочных сообщений в лог агента. //if (barNum >= 0.9 * len) //{ // string msg = String.Format("[{0}:{1}] barNum:{2}; risk:{3}; now:{4}", // m_context.Runtime.TradeName, GetType().Name, barNum, risk, now.ToString("dd-MM-yyyy HH:mm:ss.fff")); // m_context.Log(msg, MessageType.Info, false); //} // Просто заполнение свойства для отображения на UI int barsCount = ContextBarsCount; if (barNum >= barsCount - 1) { double displayValue = FixedValue.ConvertToDisplayUnits(m_valueMode, risk); m_displayRisk.Value = displayValue; } return(risk); }
/// <summary> /// Обработчик под тип входных данных OPTION_SERIES /// </summary> public IList <double> Execute(IOptionSeries optSer) { if (optSer == null) { return(Constants.EmptyListDouble); } ISecurity sec = optSer.UnderlyingAsset; int len = sec.Bars.Count; if (len <= 0) { return(Constants.EmptyListDouble); } IOptionStrikePair[] pairs = optSer.GetStrikePairs().ToArray(); if ((!Double.IsNaN(m_strikeStep)) && (m_strikeStep > Double.Epsilon)) { // Выделяем страйки, которые нацело делятся на StrikeStep var tmp = (from p in pairs let test = m_strikeStep * Math.Round(p.Strike / m_strikeStep) where DoubleUtil.AreClose(p.Strike, test) select p).ToArray(); if (tmp.Length > 1) { // Нормальная ситуация pairs = tmp; } // [02-06-2016] PROD-2812 - Защита от ошибок при указании шага страйков // В противном случае буду показывать все страйки (уже лежат в pairs). // Это хотя бы позволит Пользователю продолжить работу. } if (pairs.Length < 2) { return(Constants.EmptyListDouble); } double[] res = Context?.GetArray <double>(len) ?? new double[len]; var history = LocalHistory; double prevK = Double.NaN; for (int m = 0; m < len; m++) { DateTime now = sec.Bars[m].Date; double ck; if (history.TryGetValue(now, out ck)) { prevK = ck; res[m] = prevK; } else { double futPx = sec.Bars[m].Close; // 0. Валидация диапазона IOptionStrikePair left = pairs[0]; IOptionStrikePair right = pairs[pairs.Length - 1]; if ((futPx <= left.Strike + Double.Epsilon) || (right.Strike - Double.Epsilon <= futPx)) { res[m] = Double.IsNaN(prevK) ? Constants.NaN : prevK; continue; } // 1. Пробуем проверить середину серии в качестве кандидата на левую границу int li = pairs.Length / 2; if (pairs[li].Strike < futPx) { left = pairs[li]; } else { li = 0; } // 2. Ищем правый страйк double ratio = Double.NaN; int leftIndex = Int32.MinValue, rightIndex = Int32.MaxValue; for (int j = li; j < pairs.Length - 1; j++) { if ((pairs[j].Strike - Double.Epsilon <= futPx) && (futPx < pairs[j + 1].Strike)) { left = pairs[j]; right = pairs[j + 1]; leftIndex = Math.Max(0, j + m_shiftStrike); leftIndex = Math.Min(leftIndex, pairs.Length - 2); rightIndex = Math.Max(1, j + 1 + m_shiftStrike); rightIndex = Math.Min(rightIndex, pairs.Length - 1); ratio = (futPx - left.Strike) / (right.Strike - left.Strike); break; } } if (ratio <= (1.0 - m_switchRatio)) { prevK = pairs[leftIndex].Strike; // left.Strike; res[m] = prevK; history[now] = prevK; } else if (m_switchRatio <= ratio) { prevK = pairs[rightIndex].Strike; // right.Strike; res[m] = prevK; history[now] = prevK; } else { if (Double.IsNaN(prevK) || (prevK <= 0)) { try { prevK = pairs[rightIndex].Strike; // right.Strike; } catch (IndexOutOfRangeException ioex) { string msg = String.Format(CultureInfo.InvariantCulture, "{0} when trying to get StrikePair. rightIndex:{1}; pairs.Length:{2}; leftIndex:{3}; li:{4}; ratio:{5}; prevK:{6}; futPx:{7}; ticker:{8}", ioex.GetType().FullName, rightIndex, pairs.Length, leftIndex, li, ratio, prevK, futPx, optSer.UnderlyingAsset.Symbol); m_context.Log(msg, MessageType.Error, true); // Здесь ПОКА оставляю выброс исключения, чтобы ситуация с самозакрытием окон агента воспроизводилась. throw; } res[m] = prevK; history[now] = prevK; } else { res[m] = prevK; // Надо ли здесь обновить history или это бессмысленно и расточительно??? } } } //// "For some strange reason I didn't fill value at index m:" + m); //if (DoubleUtil.IsZero(res[m])) // m_context.Log("[DEBUG:CentralStrike] For some strange reason I didn't fill value at index m:" + m, MessageType.Error, true); } double displayValue = FixedValue.ConvertToDisplayUnits(m_valueMode, res[len - 1]); m_displayPrice.Value = displayValue; return(res); }
/// <summary> /// Метод под флаг TemplateTypes.SECURITY, чтобы подключаться к источнику-БА /// </summary> public double Execute(ISecurity sec, int barNum) { double failRes = Constants.NaN; if (m_repeatLastPx) { failRes = Double.IsNaN(m_prevPx) ? Constants.NaN : m_prevPx; } Dictionary <DateTime, double> basePrices; #region Get cache { string cashKey = VariableId + "_basePrices"; basePrices = Context.LoadObject(cashKey) as Dictionary <DateTime, double>; if (basePrices == null) { basePrices = new Dictionary <DateTime, double>(); Context.StoreObject(cashKey, basePrices); } } #endregion Get cache if (sec == null) { return(failRes); } int len = sec.Bars.Count; if (len <= 0) { return(failRes); } double px; DateTime lastBarDate = sec.Bars[barNum].Date; if ((basePrices.TryGetValue(lastBarDate, out px)) && DoubleUtil.IsPositive(px)) { m_prevPx = px; // Раз мы нашли валидную цену в архиве, то можно обновить failRes failRes = px; } // Цену в архиве нашли, теперь надо проверить свежие сведения. { int barsCount = ContextBarsCount; if (barNum < barsCount - 1) { // Если история содержит осмысленное значение, то оно уже содержится в failRes return(failRes); } else { #region Process last bar(s) #region switch(m_pxMode) switch (m_pxMode) { case BasePxMode.FixedPx: px = m_fixedPx; m_prevPx = px; break; case BasePxMode.LastTrade: if (sec.FinInfo.LastPrice.HasValue) { px = sec.FinInfo.LastPrice.Value; m_prevPx = px; } else { px = failRes; } break; case BasePxMode.BidAskMidPoint: { FinInfo info = sec.FinInfo; if (info.Ask.HasValue && info.Bid.HasValue && info.AskSize.HasValue && info.BidSize.HasValue && (info.AskSize.Value > 0) && (info.BidSize.Value > 0)) { px = (info.Ask.Value + info.Bid.Value) / 2; m_prevPx = px; } else if (info.Ask.HasValue && info.AskSize.HasValue && (info.AskSize.Value > 0)) { px = info.Ask.Value; m_prevPx = px; } else if (info.Bid.HasValue && info.BidSize.HasValue && (info.BidSize.Value > 0)) { px = info.Bid.Value; m_prevPx = px; } else { // Приемлемо ли такое решение? if (info.LastPrice.HasValue) { px = info.LastPrice.Value; m_prevPx = px; } else { px = failRes; } } } break; default: throw new NotImplementedException("pxMode:" + m_pxMode); } #endregion switch(m_pxMode) basePrices[lastBarDate] = px; double displayValue = FixedValue.ConvertToDisplayUnits(m_valueMode, px); m_displayPrice.Value = displayValue; return(px); #endregion Process last bar(s) } } }
/// <summary> /// Метод под флаг TemplateTypes.OPTION_SERIES, чтобы подключаться к источнику-серии /// </summary> public double Execute(IOptionSeries optSer, int barNum) { double failRes = Constants.NaN; if (m_repeatLastPx) { failRes = Double.IsNaN(m_prevPx) ? Constants.NaN : m_prevPx; } if (optSer == null) { return(failRes); } switch (m_pxMode) { case BasePxMode.TheorPxBased: { Dictionary <DateTime, double> basePrices; #region Get cache { string cashKey = VariableId + "_basePrices"; basePrices = Context.LoadObject(cashKey) as Dictionary <DateTime, double>; if (basePrices == null) { basePrices = new Dictionary <DateTime, double>(); Context.StoreObject(cashKey, basePrices); } } #endregion Get cache ISecurity sec = optSer.UnderlyingAsset; int len = sec.Bars.Count; if (len <= 0) { return(failRes); } double px; DateTime lastBarDate = sec.Bars[barNum].Date; if ((basePrices.TryGetValue(lastBarDate, out px)) && DoubleUtil.IsPositive(px)) { m_prevPx = px; // Раз мы нашли валидную цену в архиве, то можно обновить failRes failRes = px; } // Цену в архиве нашли, теперь надо проверить свежие сведения. { int barsCount = ContextBarsCount; if (barNum < barsCount - 1) { // Если история содержит осмысленное значение, то оно уже содержится в failRes return(failRes); } else { #region Process last bar(s) IOptionStrikePair[] pairs = optSer.GetStrikePairs().ToArray(); IOptionStrikePair pair = pairs[pairs.Length / 2]; if ((pair.PutFinInfo.TheoreticalPrice == null) || (pair.CallFinInfo.TheoreticalPrice == null)) { return(failRes); } double putPx = pair.PutFinInfo.TheoreticalPrice.Value; double callPx = pair.CallFinInfo.TheoreticalPrice.Value; px = callPx - putPx + pair.Strike; m_prevPx = px; basePrices[lastBarDate] = px; double displayValue = FixedValue.ConvertToDisplayUnits(m_valueMode, px); m_displayPrice.Value = displayValue; return(px); #endregion Process last bar(s) } } } default: return(Execute(optSer.UnderlyingAsset, barNum)); } }
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> /// Обработчик под тип входных данных OPTION_SERIES /// </summary> public InteractiveSeries Execute(double price, double trueTimeToExpiry, IOptionSeries optSer, double ratePct, int barNum) { int barsCount = ContextBarsCount; if ((barNum < barsCount - 1) || (optSer == null)) { return(Constants.EmptySeries); } // PROD-5952 - Не надо дергать стакан без нужды //optSer.UnderlyingAsset.UpdateQueueData(); FinInfo bSecFinInfo = optSer.UnderlyingAsset.FinInfo; if (bSecFinInfo.LastPrice == null) { return(Constants.EmptySeries); } double futPx = price; // ФОРТС использует плоское календарное время DateTime optExpiry = optSer.ExpirationDate.Date.Add(m_expiryTime); double dT = (optExpiry - bSecFinInfo.LastUpdate).TotalYears(); if (Double.IsNaN(dT) || (dT < Double.Epsilon)) { // [{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 (Double.IsNaN(futPx) || (futPx < Double.Epsilon)) { // [{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 (Double.IsNaN(trueTimeToExpiry) || (trueTimeToExpiry < Double.Epsilon)) { string msg = String.Format("[{0}] trueTimeToExpiry must be positive value. dT:{1}", GetType().Name, trueTimeToExpiry); m_context.Log(msg, MessageType.Error, true); return(Constants.EmptySeries); } if (Double.IsNaN(ratePct)) { //throw new ScriptException("Argument 'ratePct' contains NaN for some strange reason. rate:" + rate); return(Constants.EmptySeries); } double effectiveTime = m_rescaleTime ? trueTimeToExpiry : dT; List <double> xs = new List <double>(); List <double> ys = new List <double>(); IOptionStrikePair[] pairs = (from pair in optSer.GetStrikePairs() //orderby pair.Strike ascending -- уже отсортировано! select pair).ToArray(); if (pairs.Length < 2) { string msg = String.Format("[{0}] optSer must contain few strike pairs. pairs.Length:{1}", GetType().Name, pairs.Length); m_context.Log(msg, MessageType.Warning, true); return(Constants.EmptySeries); } for (int j = 0; j < pairs.Length; j++) { //bool showPoint = true; IOptionStrikePair sInfo = pairs[j]; double k = sInfo.Strike; //// Сверхдалекие страйки игнорируем //if ((k < m_minStrike) || (m_maxStrike < k)) //{ // showPoint = false; //} if ((sInfo.PutFinInfo == null) || (sInfo.CallFinInfo == null) || (!sInfo.PutFinInfo.TheoreticalPrice.HasValue) || (!sInfo.PutFinInfo.Volatility.HasValue) || (sInfo.PutFinInfo.TheoreticalPrice.Value <= 0) || (sInfo.PutFinInfo.Volatility.Value <= 0) || (!sInfo.CallFinInfo.TheoreticalPrice.HasValue) || (!sInfo.CallFinInfo.Volatility.HasValue) || (sInfo.CallFinInfo.TheoreticalPrice.Value <= 0) || (sInfo.CallFinInfo.Volatility.Value <= 0)) { continue; } // Биржа шлет несогласованную улыбку //double virtualExchangeF = sInfo.CallFinInfo.TheoreticalPrice.Value - sInfo.PutFinInfo.TheoreticalPrice.Value + sInfo.Strike; if ((m_optionType == StrikeType.Any) || (m_optionType == StrikeType.Put)) { double optSigma = sInfo.PutFinInfo.Volatility.Value; if (m_rescaleTime) { // optSigma = FinMath.GetOptionSigma(futPx, k, effectiveTime, optPx, 0, false); optSigma = FinMath.RescaleIvToAnotherTime(dT, optSigma, trueTimeToExpiry); } double optPx = sInfo.PutFinInfo.TheoreticalPrice ?? Double.NaN; //// ReSharper disable once UseObjectOrCollectionInitializer //InteractivePointActive ip = new InteractivePointActive(k, optSigma); //ip.IsActive = m_showNodes; //ip.Geometry = Geometries.Ellipse; //ip.Color = System.Windows.Media.Colors.Cyan; //ip.Tooltip = String.Format("K:{0}; IV:{1:0.00}; Px:{2}\r\ndT:{3:0.000}; Date:{4}", // k, Constants.PctMult * optSigma, optPx, // FixedValue.ConvertToDisplayUnits(FixedValueMode.YearsAsDays, effectiveTime), // bSecFinInfo.LastUpdate.ToString(DateTimeFormatWithMs, CultureInfo.InvariantCulture)); if (optSigma > 0) { // Это условие позволяет не вставлять точки с совпадающими абсциссами if ((xs.Count <= 0) || (!DoubleUtil.AreClose(k, xs[xs.Count - 1]))) { xs.Add(k); ys.Add(optSigma); } } } if ((m_optionType == StrikeType.Any) || (m_optionType == StrikeType.Call)) { double optSigma = sInfo.CallFinInfo.Volatility.Value; if (m_rescaleTime) { // optSigma = FinMath.GetOptionSigma(futPx, k, effectiveTime, optPx, 0, false); optSigma = FinMath.RescaleIvToAnotherTime(dT, optSigma, trueTimeToExpiry); } double optPx = sInfo.CallFinInfo.TheoreticalPrice ?? Double.NaN; //// ReSharper disable once UseObjectOrCollectionInitializer //InteractivePointActive ip = new InteractivePointActive(k, optSigma); //ip.IsActive = m_showNodes; //ip.Geometry = Geometries.Ellipse; //ip.Color = System.Windows.Media.Colors.Cyan; //ip.Tooltip = String.Format("K:{0}; IV:{1:0.00}; Px:{2}\r\ndT:{3:0.000}; Date:{4}", // k, Constants.PctMult * optSigma, optPx, // FixedValue.ConvertToDisplayUnits(FixedValueMode.YearsAsDays, effectiveTime), // bSecFinInfo.LastUpdate.ToString(DateTimeFormatWithMs, CultureInfo.InvariantCulture)); if (optSigma > 0) { // Это условие позволяет не вставлять точки с совпадающими абсциссами if ((xs.Count <= 0) || (!DoubleUtil.AreClose(k, xs[xs.Count - 1]))) { xs.Add(k); ys.Add(optSigma); } } } } #region 3. Строим сплайн по биржевой улыбке с узлами в страйках NotAKnotCubicSpline spline = null, splineD1 = null; try { if (xs.Count >= BaseCubicSpline.MinNumberOfNodes) { spline = new NotAKnotCubicSpline(xs, ys); splineD1 = spline.DeriveD1(); } else { return(Constants.EmptySeries); } } catch (Exception ex) { string msg = String.Format("bSecFinInfo.LastUpdate:{0}; Bars.Last.Date:{1}\r\n\r\nException:{2}", bSecFinInfo.LastUpdate, optSer.UnderlyingAsset.Bars[optSer.UnderlyingAsset.Bars.Count - 1].Date, ex); m_context.Log(msg, MessageType.Error, true); return(Constants.EmptySeries); } #endregion 3. Строим сплайн по биржевой улыбке с узлами в страйках #region 5. С помощью сплайна уже можно строить гладкую улыбку double futStep = optSer.UnderlyingAsset.Tick; double dK = pairs[1].Strike - pairs[0].Strike; SortedDictionary <double, IOptionStrikePair> strikePrices; if (!SmileImitation5.TryPrepareImportantPoints(pairs, futPx, futStep, -1, out strikePrices)) { string msg = String.Format("[WARNING:{0}] It looks like there is no suitable points for the smile. pairs.Length:{1}", GetType().Name, pairs.Length); m_context.Log(msg, MessageType.Warning, true); return(Constants.EmptySeries); } List <InteractiveObject> controlPoints = new List <InteractiveObject>(); //for (int j = 0; j < pairs.Length; j++) foreach (var kvp in strikePrices) { bool showPoint = (kvp.Value != null); double k = kvp.Key; double sigma; if (!spline.TryGetValue(k, out sigma)) { continue; } double vol = sigma; if (Double.IsNaN(sigma) || Double.IsInfinity(sigma) || (sigma < Double.Epsilon)) { continue; } InteractivePointLight ip; if (m_showNodes && showPoint) { // Как правило, эта ветка вообще не используется, // потому что я не смотрю узлы биржевой улыбки. double optPx = Double.NaN; // ReSharper disable once UseObjectOrCollectionInitializer InteractivePointActive tip = new InteractivePointActive(k, vol); tip.IsActive = m_showNodes && showPoint; tip.Geometry = Geometries.Ellipse; tip.Color = AlphaColors.Cyan; tip.Tooltip = String.Format("K:{0}; IV:{1:P2}; Px:{2}\r\ndT:{3:0.000}; Date:{4}", k, vol, optPx, FixedValue.ConvertToDisplayUnits(FixedValueMode.YearsAsDays, effectiveTime), bSecFinInfo.LastUpdate.ToString(DateTimeFormatWithMs, CultureInfo.InvariantCulture)); ip = tip; } else { ip = new InteractivePointLight(k, vol); } InteractiveObject obj = new InteractiveObject(ip); controlPoints.Add(obj); } #endregion 5. С помощью сплайна уже можно строить гладкую улыбку // ReSharper disable once UseObjectOrCollectionInitializer InteractiveSeries res = new InteractiveSeries(); // Здесь так надо -- мы делаем новую улыбку res.ControlPoints = new ReadOnlyCollection <InteractiveObject>(controlPoints); var baseSec = optSer.UnderlyingAsset; DateTime scriptTime = baseSec.Bars[baseSec.Bars.Count - 1].Date; // ReSharper disable once UseObjectOrCollectionInitializer SmileInfo info = new SmileInfo(); info.F = futPx; info.dT = effectiveTime; info.Expiry = optSer.ExpirationDate; info.ScriptTime = scriptTime; info.RiskFreeRate = ratePct; info.BaseTicker = baseSec.Symbol; info.ContinuousFunction = spline; info.ContinuousFunctionD1 = splineD1; res.Tag = info; return(res); }
public double Execute(double price, double time, InteractiveSeries smile, IOptionSeries optSer, double riskFreeRatePct, int barNum) { int barsCount = ContextBarsCount; if ((barNum < barsCount - 1) || (optSer == null)) { return(Constants.NaN); } // В оптимизации ничего рисовать не надо if (Context.IsOptimization) { 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 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.NaN); } 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.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, false); } 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, false); } return(Constants.NaN); } 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.NaN); } double ivAtm; if (!oldInfo.ContinuousFunction.TryGetValue(futPx, out ivAtm)) { return(Constants.NaN); } 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.NaN); } // 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.NaN); } 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.NaN); } // 3. Вычисляем эффективную волатильность и заодно разбиваем позицию на длинные и короткие // Но это имеет смысл только если сразу рисовать позу!!! double effectiveLongIvAtm, effectiveShortIvAtm; bool ok = posMan.TryEstimateEffectiveIv(oldInfo, optSer, lastBarIndex, out effectiveLongIvAtm, out effectiveShortIvAtm); if (!ok) { return(Constants.NaN); } double res; if (ShowLongPositions) { res = effectiveLongIvAtm; } else { res = effectiveShortIvAtm; } double disp = FixedValue.ConvertToDisplayUnits(m_valueMode, res); m_displayIv.Value = disp; SetHandlerInitialized(now, true); return(res); }