public static void GetBasePnl(IList <IPosition> positions, int barNum, double f, double futNominal, out CashPnlUsd cashPnlUsd, out CashPnlBtc cashPnlBtc) { double pnlBtc = 0; double pnlUsd = 0; double cashBtc = 0; double cashUsd = 0; int len = positions.Count; for (int j = 0; j < len; j++) { IPosition pos = positions[j]; // Пока что State лучше не трогать //if (pos.PositionState == PositionState.HaveError) { int sign = pos.IsLong ? 1 : -1; double qty = Math.Abs(pos.Shares); double begPx = pos.GetBalancePrice(barNum); // PROD-6085 - PnL(в битках) = (Позиция в лотах) * (номинал 10 долларов) * (1/BegPx - 1/EndPx) // На обычном рынке знак "минус" стоит в честь того, что при покупке инструмента наличные средства уменьшаются, // а прибыль вычисляется пропорционально величине (EndPx - BegPx). // Но с биткойном все наоборот: из НАЧАЛЬНОЙ цены вычисляют конечную (1/BegPx - 1/EndPx) // {Это нужно, чтобы на росте фьючерса получать прибыль} // Поэтому требуется еще одно инвертирование знака. cashBtc += sign * qty * futNominal / begPx; pnlBtc -= sign * qty * futNominal / f; //// TODO: Учет комиссии //cashBtc -= pos.EntryCommission; if (!pos.IsActiveForBar(barNum)) { // Знак "ПЛЮС" стоит в честь того, что при ЗАКРЫТИИ ЛОНГА наличные средства УВЕЛИЧИВАЮТСЯ // Но еще помним про инвертирование на Дерибите cashBtc -= sign * qty * futNominal / pos.ExitPrice; pnlBtc += sign * qty * futNominal / f; //// TODO: Учет комиссии //cashBtc -= pos.ExitCommission; } } } // End for (int j = 0; j < len; j++) // Также насколько понял описание на сайте Дерибит, упрощается конвертация в доллары // https://www.deribit.com/main#/pages/docs/futures (section 4: Example) // При этом для перевода в баксы используется КОНЕЧНЫЙ КУРС EndPx! cashUsd = cashBtc * f; pnlUsd = pnlBtc * f; cashPnlUsd = new CashPnlUsd(cashUsd, pnlUsd); cashPnlBtc = new CashPnlBtc(cashBtc, pnlBtc); }
/// <summary> /// Получить финансовые параметры опционной позиции (один опцион) /// </summary> /// <param name="positions">список закрытых и открытых позиций</param> /// <param name="curBar">номер рабочего бара</param> /// <param name="f">текущая цена БА</param> /// <param name="k">страйк</param> /// <param name="dT">время до экспирации</param> /// <param name="sigma">волатильность</param> /// <param name="r">процентная ставка</param> /// <param name="isCall">put-false; call-true</param> /// <param name="cash">денежные затраты на формирование позы (могут быть отрицательными)</param> /// <param name="pnl">текущая цена позиции</param> public static void GetOptPnl(IList <IPosition> positions, int curBar, double f, double k, double dT, double sigma, double r, bool isCall, double btcUsdInd, out CashPnlUsd cashPnlUsd, out CashPnlBtc cashPnlBtc) { if (positions.Count == 0) { cashPnlUsd = new CashPnlUsd(); cashPnlBtc = new CashPnlBtc(); return; } { var msg = $"Как получился отрицательный курс BTC/USD? btcUsdInd:{btcUsdInd}"; Contract.Assert(DoubleUtil.IsPositive(btcUsdInd), msg); if (!DoubleUtil.IsPositive(btcUsdInd)) { throw new ArgumentException(msg, nameof(btcUsdInd)); } } double pnlBtc = 0; double pnlUsd = 0; double cashBtc = 0; double cashUsd = 0; foreach (IPosition pos in positions) { int sign = pos.IsLong ? 1 : -1; double qty = Math.Abs(pos.Shares); // Знак "минус" стоит в честь того, что при покупке инструмента наличные средства уменьшаются double locCashBtcEntry = sign * pos.GetBalancePrice(curBar) * qty; cashBtc -= locCashBtcEntry; cashUsd -= locCashBtcEntry * btcUsdInd; double optPxUsd = FinMath.GetOptionPrice(f, k, dT, sigma, r, isCall); double locPnlUsdEntry = sign * optPxUsd * qty; pnlUsd += locPnlUsdEntry; pnlBtc += locPnlUsdEntry / btcUsdInd; // Учет комиссии (комиссия в битках по идее) cashBtc -= pos.EntryCommission; cashUsd -= pos.EntryCommission * btcUsdInd; if (!pos.IsActiveForBar(curBar)) { // Знак "ПЛЮС" стоит в честь того, что при ЗАКРЫТИИ ЛОНГА наличные средства УВЕЛИЧИВАЮТСЯ double locCashBtcExit = sign * pos.ExitPrice * qty; cashBtc += locCashBtcExit; cashUsd += locCashBtcExit * btcUsdInd; double locPnlUsdExit = sign * optPxUsd * qty; pnlUsd -= locPnlUsdExit; pnlBtc -= locPnlUsdExit / btcUsdInd; // Учет комиссии (комиссия в битках по идее) cashBtc -= pos.ExitCommission; cashUsd -= pos.ExitCommission * btcUsdInd; } } // End foreach (IPosition pos in positions) cashPnlUsd = new CashPnlUsd(cashUsd, pnlUsd); cashPnlBtc = new CashPnlBtc(cashBtc, pnlBtc); }
/// <summary> /// Получить финансовые параметры опционной позиции (колы и путы на одном страйке суммарно) /// </summary> /// <param name="smileInfo">улыбка</param> /// <param name="strike">страйк</param> /// <param name="putBarCount">количество баров для пута</param> /// <param name="callBarCount">количество баров для кола</param> /// <param name="putPositions">позиции пута</param> /// <param name="callPositions">позиции кола</param> /// <param name="f">текущая цена БА</param> /// <param name="dT">время до экспирации</param> /// <param name="cash">денежные затраты на формирование позы (могут быть отрицательными)</param> /// <param name="pnl">текущая цена позиции</param> /// <returns>true, если всё посчитано без ошибок</returns> public static bool TryGetPairPnl(SmileInfo smileInfo, double strike, int putBarCount, int callBarCount, IList <IPosition> putPositions, IList <IPosition> callPositions, double f, double dT, double btcUsdInd, out CashPnlUsd cashPnlUsd, out CashPnlBtc cashPnlBtc) { if ((putPositions.Count <= 0) && (callPositions.Count <= 0)) { cashPnlUsd = new CashPnlUsd(); cashPnlBtc = new CashPnlBtc(); return(true); } double?sigma = null; if (smileInfo != null) { double tmp; if (smileInfo.ContinuousFunction.TryGetValue(strike, out tmp)) { sigma = tmp; } } if ((sigma == null) || (!DoubleUtil.IsPositive(sigma.Value))) { cashPnlUsd = new CashPnlUsd(); cashPnlBtc = new CashPnlBtc(); return(false); } double pnlBtc = 0; double pnlUsd = 0; double cashBtc = 0; double cashUsd = 0; if (putPositions.Count > 0) { CashPnlUsd putUsd; CashPnlBtc putBtc; GetOptPnl(putPositions, putBarCount - 1, f, strike, dT, sigma.Value, 0.0, false, btcUsdInd, out putUsd, out putBtc); pnlUsd += putUsd.PnlUsd; cashUsd += putUsd.CashUsd; pnlBtc += putBtc.PnlBtc; cashBtc += putBtc.CashBtc; } if (callPositions.Count > 0) { CashPnlUsd callUsd; CashPnlBtc callBtc; GetOptPnl(callPositions, callBarCount - 1, f, strike, dT, sigma.Value, 0.0, true, btcUsdInd, out callUsd, out callBtc); pnlUsd += callUsd.PnlUsd; cashUsd += callUsd.CashUsd; pnlBtc += callBtc.PnlBtc; cashBtc += callBtc.CashBtc; } cashPnlUsd = new CashPnlUsd(cashUsd, pnlUsd); cashPnlBtc = new CashPnlBtc(cashBtc, pnlBtc); return(true); }