internal static bool TryEstimateSpeed(PositionsManager posMan, IOptionSeries optSer, IOptionStrikePair[] pairs, InteractiveSeries smile, NumericalGreekAlgo greekAlgo, double f, double dF, double timeToExpiry, out double rawSpeed) { rawSpeed = Double.NaN; if (timeToExpiry < Double.Epsilon) { throw new ArgumentOutOfRangeException("timeToExpiry", "timeToExpiry must be above zero. timeToExpiry:" + timeToExpiry); } double gamma1, gamma2; bool ok1 = SingleSeriesNumericalGamma.TryEstimateGamma(posMan, optSer, pairs, smile, greekAlgo, f - dF, dF, timeToExpiry, out gamma1); if (!ok1) { return(false); } bool ok2 = SingleSeriesNumericalGamma.TryEstimateGamma(posMan, optSer, pairs, smile, greekAlgo, f + dF, dF, timeToExpiry, out gamma2); if (!ok2) { return(false); } rawSpeed = (gamma2 - gamma1) / 2.0 / dF; return(true); }
/// <summary> /// Метод под флаг TemplateTypes.OPTION_SERIES, чтобы подключаться к источнику-серии /// </summary> public double Execute(double entryPermission, double strike, double risk, double maxRisk, InteractiveSeries smile, IOptionSeries optSer, int barNum) { double res = Process(entryPermission, strike, risk, maxRisk, smile, optSer, 0, 0, barNum); return(res); }
internal static bool TryEstimateGamma(double putQty, double callQty, IOptionSeries optSer, IOptionStrikePair pair, InteractiveSeries smile, NumericalGreekAlgo greekAlgo, double f, double dF, double timeToExpiry, double riskFreeRate, out double rawGamma) { rawGamma = Double.NaN; if (timeToExpiry < Double.Epsilon) { throw new ArgumentOutOfRangeException("timeToExpiry", "timeToExpiry must be above zero. timeToExpiry:" + timeToExpiry); } double delta1, delta2; bool ok1 = OptionsBoardNumericalDelta.TryEstimateDelta(putQty, callQty, optSer, pair, smile, greekAlgo, f - dF, dF, timeToExpiry, riskFreeRate, out delta1); if (!ok1) { return(false); } bool ok2 = OptionsBoardNumericalDelta.TryEstimateDelta(putQty, callQty, optSer, pair, smile, greekAlgo, f + dF, dF, timeToExpiry, riskFreeRate, out delta2); if (!ok2) { return(false); } rawGamma = (delta2 - delta1) / 2.0 / dF; return(true); }
/// <summary> /// Обработчик для двух улыбок /// </summary> public InteractiveSeries Execute(InteractiveSeries marketSmile, InteractiveSeries modelSmile, int barNum) { InteractiveSeries res = SelectSmile(m_smileIndex, marketSmile, modelSmile); return(res); }
public InteractiveSeries Execute(IList <double> xValues, IList <double> yValues) { if ((xValues == null) || (xValues.Count <= 0) || (yValues == null) || (yValues.Count <= 0)) { string msg = String.Format("[{0}] Null or empty data series.", GetType().Name); Context.Log(msg, MessageType.Error, false); return(Constants.EmptySeries); } if (xValues.Count != yValues.Count) { string msg = String.Format("[{0}] Data series have different length. X.Len:{1}; Y.Len:{2}", GetType().Name, xValues.Count, yValues.Count); Context.Log(msg, MessageType.Warning, false); //return Constants.EmptySeries; } int len = Math.Min(xValues.Count, yValues.Count); List <InteractiveObject> controlPoints = new List <InteractiveObject>(); for (int j = 0; j < len; j++) { InteractivePointLight ip = new InteractivePointLight(); ip.Value = new Point(xValues[j], yValues[j]); //ip.Tooltip = String.Format("F:{0}; D:{1}", f, yStr); controlPoints.Add(new InteractiveObject(ip)); } // ReSharper disable once UseObjectOrCollectionInitializer InteractiveSeries res = new InteractiveSeries(); // Здесь так надо -- мы делаем новую улыбку res.ControlPoints = new ReadOnlyCollection <InteractiveObject>(controlPoints); return(res); }
internal static bool TryEstimatePrice(double putQty, double callQty, IOptionSeries optSer, IOptionStrikePair pair, InteractiveSeries smile, double f, double timeToExpiry, double riskFreeRate, out double rawPrice) { rawPrice = Double.NaN; if (timeToExpiry < Double.Epsilon) { throw new ArgumentOutOfRangeException("timeToExpiry", "timeToExpiry must be above zero. timeToExpiry:" + timeToExpiry); } double pnl1 = 0; // Флаг того, что ПНЛ по всем инструментам был расчитан верно bool pnlIsCorrect1 = true; { double pairPnl; pnlIsCorrect1 &= SingleSeriesProfile.TryGetPairPrice( putQty, callQty, smile, pair, f, timeToExpiry, riskFreeRate, out pairPnl); pnl1 += pairPnl; } if (pnlIsCorrect1) { //rawPrice = (cash1 + pnl1); // В моих терминах "Цена одного опциона" будет даваться величиной pnl1 rawPrice = pnl1; return(true); } else { return(false); } }
public InteractiveSeries Execute(double xVal, double yVal, int barNum) { // 1. Если локальный накопитель еще не проинициализирован -- делаем это if (m_controlPoints == null) { m_controlPoints = new List <InteractiveObject>(); } // 3. Добавляем новую точку в локальный накопитель InteractivePointLight ip = new InteractivePointLight(); ip.Value = new Point(xVal, yVal); //ip.Tooltip = String.Format("F:{0}; D:{1}", f, yStr); m_controlPoints.Add(new InteractiveObject(ip)); // 5. Если мы еще не добрались до правого края графика -- возвращаем пустую серию int barsCount = ContextBarsCount; if (barNum < barsCount - 1) { return(Constants.EmptySeries); } // 7. На правом краю графика возвращаем подготовленную серию точек // ReSharper disable once UseObjectOrCollectionInitializer InteractiveSeries res = new InteractiveSeries(); // Здесь так надо -- мы делаем новую улыбку res.ControlPoints = new ReadOnlyCollection <InteractiveObject>(m_controlPoints); return(res); }
/// <summary> /// Метод под флаг TemplateTypes.OPTION_SERIES, чтобы подключаться к источнику-серии /// </summary> public double Execute(bool entryPermission, double strike, double risk, double maxRisk, InteractiveSeries smile, IOptionSeries optSer, InteractiveSeries callDelta, int barNum) { double permission = entryPermission ? 1 : -1; double res = Process(permission, strike, risk, maxRisk, smile, optSer, callDelta, 0, 0, barNum); return(res); }
/// <summary> /// Тета будет иметь размерность 'пункты за год'. /// Обычно же опционщики любят смотреть размерность 'пункты за день'. /// Поэтому полученное сырое значение ещё надо делить на количество дней в году. /// (Эквивалентно умножению на интересующий набег времени для получения дифференциала). /// </summary> internal static bool TryEstimateTheta(double putQty, double callQty, IOptionSeries optSer, IOptionStrikePair pair, InteractiveSeries smile, NumericalGreekAlgo greekAlgo, double f, double timeToExpiry, double tStep, double riskFreeRate, out double rawTheta) { rawTheta = Double.NaN; if (timeToExpiry < Double.Epsilon) { throw new ArgumentOutOfRangeException("timeToExpiry", "timeToExpiry must be above zero. timeToExpiry:" + timeToExpiry); } double t1 = (timeToExpiry - tStep > Double.Epsilon) ? (timeToExpiry - tStep) : (0.5 * timeToExpiry); double pnl1 = 0; // Флаг того, что ПНЛ по всем инструментам был расчитан верно bool pnlIsCorrect1 = true; { // 2. Изменение времени // ВАЖНО: нормальный алгоритм сдвига улыбки во времени будет в платной версии "Пакета Каленковича" InteractiveSeries actualSmile = SingleSeriesProfile.GetSmileAtTime(smile, NumericalGreekAlgo.FrozenSmile, t1); { double pairPnl; pnlIsCorrect1 &= SingleSeriesProfile.TryGetPairPrice( putQty, callQty, actualSmile, pair, f, t1, riskFreeRate, out pairPnl); pnl1 += pairPnl; } } double t2 = timeToExpiry + tStep; double pnl2 = 0; // Флаг того, что ПНЛ по всем инструментам был расчитан верно bool pnlIsCorrect2 = true; { // ВАЖНО: нормальный алгоритм сдвига улыбки во времени будет в платной версии "Пакета Каленковича" InteractiveSeries actualSmile = SingleSeriesProfile.GetSmileAtTime(smile, NumericalGreekAlgo.FrozenSmile, t2); { double pairPnl; pnlIsCorrect2 &= SingleSeriesProfile.TryGetPairPrice( putQty, callQty, actualSmile, pair, f, t2, riskFreeRate, out pairPnl); pnl2 += pairPnl; } } if (pnlIsCorrect1 && pnlIsCorrect2) { //rawTheta = ((cash2 + pnl2) - (cash1 + pnl1)) / (t2 - t1); rawTheta = (pnl2 - pnl1) / (t2 - t1); // Переворачиваю тету, чтобы жить в календарном времени rawTheta = -rawTheta; return(true); } else { return(false); } }
public void Dispose() { if (m_clickableSeries != null) { m_clickableSeries.ClickEvent -= InteractiveSplineOnClickEvent; m_clickableSeries = null; } }
/// <summary> /// Вега будет иметь размерность 'пункты за 100% волатильности'. /// Обычно же опционщики любят смотреть размерность 'пункты за 1% волатильности'. /// Поэтому полученное сырое значение ещё надо делить на 100%. /// (Эквивалентно умножению на интересующий набег волы для получения дифференциала). /// </summary> internal static bool TryEstimateVega(double putQty, double callQty, IOptionSeries optSer, IOptionStrikePair pair, InteractiveSeries smile, NumericalGreekAlgo greekAlgo, double f, double dSigma, double timeToExpiry, double riskFreeRate, out double rawVega) { rawVega = Double.NaN; if (timeToExpiry < Double.Epsilon) { throw new ArgumentOutOfRangeException("timeToExpiry", "timeToExpiry must be above zero. timeToExpiry:" + timeToExpiry); } SmileInfo sInfo = smile.GetTag <SmileInfo>(); if (sInfo == null) { return(false); } double pnl1 = 0; // Флаг того, что ПНЛ по всем инструментам был расчитан верно bool pnlIsCorrect1 = true; { // Для первой точки улыбку не трогаем { double pairPnl; pnlIsCorrect1 &= SingleSeriesProfile.TryGetPairPrice( putQty, callQty, smile, pair, f, timeToExpiry, riskFreeRate, out pairPnl); pnl1 += pairPnl; } } double pnl2 = 0; // Флаг того, что ПНЛ по всем инструментам был расчитан верно bool pnlIsCorrect2 = true; { //InteractiveSeries actualSmile = SingleSeriesProfile.GetRaisedSmile(smile, greekAlgo, dSigma); SmileInfo actualSmile = SingleSeriesProfile.GetRaisedSmile(sInfo, greekAlgo, dSigma); double pairPnl; pnlIsCorrect2 &= SingleSeriesProfile.TryGetPairPrice( putQty, callQty, actualSmile, pair, f, timeToExpiry, riskFreeRate, out pairPnl); pnl2 += pairPnl; } if (pnlIsCorrect1 && pnlIsCorrect2) { // Первая точка совпадает с текущей, поэтому нет деления на 2. //rawVega = ((cash2 + pnl2) - (cash1 + pnl1)) / dSigma; rawVega = (pnl2 - pnl1) / dSigma; return(true); } else { return(false); } }
public InteractiveSeries Execute(double price, InteractiveSeries line, IOptionSeries optSer, int barNum) { if (optSer == null) { return(Constants.EmptySeries); } return(Execute(price, line, optSer.UnderlyingAsset, barNum)); }
public void Dispose() { if (m_clickableSeries != null) { m_clickableSeries.ClickEvent -= InteractiveSplineOnQuoteIvEvent; //m_clickableSeries.EndDragEvent -= res_EndDragEvent; m_clickableSeries = null; } }
/// <summary> /// Обработчик под тип входных данных OPTION_SERIES /// </summary> public InteractiveSeries Execute(double price, double trueTimeToExpiry, IOptionSeries optSer, int barNum) { if (optSer == null) { return(Constants.EmptySeries); } InteractiveSeries res = Execute(price, trueTimeToExpiry, optSer, 0, barNum); return(res); }
/// <summary> /// Обработчик под тип входных данных OPTION_SERIES /// </summary> public InteractiveSeries Execute(IOptionSeries optSer) { if (optSer == null) { return(Constants.EmptySeries); } InteractiveSeries res = Execute(optSer, new[] { 0.0 }); return(res); }
//#if DEBUG // /// <summary> // /// \~english Extrapolate template with smooth continuation // /// \~russian Продлевать шаблон на бесконечность // /// </summary> // [HelperName("Extrapolate Template", Constants.En)] // [HelperName("Продлевать шаблон", Constants.Ru)] // [Description("Продлевать шаблон на бесконечность")] // [HelperDescription("Extrapolate template with smooth continuation", Language = Constants.En)] // [HandlerParameter(true, NotOptimized = true, IsVisibleInBlock = true, Default = "true")] // public bool ExtrapolateSmile // { // get { return m_useSmileTails; } // set { m_useSmileTails = value; } // } //#endif #endregion Parameters public InteractiveSeries Execute(double price, double time, InteractiveSeries smile, IOptionSeries optSer, double scaleMult, int barNum) { if (optSer == null) { return(Constants.EmptySeries); } InteractiveSeries res = Execute(price, time, smile, optSer, scaleMult, 0, barNum); return(res); }
/// <summary> /// Обработчик под тип входных данных OPTION_SERIES /// </summary> public InteractiveSeries Execute(IOptionSeries optSer, int barNum) { if (optSer == null) { return(Constants.EmptySeries); } InteractiveSeries res = Execute(optSer, 0, barNum); return(res); }
/// <summary> /// Тета опционной пары по формуле Блека-Шолза /// </summary> internal static void GetPairTheta(PositionsManager posMan, InteractiveSeries smile, IOptionStrikePair pair, double f, double dT, out double totalTheta) { totalTheta = 0; ISecurity putSec = pair.Put.Security, callSec = pair.Call.Security; // закрытые позы не дают в клада в тету, поэтому беру только активные ReadOnlyCollection <IPosition> putPositions = posMan.GetActiveForBar(putSec); ReadOnlyCollection <IPosition> callPositions = posMan.GetActiveForBar(callSec); if ((putPositions.Count <= 0) && (callPositions.Count <= 0)) { return; } double?sigma = null; if ((smile.Tag != null) && (smile.Tag is SmileInfo)) { double tmp; SmileInfo info = smile.GetTag <SmileInfo>(); if (info.ContinuousFunction.TryGetValue(pair.Strike, out tmp)) { sigma = tmp; } } if (sigma == null) { sigma = (from d2 in smile.ControlPoints let point = d2.Anchor.Value where (DoubleUtil.AreClose(pair.Strike, point.X)) select(double?) point.Y).FirstOrDefault(); } if (sigma == null) { return; } { double putTheta; GetOptTheta(putPositions, f, pair.Strike, dT, sigma.Value, 0.0, false, out putTheta); totalTheta += putTheta; } { double callTheta; GetOptTheta(callPositions, f, pair.Strike, dT, sigma.Value, 0.0, true, out callTheta); totalTheta += callTheta; } }
public void Dispose() { if (m_context != null) { InteractiveSeries oldLine = m_context.LoadObject(m_frozenSmileId) as InteractiveSeries; if (oldLine != null) { // Просто выполняю отписку от старых событий? oldLine.EndDragEvent -= res_EndDragEvent; } } }
internal static InteractiveSeries SelectSmile(int index, params InteractiveSeries[] smiles) { if ((smiles == null) || (smiles.Length <= 0) || (index < 0) || (smiles.Length <= index)) { return(null); } InteractiveSeries res = smiles[index]; return(res); }
public InteractiveSeries Execute(InteractiveSeries ser1, InteractiveSeries ser2, int barNum) { int barsCount = m_context.BarsCount; if (!m_context.IsLastBarUsed) { barsCount--; } if (barNum < barsCount - 1) { return(Constants.EmptySeries); } if ((ser1 == null) && (ser2 == null)) { return(Constants.EmptySeries); } else if ((ser1 == null) && (ser2 != null)) { return(ser2); } else if ((ser1 != null) && (ser2 == null)) { return(ser1); } var query = (from s1 in ser1.ControlPoints from s2 in ser2.ControlPoints where DoubleUtil.AreClose(s1.Anchor.Value.X, s2.Anchor.Value.X) select new { cp1 = s1, cp2 = s2 }); List <InteractiveObject> controlPoints = new List <InteractiveObject>(); foreach (var pair in query) { double x = pair.cp1.Anchor.Value.X; double y = pair.cp1.Anchor.Value.Y + pair.cp2.Anchor.Value.Y; InteractivePointActive ip = new InteractivePointActive(x, y); //ip.Geometry = Geometries.Rect; ip.Tooltip = String.Format("F:{0}; PnL:{1}", x, y); controlPoints.Add(new InteractiveObject(ip)); } InteractiveSeries res = new InteractiveSeries(); // Здесь так надо -- мы делаем новую улыбку res.ControlPoints = new ReadOnlyCollection <InteractiveObject>(controlPoints); return(res); }
private void res_EndDragEvent(object sender, InteractiveActionEventArgs e) { InteractiveSeries oldLine = sender as InteractiveSeries; if (oldLine != null) { //SmileInfo info = oldLine.GetTag<SmileInfo>(); //if (info != null) //{ // List<double> xs = new List<double>(); // List<double> ys = new List<double>(); // foreach (InteractiveObject oldObj in oldLine.ControlPoints) // { // if (!(oldObj.Anchor.IsVisible ?? oldObj.AnchorGraphPointData.IsVisible)) // continue; // double k = oldObj.Anchor.ValueX; // double sigma = oldObj.Anchor.ValueY; // xs.Add(k); // ys.Add(sigma); // } // try // { // NotAKnotCubicSpline spline = new NotAKnotCubicSpline(xs, ys); // info.ContinuousFunction = spline; // info.ContinuousFunctionD1 = spline.DeriveD1(); // oldLine.Tag = info; // } // catch (Exception ex) // { // m_context.Log(ex.ToString(), MessageType.Error, true); // //return Constants.EmptySeries; // } //} // Сразу отписываюсь! oldLine.EndDragEvent -= res_EndDragEvent; } m_context.Recalc(); }
/// <summary> /// Метод под флаг TemplateTypes.INTERACTIVESPLINE /// </summary> public InteractiveSeries Execute(InteractiveSeries quotes, int barNum) { if (quotes == null) { return(Constants.EmptySeries); } int barsCount = m_context.BarsCount; if (!m_context.IsLastBarUsed) { barsCount--; } if (barNum < barsCount - 1) { return(quotes); } // Это надо ОБЯЗАТЕЛЬНО делать ПОСЛЕ восстановления виртуальных позиций #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 InteractiveSeries res = quotes; res.ClickEvent -= InteractiveSplineOnClickEvent; res.ClickEvent += InteractiveSplineOnClickEvent; m_clickableSeries = res; return(res); }
public InteractiveSeries Execute(double price, double time, InteractiveSeries smile, IOptionSeries optSer, int barNum) { int barsCount = ContextBarsCount; if ((barNum < barsCount - 1) || (optSer == null)) { 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); if (wasInitialized) { m_context.Log(msg, MessageType.Error, false); } 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); if (wasInitialized) { m_context.Log(msg, MessageType.Error, false); } 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); } if (m_optionType == StrikeType.Any) { string msg = String.Format("[{0}] OptionType '{1}' is not supported.", GetType().Name, m_optionType); 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); } bool isCall = m_optionType == StrikeType.Call; double putQty = isCall ? 0 : m_fixedQty; double callQty = isCall ? m_fixedQty : 0; double riskFreeRate = 0; List <double> xs = new List <double>(); List <double> ys = new List <double>(); var smilePoints = smile.ControlPoints; IOptionStrikePair[] pairs = optSer.GetStrikePairs().ToArray(); PositionsManager posMan = PositionsManager.GetManager(m_context); List <InteractiveObject> controlPoints = new List <InteractiveObject>(); foreach (IOptionStrikePair pair in pairs) { double rawTheta; if (TryEstimateTheta(putQty, callQty, optSer, pair, smile, m_greekAlgo, futPx, dT, m_tStep, riskFreeRate, out rawTheta)) { // Переводим тету в дифференциал 'изменение цены за 1 сутки'. rawTheta /= OptionUtils.DaysInYear; InteractivePointActive ip = new InteractivePointActive(); ip.IsActive = m_showNodes; //ip.DragableMode = DragableMode.None; //ip.Geometry = Geometries.Rect; //ip.Color = System.Windows.Media.Colors.Green; double y = rawTheta; ip.Value = new Point(pair.Strike, y); string yStr = y.ToString(m_tooltipFormat, CultureInfo.InvariantCulture); ip.Tooltip = String.Format("K:{0}; Th:{1}", pair.Strike, yStr); controlPoints.Add(new InteractiveObject(ip)); xs.Add(pair.Strike); ys.Add(y); } } InteractiveSeries res = new InteractiveSeries(); // Здесь так надо -- мы делаем новую улыбку res.ControlPoints = new ReadOnlyCollection <InteractiveObject>(controlPoints); try { if (xs.Count >= BaseCubicSpline.MinNumberOfNodes) { SmileInfo info = new SmileInfo(); info.F = oldInfo.F; info.dT = oldInfo.dT; info.RiskFreeRate = oldInfo.RiskFreeRate; NotAKnotCubicSpline spline = new NotAKnotCubicSpline(xs, ys); info.ContinuousFunction = spline; info.ContinuousFunctionD1 = spline.DeriveD1(); res.Tag = info; } } catch (Exception ex) { m_context.Log(ex.ToString(), MessageType.Error, true); return(Constants.EmptySeries); } SetHandlerInitialized(now); return(res); }
public InteractiveSeries Execute(double trueTimeToExpiry, InteractiveSeries smile, int barNum) { int barsCount = m_context.BarsCount; if (!m_context.IsLastBarUsed) { barsCount--; } if (barNum < barsCount - 1) { return(Constants.EmptySeries); } SmileInfo refSmileInfo = smile.GetTag <SmileInfo>(); if ((refSmileInfo == null) || (refSmileInfo.ContinuousFunction == null)) { 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); } InteractiveSeries res = FrozenSmile; // Поскольку редактируемые узлы идут с заданным шагом, то // допустим только режим Yonly. DragableMode dragMode = DragableMode.Yonly; SmileInfo oldInfo = res.GetTag <SmileInfo>(); if (m_resetSmile || (oldInfo == null) || (oldInfo.ContinuousFunction == null)) { oldInfo = refSmileInfo; //oldInfo.F = sInfo.F; //oldInfo.dT = sInfo.dT; //oldInfo.RiskFreeRate = sInfo.RiskFreeRate; } double futPx = refSmileInfo.F; double dT = trueTimeToExpiry; double ivAtm = refSmileInfo.ContinuousFunction.Value(futPx); SmileInfo info = new SmileInfo(); { info.F = futPx; info.dT = trueTimeToExpiry; info.RiskFreeRate = oldInfo.RiskFreeRate; List <double> xs = new List <double>(); List <double> ys = new List <double>(); List <InteractiveObject> visibleNodes = (from oldObj in res.ControlPoints where oldObj.AnchorIsActive select oldObj).ToList(); int visibleNodesCount = visibleNodes.Count; if (m_resetSmile || (visibleNodesCount != m_numberOfNodes)) { // Здесь обязательно в начале //res.ControlPoints.Clear(); res.ControlPoints = new ReadOnlyCollection <InteractiveObject>(new InteractiveObject[0]); int half = m_numberOfNodes / 2; // Целочисленное деление! #region 1. Готовлю сплайн { double mult = m_nodeStep * ivAtm * Math.Sqrt(dT); double dK = futPx * (Math.Exp(mult) - 1); // Сдвигаю точки, чтобы избежать отрицательных значений while ((futPx - half * dK) <= Double.Epsilon) { half--; } for (int j = 0; j < m_numberOfNodes; j++) { double k = futPx + (j - half) * dK; // Обычно здесь будет лежать сплайн от замороженной улыбки... double sigma; if ((!oldInfo.ContinuousFunction.TryGetValue(k, out sigma)) || Double.IsNaN(sigma)) { string msg = String.Format("[DEBUG:{0}] Unable to get IV for strike:{1}. Please, try to decrease NodeStep.", GetType().Name, k); m_context.Log(msg, MessageType.Warning, true); return(res); } xs.Add(k); ys.Add(sigma); } } #endregion 1. Готовлю сплайн } else { // Здесь обязательно в начале //res.ControlPoints.Clear(); res.ControlPoints = new ReadOnlyCollection <InteractiveObject>(new InteractiveObject[0]); int half = m_numberOfNodes / 2; // Целочисленное деление! #region 2. Готовлю сплайн { double mult = m_nodeStep * ivAtm * Math.Sqrt(dT); double dK = futPx * (Math.Exp(mult) - 1); // Сдвигаю точки, чтобы избежать отрицательных значений while ((futPx - half * dK) <= Double.Epsilon) { half--; } // внутренние узлы... for (int j = 0; j < m_numberOfNodes; j++) { double k = futPx + (j - half) * dK; //// Обычно здесь будет лежать сплайн от замороженной улыбки... //double sigma = oldInfo.ContinuousFunction.Value(k); double sigma = visibleNodes[j].Anchor.ValueY; xs.Add(k); ys.Add(sigma); } } #endregion 2. Готовлю сплайн } try { if (xs.Count >= BaseCubicSpline.MinNumberOfNodes) { NotAKnotCubicSpline spline = new NotAKnotCubicSpline(xs, ys); info.ContinuousFunction = spline; info.ContinuousFunctionD1 = spline.DeriveD1(); //info.F = F; //info.dT = trueTimeToExpiry; res.Tag = info; } } catch (Exception ex) { m_context.Log(ex.ToString(), MessageType.Error, true); return(Constants.EmptySeries); } // 2. Формирую кривую с более плотным шагом List <InteractiveObject> controlPoints = new List <InteractiveObject>(); int editableNodesCount = FillEditableCurve(info, controlPoints, xs, dragMode); res.ControlPoints = new ReadOnlyCollection <InteractiveObject>(controlPoints); if (editableNodesCount != m_numberOfNodes) { string msg = String.Format("[DEBUG:{0}] {1} nodes requested, but only {2} were prepared.", GetType().Name, m_numberOfNodes, editableNodesCount); m_context.Log(msg, MessageType.Warning, true); } } if (!m_resetSmile) { if (m_loadSplineCoeffs) { } if (m_prepareSplineCoeffs) { #region Prepare global spline // Надо пересчитать сплайн в безразмерные коэффициенты // Обновляю уровень IV ATM? ivAtm = info.ContinuousFunction.Value(futPx); SmileInfo globInfo = new SmileInfo(); globInfo.F = futPx; globInfo.dT = trueTimeToExpiry; globInfo.RiskFreeRate = oldInfo.RiskFreeRate; StringBuilder sb = new StringBuilder(GlobalSmileKey); sb.AppendLine(); sb.AppendFormat(CultureInfo.InvariantCulture, "F:{0}", futPx); sb.AppendLine(); sb.AppendFormat(CultureInfo.InvariantCulture, "dT:{0}", dT); sb.AppendLine(); sb.AppendFormat(CultureInfo.InvariantCulture, "IvAtm:{0}", ivAtm); sb.AppendLine(); sb.AppendFormat(CultureInfo.InvariantCulture, "RiskFreeRate:{0}", globInfo.RiskFreeRate); sb.AppendLine(); sb.AppendFormat(CultureInfo.InvariantCulture, "ShapePct:{0}", ShapePct); sb.AppendLine(); sb.AppendLine("~~~"); sb.AppendFormat(CultureInfo.InvariantCulture, "X;Y"); sb.AppendLine(); //LogSimmetrizeFunc logSimmFunc = new LogSimmetrizeFunc(info.ContinuousFunction, F, 0.5); List <double> xs = new List <double>(); List <double> ys = new List <double>(); foreach (InteractiveObject oldObj in res.ControlPoints) { if (!oldObj.AnchorIsActive) { continue; } double k = oldObj.Anchor.ValueX; double x = Math.Log(k / futPx) / Math.Pow(dT, DefaultPow + m_shape) / ivAtm; double sigma = oldObj.Anchor.ValueY; double sigmaNormalized = sigma / ivAtm; xs.Add(x); ys.Add(sigmaNormalized); sb.AppendFormat(CultureInfo.InvariantCulture, "{0};{1}", x, sigmaNormalized); sb.AppendLine(); } try { NotAKnotCubicSpline globSpline = new NotAKnotCubicSpline(xs, ys); globInfo.ContinuousFunction = globSpline; globInfo.ContinuousFunctionD1 = globSpline.DeriveD1(); //global.Tag = globInfo; m_context.StoreGlobalObject(GlobalSmileKey, globInfo, true); string msg = String.Format("[{0}] The globInfo was saved in Global cache as '{1}'.", GetType().Name, GlobalSmileKey); m_context.Log(msg, MessageType.Warning, true); msg = String.Format("[{0}] The globInfo was saved in file tslab.log also. Smile:\r\n{1}", GetType().Name, sb); m_context.Log(msg, MessageType.Info, true); // Запись в клипбоард try { //Thread thread = ThreadProfiler.Create(() => System.Windows.Clipboard.SetText(sb.ToString())); XElement xel = globInfo.ToXElement(); string xelStr = @"<?xml version=""1.0""?> " + xel.AsString(); // PROD-5987 - Отключаю работу с клипбордом. Только пишу в tslab.log //Thread thread = ThreadProfiler.Create(() => System.Windows.Clipboard.SetText(xelStr)); //thread.SetApartmentState(ApartmentState.STA); //Set the thread to STA //thread.Start(); //thread.Join(); // Надо ли делать Join? s_log.WarnFormat("Global smile info:\r\n\r\n{0}\r\n\r\n", xelStr); } catch (Exception clipEx) { m_context.Log(clipEx.ToString(), MessageType.Error, true); } m_context.Recalc(); } catch (Exception ex) { m_context.Log(ex.ToString(), MessageType.Error, true); //return Constants.EmptySeries; } #endregion Prepare global spline } else if (m_pasteGlobal) { // PROD-5987 - Работа с клипбордом отключена. Функция вставки сейчас работать не будет. m_context.Log($"[{GetType().Name}] Clipboard is not available. Sorry.", MessageType.Warning, true); #region Paste spline from clipboard //string xelStr = ""; //// Чтение из клипбоард //try //{ // // PROD-5987 - Работа с клипбордом отключена. Функция вставки сейчас работать не будет. // ////Thread thread = ThreadProfiler.Create(() => System.Windows.Clipboard.SetText(sb.ToString())); // //Thread thread = ThreadProfiler.Create(() => xelStr = System.Windows.Clipboard.GetText()); // //thread.SetApartmentState(ApartmentState.STA); //Set the thread to STA // //thread.Start(); // //thread.Join(); // Надо ли делать Join? // if (!String.IsNullOrWhiteSpace(xelStr)) // { // XDocument xDoc = XDocument.Parse(xelStr); // XElement xInfo = xDoc.Root; // SmileInfo templateSmile = SmileInfo.FromXElement(xInfo); // // Обновляю уровень IV ATM? // // TODO: перепроверить как работает редактирование шаблона // ivAtm = info.ContinuousFunction.Value(futPx); // if (Double.IsNaN(ivAtm)) // { // ivAtm = refSmileInfo.ContinuousFunction.Value(futPx); // m_context.Log(String.Format("[DEBUG:{0}] ivAtm was NaN. I'll use value ivAtm:{1}", GetType().Name, ivAtm), MessageType.Warning, true); // if (Double.IsNaN(ivAtm)) // { // throw new Exception(String.Format("[DEBUG:{0}] ivAtm is NaN.", GetType().Name)); // } // } // templateSmile.F = futPx; // templateSmile.dT = trueTimeToExpiry; // templateSmile.RiskFreeRate = oldInfo.RiskFreeRate; // // Здесь обязательно в начале // //res.ControlPoints.Clear(); // res.ControlPoints = new ReadOnlyCollection<InteractiveObject>(new InteractiveObject[0]); // SmileFunction5 func = new SmileFunction5(templateSmile.ContinuousFunction, templateSmile.ContinuousFunctionD1, // ivAtm, 0, 0, futPx, dT); // info.ContinuousFunction = func; // info.ContinuousFunctionD1 = func.DeriveD1(); // // info уже лежит в Tag, поэтому при следующем пересчете сплайн уже должен пересчитаться // // по новой улыбке. Правильно? // string msg = String.Format("[DEBUG:{0}] SmileInfo was loaded from clipboard.", GetType().Name); // m_context.Log(msg, MessageType.Warning, true); // m_context.Recalc(); // } //} //catch (Exception clipEx) //{ // m_context.Log(clipEx.ToString(), MessageType.Error, true); //} #endregion Paste spline from clipboard } } //res.ClickEvent -= res_ClickEvent; //res.ClickEvent += res_ClickEvent; //res.DragEvent -= res_DragEvent; //res.DragEvent += res_DragEvent; res.EndDragEvent -= res_EndDragEvent; res.EndDragEvent += res_EndDragEvent; return(res); }
/// <summary> /// Тета будет иметь размерность 'пункты за год'. /// Обычно же опционщики любят смотреть размерность 'пункты за день'. /// Поэтому полученное сырое значение ещё надо делить на количество дней в году. /// (Эквивалентно умножению на интересующий набег времени для получения дифференциала). /// </summary> internal static bool TryEstimateTheta(PositionsManager posMan, IOptionStrikePair[] pairs, InteractiveSeries smile, NumericalGreekAlgo greekAlgo, double f, double timeToExpiry, double tStep, out double rawTheta) { rawTheta = Double.NaN; if (timeToExpiry < Double.Epsilon) { throw new ArgumentOutOfRangeException("timeToExpiry", "timeToExpiry must be above zero. timeToExpiry:" + timeToExpiry); } SmileInfo sInfo = smile.GetTag <SmileInfo>(); if (sInfo == null) { return(false); } double t1 = (timeToExpiry - tStep > Double.Epsilon) ? (timeToExpiry - tStep) : (0.5 * timeToExpiry); double cash1 = 0, pnl1 = 0; // Флаг того, что ПНЛ по всем инструментам был расчитан верно bool pnlIsCorrect1 = true; { // TODO: фьюч на даёт вклад в тету??? //SingleSeriesProfile.GetBasePnl(posMan, optSer.UnderlyingAsset, optSer.UnderlyingAsset.Bars.Count - 1, f, out cash1, out pnl1); //// 1. Изменение положения БА //InteractiveSeries actualSmile = SingleSeriesProfile.GetActualSmile(smile, greekAlgo, f); SmileInfo actualSmile; // 1. Изменение положения БА actualSmile = SingleSeriesProfile.GetActualSmile(sInfo, greekAlgo, f); // 2. Изменение времени // ВАЖНО: нормальный алгоритм сдвига улыбки во времени будет в платной версии "Пакета Каленковича" actualSmile = SingleSeriesProfile.GetSmileAtTime(actualSmile, NumericalGreekAlgo.FrozenSmile, t1); for (int j = 0; j < pairs.Length; j++) { IOptionStrikePair pair = pairs[j]; double pairPnl, pairCash; //double putPx = FinMath.GetOptionPrice(f, pair.Strike, dT, sigma.Value, 0, false); //SingleSeriesProfile.GetPairPnl(posMan, actualSmile, pair, f, t1, out pairCash, out pairPnl); pnlIsCorrect1 &= SingleSeriesProfile.TryGetPairPnl(posMan, actualSmile, pair, f, t1, out pairCash, out pairPnl); cash1 += pairCash; pnl1 += pairPnl; } } double t2 = timeToExpiry + tStep; double cash2 = 0, pnl2 = 0; // Флаг того, что ПНЛ по всем инструментам был расчитан верно bool pnlIsCorrect2 = true; { // фьюч на даёт вклад в вегу //SingleSeriesProfile.GetBasePnl(posMan, optSer.UnderlyingAsset, optSer.UnderlyingAsset.Bars.Count - 1, f, out cash2, out pnl2); //// 1. Изменение положения БА //InteractiveSeries actualSmile = SingleSeriesProfile.GetActualSmile(smile, greekAlgo, f); SmileInfo actualSmile; // 1. Изменение положения БА actualSmile = SingleSeriesProfile.GetActualSmile(sInfo, greekAlgo, f); // 2. Изменение времени // ВАЖНО: нормальный алгоритм сдвига улыбки во времени будет в платной версии "Пакета Каленковича" actualSmile = SingleSeriesProfile.GetSmileAtTime(actualSmile, NumericalGreekAlgo.FrozenSmile, t2); for (int j = 0; j < pairs.Length; j++) { IOptionStrikePair pair = pairs[j]; double pairPnl, pairCash; //double putPx = FinMath.GetOptionPrice(f, pair.Strike, dT, sigma.Value, 0, false); //SingleSeriesProfile.GetPairPnl(posMan, actualSmile, pair, f, t2, out pairCash, out pairPnl); pnlIsCorrect2 &= SingleSeriesProfile.TryGetPairPnl(posMan, actualSmile, pair, f, t2, out pairCash, out pairPnl); cash2 += pairCash; pnl2 += pairPnl; } } if (pnlIsCorrect1 && pnlIsCorrect2) { rawTheta = ((cash2 + pnl2) - (cash1 + pnl1)) / (t2 - t1); // Переворачиваю тету, чтобы жить в календарном времени rawTheta = -rawTheta; return(true); } else { return(false); } }
public InteractiveSeries Execute(double time, InteractiveSeries smile, IOptionSeries optSer, int barNum) { InteractiveSeries res = new InteractiveSeries(); int barsCount = ContextBarsCount; if ((barNum < barsCount - 1) || (optSer == null)) { return(res); } double dT = time; 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(res); } if (smile == null) { string msg = String.Format("[{0}] Argument 'smile' must be filled with InteractiveSeries.", GetType().Name); m_context.Log(msg, MessageType.Error, true); return(res); } 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); m_context.Log(msg, MessageType.Error, true); return(res); } List <double> xs = new List <double>(); List <double> ys = new List <double>(); var smilePoints = smile.ControlPoints; IOptionStrikePair[] pairs = optSer.GetStrikePairs().ToArray(); PositionsManager posMan = PositionsManager.GetManager(m_context); List <InteractiveObject> controlPoints = new List <InteractiveObject>(); foreach (InteractiveObject iob in smilePoints) { double rawTheta, f = iob.Anchor.ValueX; if (TryEstimateTheta(posMan, pairs, smile, m_greekAlgo, f, dT, m_tStep, out rawTheta)) { // Переводим тету в дифференциал 'изменение цены за 1 сутки'. rawTheta = RescaleThetaToDays(m_tRemainMode, rawTheta); // ReSharper disable once UseObjectOrCollectionInitializer InteractivePointActive ip = new InteractivePointActive(); ip.IsActive = m_showNodes; //ip.DragableMode = DragableMode.None; //ip.Geometry = Geometries.Rect; //ip.Color = System.Windows.Media.Colors.Green; double y = rawTheta; ip.Value = new Point(f, y); string yStr = y.ToString(m_tooltipFormat, CultureInfo.InvariantCulture); ip.Tooltip = String.Format(CultureInfo.InvariantCulture, "F:{0}; Th:{1}", f, yStr); controlPoints.Add(new InteractiveObject(ip)); xs.Add(f); ys.Add(y); } } res.ControlPoints = new ReadOnlyCollection <InteractiveObject>(controlPoints); try { if (xs.Count >= BaseCubicSpline.MinNumberOfNodes) { // ReSharper disable once UseObjectOrCollectionInitializer SmileInfo info = new SmileInfo(); info.F = oldInfo.F; info.dT = oldInfo.dT; info.RiskFreeRate = oldInfo.RiskFreeRate; NotAKnotCubicSpline spline = new NotAKnotCubicSpline(xs, ys); info.ContinuousFunction = spline; info.ContinuousFunctionD1 = spline.DeriveD1(); res.Tag = info; } } catch (Exception ex) { m_context.Log(ex.ToString(), MessageType.Error, true); return(Constants.EmptySeries); } return(res); }
public InteractiveSeries Execute(IOptionSeries optSer, int barNum) { int barsCount = ContextBarsCount; if ((barNum < barsCount - 1) || (optSer == null)) { return(Constants.EmptySeries); } int lastBarIndex = optSer.UnderlyingAsset.Bars.Count - 1; DateTime now = optSer.UnderlyingAsset.Bars[Math.Min(barNum, lastBarIndex)].Date; bool wasInitialized = HandlerInitializedToday(now); IOptionStrikePair[] pairs = optSer.GetStrikePairs().ToArray(); PositionsManager posMan = PositionsManager.GetManager(m_context); List <InteractiveObject> controlPoints = new List <InteractiveObject>(); if (m_countFutures) { var futPositions = posMan.GetClosedOrActiveForBar(optSer.UnderlyingAsset); if (futPositions.Count > 0) { double futQty, futAvgPx; SingleSeriesProfile.GetAveragePrice(futPositions, barNum, m_longPositions, out futAvgPx, out futQty); if (!DoubleUtil.IsZero(futQty)) { double valueToDisplay = m_countQty ? futQty : futAvgPx; // ReSharper disable once UseObjectOrCollectionInitializer InteractivePointActive ip = new InteractivePointActive(0, valueToDisplay); ip.IsActive = true; //ip.DragableMode = DragableMode.None; //ip.Geometry = Geometries.Rect; //ip.Color = Colors.DarkOrange; ip.Tooltip = String.Format("AvgPx:{0}; Qty:{1}", futAvgPx, futQty); controlPoints.Add(new InteractiveObject(ip)); } } } for (int j = 0; j < pairs.Length; j++) { IOptionStrikePair pair = pairs[j]; double putQty = 0, putAvgPx = Double.NaN; { var putPositions = posMan.GetClosedOrActiveForBar(pair.Put.Security); if (putPositions.Count > 0) { SingleSeriesProfile.GetAveragePrice(putPositions, barNum, m_longPositions, out putAvgPx, out putQty); } } double callQty = 0, callAvgPx = Double.NaN; { var callPositions = posMan.GetClosedOrActiveForBar(pair.Call.Security); if (callPositions.Count > 0) { SingleSeriesProfile.GetAveragePrice(callPositions, barNum, m_longPositions, out callAvgPx, out callQty); } } if ((!DoubleUtil.IsZero(putQty)) || (!DoubleUtil.IsZero(callQty))) { double averagePrice = 0, lotSize = 0; switch (m_optionType) { case StrikeType.Put: averagePrice = putAvgPx; lotSize = putQty; break; case StrikeType.Call: averagePrice = callAvgPx; lotSize = callQty; break; //case StrikeType.Any: // y = putQty + callQty; // break; default: throw new NotSupportedException("OptionType: " + m_optionType); } // Не хочу видеть ячейки таблицы с NaN if (Double.IsNaN(averagePrice)) { continue; } if (!DoubleUtil.IsZero(lotSize)) { double valueToDisplay = m_countQty ? lotSize : averagePrice; // ReSharper disable once UseObjectOrCollectionInitializer InteractivePointActive ip = new InteractivePointActive(pair.Strike, valueToDisplay); ip.IsActive = true; //ip.DragableMode = DragableMode.None; //ip.Geometry = Geometries.Rect; //ip.Color = Colors.DarkOrange; ip.Tooltip = String.Format("K:{0}; AvgPx:{1}; Qty:{2}", pair.Strike, averagePrice, lotSize); controlPoints.Add(new InteractiveObject(ip)); } } } // ReSharper disable once UseObjectOrCollectionInitializer InteractiveSeries res = new InteractiveSeries(); // Здесь правильно делать new res.ControlPoints = new ReadOnlyCollection <InteractiveObject>(controlPoints); SetHandlerInitialized(now); return(res); }
public InteractiveSeries Execute(double time, InteractiveSeries smile, IOptionSeries optSer, int barNum) { int barsCount = ContextBarsCount; if (barNum < barsCount - 1) { return(Constants.EmptySeries); } //double F = prices[prices.Count - 1]; 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 (smile == null) { string msg = String.Format("[{0}] Argument 'smile' must be filled with InteractiveSeries.", GetType().Name); m_context.Log(msg, MessageType.Error, true); 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); m_context.Log(msg, MessageType.Error, true); return(Constants.EmptySeries); } double f = m_minStrike; List <double> xs = new List <double>(); List <double> ys = new List <double>(); IOptionStrikePair[] pairs = optSer.GetStrikePairs().ToArray(); PositionsManager posMan = PositionsManager.GetManager(m_context); List <InteractiveObject> controlPoints = new List <InteractiveObject>(); while (f <= m_maxStrike) { double rawDelta; GetBaseDelta(posMan, optSer.UnderlyingAsset, optSer.UnderlyingAsset.Bars.Count - 1, f, out rawDelta); for (int j = 0; j < pairs.Length; j++) { IOptionStrikePair pair = pairs[j]; double totalDelta; //double putDelta = FinMath.GetOptionDelta(f, pair.Strike, dT, sigma.Value, 0, false); GetPairDelta(posMan, smile, pair, f, dT, out totalDelta); rawDelta += totalDelta; } InteractivePointActive ip = new InteractivePointActive(); ip.IsActive = m_showNodes; //ip.DragableMode = DragableMode.None; //ip.Geometry = Geometries.Rect; //ip.Color = AlphaColors.Green; double y = rawDelta; ip.Value = new Point(f, y); string yStr = y.ToString(m_tooltipFormat, CultureInfo.InvariantCulture); ip.Tooltip = String.Format(CultureInfo.InvariantCulture, "F:{0}; D:{1}", f, yStr); controlPoints.Add(new InteractiveObject(ip)); xs.Add(f); ys.Add(y); f += m_strikeStep; } InteractiveSeries res = new InteractiveSeries(); // Здесь так надо -- мы делаем новую улыбку res.ControlPoints = new ReadOnlyCollection <InteractiveObject>(controlPoints); try { if (xs.Count >= BaseCubicSpline.MinNumberOfNodes) { SmileInfo info = new SmileInfo(); info.F = oldInfo.F; info.dT = oldInfo.dT; info.RiskFreeRate = oldInfo.RiskFreeRate; NotAKnotCubicSpline spline = new NotAKnotCubicSpline(xs, ys); info.ContinuousFunction = spline; info.ContinuousFunctionD1 = spline.DeriveD1(); res.Tag = info; } } catch (Exception ex) { m_context.Log(ex.ToString(), MessageType.Error, true); return(Constants.EmptySeries); } return(res); }
/// <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); }