/// <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); } }
/// <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; } }
/// <summary> /// Получить финансовые параметры опционной позиции (колы и путы на одном страйке суммарно) /// </summary> /// <param name="smileInfo">улыбка</param> /// <param name="strike">страйк</param> /// <param name="putBarCount">количество баров для пута</param> /// <param name="callBarCount">количество баров для кола</param> /// <param name="putPositions">позиции пута</param> /// <param name="callPositions">позиции кола</param> /// <param name="f">текущая цена БА</param> /// <param name="dT">время до экспирации</param> /// <param name="cash">денежные затраты на формирование позы (могут быть отрицательными)</param> /// <param name="pnl">текущая цена позиции</param> /// <returns>true, если всё посчитано без ошибок</returns> public static bool TryGetPairPnl(SmileInfo smileInfo, double strike, int putBarCount, int callBarCount, IList <IPosition> putPositions, IList <IPosition> callPositions, double f, double dT, double btcUsdInd, out double cashUsd, out double pnlUsd) { CashPnlUsd cashPnlUsd; CashPnlBtc cashPnlBtc; bool res = TryGetPairPnl(smileInfo, strike, putBarCount, callBarCount, putPositions, callPositions, f, dT, btcUsdInd, out cashPnlUsd, out cashPnlBtc); cashUsd = cashPnlUsd.CashUsd; pnlUsd = cashPnlUsd.PnlUsd; return(res); }
/// <summary> /// Основной метод, который выполняет всю торговую логику по котированию и следит за риском /// </summary> protected double Process(double entryPermission, double centralStrike, double risk, double maxRisk, InteractiveSeries smile, IOptionSeries optSer, InteractiveSeries callDelta, double callRisk, double putRisk, int barNum) { int barsCount = m_context.BarsCount; if (!m_context.IsLastBarUsed) { barsCount--; } if ((barNum < barsCount - 1) || (optSer == null) || (smile == null)) { return(Constants.NaN); } { IOptionStrikePair testPair; if (!optSer.TryGetStrikePair(centralStrike, out testPair)) { return(Constants.NaN); } } // Если риск не был измерен говорить вообще не о чем! if (Double.IsNaN(risk)) { return(Constants.NaN); } // Если риск разумен и условие входа НЕ ВЫПОЛНЕНО -- отдыхаем. // А вот если риск превышен -- тогда по идее надо бы его подсократить! if ((risk < maxRisk) && (entryPermission <= 0)) { return(Constants.NaN); } PositionsManager posMan = PositionsManager.GetManager(m_context); if (posMan.BlockTrading) { //string msg = String.Format("Trading is blocked. Please, change 'Block Trading' parameter."); //m_context.Log(msg, MessageType.Info, true); return(Constants.NaN); } SmileInfo sInfo = smile.GetTag <SmileInfo>(); if ((sInfo == null) || (sInfo.ContinuousFunction == null)) { return(Constants.NaN); } double dT = sInfo.dT; double futPx = sInfo.F; SmileInfo callDeltaInfo = callDelta.GetTag <SmileInfo>(); if ((callDeltaInfo == null) || (callDeltaInfo.ContinuousFunction == null)) { return(Constants.NaN); } // Функция для вычисления дельты кола IFunction cDf = callDeltaInfo.ContinuousFunction; // Набираем риск if (risk < maxRisk) { List <IOptionStrikePair> orderedPairs = BuyOptionGroupDelta.GetFilteredPairs(optSer, centralStrike, cDf, m_strikeStep, m_minDelta, m_maxDelta, m_checkAbsDelta); // Сколько лотов уже выставлено в рынок double pendingQty = 0; if (orderedPairs.Count > 0) { foreach (IOptionStrikePair candidPair in orderedPairs) { double ivAtm; double strike = candidPair.Strike; if ((!sInfo.ContinuousFunction.TryGetValue(strike, out ivAtm)) || Double.IsNaN(ivAtm) || (ivAtm < Double.Epsilon)) { string msg = String.Format("[{0}.{1}] Unable to get IV at strike {2}. ivAtm:{3}", Context.Runtime.TradeName, GetType().Name, candidPair.Strike, ivAtm); m_context.Log(msg, MessageType.Error, true); return(Constants.NaN); } double theorPutPx = FinMath.GetOptionPrice(futPx, strike, dT, ivAtm, 0, false); double theorCallPx = FinMath.GetOptionPrice(futPx, strike, dT, ivAtm, 0, true); double cd, pd; // Вычисляю дельту кола и с ее помощью -- дельту пута if (!cDf.TryGetValue(strike, out cd)) { // Этого не может быть по правилу отбора страйков! Contract.Assert(false, "Почему мы не смогли вычислить дельту кола???"); continue; } // Типа, колл-пут паритет для вычисления дельты путов pd = 1 - cd; if (m_checkAbsDelta) { // Берем дельты по модулю cd = Math.Abs(cd); pd = Math.Abs(pd); } double putPx, callPx; { double putQty, callQty; DateTime putTime, callTime; putPx = IvSmile.GetOptPrice(m_context, futPx, candidPair.Put, OptionPxMode.Bid, 0, 0, out putQty, out putTime); callPx = IvSmile.GetOptPrice(m_context, futPx, candidPair.Call, OptionPxMode.Bid, 0, 0, out callQty, out callTime); } #region Набираем риск int executedQty = 0; // Если дельта пута влезает в диапазон -- выставляем котировку в путы if ((m_minDelta <= pd) && (pd <= m_maxDelta)) { #region Набираем риск в путах ISecurity sec = candidPair.Put.Security; double px = SellOptions.SafeMaxPrice(theorPutPx + m_entryShift * sec.Tick, putPx, sec); double iv = FinMath.GetOptionSigma(futPx, candidPair.Strike, dT, px, 0, false); double qty = Math.Abs(m_fixedQty); // TODO: Немного грубая оценка, но пока сойдет qty = BuyOptions.GetSafeQty(risk + pendingQty * putRisk, maxRisk, qty, putRisk); if (qty > 0) { string sigName = String.Format("Risk:{0}; MaxRisk:{1}; Px:{2}; Qty:{3}; IV:{4:P2}; dT:{5}", risk, maxRisk, px, qty, iv, dT); posMan.SellAtPrice(m_context, sec, qty, px, "Open SELL", sigName); pendingQty += qty; m_context.Log(sigName, MessageType.Info, false); executedQty += (int)qty; } #endregion Набираем риск в путах } // Если дельта кола влезает в диапазон -- выставляем котировку в колы if (Math.Abs(executedQty) < Math.Abs(m_fixedQty) && (m_minDelta <= cd) && (cd <= m_maxDelta)) { #region Набираем риск в колах ISecurity sec = candidPair.Call.Security; double px = SellOptions.SafeMaxPrice(theorCallPx + m_entryShift * sec.Tick, callPx, sec); double iv = FinMath.GetOptionSigma(futPx, candidPair.Strike, dT, px, 0, true); double qty = Math.Abs(m_fixedQty) - Math.Abs(executedQty); // TODO: Немного грубая оценка, но пока сойдет // Делаю оценку изменения текущего риска, если нам зафилят заявку в путах //qty = BuyOptions.GetSafeQty(risk + Math.Abs(executedQty) * putRisk, maxRisk, qty, callRisk); // Причем здесь уже не нужно отдельно учитывать executedQty, потому что он входит в pendingQty qty = BuyOptions.GetSafeQty(risk + pendingQty * callRisk, maxRisk, qty, callRisk); if (qty > 0) { string sigName = String.Format("Risk:{0}; MaxRisk:{1}; Px:{2}; Qty:{3}; IV:{4:P2}; dT:{5}", risk, maxRisk, px, qty, iv, dT); posMan.SellAtPrice(m_context, sec, qty, px, "Open SELL", sigName); pendingQty += qty; m_context.Log(sigName, MessageType.Info, false); //executedQty += (int)qty; } #endregion Набираем риск в колах } #endregion Набираем риск } // End foreach (IOptionStrikePair candidPair in orderedPairs) } else { string msg = String.Format("[{0}] Strike not found. risk:{1}; maxRisk:{2}; orderedPairs.Count:{3}", Context.Runtime.TradeName, risk, maxRisk, orderedPairs.Count); m_context.Log(msg, MessageType.Warning, true); } } else if (risk > maxRisk) { string msg; //string msg = String.Format("[DEBUG:{0}] risk:{1}; maxRisk:{2}", Context.Runtime.TradeName, risk, maxRisk); //m_context.Log(msg, MessageType.Info, true); // Надо взять пары, начиная от центральной и далее по возрастанию расстояния var orderedPairs = (from p in optSer.GetStrikePairs() orderby Math.Abs(p.Strike - centralStrike) ascending select p).ToArray(); if (orderedPairs.Length > 0) { foreach (IOptionStrikePair candidPair in orderedPairs) { #region Проверяю, что в страйке есть КОРОТКАЯ позиция double putOpenQty = posMan.GetTotalQty(candidPair.Put.Security, barNum); double callOpenQty = posMan.GetTotalQty(candidPair.Call.Security, barNum); if ((putOpenQty >= 0) && (callOpenQty >= 0)) { continue; } if (DoubleUtil.IsZero(putOpenQty) && DoubleUtil.IsZero(callOpenQty)) { continue; } { msg = String.Format("[{0}:{1}] Strike:{2}; putOpenQty:{3}; callOpenQty:{4}", Context.Runtime.TradeName, GetType().Name, candidPair.Strike, putOpenQty, callOpenQty); m_context.Log(msg, MessageType.Info, true); } #endregion Проверяю, что в страйке есть КОРОТКАЯ позиция double theorPutPx, theorCallPx; { double iv; if ((!sInfo.ContinuousFunction.TryGetValue(candidPair.Strike, out iv)) || Double.IsNaN(iv) || (iv < Double.Epsilon)) { msg = String.Format("[{0}.{1}] Unable to get IV at strike {2}. IV:{3}", Context.Runtime.TradeName, GetType().Name, candidPair.Strike, iv); m_context.Log(msg, MessageType.Error, true); continue; } theorPutPx = FinMath.GetOptionPrice(futPx, candidPair.Strike, dT, iv, 0, false); theorCallPx = FinMath.GetOptionPrice(futPx, candidPair.Strike, dT, iv, 0, true); } #region Сдаём риск (один квант объёма за раз) double putPx, callPx; { DateTime putTime, callTime; double putAskQty, callAskQty; putPx = IvSmile.GetOptPrice(m_context, futPx, candidPair.Put, OptionPxMode.Ask, 0, 0, out putAskQty, out putTime); callPx = IvSmile.GetOptPrice(m_context, futPx, candidPair.Call, OptionPxMode.Ask, 0, 0, out callAskQty, out callTime); } //if (m_optionType == StrikeType.Put) //{ // #region В путах // if (putOpenQty < 0) // Это означает, что в страйке есть короткие путы // { // ISecurity sec = candidPair.Put.Security; // double px = SafeMinPrice(theorPutPx + m_exitShift * sec.Tick, putPx, sec); // double iv = FinMath.GetOptionSigma(futPx, candidPair.Strike, dT, px, 0, false); // double qty = Math.Min(Math.Abs(m_fixedQty), Math.Abs(putOpenQty)); // string sigName = String.Format("Risk:{0}; MaxRisk:{1}; Px:{2}; Qty:{3}; IV:{4:P2}; dT:{5}", risk, maxRisk, px, qty, iv, dT); // posMan.BuyAtPrice(m_context, sec, qty, px, "Close BUY", sigName); // m_context.Log(sigName, MessageType.Info, false); // // Выход из foreach (IOptionStrikePair candidPair in orderedPairs) // break; // } // #endregion В путах //} //else if (m_optionType == StrikeType.Call) //{ // #region В колах // if (callOpenQty < 0) // Это означает, что в страйке есть короткие колы // { // ISecurity sec = candidPair.Call.Security; // double px = SafeMinPrice(theorCallPx + m_exitShift * sec.Tick, callPx, sec); // double iv = FinMath.GetOptionSigma(futPx, candidPair.Strike, dT, px, 0, true); // double qty = Math.Min(Math.Abs(m_fixedQty), Math.Abs(callOpenQty)); // string sigName = String.Format("Risk:{0}; MaxRisk:{1}; Px:{2}; Qty:{3}; IV:{4:P2}; dT:{5}", risk, maxRisk, px, qty, iv, dT); // posMan.BuyAtPrice(m_context, sec, qty, px, "Close BUY", sigName); // m_context.Log(sigName, MessageType.Info, false); // // Выход из foreach (IOptionStrikePair candidPair in orderedPairs) // break; // } // #endregion В колах //} //else { #region В оба вида опционов сразу встаю int executedQty = 0; if (putOpenQty < 0) // Это означает, что в страйке есть короткие путы { ISecurity sec = candidPair.Put.Security; double px = SellOptions.SafeMinPrice(theorPutPx + m_exitShift * sec.Tick, putPx, sec); double iv = FinMath.GetOptionSigma(futPx, candidPair.Strike, dT, px, 0, false); double qty = Math.Min(Math.Abs(m_fixedQty), Math.Abs(putOpenQty)); string sigName = String.Format("Risk:{0}; MaxRisk:{1}; Px:{2}; Qty:{3}; IV:{4:P2}; dT:{5}", risk, maxRisk, px, qty, iv, dT); posMan.BuyAtPrice(m_context, sec, qty, px, "Close BUY", sigName); m_context.Log(sigName, MessageType.Info, false); executedQty += (int)qty; } if ((callOpenQty < 0) && // Это означает, что в страйке есть короткие колы (Math.Abs(executedQty) < Math.Abs(m_fixedQty))) { ISecurity sec = candidPair.Call.Security; double px = SellOptions.SafeMinPrice(theorCallPx + m_exitShift * sec.Tick, callPx, sec); double iv = FinMath.GetOptionSigma(futPx, candidPair.Strike, dT, px, 0, true); double qty = Math.Min(Math.Abs(m_fixedQty) - Math.Abs(executedQty), Math.Abs(callOpenQty)); string sigName = String.Format("Risk:{0}; MaxRisk:{1}; Px:{2}; Qty:{3}; IV:{4:P2}; dT:{5}", risk, maxRisk, px, qty, iv, dT); posMan.BuyAtPrice(m_context, sec, qty, px, "Close BUY", sigName); m_context.Log(sigName, MessageType.Info, false); executedQty += (int)qty; } if (executedQty > 0) { // Выход из foreach (IOptionStrikePair candidPair in orderedPairs) break; } #endregion В оба вида опционов сразу встаю } #endregion Сдаём риск (один квант объёма за раз) } } else { msg = String.Format("[{0}.{1}] risk:{2}; maxRisk:{3}; orderedPairs.Length:{4}", Context.Runtime.TradeName, GetType().Name, risk, maxRisk, orderedPairs.Length); m_context.Log(msg, MessageType.Warning, true); } } return(Constants.NaN); }
private int FillEditableCurve(SmileInfo info, List <InteractiveObject> controlPoints, List <double> xs, DragableMode dragMode) { if (xs.Count < 2) { string msg = String.Format("[DEBUG:{0}] Not enough points in argument xs. xs.Count:{0}", xs.Count); m_context.Log(msg, MessageType.Warning, true); return(0); } int j = 0; double k = xs[0]; double strikeStep = (xs[xs.Count - 1] - xs[0]) / (xs.Count - 1); while (k <= xs[xs.Count - 1]) { if ((j < xs.Count) && (k <= xs[j]) && (xs[j] < k + strikeStep)) { #region Узел должен быть показан и доступен для редактирования { double sigma = info.ContinuousFunction.Value(xs[j]); InteractivePointActive ip = new InteractivePointActive(xs[j], sigma); ip.IsActive = true; ip.Geometry = Geometries.Ellipse; ip.DragableMode = dragMode; ip.Color = AlphaColors.GreenYellow; ip.Tooltip = String.Format("K:{0}; IV:{1:0.00}", xs[j], Constants.PctMult * sigma); InteractiveObject obj = new InteractiveObject(ip); controlPoints.Add(obj); } #endregion Узел должен быть показан и доступен для редактирования j++; } else { #region Просто точка без маркера { double sigma = info.ContinuousFunction.Value(k); //InteractivePointActive ip = new InteractivePointActive(k, sigma); //ip.IsActive = false; //ip.Geometry = Geometries.Ellipse; //ip.DragableMode = DragableMode.None; //ip.Color = System.Windows.Media.Colors.Green; //ip.Tooltip = String.Format("K:{0}; IV:{1:0.00}", k, Constants.PctMult * sigma); InteractivePointLight ip = new InteractivePointLight(k, sigma); InteractiveObject obj = new InteractiveObject(ip); controlPoints.Add(obj); } #endregion Просто точка без маркера } k += strikeStep; } return(j); }
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); }
/// <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); }
public InteractiveSeries Execute(InteractiveSeries deltaProfile, int barNum) { //InteractiveSeries res = context.LoadObject(cashKey + "positionDelta") as InteractiveSeries; //if (res == null) //{ // res = new InteractiveSeries(); // context.StoreObject(cashKey + "positionDelta", res); //} InteractiveSeries res = new InteractiveSeries(); int barsCount = ContextBarsCount; if (barNum < barsCount - 1) { return(res); } if (deltaProfile == null) { return(res); } SmileInfo sInfo = deltaProfile.GetTag <SmileInfo>(); if ((sInfo == null) || (sInfo.ContinuousFunction == null) || (sInfo.ContinuousFunctionD1 == null)) { return(res); } List <double> xs = new List <double>(); List <double> ys = new List <double>(); var deltaPoints = deltaProfile.ControlPoints; List <InteractiveObject> controlPoints = new List <InteractiveObject>(); foreach (InteractiveObject iob in deltaPoints) { double rawGamma, f = iob.Anchor.ValueX; if (sInfo.ContinuousFunctionD1.TryGetValue(f, out rawGamma)) { InteractivePointActive ip = new InteractivePointActive(); ip.IsActive = m_showNodes; //ip.DragableMode = DragableMode.None; //ip.Geometry = Geometries.Rect; //ip.Color = System.Windows.Media.Colors.Orange; double y = rawGamma; ip.Value = new Point(f, y); string yStr = y.ToString(m_tooltipFormat, CultureInfo.InvariantCulture); ip.Tooltip = String.Format(CultureInfo.InvariantCulture, "F:{0}; G:{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) { SmileInfo info = new SmileInfo(); info.F = sInfo.F; info.dT = sInfo.dT; info.RiskFreeRate = sInfo.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); }
internal static bool TryEstimateDelta(double putQty, double callQty, IOptionSeries optSer, IOptionStrikePair pair, InteractiveSeries smile, NumericalGreekAlgo greekAlgo, double f, double dF, double timeToExpiry, double riskFreeRate, out double rawDelta) { rawDelta = Double.NaN; if (timeToExpiry < Double.Epsilon) { throw new ArgumentOutOfRangeException("timeToExpiry", "timeToExpiry must be above zero. timeToExpiry:" + timeToExpiry); } SmileInfo sInfo = smile.Tag as SmileInfo; double pnl1 = 0; // Флаг того, что ПНЛ по всем инструментам был расчитан верно bool pnlIsCorrect1 = true; { if (sInfo != null) { SmileInfo actualSmile = SingleSeriesProfile.GetActualSmile(sInfo, greekAlgo, f - dF); double pairPnl; pnlIsCorrect1 &= SingleSeriesProfile.TryGetPairPrice( putQty, callQty, actualSmile, pair, f - dF, timeToExpiry, riskFreeRate, out pairPnl); pnl1 += pairPnl; } else { // PROD-5746 -- Убираю использование старого неэффективного кода pnlIsCorrect1 = false; Contract.Assert(pnlIsCorrect1, $"[{nameof(OptionsBoardNumericalDelta)}.{nameof(TryEstimateDelta)}] #1 Каким образом получили неподготовленную улыбку? (sInfo == null)"); //InteractiveSeries actualSmile = SingleSeriesProfile.GetActualSmile(smile, greekAlgo, f - dF); //double pairPnl; //pnlIsCorrect1 &= SingleSeriesProfile.TryGetPairPrice( // putQty, callQty, actualSmile, pair, f - dF, timeToExpiry, riskFreeRate, out pairPnl); //pnl1 += pairPnl; } } double pnl2 = 0; // Флаг того, что ПНЛ по всем инструментам был расчитан верно bool pnlIsCorrect2 = true; { if (sInfo != null) { SmileInfo actualSmile = SingleSeriesProfile.GetActualSmile(sInfo, greekAlgo, f + dF); double pairPnl; pnlIsCorrect2 &= SingleSeriesProfile.TryGetPairPrice( putQty, callQty, actualSmile, pair, f + dF, timeToExpiry, riskFreeRate, out pairPnl); pnl2 += pairPnl; } else { // PROD-5746 -- Убираю использование старого неэффективного кода pnlIsCorrect2 = false; Contract.Assert(pnlIsCorrect2, $"[{nameof(OptionsBoardNumericalDelta)}.{nameof(TryEstimateDelta)}] #2 Каким образом получили неподготовленную улыбку? (sInfo == null)"); //InteractiveSeries actualSmile = SingleSeriesProfile.GetActualSmile(smile, greekAlgo, f + dF); //double pairPnl; //pnlIsCorrect2 &= SingleSeriesProfile.TryGetPairPrice( // putQty, callQty, actualSmile, pair, f + dF, timeToExpiry, riskFreeRate, out pairPnl); //pnl2 += pairPnl; } } if (pnlIsCorrect1 && pnlIsCorrect2) { //rawDelta = ((cash2 + pnl2) - (cash1 + pnl1)) / 2.0 / dF; rawDelta = (pnl2 - pnl1) / 2.0 / dF; return(true); } else { return(false); } }
public InteractiveSeries Execute(double time, InteractiveSeries smile, IOptionSeries optSer, double btcUsdIndex, int barNum) { int barsCount = ContextBarsCount; if ((barNum < barsCount - 1) || (smile == null) || (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 dT = time; if (!DoubleUtil.IsPositive(dT)) { // [{0}] Time to expiry must be positive value. dT:{1} string msg = RM.GetStringFormat("OptHandlerMsg.TimeMustBePositive", GetType().Name, dT); if (wasInitialized) { m_context.Log(msg, MessageType.Error, true); } return(Constants.EmptySeries); } if (!DoubleUtil.IsPositive(btcUsdIndex)) { // TODO: сделать ресурс! [{0}] Price of BTC/USD must be positive value. scaleMult:{1} //string msg = RM.GetStringFormat("OptHandlerMsg.CurrencyScaleMustBePositive", GetType().Name, scaleMult); string msg = String.Format("[{0}] Price of BTC/USD must be positive value. scaleMult:{1}", GetType().Name, btcUsdIndex); if (wasInitialized) { 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); if (wasInitialized) { m_context.Log(msg, MessageType.Error, false); } return(Constants.EmptySeries); } double futPx = oldInfo.F; double ivAtm; if (!oldInfo.ContinuousFunction.TryGetValue(futPx, out ivAtm)) { string msg = String.Format("[{0}] Unable to get IV ATM from smile. Tag:{1}", GetType().Name, smile.Tag); if (wasInitialized) { m_context.Log(msg, MessageType.Error, true); } return(Constants.EmptySeries); } 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); } List <double> xs = new List <double>(); List <double> ys = new List <double>(); double futStep = optSer.UnderlyingAsset.Tick; PositionsManager posMan = PositionsManager.GetManager(m_context); ReadOnlyCollection <IPosition> basePositions = posMan.GetClosedOrActiveForBar(optSer.UnderlyingAsset); var optPositions = SingleSeriesProfile.GetAllOptionPositions(posMan, pairs); SortedDictionary <double, IOptionStrikePair> futPrices; if (!SmileImitation5.TryPrepareImportantPoints(pairs, futPx, futStep, -1, out futPrices)) { string msg = String.Format("[{0}] It looks like there is no suitable points for the smile. pairs.Length:{1}", GetType().Name, pairs.Length); if (wasInitialized) { m_context.Log(msg, MessageType.Warning, true); } return(Constants.EmptySeries); } // Чтобы учесть базис между фьючерсом и индексом, вычисляю их отношение: // Пример: BtcInd==9023; FutPx==8937 --> indexDivByFutRatio == 1.009623 double indexDivByFutRatio = btcUsdIndex / futPx; List <InteractiveObject> controlPoints = new List <InteractiveObject>(); // Список точек для вычисления дельт + улыбки для этих точек var deltaPoints = new List <Tuple <double, InteractivePointActive> >(); foreach (var kvp in futPrices) { // Если у нас новая цена фьючерса, значит, будет новая цена индекса double f = kvp.Key; // И при пересчете опционов в баксы НУЖНО ИСПОЛЬЗОВАТЬ ИМЕННО ЕЁ!!! double newScaleMult = f * indexDivByFutRatio; bool tradableStrike = (kvp.Value != null); CashPnlUsd cashPnlUsd; CashPnlBtc cashPnlBtc; GetBasePnl(basePositions, lastBarIndex, f, m_futNominal, out cashPnlUsd, out cashPnlBtc); double cashDollars = cashPnlUsd.CashUsd; double pnlDollars = cashPnlUsd.PnlUsd; double cashBtc = cashPnlBtc.CashBtc; double pnlBtc = cashPnlBtc.PnlBtc; SmileInfo actualSmile = SingleSeriesProfile.GetActualSmile(oldInfo, m_greekAlgo, f); // Флаг того, что ПНЛ по всем инструментам был расчитан верно bool pnlIsCorrect = true; for (int j = 0; (j < pairs.Length) && pnlIsCorrect; j++) { var tuple = optPositions[j]; IOptionStrikePair pair = pairs[j]; //int putBarCount = pair.Put.UnderlyingAsset.Bars.Count; //int callBarCount = pair.Call.UnderlyingAsset.Bars.Count; CashPnlUsd pairCashUsd; CashPnlBtc pairCashBtc; bool localRes = TryGetPairPnl(actualSmile, pair.Strike, lastBarIndex, lastBarIndex, tuple.Item1, tuple.Item2, f, dT, newScaleMult, out pairCashUsd, out pairCashBtc); pnlIsCorrect &= localRes; cashDollars += pairCashUsd.CashUsd; pnlDollars += pairCashUsd.PnlUsd; cashBtc += pairCashBtc.CashBtc; pnlBtc += pairCashBtc.PnlBtc; } // Профиль позиции будет рисоваться только если ПНЛ был посчитан верно по ВСЕМ инструментам! if (pnlIsCorrect) { InteractivePointLight ip; // Показаны будут только узлы, совпадающие с реальными страйками. // Потенциально это позволит сделать эти узлы пригодными для торговли по клику наподобие улыбки. if (m_showNodes && tradableStrike) { // ReSharper disable once UseObjectOrCollectionInitializer InteractivePointActive tmp = new InteractivePointActive(); tmp.IsActive = m_showNodes && tradableStrike; string pnlUsdStr = (cashDollars + pnlDollars).ToString(m_tooltipFormat, CultureInfo.InvariantCulture); string pnlBtcStr = (cashBtc + pnlBtc).ToString(DefaultBtcTooltipFormat, CultureInfo.InvariantCulture); tmp.Tooltip = String.Format(CultureInfo.InvariantCulture, " F: {0}\r\n PnL($): {1}\r\n PnL(B): {2}", f, pnlUsdStr, pnlBtcStr); // Если у нас получился сплайн по профилю позиции, значит мы можем вычислить дельту! if (m_showNodes && tradableStrike) { // Готовим важные точки var tuple = new Tuple <double, InteractivePointActive>(f, tmp); deltaPoints.Add(tuple); } ip = tmp; } else { ip = new InteractivePointLight(); } // PROD-6103 - Выводить профиль позиции в биткойнах if (ProfileAsBtc) { ip.Value = new Point(f, cashBtc + pnlBtc); } else { ip.Value = new Point(f, cashDollars + pnlDollars); } controlPoints.Add(new InteractiveObject(ip)); xs.Add(f); // PROD-6103 - Выводить профиль позиции в биткойнах if (ProfileAsBtc) { ys.Add(cashBtc + pnlBtc); } else { ys.Add(cashDollars + pnlDollars); } } // End if (pnlIsCorrect) } // End foreach (var kvp in futPrices) // ReSharper disable once UseObjectOrCollectionInitializer 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; // Если у нас получился сплайн по профилю позиции, значит мы можем вычислить дельту! for (int j = 1; j < deltaPoints.Count - 1; j++) { var tuple = deltaPoints[j]; double f = tuple.Item1; var ip = tuple.Item2; double actualDelta, deltaLeft, deltaRight; if (m_twoSideDelta) { double prevF = deltaPoints[j - 1].Item1; double nextF = deltaPoints[j + 1].Item1; double currY = deltaPoints[j].Item2.ValueY; double prevY = deltaPoints[j - 1].Item2.ValueY; double nextY = deltaPoints[j + 1].Item2.ValueY; deltaLeft = (currY - prevY) / (f - prevF); deltaRight = (nextY - currY) / (nextF - f); // Считаем дельты слева и справа // Мы передвинули улыбку в точку f и считаем дельту позиции В ЭТОЙ ЖЕ ТОЧКЕ(!) //if (info.ContinuousFunction.TryGetValue(f - 100 * futStep, out deltaLeft) && // info.ContinuousFunctionD1.TryGetValue(f + 100 * futStep, out deltaRight)) { // Первый пробел уже учтен в Tooltip ip.Tooltip = String.Format(CultureInfo.InvariantCulture, "{0}\r\n LeftD: {1:0.0}; RightD: {2:0.0}", ip.Tooltip, deltaLeft, deltaRight); } } else { // Мы передвинули улыбку в точку f и считаем дельту позиции В ЭТОЙ ЖЕ ТОЧКЕ(!) if (info.ContinuousFunctionD1.TryGetValue(f, out actualDelta)) { // Первый пробел уже учтен в Tooltip ip.Tooltip = String.Format(CultureInfo.InvariantCulture, "{0}\r\n D: {1:0.0}", ip.Tooltip, actualDelta); } } } } } catch (Exception ex) { m_context.Log(ex.ToString(), MessageType.Error, true); return(Constants.EmptySeries); } SetHandlerInitialized(now, true); return(res); }
public InteractiveSeries Execute(double price, double time, InteractiveSeries smile, IOptionSeries optSer, double ratePct, int barNum) { int barsCount = ContextBarsCount; if ((barNum < barsCount - 1) || (smile == null) || (optSer == null)) { return(Constants.EmptySeries); } SmileInfo oldInfo = smile.GetTag <SmileInfo>(); if ((oldInfo == null) || (oldInfo.ContinuousFunction == null) || (oldInfo.ContinuousFunctionD1 == 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 (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); if (wasInitialized) { 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); if (wasInitialized) { 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 ivAtm, slopeAtm; if ((!oldInfo.ContinuousFunction.TryGetValue(futPx, out ivAtm)) || (!oldInfo.ContinuousFunctionD1.TryGetValue(futPx, out slopeAtm))) { return(Constants.EmptySeries); } if (m_setIvByHands) { ivAtm = m_ivAtmPct.Value / Constants.PctMult; } if (m_setSlopeByHands) { slopeAtm = m_internalSlopePct.Value / Constants.PctMult; slopeAtm = slopeAtm / futPx / Math.Sqrt(dT); } if (Double.IsNaN(ivAtm) || (ivAtm < Double.Epsilon)) { // [{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); } if (Double.IsNaN(slopeAtm)) { // [{0}] Smile skew at the money must be some number. skewAtm:{1} string msg = RM.GetStringFormat("OptHandlerMsg.SkewAtmMustBeNumber", GetType().Name, slopeAtm); if (wasInitialized) { m_context.Log(msg, MessageType.Error, true); } return(Constants.EmptySeries); } //{ // string msg = String.Format("[DEBUG:{0}] ivAtm:{1}; shift:{2}; depth:{3}; F:{4}; dT:{5}; ", // GetType().Name, ivAtm, shift, depth, F, dT); // context.Log(msg, MessageType.Info, true); //} SmileFunction3 tempFunc = new SmileFunction3(ivAtm, m_shift, 0.5, futPx, dT); double depth = tempFunc.GetDepthUsingSlopeATM(slopeAtm); SmileFunction3 smileFunc = new SmileFunction3(ivAtm, m_shift, depth, futPx, dT); if (!m_setIvByHands) { m_ivAtmPct.Value = ivAtm * Constants.PctMult; } m_depthPct.Value = depth * Constants.PctMult; if (!m_setSlopeByHands) { double dSigmaDx = slopeAtm * futPx * Math.Sqrt(dT); m_internalSlopePct.Value = dSigmaDx * Constants.PctMult; } List <double> xs = new List <double>(); List <double> ys = new List <double>(); 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 minK = pairs[0].Strike; double dK = pairs[1].Strike - pairs[0].Strike; double width = (SigmaMult * ivAtm * Math.Sqrt(dT)) * futPx; width = Math.Max(width, 2 * dK); // Generate left invisible tail if (m_generateTails) { GaussSmile.AppendLeftTail(smileFunc, xs, ys, minK, dK, false); } List <InteractiveObject> controlPoints = new List <InteractiveObject>(); for (int j = 0; j < pairs.Length; j++) { bool showPoint = true; IOptionStrikePair pair = pairs[j]; // Сверхдалекие страйки игнорируем if ((pair.Strike < futPx - width) || (futPx + width < pair.Strike)) { showPoint = false; } double k = pair.Strike; double sigma = smileFunc.Value(k); double vol = sigma; if (Double.IsNaN(sigma) || Double.IsInfinity(sigma) || (sigma < Double.Epsilon)) { continue; } InteractivePointActive ip = new InteractivePointActive(k, vol); //ip.Color = (optionPxMode == OptionPxMode.Ask) ? Colors.DarkOrange : Colors.DarkCyan; //ip.DragableMode = DragableMode.None; //ip.Geometry = Geometries.Rect; // (optionPxMode == OptionPxMode.Ask) ? Geometries.Rect : Geometries.Rect; // Иначе неправильно выставляются координаты??? //ip.Tooltip = String.Format("K:{0}; IV:{1:0.00}", k, PctMult * sigma); if (showPoint) { if (k <= futPx) // Puts { FillNodeInfo(ip, futPx, dT, pair, StrikeType.Put, OptionPxMode.Mid, sigma, false, m_isVisiblePoints, ratePct); } else // Calls { FillNodeInfo(ip, futPx, dT, pair, StrikeType.Call, OptionPxMode.Mid, sigma, false, m_isVisiblePoints, ratePct); } } InteractiveObject obj = new InteractiveObject(ip); if (showPoint) { controlPoints.Add(obj); } xs.Add(k); ys.Add(vol); } // ReSharper disable once UseObjectOrCollectionInitializer InteractiveSeries res = new InteractiveSeries(); res.ControlPoints = new ReadOnlyCollection <InteractiveObject>(controlPoints); double maxK = pairs[pairs.Length - 1].Strike; // Generate right invisible tail if (m_generateTails) { GaussSmile.AppendRightTail(smileFunc, xs, ys, maxK, dK, false); } 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 = dT; info.Expiry = optSer.ExpirationDate; info.ScriptTime = scriptTime; info.RiskFreeRate = ratePct; info.BaseTicker = baseSec.Symbol; info.ContinuousFunction = smileFunc; info.ContinuousFunctionD1 = smileFunc.DeriveD1(); res.Tag = info; SetHandlerInitialized(now); return(res); }
/// <summary> /// Основной метод, который выполняет всю торговую логику по котированию и следит за риском /// </summary> protected double Process(double entryPermission, double centralStrike, double risk, double maxRisk, InteractiveSeries smile, IOptionSeries optSer, double callRisk, double putRisk, int barNum) { int barsCount = m_context.BarsCount; if (!m_context.IsLastBarUsed) { barsCount--; } if ((barNum < barsCount - 1) || (optSer == null) || (smile == null)) { return(Constants.NaN); } { IOptionStrikePair testPair; if (!optSer.TryGetStrikePair(centralStrike, out testPair)) { return(Constants.NaN); } } // Если риск не был измерен говорить вообще не о чем! if (Double.IsNaN(risk)) { return(Constants.NaN); } // Если риск разумен и условие входа НЕ ВЫПОЛНЕНО -- отдыхаем. // А вот если риск превышен -- тогда по идее надо бы его подсократить! if ((risk < maxRisk) && (entryPermission <= 0)) { return(Constants.NaN); } PositionsManager posMan = PositionsManager.GetManager(m_context); if (posMan.BlockTrading) { //string msg = String.Format("Trading is blocked. Please, change 'Block Trading' parameter."); //m_context.Log(msg, MessageType.Info, true); return(Constants.NaN); } SmileInfo sInfo = smile.GetTag <SmileInfo>(); if ((sInfo == null) || (sInfo.ContinuousFunction == null)) { return(Constants.NaN); } double dT = sInfo.dT; double futPx = sInfo.F; // Набираем риск if (risk < maxRisk) { // Надо взять пары, начиная от центральной и далее по возрастанию расстояния с учетом шага страйков IOptionStrikePair[] orderedPairs; if (m_strikeStep < Double.Epsilon) { // Просто сортируем страйки по расстоянию до Центра orderedPairs = (from p in optSer.GetStrikePairs() orderby Math.Abs(p.Strike - centralStrike) ascending select p).ToArray(); } else { // Сортировка по возрастанию до Центра + обязательно условие кратности параметру m_strikeStep orderedPairs = (from p in optSer.GetStrikePairs() let dK = Math.Abs(p.Strike - centralStrike) let dKStep = (int)Math.Round(dK / m_strikeStep) where DoubleUtil.AreClose(dK, m_strikeStep * dKStep) // проверяем, что расстояние от страйка до центра кратно m_strikeStep orderby Math.Abs(p.Strike - centralStrike) ascending select p).ToArray(); } Contract.Assert(m_strikeAmount >= 0, "Как получился отрицательный m_strikeAmount??? m_strikeAmount: " + m_strikeAmount); // Защита от дурака? Или не надо париться? m_strikeAmount = Math.Max(0, m_strikeAmount); // Котируем либо 1 центральный страйк либо центр + четное число соседей int maxStrikeCount = 2 * m_strikeAmount + 1; int strikeCounter = 0; // Сколько лотов уже выставлено в рынок double pendingQty = 0; if (orderedPairs.Length > 0) { foreach (IOptionStrikePair candidPair in orderedPairs) { if (strikeCounter >= maxStrikeCount) { // Все, выходим. Цикл завершен. break; } double ivAtm; double strike = candidPair.Strike; if ((!sInfo.ContinuousFunction.TryGetValue(strike, out ivAtm)) || Double.IsNaN(ivAtm) || (ivAtm < Double.Epsilon)) { string msg = String.Format("[{0}.{1}] Unable to get IV at strike {2}. ivAtm:{3}", Context.Runtime.TradeName, GetType().Name, candidPair.Strike, ivAtm); m_context.Log(msg, MessageType.Error, true); return(Constants.NaN); } double theorPutPx = FinMath.GetOptionPrice(futPx, strike, dT, ivAtm, 0, false); double theorCallPx = FinMath.GetOptionPrice(futPx, strike, dT, ivAtm, 0, true); #region Набираем риск double putPx, callPx; { double putQty, callQty; DateTime putTime, callTime; putPx = IvSmile.GetOptPrice(m_context, futPx, candidPair.Put, OptionPxMode.Bid, 0, 0, out putQty, out putTime); callPx = IvSmile.GetOptPrice(m_context, futPx, candidPair.Call, OptionPxMode.Bid, 0, 0, out callQty, out callTime); } if ((m_optionType == StrikeType.Put) || (m_optionType == StrikeType.Any) && (strike <= futPx)) { #region В путах ISecurity sec = candidPair.Put.Security; double qty = Math.Abs(m_fixedQty); // TODO: Немного грубая оценка, но пока сойдет qty = BuyOptions.GetSafeQty(risk + pendingQty * putRisk, maxRisk, qty, putRisk); if (qty > 0) { double px = SellOptions.SafeMaxPrice(theorPutPx + m_entryShift * sec.Tick, putPx, sec); double iv = FinMath.GetOptionSigma(futPx, candidPair.Strike, dT, px, 0, false); string sigName = String.Format("Risk:{0}; MaxRisk:{1}; Px:{2}; Qty:{3}; IV:{4:P2}; dT:{5}", risk, maxRisk, px, qty, iv, dT); posMan.SellAtPrice(m_context, sec, qty, px, "Open SELL", sigName); pendingQty += qty; m_context.Log(sigName, MessageType.Info, false); } #endregion В путах } else if ((m_optionType == StrikeType.Call) || (m_optionType == StrikeType.Any) && (futPx <= strike)) { #region В колах ISecurity sec = candidPair.Call.Security; double qty = Math.Abs(m_fixedQty); // TODO: Немного грубая оценка, но пока сойдет qty = BuyOptions.GetSafeQty(risk + pendingQty * callRisk, maxRisk, qty, callRisk); if (qty > 0) { double px = SellOptions.SafeMaxPrice(theorCallPx + m_entryShift * sec.Tick, callPx, sec); double iv = FinMath.GetOptionSigma(futPx, candidPair.Strike, dT, px, 0, true); string sigName = String.Format("Risk:{0}; MaxRisk:{1}; Px:{2}; Qty:{3}; IV:{4:P2}; dT:{5}", risk, maxRisk, px, qty, iv, dT); posMan.SellAtPrice(m_context, sec, qty, px, "Open SELL", sigName); pendingQty += qty; m_context.Log(sigName, MessageType.Info, false); } #endregion В колах } else { // Вроде бы, сюда не должны приходить никогда?.. #region В оба вида опционов сразу встаю int executedQty = 0; { ISecurity sec = candidPair.Put.Security; double px = SellOptions.SafeMaxPrice(theorPutPx + m_entryShift * sec.Tick, putPx, sec); double iv = FinMath.GetOptionSigma(futPx, candidPair.Strike, dT, px, 0, false); double qty = Math.Max(1, Math.Abs(m_fixedQty / 2)); // TODO: Немного грубая оценка, но пока сойдет qty = BuyOptions.GetSafeQty(risk + pendingQty * putRisk, maxRisk, qty, putRisk); if (qty > 0) { string sigName = String.Format("Risk:{0}; MaxRisk:{1}; Px:{2}; Qty:{3}; IV:{4:P2}; dT:{5}", risk, maxRisk, px, qty, iv, dT); posMan.SellAtPrice(m_context, sec, qty, px, "Open SELL", sigName); pendingQty += qty; m_context.Log(sigName, MessageType.Info, false); executedQty += (int)qty; } } if (Math.Abs(executedQty) < Math.Abs(m_fixedQty)) { ISecurity sec = candidPair.Call.Security; double px = SellOptions.SafeMaxPrice(theorCallPx + m_entryShift * sec.Tick, callPx, sec); double iv = FinMath.GetOptionSigma(futPx, candidPair.Strike, dT, px, 0, true); double qty = Math.Abs(m_fixedQty) - Math.Abs(executedQty); // TODO: Немного грубая оценка, но пока сойдет // Делаю оценку изменения текущего риска, если нам зафилят заявку в путах //qty = BuyOptions.GetSafeQty(risk + Math.Abs(executedQty) * putRisk, maxRisk, qty, callRisk); // Причем здесь уже не нужно отдельно учитывать executedQty, потому что он входит в pendingQty qty = BuyOptions.GetSafeQty(risk + pendingQty * callRisk, maxRisk, qty, callRisk); if (qty > 0) { string sigName = String.Format("Risk:{0}; MaxRisk:{1}; Px:{2}; Qty:{3}; IV:{4:P2}; dT:{5}", risk, maxRisk, px, qty, iv, dT); posMan.SellAtPrice(m_context, sec, qty, px, "Open SELL", sigName); pendingQty += qty; m_context.Log(sigName, MessageType.Info, false); //executedQty += (int)qty; } } #endregion В оба вида опционов сразу встаю } #endregion Набираем риск strikeCounter++; } // End foreach (IOptionStrikePair candidPair in orderedPairs) } else { string msg = String.Format("[{0}] Strike not found. risk:{1}; maxRisk:{2}; orderedPairs.Length:{3}", Context.Runtime.TradeName, risk, maxRisk, orderedPairs.Length); m_context.Log(msg, MessageType.Warning, true); } } else if (risk > maxRisk) { string msg; //string msg = String.Format("[DEBUG:{0}] risk:{1}; maxRisk:{2}", Context.Runtime.TradeName, risk, maxRisk); //m_context.Log(msg, MessageType.Info, true); // Надо взять пары, начиная от центральной и далее по возрастанию расстояния var orderedPairs = (from p in optSer.GetStrikePairs() orderby Math.Abs(p.Strike - centralStrike) ascending select p).ToArray(); if (orderedPairs.Length > 0) { foreach (IOptionStrikePair candidPair in orderedPairs) { #region Проверяю, что в страйке есть КОРОТКАЯ позиция double putOpenQty = posMan.GetTotalQty(candidPair.Put.Security, barNum); double callOpenQty = posMan.GetTotalQty(candidPair.Call.Security, barNum); if ((putOpenQty >= 0) && (callOpenQty >= 0)) { continue; } if (DoubleUtil.IsZero(putOpenQty) && DoubleUtil.IsZero(callOpenQty)) { continue; } { msg = String.Format("[{0}:{1}] Strike:{2}; putOpenQty:{3}; callOpenQty:{4}", Context.Runtime.TradeName, GetType().Name, candidPair.Strike, putOpenQty, callOpenQty); m_context.Log(msg, MessageType.Info, true); } #endregion Проверяю, что в страйке есть КОРОТКАЯ позиция double theorPutPx, theorCallPx; { double iv; if ((!sInfo.ContinuousFunction.TryGetValue(candidPair.Strike, out iv)) || Double.IsNaN(iv) || (iv < Double.Epsilon)) { msg = String.Format("[{0}.{1}] Unable to get IV at strike {2}. IV:{3}", Context.Runtime.TradeName, GetType().Name, candidPair.Strike, iv); m_context.Log(msg, MessageType.Error, true); continue; } theorPutPx = FinMath.GetOptionPrice(futPx, candidPair.Strike, dT, iv, 0, false); theorCallPx = FinMath.GetOptionPrice(futPx, candidPair.Strike, dT, iv, 0, true); } #region Сдаём риск (один квант объёма за раз) double putPx, callPx; { DateTime putTime, callTime; double putAskQty, callAskQty; putPx = IvSmile.GetOptPrice(m_context, futPx, candidPair.Put, OptionPxMode.Ask, 0, 0, out putAskQty, out putTime); callPx = IvSmile.GetOptPrice(m_context, futPx, candidPair.Call, OptionPxMode.Ask, 0, 0, out callAskQty, out callTime); } if (m_optionType == StrikeType.Put) { #region В путах if (putOpenQty < 0) // Это означает, что в страйке есть короткие путы { ISecurity sec = candidPair.Put.Security; double px = SellOptions.SafeMinPrice(theorPutPx + m_exitShift * sec.Tick, putPx, sec); double iv = FinMath.GetOptionSigma(futPx, candidPair.Strike, dT, px, 0, false); double qty = Math.Min(Math.Abs(m_fixedQty), Math.Abs(putOpenQty)); string sigName = String.Format("Risk:{0}; MaxRisk:{1}; Px:{2}; Qty:{3}; IV:{4:P2}; dT:{5}", risk, maxRisk, px, qty, iv, dT); posMan.BuyAtPrice(m_context, sec, qty, px, "Close BUY", sigName); m_context.Log(sigName, MessageType.Info, false); // Выход из foreach (IOptionStrikePair candidPair in orderedPairs) break; } #endregion В путах } else if (m_optionType == StrikeType.Call) { #region В колах if (callOpenQty < 0) // Это означает, что в страйке есть короткие колы { ISecurity sec = candidPair.Call.Security; double px = SellOptions.SafeMinPrice(theorCallPx + m_exitShift * sec.Tick, callPx, sec); double iv = FinMath.GetOptionSigma(futPx, candidPair.Strike, dT, px, 0, true); double qty = Math.Min(Math.Abs(m_fixedQty), Math.Abs(callOpenQty)); string sigName = String.Format("Risk:{0}; MaxRisk:{1}; Px:{2}; Qty:{3}; IV:{4:P2}; dT:{5}", risk, maxRisk, px, qty, iv, dT); posMan.BuyAtPrice(m_context, sec, qty, px, "Close BUY", sigName); m_context.Log(sigName, MessageType.Info, false); // Выход из foreach (IOptionStrikePair candidPair in orderedPairs) break; } #endregion В колах } else { #region В оба вида опционов сразу встаю int executedQty = 0; if (putOpenQty < 0) // Это означает, что в страйке есть короткие путы { ISecurity sec = candidPair.Put.Security; double px = SellOptions.SafeMinPrice(theorPutPx + m_exitShift * sec.Tick, putPx, sec); double iv = FinMath.GetOptionSigma(futPx, candidPair.Strike, dT, px, 0, false); double qty = Math.Min(Math.Abs(m_fixedQty), Math.Abs(putOpenQty)); string sigName = String.Format("Risk:{0}; MaxRisk:{1}; Px:{2}; Qty:{3}; IV:{4:P2}; dT:{5}", risk, maxRisk, px, qty, iv, dT); posMan.BuyAtPrice(m_context, sec, qty, px, "Close BUY", sigName); m_context.Log(sigName, MessageType.Info, false); executedQty += (int)qty; } if ((callOpenQty < 0) && // Это означает, что в страйке есть короткие колы (Math.Abs(executedQty) < Math.Abs(m_fixedQty))) { ISecurity sec = candidPair.Call.Security; double px = SellOptions.SafeMinPrice(theorCallPx + m_exitShift * sec.Tick, callPx, sec); double iv = FinMath.GetOptionSigma(futPx, candidPair.Strike, dT, px, 0, true); double qty = Math.Min(Math.Abs(m_fixedQty) - Math.Abs(executedQty), Math.Abs(callOpenQty)); string sigName = String.Format("Risk:{0}; MaxRisk:{1}; Px:{2}; Qty:{3}; IV:{4:P2}; dT:{5}", risk, maxRisk, px, qty, iv, dT); posMan.BuyAtPrice(m_context, sec, qty, px, "Close BUY", sigName); m_context.Log(sigName, MessageType.Info, false); executedQty += (int)qty; } if (executedQty > 0) { // Выход из foreach (IOptionStrikePair candidPair in orderedPairs) break; } #endregion В оба вида опционов сразу встаю } #endregion Сдаём риск (один квант объёма за раз) } } else { msg = String.Format("[{0}.{1}] risk:{2}; maxRisk:{3}; orderedPairs.Length:{4}", Context.Runtime.TradeName, GetType().Name, risk, maxRisk, orderedPairs.Length); m_context.Log(msg, MessageType.Warning, true); } } return(Constants.NaN); }
public InteractiveSeries Execute(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 dT = time; if (!DoubleUtil.IsPositive(dT)) { // [{0}] Time to expiry must be positive value. dT:{1} string msg = RM.GetStringFormat("OptHandlerMsg.TimeMustBePositive", GetType().Name, dT); if (wasInitialized) { m_context.Log(msg, MessageType.Error, true); } return(Constants.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, 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); if (wasInitialized) { m_context.Log(msg, MessageType.Error, false); } return(Constants.EmptySeries); } double futPx; if (DoubleUtil.IsPositive(oldInfo.F)) { futPx = oldInfo.F; } else { futPx = optSer.UnderlyingAsset.Bars[Math.Min(barNum, lastBarIndex)].Close; } IOptionStrikePair[] pairs = optSer.GetStrikePairs().ToArray(); PositionsManager posMan = PositionsManager.GetManager(m_context); List <InteractiveObject> controlPoints = new List <InteractiveObject>(); if (m_countFutures) { ReadOnlyCollection <IPosition> futPositions = posMan.GetActiveForBar(optSer.UnderlyingAsset); if (futPositions.Count > 0) { double futQty; GetTotalQty(futPositions, out futQty); if (!DoubleUtil.IsZero(futQty)) { // ReSharper disable once UseObjectOrCollectionInitializer InteractivePointActive ip = new InteractivePointActive(0, futQty); ip.IsActive = true; //ip.DragableMode = DragableMode.None; //ip.Geometry = Geometries.Rect; //ip.Color = Colors.DarkOrange; //// PROD-1194 - Цвет ячеек и фона //if (futQty > 0) // ip.BackColor = ScriptColors.Aquamarine; //else if (futQty < 0) // ip.BackColor = ScriptColors.LightSalmon; //if (ip.BackColor != null) // ip.ForeColor = ScriptColors.Black; // TODO: Ждем решения PROD-6009 //// PROD-1194 - Цвет ячеек и фона -- фон красится по принципу "в деньгах или нет", шрифт -- по знаку //if (futQty > 0) // ip.ForeColor = ScriptColors.LightGreen; //else if (futQty < 0) // ip.ForeColor = ScriptColors.Red; ////if () // Фьючерс не красится! //// ip.ForeColor = ScriptColors.Black; ip.Tooltip = String.Format(CultureInfo.InvariantCulture, "Fut Qty:{0}", futQty); controlPoints.Add(new InteractiveObject(ip)); } } } for (int j = 0; j < pairs.Length; j++) { IOptionStrikePair pair = pairs[j]; double putQty, callQty; GetPairQty(posMan, pair, out putQty, out callQty); if ((!DoubleUtil.IsZero(putQty)) || (!DoubleUtil.IsZero(callQty))) { double y = 0; Color? backCol = null; switch (m_optionType) { case StrikeType.Put: y = putQty; backCol = (pair.Strike > futPx) ? ScriptColors.LightCyan : ScriptColors.LightYellow; break; case StrikeType.Call: y = callQty; backCol = (pair.Strike < futPx) ? ScriptColors.LightCyan : ScriptColors.LightYellow; break; case StrikeType.Any: y = putQty + callQty; break; default: throw new NotImplementedException("OptionType: " + m_optionType); } if ( (!DoubleUtil.IsZero(y)) || ((m_optionType == StrikeType.Any) && ((!DoubleUtil.IsZero(putQty)) || (!DoubleUtil.IsZero(callQty))))) // Хотя вообще-то мы внутри if и второй блок проверок всегда true... { // ReSharper disable once UseObjectOrCollectionInitializer InteractivePointActive ip = new InteractivePointActive(pair.Strike, y); ip.IsActive = true; //ip.DragableMode = DragableMode.None; //ip.Geometry = Geometries.Rect; //ip.Color = Colors.DarkOrange; //// PROD-1194 - Цвет ячеек и фона //if (y > 0) // ip.BackColor = ScriptColors.Aquamarine; //else if (y < 0) // ip.BackColor = ScriptColors.LightSalmon; //if (ip.BackColor != null) // ip.ForeColor = ScriptColors.Black; // TODO: Ждем решения PROD-6009 //// PROD-1194 - Цвет ячеек и фона -- фон красится по принципу "в деньгах или нет", шрифт -- по знаку //if (y > 0) // ip.ForeColor = ScriptColors.LightGreen; //else if (y < 0) // ip.ForeColor = ScriptColors.Red; ////if (backCol != null) //// ip.BackColor = backCol; ip.Tooltip = String.Format(CultureInfo.InvariantCulture, "K:{0}; Qty:{1}", pair.Strike, y); controlPoints.Add(new InteractiveObject(ip)); } } } // ReSharper disable once UseObjectOrCollectionInitializer InteractiveSeries res = new InteractiveSeries(); // Здесь правильно делать new res.ControlPoints = new ReadOnlyCollection <InteractiveObject>(controlPoints); SetHandlerInitialized(now); return(res); }
private double Process(InteractiveSeries profile, double moneyness, int barNum) { // В данном случае намеренно возвращаю Double.NaN double failRes = Double.NaN; if (m_repeatLastValue) { failRes = Double.IsNaN(m_prevValue) ? Double.NaN : m_prevValue; // В данном случае намеренно возвращаю Double.NaN } Dictionary <DateTime, double> results; #region Get cache // [2019-01-30] Перевожу на использование NotClearableContainer (PROD-6683) string key = m_variableId + "_results"; var container = m_context.LoadObject(key) as NotClearableContainer <Dictionary <DateTime, double> >; if (container != null) { results = container.Content; } else { results = m_context.LoadObject(key) as Dictionary <DateTime, double>; // Старая ветка на всякий случай } if (results == null) { string msg = String.Format(RM.GetString("OptHandlerMsg.GetValueAtm.CacheNotFound"), GetType().Name, key.GetHashCode()); m_context.Log(msg, MessageType.Info); results = new Dictionary <DateTime, double>(); container = new NotClearableContainer <Dictionary <DateTime, double> >(results); m_context.StoreObject(key, container); } #endregion Get cache int len = m_context.BarsCount; if (len <= 0) { return(failRes); } // Вот так не работает. По всей видимости, это прямая индексация от утра //DateTime now = m_context.Runtime.GetBarTime(barNum); ISecurity sec = m_context.Runtime.Securities.FirstOrDefault(); if ((sec == null) || (sec.Bars.Count <= barNum)) { return(failRes); } DateTime now = sec.Bars[barNum].Date; if (results.TryGetValue(now, out double rawRes) && (barNum < len - 1)) // !!! ВАЖНО !!! На последнем баре ВСЕГДА заново делаем вычисления { m_prevValue = rawRes; return(rawRes); } else { int barsCount = ContextBarsCount; if (barNum < barsCount - 1) { // Если история содержит осмысленное значение, то оно уже содержится в failRes return(failRes); } else { #region Process last bar(s) if (profile == null) { return(failRes); } SmileInfo profInfo = profile.GetTag <SmileInfo>(); if ((profInfo == null) || (profInfo.ContinuousFunction == null)) { return(failRes); } double f = profInfo.F; double dT = profInfo.dT; if (!DoubleUtil.IsPositive(f)) { string msg = String.Format(RM.GetString("OptHandlerMsg.FutPxMustBePositive"), GetType().Name, f); m_context.Log(msg, MessageType.Error); // [GLSP-1557] В данном случае намеренно возвращаю Double.NaN, чтобы предотвратить распространение // заведомо неправильных данных по системе (их попадание в дельта-хеджер и т.п.). return(Double.NaN); } if (!DoubleUtil.IsPositive(dT)) { string msg = String.Format(RM.GetString("OptHandlerMsg.TimeMustBePositive"), GetType().Name, dT); m_context.Log(msg, MessageType.Error); // [GLSP-1557] В данном случае намеренно возвращаю Double.NaN, чтобы предотвратить распространение // заведомо неправильных данных по системе (их попадание в дельта-хеджер и т.п.). return(Double.NaN); } double effectiveF; if (DoubleUtil.IsZero(moneyness)) { effectiveF = f; } else { effectiveF = f * Math.Exp(moneyness * Math.Sqrt(dT)); } if (profInfo.ContinuousFunction.TryGetValue(effectiveF, out rawRes)) { m_prevValue = rawRes; results[now] = rawRes; } else { if (barNum < len - 1) { // [GLSP-1557] Не последний бар? Тогда использую failRes rawRes = failRes; } else { // [GLSP-1557] В данном случае намеренно возвращаю Double.NaN, чтобы предотвратить распространение // заведомо неправильных данных по системе (их попадание в дельта-хеджер и т.п.). return(Double.NaN); } } #endregion Process last bar(s) m_result.Value = rawRes; //m_context.Log(MsgId + ": " + m_result.Value, MessageType.Info, PrintInLog); return(rawRes); } } }
public void Execute(double price, double time, InteractiveSeries smile, ICanvasPane pane, int barNum) { int barsCount = ContextBarsCount; if ((barNum < barsCount - 1) || (smile == null) || (smile.Tag == null)) { return; } // PROD-3577 pane.FeetToBorder2ByDefault = true; SmileInfo smileInfo = smile.GetTag <SmileInfo>(); if ((smileInfo == null) || (smileInfo.ContinuousFunction == null)) { return; } double futPx = price; double dT = time; if (!DoubleUtil.IsPositive(futPx)) { return; } if (!DoubleUtil.IsPositive(dT)) { return; } double sigma; if ((!smileInfo.ContinuousFunction.TryGetValue(futPx, out sigma)) || (!DoubleUtil.IsPositive(sigma))) { //При работе с Эксанте и прочим Западом Биржевой улыбки не будет //Поэтому надо иметь 'План Б': подставить фиксированную волатильность 30%! sigma = DefaultSigma; } if (pane != null) { string expiryStr = smileInfo.Expiry.ToString(TimeToExpiry.DateTimeFormat, CultureInfo.InvariantCulture); Rect rect = PrepareVieportSettings(expiryStr, futPx, dT, sigma); ApplySettings(pane, rect); int cpLen = smile.ControlPoints.Count; if (ManageXGridStep && (cpLen > 1)) { double dK = smile.ControlPoints[1].Anchor.ValueX - smile.ControlPoints[0].Anchor.ValueX; if (cpLen > 2) { int t = cpLen / 2; // Делим нацело. Для cpLen==3 получаем 1 dK = smile.ControlPoints[t + 1].Anchor.ValueX - smile.ControlPoints[t].Anchor.ValueX; } pane.XAxisStep = GetXAxisStep(dK); pane.XAxisDiviser = GetXAxisDivisor(dK); } } }
/// <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> /// Метод под флаг 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> /// Получить финансовые параметры опционной позиции (колы и путы на одном страйке суммарно) /// </summary> /// <param name="smileInfo">улыбка</param> /// <param name="strike">страйк</param> /// <param name="putBarCount">количество баров для пута</param> /// <param name="callBarCount">количество баров для кола</param> /// <param name="putPositions">позиции пута</param> /// <param name="callPositions">позиции кола</param> /// <param name="f">текущая цена БА</param> /// <param name="dT">время до экспирации</param> /// <param name="cash">денежные затраты на формирование позы (могут быть отрицательными)</param> /// <param name="pnl">текущая цена позиции</param> /// <returns>true, если всё посчитано без ошибок</returns> public static bool TryGetPairPnl(SmileInfo smileInfo, double strike, int putBarCount, int callBarCount, IList <IPosition> putPositions, IList <IPosition> callPositions, double f, double dT, double btcUsdInd, out CashPnlUsd cashPnlUsd, out CashPnlBtc cashPnlBtc) { if ((putPositions.Count <= 0) && (callPositions.Count <= 0)) { cashPnlUsd = new CashPnlUsd(); cashPnlBtc = new CashPnlBtc(); return(true); } double?sigma = null; if (smileInfo != null) { double tmp; if (smileInfo.ContinuousFunction.TryGetValue(strike, out tmp)) { sigma = tmp; } } if ((sigma == null) || (!DoubleUtil.IsPositive(sigma.Value))) { cashPnlUsd = new CashPnlUsd(); cashPnlBtc = new CashPnlBtc(); return(false); } double pnlBtc = 0; double pnlUsd = 0; double cashBtc = 0; double cashUsd = 0; if (putPositions.Count > 0) { CashPnlUsd putUsd; CashPnlBtc putBtc; GetOptPnl(putPositions, putBarCount - 1, f, strike, dT, sigma.Value, 0.0, false, btcUsdInd, out putUsd, out putBtc); pnlUsd += putUsd.PnlUsd; cashUsd += putUsd.CashUsd; pnlBtc += putBtc.PnlBtc; cashBtc += putBtc.CashBtc; } if (callPositions.Count > 0) { CashPnlUsd callUsd; CashPnlBtc callBtc; GetOptPnl(callPositions, callBarCount - 1, f, strike, dT, sigma.Value, 0.0, true, btcUsdInd, out callUsd, out callBtc); pnlUsd += callUsd.PnlUsd; cashUsd += callUsd.CashUsd; pnlBtc += callBtc.PnlBtc; cashBtc += callBtc.CashBtc; } cashPnlUsd = new CashPnlUsd(cashUsd, pnlUsd); cashPnlBtc = new CashPnlBtc(cashBtc, pnlBtc); return(true); }
public static SmileInfo FromXElement(XElement xel) { if ((xel == null) || (xel.Name.LocalName != typeof(SmileInfo).FullName)) { return(null); } double futPx, dT, rate; { XAttribute xAttr = xel.Attribute("F"); if ((xAttr == null) || String.IsNullOrWhiteSpace(xAttr.Value) || (!Double.TryParse(xAttr.Value, NumberStyles.Number, CultureInfo.InvariantCulture, out futPx))) { return(null); } } { XAttribute xAttr = xel.Attribute("dT"); if ((xAttr == null) || String.IsNullOrWhiteSpace(xAttr.Value) || (!Double.TryParse(xAttr.Value, NumberStyles.Number, CultureInfo.InvariantCulture, out dT))) { return(null); } } { XAttribute xAttr = xel.Attribute("RiskFreeRate"); if ((xAttr == null) || String.IsNullOrWhiteSpace(xAttr.Value) || (!Double.TryParse(xAttr.Value, NumberStyles.Number, CultureInfo.InvariantCulture, out rate))) { return(null); } } DateTime expiry = new DateTime(), scriptTime = new DateTime(); { XAttribute xAttr = xel.Attribute("Expiry"); if ((xAttr != null) && (!String.IsNullOrWhiteSpace(xAttr.Value))) { DateTime.TryParseExact(xAttr.Value, TimeToExpiry.DateTimeFormat, CultureInfo.InvariantCulture, DateTimeStyles.AssumeLocal, out expiry); } } { XAttribute xAttr = xel.Attribute("ScriptTime"); if ((xAttr != null) && (!String.IsNullOrWhiteSpace(xAttr.Value))) { DateTime.TryParseExact(xAttr.Value, BaseContextHandler.DateTimeFormatWithMs, CultureInfo.InvariantCulture, DateTimeStyles.AssumeLocal, out scriptTime); } } string baseTicker = null; { XAttribute xAttr = xel.Attribute("BaseTicker"); if ((xAttr != null) && (!String.IsNullOrWhiteSpace(xAttr.Value))) { baseTicker = xAttr.Value; } } IFunction func = null; { XElement xCf = (from node in xel.Descendants() where (node.Name.LocalName == "ContinuousFunction") select node).FirstOrDefault(); if (xCf != null) { FunctionDeserializer.TryDeserialize(xCf, out func); } } IFunction funcD1 = null; { XElement xCfD1 = (from node in xel.Descendants() where (node.Name.LocalName == "ContinuousFunctionD1") select node).FirstOrDefault(); if (xCfD1 != null) { FunctionDeserializer.TryDeserialize(xCfD1, out funcD1); } } // ReSharper disable once UseObjectOrCollectionInitializer SmileInfo res = new SmileInfo(); res.F = futPx; res.dT = dT; res.Expiry = expiry; res.ScriptTime = scriptTime; res.RiskFreeRate = rate; res.BaseTicker = baseTicker; res.ContinuousFunction = func; res.ContinuousFunctionD1 = funcD1; #region PROD-2402 - Парсим дополнительные параметры улыбки double ivAtm, skewAtm, shape; { XAttribute xAttr = xel.Attribute("IvAtm"); if ((xAttr == null) || String.IsNullOrWhiteSpace(xAttr.Value) || (!Double.TryParse(xAttr.Value, NumberStyles.Number, CultureInfo.InvariantCulture, out ivAtm))) { ivAtm = Double.NaN; } } { XAttribute xAttr = xel.Attribute("SkewAtm"); if ((xAttr == null) || String.IsNullOrWhiteSpace(xAttr.Value) || (!Double.TryParse(xAttr.Value, NumberStyles.Number, CultureInfo.InvariantCulture, out skewAtm))) { skewAtm = Double.NaN; } } { XAttribute xAttr = xel.Attribute("Shape"); if ((xAttr == null) || String.IsNullOrWhiteSpace(xAttr.Value) || (!Double.TryParse(xAttr.Value, NumberStyles.Number, CultureInfo.InvariantCulture, out shape))) { shape = Double.NaN; } } string smileType; { XAttribute xAttr = xel.Attribute("SmileType"); if ((xAttr == null) || String.IsNullOrWhiteSpace(xAttr.Value)) { smileType = null; } else { smileType = xAttr.Value; } } res.IvAtm = ivAtm; res.SkewAtm = skewAtm; res.Shape = shape; res.SmileType = smileType; // Если улыбка невалидна, значит все равно толку с 'правильных параметров' нет? if (!res.IsValidSmileParams) { res.IvAtm = Double.NaN; res.SkewAtm = Double.NaN; res.Shape = Double.NaN; res.SmileType = null; } #endregion PROD-2402 - Парсим дополнительные параметры улыбки return(res); }
public double Execute(double price, double time, InteractiveSeries smile, IOptionSeries optSer, int barNum) { int barsCount = m_context.BarsCount; if (!m_context.IsLastBarUsed) { barsCount--; } if ((barNum < barsCount - 1) || (optSer == null)) { return(Constants.NaN); } int lastBarIndex = optSer.UnderlyingAsset.Bars.Count - 1; DateTime now = optSer.UnderlyingAsset.Bars[Math.Min(barNum, lastBarIndex)].Date; bool wasInitialized = HandlerInitializedToday(now); double f = price; double dT = time; if (!DoubleUtil.IsPositive(dT)) { // [{0}] Time to expiry must be positive value. dT:{1} string msg = RM.GetStringFormat("OptHandlerMsg.TimeMustBePositive", GetType().Name, dT); if (wasInitialized) { m_context.Log(msg, MessageType.Error, true); } return(Constants.NaN); } if (!DoubleUtil.IsPositive(f)) { // [{0}] Base asset price must be positive value. F:{1} string msg = RM.GetStringFormat("OptHandlerMsg.FutPxMustBePositive", GetType().Name, f); if (wasInitialized) { m_context.Log(msg, MessageType.Error, true); } return(Constants.NaN); } if (smile == null) { string msg = String.Format("[{0}] Argument 'smile' must be filled with InteractiveSeries.", GetType().Name); if (wasInitialized) { m_context.Log(msg, MessageType.Error, true); } return(Constants.NaN); } SmileInfo oldInfo = smile.GetTag <SmileInfo>(); if (oldInfo == null) { string msg = String.Format("[{0}] Property Tag of object smile must be filled with SmileInfo. Tag:{1}", GetType().Name, smile.Tag); if (wasInitialized) { m_context.Log(msg, MessageType.Error, true); } return(Constants.NaN); } double rawDelta, res; double dF = optSer.UnderlyingAsset.Tick; IOptionStrikePair[] pairs = optSer.GetStrikePairs().ToArray(); PositionsManager posMan = PositionsManager.GetManager(m_context); if (SingleSeriesNumericalDelta.TryEstimateDelta(posMan, optSer, pairs, smile, m_greekAlgo, f, dF, dT, out rawDelta)) { res = rawDelta; } else { res = Constants.NaN; } SmileInfo sInfo = smile.GetTag <SmileInfo>(); if ((sInfo != null) && (sInfo.ContinuousFunction != null)) { double iv = sInfo.ContinuousFunction.Value(sInfo.F); string msg = String.Format("[{0}] F:{1}; dT:{2}; smile.F:{3}; smile.dT:{4}; smile.IV:{5}", GetType().Name, f, dT, sInfo.F, sInfo.dT, iv); m_context.Log(msg, MessageType.Info, false); } m_delta.Value = res; //context.Log(String.Format("[{0}] Delta: {1}; rawDelta:{2}", MsgId, delta.Value, rawDelta), logColor, true); if (m_hedgeDelta) { #region Hedge logic try { int rounded = Math.Sign(rawDelta) * ((int)Math.Floor(Math.Abs(rawDelta))); if (rounded == 0) { string msg = String.Format("[{0}] Delta is too low to hedge. Delta: {1}", MsgId, rawDelta); m_context.Log(msg, MessageType.Info, true); } else { int len = optSer.UnderlyingAsset.Bars.Count; ISecurity sec = (from s in m_context.Runtime.Securities where (s.SecurityDescription.Equals(optSer.UnderlyingAsset.SecurityDescription)) select s).SingleOrDefault(); if (sec == null) { string msg = String.Format("[{0}] There is no security. Symbol: {1}", MsgId, optSer.UnderlyingAsset.Symbol); m_context.Log(msg, MessageType.Warning, true); } else { if (rounded < 0) { string signalName = String.Format("\r\nDelta BUY\r\nF:{0}; dT:{1}; Delta:{2}\r\n", f, dT, rawDelta); m_context.Log(signalName, MessageType.Warning, true); posMan.BuyAtPrice(m_context, sec, Math.Abs(rounded), f, signalName, null); } else if (rounded > 0) { string signalName = String.Format("\r\nDelta SELL\r\nF:{0}; dT:{1}; Delta:+{2}\r\n", f, dT, rawDelta); m_context.Log(signalName, MessageType.Warning, true); posMan.SellAtPrice(m_context, sec, Math.Abs(rounded), f, signalName, null); } } } } finally { m_hedgeDelta = false; } #endregion Hedge logic } SetHandlerInitialized(now); return(res); }
public InteractiveSeries Execute(double price, double time, int barNum) { int barsCount = ContextBarsCount; if (barNum < barsCount - 1) { return(Constants.EmptySeries); } double futPx = price; double dT = time; if (Double.IsNaN(dT) || (dT < Double.Epsilon) || Double.IsNaN(futPx) || (futPx < Double.Epsilon)) { return(Constants.EmptySeries); } double width = (SigmaMult * m_sigma * Math.Sqrt(dT)) * futPx; List <InteractiveObject> controlPoints = new List <InteractiveObject>(); int half = NumControlPoints / 2; // Целочисленное деление! double dK = width / half; // Сдвигаю точки, чтобы избежать отрицательных значений while ((futPx - half * dK) <= Double.Epsilon) { half--; } for (int j = 0; j < NumControlPoints; j++) { double k = futPx + (j - half) * dK; InteractivePointLight ip; bool edgePoint = (j == 0) || (j == NumControlPoints - 1); if (m_showNodes || (edgePoint && m_showEdgeLabels)) // На крайние точки повешу Лейблы { InteractivePointActive tmp = new InteractivePointActive(); tmp.IsActive = m_showNodes || (edgePoint && m_showEdgeLabels); //tmp.DragableMode = DragableMode.None; //tmp.Geometry = Geometries.Rect; //tmp.Color = Colors.DarkOrange; tmp.Tooltip = String.Format(CultureInfo.InvariantCulture, m_tooltipFormat, k, m_value * Constants.PctMult); // "K:{0}; V:{1:0.00}%" if (edgePoint) { tmp.Label = String.Format(CultureInfo.InvariantCulture, m_labelFormat, m_value * Constants.PctMult); // "V:{0:0.00}%" } ip = tmp; } else { ip = new InteractivePointLight(); } ip.Value = new Point(k, m_value); controlPoints.Add(new InteractiveObject(ip)); } InteractiveSeries res = new InteractiveSeries(); // Здесь так надо -- мы делаем новую улыбку res.ControlPoints = new ReadOnlyCollection <InteractiveObject>(controlPoints); SmileInfo info = new SmileInfo(); info.F = futPx; info.dT = dT; info.RiskFreeRate = 0; try { ConstantFunction spline = new ConstantFunction(m_value, futPx, dT); 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(double price, InteractiveSeries line, ISecurity sec, int barNum) { int barsCount = m_context.BarsCount; if (!m_context.IsLastBarUsed) { barsCount--; } if (barNum < barsCount - 1) { return(Constants.EmptySeries); } InteractiveSeries res = new InteractiveSeries(); // Здесь так надо -- мы делаем новую улыбку res.ClickEvent += InteractiveSplineOnClickEvent; m_clickableSeries = res; double f = price; double? sigmaAtm = null; SmileInfo sInfo = line.GetTag <SmileInfo>(); if ((sInfo != null) && (sInfo.ContinuousFunction != null)) { double tmp; if (sInfo.ContinuousFunction.TryGetValue(f, out tmp)) { if (!Double.IsNaN(tmp)) { sigmaAtm = tmp; } } } if (sigmaAtm == null) { return(res); } double h = Math.Max(m_minHeight, sigmaAtm.Value * m_offset); // Теперь определяем максимальный размах по вертикали if (line.ControlPoints.Count > 1) { var cps = line.ControlPoints; double min = Double.MaxValue, max = Double.MinValue; for (int j = 0; j < cps.Count; j++) { var anchor = cps[j].Anchor; double y = anchor.ValueY; if (y <= min) { min = y; } if (max <= y) { max = y; } } if ((min < max) && (!Double.IsInfinity(max - min))) { h = Math.Max(h, (max - min) * m_offset); } } List <InteractiveObject> controlPoints = new List <InteractiveObject>(); { InteractivePointActive ip = new InteractivePointActive(); ip.IsActive = true; ip.DragableMode = DragableMode.None; ip.Geometry = Geometries.Ellipse; // Geometries.TriangleDown; ip.Color = AlphaColors.Magenta; double y = (sigmaAtm.Value - h); ip.Value = new Point(f, y); string yStr = y.ToString(m_tooltipFormat, CultureInfo.InvariantCulture); // По умолчанию формат 'P2' -- то есть отображение с точностью 2знака, // со знаком процента и с автоматическим умножением на 100. ip.Tooltip = String.Format(CultureInfo.InvariantCulture, "F:{0}; Y:{1}", f, yStr); SmileNodeInfo node = new SmileNodeInfo(); node.OptPx = f; node.Security = sec; node.PxMode = OptionPxMode.Bid; node.Symbol = node.Security.Symbol; node.DSName = node.Security.SecurityDescription.DSName; node.Expired = node.Security.SecurityDescription.Expired; node.FullName = node.Security.SecurityDescription.FullName; ip.Tag = node; controlPoints.Add(new InteractiveObject(ip)); } { InteractivePointActive ip = new InteractivePointActive(); ip.IsActive = true; ip.DragableMode = DragableMode.None; ip.Geometry = Geometries.Triangle; ip.Color = AlphaColors.GreenYellow; double y = (sigmaAtm.Value + h); ip.Value = new Point(f, y); string yStr = y.ToString(m_tooltipFormat, CultureInfo.InvariantCulture); // По умолчанию формат 'P2' -- то есть отображение с точностью 2знака, // со знаком процента и с автоматическим умножением на 100. ip.Tooltip = String.Format(CultureInfo.InvariantCulture, "F:{0}; Y:{1}", f, yStr); SmileNodeInfo node = new SmileNodeInfo(); node.OptPx = f; node.Security = sec; node.PxMode = OptionPxMode.Ask; node.Symbol = node.Security.Symbol; node.DSName = node.Security.SecurityDescription.DSName; node.Expired = node.Security.SecurityDescription.Expired; node.FullName = node.Security.SecurityDescription.FullName; ip.Tag = node; controlPoints.Add(new InteractiveObject(ip)); } res.ControlPoints = new ReadOnlyCollection <InteractiveObject>(controlPoints); 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); }
public InteractiveSeries Execute(double price, double time, InteractiveSeries smile, IOptionSeries optSer, double scaleMult, double ratePct, int barNum) { int barsCount = ContextBarsCount; if ((barNum < barsCount - 1) || (smile == null) || (optSer == null)) { return(Constants.EmptySeries); } SmileInfo oldInfo = smile.GetTag <SmileInfo>(); if ((oldInfo == null) || (oldInfo.ContinuousFunction == null) || (oldInfo.ContinuousFunctionD1 == 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, 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); if (wasInitialized) { m_context.Log(msg, MessageType.Error, true); } return(Constants.EmptySeries); } if (!DoubleUtil.IsPositive(scaleMult)) { //throw new ScriptException("Argument 'scaleMult' contains NaN for some strange reason. scaleMult:" + scaleMult); return(Constants.EmptySeries); } if (Double.IsNaN(ratePct)) { //throw new ScriptException("Argument 'ratePct' contains NaN for some strange reason. rate:" + rate); return(Constants.EmptySeries); } double ivAtm, slopeAtm, shape; if ((!oldInfo.ContinuousFunction.TryGetValue(futPx, out ivAtm)) || (!oldInfo.ContinuousFunctionD1.TryGetValue(futPx, out slopeAtm))) { return(Constants.EmptySeries); } if (m_setIvByHands) { ivAtm = m_ivAtmPct.Value / Constants.PctMult; } if (m_setSlopeByHands) { slopeAtm = m_slopePct.Value / Constants.PctMult; //slopeAtm = slopeAtm / F / Math.Pow(dT, Pow + shape); } //if (setShapeByHands) { shape = m_shapePct.Value / Constants.PctMult; } 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); } if (Double.IsNaN(slopeAtm)) { // [{0}] Smile skew at the money must be some number. skewAtm:{1} string msg = RM.GetStringFormat("OptHandlerMsg.SkewAtmMustBeNumber", GetType().Name, slopeAtm); if (wasInitialized) { m_context.Log(msg, MessageType.Error, true); } return(Constants.EmptySeries); } SmileInfo templateInfo; #region Fill templateInfo if (m_useLocalTemplate) { InteractiveSeries templateSmile = m_context.LoadObject(m_frozenSmileId) as InteractiveSeries; if (templateSmile == null) { // [{0}] There is no LOCAL smile with ID:{1} string msg = RM.GetStringFormat("SmileImitation5.NoLocalSmile", GetType().Name, m_frozenSmileId); if (wasInitialized) { m_context.Log(msg, MessageType.Error, true); } return(Constants.EmptySeries); } SmileInfo locInfo = new SmileInfo(); locInfo.F = futPx; locInfo.dT = dT; locInfo.RiskFreeRate = oldInfo.RiskFreeRate; List <double> locXs = new List <double>(); List <double> locYs = new List <double>(); foreach (InteractiveObject oldObj in templateSmile.ControlPoints) { if (!oldObj.AnchorIsActive) { continue; } double k = oldObj.Anchor.ValueX; double sigma = oldObj.Anchor.ValueY; double x = Math.Log(k / futPx) / Math.Pow(dT, DefaultPow + shape) / ivAtm; double sigmaNormalized = sigma / ivAtm; locXs.Add(x); locYs.Add(sigmaNormalized); } try { NotAKnotCubicSpline spline = new NotAKnotCubicSpline(locXs, locYs); locInfo.ContinuousFunction = spline; locInfo.ContinuousFunctionD1 = spline.DeriveD1(); templateInfo = locInfo; } catch (Exception ex) { m_context.Log(ex.ToString(), MessageType.Error, true); return(Constants.EmptySeries); } } else { //templateSmile = context.LoadGlobalObject(globalSmileID, true) as InteractiveSeries; templateInfo = m_context.LoadGlobalObject(m_globalSmileId, true) as SmileInfo; if (templateInfo == null) { // [{0}] There is no global templateInfo with ID:{1}. I'll try to use default one. string msg = RM.GetStringFormat("SmileImitation5.TemplateWasSaved", GetType().Name, m_globalSmileId); m_context.Log(msg, MessageType.Error, true); System.Xml.Linq.XDocument xDoc = System.Xml.Linq.XDocument.Parse(SmileFunction5.XmlSmileRiz4Nov1); System.Xml.Linq.XElement xInfo = xDoc.Root; SmileInfo templateSmile = SmileInfo.FromXElement(xInfo); // Обновляю уровень IV ATM? if (Double.IsNaN(ivAtm)) { ivAtm = oldInfo.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 = dT; templateSmile.RiskFreeRate = oldInfo.RiskFreeRate; m_context.StoreGlobalObject(m_globalSmileId, templateSmile, true); // [{0}] Default templateInfo was saved to Global Cache with ID:{1}. msg = RM.GetStringFormat("SmileImitation5.TemplateWasSaved", GetType().Name, m_globalSmileId); m_context.Log(msg, MessageType.Warning, true); templateInfo = templateSmile; } } #endregion Fill templateInfo if (!m_setIvByHands) { m_ivAtmPct.Value = ivAtm * Constants.PctMult; } if (!m_setShapeByHands) { // так я ещё не умею } if (!m_setSlopeByHands) { // Пересчитываю наклон в безразмерку double dSigmaDx = slopeAtm * futPx * Math.Pow(dT, DefaultPow + shape); m_slopePct.Value = dSigmaDx * Constants.PctMult; // и теперь апдейчу локальную переменную slopeAtm: slopeAtm = m_slopePct.Value / Constants.PctMult; } // Это функция в нормированных координатах // поэтому достаточно обычной симметризации // PROD-3111: заменяю вызов на SmileFunctionExtended //SimmetrizeFunc simmFunc = new SimmetrizeFunc(templateInfo.ContinuousFunction); //SimmetrizeFunc simmFuncD1 = new SimmetrizeFunc(templateInfo.ContinuousFunctionD1); //SmileFunction5 smileFunc = new SmileFunction5(simmFunc, simmFuncD1, ivAtm, slopeAtm, shape, futPx, dT); SmileFunctionExtended smileFunc = new SmileFunctionExtended( (NotAKnotCubicSpline)templateInfo.ContinuousFunction, ivAtm, slopeAtm, shape, futPx, dT); smileFunc.UseTails = m_useSmileTails; List <double> xs = new List <double>(); List <double> ys = new List <double>(); 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 minK = pairs[0].Strike; double maxK = pairs[pairs.Length - 1].Strike; double futStep = optSer.UnderlyingAsset.Tick; double dK = pairs[1].Strike - pairs[0].Strike; double width = (SigmaMult * ivAtm * Math.Sqrt(dT)) * futPx; width = Math.Max(width, 10 * dK); // Нельзя вылезать за границу страйков??? width = Math.Min(width, Math.Abs(futPx - minK)); width = Math.Min(width, Math.Abs(maxK - futPx)); // Generate left invisible tail if (m_generateTails) { GaussSmile.AppendLeftTail(smileFunc, xs, ys, minK, dK, false); } SortedDictionary <double, IOptionStrikePair> strikePrices; if (!SmileImitation5.TryPrepareImportantPoints(pairs, futPx, futStep, width, out strikePrices)) { string msg = String.Format("[{0}] It looks like there is no suitable points for the smile. pairs.Length:{1}", GetType().Name, pairs.Length); if (wasInitialized) { 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); //IOptionStrikePair pair = pairs[j]; //// Сверхдалекие страйки игнорируем //if ((pair.Strike < futPx - width) || (futPx + width < pair.Strike)) //{ // showPoint = false; //} //double k = pair.Strike; double k = kvp.Key; double sigma; if (!smileFunc.TryGetValue(k, out sigma)) { continue; } double vol = sigma; if (!DoubleUtil.IsPositive(sigma)) { continue; } //InteractivePointActive ip = new InteractivePointActive(k, vol); //ip.Color = (optionPxMode == OptionPxMode.Ask) ? Colors.DarkOrange : Colors.DarkCyan; //ip.DragableMode = DragableMode.None; //ip.Geometry = Geometries.Rect; // (optionPxMode == OptionPxMode.Ask) ? Geometries.Rect : Geometries.Rect; // Иначе неправильно выставляются координаты??? //ip.Tooltip = String.Format("K:{0}; IV:{1:0.00}", k, PctMult * sigma); InteractivePointLight ip; if (showPoint) { var tip = new InteractivePointActive(k, vol); if (k <= futPx) // Puts { SmileImitationDeribit5.FillNodeInfo(tip, futPx, dT, kvp.Value, StrikeType.Put, OptionPxMode.Mid, sigma, false, m_showNodes, scaleMult, ratePct); } else // Calls { SmileImitationDeribit5.FillNodeInfo(tip, futPx, dT, kvp.Value, StrikeType.Call, OptionPxMode.Mid, sigma, false, m_showNodes, scaleMult, ratePct); } ip = tip; } else { ip = new InteractivePointLight(k, vol); } InteractiveObject obj = new InteractiveObject(ip); //if (showPoint) // Теперь мы понимаем, что точки либо рисуются // потому что это страйки (и тогда они автоматом InteractivePointActive) // либо они присутствуют, но не рисуются их узлы. Потому что они InteractivePointLight. { controlPoints.Add(obj); } xs.Add(k); ys.Add(vol); } // ReSharper disable once UseObjectOrCollectionInitializer InteractiveSeries res = new InteractiveSeries(); res.ControlPoints = new ReadOnlyCollection <InteractiveObject>(controlPoints); // Generate right invisible tail if (m_generateTails) { GaussSmile.AppendRightTail(smileFunc, xs, ys, maxK, dK, false); } 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 = dT; info.Expiry = optSer.ExpirationDate; info.ScriptTime = scriptTime; info.RiskFreeRate = ratePct; info.BaseTicker = baseSec.Symbol; info.ContinuousFunction = smileFunc; info.ContinuousFunctionD1 = smileFunc.DeriveD1(); res.Tag = info; SetHandlerInitialized(now); 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); } }
/// <summary> /// Вега будет иметь размерность 'пункты за 100% волатильности'. /// Обычно же опционщики любят смотреть размерность 'пункты за 1% волатильности'. /// Поэтому полученное сырое значение ещё надо делить на 100%. /// (Эквивалентно умножению на интересующий набег волы для получения дифференциала). /// </summary> internal static bool TryEstimateVega(PositionsManager posMan, IOptionSeries optSer, IOptionStrikePair[] pairs, InteractiveSeries smile, NumericalGreekAlgo greekAlgo, double f, double dSigma, double timeToExpiry, 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 cash1 = 0, pnl1 = 0; // Флаг того, что ПНЛ по всем инструментам был расчитан верно bool pnlIsCorrect1 = true; { // фьюч на даёт вклад в вегу //SingleSeriesProfile.GetBasePnl(posMan, optSer.UnderlyingAsset, optSer.UnderlyingAsset.Bars.Count - 1, f, out cash1, out pnl1); //InteractiveSeries actualSmile; //// 1. Изменение положения БА //actualSmile = SingleSeriesProfile.GetActualSmile(smile, greekAlgo, f); SmileInfo actualSmile; // 1. Изменение положения БА actualSmile = SingleSeriesProfile.GetActualSmile(sInfo, greekAlgo, f); 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, timeToExpiry, out pairCash, out pairPnl); pnlIsCorrect1 &= SingleSeriesProfile.TryGetPairPnl(posMan, actualSmile, pair, f, timeToExpiry, out pairCash, out pairPnl); cash1 += pairCash; pnl1 += pairPnl; } } double cash2 = 0, pnl2 = 0; // Флаг того, что ПНЛ по всем инструментам был расчитан верно bool pnlIsCorrect2 = true; { // фьюч на даёт вклад в вегу //SingleSeriesProfile.GetBasePnl(posMan, optSer.UnderlyingAsset, optSer.UnderlyingAsset.Bars.Count - 1, f, out cash2, out pnl2); //InteractiveSeries actualSmile; //// 1. Изменение положения БА //actualSmile = SingleSeriesProfile.GetActualSmile(smile, greekAlgo, f); //// 2. Подъём улыбки на dSigma //actualSmile = SingleSeriesProfile.GetRaisedSmile(actualSmile, greekAlgo, dSigma); SmileInfo actualSmile; // 1. Изменение положения БА actualSmile = SingleSeriesProfile.GetActualSmile(sInfo, greekAlgo, f); // 2. Подъём улыбки на dSigma actualSmile = SingleSeriesProfile.GetRaisedSmile(actualSmile, greekAlgo, dSigma); 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, timeToExpiry, out pairCash, out pairPnl); pnlIsCorrect2 &= SingleSeriesProfile.TryGetPairPnl(posMan, actualSmile, pair, f, timeToExpiry, out pairCash, out pairPnl); cash2 += pairCash; pnl2 += pairPnl; } } if (pnlIsCorrect1 && pnlIsCorrect2) { // Первая точка совпадает с текущей, поэтому нет деления на 2. rawVega = ((cash2 + pnl2) - (cash1 + pnl1)) / dSigma; return(true); } else { return(false); } }
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> /// Вомма будет иметь размерность 'пункты за 100% волатильности'. /// Обычно же опционщики любят смотреть размерность 'пункты за 1% волатильности'. /// Поэтому полученное сырое значение ещё надо делить на 100%. /// (Эквивалентно умножению на интересующий набег волы для получения дифференциала). /// </summary> internal static bool TryEstimateVomma(PositionsManager posMan, IOptionSeries optSer, IOptionStrikePair[] pairs, InteractiveSeries smile, NumericalGreekAlgo greekAlgo, double f, double dSigma, double timeToExpiry, out double rawVomma) { rawVomma = 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 cash1 = 0, pnl1 = 0; // Флаг того, что ПНЛ по всем инструментам был расчитан верно bool pnlIsCorrect1 = true; { // фьюч на даёт вклад в вегу //SingleSeriesProfile.GetBasePnl(posMan, optSer.UnderlyingAsset, optSer.UnderlyingAsset.Bars.Count - 1, f, out cash1, out pnl1); //InteractiveSeries actualSmile; //// 1. Изменение положения БА //actualSmile = SingleSeriesProfile.GetActualSmile(smile, greekAlgo, f); SmileInfo actualSmile; // 1. Изменение положения БА actualSmile = SingleSeriesProfile.GetActualSmile(sInfo, greekAlgo, f); for (int j = 0; j < pairs.Length; j++) { IOptionStrikePair pair = pairs[j]; double pairPnl, pairCash; pnlIsCorrect1 &= SingleSeriesProfile.TryGetPairPnl(posMan, actualSmile, pair, f, timeToExpiry, out pairCash, out pairPnl); cash1 += pairCash; pnl1 += pairPnl; } } double cash2 = 0, pnl2 = 0; // Флаг того, что ПНЛ по всем инструментам был расчитан верно bool pnlIsCorrect2 = true; { // фьюч на даёт вклад в вегу //SingleSeriesProfile.GetBasePnl(posMan, optSer.UnderlyingAsset, optSer.UnderlyingAsset.Bars.Count - 1, f, out cash2, out pnl2); //InteractiveSeries actualSmile; //// 1. Изменение положения БА //actualSmile = SingleSeriesProfile.GetActualSmile(smile, greekAlgo, f); //// 2. Подъём улыбки на dSigma //actualSmile = SingleSeriesProfile.GetRaisedSmile(actualSmile, greekAlgo, dSigma); SmileInfo actualSmile; // 1. Изменение положения БА actualSmile = SingleSeriesProfile.GetActualSmile(sInfo, greekAlgo, f); // 2. Подъём улыбки на dSigma actualSmile = SingleSeriesProfile.GetRaisedSmile(actualSmile, greekAlgo, dSigma); for (int j = 0; j < pairs.Length; j++) { IOptionStrikePair pair = pairs[j]; double pairPnl, pairCash; pnlIsCorrect2 &= SingleSeriesProfile.TryGetPairPnl(posMan, actualSmile, pair, f, timeToExpiry, out pairCash, out pairPnl); cash2 += pairCash; pnl2 += pairPnl; } } double cash3 = 0, pnl3 = 0; // Флаг того, что ПНЛ по всем инструментам был расчитан верно bool pnlIsCorrect3 = true; { // фьюч на даёт вклад в вегу //SingleSeriesProfile.GetBasePnl(posMan, optSer.UnderlyingAsset, optSer.UnderlyingAsset.Bars.Count - 1, f, out cash2, out pnl2); //InteractiveSeries actualSmile; //// 1. Изменение положения БА //actualSmile = SingleSeriesProfile.GetActualSmile(smile, greekAlgo, f); //// 2. Подъём улыбки на dSigma //actualSmile = SingleSeriesProfile.GetRaisedSmile(actualSmile, greekAlgo, dSigma); SmileInfo actualSmile; // 1. Изменение положения БА actualSmile = SingleSeriesProfile.GetActualSmile(sInfo, greekAlgo, f); // 2. Подъём улыбки на 2*dSigma actualSmile = SingleSeriesProfile.GetRaisedSmile(actualSmile, greekAlgo, 2 * dSigma); for (int j = 0; j < pairs.Length; j++) { IOptionStrikePair pair = pairs[j]; double pairPnl, pairCash; pnlIsCorrect2 &= SingleSeriesProfile.TryGetPairPnl(posMan, actualSmile, pair, f, timeToExpiry, out pairCash, out pairPnl); cash3 += pairCash; pnl3 += pairPnl; } } if (pnlIsCorrect1 && pnlIsCorrect2 && pnlIsCorrect3) { // См. Вики случай r=2, N=2: https://ru.wikipedia.org/wiki/Численное_дифференцирование // f''(x0) ~= (f0 - 2*f1 + f2)/h/h rawVomma = ((cash1 + pnl1) - 2 * (cash2 + pnl2) + (cash3 + pnl3)) / dSigma / dSigma; return(true); } else { return(false); } }
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 time, InteractiveSeries smile, IOptionSeries optSer, int barNum) { //InteractiveSeries res = context.LoadObject(cashKey + "positionDelta") as InteractiveSeries; //if (res == null) //{ // res = new InteractiveSeries(); // context.StoreObject(cashKey + "positionDelta", res); //} InteractiveSeries res = new InteractiveSeries(); int barsCount = ContextBarsCount; if ((barNum < barsCount - 1) || (optSer == null)) { return(res); } //double F = prices[prices.Count - 1]; 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); } //// TODO: переписаться на обновление старых значений //res.ControlPoints.Clear(); 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 rawVega, f = iob.Anchor.ValueX; if (TryEstimateVega(posMan, optSer, pairs, smile, m_greekAlgo, f, m_sigmaStep, dT, out rawVega)) { // Переводим вегу в дифференциал 'изменение цены за 1% волы'. rawVega /= Constants.PctMult; 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 = rawVega; ip.Value = new Point(f, y); string yStr = y.ToString(m_tooltipFormat, CultureInfo.InvariantCulture); ip.Tooltip = String.Format("F:{0}; V:{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) { 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); }