2091 lines
91 KiB
C#
2091 lines
91 KiB
C#
using Qdp.Pricing.Base.Implementations;
|
||
using Qdp.Pricing.Base.Utilities;
|
||
using System.Diagnostics;
|
||
using System.Linq.Expressions;
|
||
using YLErp.BLL;
|
||
using YLErp.BLL.Eod;
|
||
using YLErp.BLL.Hedge;
|
||
using YLErp.Configuration;
|
||
using YLErp.DBModels.Consts;
|
||
using YLErp.DBModels.Helpers;
|
||
using YLErp.Enums;
|
||
using YLErp.Modules.CalculationModule;
|
||
using YLErp.Modules.CalculationModule.Abstract;
|
||
using YLErp.Modules.DataProviderModule;
|
||
using YLErp.QdpModule;
|
||
using YLErp.QdpModule.Constants;
|
||
|
||
namespace YLErp.Modules.EodModule.SettlementModule
|
||
{
|
||
/// <summary>
|
||
/// 日终持仓结算服务
|
||
/// </summary>
|
||
public class EodPositionSettleService : EodSettleServiceBaseV2
|
||
{
|
||
public const string Step = "日终结算";
|
||
|
||
public EodPositionSettleService(EodSettlementContextV2 context) : base(context)
|
||
{
|
||
|
||
}
|
||
|
||
public IEnumerable<EodTradePosition> EodTradePositions { get; private set; }
|
||
|
||
public void Execute(string volType, bool useClosePrice, IAsyncTaskManager taskManager)
|
||
{
|
||
if (volType == "光证" && PS.Config.Company == CompanyEnum.光大光子 && _context.SystemValue.RiskFreeRateExtend == null)
|
||
{
|
||
_context.RaiseError(Step, "RiskFreeRateExtend缺失!");
|
||
}
|
||
|
||
IEodPositionSettleService executor = null;
|
||
|
||
if (useClosePrice)
|
||
{
|
||
switch (volType)
|
||
{
|
||
case "光证":
|
||
case "BidAskVol":
|
||
case "风控":
|
||
case "分红率0":
|
||
executor = new EodPositionSettleService<eod_trade_risk_extend, eod_trade_position_extend, eod_pnl_extend>(_context);
|
||
break;
|
||
case "持仓":
|
||
executor = new EodPositionSettleService<eod_trade_risk, eod_trade_position, eod_pnl>(_context);
|
||
break;
|
||
case "开仓":
|
||
executor = new EodPositionSettleService<eod_trade_risk_openvol, eod_trade_position_openvol, eod_pnl_openvol>(_context);
|
||
break;
|
||
case "对冲":
|
||
executor = new EodPositionSettleService<eod_trade_risk_hedgevol, eod_trade_position_hedgevol, eod_pnl_hedgevol>(_context);
|
||
break;
|
||
default:
|
||
_context.RaiseError(Step, "不能辨认的结算波动率类型:" + volType);
|
||
break;
|
||
}
|
||
}
|
||
else
|
||
{
|
||
switch (volType)
|
||
{
|
||
case "光证":
|
||
_context.RaiseError(Step, "使用结算价结算时不支持光证波动率");
|
||
break;
|
||
case "BidAskVol":
|
||
_context.RaiseError(Step, "使用结算价结算时不支持BidAskVol波动率");
|
||
break;
|
||
case "持仓":
|
||
executor = new EodPositionSettleService<eod_trade_risk_s, eod_trade_position_s, eod_pnl_s>(_context);
|
||
break;
|
||
case "开仓":
|
||
executor = new EodPositionSettleService<eod_trade_risk_openvol_s, eod_trade_position_openvol_s, eod_pnl_openvol_s>(_context);
|
||
break;
|
||
case "对冲":
|
||
executor = new EodPositionSettleService<eod_trade_risk_hedgevol_s, eod_trade_position_hedgevol_s, eod_pnl_hedgevol_s>(_context);
|
||
break;
|
||
case "风控":
|
||
case "分红率0":
|
||
executor = new EodPositionSettleService<eod_trade_risk_extend_s, eod_trade_position_extend_s, eod_pnl_extend_s>(_context);
|
||
break;
|
||
default:
|
||
_context.RaiseError(Step, "不能辨认的结算波动率类型:" + volType);
|
||
break;
|
||
}
|
||
}
|
||
|
||
//计算预付金
|
||
|
||
var tradeSpanTask = Task.FromResult<Dictionary<int, trade_span>>(null);
|
||
|
||
var _useClosePrice = useClosePrice;
|
||
if (volType == "持仓" && useClosePrice && PS.Config.Company == CompanyEnum.厦门象屿 && _context.SettleDate > new DateTime(2021, 12, 30))
|
||
{
|
||
_useClosePrice = false;
|
||
}
|
||
executor.Execute(volType, _useClosePrice, tradeSpanTask, taskManager);
|
||
|
||
EodTradePositions = executor.EodTradePositions;
|
||
}
|
||
|
||
//计算预付金
|
||
private Task<Dictionary<int, trade_span>> CalcTradeSpanAsync(string volType, bool useClosePrice)
|
||
{
|
||
return Task.Run(() =>
|
||
{
|
||
if (volType == "持仓" && useClosePrice)
|
||
{
|
||
var tdQuery = _context.OtcTrades.Where(t => t.TradeStatus == "确认成交"&&t.TradeType != ConsGlobal.TradeType.PayoffSwap);
|
||
if (PS.Config.ErpElement.SupportMultiCalendar)
|
||
{
|
||
tdQuery = tdQuery.Where(n => !_context.IsHolidayTrade(n.id));
|
||
}
|
||
var tradeList = tdQuery.ToList();
|
||
var needCalaTradeId = tradeList.Select(o => o.id).ToList();
|
||
var exTradeSapns = new List<trade_span>();
|
||
//多日历时遇到节假日的交易复制上日预付金
|
||
if (PS.Config.ErpElement.SupportMultiCalendar && _context.HolidayTrades.Any())
|
||
{
|
||
var preTdSpanList = DbContext.trade_span.AsNoTracking()
|
||
.Where(n => n.ValueDate == _context.PreSettleDate && !needCalaTradeId.Contains(n.TradeId)).ToArray();
|
||
|
||
foreach (var ts in preTdSpanList)
|
||
{
|
||
if (_context.IsHolidayTrade(ts.TradeId))
|
||
{
|
||
ts.id = 0;
|
||
ts.ValueDate = _context.SettleDate;
|
||
exTradeSapns.Add(ts);
|
||
}
|
||
}
|
||
}
|
||
if (new EodWorstClientPayableCalc(_context).WorstClientPayableCalc(tradeList, exTradeSapns))
|
||
{
|
||
//多日历时遇到节假日的交易复制上日
|
||
if (PS.Config.ErpElement.SupportMultiCalendar && _context.HolidayTrades.Any())
|
||
{
|
||
var todayTdPositonSwapList = DbContext.eod_trade_position_swap
|
||
.Where(n => n.ValueDate == _context.SettleDate && !needCalaTradeId.Contains(n.TradeId)).ToArray();
|
||
DbContext.eod_trade_position_swap.RemoveRange(todayTdPositonSwapList);
|
||
var preTdPositonSwapList = DbContext.eod_trade_position_swap.AsNoTracking()
|
||
.Where(n => n.ValueDate == _context.PreSettleDate && !needCalaTradeId.Contains(n.TradeId)).ToArray();
|
||
foreach (var tp in preTdPositonSwapList)
|
||
{
|
||
if (_context.IsHolidayTrade(tp.TradeId))
|
||
{
|
||
var TdPositonSwap = tp.Clone();
|
||
TdPositonSwap.id = 0;
|
||
TdPositonSwap.ValueDate = _context.SettleDate;
|
||
DbContext.eod_trade_position_swap.Add(TdPositonSwap);
|
||
}
|
||
}
|
||
DbContext.SaveChanges();
|
||
}
|
||
var tsQuery = DbContext.trade_span.Where(n => n.ValueDate == _context.SettleDate);
|
||
return tsQuery.ToDictionary(n => n.TradeId, m => m);
|
||
}
|
||
else
|
||
{
|
||
_context.RaiseError(Step, "计算预付金未成功");
|
||
return null;
|
||
}
|
||
}
|
||
else
|
||
{
|
||
using var db = DbContextFactory.GetYLDbContext();
|
||
return db.trade_span.Where(t => t.ValueDate == _context.SettleDate)
|
||
.ToDictionary(n => n.TradeId, m => m);
|
||
}
|
||
|
||
}).ContinueWith(t =>
|
||
{
|
||
if (t.Exception != null)
|
||
{
|
||
_context.RaiseError(Step, "计算预付金出错:" + t.Exception.Messages(), t.Exception);
|
||
return null;
|
||
}
|
||
return t.Result;
|
||
});
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
///
|
||
/// </summary>
|
||
interface IEodPositionSettleService
|
||
{
|
||
IEnumerable<EodTradePosition> EodTradePositions { get; }
|
||
|
||
void Execute(string volType, bool useClosePrice, Task<Dictionary<int, trade_span>> tradeSpanTask, IAsyncTaskManager taskManager);
|
||
}
|
||
|
||
/// <summary>
|
||
///
|
||
/// </summary>
|
||
class EodPositionSettleService<TRisk, TPosition, TPnl> : EodSettleServiceBaseV2, IEodPositionSettleService
|
||
where TPnl : EodPnl, new()
|
||
where TRisk : EodTradeRisk, new()
|
||
where TPosition : EodTradePosition, new()
|
||
{
|
||
readonly List<TPnl> _pnlList;
|
||
readonly List<TRisk> _riskList;
|
||
readonly List<TPosition> _positionList;
|
||
readonly List<eod_trade_position_swap> _positionSwapList;
|
||
|
||
readonly string[] _tcActions = new string[] {
|
||
ClientCashInCashOut.系统操作_期权费,
|
||
ClientCashInCashOut.系统操作_平仓费,
|
||
ClientCashInCashOut.系统操作_行权费,
|
||
ClientCashInCashOut.系统操作_互换,
|
||
ClientCashInCashOut.系统操作_票息,
|
||
ClientCashInCashOut.人工操作_其他,//其他收支
|
||
};
|
||
|
||
string _step = "日终结算";
|
||
|
||
readonly Action<string> _outputDebugLog;
|
||
|
||
bool _useClosePrice;
|
||
SettlementTypeEnum _settlementType;
|
||
EodPriceProvider _eodPriceProvider;
|
||
|
||
Dictionary<int, PreSettleData> _preOtcDic;
|
||
|
||
public EodPositionSettleService(EodSettlementContextV2 context) : base(context)
|
||
{
|
||
_pnlList = new List<TPnl>();
|
||
_riskList = new List<TRisk>();
|
||
_positionList = new List<TPosition>();
|
||
_positionSwapList = new List<eod_trade_position_swap>();
|
||
if (_context.SettlementConfig.OutputDebugLog)
|
||
{
|
||
_outputDebugLog = LogFactory.GetLogger("收盘DEBUG").Info;
|
||
}
|
||
else
|
||
{
|
||
_outputDebugLog = LogFactory.GetLogger("收盘DEBUG").Debug;
|
||
}
|
||
}
|
||
|
||
public IEnumerable<EodTradePosition> EodTradePositions => _positionList;
|
||
|
||
public void Execute(string volType, bool useClosePrice,
|
||
Task<Dictionary<int, trade_span>> tradeSpanTask, IAsyncTaskManager taskManager)
|
||
{
|
||
if (tradeSpanTask is null)
|
||
{
|
||
throw new ArgumentNullException(nameof(tradeSpanTask));
|
||
}
|
||
|
||
if (taskManager is null)
|
||
{
|
||
throw new ArgumentNullException(nameof(taskManager));
|
||
}
|
||
|
||
_pnlList.Clear();
|
||
_riskList.Clear();
|
||
_positionList.Clear();
|
||
_positionSwapList.Clear();
|
||
|
||
_useClosePrice = useClosePrice;
|
||
_settlementType = useClosePrice ? SettlementTypeEnum.ClosePrice : SettlementTypeEnum.SettlePrice;
|
||
_eodPriceProvider = _context.GetEodPriceProvider();
|
||
|
||
var request = _context.Request;
|
||
var priceType = useClosePrice ? "收盘价" : "结算价";
|
||
|
||
_step = $"日终结算({volType}+{priceType})";
|
||
|
||
//构建条件过滤
|
||
var posPredicate = GetPositionPredicate(out var where);
|
||
|
||
//清除历史结算数据
|
||
|
||
var removeOldDataTask = Task.Run(() =>
|
||
{
|
||
taskManager.SetTaskStep("清除历史结算数据");
|
||
|
||
using (var db = DbContextFactory.GetYLDbContext())
|
||
{
|
||
db.BulkDelete<TPosition>(where);
|
||
|
||
var notExists = string.Empty;
|
||
if (_context.Request.IsPartialSettlement)
|
||
{
|
||
notExists = @" and not exists(select * from {0}
|
||
where ValueDate=t1.ValueDate and TradeId=t1.TradeId and IFNULL(HedgeUniqueCode,'')=IFNULL(t1.HedgeUniqueCode,''))";
|
||
notExists = string.Format(notExists, db.GetTableName<TPosition>());
|
||
}
|
||
var sql = @"
|
||
delete t1 from {0} t1 where t1.ValueDate='{1}'{2};
|
||
delete t1 from {3} t1 where t1.ValueDate='{1}'{2};";
|
||
sql = string.Format(sql, db.GetTableName<TPnl>()
|
||
, _context.SettleDateStr, notExists, db.GetTableName<TRisk>(), db.GetTableName<TPosition>());
|
||
|
||
if (volType == "持仓")
|
||
{
|
||
var swapNotExists = string.Empty;
|
||
if (_context.Request.IsPartialSettlement)
|
||
{
|
||
swapNotExists = @" and not exists(select * from {0}
|
||
where ValueDate=t1.ValueDate and TradeId=t1.TradeId)";
|
||
swapNotExists = string.Format(swapNotExists, db.GetTableName<TPosition>());
|
||
}
|
||
sql += string.Format("delete t1 from {0} t1 where t1.ValueDate='{1}'{2};"
|
||
, db.GetTableName<eod_trade_position_swap>(), _context.SettleDateStr, swapNotExists);
|
||
}
|
||
|
||
db.Database.ExecuteSqlRaw(sql);
|
||
}
|
||
|
||
_context.LogInfo("结束任务:清除历史结算数据");
|
||
});
|
||
|
||
//上一交易日持仓数据
|
||
var prePositions = DbContext.Set<TPosition>().AsNoTracking().Where(posPredicate).ToArray();
|
||
|
||
InnerExecuteOtcTradesV2(volType, useClosePrice, prePositions);
|
||
|
||
_context.CheckCanceled();
|
||
|
||
InnerExecuteHedgeTrades(volType, useClosePrice, prePositions);
|
||
|
||
removeOldDataTask.Wait();
|
||
|
||
SaveResultData(tradeSpanTask, taskManager);
|
||
}
|
||
|
||
private Expression<Func<TPosition, bool>> GetPositionPredicate(out string where)
|
||
{
|
||
var request = _context.Request;
|
||
|
||
where = $"ValueDate='{_context.SettleDateStr}'";
|
||
|
||
var predicate = PredicateBuilder.Create<TPosition>(n => n.ValueDate == _context.PreSettleDate);
|
||
|
||
if (request.IsPartialSettlement)
|
||
{
|
||
var wArr = new string[2];
|
||
var pArr = new Expression<Func<TPosition, bool>>[2];
|
||
|
||
if (request.IsSettleOtcTrades)
|
||
{
|
||
if (request.ClientIds != null && request.ClientIds.Any())
|
||
{
|
||
wArr[0] = string.Format("ClientId in ({0})", string.Join(",", request.ClientIds));
|
||
pArr[0] = PredicateBuilder.Create<TPosition>(t => request.ClientIds.Contains(t.ClientId));
|
||
}
|
||
else
|
||
{
|
||
wArr[0] = "TradeId > 0";
|
||
pArr[0] = PredicateBuilder.Create<TPosition>(t => t.TradeId > 0);
|
||
}
|
||
}
|
||
|
||
if (request.IsSettleExchangeTrades)
|
||
{
|
||
wArr[1] = "TradeId=0";
|
||
pArr[1] = PredicateBuilder.Create<TPosition>(t => t.TradeId == 0);
|
||
}
|
||
|
||
if (pArr.All(n => n != null))
|
||
{
|
||
predicate = predicate.And(pArr[0].Or(pArr[1]));
|
||
|
||
where += $"AND ({wArr[0]} OR {wArr[1]})";
|
||
}
|
||
else
|
||
{
|
||
predicate = predicate.And(pArr[0] ?? pArr[1]);
|
||
|
||
where += "AND " + (wArr[0] ?? wArr[1]);
|
||
}
|
||
}
|
||
|
||
return predicate;
|
||
}
|
||
|
||
//保存新的计算数据
|
||
private void SaveResultData(Task<Dictionary<int, trade_span>> tradeSpanTask, IAsyncTaskManager taskManager)
|
||
{
|
||
_context.CheckCanceled();
|
||
|
||
var saveRiskTask = _riskList.Any() ? Task.Run(() =>
|
||
{
|
||
taskManager.SetTaskStep("保存日终风险数据");
|
||
|
||
using var db = DbContextFactory.GetYLDbContext();
|
||
db.Set<TRisk>().AddRange(_riskList);
|
||
db.SaveChanges();
|
||
|
||
}, _context.CancellationToken) : null;
|
||
|
||
var savePnlTask = _pnlList.Any() ? Task.Run(() =>
|
||
{
|
||
taskManager.SetTaskStep("保存盈亏分解数据");
|
||
|
||
using var db = DbContextFactory.GetYLDbContext();
|
||
db.Set<TPnl>().AddRange(_pnlList);
|
||
db.SaveChanges();
|
||
|
||
}, _context.CancellationToken) : null;
|
||
|
||
var savePosTask = _positionList.Any() ? Task.Run(() =>
|
||
{
|
||
taskManager.SetTaskStep("保存日终持仓数据");
|
||
|
||
SavePosition(tradeSpanTask);
|
||
|
||
}, _context.CancellationToken) : null;
|
||
|
||
var savePosSwapTask = _positionSwapList.Any() ? Task.Run(() =>
|
||
{
|
||
taskManager.SetTaskStep("保存互换日终持仓数据");
|
||
|
||
using var db = DbContextFactory.GetYLDbContext();
|
||
if (_positionSwapList.Any())
|
||
{
|
||
db.eod_trade_position_swap.AddRange(_positionSwapList);
|
||
db.SaveChanges();
|
||
}
|
||
|
||
}, _context.CancellationToken) : null;
|
||
|
||
taskManager.WaitTasks(saveRiskTask, savePnlTask, savePosTask, savePosSwapTask);
|
||
}
|
||
|
||
//保存eod_position
|
||
private void SavePosition(Task<Dictionary<int, trade_span>> tradeSpanTask)
|
||
{
|
||
tradeSpanTask.Wait();
|
||
|
||
_context.CheckCanceled();
|
||
|
||
var tradeSpanDic = tradeSpanTask.Result;
|
||
|
||
foreach (var pos in _positionList)
|
||
{
|
||
var margin = 0d;
|
||
var swapInitMargin = 0d;
|
||
var swapWinLoss = 0d;
|
||
var swapUnMargin = 0d;
|
||
|
||
if (tradeSpanDic != null)
|
||
{
|
||
tradeSpanDic.TryGetValue(pos.TradeId, out var tradeSpan);
|
||
if (tradeSpan != null)
|
||
{
|
||
margin = tradeSpan.WorstCastClientPayable ?? 0.0;
|
||
swapInitMargin = tradeSpan.SwapInitMargin ?? 0.0;
|
||
swapWinLoss = tradeSpan.SwapWinLoss ?? 0.0;
|
||
swapUnMargin = tradeSpan.Margin == 0 ? Math.Max(tradeSpan.SwapWinLoss ?? 0.0, 0.0) : 0.0;
|
||
}
|
||
}
|
||
|
||
pos.Margin = margin;
|
||
pos.SwapInitMargin = swapInitMargin;
|
||
pos.SwapWinLoss = swapWinLoss;
|
||
pos.SwapUnMargin = swapUnMargin;
|
||
}
|
||
|
||
using var db = DbContextFactory.GetYLDbContext();
|
||
db.Set<TPosition>().AddRange(_positionList);
|
||
db.SaveChanges();
|
||
}
|
||
|
||
#region----场外交易盈亏----
|
||
|
||
private void InnerExecuteOtcTradesV2(string volType, bool useClosePrice, IEnumerable<TPosition> prePositions)
|
||
{
|
||
var otcTrades = _context.OtcTrades;
|
||
|
||
if (!otcTrades.Any())
|
||
{
|
||
return;
|
||
}
|
||
|
||
var preOtcPositionDic = prePositions.Where(n => n.TradeId > 0).ToDictionary(n => n.TradeId);
|
||
|
||
if (_context.SettlementConfig.SkipLastPvCheck)
|
||
{
|
||
var unexpectTrades = otcTrades.Where(td => td.TradeDate <= _context.PreSettleDate && !preOtcPositionDic.ContainsKey(td.id)).ToArray();
|
||
foreach (var unt in unexpectTrades)
|
||
{
|
||
preOtcPositionDic[unt.id] = new TPosition();
|
||
}
|
||
}
|
||
else
|
||
{
|
||
var unexpectTrades = otcTrades.Where(td => td.TradeDate <= _context.PreSettleDate && !preOtcPositionDic.ContainsKey(td.id))
|
||
.Select(t => t.TradeNumber).Take(10);
|
||
if (unexpectTrades.Any())
|
||
{
|
||
var joinStr = string.Join(",", unexpectTrades);
|
||
_context.RaiseError(_step, string.Concat("交易:", joinStr, ",尚未完成上一交易日的结算"));
|
||
}
|
||
}
|
||
|
||
var volProvider = EodVolProviderFactory.GetEodVolProvider(_context.SettleDate, volType);
|
||
var riskFreeRate = ((volType == "光证" ? _context.SystemValue.RiskFreeRateExtend : _context.SystemValue.RiskFreeRate) ?? 0) / 100;
|
||
|
||
var sw = new Stopwatch();
|
||
|
||
_outputDebugLog?.Invoke($"####开始计算,条数:{otcTrades.Count()}####");
|
||
|
||
foreach (var td in otcTrades)
|
||
{
|
||
sw.Restart();
|
||
|
||
_context.CheckCanceled();
|
||
preOtcPositionDic.TryGetValue(td.id, out var prePos);
|
||
|
||
//收益互换在假期日也要计算利息
|
||
if (PS.Config.ErpElement.SupportMultiCalendar && prePos != null && td.TradeType != ConsGlobal.TradeType.PayoffSwap && _context.IsHolidayTrade(td.id))
|
||
{
|
||
CopyPreValues(prePos, preOtcPositionDic.Keys, volType == "持仓" && _useClosePrice);
|
||
continue;
|
||
}
|
||
|
||
var curPnl = GetEodPnl(td);
|
||
TPosition curPos = null;
|
||
eod_trade_position_swap curPosSwap = null;
|
||
TradeValueResult curValue = null;
|
||
var calcTrade = td;
|
||
|
||
_outputDebugLog?.Invoke($"[{volType}][{td.TradeType}][{td.TradeNumber}]开始");
|
||
|
||
try
|
||
{
|
||
if (ConsTrade.TradeCompleteStatus.Contains(td.TradeStatus))
|
||
{
|
||
_riskList.AddRange(GetEodRisks(td, null, null));
|
||
curPos = GetEodPosition(td, prePos, new TradeValueResult(), true, volType, useClosePrice);
|
||
curPos.UnderlyingPrice = _eodPriceProvider.GetPrice(td.UnderlyingCode, SettlementTypeEnum.ClosePrice);
|
||
//盈亏分解
|
||
if (_context.SettlementConfig.CalcPnlExplain && CanCalcPnlExplain(calcTrade))
|
||
{
|
||
var clone = td.Clone();
|
||
clone.Notional = 1;
|
||
if (volType == "分红率0")
|
||
{
|
||
clone.DividendRate = 0;
|
||
}
|
||
if (volType == "光证" || !calcTrade.NoRiskRate.HasValue)
|
||
{
|
||
clone.NoRiskRate = riskFreeRate;
|
||
}
|
||
var spot = _eodPriceProvider.GetPrice(clone.UnderlyingCode, _useClosePrice ? clone.SettlementType : SettlementTypeEnum.SettlePrice);
|
||
curValue = CalcPnlExplainV2(clone, new CalcField
|
||
{
|
||
spot = spot,
|
||
vol = volProvider.GetVol(td, spot) ?? ConsGlobal.DefaultVol,
|
||
dividendRate = clone.DividendRate ?? 0,
|
||
riskFreeRate = riskFreeRate,
|
||
date = _context.SettleDate
|
||
}, PricingRequest.Pv | PricingRequest.Delta);
|
||
}
|
||
}
|
||
else
|
||
{
|
||
calcTrade = td.Clone();
|
||
if (volType == "分红率0")
|
||
{
|
||
calcTrade.DividendRate = 0;
|
||
}
|
||
if (volType == "光证" || !calcTrade.NoRiskRate.HasValue)
|
||
{
|
||
calcTrade.NoRiskRate = riskFreeRate;
|
||
}
|
||
|
||
curValue = CalcLiveOtcTradeValue(calcTrade, volProvider, riskFreeRate, volType, CalcScenarioEnum.EodSettlement, out var underlyings);
|
||
|
||
if (curValue == null)
|
||
{
|
||
_context.RaiseError(_step, $"[{td.TradeType}]交易'{td.TradeNumber}'计算结果为空");
|
||
}
|
||
|
||
var eodRisks = GetEodRisks(td, curValue, underlyings);
|
||
|
||
_riskList.AddRange(eodRisks);
|
||
|
||
curPos = GetEodPosition(td, prePos, curValue, false, volType, useClosePrice);
|
||
curPos.UnderlyingPrice = _eodPriceProvider.GetPrice(calcTrade.UnderlyingCode, _useClosePrice ? td.SettlementType : SettlementTypeEnum.SettlePrice);
|
||
|
||
if (volType == "持仓" && _useClosePrice && curValue.ExtendInfo != null)
|
||
{
|
||
curPosSwap = new eod_trade_position_swap()
|
||
{
|
||
ValueDate = _context.SettleDate,
|
||
TradeId = td.id,
|
||
Commission = curValue.ExtendInfo.Commission,
|
||
QuoteCommission = curValue.ExtendInfo.QuoteCommission,
|
||
AnnualFee = curValue.ExtendInfo.AnnualFee,
|
||
QuoteAnnualFee = curValue.ExtendInfo.QuoteAnnualFee,
|
||
FloatingWinLoss = curValue.ExtendInfo.FloatingWinLoss,
|
||
QuoteFloatingWinLoss = curValue.ExtendInfo.QuoteFloatingWinLoss,
|
||
OptDate = DateTime.Now,
|
||
OptId = UserId,
|
||
OptName = UserName
|
||
};
|
||
}
|
||
|
||
if (td.TradeType == "合成价差期权")
|
||
{
|
||
var otherRisks = CalculateCrossGammasForSyntheticSpread(td, volProvider, riskFreeRate);
|
||
eodRisks[0].OtherRisks = JsonHelper.Serialize(otherRisks);
|
||
}
|
||
}
|
||
|
||
//盈亏分解(暂时已经了结的或者手动风险维护的交易不计算盈亏分解)
|
||
if (curValue != null && !curValue.FromManual && _context.SettlementConfig.CalcPnlExplain && CanCalcPnlExplain(calcTrade))
|
||
{
|
||
CalcPnlExplainV2(new CalcPnlExplainData
|
||
{
|
||
curPnl = curPnl,
|
||
curPos = curPos,
|
||
curValue = curValue,
|
||
prePos = prePos,
|
||
trade = calcTrade
|
||
});
|
||
}
|
||
}
|
||
catch (EodSettleException)
|
||
{
|
||
throw;
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
_context.RaiseError(_step, $"[{volType}][{td.TradeType}]交易'{td.TradeNumber}'计算出错:" + ex.Message, ex);
|
||
}
|
||
|
||
_pnlList.Add(curPnl);
|
||
_positionList.Add(curPos);
|
||
if (curPosSwap != null)
|
||
{
|
||
_positionSwapList.Add(curPosSwap);
|
||
}
|
||
|
||
sw.Stop();
|
||
|
||
if (sw.ElapsedMilliseconds > 1000)
|
||
{
|
||
_context.LogInfo($"[{volType}][{td.TradeType}]交易编号:{td.TradeNumber},计算用时{sw.ElapsedMilliseconds / 1000d}秒");
|
||
}
|
||
}
|
||
|
||
_outputDebugLog?.Invoke("####全部结束####");
|
||
}
|
||
|
||
//计算活着的场外衍生品交易价值和风险
|
||
private TradeValueResult CalcLiveOtcTradeValue(trade td, IEodVolProvider volProvider, double riskFreeRate, string volType, CalcScenarioEnum calcScenario, out underlying_manager[] underlyings)
|
||
{
|
||
var result = CalcLiveOtcTradeValueR(td, volProvider, riskFreeRate, volType, calcScenario, out underlyings);
|
||
|
||
if (result != null && PS.Config.ErpElement.IsPVIncludePrincipal)
|
||
{
|
||
result.Pv += td.PrincipalSum() * (td.BuySell == "卖出" ? -1 : 1);
|
||
result.RoundedPv += td.PrincipalSum() * (td.BuySell == "卖出" ? -1 : 1);
|
||
}
|
||
|
||
return result;
|
||
}
|
||
|
||
private TradeValueResult CalcLiveOtcTradeValueR(trade td, IEodVolProvider volProvider, double riskFreeRate, string volType, CalcScenarioEnum calcScenario, out underlying_manager[] underlyings)
|
||
{
|
||
underlyings = new underlying_manager[] { new underlying_manager { UnderlyingCode = td.UnderlyingCode } };
|
||
|
||
if (td.TradeType == ConsGlobal.TradeType.PayoffSwap)
|
||
{
|
||
return PayoffSwapCalcService.CalcValue(td, _context.SettleDate,
|
||
_eodPriceProvider.GetPriceProvider(_settlementType), true);
|
||
}
|
||
|
||
var spotPrice = _eodPriceProvider.GetPrice(td.UnderlyingCode, _useClosePrice ? td.SettlementType : SettlementTypeEnum.SettlePrice);
|
||
|
||
var optionResult = new TradeValueResult();
|
||
var lazyManual = new Lazy<(eod_trade_risk_manual manual, TradeValueResult optionValue)>(() =>
|
||
{
|
||
var isCustom = td.TradeType == "自定义交易";
|
||
var volValue = volProvider.GetVol(td, spotPrice) ?? ConsGlobal.DefaultVol;
|
||
var m = TradeRiskCalcUtil.GetManualOptionValue(_context.SettleDate, td, spotPrice, volValue, isCustom, calcScenario, volType, _useClosePrice ? td.SettlementType : SettlementTypeEnum.SettlePrice, isSettle: true);
|
||
//存在自定义风险但自定义风险中的波动率不存在则补充期权价值的波动率
|
||
if (m.manual != null && m.manual.Vol == null)
|
||
{
|
||
m.manual.Vol = volValue;
|
||
m.optionValue.Vol = volValue;
|
||
}
|
||
return m;
|
||
});
|
||
|
||
//没有自定义风险维护值 或者 自定风险维护没有涵盖PV和所有希腊值(部分维护场景)
|
||
//部分维护场景下,需要系统计算出未赋值的属性,进行合并返还
|
||
if (lazyManual.Value.manual == null || (lazyManual.Value.manual != null && !lazyManual.Value.manual.IsPVAndAllGreek))
|
||
{
|
||
switch (td.TradeType)
|
||
{
|
||
case ConsGlobal.TradeType.Custom:
|
||
{
|
||
var (manual, optionValue) = lazyManual.Value;
|
||
//收盘时如果自定义交易还活着且没有维护当日风险,并且收的时系统日期当日的盘,抛出exception
|
||
if (_context.IsCurrentDay && !PS.Config.Is润和 && (manual == null || manual.ValueDate != valuedateBLL.ValueDate) && !PS.Config.IsMustRiskManual)
|
||
{
|
||
var error = $"[{td.TradeType}]交易'{td.TradeNumber}'结算需先进行交易风险维护";
|
||
_context.RaiseError(_step, error);
|
||
}
|
||
|
||
optionResult = optionValue;
|
||
break;
|
||
}
|
||
case ConsGlobal.TradeType.Forward:
|
||
{
|
||
if (PS.Config.ErpElement.ForwardTradePriceModel == Configuration.Enums.ForwardTradePriceModel.STANDARD && !string.IsNullOrWhiteSpace(td.BasisUnderlyingCode))
|
||
{
|
||
var BasiseodPrice = _eodPriceProvider.GetPrice(td.BasisUnderlyingCode, _useClosePrice ? td.SettlementType : SettlementTypeEnum.SettlePrice);
|
||
spotPrice -= BasiseodPrice;
|
||
}
|
||
optionResult = ForwardradeCalcService.CalcValue(td, spotPrice);
|
||
break;
|
||
}
|
||
default:
|
||
{
|
||
var req = new OptionValueCalcRequest(riskFreeRate)
|
||
{
|
||
correlations = null,//不计算彩虹等多标的期权暂时不需要
|
||
engineName = null,
|
||
preciseTimeMode = false, //日终一定是false
|
||
isEodCalc = true,
|
||
pricingRequest = QdpPricingRequest.BASIC_GREEKS,
|
||
spotPrices = new[] { spotPrice },
|
||
calcScenario = Enums.CalcScenarioEnum.EodSettlement,
|
||
};
|
||
if (td.TradeType != ConsGlobal.TradeType.CashFlow)
|
||
{
|
||
var vol = volProvider.GetVol(td, spotPrice) ?? ConsGlobal.DefaultVol;
|
||
|
||
req.vols = new[] { vol };
|
||
}
|
||
optionResult = OptionCalculatorV2.GetOptionValueResult(_context.SettleDate, td, req, out underlyings);
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
|
||
if (lazyManual.Value.manual != null)
|
||
{
|
||
//系统计算结果和手动维护值合并
|
||
TradeRiskCalcUtil.GetOptionValueWithManual(lazyManual.Value.manual, optionResult);
|
||
}
|
||
|
||
return optionResult;
|
||
}
|
||
|
||
private TPosition GetEodPosition(trade td, TPosition lastPosition, TradeValueResult curValue, bool isFinished, string volType, bool useClosePrice)
|
||
{
|
||
var pv = NumberHelper.Normalize(curValue.Pv);
|
||
var roundedPv = NumberHelper.Normalize(curValue.RoundedPv);
|
||
|
||
TPosition pos = null;
|
||
|
||
//部分平仓计算
|
||
var tradeCashList = _context.TradeCashProvider.GetTradeCashes(td.id).Where(t => _tcActions.Contains(t.Action) || t.IsLastAction).ToArray();
|
||
|
||
//当日平仓费用
|
||
var unwindTradeCashList = tradeCashList.Where(t => t.Action != ClientCashInCashOut.系统操作_期权费 && ((t.HappenedDate == null && t.ValueDate == _context.SettleDate) || t.HappenedDate == _context.SettleDate)).ToArray();
|
||
|
||
//结算收益
|
||
var unWindProfit = unwindTradeCashList.Sum(t => (double?)t.Amount) ?? 0;
|
||
|
||
var unwindPercentRate = unwindTradeCashList.Where(t => t.Action == ClientCashInCashOut.系统操作_行权费 || t.Action == ClientCashInCashOut.系统操作_平仓费 || t.Action == ClientCashInCashOut.系统操作_互换 || t.IsLastAction)
|
||
.Sum(t => t.UnwindPercentRate ?? 0);
|
||
|
||
if (!PS.Config.ErpElement.IsPVIncludePrincipal && td.OriginalPrincipalSum > 0)
|
||
{
|
||
if (unwindPercentRate > 0)
|
||
{
|
||
unWindProfit -= unwindPercentRate * td.OriginalPrincipalSum.Value * TradeCalcHelper.GetSign(td.BuySell);
|
||
}
|
||
}
|
||
|
||
if (isFinished)
|
||
{
|
||
var totalCash = tradeCashList.Sum(t => (double?)t.Amount) ?? 0;
|
||
pos = GetFinishedOtcPosition(td, lastPosition, unWindProfit, unwindPercentRate, totalCash);
|
||
}
|
||
else
|
||
{
|
||
if (lastPosition == null)
|
||
{
|
||
pos = GetNewOtcPosition(td, pv, roundedPv, unWindProfit);
|
||
}
|
||
else
|
||
{
|
||
pos = GetOldOtcPosition(td, lastPosition, pv, roundedPv, unWindProfit, unwindPercentRate, volType, useClosePrice);
|
||
}
|
||
|
||
pos.PositionRelizedAmount = DbContext.trade_cash.Where(t => (t.Action == "系统操作-票息" || t.Action == "系统操作-互换") && t.ValidState != "InValid" && ((t.ValueDate <= _context.SettleDate && t.HappenedDate == null) || t.HappenedDate <= _context.SettleDate) && t.TradeId == td.id && t.IsLastAction != true)
|
||
.Sum(x => (double?)x.Amount) ?? 0;
|
||
}
|
||
|
||
return pos;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 已了结的场外衍生品交易结算(包括当日开仓当日了结的交易)
|
||
/// </summary>
|
||
private TPosition GetFinishedOtcPosition(trade td, TPosition lastPosition, double unWindProfit, double unwindPercentRate, double totalCash)
|
||
{
|
||
var lastPv = lastPosition?.Pv ?? 0;
|
||
|
||
var eodPos = GetEodPosition(td);
|
||
|
||
eodPos.Pv = 0;
|
||
eodPos.LastPv = lastPv;
|
||
eodPos.RoundedPv = 0;
|
||
|
||
eodPos.ClosedPnL = totalCash;
|
||
|
||
//当日新增交易
|
||
if (lastPosition == null)
|
||
{
|
||
eodPos.DailyPnL = totalCash;
|
||
}
|
||
else
|
||
{
|
||
//了结金额部分需要加上预付金,因为昨日持仓市值是包含预付金的
|
||
eodPos.DailyPnL = unWindProfit + (td.OriginalStockEqvNotional ?? 0) * unwindPercentRate * (td.trade_snowball?.PrepaymentRatio ?? 0) * (td.BuySell == "卖出" ? -1 : 1) - lastPv;
|
||
}
|
||
|
||
eodPos.TotalPnL = (double)totalCash;
|
||
|
||
return eodPos;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 新增的场外衍生品交易持仓结算
|
||
/// </summary>
|
||
private TPosition GetNewOtcPosition(trade td, double pv, double roundedPv, double unWindProfit)
|
||
{
|
||
var sign = TradeCalcHelper.GetSign(td.BuySell);
|
||
var removePrincipal = !PS.Config.ErpElement.IsPVIncludePrincipal && td.OriginalPrincipalSum > 0
|
||
? td.OriginalPrincipalSum.Value : 0;
|
||
|
||
var totalCost = ((td.TradePrice ?? 0) - removePrincipal) * sign;
|
||
//当前持仓成本
|
||
var positionCost = td.OriginalNotional > 0 ? td.Notional / td.OriginalNotional.Value * totalCost : 0;
|
||
|
||
var dailyPnl = 0d;
|
||
|
||
var eodPos = GetEodPosition(td);
|
||
|
||
dailyPnl = pv - td.StockEqvNotional * (td.trade_snowball?.PrepaymentRatio ?? 0) * (td.BuySell == "卖出" ? -1 : 1) - totalCost + unWindProfit;
|
||
|
||
if (td.TradeType == "自定义交易")
|
||
{
|
||
dailyPnl = pv - totalCost;
|
||
}
|
||
|
||
if (td.TradeType == "远期")
|
||
{
|
||
//远期交易日当天pv,pnl计算
|
||
using var db = DbContextFactory.GetYLDbContext();
|
||
var marginCost = db.eod_forward_margin.Where(x => x.ValueDate == _context.SettleDate && x.TradeId == td.id)
|
||
.Select(n => (double?)n.MarginCost).FirstOrDefault() ?? 0;
|
||
|
||
var unwindTradeCashList = db.trade_cash.Where(t => t.TradeId == td.id && t.ValidState != "InValid" && !t.IsDeleted && t.Action != ClientCashInCashOut.系统操作_期权费 && t.ValueDate == _context.SettleDate).ToList();
|
||
//平仓比例
|
||
var unwindRatio = unwindTradeCashList.Any() ? unwindTradeCashList.Sum(a => a.UnwindNotional.Value) / td.OriginalNotional.Value : 0;
|
||
//持仓比例
|
||
var positionRatio = 1 - unwindRatio;
|
||
//实现盈亏 = (交易已平仓) 开仓总费用 * 平仓比例 + 平仓总费用
|
||
//实现盈亏 = (交易未平仓) 0
|
||
var totalFee = unwindTradeCashList.Any() ? (td.TradePrice ?? 0) * unwindRatio + unwindTradeCashList.Sum(a => a.Amount) : 0;
|
||
totalCost = (td.TradePrice ?? 0) * positionRatio;
|
||
//持仓市值 = 交易员视角的合约总价值(远期合约价值+开仓费用+平仓费用)-交易员视角的已实现盈亏
|
||
eodPos.Pv = pv;
|
||
eodPos.LastPv = 0;
|
||
eodPos.RoundedPv = roundedPv;
|
||
dailyPnl = pv + totalCost + unWindProfit + marginCost;
|
||
//持仓盈亏 = 持仓市值+开仓总费用 * 持仓比例
|
||
eodPos.PositionPnL = eodPos.Pv + totalCost + marginCost;
|
||
eodPos.RoundedPositionPnL = eodPos.RoundedPv + totalCost + marginCost;
|
||
eodPos.ClosedPnL = totalFee;
|
||
eodPos.DailyPnL = dailyPnl;
|
||
eodPos.TotalPnL = (double)dailyPnl;
|
||
|
||
return eodPos;
|
||
}
|
||
|
||
eodPos.Pv = pv;
|
||
eodPos.LastPv = 0;
|
||
eodPos.RoundedPv = roundedPv;
|
||
|
||
eodPos.PositionPnL = pv - td.StockEqvNotional * (td.trade_snowball?.PrepaymentRatio ?? 0) * (td.BuySell == "卖出" ? -1 : 1) - positionCost;
|
||
eodPos.RoundedPositionPnL = roundedPv - td.StockEqvNotional * (td.trade_snowball?.PrepaymentRatio ?? 0) * (td.BuySell == "卖出" ? -1 : 1) - positionCost;
|
||
//平仓总额是交易员收支方向 成本是QDP方向() 这两个方向是反的,所以这里应该相减
|
||
eodPos.ClosedPnL = unWindProfit - (totalCost - positionCost);
|
||
|
||
eodPos.DailyPnL = dailyPnl;
|
||
eodPos.TotalPnL = (double)dailyPnl;
|
||
|
||
return eodPos;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 历史开仓未了结的场外衍生品交易持仓结算
|
||
/// </summary>
|
||
/// <param name="td"></param>
|
||
/// <param name="lastPosition">昨日持仓</param>
|
||
/// <param name="pv">当前市值</param>
|
||
/// <param name="roundedPv">四舍五入后的市值</param>
|
||
/// <param name="unWindProfit">平仓总额(带方向)</param>
|
||
/// <param name="volType"></param>
|
||
/// <returns></returns>
|
||
private TPosition GetOldOtcPosition(trade td, TPosition lastPosition, double pv, double roundedPv, double unWindProfit, double unwindPercentRate, string volType, bool useClosePrice)
|
||
{
|
||
var settlementType = useClosePrice ? SettlementTypeEnum.ClosePrice : SettlementTypeEnum.SettlePrice;
|
||
|
||
//前日总盈亏
|
||
var lastTotalPnl = lastPosition.TotalPnL;
|
||
var lastPv = lastPosition.Pv;
|
||
//了结金额部分需要加上预付金,因为昨日持仓市值是包含预付金的
|
||
var dailyPnl = pv - lastPv + (unWindProfit + (td.OriginalStockEqvNotional ?? 0) * unwindPercentRate * (td.trade_snowball?.PrepaymentRatio ?? 0) * (td.BuySell == "卖出" ? -1 : 1));
|
||
|
||
|
||
var sign = TradeCalcHelper.GetSign(td.BuySell);
|
||
var removePrincipal = !PS.Config.ErpElement.IsPVIncludePrincipal && td.OriginalPrincipalSum > 0
|
||
? td.OriginalPrincipalSum.Value : 0;
|
||
var totalCost = ((td.TradePrice ?? 0) - removePrincipal) * sign;//开仓成本
|
||
var positionCost = td.OriginalNotional > 0 ? td.Notional / td.OriginalNotional.Value * totalCost : 0; //当前持仓成本
|
||
|
||
var eodPos = GetEodPosition(td);
|
||
|
||
var manual = getManual(td, volType, settlementType, false);
|
||
if (manual != null && manual.Pv != null)
|
||
{
|
||
pv = manual.Pv.Value;
|
||
roundedPv = manual.Pv.Value;
|
||
dailyPnl = pv - lastPv + unWindProfit;
|
||
}
|
||
else if (td.TradeType == "自定义交易")
|
||
{
|
||
//自定义交易获取最近的一次维护数据
|
||
if (DbContext.eod_trade_risk_manual.Where(x => x.TradeId == td.id && x.ValueDate <= _context.SettleDate).Any())
|
||
{
|
||
manual = getManual(td, volType, settlementType, true);
|
||
|
||
if (manual != null)
|
||
{
|
||
pv = manual.Pv ?? 0;
|
||
roundedPv = manual.Pv ?? 0;
|
||
}
|
||
}
|
||
dailyPnl = pv - lastPv + unWindProfit;
|
||
}
|
||
|
||
if (td.TradeType == "远期")
|
||
{
|
||
using var db = DbContextFactory.GetYLDbContext();
|
||
var marginCost = DbContext.eod_forward_margin.Where(x => x.ValueDate == _context.SettleDate && x.TradeId == td.id)
|
||
.Select(n => (double?)n.MarginCost).FirstOrDefault() ?? 0;
|
||
dailyPnl += marginCost;
|
||
|
||
var unwindTradeCashList = db.trade_cash.Where(t => t.TradeId == td.id && t.ValidState != "InValid" && !t.IsDeleted && t.Action != ClientCashInCashOut.系统操作_期权费 && t.ValueDate == _context.SettleDate).ToList();
|
||
//平仓比例
|
||
var unwindRatio = unwindTradeCashList.Any() ? unwindTradeCashList.Sum(a => a.UnwindNotional.Value) / td.OriginalNotional.Value : 0;
|
||
//持仓比例
|
||
var positionRatio = 1 - unwindRatio;
|
||
//实现盈亏 = (交易已平仓) 开仓总费用 * 平仓比例 + 平仓总费用
|
||
//实现盈亏 = (交易未平仓) 0
|
||
var totalFee = unwindTradeCashList.Any() ? (td.TradePrice ?? 0) * unwindRatio + unwindTradeCashList.Sum(a => a.Amount) : 0;
|
||
//持仓市值 = 交易员视角的合约总价值(远期合约价值)
|
||
eodPos.Pv = pv;
|
||
eodPos.LastPv = 0;
|
||
eodPos.RoundedPv = roundedPv;
|
||
//持仓盈亏 = 持仓市值+开仓总费用 * 开仓比例
|
||
eodPos.PositionPnL = eodPos.Pv + ((td.TradePrice ?? 0) * positionRatio) + marginCost;
|
||
eodPos.RoundedPositionPnL = eodPos.RoundedPv + ((td.TradePrice ?? 0) * positionRatio) + marginCost;
|
||
eodPos.ClosedPnL = totalFee;
|
||
eodPos.DailyPnL = dailyPnl;
|
||
eodPos.TotalPnL = lastTotalPnl + (double)dailyPnl;
|
||
|
||
return eodPos;
|
||
}
|
||
|
||
eodPos.Pv = pv;
|
||
eodPos.LastPv = lastPv;
|
||
eodPos.RoundedPv = roundedPv;
|
||
//平仓总额是交易员方向 成本是QDP方向 这两个方向是反的,所以这里应该相减
|
||
eodPos.ClosedPnL = lastPosition.ClosedPnL + (unWindProfit - totalCost * unwindPercentRate);
|
||
eodPos.PositionPnL = pv - td.StockEqvNotional * (td.trade_snowball?.PrepaymentRatio ?? 0) * (td.BuySell == "卖出" ? -1 : 1) - positionCost;
|
||
eodPos.RoundedPositionPnL = roundedPv - td.StockEqvNotional * (td.trade_snowball?.PrepaymentRatio ?? 0) * (td.BuySell == "卖出" ? -1 : 1) - positionCost;
|
||
|
||
eodPos.DailyPnL = dailyPnl;
|
||
eodPos.TotalPnL = lastTotalPnl + (double)dailyPnl;
|
||
|
||
return eodPos;
|
||
}
|
||
|
||
private eod_trade_risk_manual getManual(trade td, string volType, SettlementTypeEnum settlementType, bool isCustomerTrade)
|
||
{
|
||
var maxDate = new DateTime();
|
||
if (isCustomerTrade)
|
||
{
|
||
maxDate = DbContext.eod_trade_risk_manual.Where(x => x.TradeId == td.id && x.ValueDate <= _context.SettleDate).Max(x => x.ValueDate);
|
||
}
|
||
else
|
||
{
|
||
maxDate = _context.SettleDate;
|
||
}
|
||
|
||
//分类维护的风险数据优先级高于普通的风险数据
|
||
var manual = DbContext.eod_trade_risk_manual.Where(x => x.TradeId == td.id && x.ValueDate == maxDate && x.VolType == volType && x.SettlementType == settlementType).FirstOrDefault();
|
||
if (manual == null)
|
||
{
|
||
manual = DbContext.eod_trade_risk_manual.Where(x => x.TradeId == td.id && x.ValueDate == maxDate && string.IsNullOrEmpty(x.VolType)).FirstOrDefault();
|
||
}
|
||
else
|
||
{
|
||
//如果分类维护的数据不全,通过单一数据补充
|
||
var singleManual = DbContext.eod_trade_risk_manual.Where(x => x.TradeId == td.id && x.ValueDate == maxDate && string.IsNullOrEmpty(x.VolType)).FirstOrDefault();
|
||
if (singleManual != null)
|
||
{
|
||
manual.Pv ??= singleManual.Pv;
|
||
}
|
||
}
|
||
|
||
return manual;
|
||
}
|
||
|
||
private TPosition GetEodPosition(trade td)
|
||
{
|
||
return new TPosition
|
||
{
|
||
ValueDate = _context.SettleDate,
|
||
|
||
TradeId = td.id,
|
||
BookId = td.AssetId,
|
||
ClientId = td.ClientId,
|
||
TradeType = td.TradeType,
|
||
UnderlyingId = td.UnderlyingId,
|
||
UnderlyingCode = td.UnderlyingCode,
|
||
Amount = td.Notional,
|
||
BuySell = td.BuySell,
|
||
Cost = (td.TradeSinglePrice ?? 0) * td.Notional,
|
||
ParentTradeId = td.ParentTradeId,
|
||
|
||
OptId = UserId,
|
||
OptName = UserName,
|
||
OptDate = DateTime.Now,
|
||
|
||
Margin = 0,
|
||
Commission = 0,
|
||
|
||
Pv = 0,
|
||
LastPv = 0,
|
||
RoundedPv = 0,
|
||
|
||
ClosedPnL = 0,
|
||
PositionPnL = 0,
|
||
RoundedPositionPnL = 0,
|
||
|
||
DailyPnL = 0,
|
||
TotalPnL = 0,
|
||
|
||
//下面这三个为了兼容历史数据不能赋Null或不赋值
|
||
PositionType = string.Empty,
|
||
ExchangeOptionCode = string.Empty,
|
||
HedgeUniqueCode = string.Empty,
|
||
|
||
StructureType = td.StructureType,
|
||
|
||
PositionRelizedAmount = 0
|
||
};
|
||
}
|
||
|
||
private TPnl GetEodPnl(trade td)
|
||
{
|
||
return new TPnl
|
||
{
|
||
ValueDate = _context.SettleDate,
|
||
|
||
TradeId = td.id,
|
||
|
||
PnLPriceResidual = 0,
|
||
PnLVolResidual = 0,
|
||
PnLRho = 0,
|
||
PnLPrice = 0,
|
||
PnLDelta = 0,
|
||
PnLGamma = 0,
|
||
PnLVega = 0,
|
||
PnLTheta = 0,
|
||
PnLPriceVolCross = 0,
|
||
PnLVol = 0,
|
||
UnexplainedPnL = 0,
|
||
ExplainedPnL = 0,
|
||
|
||
OptId = UserId,
|
||
OptName = UserName,
|
||
OptDate = DateTime.Now,
|
||
|
||
HedgeUniqueCode = ConsTrade.TradeTypesForHedge.Contains(td.TradeType)
|
||
? HedgePnlCalc.GetHedgeUniqueCode(td.AssetId, td.TradeType, TradeHelper.GetPositionType(td.BuySell).ToString(), td.UnderlyingCode, td.ExchangeOptionCode)
|
||
: string.Empty
|
||
};
|
||
}
|
||
|
||
private TRisk[] GetEodRisks(trade td, TradeValueResult vr, underlying_manager[] underlyings)
|
||
{
|
||
var creditExposure = 0d;
|
||
|
||
try
|
||
{
|
||
creditExposure = EodPositionRisksQueryService.ExposureCalc(td,
|
||
_eodPriceProvider.GetPrice(td.UnderlyingCode, _useClosePrice ? td.SettlementType : SettlementTypeEnum.SettlePrice), _context.SettleDate);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
_context.RaiseError(_step, $"[{td.TradeType}交易'{td.TradeNumber}']计算风险敞口出错:" + ex.Message, ex);
|
||
}
|
||
|
||
var risk = new TRisk
|
||
{
|
||
ValueDate = _context.SettleDate,
|
||
TradeId = td.id,
|
||
BookId = td.AssetId,
|
||
ClientId = td.ClientId,
|
||
OptId = UserId,
|
||
OptName = UserName,
|
||
OptDate = DateTime.Now,
|
||
Exposure = td.UnderlyingCode,
|
||
CreditExposure = NumberHelper.Normalize(creditExposure),
|
||
HedgeUniqueCode = string.Empty
|
||
};
|
||
|
||
if (vr == null || underlyings == null)
|
||
{
|
||
return new TRisk[] { risk };
|
||
}
|
||
|
||
var risks = new TRisk[underlyings.Length];
|
||
|
||
for (var i = 0; i < risks.Length; i++)
|
||
{
|
||
risks[i] = risk = (TRisk)risk.Clone();
|
||
|
||
risk.Exposure = underlyings[i]?.UnderlyingCode;
|
||
|
||
risk.Pv = NumberHelper.Normalize(vr.Pv);
|
||
risk.Theta = NumberHelper.Normalize(vr.Theta);
|
||
risk.Rho = NumberHelper.Normalize(vr.Rho);
|
||
risk.Delta = NumberHelper.Normalize(vr.GetDelta(i));
|
||
risk.Gamma = NumberHelper.Normalize(vr.GetGamma(i));
|
||
risk.Vega = NumberHelper.Normalize(vr.GetVega(i));
|
||
risk.DeltaCash = NumberHelper.Normalize(vr.GetDeltaCash(i));
|
||
risk.GammaCash = NumberHelper.Normalize(vr.GetGammaCash(i));
|
||
risk.Vol = NumberHelper.Normalize(vr.Vol);
|
||
}
|
||
|
||
return risks;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 在多交易日历的情况下如果为节假日则拷贝前值
|
||
/// </summary>
|
||
private void CopyPreValues(TPosition prePos, IEnumerable<int> allPreTradeIds, bool copyPosSwap)
|
||
{
|
||
if (_preOtcDic == null)
|
||
{
|
||
_preOtcDic = new Dictionary<int, PreSettleData>();
|
||
|
||
var preRisks = DbContext.Set<TRisk>().AsNoTracking().Where(n => n.ValueDate == _context.PreSettleDate && allPreTradeIds.Contains(n.TradeId)).ToArray();
|
||
foreach (var risk in preRisks)
|
||
{
|
||
if (!_preOtcDic.TryGetValue(risk.TradeId, out var pre))
|
||
{
|
||
_preOtcDic[risk.TradeId] = pre = new PreSettleData();
|
||
}
|
||
pre.AddRisk(risk);
|
||
}
|
||
|
||
var prePnls = DbContext.Set<TPnl>().AsNoTracking().Where(n => n.ValueDate == _context.PreSettleDate && allPreTradeIds.Contains(n.TradeId)).ToArray();
|
||
foreach (var pnl in prePnls)
|
||
{
|
||
if (!_preOtcDic.TryGetValue(pnl.TradeId, out var pre))
|
||
{
|
||
_preOtcDic[pnl.TradeId] = pre = new PreSettleData();
|
||
}
|
||
pre.Pnl = pnl;
|
||
}
|
||
|
||
if (copyPosSwap)
|
||
{
|
||
var prePosSwaps = DbContext.Set<eod_trade_position_swap>().AsNoTracking().Where(n => n.ValueDate == _context.PreSettleDate && allPreTradeIds.Contains(n.TradeId)).ToArray();
|
||
foreach (var posSwap in prePosSwaps)
|
||
{
|
||
if (!_preOtcDic.TryGetValue(posSwap.TradeId, out var pre))
|
||
{
|
||
_preOtcDic[posSwap.TradeId] = pre = new PreSettleData { PosSwap = posSwap };
|
||
}
|
||
pre.PosSwap = posSwap;
|
||
}
|
||
}
|
||
}
|
||
|
||
var curPos = (TPosition)prePos.Clone();
|
||
curPos.id = 0;
|
||
curPos.ValueDate = _context.SettleDate;
|
||
_positionList.Add(curPos);
|
||
|
||
if (_preOtcDic.TryGetValue(prePos.TradeId, out var pre2))
|
||
{
|
||
foreach (var risk in pre2.Risks)
|
||
{
|
||
var curRisk = (TRisk)risk.Clone();
|
||
curRisk.id = 0;
|
||
curRisk.ValueDate = _context.SettleDate;
|
||
_riskList.Add(curRisk);
|
||
}
|
||
|
||
if (pre2.Pnl != null)
|
||
{
|
||
var curPnl = (TPnl)pre2.Pnl.Clone();
|
||
curPnl.id = 0;
|
||
curPnl.ValueDate = _context.SettleDate;
|
||
_pnlList.Add(curPnl);
|
||
}
|
||
|
||
if (copyPosSwap && pre2.PosSwap != null)
|
||
{
|
||
var curPosSwap = pre2.PosSwap.Clone();
|
||
curPosSwap.id = 0;
|
||
curPosSwap.ValueDate = _context.SettleDate;
|
||
_positionSwapList.Add(curPosSwap);
|
||
}
|
||
}
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region----对冲交易盈亏----
|
||
|
||
private void InnerExecuteHedgeTrades(string volType, bool useClosePrice, IEnumerable<TPosition> prePositions)
|
||
{
|
||
var _volType = volType;
|
||
volType = volType == "持仓" ? _context.SystemValue.EodSettleVolMode.TrimToNull() ?? ConsVolInfos.财务 : "对冲";
|
||
|
||
//首先从static表同步的类型,如果static表未同步成功再进行合计持仓
|
||
if (!SyncStaticHedgePosition(volType) && _context.Request.IsSettleExchangeTrades)
|
||
{
|
||
//非期权交易 股票、商品期货等持仓处理 场内期权按对冲交易处理
|
||
prePositions = prePositions.Where(IsLiveHedgePosition).ToList();
|
||
|
||
//当日新增交易
|
||
var newTrades = DbContext.ExchangeTrade.AsNoTracking().Where(t => t.TradeDate == _context.SettleDate && t.IsValid).ToList();
|
||
|
||
var hedgePnlList = GetHedgePnlCalc(volType).Calculate(newTrades, prePositions);
|
||
|
||
foreach (var hedgePnl in hedgePnlList)
|
||
{
|
||
if (_context.SettlementConfig.CheckExchangeClose && (hedgePnl.PositionType == "long" ? hedgePnl.Notional < 0 : hedgePnl.Notional > 0))
|
||
{
|
||
_context.RaiseError(_step, $"[{hedgePnl.TradeType}交易-{hedgePnl.UnderlyingCode}]平仓数量超出开仓数量");
|
||
}
|
||
if (PS.Config.ErpElement.AutoCloseExpiriedExchangeOptionPosition && useClosePrice && _volType == "持仓")
|
||
{
|
||
GetOptionHedgePnlCalc(_volType).Calculate(hedgePnl, _context.UserInfo);
|
||
}
|
||
var pos = GetEodPosition(hedgePnl);
|
||
pos.UnderlyingPrice = _eodPriceProvider.GetPrice(hedgePnl.UnderlyingCode, SettlementTypeEnum.ClosePrice);
|
||
|
||
_positionList.Add(pos);
|
||
|
||
if (hedgePnl.TradeType == "场内期权")
|
||
{
|
||
var risk = GetEodRisk(hedgePnl);
|
||
_riskList.Add(risk);
|
||
|
||
//盈亏分解
|
||
if (_context.SettlementConfig.CalcPnlExplain)
|
||
{
|
||
TPnl curPnl = null;
|
||
var sysRiskFreeRate = _context.SystemValue.RiskFreeRate / 100;
|
||
var exchangeOption = DataCacheProvider.GetExchangeListOptionDataSource().GetData(hedgePnl.ExchangeOptionCode);
|
||
if (exchangeOption == null || exchangeOption.MaturityDate < _context.SettleDate)
|
||
{
|
||
continue;
|
||
}
|
||
var tempUm = DataCacheProvider.GetUnderlyingDataSource().GetData(hedgePnl.UnderlyingCode);
|
||
var prePos =
|
||
prePositions.Where(O =>
|
||
HedgePnlCalc.GetHedgeUniqueCode(O.BookId, O.TradeType, O.PositionType, O.UnderlyingCode, O.ExchangeOptionCode) ==
|
||
HedgePnlCalc.GetHedgeUniqueCode(hedgePnl.BookId, hedgePnl.TradeType, hedgePnl.PositionType, hedgePnl.UnderlyingCode, hedgePnl.ExchangeOptionCode)
|
||
).FirstOrDefault();
|
||
if (prePos != null)
|
||
{
|
||
var tempTrade = new trade
|
||
{
|
||
TradeType = hedgePnl.TradeType,
|
||
UnderlyingCode = hedgePnl.UnderlyingCode,
|
||
UnderlyingId = hedgePnl.UnderlyingId,
|
||
TradeDate = hedgePnl.ValueDate,
|
||
BuySell = hedgePnl.BuySell,
|
||
StartDate = hedgePnl.ValueDate,
|
||
ExerciseDate = exchangeOption.MaturityDate,
|
||
MaturityDate = tempUm.MaturityDate,
|
||
TradePrice = Math.Abs(prePos.Cost),
|
||
TradeStatus = "确认成交",
|
||
ExerciseMode = exchangeOption.ExerciseMode,
|
||
OptionType = exchangeOption.OptionType,
|
||
Strike = exchangeOption.Strike,
|
||
Notional = prePos.Amount,
|
||
UnderlyingInstrumentType = tempUm.UnderlyingInstrumentType,
|
||
ExchangeOptionCode = hedgePnl.ExchangeOptionCode,
|
||
AssetId = hedgePnl.BookId,
|
||
id = 0,
|
||
UnderlyingAssetClass = tempUm.UnderlyingType,
|
||
NoRiskRate = sysRiskFreeRate,
|
||
DividendRate = tempUm.DividendRate ?? sysRiskFreeRate,
|
||
};
|
||
curPnl = GetEodPnl(tempTrade);
|
||
_pnlList.Add(curPnl);
|
||
//之所以要再算一遍,是因为需要用昨天的持仓成本和数量计算
|
||
var tempRisk = CalcPnlExplainV2(tempTrade, new CalcField
|
||
{
|
||
spot = _eodPriceProvider.GetPrice(pos.UnderlyingCode, _settlementType),
|
||
vol = hedgePnl.Vol,
|
||
dividendRate = tempTrade.DividendRate ?? 0,
|
||
riskFreeRate = tempTrade.NoRiskRate ?? 0,
|
||
date = hedgePnl.ValueDate
|
||
}, PricingRequest.Pv);
|
||
|
||
CalcPnlExplainV2_OldExchangeTrade(new CalcPnlExplainData
|
||
{
|
||
curPnl = curPnl,
|
||
curPos = prePos,
|
||
curValue = tempRisk,
|
||
prePos = prePos,
|
||
|
||
trade = tempTrade
|
||
});
|
||
}
|
||
|
||
var newExchangeTrades = newTrades.Where(O => HedgePnlCalc.GetHedgeUniqueCode(O.AssetBookId, O.TradeType, TradeHelper.GetPositionType(O.TradeSide).ToString(), O.UnderlyingCode, O.OptionCode) == hedgePnl.HedgeUniqueCode);
|
||
foreach (var item in newExchangeTrades)
|
||
{
|
||
var tempTrade = new trade
|
||
{
|
||
TradeType = hedgePnl.TradeType,
|
||
UnderlyingCode = hedgePnl.UnderlyingCode,
|
||
UnderlyingId = hedgePnl.UnderlyingId,
|
||
TradeDate = hedgePnl.ValueDate,
|
||
BuySell = hedgePnl.BuySell,
|
||
StartDate = hedgePnl.ValueDate,
|
||
ExerciseDate = exchangeOption.MaturityDate,
|
||
MaturityDate = tempUm.MaturityDate,
|
||
TradeSinglePrice = item.TradeSinglePrice,
|
||
TradePrice = item.TradeSinglePrice * item.Notional,
|
||
TradeStatus = "确认成交",
|
||
ExerciseMode = exchangeOption.ExerciseMode,
|
||
OptionType = exchangeOption.OptionType,
|
||
SpotPrice = _eodPriceProvider.GetPrice(pos.UnderlyingCode, _settlementType),
|
||
Strike = exchangeOption.Strike,
|
||
Notional = item.Notional,
|
||
UnderlyingInstrumentType = tempUm.UnderlyingInstrumentType,
|
||
ExchangeOptionCode = hedgePnl.ExchangeOptionCode,
|
||
AssetId = hedgePnl.BookId,
|
||
id = 0,
|
||
UnderlyingAssetClass = tempUm.UnderlyingType,
|
||
NoRiskRate = sysRiskFreeRate,
|
||
DividendRate = tempUm.DividendRate ?? sysRiskFreeRate,
|
||
};
|
||
if (curPnl == null)
|
||
{
|
||
curPnl = GetEodPnl(tempTrade);
|
||
_pnlList.Add(curPnl);
|
||
}
|
||
var tempRisk = CalcPnlExplainV2(tempTrade, new CalcField
|
||
{
|
||
spot = _eodPriceProvider.GetPrice(pos.UnderlyingCode, _settlementType),
|
||
vol = hedgePnl.Vol,
|
||
dividendRate = tempTrade.DividendRate ?? 0,
|
||
riskFreeRate = tempTrade.NoRiskRate ?? 0,
|
||
date = hedgePnl.ValueDate
|
||
}, PricingRequest.Pv);
|
||
|
||
CalcPnlExplainV2_NewExchangeTrade(new CalcPnlExplainData
|
||
{
|
||
curPnl = curPnl,
|
||
curPos = prePos,
|
||
curValue = tempRisk,
|
||
prePos = prePos,
|
||
|
||
trade = tempTrade
|
||
});
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
private bool IsLiveHedgePosition(TPosition pos)
|
||
{
|
||
if (pos.TradeType == "场内期权")
|
||
{
|
||
var exoption = DataCacheProvider.GetExchangeListOptionDataSource().GetData(pos.ExchangeOptionCode);
|
||
if (exoption == null)
|
||
{
|
||
throw new EodPnlExplainerException("找不到场内期权信息:" + pos.ExchangeOptionCode);
|
||
}
|
||
if (exoption.MaturityDate < _context.SettleDate)
|
||
{
|
||
return false;
|
||
}
|
||
}
|
||
else if (pos.TradeType == "商品期货")
|
||
{
|
||
var un = DataCacheProvider.GetUnderlyingDataSource().GetData(pos.UnderlyingCode);
|
||
if (un == null)
|
||
{
|
||
throw new EodPnlExplainerException("找不到商品期货标的信息:" + pos.ExchangeOptionCode);
|
||
}
|
||
if (un.MaturityDate.HasValue && un.MaturityDate.Value < _context.SettleDate)
|
||
{
|
||
return false;
|
||
}
|
||
}
|
||
else if (!(ConsTrade.TradeTypesForHedge.Contains(pos.TradeType)||ConsTrade.BondTypeList.Contains(pos.TradeType)))
|
||
{
|
||
return false;
|
||
}
|
||
|
||
return true;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 同步对冲持仓
|
||
/// </summary>
|
||
private bool SyncStaticHedgePosition(string volType)
|
||
{
|
||
var staticPositions = DbContext.eod_trade_position_static.Where(t => t.ValueDate == _context.SettleDate).ToList();
|
||
|
||
if (!staticPositions.Any())
|
||
{
|
||
return false;
|
||
}
|
||
|
||
var tPositions = staticPositions.Select(t =>
|
||
{
|
||
var um = DataCacheProvider.GetUnderlyingDataSource().GetData(t.UnderlyingCode);
|
||
if (um == null)
|
||
{
|
||
throw new Exception($"未找到标的信息:{t.UnderlyingCode}!");
|
||
}
|
||
|
||
if (!_eodPriceProvider.TryGetPrice(t.UnderlyingCode, _settlementType, out var price))
|
||
{
|
||
throw new Exception($"标的:{t.UnderlyingCode}在{_context.SettleDate:yyyy-MM-dd}价格不存在!");
|
||
}
|
||
|
||
var PositionPv = t.Notional * price;
|
||
var PositionPnL = t.PositionPnL;
|
||
var Pv = t.PositionType == "long" ? PositionPv : -PositionPv;
|
||
|
||
var data = new TPosition
|
||
{
|
||
ValueDate = t.ValueDate,
|
||
TradeType = t.TradeType,
|
||
BookId = t.AssetBookId,
|
||
UnderlyingId = um.id,
|
||
UnderlyingCode = t.UnderlyingCode,
|
||
PositionType = t.PositionType,
|
||
Cost = 0,
|
||
Amount = Math.Abs(t.Notional) * (t.PositionType == "long" ? 1 : -1),
|
||
Pv = Pv,
|
||
RoundedPv = Pv,
|
||
DailyPnL = PositionPnL,
|
||
TotalPnL = t.PositionPnL,
|
||
PositionPnL = PositionPnL,
|
||
RoundedPositionPnL = PositionPnL,
|
||
OptDate = DateTime.Now,
|
||
OptId = UserId,
|
||
OptName = UserName,
|
||
Commission = 0,
|
||
HedgeUniqueCode = HedgePnlCalc.GetHedgeUniqueCode(t.AssetBookId, t.TradeType, t.PositionType, t.UnderlyingCode, t.OptionCode),
|
||
ExchangeOptionCode = t.OptionCode
|
||
};
|
||
data.Cost = data.Amount * t.AveragePrice;
|
||
return data;
|
||
|
||
}).ToList();
|
||
|
||
_positionList.AddRange(tPositions.Where(n => n.TradeType != "场内期权"));
|
||
|
||
//场内期权现算Pv
|
||
|
||
var exPostions = tPositions.Where(p => p.TradeType == "场内期权").ToList();
|
||
|
||
if (exPostions?.Count > 0)
|
||
{
|
||
var calc = GetHedgePnlCalc(volType);
|
||
|
||
var hedgePnls = calc.Calculate(null, exPostions);
|
||
|
||
foreach (var hedgePnl in hedgePnls)
|
||
{
|
||
var pos = GetEodPosition(hedgePnl);
|
||
var risk = GetEodRisk(hedgePnl);
|
||
|
||
_positionList.Add(pos);
|
||
_riskList.Add(risk);
|
||
}
|
||
}
|
||
|
||
return true;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取对冲PNL计算提供类
|
||
/// </summary>
|
||
private HedgePnlCalc GetHedgePnlCalc(string volType)
|
||
{
|
||
//获取场内期权结算价提供
|
||
var exchangeOptionProvider = new EodExchangeOptionPriceProvider(_context.SettleDate, _useClosePrice);
|
||
|
||
return new HedgePnlCalcContext(CalcScenarioEnum.EodSettlement, _context.SettleDate,
|
||
volType, true, _eodPriceProvider.GetPriceProvider(_settlementType)
|
||
, _eodPriceProvider.GetPriceProvider(SettlementTypeEnum.SettlePrice), exchangeOptionProvider, OptUser)
|
||
{
|
||
ExchangeOptionPriceUseFlag = PS.Config.ErpElement.ExchangeOptionSettleByPrice
|
||
? ExchangeOptionPriceUseFlag.CalcPv : ExchangeOptionPriceUseFlag.None
|
||
}.GetHedgePnlCalc();
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取对冲PNL计算提供类
|
||
/// </summary>
|
||
private MaturityOptionHedgePnlCalc GetOptionHedgePnlCalc(string volType)
|
||
{
|
||
//获取场内期权结算价提供
|
||
var exchangeOptionProvider = new EodExchangeOptionPriceProvider(_context.SettleDate, _useClosePrice);
|
||
|
||
return new HedgePnlCalcContext(Enums.CalcScenarioEnum.EodSettlement, _context.SettleDate,
|
||
volType, true, _eodPriceProvider.GetPriceProvider(_settlementType),
|
||
_eodPriceProvider.GetPriceProvider(SettlementTypeEnum.SettlePrice), exchangeOptionProvider, OptUser)
|
||
{
|
||
ExchangeOptionPriceUseFlag = PS.Config.ErpElement.ExchangeOptionSettleByPrice
|
||
? ExchangeOptionPriceUseFlag.CalcPv : ExchangeOptionPriceUseFlag.None
|
||
}.GetOptionHedgePnlCalc();
|
||
}
|
||
|
||
private TPosition GetEodPosition(HedgePnl hedgePnl)
|
||
{
|
||
var cost = (double)hedgePnl.Cost;
|
||
var notional = (double)hedgePnl.Notional;
|
||
var positionPnL = hedgePnl.Pv - hedgePnl.Cost;
|
||
|
||
return new TPosition
|
||
{
|
||
ValueDate = _context.SettleDate,
|
||
TradeType = hedgePnl.TradeType,
|
||
BookId = hedgePnl.BookId,
|
||
UnderlyingId = hedgePnl.UnderlyingId,
|
||
UnderlyingCode = hedgePnl.UnderlyingCode,
|
||
BuySell = hedgePnl.BuySell,
|
||
PositionType = hedgePnl.PositionType,
|
||
Cost = cost,
|
||
Amount = notional,
|
||
LastPv = hedgePnl.LastPv,
|
||
Pv = hedgePnl.Pv,
|
||
RoundedPv = hedgePnl.Pv,
|
||
DailyPnL = hedgePnl.DailyPnL,
|
||
TotalPnL = (double)hedgePnl.TotalPnl,
|
||
PositionPnL = positionPnL,
|
||
RoundedPositionPnL = positionPnL,
|
||
OptDate = DateTime.Now,
|
||
OptId = OptUser.UserId,
|
||
OptName = OptUser.UserName,
|
||
HedgeUniqueCode = hedgePnl.HedgeUniqueCode,
|
||
ExchangeOptionCode = hedgePnl.ExchangeOptionCode,
|
||
Commission = hedgePnl.Commission,
|
||
Margin = 0,
|
||
TradeId = 0,
|
||
ParentTradeId = 0,
|
||
ClientId = 0,
|
||
ClosedPnL = hedgePnl.RealizedPnL
|
||
};
|
||
}
|
||
|
||
private TRisk GetEodRisk(HedgePnl hedgePnl)
|
||
{
|
||
return new TRisk()
|
||
{
|
||
Pv = hedgePnl.Pv,
|
||
Delta = NumberHelper.Normalize(hedgePnl.Delta),
|
||
DeltaCash = NumberHelper.Normalize(hedgePnl.DeltaCash),
|
||
Gamma = NumberHelper.Normalize(hedgePnl.Gamma),
|
||
GammaCash = NumberHelper.Normalize(hedgePnl.GammaCash),
|
||
Vega = NumberHelper.Normalize(hedgePnl.Vega),
|
||
Theta = NumberHelper.Normalize(hedgePnl.Theta),
|
||
Rho = NumberHelper.Normalize(hedgePnl.Rho),
|
||
Vol = NumberHelper.Normalize(hedgePnl.Vol),
|
||
HedgeUniqueCode = hedgePnl.HedgeUniqueCode,
|
||
ValueDate = _context.SettleDate,
|
||
OptId = UserId,
|
||
OptName = UserName,
|
||
OptDate = DateTime.Now,
|
||
BookId = hedgePnl.BookId,
|
||
Exposure = hedgePnl.UnderlyingCode
|
||
};
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region----其他风险指标----
|
||
|
||
/// <summary>
|
||
/// 合成价差期权的Cross Gamma
|
||
/// </summary>
|
||
private OtherRisks CalculateCrossGammasForSyntheticSpread(trade td, IEodVolProvider volProvider, double riskFreeRate)
|
||
{
|
||
try
|
||
{
|
||
var syntheticUnderlying = DataCacheProvider.GetUnderlyingDataSource().GetSyntheticUnderlying(td.UnderlyingCode);
|
||
var coefficients = GetCoefficients(syntheticUnderlying);
|
||
if (coefficients.Count < 2)
|
||
{
|
||
LogFactory.GetLogger("EodRisk").Error($"不需要计算出CrossGamma,因为组合标的只包含一个基础标的,交易编号:{td.TradeNumber},id:{td.id},标的:{td.UnderlyingCode}");
|
||
return null;
|
||
}
|
||
|
||
var spot = _eodPriceProvider.GetPrice(td.UnderlyingCode, _useClosePrice ? td.SettlementType : SettlementTypeEnum.SettlePrice);
|
||
var vol = volProvider.GetVol(td, spot) ?? ConsGlobal.DefaultVol;
|
||
|
||
var request = new OptionValueCalcRequest(riskFreeRate)
|
||
{
|
||
vols = new[] { vol },
|
||
spotPrices = new[] { spot },
|
||
calcScenario = Enums.CalcScenarioEnum.EodSettlement
|
||
};
|
||
|
||
var crossGammas = OptionCalculatorV2.CalcSSpreadCrossGammas(_context.SettleDate, td, request, coefficients.ToArray());
|
||
|
||
if (crossGammas == null)
|
||
{
|
||
LogFactory.GetLogger("EodRisk").Error($"未能计算出CrossGamma,交易编号:{td.TradeNumber},id:{td.id},标的:{td.UnderlyingCode}");
|
||
return null;
|
||
}
|
||
|
||
return new OtherRisks
|
||
{
|
||
CrossGamma = string.Join(",", crossGammas.Select(n => n.OtcFormat(OtcFormatFlag.greek)))
|
||
};
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
LogFactory.GetLogger<EodOperationBase>().Error("扩展风险指标计算错误:" + ex.ToString());
|
||
return null;
|
||
}
|
||
}
|
||
|
||
private static List<double> GetCoefficients(SyntheticUnderlying underlying)
|
||
{
|
||
if (underlying == null || underlying.Coefficient1 == null)
|
||
{
|
||
return new List<double>();
|
||
}
|
||
var coefficients = new List<double>(4) { underlying.Coefficient1.Value };
|
||
|
||
if (underlying.Coefficient2.HasValue && !string.IsNullOrWhiteSpace(underlying.UnderlyingCode2))
|
||
{
|
||
coefficients.Add(underlying.Coefficient2.Value);
|
||
}
|
||
|
||
if (underlying.Coefficient3.HasValue && !string.IsNullOrWhiteSpace(underlying.UnderlyingCode3))
|
||
{
|
||
coefficients.Add(underlying.Coefficient3.Value);
|
||
}
|
||
|
||
if (underlying.Coefficient4.HasValue && !string.IsNullOrWhiteSpace(underlying.UnderlyingCode4))
|
||
{
|
||
coefficients.Add(underlying.Coefficient4.Value);
|
||
}
|
||
|
||
return coefficients;
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region----盈亏分解V2----
|
||
|
||
//判断是否可以进行盈亏分解计算
|
||
private bool CanCalcPnlExplain(OtcTradeBase td)
|
||
{
|
||
switch (td.TradeType)
|
||
{
|
||
case "远期":
|
||
case "收益互换":
|
||
case "自定义交易":
|
||
return false;
|
||
default:
|
||
return true;
|
||
}
|
||
}
|
||
|
||
private void CalcPnlExplainV2_OldExchangeTrade(CalcPnlExplainData calcData)
|
||
{
|
||
var calcTrade = calcData.trade;
|
||
var curValue = calcData.curValue;
|
||
var prePos = calcData.prePos;
|
||
var curPos = calcData.curPos;
|
||
|
||
var f1 = new CalcField
|
||
{
|
||
spot = curValue.SpotPrice ?? 0,
|
||
pv = curValue.Pv,
|
||
vol = curValue.Vol,
|
||
date = _context.SettleDate,
|
||
riskFreeRate = calcTrade.NoRiskRate ?? 0,
|
||
dividendRate = calcTrade.DividendRate ?? 0,
|
||
};
|
||
var f0 = GetCalcField0(calcTrade);
|
||
if (f0 == null)
|
||
{
|
||
_context.RaiseError("计算盈亏分解", $"[{calcTrade.TradeType},{calcTrade.TradeNumber}]没有找到上日({_context.PreSettleDate:yyyy-MM-dd})风险数据");
|
||
return;
|
||
}
|
||
f0.pv *= curPos.Amount / prePos.Amount; //存续的pv0
|
||
CalcPnlExplainV2_OldOtcTrade(calcTrade, ref calcData.curPnl, f1, f0);
|
||
}
|
||
|
||
private void CalcPnlExplainV2_NewExchangeTrade(CalcPnlExplainData calcData)
|
||
{
|
||
var calcTrade = calcData.trade;
|
||
var curValue = calcData.curValue;
|
||
|
||
var f1 = new CalcField
|
||
{
|
||
spot = curValue.SpotPrice ?? 0,
|
||
pv = curValue.Pv,
|
||
vol = curValue.Vol,
|
||
date = _context.SettleDate,
|
||
riskFreeRate = calcTrade.NoRiskRate ?? 0,
|
||
dividendRate = calcTrade.DividendRate ?? 0,
|
||
};
|
||
//计算范围:当天新发生的交易(开仓)
|
||
CalcPnlExplainV2_NewOtcTradeDeal(calcTrade, ref calcData.curPnl, f1, calcTrade.SpotPrice ?? 0);
|
||
}
|
||
|
||
private void CalcPnlExplainV2(CalcPnlExplainData calcData)
|
||
{
|
||
var calcTrade = calcData.trade;
|
||
var curValue = calcData.curValue;
|
||
var prePos = calcData.prePos;
|
||
var curPos = calcData.curPos;
|
||
var completed = ConsTrade.TradeCompleteStatus.Contains(calcData.trade.TradeStatus);
|
||
|
||
//有存续持仓的情况
|
||
if (!completed)
|
||
{
|
||
var f1 = new CalcField
|
||
{
|
||
spot = curValue.SpotPrice ?? 0,
|
||
pv = curValue.Pv,
|
||
vol = curValue.Vol,
|
||
date = _context.SettleDate,
|
||
riskFreeRate = calcTrade.NoRiskRate ?? 0,
|
||
dividendRate = calcTrade.DividendRate ?? 0,
|
||
};
|
||
|
||
if (prePos != null && prePos.TradeId > 0)
|
||
{
|
||
var f0 = GetCalcField0(calcTrade);
|
||
if (f0 == null)
|
||
{
|
||
_context.RaiseError("计算盈亏分解", $"[{calcTrade.TradeType},{calcTrade.TradeNumber}]没有找到上日({_context.PreSettleDate:yyyy-MM-dd})风险数据");
|
||
return;
|
||
}
|
||
f0.pv *= curPos.Amount / prePos.Amount; //存续的pv0
|
||
CalcPnlExplainV2_OldOtcTrade(calcTrade, ref calcData.curPnl, f1, f0);
|
||
}
|
||
else
|
||
{
|
||
//计算范围:当天新发生的交易(开仓)
|
||
CalcPnlExplainV2_NewOtcTradeDeal(calcTrade, ref calcData.curPnl, f1, calcTrade.SpotPrice ?? 0);
|
||
}
|
||
}
|
||
|
||
//计算范围:当天新发生的交易(平仓、到期、敲出终结等等)
|
||
var closedNotional = prePos != null
|
||
? prePos.Amount - curPos.Amount
|
||
: (calcData.trade.OriginalNotional ?? 0) - (completed ? 0 : calcData.trade.Notional);
|
||
|
||
if (closedNotional > 0)
|
||
{
|
||
var clone = calcData.trade.Clone();
|
||
var tcArr = DbContext.trade_cash.Where(n => n.ValueDate == _context.SettleDate && n.TradeId == calcData.trade.id && !n.IsDeleted)
|
||
.Where(n => n.Action == ClientCashInCashOut.系统操作_行权费 || n.Action == ClientCashInCashOut.系统操作_平仓费)
|
||
.Select(n => new { n.UnwindPercentRate, n.UnwindNotional, spotPrice = n.FinalPrice ?? 0, n.Amount, n.UnwindPrice });
|
||
foreach (var tc in tcArr)
|
||
{
|
||
if (tc.UnwindNotional > 0)
|
||
{
|
||
clone.Notional = tc.UnwindNotional.Value;
|
||
if (ConsTrade.TradeCompleteStatus.Contains(clone.TradeStatus))
|
||
{
|
||
curValue.Pv *= clone.Notional;
|
||
}
|
||
var f1 = new CalcField
|
||
{
|
||
spot = curValue.SpotPrice ?? 0,
|
||
pv = curValue.Pv,
|
||
vol = curValue.Vol,
|
||
date = _context.SettleDate,
|
||
riskFreeRate = calcTrade.NoRiskRate ?? 0,
|
||
dividendRate = calcTrade.DividendRate ?? 0,
|
||
};
|
||
//clone.TradePrice = tc.Amount;
|
||
clone.TradeSinglePrice = tc.UnwindPrice;
|
||
//clone.BuySell = clone.BuySell == "买入" ? "卖出" : "买入";
|
||
CalcPnlExplainV2_NewOtcTradeDeal(clone, ref calcData.curPnl, f1, tc.spotPrice);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
//----------------------------------------------------
|
||
// 国海提供的算法
|
||
// 符号说明: P(·):估值函数 T:时间(按交易日算) S:标的价格 V:波动率 Q:分红率
|
||
// 存续头寸 PNL:THETA,OLD VEGA,PSI,OLD DELTA,OLD GAMMA
|
||
// THETA = P(S0, V0, Q0, T1) − P(S0, V0, Q0, T0)
|
||
// OLD VEGA = P(S0, V1, Q0, T1) − P(S0, V0, Q0, T1)
|
||
// PSI = P(S0, V1, Q1, T1) − P(S0, V1, Q0, T1)
|
||
// OLD DELTA = DELTA(S0, V1, Q1, T1 − 0.5) × (S1-S0)
|
||
// OLD GAMMA = P(S1, V1, Q1, T1) − P(S0, V1, Q1, T1) − OLD DELTA
|
||
// 计算范围:上一交易日收盘后依然存续的头寸
|
||
// 新发生交易 PnL:New Vega,New Delta,New Gamma
|
||
// NEW VEGA = P(S0, V1, Q1, T1) − P0,P0为交易时价格,S0为交易时标的价格
|
||
// NEW DELTA = DELTA(S0, V1, Q1, T1) * (S1 − S0)
|
||
// NEW GAMMA = P(S1, V1, Q1,T1) − P(S0, V1, Q1, T1) − NEW DELTA
|
||
// 计算范围:当天新发生的交易,包括对冲、开仓、平仓、到期、敲出终结等等
|
||
//----------------------------------------------------
|
||
|
||
//上一交易日收盘后依然存续的头寸
|
||
private void CalcPnlExplainV2_OldOtcTrade(trade td, ref TPnl pnl, CalcField f1, CalcField f0)
|
||
{
|
||
if (f0 == null)
|
||
{
|
||
_context.RaiseError("计算盈亏分解", $"[{td.TradeType},{td.TradeNumber}]没有找到上日({_context.PreSettleDate:yyyy-MM-dd})风险数据");
|
||
return;
|
||
}
|
||
|
||
var pS0V0Q0T1 = CalcPnlExplainV2(td, new CalcField
|
||
{
|
||
spot = f0.spot,
|
||
vol = f0.vol,
|
||
dividendRate = f0.dividendRate,
|
||
riskFreeRate = f1.riskFreeRate,
|
||
date = f1.date
|
||
}, PricingRequest.Pv);
|
||
|
||
var pS0V1Q0T1 = pS0V0Q0T1;
|
||
|
||
if (Math.Abs(f0.vol - f1.vol) > 1e-7)
|
||
{
|
||
pS0V1Q0T1 = CalcPnlExplainV2(td, new CalcField
|
||
{
|
||
spot = f0.spot,
|
||
vol = f1.vol,
|
||
dividendRate = f0.dividendRate,
|
||
riskFreeRate = f1.riskFreeRate,
|
||
date = f1.date
|
||
}, PricingRequest.Pv);
|
||
}
|
||
|
||
var cf = new CalcField
|
||
{
|
||
spot = f0.spot,
|
||
vol = f1.vol,
|
||
dividendRate = f1.dividendRate,
|
||
riskFreeRate = f1.riskFreeRate,
|
||
date = f1.date
|
||
};
|
||
var pS0V1Q1T1 = pS0V1Q0T1;
|
||
if (Math.Abs(f0.dividendRate - f1.dividendRate) > 1e-7)
|
||
{
|
||
pS0V1Q1T1 = CalcPnlExplainV2(td, cf, PricingRequest.Pv);
|
||
}
|
||
var pS0V1Q1T1_Delta = CalcPnlExplainV2(td, cf, PricingRequest.Delta, p =>
|
||
{
|
||
p.timeToMaturityDays = BLL.valuedateBLL.TradeDayCount.ToDayCountImpl().DaysInPeriod(_context.SettleDate, td.ExerciseDate.Value) + 0.5;
|
||
});
|
||
|
||
//THETA = P(S0, V0, Q0, T1) − P(S0, V0, Q0, T0)
|
||
if (pS0V0Q0T1 != null)
|
||
{
|
||
pnl.PnLTheta = NumberHelper.Normalize(pS0V0Q0T1.Pv - f0.pv);
|
||
}
|
||
|
||
//OLD VEGA = P(S0, V1, Q0, T1) − P(S0, V0, Q0, T1)
|
||
if (pS0V0Q0T1 != null && pS0V1Q0T1 != null)
|
||
{
|
||
pnl.PnLVega = NumberHelper.Normalize(pS0V1Q0T1.Pv - pS0V0Q0T1.Pv);
|
||
}
|
||
|
||
//PSI = P(S0, V1, Q1, T1) − P(S0, V1, Q0, T1)
|
||
if (pS0V1Q1T1 != null && pS0V1Q0T1 != null)
|
||
{
|
||
pnl.PnLPsi = NumberHelper.Normalize(pS0V1Q1T1.Pv - pS0V1Q0T1.Pv);
|
||
}
|
||
|
||
//OLD DELTA = DELTA(S0, V1, Q1, T1 − 0.5) × (S1-S0)
|
||
//NEW GAMMA = P(S1, V1, Q1,T1) − P(S0, V1, Q1, T1) − NEW DELTA
|
||
if (pS0V1Q1T1_Delta != null && pS0V1Q1T1 != null)
|
||
{
|
||
pnl.PnLDelta = NumberHelper.Normalize(pS0V1Q1T1_Delta.Delta * (f1.spot - f0.spot));
|
||
pnl.PnLGamma = NumberHelper.Normalize(f1.pv - pS0V1Q1T1.Pv - pnl.PnLDelta);
|
||
}
|
||
}
|
||
|
||
//当天新发生的交易
|
||
private void CalcPnlExplainV2_NewOtcTradeDeal(trade td, ref TPnl pnl, CalcField f1, double spot0)
|
||
{
|
||
var pS0V1Q1T1 = CalcPnlExplainV2(td, new CalcField
|
||
{
|
||
spot = spot0,
|
||
vol = f1.vol,
|
||
dividendRate = f1.dividendRate,
|
||
riskFreeRate = f1.riskFreeRate,
|
||
date = f1.date
|
||
}, PricingRequest.Pv | PricingRequest.Delta);
|
||
|
||
if (pS0V1Q1T1 != null)
|
||
{
|
||
pnl.PnLVega += NumberHelper.Normalize(pS0V1Q1T1.Pv - (td.TradeSinglePrice ?? 0) * td.Notional * TradeCalcHelper.GetBuySellSign(td.BuySell));
|
||
pnl.PnLDelta += NumberHelper.Normalize(pS0V1Q1T1.Delta * (f1.spot - spot0));
|
||
pnl.PnLGamma += NumberHelper.Normalize(f1.pv - pS0V1Q1T1.Pv - pnl.PnLDelta);
|
||
}
|
||
}
|
||
|
||
//计算pv和delta
|
||
private static TradeValueResult CalcPnlExplainV2(trade td, CalcField f, PricingRequest pricingRequest
|
||
, Action<OptionTradeParamBase> paramOverride = null)
|
||
{
|
||
if (f.date >= td.ExerciseDate.Value)
|
||
{
|
||
return new TradeValueResult
|
||
{
|
||
BuySell = td.BuySell,
|
||
UnderlyingCode = td.UnderlyingCode,
|
||
TradeId = td.id,
|
||
Strike = td.Strike,
|
||
Succeeded = true,
|
||
SpotPrice = f.spot
|
||
};
|
||
}
|
||
|
||
td.NoRiskRate = f.riskFreeRate;
|
||
td.DividendRate = f.dividendRate;
|
||
|
||
var req = new OptionValueCalcRequest(f.riskFreeRate)
|
||
{
|
||
correlations = null,//不计算彩虹等多标的期权暂时不需要
|
||
engineName = null,
|
||
preciseTimeMode = false, //日终一定是false
|
||
isEodCalc = true,
|
||
pricingRequest = pricingRequest,
|
||
spotPrices = new[] { f.spot },
|
||
vols = new[] { f.vol },
|
||
ParamOverride = paramOverride,
|
||
calcScenario = Enums.CalcScenarioEnum.EodSettlement
|
||
};
|
||
|
||
return OptionCalculatorV2.GetOptionValueResult(f.date, td, req, out _);
|
||
}
|
||
|
||
//获取上日计算用到的计算参数
|
||
private CalcField GetCalcField0(OtcTradeBase td)
|
||
{
|
||
var uniqueCode = ConsTrade.TradeTypesForHedge.Contains(td.TradeType) ? GetHedgeUniqueCode(td) : td.id.ToString();
|
||
|
||
var preRisk = DbContext.Set<TRisk>()
|
||
.Where(n => n.ValueDate == _context.PreSettleDate && uniqueCode == (n.TradeId > 0 ? n.TradeId + "" : n.HedgeUniqueCode))
|
||
.Select(n => new { n.ValueDate, n.Vol, n.Pv }).FirstOrDefault();
|
||
|
||
if (preRisk == null)
|
||
{
|
||
return null;
|
||
}
|
||
|
||
var hisQuery = from n in DbContext.TradeHisData
|
||
where n.TradeId == td.id && n.ValueDate < _context.SettleDate && n.ValueType == ConsTradeField.DividendRate
|
||
orderby n.ValueDate descending
|
||
select new { n.ValueType, n.Value };
|
||
//分红率
|
||
var dividendRate = hisQuery.FirstOrDefault()?.Value ?? 0;
|
||
|
||
//标的价格
|
||
var spot = _context.GetPreEodPriceProvider().GetPrice(td.UnderlyingCode, _useClosePrice ? td.SettlementType : SettlementTypeEnum.SettlePrice);
|
||
|
||
return new CalcField
|
||
{
|
||
pv = preRisk.Pv,
|
||
vol = preRisk.Vol,
|
||
date = preRisk.ValueDate,
|
||
riskFreeRate = td.NoRiskRate ?? 0,
|
||
dividendRate = dividendRate,
|
||
spot = spot
|
||
};
|
||
}
|
||
|
||
#endregion
|
||
|
||
static string GetHedgeUniqueCode(OtcTradeBase td)
|
||
{
|
||
return HedgePnlCalc.GetHedgeUniqueCode(td.AssetId, td.TradeType, TradeHelper.GetPositionType(td.BuySell).ToString(), td.UnderlyingCode, td.ExchangeOptionCode);
|
||
}
|
||
|
||
class CalcPnlExplainData
|
||
{
|
||
/// <summary>
|
||
/// 交易数据
|
||
/// </summary>
|
||
public trade trade;
|
||
|
||
/// <summary>
|
||
/// 上日持仓,为null时表示交易是当天开仓的交易
|
||
/// </summary>
|
||
public TPosition prePos;
|
||
|
||
/// <summary>
|
||
/// 当前持仓,必须赋值
|
||
/// </summary>
|
||
public TPosition curPos;
|
||
|
||
/// <summary>
|
||
/// 当前估值,必须赋值
|
||
/// </summary>
|
||
public TPnl curPnl;
|
||
|
||
/// <summary>
|
||
/// 为null时表示交易已完全了结
|
||
/// </summary>
|
||
public TradeValueResult curValue;
|
||
}
|
||
|
||
class CalcField
|
||
{
|
||
public double pv;
|
||
|
||
public DateTime date;
|
||
|
||
public double vol;
|
||
|
||
public double dividendRate;
|
||
|
||
public double spot;
|
||
|
||
public double riskFreeRate;
|
||
}
|
||
|
||
class PreSettleData
|
||
{
|
||
readonly List<TRisk> _riskList;
|
||
|
||
public PreSettleData()
|
||
{
|
||
_riskList = new List<TRisk>();
|
||
}
|
||
|
||
public IEnumerable<TRisk> Risks => _riskList;
|
||
|
||
public TPnl Pnl { get; set; }
|
||
|
||
public eod_trade_position_swap PosSwap { get; set; }
|
||
|
||
public void AddRisk(TRisk risk)
|
||
{
|
||
_riskList.Add(risk);
|
||
}
|
||
}
|
||
}
|
||
} |