1191 lines
54 KiB
C#
1191 lines
54 KiB
C#
using BaseOUDAL;
|
|
using Microsoft.Extensions.Caching.Memory;
|
|
using Qdp.Pricing.Base.Utilities;
|
|
using YLErp.Abstract;
|
|
using YLErp.BLL;
|
|
using YLErp.BLL.Eod;
|
|
using YLErp.Configuration;
|
|
using YLErp.DBModels.Enums;
|
|
using YLErp.Models;
|
|
using YLErp.Modules.CalculationModule;
|
|
using YLErp.Modules.CalculationModule.Abstract;
|
|
using YLErp.Modules.TradeRiskCalcModule.Abstract;
|
|
using YLErp.Modules.SwapModule;
|
|
using YLErp.Modules.TradeRiskCalcModule.TaskRunner;
|
|
using YLErp.Modules.VolatilityModule;
|
|
using YLErp.QdpModule;
|
|
|
|
namespace YLErp.Modules.TradeRiskCalcModule
|
|
{
|
|
/// <summary>
|
|
/// 交易风险计算业务服务
|
|
/// </summary>
|
|
public class TradeRiskCalcService
|
|
{
|
|
static readonly IYcLogger _logger;
|
|
static readonly MemoryCache _cache;
|
|
static TradeRiskCalcService()
|
|
{
|
|
_cache = new MemoryCache(new MemoryCacheOptions());
|
|
_logger = LogFactory.GetLogger(nameof(TradeRiskCalcService));
|
|
}
|
|
|
|
readonly ITradeRiskCalcContext _context;
|
|
readonly IOptionCalcDataProvider _dataProvider;
|
|
HashSet<string> _underlyingCodeFilterSet;
|
|
readonly string _skipTradeTypes;
|
|
|
|
public TradeRiskCalcService(ITradeRiskCalcContext context, string skipTradeTypes)
|
|
{
|
|
_context = context ?? throw new ArgumentNullException(nameof(context));
|
|
_dataProvider = CalcCheckHelper.CheckOptionCalcDataProvider(context.OptionCalcDataProvider);
|
|
_skipTradeTypes = skipTradeTypes;
|
|
}
|
|
|
|
public TradeRiskCalcService SetUnderlyingFilter(params string[] underlyingCodes)
|
|
{
|
|
if (underlyingCodes != null && underlyingCodes.Any(n => !string.IsNullOrEmpty(n)))
|
|
{
|
|
var linq = underlyingCodes.Where(n => !string.IsNullOrEmpty(n));
|
|
_underlyingCodeFilterSet = new HashSet<string>(linq, StringComparer.OrdinalIgnoreCase);
|
|
}
|
|
return this;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 计算
|
|
/// </summary>
|
|
public IEnumerable<TradingRiskParameter> Calculate(ITradeDataSource tradeDataSource, IDataSource<ManualRisk> manualRiskProvider)
|
|
{
|
|
if (tradeDataSource == null)
|
|
{
|
|
throw new ArgumentNullException(nameof(tradeDataSource));
|
|
}
|
|
|
|
IOtcTradeValueCalcContext optionContext = null;
|
|
|
|
try
|
|
{
|
|
_logger?.Debug($"[{_context.VolType}]开始计算当前风险>>>>>>>>>");
|
|
|
|
//构建计算上下文
|
|
var sysRiskFreeRate = valuedateBLL.RiskFreeRate * 0.01;
|
|
optionContext = _context.CreateOptionValueCalcContext(sysRiskFreeRate);
|
|
|
|
//场外交易处理
|
|
var tradeRiskList = CalcOtcRisks(tradeDataSource, optionContext, manualRiskProvider);
|
|
var swapTradeRiskList = CalcSwapRisks(tradeDataSource, optionContext);
|
|
tradeRiskList.AddRange(swapTradeRiskList);
|
|
var hedgeOptionContext = PS.Config.Is润和 ? new InnerOptionValueCalcContext(DateTime.Today, optionContext) : optionContext;
|
|
//对冲交易处理
|
|
var hedgingRisk = CalcHedgeTradeRisks(tradeDataSource, hedgeOptionContext);
|
|
|
|
tradeRiskList.AddRange(hedgingRisk);
|
|
|
|
//计算每笔期权的盈亏
|
|
TradeRiskHelper.CalculateOptionTradePnlWithHedge(tradeRiskList);
|
|
|
|
_logger?.Debug($"[{_context.VolType}]当前风险计算完毕<<<<<<<<<<<");
|
|
|
|
return tradeRiskList;
|
|
}
|
|
finally
|
|
{
|
|
optionContext?.Dispose();
|
|
}
|
|
}
|
|
|
|
#region----场外交易处理----
|
|
|
|
//场外交易风险和盈亏计算(除收益互换)
|
|
private List<TradingRiskParameter> CalcOtcRisks(ITradeDataSource tradeDataSource
|
|
, IOtcTradeValueCalcContext optionContext, IDataSource<ManualRisk> manualRiskProvider)
|
|
{
|
|
var volType = _context.VolType;
|
|
|
|
//准备计算数据
|
|
var tradeRiskList = new List<TradingRiskParameter>();
|
|
var otcTrades = tradeDataSource.GetOtcTrades() ?? Enumerable.Empty<trade>();
|
|
otcTrades = otcTrades.Where(x => x.TradeType != "收益互换");
|
|
if (_underlyingCodeFilterSet != null)
|
|
{
|
|
otcTrades = otcTrades.Where(n => _underlyingCodeFilterSet.Contains(n.UnderlyingCode)).ToArray();
|
|
}
|
|
|
|
_logger?.Debug($"[{volType}]参与计算的场外交易数量:" + otcTrades.Count());
|
|
|
|
var set = new HashSet<int>(otcTrades.Select(t => t.id));
|
|
var tradeCashLookup = _context.GetTrade_Cashes(set).ToLookup(n => n.TradeId);
|
|
var ydContexts = GetYdOptionCalculateContexts(_context, optionContext);
|
|
|
|
DateTime valueDate, preValueDate;
|
|
|
|
//执行风险计算
|
|
foreach (var trad in otcTrades)
|
|
{
|
|
SetTradeLots(trad);
|
|
|
|
valueDate = _context.ValueDate;
|
|
preValueDate = _context.PreValueDate;
|
|
|
|
void getTradeCashList(out IEnumerable<trade_cash> tradeCashList, out IEnumerable<trade_cash> tdTradeCashUnwindList)
|
|
{
|
|
tradeCashList = tradeCashLookup.FirstOrDefault(a => a.Key == trad.id)?.AsEnumerable() ?? Enumerable.Empty<trade_cash>();
|
|
|
|
tdTradeCashUnwindList = tradeCashList.Where(tc => tc.ValidState != ConsGlobal.InValid && !tc.IsDeleted
|
|
&& ClientCashInCashOut.PROFIT_ACTION.Contains(tc.Action) && tc.ValueDate <= valueDate && tc.ValueDate > preValueDate);
|
|
}
|
|
|
|
if (!string.IsNullOrEmpty(_skipTradeTypes) && _skipTradeTypes.Contains(trad.TradeType))
|
|
{
|
|
var resultRisk = UseManualRisk(trad, volType, manualRiskProvider, (calcRisk, lastPv) =>
|
|
{
|
|
getTradeCashList(out var tradeCashList, out var tdTradeCashUnwindList);
|
|
|
|
UpdateOTCPnl(trad, calcRisk, lastPv, tradeCashList: tradeCashList, tdTradeCashUnwindList: tdTradeCashUnwindList);
|
|
});
|
|
|
|
if (resultRisk != null)
|
|
{
|
|
tradeRiskList.Add(resultRisk);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
TradingRiskParameter firstRisk = null;
|
|
IOtcTradeValueCalcContext ydContext = null;
|
|
|
|
try
|
|
{
|
|
var tdOtionContext = optionContext;
|
|
ydContext = ydContexts[(int)trad.SettlementType];
|
|
|
|
if (PS.Config.Is润和)
|
|
{
|
|
tdOtionContext = new InnerOptionValueCalcContext(DateTime.Today, optionContext);
|
|
ydContext = new InnerOptionValueCalcContext(DateTime.Today.AddDays(-1), optionContext);
|
|
}
|
|
|
|
ydContext.UserGroup = tdOtionContext.UserGroup = UserBLL.GetUserGroup(trad.TraderId);
|
|
if (volType == "交易曲面分红率0")
|
|
{
|
|
trad.DividendRate = 0;
|
|
}
|
|
var result = TradeRiskCalcUtil.CalcTradeRisk(trad, tdOtionContext, out var underlyings);
|
|
|
|
if (result == null)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
result.TradeId = trad.id;
|
|
|
|
if (underlyings == null)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
var index = 0;
|
|
foreach (var underlying in underlyings)
|
|
{
|
|
var trp = TransformOneUnderlying(underlying, trad, new List<TradeValueResult> { result }, index++);
|
|
tradeRiskList.Add(trp);
|
|
if (index == 1)
|
|
{
|
|
firstRisk = trp;
|
|
}
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
var key = "today_otcTrade_calc_" + trad.id;
|
|
if (_cache.Get(key) == null)
|
|
{
|
|
_cache.Set(key, this, DateTimeOffset.Now.AddMinutes(10));
|
|
_logger?.Error(ex, $"[{volType}-{trad.id}-{trad.TradeNumber}]当前风险计算出错");
|
|
}
|
|
}
|
|
|
|
if (firstRisk != null)
|
|
{
|
|
double? lastPv = null;
|
|
|
|
getTradeCashList(out var tradeCashList, out var tdTradeCashUnwindList);
|
|
|
|
if (firstRisk.TradeType == "远期")
|
|
{
|
|
firstRisk.TotalPnl = 0;
|
|
}
|
|
|
|
if (trad.TradeDate < valueDate)
|
|
{
|
|
var yp = _context.YdEodPositionDataProvider.GetOtcTradePositionData(trad.id, _context.VolType);
|
|
|
|
if (yp != null)
|
|
{
|
|
lastPv = yp.Position.Pv;
|
|
|
|
if (firstRisk.TradeType == "远期")
|
|
{
|
|
firstRisk.TotalPnl = yp.Position.TotalPnL;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
lastPv = CalcYdOtcValue(trad, ydContext, tdTradeCashUnwindList, volType, out var spotPrice, out var vol);
|
|
|
|
if (firstRisk.Debug != null)
|
|
{
|
|
firstRisk.Debug.Add(spotPrice);
|
|
firstRisk.Debug.Add(vol);
|
|
}
|
|
}
|
|
}
|
|
|
|
UpdateOTCPnl(trad, firstRisk, lastPv, tradeCashList: tradeCashList, tdTradeCashUnwindList: tdTradeCashUnwindList);
|
|
}
|
|
}
|
|
}
|
|
|
|
AppManager.SetSysInfo(string.Concat("实时风险-", volType, "-计算结果"), tradeRiskList.Count + "条");
|
|
|
|
_logger?.Debug($"[{volType}]场外交易风险计算完毕");
|
|
|
|
return tradeRiskList;
|
|
}
|
|
|
|
//场外收益互换交易风险和盈亏计算
|
|
private List<TradingRiskParameter> CalcSwapRisks(ITradeDataSource tradeDataSource, IOtcTradeValueCalcContext optionContext)
|
|
{
|
|
var volType = _context.VolType;
|
|
|
|
//厦门象屿
|
|
DateTime? nextDateXmxy = null;
|
|
if (PS.Config.Company == Configuration.CompanyEnum.厦门象屿)
|
|
{
|
|
var date = SpecialModule.XiaMenXiangYuHelper.GetRefernceValueDate(_context.ValueDate);
|
|
if (date > _context.ValueDate)
|
|
{
|
|
nextDateXmxy = date;
|
|
}
|
|
}
|
|
|
|
//准备计算数据
|
|
var tradeRiskList = new List<TradingRiskParameter>();
|
|
var otcTrades = tradeDataSource.GetOtcTrades() ?? Enumerable.Empty<trade>();
|
|
otcTrades = otcTrades.Where(x => x.TradeType == "收益互换");
|
|
var tradeIds = otcTrades.Select(t => t.id).ToList();
|
|
var swapTradeService = new SwapTradeService(OptUserInfo.SystemUser);
|
|
var swapEodPositionService = new SwapEodPositionService(OptUserInfo.SystemUser);
|
|
var tradePositions = swapTradeService.GetSwapPositions(tradeIds, _underlyingCodeFilterSet);
|
|
var tradePositionSwapIds = tradePositions.Select(s => s.SwapTradeId).Distinct().ToList();
|
|
otcTrades = otcTrades.Where(x => tradePositionSwapIds.Contains(x.id));
|
|
_logger?.Debug($"[{volType}]参与计算的场外互换交易数量:" + otcTrades.Count());
|
|
var ydContexts = GetYdOptionCalculateContexts(_context, optionContext);
|
|
DateTime valueDate = _context.ValueDate;
|
|
DateTime preValueDate = _context.PreValueDate;
|
|
var swapEodPositions = swapEodPositionService.GetEodPositions(tradePositionSwapIds, valueDate, preValueDate);
|
|
var eodSwaps = swapEodPositionService.GetEodSwaps(tradePositionSwapIds, valueDate, preValueDate);
|
|
//风险计算
|
|
foreach (var trad in otcTrades)
|
|
{
|
|
var lastSwapEodPositions = swapEodPositions.Where(x => x.SwapTradeId == trad.id).OrderByDescending(o => o.ValueDate).ToList();//可能没有
|
|
var lastPositionIds = lastSwapEodPositions.Select(x => x.PositionId).Distinct();
|
|
var eodSwap = eodSwaps.Where(x => x.SwapTradeId == trad.id).OrderByDescending(o => o.ValueDate).FirstOrDefault();
|
|
var positions = tradePositions.Where(x=>x.SwapTradeId==trad.id).ToList();
|
|
if (lastPositionIds.Any())
|
|
{
|
|
foreach (var item in lastPositionIds)
|
|
{
|
|
var todayPosition = lastSwapEodPositions.FirstOrDefault(x => x.PositionId == item && x.ValueDate == valueDate);
|
|
var lastPosition = lastSwapEodPositions.FirstOrDefault(x => x.PositionId == item && x.ValueDate == preValueDate);
|
|
var currentPosition = todayPosition == null ? lastPosition : todayPosition;
|
|
var underlying = DataCacheProvider.GetUnderlyingDataSource().GetData(currentPosition.UnderlyingCode);
|
|
var position = tradePositions.FirstOrDefault(f => f.id == item);
|
|
var result = new TradeValueResult();
|
|
var spotPrice = swapEodPositionService.UnderlyingCodePrice(currentPosition.UnderlyingCode, valueDate,out decimal vobp);
|
|
int shortRatio = currentPosition.PositionType == (int)PositionTypeFlag.Long ? 1 : -1;
|
|
int directionRatio = currentPosition.PosiDirection == (int)SwapDirectionEnum.收取 ? 1 : -1;
|
|
result.Pv =Convert.ToDouble( currentPosition.UnderlyingMarketValue);
|
|
result.Delta = directionRatio * shortRatio * Convert.ToDouble(currentPosition.PosiQuantity) * Convert.ToDouble(currentPosition.CountRatio);
|
|
result.DeltaCash = result.Delta * Convert.ToDouble(spotPrice);
|
|
result.Gamma = 0;
|
|
result.GammaCash = 0;
|
|
result.Vega = 0;
|
|
result.VegaCash = 0;
|
|
result.TradingDayTheta = 0;
|
|
result.CalendarDayTheta = 0;
|
|
result.Rho = 0;
|
|
var resultRisk = SwapTransformOneUnderlying(underlying, trad, new List<TradeValueResult> { result }, currentPosition, position);
|
|
if (resultRisk != null)
|
|
{
|
|
tradeRiskList.Add(resultRisk);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
AppManager.SetSysInfo(string.Concat("实时风险-", volType, "-计算结果"), tradeRiskList.Count + "条");
|
|
|
|
_logger?.Debug($"[{volType}]互换交易风险计算完毕");
|
|
return tradeRiskList;
|
|
}
|
|
//使用自定义交易风险,注意:应该屏蔽远期
|
|
private TradingRiskParameter UseManualRisk(trade trad, string volType, IDataSource<ManualRisk> manualRiskProvider,
|
|
Action<TradingRiskParameter, double?> UpdateOTCPnl)
|
|
{
|
|
var usePrePve = false;
|
|
var result = new TradeValueResult();
|
|
|
|
if (!ConsTrade.TradeCompleteStatus.Contains(trad.TradeStatus))
|
|
{
|
|
var risk = manualRiskProvider?.GetData(trad.id);
|
|
|
|
if (risk == null)
|
|
{
|
|
var yp = _context.YdEodPositionDataProvider.GetOtcTradePositionData(trad.id, _context.VolType);
|
|
if (yp != null)
|
|
{
|
|
var rate = trad.Notional / yp.Position.Amount;
|
|
result.Pv = yp.Position.Pv * rate;
|
|
var ypRisk = yp.Risk;
|
|
if (ypRisk != null)
|
|
{
|
|
result.Delta = ypRisk.Delta * rate;
|
|
result.DeltaCash = ypRisk.DeltaCash * rate;
|
|
result.Gamma = ypRisk.Gamma * rate;
|
|
result.GammaCash = ypRisk.GammaCash * rate;
|
|
result.Vega = ypRisk.Vega * rate;
|
|
result.VegaCash = ypRisk.VegaCash * rate;
|
|
result.TradingDayTheta = ypRisk.Theta * rate;
|
|
result.CalendarDayTheta = ypRisk.Theta * rate;
|
|
result.Rho = ypRisk.Rho * rate;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
result.Pv = risk.Pv ?? 0;
|
|
result.Delta = risk.Delta ?? 0;
|
|
result.Gamma = risk.Gamma ?? 0;
|
|
result.Vega = risk.Vega ?? 0;
|
|
result.TradingDayTheta = risk.Theta ?? 0;
|
|
result.CalendarDayTheta = risk.Theta ?? 0;
|
|
result.Rho = risk.Rho ?? 0;
|
|
|
|
if (_dataProvider.UnderlyingPriceProvider.TryGetPrice(trad.UnderlyingCode, out var price))
|
|
{
|
|
result.DeltaCash = result.Delta * price;
|
|
result.GammaCash = result.Gamma * Math.Pow(price, 2) / 100;
|
|
result.VegaCash = result.Vega * price;
|
|
}
|
|
|
|
usePrePve = true;
|
|
}
|
|
}
|
|
|
|
var underlying = DataCacheProvider.GetUnderlyingDataSource().GetData(trad.UnderlyingCode);
|
|
var resultRisk = TransformOneUnderlying(underlying, trad, new List<TradeValueResult> { result }, 0);
|
|
|
|
if (usePrePve)
|
|
{
|
|
double? lastPv = null;
|
|
var yp = _context.YdEodPositionDataProvider.GetOtcTradePositionData(trad.id, _context.VolType);
|
|
if (yp != null)
|
|
{
|
|
lastPv = yp.Position.Pv;
|
|
}
|
|
UpdateOTCPnl(resultRisk, lastPv);
|
|
}
|
|
|
|
return resultRisk;
|
|
}
|
|
|
|
//计算场外交易上个交易日市值
|
|
private double? CalcYdOtcValue(trade trad, IOtcTradeValueCalcContext ydContext, IEnumerable<trade_cash> tdTradeCashUnwindList, string volType
|
|
, out double? spotPrice, out double? vol)
|
|
{
|
|
vol = null;
|
|
spotPrice = null;
|
|
|
|
try
|
|
{
|
|
if (ConsTrade.TradeCompleteStatus.Contains(trad.TradeStatus))
|
|
{
|
|
trad.Notional = 0;
|
|
}
|
|
|
|
//计算昨日pv时需要还原昨日的持仓数量
|
|
trad.Notional += tdTradeCashUnwindList.Sum(tc => tc.UnwindNotional ?? 0);
|
|
|
|
var result = TradeRiskCalcUtil.CalcTradeRisk(trad, ydContext, out var underlyings);
|
|
|
|
if (result != null)
|
|
{
|
|
vol = result.Vol;
|
|
spotPrice = result.SpotPrice;
|
|
}
|
|
|
|
return result?.Pv;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
var key = "yeday_otcTrade_calc_" + trad.id;
|
|
if (_cache.Get(key) == null)
|
|
{
|
|
_cache.Set(key, this, DateTimeOffset.Now.AddMinutes(10));
|
|
_logger?.Error(ex, $"[{volType}-{trad.id}-{trad.TradeNumber}]昨日场外期权交易PV计算出错");
|
|
}
|
|
return null;
|
|
}
|
|
}
|
|
|
|
//获取三种结算方式的昨日交易估值上下文(因为昨日交易有日终价格)
|
|
private static YdOptionCalculateContext[] GetYdOptionCalculateContexts(ITradeRiskCalcContext riskContext, IOtcTradeValueCalcContext optionContext)
|
|
{
|
|
var ydContexts = new YdOptionCalculateContext[3];
|
|
|
|
ydContexts[(int)SettlementTypeEnum.ClosePrice] =
|
|
new YdOptionCalculateContext(riskContext.PreValueDate, optionContext, riskContext.YdEodPriceProvider, riskContext.YdTradeHisDataProvider, SettlementTypeEnum.ClosePrice)
|
|
{
|
|
CalcScenario = Enums.CalcScenarioEnum.RealtimeRisk
|
|
};
|
|
|
|
ydContexts[(int)SettlementTypeEnum.SettlePrice] =
|
|
new YdOptionCalculateContext(riskContext.PreValueDate, optionContext, riskContext.YdEodPriceProvider, riskContext.YdTradeHisDataProvider, SettlementTypeEnum.SettlePrice)
|
|
{
|
|
CalcScenario = Enums.CalcScenarioEnum.RealtimeRisk
|
|
};
|
|
|
|
ydContexts[(int)SettlementTypeEnum.ReferencePrice] =
|
|
new YdOptionCalculateContext(riskContext.PreValueDate, optionContext, riskContext.YdEodPriceProvider, riskContext.YdTradeHisDataProvider, SettlementTypeEnum.ReferencePrice)
|
|
{
|
|
CalcScenario = Enums.CalcScenarioEnum.RealtimeRisk
|
|
};
|
|
|
|
return ydContexts;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 设置交易记录手数值
|
|
/// </summary>
|
|
private void SetTradeLots(trade trad)
|
|
{
|
|
//成交手数
|
|
trad.Lots = TradeLotsCalc.GetLots(trad.UnderlyingCode, trad.OriginalNotional ?? 0);
|
|
trad.LotsNewInfo = TradeLotsCalc.GetLots(trad.UnderlyingCode, trad.Notional);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region----对冲交易处理----
|
|
|
|
/// <summary>
|
|
/// 更新对冲交易盈亏数据
|
|
/// </summary>
|
|
private IEnumerable<TradingRiskParameter> CalcHedgeTradeRisks(ITradeDataSource tradeDataSource, IOtcTradeValueCalcContext optionCalcContext)
|
|
{
|
|
_logger?.Debug($"[{_context.VolType}]对冲交易PNL处理开始");
|
|
|
|
//获取所有未结算过的对冲交易
|
|
var HedgeTradeList = tradeDataSource.GetExchangeTrades() ?? Enumerable.Empty<ExchangeTrade>();
|
|
|
|
if (_underlyingCodeFilterSet != null)
|
|
{
|
|
HedgeTradeList = HedgeTradeList.Where(n => _underlyingCodeFilterSet.Contains(n.UnderlyingCode)).ToArray();
|
|
}
|
|
|
|
//股票 商品期货临时tradeId
|
|
var tempTradeId = -1;
|
|
var togetherUnOptionList = new List<TradingRiskParameter>();
|
|
|
|
var hedgePnlContext = _context.CreateHedgePnlCalcContext(optionCalcContext);
|
|
|
|
//昨日持仓信息
|
|
var predicat = PredicateBuilder.True<EodTradePosition>();
|
|
if (_underlyingCodeFilterSet != null)
|
|
{
|
|
predicat = predicat.And(t => _underlyingCodeFilterSet.Contains(t.UnderlyingCode));
|
|
}
|
|
var lastEodPositions = _context.YdEodPositionDataProvider
|
|
.GetExchangeTradePositionList(_context.VolType).Where(predicat.Compile()).ToArray();
|
|
|
|
//计算昨日持仓+新增加交易合计的PV
|
|
var hedgePnlList = new HedgePnlCalc(hedgePnlContext).Calculate(HedgeTradeList, lastEodPositions);
|
|
|
|
//根据是否展示累积盈亏过滤前一交易日已清仓的持仓为0的持仓信息
|
|
if (!PS.Config.ShowAccruedTotalPnL)
|
|
{
|
|
hedgePnlList = hedgePnlList.Where(t => t.HasNewTrade || Math.Abs(t.Notional) != 0).ToList();
|
|
}
|
|
|
|
var calcDeltaT1 = TradeRiskHelper.IsCalcDeltaT1();
|
|
|
|
foreach (var pnl in hedgePnlList)
|
|
{
|
|
var um = _dataProvider.UnderlyingDataProvider.GetUnderlying(pnl.UnderlyingCode);
|
|
if (um == null)
|
|
{
|
|
continue;
|
|
}
|
|
um.Price = pnl.SettlePrice;
|
|
var variety = _dataProvider.UnderlyingDataProvider.GetVariety(um.UnderlyingTypeId);
|
|
if (variety == null)
|
|
{
|
|
optionCalcContext.ErrorHandler?.AddError($"{um.UnderlyingCode}没有找到品种信息");
|
|
variety = new Variety();
|
|
}
|
|
|
|
var TradeUnitValue = variety?.TradeUnitValue;
|
|
var tempRisk = new TradingRiskParameter
|
|
{
|
|
VarietyCode = variety.VarietyCode?.ToUpperInvariant(),
|
|
IsOption = false,
|
|
TradeId = tempTradeId--,
|
|
UnderlyingId = um.id,
|
|
UnderlyingCode = pnl.UnderlyingCode,
|
|
UnderlyingName = um?.UnderlyingName ?? string.Empty,
|
|
SpotPriceChangePercent = um.GetPriceChangePercent(),
|
|
TradeType = pnl.TradeType,
|
|
BookId = pnl.BookId,
|
|
BuySell = pnl.BuySell,
|
|
Notional = pnl.Notional,
|
|
Pv = pnl.Pv,
|
|
Tv = pnl.TimeValue,
|
|
TradePrice = pnl.Cost,
|
|
OriginalPrincipalSum = 0,
|
|
DailyPnl = pnl.DailyPnL,
|
|
TotalPnl = pnl.TotalPnl,
|
|
ExercisePnl = pnl.TotalPnl,
|
|
SpotPrice = um.Price,
|
|
TradeStatus = ConsTrade.确认成交,
|
|
InstrumentType = um.UnderlyingInstrumentType,
|
|
IsSynthetic = um.IsSynthetic(),
|
|
ExchangeOptionCode = pnl.ExchangeOptionCode,
|
|
PositionType = pnl.PositionType,
|
|
CallPut = pnl.CallPut,
|
|
Strike = pnl.Strike,
|
|
//风险参数
|
|
Delta = pnl.Delta,
|
|
SA_Delta = pnl.SA_Delta,
|
|
DeltaCash = pnl.DeltaCash,
|
|
Gamma = pnl.Gamma,
|
|
Vega = pnl.Vega,
|
|
GammaCash = pnl.GammaCash,
|
|
DeltaInLots = pnl.DeltaInLots,
|
|
GammaInLots = pnl.GammaInLots,
|
|
Theta = pnl.Theta,
|
|
Rho = pnl.Rho,
|
|
DdeltaDt = pnl.DdeltaDt,
|
|
DdeltaDvol = pnl.DdeltaDvol,
|
|
DvegaDt = pnl.DvegaDt,
|
|
DvegaDvol = pnl.DvegaDvol,
|
|
Lots = pnl.Lots,
|
|
LotsNewInfo = pnl.Lots,
|
|
Cost = pnl.Cost,
|
|
AccruedTotalPnl = pnl.TotalPnl,
|
|
ExerciseDate = pnl.ExerciseDate,
|
|
Vol = pnl.Vol,
|
|
TradeSavedVol = pnl.Vol,
|
|
ExOptionPrice = pnl.ExOptionPrice
|
|
};
|
|
|
|
if (calcDeltaT1)
|
|
{
|
|
tempRisk.DeltaT1 = pnl.TradeType == "场内期权" ? pnl.DeltaT1 : pnl.Delta;
|
|
}
|
|
|
|
if (PS.Config.Is渤海)
|
|
{
|
|
tempRisk.ExercisePnl = 0.0;
|
|
}
|
|
|
|
tempRisk.OriginalNotionalV = tempRisk.OriginalNotional = tempRisk.Notional;
|
|
tempRisk.TradeFlag = (int)EnumTradeTypeUtil.GetTradeFlag(tempRisk.TradeType);
|
|
|
|
if (Math.Abs(tempRisk.Notional) > 1e-7)
|
|
{
|
|
tempRisk.Premium = tempRisk.TradePrice / tempRisk.Notional;
|
|
tempRisk.StockEqvNotional = Math.Abs(tempRisk.Pv);
|
|
tempRisk.BuySell = tempRisk.Notional > 0 ? "买入" : "卖出";
|
|
}
|
|
else
|
|
{
|
|
tempRisk.BuySell = string.Empty;
|
|
}
|
|
|
|
if (PS.Config.Company == CompanyEnum.红塔众鑫)
|
|
{
|
|
var day = QdpCalendarHelper.GetNonHolidayDaysBetween(valuedateBLL.ValueDate
|
|
,pnl.ExerciseDate.GetValueOrDefault()); //剩余天数
|
|
tempRisk.ThetaNet = Math.Round(pnl.Theta,2) * day;
|
|
|
|
}
|
|
togetherUnOptionList.Add(tempRisk);
|
|
}
|
|
|
|
if (togetherUnOptionList.Any())
|
|
{
|
|
//更新行权盈亏
|
|
foreach (var t in togetherUnOptionList)
|
|
{
|
|
if (t.TradeType == "场内期权")
|
|
{
|
|
var pnl = (t.CallPut == "Call" ? 1 : -1) * ((t.SpotPrice ?? 0.0) - (t.Strike ?? 0.0)) * Math.Abs(t.Notional);
|
|
pnl = pnl < 0 ? 0 : pnl * EodOperationBase.GetSign(t.BuySell);
|
|
t.ExercisePnl = pnl - t.Cost;
|
|
}
|
|
else
|
|
{
|
|
t.ExercisePnl = t.Pv - t.Cost;
|
|
}
|
|
}
|
|
}
|
|
|
|
_logger?.Debug($"[{_context.VolType}]对冲交易PNL计算完毕");
|
|
|
|
return togetherUnOptionList;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region-----tradingRiskParameter-----
|
|
|
|
//衍生品交易计算结果转换(未考虑多标的期权--待明确业务后重写)
|
|
private TradingRiskParameter TransformOneUnderlying(underlying_manager um, trade tradeObj, List<TradeValueResult> optionValueResult, int index)
|
|
{
|
|
var contractSize = 0d;
|
|
var variety = um == null ? null : _dataProvider.UnderlyingDataProvider.GetVariety(um.UnderlyingCode, out contractSize) ?? new Variety();
|
|
var sign = EodOperationBase.GetSign(tradeObj.BuySell);
|
|
|
|
var trp = new TradingRiskParameter
|
|
{
|
|
IsOption = true,
|
|
UnderlyingCode = um?.UnderlyingCode,
|
|
UnderlyingId = um?.id,
|
|
UnderlyingName = um?.UnderlyingName,
|
|
InstrumentType = um?.UnderlyingInstrumentType,
|
|
SpotPrice = um?.Price,
|
|
SpotPriceChangePercent = um?.GetPriceChangePercent(),
|
|
|
|
IsGroup = tradeObj.IsGroup,
|
|
TradeId = tradeObj.id,
|
|
TradeNumber = tradeObj.TradeNumber,
|
|
TradeType = tradeObj.TradeType,
|
|
StructureType = tradeObj.StructureType,
|
|
TradeStatus = tradeObj.TradeStatus,
|
|
TradeDate = tradeObj.TradeDate,
|
|
TradePrice = (tradeObj.TradePrice ?? 0) * sign,
|
|
OriginalPrincipalSum = (tradeObj.OriginalPrincipalSum ?? 0) * sign,
|
|
ClientId = tradeObj.ClientId,
|
|
ClientName = tradeObj.ClientName,
|
|
CallPut = tradeObj.CallPut,
|
|
BuySell = tradeObj.BuySell,
|
|
BookId = tradeObj.AssetId,
|
|
ExerciseMode = tradeObj.ExerciseMode,
|
|
ExerciseDate = tradeObj.ExerciseDate,
|
|
InitSpotPrice = tradeObj.SpotPrice,
|
|
Notional = tradeObj.Notional,
|
|
OriginalNotional = tradeObj.OriginalNotional ?? tradeObj.Notional,
|
|
StockEqvNotional = tradeObj.StockEqvNotional,
|
|
IsMoneynessOption = tradeObj.IsMoneynessOption,
|
|
IsPremiumRate = tradeObj.IsUsePremiumRate ?? false,
|
|
DividendRate = tradeObj.DividendRate,
|
|
NoRiskRate = tradeObj.NoRiskRate,
|
|
OpenVol = tradeObj.TradeOpenVolatility ?? 0,
|
|
Premium = tradeObj.IsUsePremiumRate == true ? (tradeObj.PremiumRate ?? 0) : (tradeObj.TradeSinglePrice ?? 0),
|
|
Lots = tradeObj.Lots,
|
|
LotsNewInfo = tradeObj.LotsNewInfo,
|
|
TradeSavedVol = tradeObj.TradeSavedVol,
|
|
|
|
Pv = optionValueResult.Sum(t => t.Pv),
|
|
NPv = optionValueResult.Sum(t => t.NPv),
|
|
Tv = optionValueResult.Sum(t => t.TimeValue),
|
|
Delta = optionValueResult.Sum(t => t.GetDelta(index)),
|
|
DeltaT1 = optionValueResult.FirstOrDefault()?.DeltaT1,
|
|
SA_Delta = optionValueResult.Sum(t => t.SA_Delta),
|
|
Gamma = optionValueResult.Sum(t => t.GetGamma(index)),
|
|
Vega = optionValueResult.Sum(t => t.GetVega(index)),
|
|
DeltaCash = optionValueResult.Sum(t => t.GetDeltaCash(index)),
|
|
GammaCash = optionValueResult.Sum(t => t.GetGammaCash(index)),
|
|
|
|
TradeFlag = (int)EnumTradeTypeUtil.GetTradeFlag(tradeObj.TradeType, tradeObj.Comments),
|
|
|
|
AccruedTotalPnl = 0,
|
|
CompanyObj = null,
|
|
Cost = 0,
|
|
DailyPnl = 0,
|
|
GammaInLots = 0,
|
|
Strike = tradeObj.Strike,
|
|
Debug = new List<object> { optionValueResult[0].SpotPrice, optionValueResult[0].Vol }
|
|
};
|
|
|
|
if (tradeObj.TradeType == "远期")
|
|
{
|
|
trp.TradePrice = tradeObj.TradePrice ?? 0;
|
|
}
|
|
|
|
var list = DbContextFactory.GetYLDbContext().dividendrate_record.Where(x => x.TradeType.Contains(tradeObj.TradeType) && valuedateBLL.ValueDate >= x.ValueDate && (x.OptionType == tradeObj.OptionType || x.OptionType == "全部")).ToList();
|
|
var record = list.Where(x => x.UnderlyingCode.Split(',').Any(code => code == tradeObj.UnderlyingCode)).OrderByDescending(x => x.OptDate).OrderByDescending(x => x.ValueDate);
|
|
if (record.Any())
|
|
{
|
|
trp.DividendRate = record.FirstOrDefault()?.DividendRate;
|
|
}
|
|
|
|
if (!trp.DividendRate.HasValue)
|
|
{
|
|
trp.DividendRate = trp.NoRiskRate;
|
|
}
|
|
|
|
//成交份额(countRatio传入1算出来的是成交份额)
|
|
trp.OriginalNotionalV = TradeCalcHelper.GetTradeAmountV(tradeObj, tradeObj.OriginalNotional ?? tradeObj.Notional, 1);
|
|
|
|
//组合标的设置DeltaInLots为0
|
|
if (um != null)
|
|
{
|
|
if (um.IsSynthetic())
|
|
{
|
|
trp.IsSynthetic = true;
|
|
trp.VarietyCode = "组合标的";
|
|
trp.DeltaInLots = optionValueResult.Sum(t => t.GetDelta(index));
|
|
if (um.ContractSize > 0)
|
|
{
|
|
trp.DeltaInLots /= um.ContractSize;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
trp.VarietyCode = variety?.VarietyCode?.ToUpperInvariant();
|
|
trp.DeltaInLots = optionValueResult.Sum(t => t.GetDelta(index)) / contractSize;
|
|
}
|
|
}
|
|
else if (string.IsNullOrEmpty(trp.UnderlyingCode))
|
|
{
|
|
if (trp.TradeType == ConsGlobal.TradeType.CashFlow)
|
|
{
|
|
trp.VarietyCode = trp.UnderlyingCode = "现金流";
|
|
}
|
|
else
|
|
{
|
|
trp.VarietyCode = trp.UnderlyingCode = "未知";
|
|
}
|
|
}
|
|
|
|
trp.GammaInLots = optionValueResult.Sum(t => t.GetGamma(index)) / contractSize;
|
|
trp.Theta = optionValueResult.Sum(t => t.Theta);
|
|
if (PS.Config.Company == CompanyEnum.红塔众鑫)
|
|
{
|
|
trp.ThetaNet = optionValueResult.Sum(t =>
|
|
{
|
|
var day = QdpCalendarHelper.GetNonHolidayDaysBetween(
|
|
valuedateBLL.ValueDate,tradeObj.ExerciseDate.GetValueOrDefault()); //剩余天数
|
|
return Math.Round(t.Theta, 2) * day;
|
|
});
|
|
}
|
|
trp.Rho = optionValueResult.Sum(t => t.Rho) * 100;
|
|
trp.DdeltaDt = optionValueResult.Sum(t => t.DDeltaDt);
|
|
trp.DdeltaDvol = optionValueResult.Sum(t => t.DDeltaDVol);
|
|
trp.DvegaDt = optionValueResult.Sum(t => t.DVegaDt);
|
|
trp.DvegaDvol = optionValueResult.Sum(t => t.DVegaDVol);
|
|
|
|
if (tradeObj.TradeType == "场内期权")
|
|
{
|
|
trp.PositionType = sign > 0 ? "long" : "short";
|
|
trp.ExchangeOptionCode = tradeObj.ExchangeOptionCode;
|
|
}
|
|
else if (tradeObj.TradeType == "结构化交易")
|
|
{
|
|
trp.TradeType = tradeObj.StructureType ?? string.Empty;
|
|
trp.StructureType = "结构化交易";
|
|
}
|
|
|
|
//目前optionValueResult中只有一个标的的Vol值
|
|
//对于彩虹期权、价差期权等多标的的期权交易,并不能获取各标的的Vol,因此暂时使用第一个Vol
|
|
//TODO: 当optionValueResult支持多个Vol之后,需要做相应修改
|
|
|
|
trp.Vol = optionValueResult[0].Vol;
|
|
trp.PricingT = optionValueResult[0].PricingT;
|
|
|
|
//场外期权行权盈亏
|
|
if (trp.TradeFlag > (int)EnumTradeType.ExchangeOption)
|
|
{
|
|
trp.ExercisePnl = GetExercisePnl(tradeObj, um?.Price ?? 0.0, out var actualStrike);
|
|
trp.Strike = actualStrike;
|
|
}
|
|
|
|
if (PS.Config.Is润和)
|
|
{
|
|
var unVol = VolatilityHelper.GetVol(DateTime.Today, "交易", tradeObj.UnderlyingCode, DataCacheProvider.GetAssetUnitDataSource().GetData(tradeObj.AssetId)?.UserGroup);
|
|
trp.TradeSavedVol = VolatilityHelper.GetInterpolatedVol(
|
|
volConstructionType: PS.Config.ErpElement.SkewMapVolConstruction ? VolConstructionType.SkewMap : VolConstructionType.Normal,
|
|
volSurface: unVol,
|
|
valueDate: DateTime.Today,
|
|
underlyingCode: tradeObj.UnderlyingCode,
|
|
exerciseDate: tradeObj.ExerciseDate.Value,
|
|
strike: tradeObj.Strike ?? 0,
|
|
isBuy: tradeObj.BuySell == "买入",
|
|
isCall: tradeObj.CallPut == "Call",
|
|
spotPrice: tradeObj.SpotPrice ?? 0,
|
|
isMoneynessOption: tradeObj.IsMoneynessOption == "是");
|
|
}
|
|
|
|
trp.QuoteUnit = variety?.QuoteUnit;
|
|
|
|
|
|
|
|
if (optionValueResult != null && optionValueResult.Count() > 0 && optionValueResult[0] != null && optionValueResult[0].IsKnockOut)
|
|
{
|
|
trp.IsKnockOut = true;
|
|
trp.KnockOutPayoff = optionValueResult.Sum(t => t.KnockOutPayoff);
|
|
}
|
|
return trp;
|
|
}
|
|
|
|
//衍生品互换交易计算结果转换(未考虑多标的期权--待明确业务后重写)
|
|
private TradingRiskParameter SwapTransformOneUnderlying(underlying_manager um, trade tradeObj, List<TradeValueResult> optionValueResult, eod_swap_position eodPosition, swap_position position)
|
|
{
|
|
var contractSize = 0d;
|
|
var variety = um == null ? null : _dataProvider.UnderlyingDataProvider.GetVariety(um.UnderlyingCode, out contractSize) ?? new Variety();
|
|
int shortRatio = position.PositionType == (int)PositionTypeFlag.Long ? 1 : -1;
|
|
int directionRatio = position.PosiDirection == (int)SwapDirectionEnum.收取 ? 1 : -1;
|
|
var trp = new TradingRiskParameter
|
|
{
|
|
IsOption = true,
|
|
UnderlyingCode = um?.UnderlyingCode,
|
|
UnderlyingId = um?.id,
|
|
UnderlyingName = um?.UnderlyingName,
|
|
InstrumentType = um?.UnderlyingInstrumentType,
|
|
SpotPrice = um?.Price,
|
|
SpotPriceChangePercent = um?.GetPriceChangePercent(),
|
|
|
|
IsGroup = tradeObj.IsGroup,
|
|
TradeId = tradeObj.id,
|
|
TradeNumber = tradeObj.TradeNumber,
|
|
TradeType = tradeObj.TradeType,
|
|
StructureType = tradeObj.StructureType,
|
|
TradeStatus = tradeObj.TradeStatus,
|
|
TradeDate = tradeObj.TradeDate,
|
|
OriginalPrincipalSum = 0,
|
|
ClientId = tradeObj.ClientId,
|
|
ClientName = tradeObj.ClientName,
|
|
CallPut = shortRatio == 1 ? "Call" : "Put",
|
|
BuySell = directionRatio == 1 ? "卖出" : "买入",
|
|
BookId = tradeObj.AssetId,
|
|
ExerciseMode = tradeObj.ExerciseMode,
|
|
ExerciseDate = tradeObj.ExerciseDate,
|
|
InitSpotPrice = Convert.ToDouble(position.PosiNetPrice),
|
|
OriginalNotional = Convert.ToDouble(position.PosiQuantity * position.CountRatio),
|
|
IsMoneynessOption = tradeObj.IsMoneynessOption,
|
|
IsPremiumRate = false,
|
|
DividendRate = 0,
|
|
NoRiskRate = 0,
|
|
OpenVol = 0,
|
|
Premium = Convert.ToDouble(position.PosiNetPrice),
|
|
Lots = Convert.ToDouble(position.PosiQuantity * position.CountRatio / position.ContractSize),
|
|
TradeSavedVol = tradeObj.TradeSavedVol,
|
|
|
|
Pv = optionValueResult.Sum(t => t.Pv),
|
|
Tv = optionValueResult.Sum(t => t.TimeValue),
|
|
Delta = optionValueResult.Sum(t => t.GetDelta(0)),
|
|
DeltaT1 = optionValueResult.FirstOrDefault()?.DeltaT1,
|
|
SA_Delta = optionValueResult.Sum(t => t.SA_Delta),
|
|
Gamma = optionValueResult.Sum(t => t.GetGamma(0)),
|
|
Vega = optionValueResult.Sum(t => t.GetVega(0)),
|
|
DeltaCash = optionValueResult.Sum(t => t.GetDeltaCash(0)),
|
|
GammaCash = optionValueResult.Sum(t => t.GetGammaCash(0)),
|
|
|
|
TradeFlag = (int)EnumTradeType.SwapOption,
|
|
|
|
CompanyObj = null,
|
|
Cost = 0,
|
|
DailyPnl = 0,
|
|
GammaInLots = 0,
|
|
Strike = tradeObj.Strike,
|
|
Debug = new List<object> { optionValueResult[0].SpotPrice, optionValueResult[0].Vol }
|
|
};
|
|
if (eodPosition!=null)
|
|
{
|
|
trp.AccruedTotalPnl = Convert.ToDouble(eodPosition.RealizedMtmPnL);
|
|
trp.LotsNewInfo = Convert.ToDouble(eodPosition.PosiQuantity * eodPosition.CountRatio / eodPosition.ContractSize);
|
|
trp.TradePrice = Convert.ToDouble(eodPosition.PosiNetPrice * shortRatio);
|
|
trp.Notional = Convert.ToDouble(eodPosition.PosiQuantity * eodPosition.CountRatio);
|
|
trp.StockEqvNotional = Convert.ToDouble(eodPosition.PosiNotionalValue);
|
|
}
|
|
else
|
|
{
|
|
trp.LotsNewInfo = trp.Lots;
|
|
trp.TradePrice = Convert.ToDouble(position.PosiNetPrice * shortRatio);
|
|
trp.Notional = Convert.ToDouble(position.PosiQuantity * position.CountRatio);
|
|
trp.StockEqvNotional = Convert.ToDouble(position.PosiNotionalValue);
|
|
}
|
|
//成交份额(countRatio传入1算出来的是成交份额)
|
|
trp.OriginalNotionalV = trp.OriginalNotional;
|
|
trp.GammaInLots = optionValueResult.Sum(t => t.GetGamma(0)) / contractSize;
|
|
trp.Theta = optionValueResult.Sum(t => t.Theta);
|
|
trp.Rho = optionValueResult.Sum(t => t.Rho) * 100;
|
|
trp.DdeltaDt = optionValueResult.Sum(t => t.DDeltaDt);
|
|
trp.DdeltaDvol = optionValueResult.Sum(t => t.DDeltaDVol);
|
|
trp.DvegaDt = optionValueResult.Sum(t => t.DVegaDt);
|
|
trp.DvegaDvol = optionValueResult.Sum(t => t.DVegaDVol);
|
|
trp.Vol = optionValueResult[0].Vol;
|
|
trp.PricingT = optionValueResult[0].PricingT;
|
|
if (PS.Config.Is润和)
|
|
{
|
|
var unVol = VolatilityHelper.GetVol(DateTime.Today, "交易", um.UnderlyingCode, DataCacheProvider.GetAssetUnitDataSource().GetData(tradeObj.AssetId)?.UserGroup);
|
|
trp.TradeSavedVol = VolatilityHelper.GetInterpolatedVol(
|
|
volConstructionType: PS.Config.ErpElement.SkewMapVolConstruction ? VolConstructionType.SkewMap : VolConstructionType.Normal,
|
|
volSurface: unVol,
|
|
valueDate: DateTime.Today,
|
|
underlyingCode: um.UnderlyingCode,
|
|
exerciseDate: tradeObj.ExerciseDate.Value,
|
|
strike: Convert.ToDouble(eodPosition.PosiNetPrice),
|
|
isBuy: trp.BuySell == "买入",
|
|
isCall: trp.CallPut == "Call",
|
|
spotPrice: tradeObj.SpotPrice ?? 0,
|
|
isMoneynessOption: tradeObj.IsMoneynessOption == "是");
|
|
}
|
|
|
|
trp.QuoteUnit = variety?.QuoteUnit;
|
|
|
|
return trp;
|
|
}
|
|
|
|
//更新实时盈亏
|
|
private static void UpdateOTCPnl(trade td, TradingRiskParameter para, double? lastPv
|
|
, IEnumerable<trade_cash> tradeCashList, IEnumerable<trade_cash> tdTradeCashUnwindList)
|
|
{
|
|
var hasLastPv = lastPv.HasValue;
|
|
var hasNotional = Math.Abs(para.Notional) > 1e-6;
|
|
var completed = ConsTrade.TradeCompleteStatus.Contains(para.TradeStatus);
|
|
var pendingForAudit = !completed && para.TradeStatus == ConsTrade.平仓待复核 || para.TradeStatus == ConsTrade.行权待复核;
|
|
|
|
//当日实现收益
|
|
double RealizedProfit = 0;
|
|
|
|
if (hasLastPv)
|
|
{
|
|
RealizedProfit = tdTradeCashUnwindList.Sum(tc => tc.Amount);
|
|
}
|
|
else
|
|
{
|
|
var lastTc = tradeCashList.Where(tc => tc.ValidState != ConsGlobal.InValid && !tc.IsDeleted && tc.Action == ClientCashInCashOut.系统操作_期权费);
|
|
if (lastTc.Any())
|
|
{
|
|
lastPv = -lastTc.Sum(tc => tc.Amount - (tc.ExtraAmount ?? 0));
|
|
}
|
|
else if (para.TradeType == "远期")
|
|
{
|
|
lastPv = -para.TradePrice; //远期交易金额没有买卖方向区分,转换为pv直接取符号即可
|
|
}
|
|
else
|
|
{
|
|
var sign = TradeCalcHelper.GetBuySellSign(para.BuySell);
|
|
lastPv = para.TradePrice - para.OriginalPrincipalSum * sign;
|
|
}
|
|
if (td.TradeType == "雪球期权" && td.trade_snowball.PrepaymentUsed)
|
|
{
|
|
lastPv += (td.trade_snowball.PrepaymentRatio ?? 0) * (td.OriginalStockEqvNotional ?? 0) * (td.BuySell == "卖出" ? -1 : 1);
|
|
}
|
|
RealizedProfit = tradeCashList.Where(tc => tc.ValidState != ConsGlobal.InValid && !tc.IsDeleted && ClientCashInCashOut.PROFIT_ACTION.Contains(tc.Action)).Sum(tc => tc.Amount);
|
|
}
|
|
|
|
//已平仓 已到期 已行权
|
|
if (completed)
|
|
{
|
|
para.DailyPnl = RealizedProfit - lastPv.Value;
|
|
para.TotalPnl = tradeCashList.Where(tc => tc.ValidState != ConsGlobal.InValid && !tc.IsDeleted).Sum(tc => tc.Amount);
|
|
para.AccruedTotalPnl = para.TotalPnl;
|
|
para.ExercisePnl = 0;
|
|
para.Notional = 0;
|
|
para.TradePrice = 0;
|
|
para.OriginalPrincipalSum = 0;
|
|
para.Pv = 0;
|
|
para.Delta = 0;
|
|
para.DeltaT1 = 0;
|
|
para.DeltaT1Lots = 0;
|
|
para.SA_Delta = 0;
|
|
para.DeltaCash = 0;
|
|
para.Gamma = 0;
|
|
para.Vega = 0;
|
|
para.GammaCash = 0;
|
|
para.DeltaInLots = 0;
|
|
para.GammaInLots = 0;
|
|
para.Theta = 0;
|
|
para.Rho = 0;
|
|
para.DdeltaDt = 0;
|
|
para.DdeltaDvol = 0;
|
|
para.DvegaDt = 0;
|
|
para.DvegaDvol = 0;
|
|
para.StockEqvNotional = 0;
|
|
para.KnockOutPayoff = 0;
|
|
}
|
|
else if (pendingForAudit)
|
|
{
|
|
//平仓待复核 行权待复核
|
|
var tradeCashTemp = tradeCashList.Where(tc => tc.ValidState == ConsGlobal.InValid && !tc.IsDeleted && tc.Action != ClientCashInCashOut.系统操作_期权费).OrderByDescending(a => a.id).FirstOrDefault();
|
|
//剩余持仓
|
|
var holdNotional = para.Notional - (tradeCashTemp == null ? 0 : ConsTrade.平仓待复核.Equals(para.TradeStatus) ? (tradeCashTemp.UnwindNotional ?? 0) : para.Notional);
|
|
//剩余持仓百分比
|
|
var holdRatio = hasNotional ? holdNotional / para.Notional : 0;
|
|
|
|
var newPv = para.Pv * holdRatio + (tradeCashTemp?.Amount ?? 0);
|
|
|
|
para.Notional = holdNotional;
|
|
para.DailyPnl = newPv + RealizedProfit - lastPv.Value;
|
|
para.AccruedTotalPnl = para.TotalPnl = tradeCashList.Where(tc => tc.ValidState != ConsGlobal.InValid && !tc.IsDeleted).Sum(t => t.Amount) + newPv;
|
|
|
|
if (Math.Abs(1 - holdRatio) > 1e-5)
|
|
{
|
|
para.ExercisePnl *= holdRatio;
|
|
para.Pv *= holdRatio;
|
|
para.Tv *= holdRatio;
|
|
para.TradePrice *= holdRatio;
|
|
para.OriginalPrincipalSum *= holdRatio;
|
|
para.Delta *= holdRatio;
|
|
para.SA_Delta *= holdRatio;
|
|
para.DeltaCash *= holdRatio;
|
|
para.Gamma *= holdRatio;
|
|
para.Vega *= holdRatio;
|
|
para.GammaCash *= holdRatio;
|
|
para.DeltaInLots *= holdRatio;
|
|
para.GammaInLots *= holdRatio;
|
|
para.Theta *= holdRatio;
|
|
para.Rho *= holdRatio;
|
|
para.DdeltaDt *= holdRatio;
|
|
para.DdeltaDvol *= holdRatio;
|
|
para.DvegaDt *= holdRatio;
|
|
para.DvegaDvol *= holdRatio;
|
|
if (para.IsKnockOut)
|
|
{
|
|
para.KnockOutPayoff *= holdRatio;
|
|
}
|
|
}
|
|
}
|
|
else if (para.TradeType == "远期")
|
|
{
|
|
para.DailyPnl = para.Pv + RealizedProfit - lastPv.Value;
|
|
if (hasLastPv)
|
|
{
|
|
para.TotalPnl += para.DailyPnl;
|
|
}
|
|
else
|
|
{
|
|
para.TotalPnl = para.DailyPnl;
|
|
}
|
|
para.AccruedTotalPnl = para.TotalPnl;
|
|
}
|
|
else if (ConsTrade.TradeStatusBeforConfirmed.Contains(para.TradeStatus))
|
|
{
|
|
para.DailyPnl = para.Pv - para.TradePrice;
|
|
if (td.TradeType == "雪球期权" && td.trade_snowball.PrepaymentUsed)
|
|
{
|
|
para.DailyPnl -= (td.trade_snowball.PrepaymentRatio ?? 0) * td.StockEqvNotional * (td.BuySell == "卖出" ? -1 : 1);
|
|
}
|
|
para.TotalPnl = para.DailyPnl;
|
|
para.AccruedTotalPnl = para.DailyPnl;
|
|
}
|
|
else
|
|
{
|
|
para.DailyPnl = para.Pv + RealizedProfit - lastPv.Value;
|
|
para.TotalPnl = para.Pv + tradeCashList.Where(tc => tc.ValidState != ConsGlobal.InValid && !tc.IsDeleted).Sum(t => t.Amount);
|
|
if (td.TradeType == "雪球期权" && td.trade_snowball.PrepaymentUsed)
|
|
{
|
|
//只关注持仓的部分
|
|
para.TotalPnl -= (td.trade_snowball.PrepaymentRatio ?? 0) * para.Notional * (td.SpotPrice ?? 0) * (td.BuySell == "卖出" ? -1 : 1);
|
|
}
|
|
para.AccruedTotalPnl = para.TotalPnl;
|
|
}
|
|
|
|
//用于计算错误时查看
|
|
para.LastPv = lastPv;
|
|
}
|
|
|
|
//获取行权盈亏
|
|
private double GetExercisePnl(OtcTradeBase tradeObj, double spotPrice, out double? actualStrike)
|
|
{
|
|
actualStrike = null;
|
|
|
|
if (tradeObj == null)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
if ("亚式期权".Equals(tradeObj.TradeType))
|
|
{
|
|
double? avgPrice = null;
|
|
|
|
var trade_asian_option = _dataProvider.TradeExtendDataProvider.GetTrade_Asian_Option(tradeObj.id);
|
|
|
|
if (trade_asian_option != null)
|
|
{
|
|
var fixings = AsiaOptionProvider.Default.GetFixingString(_context.ValueDate, tradeObj, trade_asian_option);
|
|
avgPrice = GetAsianAveragePrice(fixings, trade_asian_option.PayoffType);
|
|
if (avgPrice != null && "Floating".Equals(trade_asian_option.StrikeType))
|
|
{
|
|
actualStrike = avgPrice;
|
|
}
|
|
}
|
|
|
|
if (actualStrike == null)
|
|
{
|
|
actualStrike = tradeObj.IsMoneynessOptionData ? (tradeObj.SpotPrice * tradeObj.Strike) : tradeObj.Strike;
|
|
}
|
|
|
|
if (trade_asian_option == null)
|
|
{
|
|
return 0.0;
|
|
}
|
|
|
|
if ("Fixed".Equals(trade_asian_option?.StrikeType))
|
|
{
|
|
return (Math.Max((tradeObj.CallPut == "Call" ? 1 : -1) * ((avgPrice ?? spotPrice) - (actualStrike ?? 0.0)) * tradeObj.Notional, 0.0) - (tradeObj.TradePrice ?? 0.0)) * EodOperationBase.GetSign(tradeObj.BuySell);
|
|
}
|
|
|
|
if (avgPrice == null)
|
|
{
|
|
return 0.0;
|
|
}
|
|
|
|
return (Math.Max((tradeObj.CallPut == "Call" ? 1 : -1) * (spotPrice - avgPrice.Value) * tradeObj.Notional, 0.0) - (tradeObj.TradePrice ?? 0.0)) * EodOperationBase.GetSign(tradeObj.BuySell);
|
|
}
|
|
else if ("结构化交易".Equals(tradeObj.TradeType))
|
|
{
|
|
throw new ServiceException($"不支持'结构化交易'主交易的计算,tradeId:{tradeObj.id},tradeNumber:{tradeObj.TradeNumber}");
|
|
}
|
|
else
|
|
{
|
|
actualStrike = tradeObj.IsMoneynessOptionData ? (tradeObj.SpotPrice * tradeObj.Strike) : tradeObj.Strike;
|
|
return (Math.Max((tradeObj.CallPut == "Call" ? 1 : -1) * (spotPrice - (actualStrike ?? 0.0)) * tradeObj.Notional, 0.0) - (tradeObj.TradePrice ?? 0.0)) * EodOperationBase.GetSign(tradeObj.BuySell);
|
|
}
|
|
}
|
|
|
|
//获取亚式期权均价起算日之后的均价,未到均价起算日则返回Null
|
|
private static double? GetAsianAveragePrice(string fixings, string PayoffType)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(fixings))
|
|
{
|
|
return null;
|
|
}
|
|
|
|
var arr = fixings.Split(QdpConsts.Semilicon);
|
|
|
|
var fixingValues = arr.Select(x => DataConvert.ConvertCommaValueToDouble(x, 1) ?? 0).ToArray();
|
|
|
|
if ("GeometricAverage".Equals(PayoffType))
|
|
{
|
|
var n = fixingValues.Length;
|
|
return Math.Pow(fixingValues.Aggregate(func: (result, item) => result * item), 1.0 / n);
|
|
}
|
|
|
|
if ("ArithmeticAverage".Equals(PayoffType) || "DiscreteArithmeticAverage".Equals(PayoffType))
|
|
{
|
|
return fixingValues.Average();
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
#endregion
|
|
}
|
|
}
|