public static double GetOptPx(this double strikePrice, double strikeStep, bool isCall, double price, double dT, double sigma) { var optPx = FinMath.GetOptionPrice(price, strikePrice, dT, sigma, 0, isCall); optPx /= price; optPx = Math.Truncate(optPx / strikeStep + 1) * strikeStep; return(Math.Max(optPx, strikeStep)); }
public void TestAbs() { Assert.AreEqual(5, FinMath.Abs(5)); Assert.AreEqual(5, FinMath.Abs(-5)); Assert.AreEqual(5d, FinMath.Abs(5d)); Assert.AreEqual(5d, FinMath.Abs(-5d)); }
private void ProcessScheduledJump_() { if (this.scheduledJumpState_ == ScheduledState.STARTING) { if (this.StateMachine.CanJumpFromGround) { var isLongjump = this.StateMachine.State == PlayerState.SLIDING; var isBackflip = this.StateMachine.State == PlayerState.TURNING && FinMath.Abs(this.Rigidbody.XVelocity) > PlayerConstants.UPRIGHT_MAX_SLOW_XSPD; var newYVel = isLongjump ? PlayerConstants.LONGJUMP_SPEED : isBackflip ? PlayerConstants.BACKFLIP_JUMP_SPEED : PlayerConstants.JUMP_SPEED; var newState = isLongjump ? PlayerState.LONGJUMPING : isBackflip ? PlayerState.BACKFLIPPING : PlayerState.JUMPING; this.Rigidbody.YVelocity = newYVel; this.StateMachine.State = newState; // TODO: Longjump feels weird. if (isLongjump) { this.Rigidbody.XVelocity = this.Rigidbody.XVelocity / PlayerConstants.UPRIGHT_MAX_FAST_XSPD * PlayerConstants.LONGJUMP_MAX_XSPD; } else if (isBackflip) { // Instantly flip horizontal velocity. var moveDirection = FinMath.Sign(this.Rigidbody.XVelocity); this.Rigidbody.XVelocity = -moveDirection * PlayerConstants.BACKFLIP_XSPD; } } else if (this.StateMachine.State == PlayerState.WALL_SLIDING) { this.StateMachine.State = PlayerState.WALLJUMPING; this.Rigidbody.YVelocity = PlayerConstants.JUMP_SPEED; this.Rigidbody.XVelocity = (this.StateMachine.WallSlidingOnLeft ? 1 : -1) * PlayerConstants.WALL_SLIDING_XSPD; } } else if (this.scheduledJumpState_ == ScheduledState.STOPPING) { if (this.StateMachine.CanStallJumpingMomentum && this.Rigidbody.YVelocity < 0) { this.StateMachine.State = PlayerState.FALLING; this.Rigidbody.YVelocity /= 2; } } }
public void TestSign() { Assert.AreEqual(-1, FinMath.Sign(-1)); Assert.AreEqual(1, FinMath.Sign(1)); Assert.AreEqual(0, FinMath.Sign(0)); Assert.AreEqual(-1, FinMath.Sign(-1d)); Assert.AreEqual(1, FinMath.Sign(1d)); Assert.AreEqual(0, FinMath.Sign(0d)); }
public void TestMin() { Assert.AreEqual(0, FinMath.Min(0, 1)); Assert.AreEqual(0, FinMath.Min(1, 0)); Assert.AreEqual(1L, FinMath.Min(1L, 2L)); Assert.AreEqual(.123, FinMath.Min(.123, .234)); Assert.AreEqual(0x01, FinMath.Min(0x01, 0x02)); }
public void TestMax() { Assert.AreEqual(1, FinMath.Max(0, 1)); Assert.AreEqual(1, FinMath.Max(1, 0)); Assert.AreEqual(2L, FinMath.Max(1L, 2L)); Assert.AreEqual(.234, FinMath.Max(.123, .234)); Assert.AreEqual(0x02, FinMath.Max(0x01, 0x02)); }
public void TestMod() { Assert.AreEqual(0, FinMath.Mod(10, 2)); Assert.AreEqual(1, FinMath.Mod(10, 3)); Assert.AreEqual(1L, FinMath.Mod(10L, 3L)); Assert.AreEqual(.111, FinMath.Mod(.234, .123), .0001); Assert.AreEqual(0x01, FinMath.Mod(0x10, 0x03)); }
public double GetRandom() { double SearchValue = FreeQuant.Quant.Random.Rndm(); int Index = FinMath.BinarySearch(this.fNBins, this.GetIntegral(), SearchValue); if (Index >= 0 && Index < this.fNBins) { return(this.GetBinMin(Index) + this.fBinSize * (SearchValue - this.fIntegral[Index]) / (this.fIntegral[Index + 1] - this.fIntegral[Index])); } Console.WriteLine("Index111" + Index); return(0.0); }
internal static void FillNodeInfo(InteractivePointActive ip, double f, double dT, IOptionStrikePair sInfo, StrikeType optionType, OptionPxMode optPxMode, double optSigma, bool returnPct, bool isVisiblePoints, double scaleMult, double riskfreeRatePct) { if (optionType == StrikeType.Any) { throw new ArgumentException("Option type 'Any' is not supported.", "optionType"); } bool isCall = (optionType == StrikeType.Call); double optPx = FinMath.GetOptionPrice(f, sInfo.Strike, dT, optSigma, 0, isCall); // Сразу переводим котировку из баксов в битки optPx /= scaleMult; // ReSharper disable once UseObjectOrCollectionInitializer SmileNodeInfo nodeInfo = new SmileNodeInfo(); nodeInfo.F = f; nodeInfo.dT = dT; nodeInfo.RiskFreeRate = riskfreeRatePct; nodeInfo.Sigma = returnPct ? optSigma * Constants.PctMult : optSigma; nodeInfo.OptPx = optPx; nodeInfo.Strike = sInfo.Strike; // Сюда мы приходим когда хотим торговать, поэтому обращение к Security уместно nodeInfo.Security = (optionType == StrikeType.Put) ? sInfo.Put.Security : sInfo.Call.Security; nodeInfo.PxMode = optPxMode; nodeInfo.OptionType = optionType; nodeInfo.Pair = sInfo; //nodeInfo.ScriptTime = optTime; nodeInfo.CalendarTime = DateTime.Now; nodeInfo.Symbol = nodeInfo.Security.Symbol; nodeInfo.DSName = nodeInfo.Security.SecurityDescription.DSName; nodeInfo.Expired = nodeInfo.Security.SecurityDescription.Expired; nodeInfo.FullName = nodeInfo.Security.SecurityDescription.FullName; ip.Tag = nodeInfo; //ip.Color = Colors.Yellow; ip.IsActive = isVisiblePoints; ip.Geometry = Geometries.Ellipse; ip.DragableMode = DragableMode.None; //ip.Value = new System.Windows.Point(sInfo.Strike, nodeInfo.Sigma); // Поскольку цены скорее всего расчетные, показываю на 1 знак болььше, чем объявлена точность в инструменте int decim = Math.Max(0, nodeInfo.Security.Decimals + 1); string optPxStr = optPx.ToString("N" + decim, CultureInfo.InvariantCulture); ip.Tooltip = String.Format(CultureInfo.InvariantCulture, " K: {0}; IV: {1:#0.00}%\r\n {2} px {3}", sInfo.Strike, optSigma * Constants.PctMult, optionType, optPx); }
public void TestClamp() { Assert.AreEqual(0, FinMath.Clamp(-1, 0, 1)); Assert.AreEqual(-1, FinMath.Clamp(-1, -1, 1)); Assert.AreEqual(1, FinMath.Clamp(-1, 1, 1)); Assert.AreEqual(1, FinMath.Clamp(-1, 2, 1)); Assert.AreEqual(-1, FinMath.Clamp(-1, -2, 1)); Assert.AreEqual(1L, FinMath.Clamp(0L, 2L, 1L)); Assert.AreEqual(.234, FinMath.Clamp(.123, 456d, .234)); Assert.AreEqual(-0x01, FinMath.Clamp(-0x01, -0x10, 0x01)); }
public void TestWrap() { Assert.AreEqual(0, FinMath.Wrap(-1, 0, 1)); Assert.AreEqual(-1, FinMath.Wrap(-1, -1, 1)); Assert.AreEqual(-1, FinMath.Wrap(-1, 1, 1)); Assert.AreEqual(-1, FinMath.Wrap(-2, 3, 2)); Assert.AreEqual(1, FinMath.Wrap(-2, -3, 2)); Assert.AreEqual(1L, FinMath.Wrap(1L, 3L, 2L)); Assert.AreEqual(.124, FinMath.Wrap(.123, .235, .234), .0001); Assert.AreEqual(0x01, FinMath.Wrap(-0x02, -0x03, 0x02)); }
internal static void FillNodeInfo(InteractivePointActive ip, double f, double dT, IOptionStrikePair sInfo, StrikeType optionType, OptionPxMode optPxMode, double optSigma, bool returnPct, bool isVisiblePoints, double riskfreeRatePct) { if (optionType == StrikeType.Any) { throw new ArgumentException("Option type 'Any' is not supported.", "optionType"); } bool isCall = (optionType == StrikeType.Call); double optPx = FinMath.GetOptionPrice(f, sInfo.Strike, dT, optSigma, 0, isCall); // ReSharper disable once UseObjectOrCollectionInitializer SmileNodeInfo nodeInfo = new SmileNodeInfo(); nodeInfo.F = f; nodeInfo.dT = dT; nodeInfo.RiskFreeRate = riskfreeRatePct; nodeInfo.Sigma = returnPct ? optSigma * Constants.PctMult : optSigma; nodeInfo.OptPx = optPx; nodeInfo.Strike = sInfo.Strike; // Сюда мы приходим когда хотим торговать, поэтому обращение к Security уместно nodeInfo.Security = (optionType == StrikeType.Put) ? sInfo.Put.Security : sInfo.Call.Security; nodeInfo.PxMode = optPxMode; nodeInfo.OptionType = optionType; nodeInfo.Pair = sInfo; //nodeInfo.ScriptTime = optTime; nodeInfo.CalendarTime = DateTime.Now; nodeInfo.Symbol = nodeInfo.Security.Symbol; nodeInfo.DSName = nodeInfo.Security.SecurityDescription.DSName; nodeInfo.Expired = nodeInfo.Security.SecurityDescription.Expired; nodeInfo.FullName = nodeInfo.Security.SecurityDescription.FullName; ip.Tag = nodeInfo; ip.Color = AlphaColors.Yellow; ip.IsActive = isVisiblePoints; ip.Geometry = Geometries.Ellipse; ip.DragableMode = DragableMode.None; //ip.Value = new System.Windows.Point(sInfo.Strike, nodeInfo.Sigma); ip.Tooltip = String.Format(CultureInfo.InvariantCulture, "F:{0}; K:{1}; IV:{2:#0.00}%\r\n{3} px {4}", f, sInfo.Strike, optSigma * Constants.PctMult, optionType, optPx); }
protected static void FillNodeInfo(InteractivePointLight ip, double f, double dT, IOptionStrikePair sInfo, StrikeType optionType, OptionPxMode optPxMode, double optSigma, bool returnPct, double pctRate) { if (optionType == StrikeType.Any) { throw new ArgumentException("Option type 'Any' is not supported.", "optionType"); } bool isCall = (optionType == StrikeType.Call); double optPx = FinMath.GetOptionPrice(f, sInfo.Strike, dT, optSigma, pctRate, isCall); // ReSharper disable once UseObjectOrCollectionInitializer SmileNodeInfo nodeInfo = new SmileNodeInfo(); nodeInfo.F = f; nodeInfo.dT = dT; nodeInfo.RiskFreeRate = pctRate; nodeInfo.Sigma = returnPct ? optSigma * Constants.PctMult : optSigma; nodeInfo.OptPx = optPx; nodeInfo.Strike = sInfo.Strike; nodeInfo.Security = (optionType == StrikeType.Put) ? sInfo.Put.Security : sInfo.Call.Security; nodeInfo.PxMode = optPxMode; nodeInfo.OptionType = optionType; nodeInfo.Pair = sInfo; //nodeInfo.ScriptTime = optTime; nodeInfo.CalendarTime = DateTime.Now; nodeInfo.Symbol = nodeInfo.Security.Symbol; nodeInfo.DSName = nodeInfo.Security.SecurityDescription.DSName; nodeInfo.FullName = nodeInfo.Security.SecurityDescription.FullName; nodeInfo.Expired = nodeInfo.Security.SecurityDescription.Expired; ip.Tag = nodeInfo; ip.Value = new Point(sInfo.Strike, nodeInfo.Sigma); if (ip is InteractivePointActive) { InteractivePointActive ipa = (InteractivePointActive)ip; ipa.Tooltip = String.Format("K:{0}; IV:{1:#0.00}%\r\n{2} {3} @ {4}", sInfo.Strike, optSigma * Constants.PctMult, optionType, optPx, DefaultOptQty); } }
protected static void FillNodeInfo(InteractivePointActive ip, double f, double dT, IOptionStrikePair sInfo, StrikeType optionType, OptionPxMode optPxMode, double optSigma, bool returnPct, bool isVisiblePoints) { if (optionType == StrikeType.Any) { throw new ArgumentException("Option type 'Any' is not supported.", "optionType"); } bool isCall = (optionType == StrikeType.Call); double optPx = FinMath.GetOptionPrice(f, sInfo.Strike, dT, optSigma, 0, isCall); SmileNodeInfo nodeInfo = new SmileNodeInfo(); nodeInfo.F = f; nodeInfo.dT = dT; nodeInfo.Sigma = returnPct ? optSigma * Constants.PctMult : optSigma; nodeInfo.OptPx = optPx; nodeInfo.Strike = sInfo.Strike; nodeInfo.Security = (optionType == StrikeType.Put) ? sInfo.Put.Security : sInfo.Call.Security; nodeInfo.PxMode = optPxMode; nodeInfo.OptionType = optionType; nodeInfo.Pair = sInfo; //nodeInfo.ScriptTime = optTime; nodeInfo.CalendarTime = DateTime.Now; nodeInfo.Symbol = nodeInfo.Security.Symbol; nodeInfo.Expired = nodeInfo.Security.SecurityDescription.Expired; ip.Tag = nodeInfo; ip.Color = AlphaColors.Yellow; ip.IsActive = isVisiblePoints; ip.Geometry = Geometries.Ellipse; ip.DragableMode = DragableMode.None; //ip.Value = new System.Windows.Point(sInfo.Strike, nodeInfo.Sigma); ip.Tooltip = String.Format("F:{0}; K:{1}; IV:{2:#0.00}%\r\n{3} {4} @ {5}", f, sInfo.Strike, optSigma * Constants.PctMult, optionType, optPx, DefaultOptQty); }
/// <summary> /// Тета опциона по формуле Блека-Шолза /// </summary> internal static void GetOptTheta(IEnumerable <IPosition> positions, double f, double k, double dT, double sigma, double r, bool isCall, out double theta) { theta = 0; foreach (IPosition pos in positions) { //if (pos.EntrySignalName.StartsWith("CHT-RI-03.", StringComparison.InvariantCultureIgnoreCase)) //{ // string str = ""; //} // Пока что State лучше не трогать //if (pos.PositionState == PositionState.HaveError) { int sign = pos.IsLong ? 1 : -1; double qty = Math.Abs(pos.Shares); double optVega = FinMath.GetOptionTheta(f, k, dT, sigma, r, isCall); theta += sign * optVega * qty; } } }
internal static void GetOptGreek(IEnumerable <IPosition> positions, double f, double k, double dT, double sigma, double ratePct, bool isCall, Greeks greek, out double optGreek) { optGreek = 0; foreach (IPosition pos in positions) { // Пока что State лучше не трогать //if (pos.PositionState == PositionState.HaveError) { int sign = pos.IsLong ? 1 : -1; double qty = Math.Abs(pos.Shares); double tmp; switch (greek) { case Greeks.Delta: tmp = FinMath.GetOptionDelta(f, k, dT, sigma, ratePct, isCall); break; case Greeks.Theta: tmp = FinMath.GetOptionTheta(f, k, dT, sigma, ratePct, isCall); break; case Greeks.Vega: tmp = FinMath.GetOptionVega(f, k, dT, sigma, ratePct, isCall); break; case Greeks.Gamma: tmp = FinMath.GetOptionGamma(f, k, dT, sigma, ratePct, isCall); break; default: throw new NotImplementedException("Greek '" + greek + "' is not yet supported."); } optGreek += sign * tmp * qty; } } }
public void TestAddTowards() { Assert.AreEqual(.1, FinMath.AddTowards(0, 1, .1)); Assert.AreEqual(.9, FinMath.AddTowards(1, 0, .1)); Assert.AreEqual(1, FinMath.AddTowards(0, 1, 1.1)); }
/// <summary> /// Метод под флаг TemplateTypes.INTERACTIVESPLINE /// </summary> public InteractiveSeries Execute(InteractiveSeries smile, IOptionSeries optSer, double scaleMult, int barNum) { if ((smile == null) || (optSer == null)) { return(Constants.EmptySeries); } int barsCount = m_context.BarsCount; if (!m_context.IsLastBarUsed) { barsCount--; } if (barNum < barsCount - 1) { return(Constants.EmptySeries); } SmileInfo oldInfo = smile.GetTag <SmileInfo>(); if ((oldInfo == null) || (oldInfo.ContinuousFunction == null)) { return(Constants.EmptySeries); } double futPx = oldInfo.F; double dT = oldInfo.dT; if (m_executeCommand) { string msg = String.Format("[{0}.StartButton] Strike: {1}", GetType().Name, m_strike); m_context.Log(msg, MessageType.Info, false); } #region 1. Список страйков HashSet <string> serList = StrikeList; serList.Clear(); IOptionStrikePair[] pairs; if (Double.IsNaN(m_strikeStep) || (m_strikeStep <= Double.Epsilon)) { pairs = optSer.GetStrikePairs().ToArray(); } else { // Выделяем страйки, которые нацело делятся на StrikeStep pairs = (from p in optSer.GetStrikePairs() let test = m_strikeStep * Math.Round(p.Strike / m_strikeStep) where DoubleUtil.AreClose(p.Strike, test) select p).ToArray(); // [2015-12-24] Если шаг страйков по ошибке задан совершенно неправильно, // то в коллекцию ставим все имеющиеся страйки. // Пользователь потом разберется if (pairs.Length <= 0) { pairs = optSer.GetStrikePairs().ToArray(); } } //if (pairs.Length < 2) // return Constants.EmptyListDouble; foreach (IOptionStrikePair pair in pairs) { double k = pair.Strike; serList.Add(k.ToString(StrikeFormat, CultureInfo.InvariantCulture)); } #endregion 1. Список страйков InteractiveSeries res = Constants.EmptySeries; List <InteractiveObject> controlPoints = new List <InteractiveObject>(); #region 2. Формируем улыбку просто для отображения текущего положения потенциальной котировки // При нулевом рабочем объёме не утруждаемся рисованием лишних линий if (!DoubleUtil.IsZero(m_qty)) { for (int j = 0; j < pairs.Length; j++) { var pair = pairs[j]; double sigma = oldInfo.ContinuousFunction.Value(pair.Strike) + m_shiftIv; if (!DoubleUtil.IsPositive(sigma)) { //string msg = String.Format("[DEBUG:{0}] Invalid sigma:{1} for strike:{2}", GetType().Name, sigma, nodeInfo.Strike); //m_context.Log(msg, MessageType.Warning, true); continue; } //bool isCall = (futPx <= pair.Strike); bool isCall; if (m_optionType == StrikeType.Call) { isCall = true; } else if (m_optionType == StrikeType.Put) { isCall = false; } else { isCall = (futPx <= pair.Strike); } StrikeType optionType = isCall ? StrikeType.Call : StrikeType.Put; Contract.Assert(pair.Tick < 1, $"#1 На тестовом контуре Дерибит присылает неправильный шаг цены! Tick:{pair.Tick}; Decimals:{pair.Put.Security.Decimals}"); double theorOptPxDollars = FinMath.GetOptionPrice(futPx, pair.Strike, dT, sigma, oldInfo.RiskFreeRate, isCall); // Сразу(!!!) переводим котировку из баксов в битки double theorOptPxBitcoins = theorOptPxDollars / scaleMult; // Сдвигаем цену в долларах (с учетом ш.ц. в баксах) theorOptPxDollars += m_shiftPriceStep * pair.Tick * scaleMult; theorOptPxDollars = Math.Round(theorOptPxDollars / (pair.Tick * scaleMult)) * (pair.Tick * scaleMult); // Сдвигаем цену в биткойнах (с учетом ш.ц. в битках) theorOptPxBitcoins += m_shiftPriceStep * pair.Tick; theorOptPxBitcoins = Math.Round(theorOptPxBitcoins / pair.Tick) * pair.Tick; if ((!DoubleUtil.IsPositive(theorOptPxBitcoins)) || (!DoubleUtil.IsPositive(theorOptPxDollars))) { //string msg = String.Format("[DEBUG:{0}] Invalid theorOptPx:{1} for strike:{2}", GetType().Name, theorOptPx, nodeInfo.Strike); //m_context.Log(msg, MessageType.Warning, true); continue; } // Пересчитываем сигму обратно, ЕСЛИ мы применили сдвиг цены в абсолютном выражении if (m_shiftPriceStep != 0) { // Обратный пересчет в волатильность sigma = FinMath.GetOptionSigma(futPx, pair.Strike, dT, theorOptPxDollars, oldInfo.RiskFreeRate, isCall); if (!DoubleUtil.IsPositive(sigma)) { //string msg = String.Format("[DEBUG:{0}] Invalid sigma:{1} for strike:{2}", GetType().Name, sigma, nodeInfo.Strike); //m_context.Log(msg, MessageType.Warning, true); continue; } } // ReSharper disable once UseObjectOrCollectionInitializer SmileNodeInfo nodeInfo = new SmileNodeInfo(); var secDesc = isCall ? pair.CallFinInfo.Security : pair.PutFinInfo.Security; nodeInfo.F = oldInfo.F; nodeInfo.dT = oldInfo.dT; nodeInfo.RiskFreeRate = oldInfo.RiskFreeRate; nodeInfo.Strike = pair.Strike; nodeInfo.Sigma = sigma; nodeInfo.OptPx = theorOptPxBitcoins; nodeInfo.OptionType = isCall ? StrikeType.Call : StrikeType.Put; nodeInfo.Pair = pair; nodeInfo.Symbol = secDesc.Name; nodeInfo.DSName = secDesc.DSName; nodeInfo.Expired = secDesc.Expired; nodeInfo.FullName = secDesc.FullName; // ReSharper disable once UseObjectOrCollectionInitializer InteractivePointActive tmp = new InteractivePointActive(); tmp.IsActive = true; tmp.ValueX = pair.Strike; tmp.ValueY = sigma; tmp.DragableMode = DragableMode.Yonly; tmp.Tooltip = String.Format(CultureInfo.InvariantCulture, " F: {0}\r\n K: {1}; IV: {2:P2}\r\n {3} px {4}", futPx, pair.Strike, sigma, optionType, theorOptPxBitcoins); tmp.Tag = nodeInfo; //tmp.Color = Colors.White; if (m_qty > 0) { tmp.Geometry = Geometries.Triangle; } else if (m_qty < 0) { tmp.Geometry = Geometries.TriangleDown; } else { tmp.Geometry = Geometries.None; } InteractiveObject obj = new InteractiveObject(); obj.Anchor = tmp; controlPoints.Add(obj); } // ReSharper disable once UseObjectOrCollectionInitializer res = new InteractiveSeries(); // Здесь так надо -- мы делаем новую улыбку res.ControlPoints = new ReadOnlyCollection <InteractiveObject>(controlPoints); // ReSharper disable once UseObjectOrCollectionInitializer SmileInfo sInfo = new SmileInfo(); sInfo.F = futPx; sInfo.dT = dT; sInfo.Expiry = oldInfo.Expiry; sInfo.ScriptTime = oldInfo.ScriptTime; sInfo.RiskFreeRate = oldInfo.RiskFreeRate; sInfo.BaseTicker = oldInfo.BaseTicker; res.Tag = sInfo; if (controlPoints.Count > 0) { res.ClickEvent -= InteractiveSplineOnQuoteIvEvent; res.ClickEvent += InteractiveSplineOnQuoteIvEvent; m_clickableSeries = res; } } #endregion 2. Формируем улыбку просто для отображения текущего положения потенциальной котировки PositionsManager posMan = PositionsManager.GetManager(m_context); if (m_cancelAllLong) { posMan.DropAllLongIvTargets(m_context); } if (m_cancelAllShort) { posMan.DropAllShortIvTargets(m_context); } #region 4. Котирование { var longTargets = posMan.GetIvTargets(true); var shortTargets = posMan.GetIvTargets(false); var ivTargets = longTargets.Union(shortTargets).ToList(); for (int j = 0; j < ivTargets.Count; j++) { var ivTarget = ivTargets[j]; // PROD-6102 - Требуется точное совпадение опционной серии if (optSer.ExpirationDate.Date != ivTarget.SecInfo.Expiry.Date) { // Вывести предупреждение??? continue; } IOptionStrikePair pair; double k = ivTarget.SecInfo.Strike; if (!optSer.TryGetStrikePair(k, out pair)) { // Вывести предупреждение??? continue; } double sigma; QuoteIvMode quoteMode = ivTarget.QuoteMode; if (quoteMode == QuoteIvMode.Absolute) { sigma = ivTarget.EntryIv; } else { sigma = oldInfo.ContinuousFunction.Value(k) + ivTarget.EntryIv; if (!DoubleUtil.IsPositive(sigma)) { //string msg = String.Format("[DEBUG:{0}] Invalid sigma:{1} for strike:{2}", GetType().Name, sigma, nodeInfo.Strike); //m_context.Log(msg, MessageType.Warning, true); continue; } } //bool isCall = (futPx <= pair.Strike); // Определяю тип опциона на основании информации в Задаче StrikeType taskOptionType = StrikeType.Any; if (ivTarget.SecInfo.StrikeType.HasValue) { taskOptionType = ivTarget.SecInfo.StrikeType.Value; } bool isCall; if (taskOptionType == StrikeType.Call) { isCall = true; } else if (taskOptionType == StrikeType.Put) { isCall = false; } else { isCall = (futPx <= pair.Strike); // Это аварийная ситуация? } StrikeType optionType = isCall ? StrikeType.Call : StrikeType.Put; Contract.Assert(pair.Tick < 1, $"#3 На тестовом контуре Дерибит присылает неправильный шаг цены! Tick:{pair.Tick}; Decimals:{pair.Put.Security.Decimals}"); double theorOptPxDollars = FinMath.GetOptionPrice(futPx, pair.Strike, dT, sigma, oldInfo.RiskFreeRate, isCall); // Сразу(!!!) переводим котировку из баксов в битки double theorOptPxBitcoins = theorOptPxDollars / scaleMult; // Сдвигаем цену в долларах (с учетом ш.ц. в баксах) theorOptPxDollars += ivTarget.EntryShiftPrice * pair.Tick * scaleMult; theorOptPxDollars = Math.Round(theorOptPxDollars / (pair.Tick * scaleMult)) * (pair.Tick * scaleMult); // Сдвигаем цену в биткойнах (с учетом ш.ц. в битках) theorOptPxBitcoins += ivTarget.EntryShiftPrice * pair.Tick; theorOptPxBitcoins = Math.Round(theorOptPxBitcoins / pair.Tick) * pair.Tick; if ((!DoubleUtil.IsPositive(theorOptPxBitcoins)) || (!DoubleUtil.IsPositive(theorOptPxDollars))) { //string msg = String.Format("[DEBUG:{0}] Invalid theorOptPx:{1} for strike:{2}", GetType().Name, theorOptPx, nodeInfo.Strike); //m_context.Log(msg, MessageType.Warning, true); continue; } IOptionStrike optStrike = isCall ? pair.Call : pair.Put; ISecurity sec = optStrike.Security; double totalQty = posMan.GetTotalQty(sec, m_context.BarsCount, TotalProfitAlgo.AllPositions, ivTarget.IsLong); // Поскольку котирование страйка по волатильности -- это вопрос набора нужного количества СТРЕДДЛОВ, // то учитывать надо суммарный объём опционов как в колах, так и в путах. // НО ЗАДАЧУ-ТО Я СТАВЛЮ ДЛЯ КОНКРЕТНОГО ИНСТРУМЕНТА! // Как быть? //double totalQty = posMan.GetTotalQty(pair.Put.Security, m_context.BarsCount, TotalProfitAlgo.AllPositions, ivTarget.IsLong); //totalQty += posMan.GetTotalQty(pair.Call.Security, m_context.BarsCount, TotalProfitAlgo.AllPositions, ivTarget.IsLong); double targetQty = Math.Abs(ivTarget.TargetShares) - totalQty; // Если имеется дробный LotTick (как в Дерибит к примеру), то надо предварительно округлить targetQty = sec.RoundShares(targetQty); if (targetQty > 0) { string note = String.Format(CultureInfo.InvariantCulture, "{0}; ActQty:{1}; Px:{2}; IV:{3:P2}", ivTarget.EntryNotes, targetQty, theorOptPxBitcoins, sigma); if (ivTarget.IsLong) { posMan.BuyAtPrice(m_context, sec, targetQty, theorOptPxBitcoins, ivTarget.EntrySignalName, note); } else { posMan.SellAtPrice(m_context, sec, targetQty, theorOptPxBitcoins, ivTarget.EntrySignalName, note); } } else { string msg = String.Format(CultureInfo.InvariantCulture, "IvTarget cancelled. SignalName:{0}; Notes:{1}", ivTarget.EntrySignalName, ivTarget.EntryNotes); posMan.CancelVolatility(m_context, ivTarget, msg); // TODO: потом убрать из ГЛ m_context.Log(msg, MessageType.Info, true, new Dictionary <string, object> { { "VOLATILITY_ORDER_CANCELLED", msg } }); } } } #endregion 4. Котирование #region 5. Торговля if (m_executeCommand && (!DoubleUtil.IsZero(m_qty))) { double k; if ((!Double.TryParse(m_strike, out k)) && (!Double.TryParse(m_strike, NumberStyles.Any, CultureInfo.InvariantCulture, out k))) { return(res); } var pair = (from p in pairs where DoubleUtil.AreClose(k, p.Strike) select p).SingleOrDefault(); if (pair == null) { return(res); } InteractiveObject obj = (from o in controlPoints where DoubleUtil.AreClose(k, o.Anchor.ValueX) select o).SingleOrDefault(); if (obj == null) { return(res); } // TODO: для режима котирования в абсолютных числах сделать отдельную ветку //double iv = obj.Anchor.ValueY; const QuoteIvMode QuoteMode = QuoteIvMode.Relative; if (posMan.BlockTrading) { string msg = String.Format(RM.GetString("OptHandlerMsg.PositionsManager.TradingBlocked"), m_context.Runtime.TradeName + ":QuoteIv"); m_context.Log(msg, MessageType.Warning, true); return(res); } // Выбираю тип инструмента пут или колл? bool isCall; if (m_optionType == StrikeType.Call) { isCall = true; } else if (m_optionType == StrikeType.Put) { isCall = false; } else { isCall = (futPx <= k); } double iv = m_shiftIv; int shift = m_shiftPriceStep; var option = isCall ? pair.Call : pair.Put; if (m_qty > 0) { // Пересчитываю целочисленный параметр Qty в фактические лоты конкретного инструмента double actQty = m_qty * option.LotTick; string sigName = String.Format(CultureInfo.InvariantCulture, "Qty:{0}; IV:{1:P2}+{2}; dT:{3}; Mode:{4}", actQty, iv, shift, dT, QuoteMode); posMan.BuyVolatility(m_context, option, Math.Abs(actQty), QuoteMode, iv, shift, "BuyVola", sigName); m_context.Log(sigName, MessageType.Info, false); } else if (m_qty < 0) { // Пересчитываю целочисленный параметр Qty в фактические лоты конкретного инструмента double actQty = m_qty * option.LotTick; string sigName = String.Format(CultureInfo.InvariantCulture, "Qty:{0}; IV:{1:P2}+{2}; dT:{3}; Mode:{4}", actQty, iv, shift, dT, QuoteMode); posMan.SellVolatility(m_context, option, Math.Abs(actQty), QuoteMode, iv, shift, "SellVola", sigName); m_context.Log(sigName, MessageType.Info, false); } } #endregion 5. Торговля return(res); }
/// <summary> /// Обработчик под тип входных данных INTERACTIVESPLINE /// </summary> /// <param name="price">цена БА</param> /// <param name="time">время до экспирации в долях года</param> /// <param name="optPrices">опционные цены</param> /// <param name="rate">процентная ставка</param> /// <param name="barNum">индекс бара в серии</param> /// <returns>улыбка, восстановленная из цен опционов</returns> public InteractiveSeries Execute(double price, double time, InteractiveSeries optPrices, double scaleMult, double rate, int barNum) { int barsCount = ContextBarsCount; if ((barNum < barsCount - 1) || (optPrices == null) || (optPrices.ControlPoints.Count <= 0)) { return(Constants.EmptySeries); } IReadOnlyList <InteractiveObject> cps = optPrices.ControlPoints; double f = price; double dT = time; if (!DoubleUtil.IsPositive(f)) { //throw new ScriptException("Argument 'price' contains NaN for some strange reason. f:" + f); return(Constants.EmptySeries); } if (!DoubleUtil.IsPositive(scaleMult)) { //throw new ScriptException("Argument 'scaleMult' contains NaN for some strange reason. scaleMult:" + scaleMult); return(Constants.EmptySeries); } if (!DoubleUtil.IsPositive(dT)) { return(Constants.EmptySeries); } if (Double.IsNaN(rate)) { //throw new ScriptException("Argument 'rate' contains NaN for some strange reason. rate:" + rate); return(Constants.EmptySeries); } List <InteractiveObject> controlPoints = new List <InteractiveObject>(); for (int j = 0; j < cps.Count; j++) { InteractiveObject strikeObj = cps[j]; double strike = strikeObj.Anchor.ValueX; // Сверхдалекие страйки игнорируем if ((strike < m_minStrike) || (m_maxStrike < strike)) { continue; } double optPx; double stradlePx = Double.NaN; double putPx = Double.NaN, callPx = Double.NaN; if (m_optionType == StrikeType.Put) { putPx = strikeObj.Anchor.ValueY; optPx = putPx; // Здесь нельзя сразу домножать на scaleMultiplier! Потому что тогда в метод FillNodeInfo пойдут бредовые цены. } else if (m_optionType == StrikeType.Call) { callPx = strikeObj.Anchor.ValueY; optPx = callPx; // Здесь нельзя сразу домножать на scaleMultiplier! Потому что тогда в метод FillNodeInfo пойдут бредовые цены. } else { stradlePx = strikeObj.Anchor.ValueY; optPx = stradlePx; // Здесь нельзя сразу домножать на scaleMultiplier! Потому что тогда в метод FillNodeInfo пойдут бредовые цены. } double sigma = Double.NaN; double putSigma = Double.NaN, callSigma = Double.NaN, stradleSigma = Double.NaN, precision; if (DoubleUtil.IsPositive(putPx)) { // Цену опциона переводим в баксы только в момент вычисления айви putSigma = FinMath.GetOptionSigma(f, strike, dT, putPx * scaleMult, rate, false, out precision); putSigma = Math.Min(putSigma, m_maxSigma); if (putSigma <= 0) { putSigma = Double.NaN; } else { if (m_optionType == StrikeType.Put) { sigma = putSigma; } } } if (DoubleUtil.IsPositive(callPx)) { // Цену опциона переводим в баксы только в момент вычисления айви callSigma = FinMath.GetOptionSigma(f, strike, dT, callPx * scaleMult, rate, true, out precision); callSigma = Math.Min(callSigma, m_maxSigma); if (callSigma <= 0) { callSigma = Double.NaN; } else { if (m_optionType == StrikeType.Call) { sigma = callSigma; } } } if (DoubleUtil.IsPositive(stradlePx)) { // Цену опциона переводим в баксы только в момент вычисления айви stradleSigma = FinMath.GetStradleSigma(f, strike, dT, stradlePx * scaleMult, rate, out precision); stradleSigma = Math.Min(stradleSigma, m_maxSigma); if (stradleSigma <= 0) { stradleSigma = Double.NaN; } else { if (m_optionType == StrikeType.Any) { sigma = stradleSigma; } } } if (Double.IsNaN(sigma) || (sigma <= 0) || Double.IsNaN(optPx) || (optPx <= 0)) { continue; } InteractivePointActive ip = new InteractivePointActive(); { //ip.Color = (m_optionPxMode == OptionPxMode.Ask) ? Colors.DarkOrange : Colors.DarkCyan; //ip.DragableMode = DragableMode.None; //ip.Geometry = Geometries.Rect; // (optionPxMode == OptionPxMode.Ask) ? Geometries.Rect : Geometries.Rect; //ip.IsActive = true; ip.Value = new Point(strike, sigma); string nowStr = DateTime.Now.ToString(DateTimeFormatWithMs, CultureInfo.InvariantCulture); ip.Tooltip = String.Format(CultureInfo.InvariantCulture, "K:{0}; IV:{1:#0.00}%\r\n{2} {3} @ {4}\r\nDate: {5}", strike, sigma * Constants.PctMult, m_optionType, optPx, 1, nowStr); } InteractiveObject obj = new InteractiveObject(ip); if (m_optionType == StrikeType.Put) { if (!Double.IsNaN(putSigma)) { //FillNodeInfoDeribit(ip, f, dT, sInfo, StrikeType.Put, m_optionPxMode, putPx, putQty, putSigma, putTime, false); controlPoints.Add(obj); } } else if (m_optionType == StrikeType.Call) { if (!Double.IsNaN(callSigma)) { //FillNodeInfoDeribit(ip, f, dT, sInfo, StrikeType.Call, m_optionPxMode, callPx, callQty, callSigma, callTime, false); controlPoints.Add(obj); } } else if (m_optionType == StrikeType.Any) { if (!Double.IsNaN(stradleSigma)) { if (m_optionPxMode == OptionPxMode.Ask) { //if (putSigma < callSigma) // FillNodeInfoDeribit(ip, f, dT, sInfo, StrikeType.Put, m_optionPxMode, putPx, putQty, putSigma, putTime, false); //else // FillNodeInfoDeribit(ip, f, dT, sInfo, StrikeType.Call, m_optionPxMode, callPx, callQty, callSigma, callTime, false); } else if (m_optionPxMode == OptionPxMode.Bid) { //if (putSigma > callSigma) // FillNodeInfoDeribit(ip, f, dT, sInfo, StrikeType.Put, m_optionPxMode, putPx, putQty, putSigma, putTime, false); //else // FillNodeInfoDeribit(ip, f, dT, sInfo, StrikeType.Call, m_optionPxMode, callPx, callQty, callSigma, callTime, false); } controlPoints.Add(obj); } } } // ReSharper disable once UseObjectOrCollectionInitializer InteractiveSeries res = new InteractiveSeries(); // Здесь так надо -- мы делаем новую улыбку res.ControlPoints = new ReadOnlyCollection <InteractiveObject>(controlPoints); SmileInfo info; if (!IvSmile.TryPrepareSmileInfo(m_context, f, dT, rate, new DateTime(), new DateTime(), null, res, out info)) { return(Constants.EmptySeries); } return(res); }
/// <summary> /// Обработчик под тип входных данных OPTION_SERIES /// </summary> /// <param name="price">цена БА</param> /// <param name="time">время до экспирации в долях года</param> /// <param name="optSer">опционная серия</param> /// <param name="rate">процентная ставка</param> /// <param name="barNum">индекс бара в серии</param> /// <returns>улыбка, восстановленная из цен опционов</returns> public InteractiveSeries Execute(double price, double time, IOptionSeries optSer, double scaleMult, double rate, int barNum) { int barsCount = ContextBarsCount; if ((barNum < barsCount - 1) || (optSer == null)) { return(Constants.EmptySeries); } double f = price; double dT = time; if (!DoubleUtil.IsPositive(f)) { //throw new ScriptException("Argument 'price' contains NaN for some strange reason. f:" + f); return(Constants.EmptySeries); } if (!DoubleUtil.IsPositive(scaleMult)) { //throw new ScriptException("Argument 'scaleMult' contains NaN for some strange reason. scaleMult:" + scaleMult); return(Constants.EmptySeries); } if (!DoubleUtil.IsPositive(dT)) { return(Constants.EmptySeries); } if (Double.IsNaN(rate)) { //throw new ScriptException("Argument 'rate' contains NaN for some strange reason. rate:" + rate); return(Constants.EmptySeries); } IOptionStrikePair[] strikes = (from strike in optSer.GetStrikePairs() //orderby strike.Strike ascending -- уже отсортировано select strike).ToArray(); List <InteractiveObject> controlPoints = new List <InteractiveObject>(); for (int j = 0; j < strikes.Length; j++) { IOptionStrikePair sInfo = strikes[j]; // Сверхдалекие страйки игнорируем if ((sInfo.Strike < m_minStrike) || (m_maxStrike < sInfo.Strike)) { continue; } double putPxBtc, callPxBtc, putPxUsd, callPxUsd; double putQty, callQty; DateTime putTime, callTime; { putPxBtc = IvSmile.GetOptPrice(m_context, f, sInfo.Put, m_optionPxMode, sInfo.Tick * m_shiftAsk, sInfo.Tick * m_shiftBid, out putQty, out putTime); putPxUsd = putPxBtc * scaleMult; // Здесь нельзя сразу домножать на scaleMultiplier! Потому что тогда в метод FillNodeInfo пойдут бредовые цены. } { callPxBtc = IvSmile.GetOptPrice(m_context, f, sInfo.Call, m_optionPxMode, sInfo.Tick * m_shiftAsk, sInfo.Tick * m_shiftBid, out callQty, out callTime); callPxUsd = callPxBtc * scaleMult; // Здесь нельзя сразу домножать на scaleMultiplier! Потому что тогда в метод FillNodeInfo пойдут бредовые цены. } double putSigma = Double.NaN, callSigma = Double.NaN, precision; if (DoubleUtil.IsPositive(putPxBtc)) { // Цену опциона переводим в баксы только в момент вычисления айви putSigma = FinMath.GetOptionSigma(f, sInfo.Strike, dT, putPxUsd, rate, false, out precision); putSigma = Math.Min(putSigma, m_maxSigma); if (putSigma <= 0) { putSigma = Double.NaN; } } if (DoubleUtil.IsPositive(callPxBtc)) { // Цену опциона переводим в баксы только в момент вычисления айви callSigma = FinMath.GetOptionSigma(f, sInfo.Strike, dT, callPxUsd, rate, true, out precision); callSigma = Math.Min(callSigma, m_maxSigma); if (callSigma <= 0) { callSigma = Double.NaN; } } InteractivePointActive ip = new InteractivePointActive(); { //ip.Color = (m_optionPxMode == OptionPxMode.Ask) ? Colors.DarkOrange : Colors.DarkCyan; //ip.DragableMode = DragableMode.None; //ip.Geometry = Geometries.Rect; // (optionPxMode == OptionPxMode.Ask) ? Geometries.Rect : Geometries.Rect; //ip.IsActive = true; //ip.Value = new Point(d2.V1, d2.V2); //ip.Tooltip = String.Format("K:{0}; IV:{1:#0.00}", d2.V1, d2.V2 * PctMult); } InteractiveObject obj = new InteractiveObject(ip); if (m_optionType == StrikeType.Put) { if (DoubleUtil.IsPositive(putSigma)) { // Здесь используем первичную цену в том виде, как ее нам дал Дерибит FillNodeInfoDeribit(ip, f, dT, sInfo, StrikeType.Put, m_optionPxMode, putPxBtc, putQty, putSigma, putTime, false, rate, scaleMult); controlPoints.Add(obj); } } else if (m_optionType == StrikeType.Call) { if (DoubleUtil.IsPositive(callSigma)) { // Здесь используем первичную цену в том виде, как ее нам дал Дерибит FillNodeInfoDeribit(ip, f, dT, sInfo, StrikeType.Call, m_optionPxMode, callPxBtc, callQty, callSigma, callTime, false, rate, scaleMult); controlPoints.Add(obj); } } else if (m_optionType == StrikeType.Any) { if (DoubleUtil.IsPositive(putSigma) && DoubleUtil.IsPositive(callSigma)) { // Здесь используем первичную цену в том виде, как ее нам дал Дерибит if (m_optionPxMode == OptionPxMode.Ask) { if (putSigma < callSigma) { FillNodeInfoDeribit(ip, f, dT, sInfo, StrikeType.Put, m_optionPxMode, putPxBtc, putQty, putSigma, putTime, false, rate, scaleMult); } else { FillNodeInfoDeribit(ip, f, dT, sInfo, StrikeType.Call, m_optionPxMode, callPxBtc, callQty, callSigma, callTime, false, rate, scaleMult); } } else if (m_optionPxMode == OptionPxMode.Bid) { if (putSigma > callSigma) { FillNodeInfoDeribit(ip, f, dT, sInfo, StrikeType.Put, m_optionPxMode, putPxBtc, putQty, putSigma, putTime, false, rate, scaleMult); } else { FillNodeInfoDeribit(ip, f, dT, sInfo, StrikeType.Call, m_optionPxMode, callPxBtc, callQty, callSigma, callTime, false, rate, scaleMult); } } controlPoints.Add(obj); } else if (DoubleUtil.IsPositive(putSigma) && Double.IsNaN(callSigma)) { // Здесь используем первичную цену в том виде, как ее нам дал Дерибит FillNodeInfoDeribit(ip, f, dT, sInfo, StrikeType.Put, m_optionPxMode, putPxBtc, putQty, putSigma, putTime, false, rate, scaleMult); controlPoints.Add(obj); } else if (Double.IsNaN(putSigma) && DoubleUtil.IsPositive(callSigma)) { // Здесь используем первичную цену в том виде, как ее нам дал Дерибит FillNodeInfoDeribit(ip, f, dT, sInfo, StrikeType.Call, m_optionPxMode, callPxBtc, callQty, callSigma, callTime, false, rate, scaleMult); controlPoints.Add(obj); } } } // ReSharper disable once UseObjectOrCollectionInitializer InteractiveSeries res = new InteractiveSeries(); // Здесь так надо -- мы делаем новую улыбку res.ControlPoints = new ReadOnlyCollection <InteractiveObject>(controlPoints); SmileInfo info; var baseSec = optSer.UnderlyingAsset; DateTime scriptTime = baseSec.Bars[baseSec.Bars.Count - 1].Date; if (!IvSmile.TryPrepareSmileInfo(m_context, f, dT, rate, optSer.ExpirationDate, scriptTime, baseSec.Symbol, res, out info)) { return(Constants.EmptySeries); } return(res); }
public InteractiveSeries Execute(double price, double time, IOptionSeries optSer, double riskFreeRatePct, int barNum) { int barsCount = ContextBarsCount; if ((barNum < barsCount - 1) || (optSer == null)) { return(Constants.EmptySeries); } // В оптимизации ничего рисовать не надо if (Context.IsOptimization) { return(Constants.EmptySeries); } double futPx = price; double dT = time; if (!DoubleUtil.IsPositive(futPx)) { // [{0}] Base asset price must be positive value. F:{1} string msg = RM.GetStringFormat("OptHandlerMsg.FutPxMustBePositive", GetType().Name, futPx); m_context.Log(msg, MessageType.Error, true); return(Constants.EmptySeries); } if (!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); } // TODO: Нужно ли писать отдельный код для лаборатории? Чтобы показывать позиции из симуляции? // if (!Context.Runtime.IsAgentMode) List <InteractiveObject> controlPoints = new List <InteractiveObject>(); var allRealtimeSecs = Context.Runtime.Securities; IOptionStrikePair[] pairs = optSer.GetStrikePairs().ToArray(); for (int j = 0; j < pairs.Length; j++) { IOptionStrikePair pair = pairs[j]; #region Process put { var put = pair.Put.Security; // TODO: Нужно ли тут проверить наличие позиций??? //if (put.Positions.HavePositions) ISecurityRt secRt; if (put is ISecurityRt) { secRt = (ISecurityRt)put; } else { secRt = (from s in allRealtimeSecs where s.SecurityDescription.Equals(put) && (s is ISecurityRt) select(ISecurityRt) s).SingleOrDefault(); } if ((secRt != null) && secRt.HasActiveOrders) { //secRt.SecurityDescription.TradePlace.DataSource // ОТЛИЧНО! Эта коллекция позволит мне нарисовать свои заявки (это коллекция реальных заявок агента из таблицы My Orders) var orders = secRt.Orders.ToList(); foreach (IOrder ord in orders) { if (!ord.IsActive) { continue; } // Объект ord является RealtimeOrder. Его идентификатор совпадает с OrderNumber в таблице MyOrders if ((m_showLongOrders && ord.IsBuy) || ((!m_showLongOrders) && (!ord.IsBuy))) { // Почему-то InteractivePointLight хоть и давал себя настроить, но не отображался толком. double sigma = FinMath.GetOptionSigma(futPx, pair.Strike, dT, ord.Price, riskFreeRatePct, false); var ip = new InteractivePointActive(pair.Strike, sigma); ip.Tooltip = String.Format(CultureInfo.InvariantCulture, " F: {0}\r\n K: {1}; IV: {2:P2}\r\n {3} px {4} qty {5}", futPx, pair.Strike, sigma, pair.Put.StrikeType, ord.Price, ord.RestQuantity); controlPoints.Add(new InteractiveObject(ip)); } } } } #endregion Process put #region Process call { var call = pair.Call.Security; // TODO: Нужно ли тут проверить наличие позиций??? //if (call.Positions.HavePositions) ISecurityRt secRt; if (call is ISecurityRt) { secRt = (ISecurityRt)call; } else { secRt = (from s in allRealtimeSecs where s.SecurityDescription.Equals(call) && (s is ISecurityRt) select(ISecurityRt) s).SingleOrDefault(); } if ((secRt != null) && secRt.HasActiveOrders) { // ОТЛИЧНО! Эта коллекция позволит мне нарисовать свои заявки (это коллекция реальных заявок агента из таблицы My Orders) var orders = secRt.Orders.ToList(); foreach (IOrder ord in orders) { if (!ord.IsActive) { continue; } // Объект ord является RealtimeOrder. Его идентификатор совпадает с OrderNumber в таблице MyOrders if ((m_showLongOrders && ord.IsBuy) || ((!m_showLongOrders) && (!ord.IsBuy))) { // Почему-то InteractivePointLight хоть и давал себя настроить, но не отображался толком. double sigma = FinMath.GetOptionSigma(futPx, pair.Strike, dT, ord.Price, riskFreeRatePct, true); var ip = new InteractivePointActive(pair.Strike, sigma); ip.Tooltip = String.Format(CultureInfo.InvariantCulture, " F: {0}\r\n K: {1}; IV: {2:P2}\r\n {3} px {4} qty {5}", futPx, pair.Strike, sigma, pair.Call.StrikeType, ord.Price, ord.RestQuantity); controlPoints.Add(new InteractiveObject(ip)); } } } } #endregion Process call } // End for (int j = 0; j < pairs.Length; j++) // ReSharper disable once UseObjectOrCollectionInitializer InteractiveSeries res = new InteractiveSeries(); // Здесь так надо -- мы делаем новую улыбку res.ControlPoints = new ReadOnlyCollection <InteractiveObject>(controlPoints); return(res); }
/// <summary> /// Обработчик под тип входных данных OPTION_SERIES /// </summary> public IList <double> Execute(IOptionSeries optSer) { if (optSer == null) { string msg = "[IV ATM] (optSer == null)"; m_context.Log(msg, MessageType.Warning, false); return(Constants.EmptyListDouble); } Dictionary <DateTime, double> ivSigmas; #region Get cache DateTime expiry = optSer.ExpirationDate.Date; string cashKey = IvOnF.GetCashKey(optSer.UnderlyingAsset.Symbol, expiry, m_rescaleTime, m_tRemainMode); ivSigmas = LoadOrCreateHistoryDict(UseGlobalCache, cashKey); #endregion Get cache List <double> res; ISecurity sec = optSer.UnderlyingAsset; int len = sec.Bars.Count; if (len <= 0) { return(Constants.EmptyListDouble); } if (m_context.IsFixedBarsCount) { #region Ветка с ФИКСИРОВАННЫМ количеством баров double lastIv = Double.NaN; res = new List <double>(len); for (int j = 0; j < len; j++) { DateTime now = sec.Bars[j].Date; double iv; if ((ivSigmas.TryGetValue(now, out iv)) && (!Double.IsNaN(iv)) && (iv > 0)) { lastIv = iv; res.Add(iv); } else { if (m_repeatLastIv && (!Double.IsNaN(lastIv))) { res.Add(lastIv); } else { res.Add(Constants.NaN); } } } #endregion Ветка с ФИКСИРОВАННЫМ количеством баров } else { #region Ветка с нарастающим количеством баров res = LocalHistory; // PROD-1933 // 1. Выполняю очистку локального кеша в сценарии восстановления соединения после дисконнекта if (res.Count > len) { res.Clear(); } // 2. Ищу последнее валидное значение в кеше причем только если это может быть нужно double lastIv = Double.NaN; if (m_repeatLastIv) { for (int j = res.Count - 1; j >= 0; j--) { if ((!Double.IsNaN(res[j])) && (res[j] > 0)) { lastIv = res[j]; break; } } } for (int j = res.Count; j < len; j++) { DateTime now = sec.Bars[j].Date; double iv; if ((ivSigmas.TryGetValue(now, out iv)) && (!Double.IsNaN(iv)) && (iv > 0)) { lastIv = iv; res.Add(iv); } else { if (m_repeatLastIv && (!Double.IsNaN(lastIv))) { res.Add(lastIv); } else { res.Add(Constants.NaN); } } } #endregion Ветка с нарастающим количеством баров } Debug.Assert(res != null, "How is it possible (res == null)?"); Debug.Assert(res.Count == len, String.Format("Wrong res.Count. res.Count:{0}; expected len:{1}; IsFixedBarsCount:{2}", res.Count, len, m_context.IsFixedBarsCount)); FinInfo baseFinInfo = optSer.UnderlyingAsset.FinInfo; // Эта проверка намекает на проблемы с маркет-датой. if (baseFinInfo.LastPrice == null) { string msg = "[IV ATM] (baseFinInfo.LastPrice == null)"; m_context.Log(msg, MessageType.Warning, false); return(res); } try { double sigma; double futPx = baseFinInfo.LastPrice.Value; NotAKnotCubicSpline spline = PrepareExchangeSmileSpline(optSer, Double.MinValue, Double.MaxValue); if ((spline != null) && spline.TryGetValue(futPx, out sigma) && DoubleUtil.IsPositive(sigma)) { DateTime lastBarDate = sec.Bars[len - 1].Date; if (m_rescaleTime) { #region Зверская ветка по замене времени double ivAtm = sigma; DateTime expDate = optSer.ExpirationDate.Date + m_expiryTime; DateTime now = baseFinInfo.LastUpdate; // 1. Надо перевести волатильность в абсолютную цену // с учетом плоского календарного времени применяемого РТС double plainTimeAsYears; { double plainTimeAsDays; TimeToExpiry.GetDt(expDate, now, TimeRemainMode.PlainCalendar, false, out plainTimeAsDays, out plainTimeAsYears); } // 2. Вычисляем 'нормальное' время double timeAsDays, timeAsYears; TimeToExpiry.GetDt(expDate, now, m_tRemainMode, false, out timeAsDays, out timeAsYears); sigma = FinMath.RescaleIvToAnotherTime(plainTimeAsYears, ivAtm, timeAsYears); if (DoubleUtil.IsPositive(sigma)) { // Это просто запись на диск. К успешности вычисления волы success отношения не имеет bool success = TryWrite(m_context, UseGlobalCache, AllowGlobalReadWrite, GlobalSavePeriod, cashKey, ivSigmas, lastBarDate, sigma); // Теперь надо вычислить безразмерный наклон кодом в классе SmileImitation5 bool successSkew = TryCalcAndWriteSkews(m_context, spline, UseGlobalCache, AllowGlobalReadWrite, GlobalSavePeriod, optSer.UnderlyingAsset.Symbol, expiry, futPx, lastBarDate, m_tRemainMode, plainTimeAsYears, timeAsYears); } else { // Если перемасштабировать улыбку не получается придется эту точку проигнорировать // Надо ли сделать соответствующую запись в логе??? sigma = Constants.NaN; } #endregion Зверская ветка по замене времени } else { // Это просто запись на диск. К успешности вычисления волы success отношения не имеет bool success = TryWrite(m_context, UseGlobalCache, AllowGlobalReadWrite, GlobalSavePeriod, cashKey, ivSigmas, lastBarDate, sigma); } } else { sigma = Constants.NaN; } res[len - 1] = sigma; } catch (Exception ex) { m_context.Log(ex.ToString(), MessageType.Error, false); return(res); } if (m_repeatLastIv) { if (DoubleUtil.AreClose(res[len - 1], Constants.NaN) || Double.IsNaN(res[len - 1]) || (res[len - 1] <= 0)) { // Итерируюсь с конца в начало пока не найду последний ненулевой элемент. // Использую его в качестве ВСЕХ последних значений ряда. for (int j = len - 1; j >= 0; j--) { if ((!DoubleUtil.AreClose(res[j], Constants.NaN)) && (!Double.IsNaN(res[j])) && (res[j] > 0)) { double lastIv = res[j]; for (int k = j + 1; k < len; k++) { res[k] = lastIv; } break; } } } } return(new ReadOnlyCollection <double>(res)); }
/// <summary> /// Метод под флаг TemplateTypes.INTERACTIVESPLINE /// </summary> public InteractiveSeries Execute(InteractiveSeries smile, IOptionSeries optSer, InteractiveSeries quoteIv, double scaleMult, int barNum) { if ((smile == null) || (optSer == null)) { return(Constants.EmptySeries); } int barsCount = m_context.BarsCount; if (!m_context.IsLastBarUsed) { barsCount--; } if (barNum < barsCount - 1) { return(Constants.EmptySeries); } SmileInfo oldInfo = smile.GetTag <SmileInfo>(); if ((oldInfo == null) || (oldInfo.ContinuousFunction == null)) { return(Constants.EmptySeries); } double futPx = oldInfo.F; double dT = oldInfo.dT; // 1. Формируем маркеры заявок List <InteractiveObject> controlPoints = new List <InteractiveObject>(); PositionsManager posMan = PositionsManager.GetManager(m_context); IList <PositionsManager.IvTargetInfo> ivTargets = posMan.GetIvTargets(m_isLong, true); for (int j = 0; j < ivTargets.Count; j++) { var ivTarget = ivTargets[j]; // PROD-6102 - Требуется точное совпадение опционной серии if (optSer.ExpirationDate.Date != ivTarget.SecInfo.Expiry.Date) { // Вывести предупреждение??? continue; } IOptionStrikePair pair; double k = ivTarget.SecInfo.Strike; if (!optSer.TryGetStrikePair(k, out pair)) { // Вывести предупреждение??? continue; } double sigma; QuoteIvMode quoteMode = ivTarget.QuoteMode; if (quoteMode == QuoteIvMode.Absolute) { sigma = ivTarget.EntryIv; } else { sigma = oldInfo.ContinuousFunction.Value(k) + ivTarget.EntryIv; if (!DoubleUtil.IsPositive(sigma)) { //string msg = String.Format("[DEBUG:{0}] Invalid sigma:{1} for strike:{2}", GetType().Name, sigma, nodeInfo.Strike); //m_context.Log(msg, MessageType.Warning, true); continue; } } bool isCall = (futPx <= k); if ((ivTarget.SecInfo.StrikeType != null) && (ivTarget.SecInfo.StrikeType.Value != StrikeType.Any)) { isCall = (ivTarget.SecInfo.StrikeType.Value == StrikeType.Call); } StrikeType optionType = isCall ? StrikeType.Call : StrikeType.Put; Contract.Assert(pair.Tick < 1, $"На тестовом контуре Дерибит присылает неправильный шаг цены! Tick:{pair.Tick}; Decimals:{pair.Put.Security.Decimals}"); double theorOptPxDollars = FinMath.GetOptionPrice(futPx, pair.Strike, dT, sigma, oldInfo.RiskFreeRate, isCall); // Сразу(!!!) переводим котировку из баксов в битки double theorOptPxBitcoins = theorOptPxDollars / scaleMult; // Сдвигаем цену в долларах (с учетом ш.ц. в баксах) theorOptPxDollars += ivTarget.EntryShiftPrice * pair.Tick * scaleMult; theorOptPxDollars = Math.Round(theorOptPxDollars / (pair.Tick * scaleMult)) * (pair.Tick * scaleMult); // Сдвигаем цену в биткойнах (с учетом ш.ц. в битках) theorOptPxBitcoins += ivTarget.EntryShiftPrice * pair.Tick; theorOptPxBitcoins = Math.Round(theorOptPxBitcoins / pair.Tick) * pair.Tick; if ((!DoubleUtil.IsPositive(theorOptPxBitcoins)) || (!DoubleUtil.IsPositive(theorOptPxDollars))) { //string msg = String.Format("[DEBUG:{0}] Invalid theorOptPx:{1} for strike:{2}", GetType().Name, theorOptPx, nodeInfo.Strike); //m_context.Log(msg, MessageType.Warning, true); continue; } // Пересчитываем сигму обратно, ЕСЛИ мы применили сдвиг цены в абсолютном выражении if (ivTarget.EntryShiftPrice != 0) { sigma = FinMath.GetOptionSigma(futPx, pair.Strike, dT, theorOptPxDollars, oldInfo.RiskFreeRate, isCall); if (!DoubleUtil.IsPositive(sigma)) { //string msg = String.Format("[DEBUG:{0}] Invalid sigma:{1} for strike:{2}", GetType().Name, sigma, nodeInfo.Strike); //m_context.Log(msg, MessageType.Warning, true); continue; } } double totalQty; if (isCall) { totalQty = posMan.GetTotalQty(pair.Call.Security, m_context.BarsCount, TotalProfitAlgo.AllPositions, ivTarget.IsLong); } else { totalQty = posMan.GetTotalQty(pair.Put.Security, m_context.BarsCount, TotalProfitAlgo.AllPositions, ivTarget.IsLong); } double targetQty = Math.Abs(ivTarget.TargetShares) - totalQty; // ReSharper disable once UseObjectOrCollectionInitializer InteractivePointActive tmp = new InteractivePointActive(); // Попробуем по-простому? tmp.Tag = ivTarget; tmp.IsActive = true; tmp.ValueX = k; tmp.ValueY = sigma; tmp.DragableMode = DragableMode.None; if (ivTarget.EntryShiftPrice == 0) { tmp.Tooltip = String.Format(CultureInfo.InvariantCulture, " F: {0}\r\n K: {1}; IV: {2:P2}\r\n {3} px {4} rIV {5:P2} @ {6}", futPx, k, sigma, optionType, theorOptPxBitcoins, ivTarget.EntryIv, targetQty); } else { string shiftStr = (ivTarget.EntryShiftPrice > 0) ? "+" : "-"; shiftStr = shiftStr + Math.Abs(ivTarget.EntryShiftPrice) + "ps"; tmp.Tooltip = String.Format(CultureInfo.InvariantCulture, " F: {0}\r\n K: {1}; IV: {2:P2}\r\n {3} px {4} rIV {5:P2} {6} @ {7}", futPx, k, sigma, optionType, theorOptPxBitcoins, ivTarget.EntryIv, shiftStr, targetQty); } //tmp.Color = Colors.White; //if (m_qty > 0) // tmp.Geometry = Geometries.Triangle; //else if (m_qty < 0) // tmp.Geometry = Geometries.TriangleDown; //else // tmp.Geometry = Geometries.None; InteractiveObject obj = new InteractiveObject(); obj.Anchor = tmp; controlPoints.Add(obj); } // ReSharper disable once UseObjectOrCollectionInitializer InteractiveSeries res = new InteractiveSeries(); // Здесь так надо -- мы делаем новую улыбку res.ControlPoints = new ReadOnlyCollection <InteractiveObject>(controlPoints); if (controlPoints.Count > 0) { res.ClickEvent -= InteractiveSplineOnClickEvent; res.ClickEvent += InteractiveSplineOnClickEvent; m_clickableSeries = res; } return(res); }
/// <summary> /// Метод под флаг TemplateTypes.INTERACTIVESPLINE /// </summary> public InteractiveSeries Execute(InteractiveSeries quotes, InteractiveSeries smile, int barNum) { if ((quotes == null) || (smile == null)) { return(Constants.EmptySeries); } int barsCount = m_context.BarsCount; if (!m_context.IsLastBarUsed) { barsCount--; } if (barNum < barsCount - 1) { return(quotes); } SmileInfo oldInfo = smile.GetTag <SmileInfo>(); if ((oldInfo == null) || (oldInfo.ContinuousFunction == null)) { return(Constants.EmptySeries); } InteractiveSeries res = quotes; double futPx = oldInfo.F; double dT = oldInfo.dT; foreach (InteractiveObject obj in res.ControlPoints) { SmileNodeInfo nodeInfo = obj.Anchor.Tag as SmileNodeInfo; if (nodeInfo == null) { continue; } double realOptPx = nodeInfo.OptPx; bool isCall = (nodeInfo.OptionType == StrikeType.Call); double sigma = oldInfo.ContinuousFunction.Value(nodeInfo.Strike); if (Double.IsNaN(sigma) || (sigma < Double.Epsilon)) { //string msg = String.Format("[DEBUG:{0}] Invalid sigma:{1} for strike:{2}", GetType().Name, sigma, nodeInfo.Strike); //m_context.Log(msg, MessageType.Warning, true); continue; } double theorOptPx = FinMath.GetOptionPrice(futPx, nodeInfo.Strike, dT, sigma, oldInfo.RiskFreeRate, isCall); if (Double.IsNaN(theorOptPx) || (theorOptPx < Double.Epsilon)) { //string msg = String.Format("[DEBUG:{0}] Invalid theorOptPx:{1} for strike:{2}", GetType().Name, theorOptPx, nodeInfo.Strike); //m_context.Log(msg, MessageType.Warning, true); continue; } if (nodeInfo.PxMode == OptionPxMode.Ask) { double doLevel = theorOptPx - m_widthPx * nodeInfo.Pair.Tick; if (realOptPx <= doLevel) { var anchor = (InteractivePointActive)obj.Anchor; // ReSharper disable once UseObjectOrCollectionInitializer var tmp = new InteractivePointActive(); tmp.IsActive = true; tmp.Tag = anchor.Tag; tmp.Tooltip = null; // anchor.Tooltip; int decimals = (nodeInfo.Security != null) ? nodeInfo.Security.Decimals : 0; string pot = Math.Abs(theorOptPx - realOptPx).ToString("N" + decimals, CultureInfo.InvariantCulture); tmp.Label = anchor.Tooltip + " (" + pot + ")"; tmp.ValueX = anchor.ValueX; tmp.ValueY = anchor.ValueY + m_outlet; tmp.DragableMode = DragableMode.None; tmp.Size = m_outletSize; //tmp.Color = Colors.White; //tmp.Geometry = m_outletGeometry; // Geometries.Ellipse; obj.ControlPoint1 = tmp; } } if (nodeInfo.PxMode == OptionPxMode.Bid) { double upLevel = theorOptPx + m_widthPx * nodeInfo.Pair.Tick; if (realOptPx >= upLevel) { var anchor = (InteractivePointActive)obj.Anchor; // ReSharper disable once UseObjectOrCollectionInitializer var tmp = new InteractivePointActive(); tmp.IsActive = true; tmp.Tag = anchor.Tag; tmp.Tooltip = null; // anchor.Tooltip; int decimals = (nodeInfo.Security != null) ? nodeInfo.Security.Decimals : 0; string pot = Math.Abs(theorOptPx - realOptPx).ToString("N" + decimals, CultureInfo.InvariantCulture); tmp.Label = anchor.Tooltip + " (" + pot + ")"; tmp.ValueX = anchor.ValueX; tmp.ValueY = anchor.ValueY - m_outlet; tmp.DragableMode = DragableMode.None; tmp.Size = m_outletSize; //tmp.Color = Colors.White; //tmp.Geometry = m_outletGeometry; // Geometries.Ellipse; obj.ControlPoint1 = tmp; } } } res.ClickEvent -= InteractiveSplineOnClickEvent; res.ClickEvent += InteractiveSplineOnClickEvent; m_clickableSeries = res; return(res); }
public InteractiveSeries Execute(double price, double time, InteractiveSeries smile, IOptionSeries optSer, double riskFreeRatePct, int barNum) { int barsCount = ContextBarsCount; if ((barNum < barsCount - 1) || (optSer == null)) { return(Constants.EmptySeries); } // В оптимизации ничего рисовать не надо if (Context.IsOptimization) { return(Constants.EmptySeries); } int lastBarIndex = optSer.UnderlyingAsset.Bars.Count - 1; DateTime now = optSer.UnderlyingAsset.Bars[Math.Min(barNum, lastBarIndex)].Date; bool wasInitialized = HandlerInitializedToday(now); double futPx = price; double dT = time; if (!DoubleUtil.IsPositive(dT)) { // [{0}] Time to expiry must be positive value. dT:{1} string msg = RM.GetStringFormat("OptHandlerMsg.TimeMustBePositive", GetType().Name, dT); m_context.Log(msg, MessageType.Error, true); return(Constants.EmptySeries); } if (!DoubleUtil.IsPositive(futPx)) { // [{0}] Base asset price must be positive value. F:{1} string msg = RM.GetStringFormat("OptHandlerMsg.FutPxMustBePositive", GetType().Name, futPx); m_context.Log(msg, MessageType.Error, true); return(Constants.EmptySeries); } if (smile == null) { string msg = String.Format("[{0}] Argument 'smile' must be filled with InteractiveSeries.", GetType().Name); if (wasInitialized) { m_context.Log(msg, MessageType.Error, false); } return(Constants.EmptySeries); } SmileInfo oldInfo = smile.GetTag <SmileInfo>(); if (oldInfo == null) { string msg = String.Format("[{0}] Property Tag of object smile must be filled with SmileInfo. Tag:{1}", GetType().Name, smile.Tag); if (wasInitialized) { m_context.Log(msg, MessageType.Error, false); } return(Constants.EmptySeries); } if (!oldInfo.IsValidSmileParams) { string msg = String.Format("[{0}] SmileInfo must have valid smile params. IsValidSmileParams:{1}", GetType().Name, oldInfo.IsValidSmileParams); if (wasInitialized) { m_context.Log(msg, MessageType.Error, false); } return(Constants.EmptySeries); } double ivAtm; if (!oldInfo.ContinuousFunction.TryGetValue(futPx, out ivAtm)) { return(Constants.EmptySeries); } if (!DoubleUtil.IsPositive(ivAtm)) { // [{0}] ivAtm must be positive value. ivAtm:{1} string msg = RM.GetStringFormat("OptHandlerMsg.IvAtmMustBePositive", GetType().Name, ivAtm); if (wasInitialized) { m_context.Log(msg, MessageType.Error, true); } return(Constants.EmptySeries); } // TODO: Нужно ли писать отдельный код для лаборатории? Чтобы показывать позиции из симуляции? // if (!Context.Runtime.IsAgentMode) IOptionStrikePair[] pairs = optSer.GetStrikePairs().ToArray(); if (pairs.Length < 2) { string msg = String.Format("[{0}] optSer must contain few strike pairs. pairs.Length:{1}", GetType().Name, pairs.Length); if (wasInitialized) { m_context.Log(msg, MessageType.Warning, true); } return(Constants.EmptySeries); } double futStep = optSer.UnderlyingAsset.Tick; PositionsManager posMan = PositionsManager.GetManager(m_context); // Вытаскиваем ВСЕ позиции фьючерса ReadOnlyCollection <IPosition> basePositions = posMan.GetClosedOrActiveForBar(optSer.UnderlyingAsset); // Вытаскиваем ВСЕ позиции опционов var optPositions = SingleSeriesProfile.GetAllOptionPositions(posMan, pairs); // 1. Если в позиции вообще нет опционов -- сразу выходим. Эффективную волатильность построить нельзя. int posAmount = (from t in optPositions select(t.Item1.Count + t.Item2.Count)).Sum(); if (posAmount <= 0) { return(Constants.EmptySeries); } // 2. TODO: посчитать позиции без учета синтетических фьючерсов. Если позиция только из синтетики -- выходим. // 3. Вычисляем эффективную волатильность и заодно разбиваем позицию на длинные и короткие // Но это имеет смысл только если сразу рисовать позу!!! double effectiveLongIvAtm, effectiveShortIvAtm; Tuple <ReadOnlyCollection <IPosition>, ReadOnlyCollection <IPosition> >[] longPositions; Tuple <ReadOnlyCollection <IPosition>, ReadOnlyCollection <IPosition> >[] shortPositions; bool ok = posMan.TryEstimateEffectiveIv(oldInfo, optSer, lastBarIndex, out effectiveLongIvAtm, out effectiveShortIvAtm, out longPositions, out shortPositions); if (!ok) { // Мы не смогли завершить алгоритм, но получили какую-то оценку волатильностей. Нарисуем ее? if ((!DoubleUtil.IsPositive(effectiveLongIvAtm)) || (!DoubleUtil.IsPositive(effectiveShortIvAtm))) { return(Constants.EmptySeries); } } Contract.Assert(longPositions != null, "longPositions==null ???"); Contract.Assert(shortPositions != null, "shortPositions==null ???"); double actualIv = ShowLongPositions ? effectiveLongIvAtm : effectiveShortIvAtm; double displayValue = FixedValue.ConvertToDisplayUnits(m_valueMode, actualIv); m_displayIv.Value = displayValue; Contract.Assert(DoubleUtil.IsPositive(actualIv), $"Это вообще что-то странное. Почему плохой айви? actualIv:{actualIv}"); // Это вообще что-то странное. Как так? if (!DoubleUtil.IsPositive(actualIv)) { return(Constants.EmptySeries); } // 5. Подготавливаю улыбку (достаточно функции, без обвязки) var lowSmileFunc = new SmileFunctionExtended( SmileFunction5.TemplateFuncRiz4Nov1, actualIv, oldInfo.SkewAtm, oldInfo.Shape, futPx, dT); // 7. Подготавливаем графическое отображение позиции. Причем нам даже сплайн не нужен. var actualPositions = ShowLongPositions ? longPositions : shortPositions; List <InteractiveObject> controlPoints = new List <InteractiveObject>(); for (int j = 0; j < pairs.Length; j++) { var pair = pairs[j]; var tuple = actualPositions[j]; // На данном страйке позиций нет? Идем дальше. if ((tuple.Item1.Count <= 0) && (tuple.Item2.Count <= 0)) { continue; } double sigma; if ((!lowSmileFunc.TryGetValue(pair.Strike, out sigma)) || (!DoubleUtil.IsPositive(sigma))) { // TODO: Это очень странно. Вывести в лог? Проигнорировать страйк? sigma = actualIv; } var putPositions = tuple.Item1; var callPositions = tuple.Item2; double putQty = PositionsManager.GetTotalQty(putPositions); double callQty = PositionsManager.GetTotalQty(callPositions); int decimals = optSer.UnderlyingAsset.Decimals + 1; double putPx = FinMath.GetOptionPrice(futPx, pair.Strike, dT, sigma, riskFreeRatePct, false); double callPx = FinMath.GetOptionPrice(futPx, pair.Strike, dT, sigma, riskFreeRatePct, true); string putPxStr = putPx.ToString("N" + decimals, CultureInfo.InvariantCulture); string callPxStr = callPx.ToString("N" + decimals, CultureInfo.InvariantCulture); // ReSharper disable once UseObjectOrCollectionInitializer InteractivePointActive ip = new InteractivePointActive(); // TODO: вывести в тултип дополнительные подробности о составе позиции на этом страйке ip.Tooltip = String.Format(CultureInfo.InvariantCulture, " K: {0}; IV: {1:#0.00}%\r\n PutQty: {2}; CallQty: {3}\r\n PutPx: {4}; CallPx: {5}\r\n Total: {6}", pair.Strike, sigma * Constants.PctMult, putQty, callQty, putPxStr, callPxStr, putQty + callQty); ip.Value = new Point(pair.Strike, sigma); controlPoints.Add(new InteractiveObject(ip)); } // ReSharper disable once UseObjectOrCollectionInitializer InteractiveSeries res = new InteractiveSeries(); // Здесь так надо -- мы делаем новую улыбку res.ControlPoints = new ReadOnlyCollection <InteractiveObject>(controlPoints); SetHandlerInitialized(now, true); return(res); }
/// <summary> /// Основной метод, который выполняет всю торговую логику по котированию и следит за риском /// </summary> protected double Process(double entryPermission, double strike, double risk, double maxRisk, InteractiveSeries smile, IOptionSeries optSer, double callRisk, double putRisk, int barNum) { int barsCount = m_context.BarsCount; if (!m_context.IsLastBarUsed) { barsCount--; } if ((barNum < barsCount - 1) || (optSer == null) || (smile == null)) { return(Constants.NaN); } IOptionStrikePair pair; if (!optSer.TryGetStrikePair(strike, out pair)) { return(Constants.NaN); } // Если риск не был измерен говорить вообще не о чем! if (Double.IsNaN(risk) || Double.IsInfinity(risk)) { return(Constants.NaN); } // Если риск разумен и условие входа НЕ ВЫПОЛНЕНО -- отдыхаем. // А вот если риск превышен -- тогда по идее надо бы его подсократить! if ((risk < maxRisk) && (entryPermission <= 0)) { return(Constants.NaN); } PositionsManager posMan = PositionsManager.GetManager(m_context); if (posMan.BlockTrading) { //string msg = String.Format("Trading is blocked. Please, change 'Block Trading' parameter."); //m_context.Log(msg, MessageType.Info, true); return(Constants.NaN); } SmileInfo sInfo = smile.GetTag <SmileInfo>(); if ((sInfo == null) || (sInfo.ContinuousFunction == null)) { return(Constants.NaN); } double dT = sInfo.dT; double futPx = sInfo.F; // Набираем риск if (risk < maxRisk) { double ivAtm; if ((!sInfo.ContinuousFunction.TryGetValue(strike, out ivAtm)) || Double.IsNaN(ivAtm) || (ivAtm < Double.Epsilon)) { string msg = String.Format("[{0}.{1}] Unable to get IV at strike {2}. ivAtm:{3}", Context.Runtime.TradeName, GetType().Name, pair.Strike, ivAtm); m_context.Log(msg, MessageType.Error, true); return(Constants.NaN); } double theorPutPx = FinMath.GetOptionPrice(futPx, strike, dT, ivAtm, 0, false); double theorCallPx = FinMath.GetOptionPrice(futPx, strike, dT, ivAtm, 0, true); #region Набираем риск double putPx, callPx; { double putQty, callQty; DateTime putTime, callTime; putPx = IvSmile.GetOptPrice(m_context, futPx, pair.Put, OptionPxMode.Ask, 0, 0, out putQty, out putTime); callPx = IvSmile.GetOptPrice(m_context, futPx, pair.Call, OptionPxMode.Ask, 0, 0, out callQty, out callTime); } if (m_optionType == StrikeType.Put) { #region В путах ISecurity sec = pair.Put.Security; double qty = Math.Abs(m_fixedQty); qty = GetSafeQty(risk, maxRisk, qty, putRisk); if (qty > 0) { double px = SellOptions.SafeMinPrice(theorPutPx + m_entryShift * sec.Tick, putPx, sec); double iv = FinMath.GetOptionSigma(futPx, pair.Strike, dT, px, 0, false); string sigName = String.Format("Risk:{0}; MaxRisk:{1}; Px:{2}; Qty:{3}; IV:{4:P2}; dT:{5}", risk, maxRisk, px, qty, iv, dT); posMan.BuyAtPrice(m_context, sec, qty, px, "Open BUY", sigName); m_context.Log(sigName, MessageType.Info, false); } #endregion В путах } else if (m_optionType == StrikeType.Call) { #region В колах ISecurity sec = pair.Call.Security; double qty = Math.Abs(m_fixedQty); qty = GetSafeQty(risk, maxRisk, qty, callRisk); if (qty > 0) { double px = SellOptions.SafeMinPrice(theorCallPx + m_entryShift * sec.Tick, callPx, sec); double iv = FinMath.GetOptionSigma(futPx, pair.Strike, dT, px, 0, true); string sigName = String.Format("Risk:{0}; MaxRisk:{1}; Px:{2}; Qty:{3}; IV:{4:P2}; dT:{5}", risk, maxRisk, px, qty, iv, dT); posMan.BuyAtPrice(m_context, sec, qty, px, "Open BUY", sigName); m_context.Log(sigName, MessageType.Info, false); } #endregion В колах } else { #region В оба вида опционов сразу встаю int executedQty = 0; { ISecurity sec = pair.Put.Security; double px = SellOptions.SafeMinPrice(theorPutPx + m_entryShift * sec.Tick, putPx, sec); double iv = FinMath.GetOptionSigma(futPx, pair.Strike, dT, px, 0, false); double qty = Math.Max(1, Math.Abs(m_fixedQty / 2)); qty = GetSafeQty(risk, maxRisk, qty, putRisk); if (qty > 0) { string sigName = String.Format("Risk:{0}; MaxRisk:{1}; Px:{2}; Qty:{3}; IV:{4:P2}; dT:{5}", risk, maxRisk, px, qty, iv, dT); posMan.BuyAtPrice(m_context, sec, qty, px, "Open BUY", sigName); m_context.Log(sigName, MessageType.Info, false); executedQty += (int)qty; } } if (Math.Abs(executedQty) < Math.Abs(m_fixedQty)) { ISecurity sec = pair.Call.Security; double px = SellOptions.SafeMinPrice(theorCallPx + m_entryShift * sec.Tick, callPx, sec); double iv = FinMath.GetOptionSigma(futPx, pair.Strike, dT, px, 0, true); double qty = Math.Abs(m_fixedQty) - Math.Abs(executedQty); // Делаю оценку изменения текущего риска, если нам зафилят заявку в путах qty = GetSafeQty(risk + Math.Abs(executedQty) * putRisk, maxRisk, qty, callRisk); if (qty > 0) { string sigName = String.Format("Risk:{0}; MaxRisk:{1}; Px:{2}; Qty:{3}; IV:{4:P2}; dT:{5}", risk, maxRisk, px, qty, iv, dT); posMan.BuyAtPrice(m_context, sec, qty, px, "Open BUY", sigName); m_context.Log(sigName, MessageType.Info, false); //executedQty += (int)qty; } } #endregion В оба вида опционов сразу встаю } #endregion Набираем риск } else if (risk > maxRisk) { string msg; //string msg = String.Format("[DEBUG:{0}] risk:{1}; maxRisk:{2}", Context.Runtime.TradeName, risk, maxRisk); //m_context.Log(msg, MessageType.Info, true); // Надо взять пары, начиная от центральной и далее по возрастанию расстояния var orderedPairs = (from p in optSer.GetStrikePairs() orderby Math.Abs(p.Strike - strike) ascending select p).ToArray(); if (orderedPairs.Length > 0) { foreach (IOptionStrikePair candidPair in orderedPairs) { #region Проверяю, что в страйке есть ДЛИННАЯ позиция double putOpenQty = posMan.GetTotalQty(candidPair.Put.Security, barNum); double callOpenQty = posMan.GetTotalQty(candidPair.Call.Security, barNum); if ((putOpenQty <= 0) && (callOpenQty <= 0)) { continue; } if (DoubleUtil.IsZero(putOpenQty) && DoubleUtil.IsZero(callOpenQty)) { continue; } { msg = String.Format("[{0}:{1}] Strike:{2}; putOpenQty:{3}; callOpenQty:{4}", Context.Runtime.TradeName, GetType().Name, candidPair.Strike, putOpenQty, callOpenQty); m_context.Log(msg, MessageType.Info, true); } #endregion Проверяю, что в страйке есть ДЛИННАЯ позиция double theorPutPx, theorCallPx; { double iv; if ((!sInfo.ContinuousFunction.TryGetValue(candidPair.Strike, out iv)) || Double.IsNaN(iv) || (iv < Double.Epsilon)) { msg = String.Format("[{0}.{1}] Unable to get IV at strike {2}. IV:{3}", Context.Runtime.TradeName, GetType().Name, candidPair.Strike, iv); m_context.Log(msg, MessageType.Error, true); continue; } theorPutPx = FinMath.GetOptionPrice(futPx, candidPair.Strike, dT, iv, 0, false); theorCallPx = FinMath.GetOptionPrice(futPx, candidPair.Strike, dT, iv, 0, true); } #region Сдаём риск (один квант объёма за раз) double putPx, callPx; { double putQty, callQty; DateTime putTime, callTime; putPx = IvSmile.GetOptPrice(m_context, futPx, candidPair.Put, OptionPxMode.Bid, 0, 0, out putQty, out putTime); callPx = IvSmile.GetOptPrice(m_context, futPx, candidPair.Call, OptionPxMode.Bid, 0, 0, out callQty, out callTime); } if (m_optionType == StrikeType.Put) { #region В путах if (putOpenQty > 0) // Это означает, что в страйке есть длинные путы { ISecurity sec = candidPair.Put.Security; double px = SellOptions.SafeMaxPrice(theorPutPx + m_exitShift * sec.Tick, putPx, sec); double iv = FinMath.GetOptionSigma(futPx, candidPair.Strike, dT, px, 0, false); double qty = Math.Min(Math.Abs(m_fixedQty), Math.Abs(putOpenQty)); string sigName = String.Format("Risk:{0}; MaxRisk:{1}; Px:{2}; Qty:{3}; IV:{4:P2}; dT:{5}", risk, maxRisk, px, qty, iv, dT); posMan.SellAtPrice(m_context, sec, qty, px, "Close SELL", sigName); m_context.Log(sigName, MessageType.Info, false); // Выход из foreach (IOptionStrikePair candidPair in orderedPairs) break; } #endregion В путах } else if (m_optionType == StrikeType.Call) { #region В колах if (callOpenQty > 0) // Это означает, что в страйке есть длинные колы { ISecurity sec = candidPair.Call.Security; double px = SellOptions.SafeMaxPrice(theorCallPx + m_exitShift * sec.Tick, callPx, sec); double iv = FinMath.GetOptionSigma(futPx, candidPair.Strike, dT, px, 0, true); double qty = Math.Min(Math.Abs(m_fixedQty), Math.Abs(callOpenQty)); string sigName = String.Format("Risk:{0}; MaxRisk:{1}; Px:{2}; Qty:{3}; IV:{4:P2}; dT:{5}", risk, maxRisk, px, qty, iv, dT); posMan.SellAtPrice(m_context, sec, qty, px, "Close SELL", sigName); m_context.Log(sigName, MessageType.Info, false); // Выход из foreach (IOptionStrikePair candidPair in orderedPairs) break; } #endregion В колах } else { #region В оба вида опционов сразу встаю int executedQty = 0; if (putOpenQty > 0) // Это означает, что в страйке есть длинные путы { ISecurity sec = candidPair.Put.Security; double px = SellOptions.SafeMaxPrice(theorPutPx + m_exitShift * sec.Tick, putPx, sec); double iv = FinMath.GetOptionSigma(futPx, candidPair.Strike, dT, px, 0, false); double qty = Math.Min(Math.Abs(m_fixedQty), Math.Abs(putOpenQty)); string sigName = String.Format("Risk:{0}; MaxRisk:{1}; Px:{2}; Qty:{3}; IV:{4:P2}; dT:{5}", risk, maxRisk, px, qty, iv, dT); posMan.SellAtPrice(m_context, sec, qty, px, "Close SELL", sigName); m_context.Log(sigName, MessageType.Info, false); executedQty += (int)qty; } if ((callOpenQty > 0) && // Это означает, что в страйке есть длинные колы (Math.Abs(executedQty) < Math.Abs(m_fixedQty))) { ISecurity sec = candidPair.Call.Security; double px = SellOptions.SafeMaxPrice(theorCallPx + m_exitShift * sec.Tick, callPx, sec); double iv = FinMath.GetOptionSigma(futPx, candidPair.Strike, dT, px, 0, true); double qty = Math.Min(Math.Abs(m_fixedQty) - Math.Abs(executedQty), Math.Abs(callOpenQty)); string sigName = String.Format("Risk:{0}; MaxRisk:{1}; Px:{2}; Qty:{3}; IV:{4:P2}; dT:{5}", risk, maxRisk, px, qty, iv, dT); posMan.SellAtPrice(m_context, sec, qty, px, "Close SELL", sigName); m_context.Log(sigName, MessageType.Info, false); executedQty += (int)qty; } if (executedQty > 0) { // Выход из foreach (IOptionStrikePair candidPair in orderedPairs) break; } #endregion В оба вида опционов сразу встаю } #endregion Сдаём риск (один квант объёма за раз) } } else { msg = String.Format("[{0}.{1}] risk:{2}; maxRisk:{3}; orderedPairs.Length:{4}", Context.Runtime.TradeName, GetType().Name, risk, maxRisk, orderedPairs.Length); m_context.Log(msg, MessageType.Warning, true); } } return(Constants.NaN); }
/// <summary> /// Основной метод, который выполняет всю торговую логику по котированию и следит за риском /// </summary> protected double Process(double entryPermission, double centralStrike, double risk, double maxRisk, InteractiveSeries smile, IOptionSeries optSer, InteractiveSeries callDelta, double callRisk, double putRisk, int barNum) { int barsCount = m_context.BarsCount; if (!m_context.IsLastBarUsed) { barsCount--; } if ((barNum < barsCount - 1) || (optSer == null) || (smile == null)) { return(Constants.NaN); } { IOptionStrikePair testPair; if (!optSer.TryGetStrikePair(centralStrike, out testPair)) { return(Constants.NaN); } } // Если риск не был измерен говорить вообще не о чем! if (Double.IsNaN(risk)) { return(Constants.NaN); } // Если риск разумен и условие входа НЕ ВЫПОЛНЕНО -- отдыхаем. // А вот если риск превышен -- тогда по идее надо бы его подсократить! if ((risk < maxRisk) && (entryPermission <= 0)) { return(Constants.NaN); } PositionsManager posMan = PositionsManager.GetManager(m_context); if (posMan.BlockTrading) { //string msg = String.Format("Trading is blocked. Please, change 'Block Trading' parameter."); //m_context.Log(msg, MessageType.Info, true); return(Constants.NaN); } SmileInfo sInfo = smile.GetTag <SmileInfo>(); if ((sInfo == null) || (sInfo.ContinuousFunction == null)) { return(Constants.NaN); } double dT = sInfo.dT; double futPx = sInfo.F; SmileInfo callDeltaInfo = callDelta.GetTag <SmileInfo>(); if ((callDeltaInfo == null) || (callDeltaInfo.ContinuousFunction == null)) { return(Constants.NaN); } // Функция для вычисления дельты кола IFunction cDf = callDeltaInfo.ContinuousFunction; // Набираем риск if (risk < maxRisk) { List <IOptionStrikePair> orderedPairs = BuyOptionGroupDelta.GetFilteredPairs(optSer, centralStrike, cDf, m_strikeStep, m_minDelta, m_maxDelta, m_checkAbsDelta); // Сколько лотов уже выставлено в рынок double pendingQty = 0; if (orderedPairs.Count > 0) { foreach (IOptionStrikePair candidPair in orderedPairs) { double ivAtm; double strike = candidPair.Strike; if ((!sInfo.ContinuousFunction.TryGetValue(strike, out ivAtm)) || Double.IsNaN(ivAtm) || (ivAtm < Double.Epsilon)) { string msg = String.Format("[{0}.{1}] Unable to get IV at strike {2}. ivAtm:{3}", Context.Runtime.TradeName, GetType().Name, candidPair.Strike, ivAtm); m_context.Log(msg, MessageType.Error, true); return(Constants.NaN); } double theorPutPx = FinMath.GetOptionPrice(futPx, strike, dT, ivAtm, 0, false); double theorCallPx = FinMath.GetOptionPrice(futPx, strike, dT, ivAtm, 0, true); double cd, pd; // Вычисляю дельту кола и с ее помощью -- дельту пута if (!cDf.TryGetValue(strike, out cd)) { // Этого не может быть по правилу отбора страйков! Contract.Assert(false, "Почему мы не смогли вычислить дельту кола???"); continue; } // Типа, колл-пут паритет для вычисления дельты путов pd = 1 - cd; if (m_checkAbsDelta) { // Берем дельты по модулю cd = Math.Abs(cd); pd = Math.Abs(pd); } double putPx, callPx; { double putQty, callQty; DateTime putTime, callTime; putPx = IvSmile.GetOptPrice(m_context, futPx, candidPair.Put, OptionPxMode.Bid, 0, 0, out putQty, out putTime); callPx = IvSmile.GetOptPrice(m_context, futPx, candidPair.Call, OptionPxMode.Bid, 0, 0, out callQty, out callTime); } #region Набираем риск int executedQty = 0; // Если дельта пута влезает в диапазон -- выставляем котировку в путы if ((m_minDelta <= pd) && (pd <= m_maxDelta)) { #region Набираем риск в путах ISecurity sec = candidPair.Put.Security; double px = SellOptions.SafeMaxPrice(theorPutPx + m_entryShift * sec.Tick, putPx, sec); double iv = FinMath.GetOptionSigma(futPx, candidPair.Strike, dT, px, 0, false); double qty = Math.Abs(m_fixedQty); // TODO: Немного грубая оценка, но пока сойдет qty = BuyOptions.GetSafeQty(risk + pendingQty * putRisk, maxRisk, qty, putRisk); if (qty > 0) { string sigName = String.Format("Risk:{0}; MaxRisk:{1}; Px:{2}; Qty:{3}; IV:{4:P2}; dT:{5}", risk, maxRisk, px, qty, iv, dT); posMan.SellAtPrice(m_context, sec, qty, px, "Open SELL", sigName); pendingQty += qty; m_context.Log(sigName, MessageType.Info, false); executedQty += (int)qty; } #endregion Набираем риск в путах } // Если дельта кола влезает в диапазон -- выставляем котировку в колы if (Math.Abs(executedQty) < Math.Abs(m_fixedQty) && (m_minDelta <= cd) && (cd <= m_maxDelta)) { #region Набираем риск в колах ISecurity sec = candidPair.Call.Security; double px = SellOptions.SafeMaxPrice(theorCallPx + m_entryShift * sec.Tick, callPx, sec); double iv = FinMath.GetOptionSigma(futPx, candidPair.Strike, dT, px, 0, true); double qty = Math.Abs(m_fixedQty) - Math.Abs(executedQty); // TODO: Немного грубая оценка, но пока сойдет // Делаю оценку изменения текущего риска, если нам зафилят заявку в путах //qty = BuyOptions.GetSafeQty(risk + Math.Abs(executedQty) * putRisk, maxRisk, qty, callRisk); // Причем здесь уже не нужно отдельно учитывать executedQty, потому что он входит в pendingQty qty = BuyOptions.GetSafeQty(risk + pendingQty * callRisk, maxRisk, qty, callRisk); if (qty > 0) { string sigName = String.Format("Risk:{0}; MaxRisk:{1}; Px:{2}; Qty:{3}; IV:{4:P2}; dT:{5}", risk, maxRisk, px, qty, iv, dT); posMan.SellAtPrice(m_context, sec, qty, px, "Open SELL", sigName); pendingQty += qty; m_context.Log(sigName, MessageType.Info, false); //executedQty += (int)qty; } #endregion Набираем риск в колах } #endregion Набираем риск } // End foreach (IOptionStrikePair candidPair in orderedPairs) } else { string msg = String.Format("[{0}] Strike not found. risk:{1}; maxRisk:{2}; orderedPairs.Count:{3}", Context.Runtime.TradeName, risk, maxRisk, orderedPairs.Count); m_context.Log(msg, MessageType.Warning, true); } } else if (risk > maxRisk) { string msg; //string msg = String.Format("[DEBUG:{0}] risk:{1}; maxRisk:{2}", Context.Runtime.TradeName, risk, maxRisk); //m_context.Log(msg, MessageType.Info, true); // Надо взять пары, начиная от центральной и далее по возрастанию расстояния var orderedPairs = (from p in optSer.GetStrikePairs() orderby Math.Abs(p.Strike - centralStrike) ascending select p).ToArray(); if (orderedPairs.Length > 0) { foreach (IOptionStrikePair candidPair in orderedPairs) { #region Проверяю, что в страйке есть КОРОТКАЯ позиция double putOpenQty = posMan.GetTotalQty(candidPair.Put.Security, barNum); double callOpenQty = posMan.GetTotalQty(candidPair.Call.Security, barNum); if ((putOpenQty >= 0) && (callOpenQty >= 0)) { continue; } if (DoubleUtil.IsZero(putOpenQty) && DoubleUtil.IsZero(callOpenQty)) { continue; } { msg = String.Format("[{0}:{1}] Strike:{2}; putOpenQty:{3}; callOpenQty:{4}", Context.Runtime.TradeName, GetType().Name, candidPair.Strike, putOpenQty, callOpenQty); m_context.Log(msg, MessageType.Info, true); } #endregion Проверяю, что в страйке есть КОРОТКАЯ позиция double theorPutPx, theorCallPx; { double iv; if ((!sInfo.ContinuousFunction.TryGetValue(candidPair.Strike, out iv)) || Double.IsNaN(iv) || (iv < Double.Epsilon)) { msg = String.Format("[{0}.{1}] Unable to get IV at strike {2}. IV:{3}", Context.Runtime.TradeName, GetType().Name, candidPair.Strike, iv); m_context.Log(msg, MessageType.Error, true); continue; } theorPutPx = FinMath.GetOptionPrice(futPx, candidPair.Strike, dT, iv, 0, false); theorCallPx = FinMath.GetOptionPrice(futPx, candidPair.Strike, dT, iv, 0, true); } #region Сдаём риск (один квант объёма за раз) double putPx, callPx; { DateTime putTime, callTime; double putAskQty, callAskQty; putPx = IvSmile.GetOptPrice(m_context, futPx, candidPair.Put, OptionPxMode.Ask, 0, 0, out putAskQty, out putTime); callPx = IvSmile.GetOptPrice(m_context, futPx, candidPair.Call, OptionPxMode.Ask, 0, 0, out callAskQty, out callTime); } //if (m_optionType == StrikeType.Put) //{ // #region В путах // if (putOpenQty < 0) // Это означает, что в страйке есть короткие путы // { // ISecurity sec = candidPair.Put.Security; // double px = SafeMinPrice(theorPutPx + m_exitShift * sec.Tick, putPx, sec); // double iv = FinMath.GetOptionSigma(futPx, candidPair.Strike, dT, px, 0, false); // double qty = Math.Min(Math.Abs(m_fixedQty), Math.Abs(putOpenQty)); // string sigName = String.Format("Risk:{0}; MaxRisk:{1}; Px:{2}; Qty:{3}; IV:{4:P2}; dT:{5}", risk, maxRisk, px, qty, iv, dT); // posMan.BuyAtPrice(m_context, sec, qty, px, "Close BUY", sigName); // m_context.Log(sigName, MessageType.Info, false); // // Выход из foreach (IOptionStrikePair candidPair in orderedPairs) // break; // } // #endregion В путах //} //else if (m_optionType == StrikeType.Call) //{ // #region В колах // if (callOpenQty < 0) // Это означает, что в страйке есть короткие колы // { // ISecurity sec = candidPair.Call.Security; // double px = SafeMinPrice(theorCallPx + m_exitShift * sec.Tick, callPx, sec); // double iv = FinMath.GetOptionSigma(futPx, candidPair.Strike, dT, px, 0, true); // double qty = Math.Min(Math.Abs(m_fixedQty), Math.Abs(callOpenQty)); // string sigName = String.Format("Risk:{0}; MaxRisk:{1}; Px:{2}; Qty:{3}; IV:{4:P2}; dT:{5}", risk, maxRisk, px, qty, iv, dT); // posMan.BuyAtPrice(m_context, sec, qty, px, "Close BUY", sigName); // m_context.Log(sigName, MessageType.Info, false); // // Выход из foreach (IOptionStrikePair candidPair in orderedPairs) // break; // } // #endregion В колах //} //else { #region В оба вида опционов сразу встаю int executedQty = 0; if (putOpenQty < 0) // Это означает, что в страйке есть короткие путы { ISecurity sec = candidPair.Put.Security; double px = SellOptions.SafeMinPrice(theorPutPx + m_exitShift * sec.Tick, putPx, sec); double iv = FinMath.GetOptionSigma(futPx, candidPair.Strike, dT, px, 0, false); double qty = Math.Min(Math.Abs(m_fixedQty), Math.Abs(putOpenQty)); string sigName = String.Format("Risk:{0}; MaxRisk:{1}; Px:{2}; Qty:{3}; IV:{4:P2}; dT:{5}", risk, maxRisk, px, qty, iv, dT); posMan.BuyAtPrice(m_context, sec, qty, px, "Close BUY", sigName); m_context.Log(sigName, MessageType.Info, false); executedQty += (int)qty; } if ((callOpenQty < 0) && // Это означает, что в страйке есть короткие колы (Math.Abs(executedQty) < Math.Abs(m_fixedQty))) { ISecurity sec = candidPair.Call.Security; double px = SellOptions.SafeMinPrice(theorCallPx + m_exitShift * sec.Tick, callPx, sec); double iv = FinMath.GetOptionSigma(futPx, candidPair.Strike, dT, px, 0, true); double qty = Math.Min(Math.Abs(m_fixedQty) - Math.Abs(executedQty), Math.Abs(callOpenQty)); string sigName = String.Format("Risk:{0}; MaxRisk:{1}; Px:{2}; Qty:{3}; IV:{4:P2}; dT:{5}", risk, maxRisk, px, qty, iv, dT); posMan.BuyAtPrice(m_context, sec, qty, px, "Close BUY", sigName); m_context.Log(sigName, MessageType.Info, false); executedQty += (int)qty; } if (executedQty > 0) { // Выход из foreach (IOptionStrikePair candidPair in orderedPairs) break; } #endregion В оба вида опционов сразу встаю } #endregion Сдаём риск (один квант объёма за раз) } } else { msg = String.Format("[{0}.{1}] risk:{2}; maxRisk:{3}; orderedPairs.Length:{4}", Context.Runtime.TradeName, GetType().Name, risk, maxRisk, orderedPairs.Length); m_context.Log(msg, MessageType.Warning, true); } } return(Constants.NaN); }
/// <summary> /// Основной метод, который выполняет всю торговую логику по котированию и следит за риском /// </summary> protected double Process(double entryPermission, double centralStrike, double risk, double maxRisk, InteractiveSeries smile, IOptionSeries optSer, double callRisk, double putRisk, int barNum) { int barsCount = m_context.BarsCount; if (!m_context.IsLastBarUsed) { barsCount--; } if ((barNum < barsCount - 1) || (optSer == null) || (smile == null)) { return(Constants.NaN); } { IOptionStrikePair testPair; if (!optSer.TryGetStrikePair(centralStrike, out testPair)) { return(Constants.NaN); } } // Если риск не был измерен говорить вообще не о чем! if (Double.IsNaN(risk)) { return(Constants.NaN); } // Если риск разумен и условие входа НЕ ВЫПОЛНЕНО -- отдыхаем. // А вот если риск превышен -- тогда по идее надо бы его подсократить! if ((risk < maxRisk) && (entryPermission <= 0)) { return(Constants.NaN); } PositionsManager posMan = PositionsManager.GetManager(m_context); if (posMan.BlockTrading) { //string msg = String.Format("Trading is blocked. Please, change 'Block Trading' parameter."); //m_context.Log(msg, MessageType.Info, true); return(Constants.NaN); } SmileInfo sInfo = smile.GetTag <SmileInfo>(); if ((sInfo == null) || (sInfo.ContinuousFunction == null)) { return(Constants.NaN); } double dT = sInfo.dT; double futPx = sInfo.F; // Набираем риск if (risk < maxRisk) { // Надо взять пары, начиная от центральной и далее по возрастанию расстояния с учетом шага страйков IOptionStrikePair[] orderedPairs; if (m_strikeStep < Double.Epsilon) { // Просто сортируем страйки по расстоянию до Центра orderedPairs = (from p in optSer.GetStrikePairs() orderby Math.Abs(p.Strike - centralStrike) ascending select p).ToArray(); } else { // Сортировка по возрастанию до Центра + обязательно условие кратности параметру m_strikeStep orderedPairs = (from p in optSer.GetStrikePairs() let dK = Math.Abs(p.Strike - centralStrike) let dKStep = (int)Math.Round(dK / m_strikeStep) where DoubleUtil.AreClose(dK, m_strikeStep * dKStep) // проверяем, что расстояние от страйка до центра кратно m_strikeStep orderby Math.Abs(p.Strike - centralStrike) ascending select p).ToArray(); } Contract.Assert(m_strikeAmount >= 0, "Как получился отрицательный m_strikeAmount??? m_strikeAmount: " + m_strikeAmount); // Защита от дурака? Или не надо париться? m_strikeAmount = Math.Max(0, m_strikeAmount); // Котируем либо 1 центральный страйк либо центр + четное число соседей int maxStrikeCount = 2 * m_strikeAmount + 1; int strikeCounter = 0; // Сколько лотов уже выставлено в рынок double pendingQty = 0; if (orderedPairs.Length > 0) { foreach (IOptionStrikePair candidPair in orderedPairs) { if (strikeCounter >= maxStrikeCount) { // Все, выходим. Цикл завершен. break; } double ivAtm; double strike = candidPair.Strike; if ((!sInfo.ContinuousFunction.TryGetValue(strike, out ivAtm)) || Double.IsNaN(ivAtm) || (ivAtm < Double.Epsilon)) { string msg = String.Format("[{0}.{1}] Unable to get IV at strike {2}. ivAtm:{3}", Context.Runtime.TradeName, GetType().Name, candidPair.Strike, ivAtm); m_context.Log(msg, MessageType.Error, true); return(Constants.NaN); } double theorPutPx = FinMath.GetOptionPrice(futPx, strike, dT, ivAtm, 0, false); double theorCallPx = FinMath.GetOptionPrice(futPx, strike, dT, ivAtm, 0, true); #region Набираем риск double putPx, callPx; { double putQty, callQty; DateTime putTime, callTime; putPx = IvSmile.GetOptPrice(m_context, futPx, candidPair.Put, OptionPxMode.Bid, 0, 0, out putQty, out putTime); callPx = IvSmile.GetOptPrice(m_context, futPx, candidPair.Call, OptionPxMode.Bid, 0, 0, out callQty, out callTime); } if ((m_optionType == StrikeType.Put) || (m_optionType == StrikeType.Any) && (strike <= futPx)) { #region В путах ISecurity sec = candidPair.Put.Security; double qty = Math.Abs(m_fixedQty); // TODO: Немного грубая оценка, но пока сойдет qty = BuyOptions.GetSafeQty(risk + pendingQty * putRisk, maxRisk, qty, putRisk); if (qty > 0) { double px = SellOptions.SafeMaxPrice(theorPutPx + m_entryShift * sec.Tick, putPx, sec); double iv = FinMath.GetOptionSigma(futPx, candidPair.Strike, dT, px, 0, false); string sigName = String.Format("Risk:{0}; MaxRisk:{1}; Px:{2}; Qty:{3}; IV:{4:P2}; dT:{5}", risk, maxRisk, px, qty, iv, dT); posMan.SellAtPrice(m_context, sec, qty, px, "Open SELL", sigName); pendingQty += qty; m_context.Log(sigName, MessageType.Info, false); } #endregion В путах } else if ((m_optionType == StrikeType.Call) || (m_optionType == StrikeType.Any) && (futPx <= strike)) { #region В колах ISecurity sec = candidPair.Call.Security; double qty = Math.Abs(m_fixedQty); // TODO: Немного грубая оценка, но пока сойдет qty = BuyOptions.GetSafeQty(risk + pendingQty * callRisk, maxRisk, qty, callRisk); if (qty > 0) { double px = SellOptions.SafeMaxPrice(theorCallPx + m_entryShift * sec.Tick, callPx, sec); double iv = FinMath.GetOptionSigma(futPx, candidPair.Strike, dT, px, 0, true); string sigName = String.Format("Risk:{0}; MaxRisk:{1}; Px:{2}; Qty:{3}; IV:{4:P2}; dT:{5}", risk, maxRisk, px, qty, iv, dT); posMan.SellAtPrice(m_context, sec, qty, px, "Open SELL", sigName); pendingQty += qty; m_context.Log(sigName, MessageType.Info, false); } #endregion В колах } else { // Вроде бы, сюда не должны приходить никогда?.. #region В оба вида опционов сразу встаю int executedQty = 0; { ISecurity sec = candidPair.Put.Security; double px = SellOptions.SafeMaxPrice(theorPutPx + m_entryShift * sec.Tick, putPx, sec); double iv = FinMath.GetOptionSigma(futPx, candidPair.Strike, dT, px, 0, false); double qty = Math.Max(1, Math.Abs(m_fixedQty / 2)); // TODO: Немного грубая оценка, но пока сойдет qty = BuyOptions.GetSafeQty(risk + pendingQty * putRisk, maxRisk, qty, putRisk); if (qty > 0) { string sigName = String.Format("Risk:{0}; MaxRisk:{1}; Px:{2}; Qty:{3}; IV:{4:P2}; dT:{5}", risk, maxRisk, px, qty, iv, dT); posMan.SellAtPrice(m_context, sec, qty, px, "Open SELL", sigName); pendingQty += qty; m_context.Log(sigName, MessageType.Info, false); executedQty += (int)qty; } } if (Math.Abs(executedQty) < Math.Abs(m_fixedQty)) { ISecurity sec = candidPair.Call.Security; double px = SellOptions.SafeMaxPrice(theorCallPx + m_entryShift * sec.Tick, callPx, sec); double iv = FinMath.GetOptionSigma(futPx, candidPair.Strike, dT, px, 0, true); double qty = Math.Abs(m_fixedQty) - Math.Abs(executedQty); // TODO: Немного грубая оценка, но пока сойдет // Делаю оценку изменения текущего риска, если нам зафилят заявку в путах //qty = BuyOptions.GetSafeQty(risk + Math.Abs(executedQty) * putRisk, maxRisk, qty, callRisk); // Причем здесь уже не нужно отдельно учитывать executedQty, потому что он входит в pendingQty qty = BuyOptions.GetSafeQty(risk + pendingQty * callRisk, maxRisk, qty, callRisk); if (qty > 0) { string sigName = String.Format("Risk:{0}; MaxRisk:{1}; Px:{2}; Qty:{3}; IV:{4:P2}; dT:{5}", risk, maxRisk, px, qty, iv, dT); posMan.SellAtPrice(m_context, sec, qty, px, "Open SELL", sigName); pendingQty += qty; m_context.Log(sigName, MessageType.Info, false); //executedQty += (int)qty; } } #endregion В оба вида опционов сразу встаю } #endregion Набираем риск strikeCounter++; } // End foreach (IOptionStrikePair candidPair in orderedPairs) } else { string msg = String.Format("[{0}] Strike not found. risk:{1}; maxRisk:{2}; orderedPairs.Length:{3}", Context.Runtime.TradeName, risk, maxRisk, orderedPairs.Length); m_context.Log(msg, MessageType.Warning, true); } } else if (risk > maxRisk) { string msg; //string msg = String.Format("[DEBUG:{0}] risk:{1}; maxRisk:{2}", Context.Runtime.TradeName, risk, maxRisk); //m_context.Log(msg, MessageType.Info, true); // Надо взять пары, начиная от центральной и далее по возрастанию расстояния var orderedPairs = (from p in optSer.GetStrikePairs() orderby Math.Abs(p.Strike - centralStrike) ascending select p).ToArray(); if (orderedPairs.Length > 0) { foreach (IOptionStrikePair candidPair in orderedPairs) { #region Проверяю, что в страйке есть КОРОТКАЯ позиция double putOpenQty = posMan.GetTotalQty(candidPair.Put.Security, barNum); double callOpenQty = posMan.GetTotalQty(candidPair.Call.Security, barNum); if ((putOpenQty >= 0) && (callOpenQty >= 0)) { continue; } if (DoubleUtil.IsZero(putOpenQty) && DoubleUtil.IsZero(callOpenQty)) { continue; } { msg = String.Format("[{0}:{1}] Strike:{2}; putOpenQty:{3}; callOpenQty:{4}", Context.Runtime.TradeName, GetType().Name, candidPair.Strike, putOpenQty, callOpenQty); m_context.Log(msg, MessageType.Info, true); } #endregion Проверяю, что в страйке есть КОРОТКАЯ позиция double theorPutPx, theorCallPx; { double iv; if ((!sInfo.ContinuousFunction.TryGetValue(candidPair.Strike, out iv)) || Double.IsNaN(iv) || (iv < Double.Epsilon)) { msg = String.Format("[{0}.{1}] Unable to get IV at strike {2}. IV:{3}", Context.Runtime.TradeName, GetType().Name, candidPair.Strike, iv); m_context.Log(msg, MessageType.Error, true); continue; } theorPutPx = FinMath.GetOptionPrice(futPx, candidPair.Strike, dT, iv, 0, false); theorCallPx = FinMath.GetOptionPrice(futPx, candidPair.Strike, dT, iv, 0, true); } #region Сдаём риск (один квант объёма за раз) double putPx, callPx; { DateTime putTime, callTime; double putAskQty, callAskQty; putPx = IvSmile.GetOptPrice(m_context, futPx, candidPair.Put, OptionPxMode.Ask, 0, 0, out putAskQty, out putTime); callPx = IvSmile.GetOptPrice(m_context, futPx, candidPair.Call, OptionPxMode.Ask, 0, 0, out callAskQty, out callTime); } if (m_optionType == StrikeType.Put) { #region В путах if (putOpenQty < 0) // Это означает, что в страйке есть короткие путы { ISecurity sec = candidPair.Put.Security; double px = SellOptions.SafeMinPrice(theorPutPx + m_exitShift * sec.Tick, putPx, sec); double iv = FinMath.GetOptionSigma(futPx, candidPair.Strike, dT, px, 0, false); double qty = Math.Min(Math.Abs(m_fixedQty), Math.Abs(putOpenQty)); string sigName = String.Format("Risk:{0}; MaxRisk:{1}; Px:{2}; Qty:{3}; IV:{4:P2}; dT:{5}", risk, maxRisk, px, qty, iv, dT); posMan.BuyAtPrice(m_context, sec, qty, px, "Close BUY", sigName); m_context.Log(sigName, MessageType.Info, false); // Выход из foreach (IOptionStrikePair candidPair in orderedPairs) break; } #endregion В путах } else if (m_optionType == StrikeType.Call) { #region В колах if (callOpenQty < 0) // Это означает, что в страйке есть короткие колы { ISecurity sec = candidPair.Call.Security; double px = SellOptions.SafeMinPrice(theorCallPx + m_exitShift * sec.Tick, callPx, sec); double iv = FinMath.GetOptionSigma(futPx, candidPair.Strike, dT, px, 0, true); double qty = Math.Min(Math.Abs(m_fixedQty), Math.Abs(callOpenQty)); string sigName = String.Format("Risk:{0}; MaxRisk:{1}; Px:{2}; Qty:{3}; IV:{4:P2}; dT:{5}", risk, maxRisk, px, qty, iv, dT); posMan.BuyAtPrice(m_context, sec, qty, px, "Close BUY", sigName); m_context.Log(sigName, MessageType.Info, false); // Выход из foreach (IOptionStrikePair candidPair in orderedPairs) break; } #endregion В колах } else { #region В оба вида опционов сразу встаю int executedQty = 0; if (putOpenQty < 0) // Это означает, что в страйке есть короткие путы { ISecurity sec = candidPair.Put.Security; double px = SellOptions.SafeMinPrice(theorPutPx + m_exitShift * sec.Tick, putPx, sec); double iv = FinMath.GetOptionSigma(futPx, candidPair.Strike, dT, px, 0, false); double qty = Math.Min(Math.Abs(m_fixedQty), Math.Abs(putOpenQty)); string sigName = String.Format("Risk:{0}; MaxRisk:{1}; Px:{2}; Qty:{3}; IV:{4:P2}; dT:{5}", risk, maxRisk, px, qty, iv, dT); posMan.BuyAtPrice(m_context, sec, qty, px, "Close BUY", sigName); m_context.Log(sigName, MessageType.Info, false); executedQty += (int)qty; } if ((callOpenQty < 0) && // Это означает, что в страйке есть короткие колы (Math.Abs(executedQty) < Math.Abs(m_fixedQty))) { ISecurity sec = candidPair.Call.Security; double px = SellOptions.SafeMinPrice(theorCallPx + m_exitShift * sec.Tick, callPx, sec); double iv = FinMath.GetOptionSigma(futPx, candidPair.Strike, dT, px, 0, true); double qty = Math.Min(Math.Abs(m_fixedQty) - Math.Abs(executedQty), Math.Abs(callOpenQty)); string sigName = String.Format("Risk:{0}; MaxRisk:{1}; Px:{2}; Qty:{3}; IV:{4:P2}; dT:{5}", risk, maxRisk, px, qty, iv, dT); posMan.BuyAtPrice(m_context, sec, qty, px, "Close BUY", sigName); m_context.Log(sigName, MessageType.Info, false); executedQty += (int)qty; } if (executedQty > 0) { // Выход из foreach (IOptionStrikePair candidPair in orderedPairs) break; } #endregion В оба вида опционов сразу встаю } #endregion Сдаём риск (один квант объёма за раз) } } else { msg = String.Format("[{0}.{1}] risk:{2}; maxRisk:{3}; orderedPairs.Length:{4}", Context.Runtime.TradeName, GetType().Name, risk, maxRisk, orderedPairs.Length); m_context.Log(msg, MessageType.Warning, true); } } return(Constants.NaN); }
/// <summary> /// Получить финансовые параметры опционной позиции (один опцион) /// </summary> /// <param name="positions">список закрытых и открытых позиций</param> /// <param name="curBar">номер рабочего бара</param> /// <param name="f">текущая цена БА</param> /// <param name="k">страйк</param> /// <param name="dT">время до экспирации</param> /// <param name="sigma">волатильность</param> /// <param name="r">процентная ставка</param> /// <param name="isCall">put-false; call-true</param> /// <param name="cash">денежные затраты на формирование позы (могут быть отрицательными)</param> /// <param name="pnl">текущая цена позиции</param> public static void GetOptPnl(IList <IPosition> positions, int curBar, double f, double k, double dT, double sigma, double r, bool isCall, double btcUsdInd, out CashPnlUsd cashPnlUsd, out CashPnlBtc cashPnlBtc) { if (positions.Count == 0) { cashPnlUsd = new CashPnlUsd(); cashPnlBtc = new CashPnlBtc(); return; } { var msg = $"Как получился отрицательный курс BTC/USD? btcUsdInd:{btcUsdInd}"; Contract.Assert(DoubleUtil.IsPositive(btcUsdInd), msg); if (!DoubleUtil.IsPositive(btcUsdInd)) { throw new ArgumentException(msg, nameof(btcUsdInd)); } } double pnlBtc = 0; double pnlUsd = 0; double cashBtc = 0; double cashUsd = 0; foreach (IPosition pos in positions) { int sign = pos.IsLong ? 1 : -1; double qty = Math.Abs(pos.Shares); // Знак "минус" стоит в честь того, что при покупке инструмента наличные средства уменьшаются double locCashBtcEntry = sign * pos.GetBalancePrice(curBar) * qty; cashBtc -= locCashBtcEntry; cashUsd -= locCashBtcEntry * btcUsdInd; double optPxUsd = FinMath.GetOptionPrice(f, k, dT, sigma, r, isCall); double locPnlUsdEntry = sign * optPxUsd * qty; pnlUsd += locPnlUsdEntry; pnlBtc += locPnlUsdEntry / btcUsdInd; // Учет комиссии (комиссия в битках по идее) cashBtc -= pos.EntryCommission; cashUsd -= pos.EntryCommission * btcUsdInd; if (!pos.IsActiveForBar(curBar)) { // Знак "ПЛЮС" стоит в честь того, что при ЗАКРЫТИИ ЛОНГА наличные средства УВЕЛИЧИВАЮТСЯ double locCashBtcExit = sign * pos.ExitPrice * qty; cashBtc += locCashBtcExit; cashUsd += locCashBtcExit * btcUsdInd; double locPnlUsdExit = sign * optPxUsd * qty; pnlUsd -= locPnlUsdExit; pnlBtc -= locPnlUsdExit / btcUsdInd; // Учет комиссии (комиссия в битках по идее) cashBtc -= pos.ExitCommission; cashUsd -= pos.ExitCommission * btcUsdInd; } } // End foreach (IPosition pos in positions) cashPnlUsd = new CashPnlUsd(cashUsd, pnlUsd); cashPnlBtc = new CashPnlBtc(cashBtc, pnlBtc); }
private void ProcessScheduledHorizontalMovement_() { var heldXAxis = this.HeldXAxis; var isRunning = this.IsRunning; var isTryingToRun = FloatMath.Abs(heldXAxis) > .5f; var heldXAxisSign = FinMath.Sign(heldXAxis); float?targetXVelocity = null; float xAcceleration = 0; if (this.StateMachine.CanMoveUprightOnGround) { var maxGroundXVelocity = isRunning ? PlayerConstants.UPRIGHT_MAX_FAST_XSPD : PlayerConstants.UPRIGHT_MAX_SLOW_XSPD; var groundAcceleration = isTryingToRun ? PlayerConstants.GROUND_UPRIGHT_FAST_XACC : PlayerConstants.GROUND_UPRIGHT_SLOW_XACC; var reactionFraction = heldXAxisSign == -FinMath.Sign(this.Rigidbody.XVelocity) ? PlayerConstants.GROUND_REACTION_FRAC : 1; targetXVelocity = maxGroundXVelocity * heldXAxis; xAcceleration = groundAcceleration * reactionFraction * heldXAxisSign; // If holding a direction on the ground, we're either turning, running, or walking. if (heldXAxisSign != 0) { this.StateMachine.State = reactionFraction != 1 ? PlayerState.TURNING : isTryingToRun ? PlayerState.RUNNING : PlayerState.WALKING; } // If not holding a direction on the ground but velocity is not zero, we're stopping. else if (FinMath.Abs(this.Rigidbody.XVelocity) > .001) { this.StateMachine.State = PlayerState.STOPPING; } } else if (this.StateMachine.CanMoveDuckedOnGround) { var maxGroundXVelocity = isRunning ? PlayerConstants.DUCKED_MAX_FAST_XSPD : PlayerConstants.DUCKED_MAX_SLOW_XSPD; var groundAcceleration = isTryingToRun ? PlayerConstants.GROUND_DUCKED_FAST_XACC : PlayerConstants.GROUND_DUCKED_SLOW_XACC; targetXVelocity = maxGroundXVelocity * heldXAxis; xAcceleration = groundAcceleration * heldXAxisSign; // If holding a direction on the ground, we're either turning, running, or walking. if (heldXAxisSign != 0) { this.StateMachine.State = PlayerState.DUCKWALKING; } } else if (this.StateMachine.CanMoveInAir) { var maxAirXVelocity = isRunning ? PlayerConstants.UPRIGHT_MAX_FAST_XSPD : PlayerConstants.UPRIGHT_MAX_SLOW_XSPD; var airAcceleration = isTryingToRun ? PlayerConstants.AIR_FAST_XACC : PlayerConstants.AIR_SLOW_XACC; targetXVelocity = maxAirXVelocity * heldXAxis; xAcceleration = airAcceleration * heldXAxisSign; } this.Rigidbody.TargetXVelocity = targetXVelocity; this.Rigidbody.XAcceleration = xAcceleration; }