Files
zszq-trs/YLErpDAL/Modules/SwapModule/SwapEodPositionService.cs
T
张名锐 4449bd5824 feat(swap): 更新互换估值功能并优化数据模型
- 添加互换估值V1配置常量以支持新版本
- 将PeriodAmount和DividendAmount字段改为可空decimal类型
- 更新字段注释说明预付金利率和利息的计算方式
- 修改净额结算金额定义,排除期初和追加预付金本金
- 在报表服务中添加安全求和操作防止空值异常
- 增加多个汇总字段包括股息、保证金利息等统计
- 重构预付金处理逻辑,改用加权平均计算方式
- 新增加权保证金利率和利息计算辅助方法
- 调整前端表格列顺序,优化交易编号显示位置
- 更新数值格式化器以支持空值处理和精度控制
- 添加文档列顺序恢复功能确保表格一致性
2026-07-14 09:46:54 +08:00

2704 lines
156 KiB
C#

using BaseOUDAL;
using Newtonsoft.Json;
using NPOI.POIFS.Properties;
using System;
using System.Linq.Expressions;
using YLErp.DBModels;
using YLErp.DBModels.Consts;
using YLErp.DBModels.Enums;
using YLErp.Helpers;
using YLErp.Model;
using YLErp.Model.Enum;
using YLErp.Models;
using YLErp.Modules.DataProviderModule;
using YLErp.Modules.EodModule;
using YLErp.QdpModule;
namespace YLErp.Modules.SwapModule
{
/// <summary>
/// 互换流水日终归档服务
/// </summary>
public class SwapEodPositionService : SwapTradeBaseService
{
private List<int> marginTypes = new List<int>() { (int)InterestModeEnum.追加预付金, (int)InterestModeEnum.初始预付金 };
private static readonly IYcLogger Log = LogFactory.GetLogger(typeof(SwapEodPositionService).FullName);
public SwapEodPositionService(OptUserInfo optUser) : base(optUser)
{
}
public SwapEodPositionService(YLBaseService baseService) : base(baseService)
{
}
#region 可测试化接缝(Seams)——override 这些虚方法可在测试中替换 DB/外部调用,生产代码行为不变
/// <summary>持久化 eod 持仓记录(生产: DbContext.Add;测试: 收集到列表)</summary>
protected virtual void PersistEodSwapPosition(eod_swap_position position)
{
if (position.id == 0)
{
DbContext.eod_swap_position.Add(position);
}
else
{
UpdateDbOption(position);
}
}
/// <summary>保存所有变更(生产: DbContext.SaveChanges;测试: 计数)</summary>
protected virtual void SaveAllChanges()
{
DbContext.SaveChanges();
}
/// <summary>获取汇率(生产: EodCurrencyRateService;测试: 返回固定值)</summary>
protected virtual double GetCurrencyRate(string quoteCurrency, string settlementCurrency, DateTime valueDate, bool seekPreday, CurrencyRateType currencyRateType)
{
return new EodCurrencyRateService(UserInfo).GetCurrencyRate(quoteCurrency, settlementCurrency, valueDate, seekPreday, currencyRateType);
}
/// <summary>
/// 计算利息腿利息明细(生产: new SwapDealService(this).GetInterests;测试: 用StubSwapDealService内存算)
/// 参数与 SwapDealService.GetInterests 完全一致,保证行为不变。
/// </summary>
protected virtual List<swap_flow_event> CalcSwapInterests(
trade td, trade_extend tradeExtend,
DateTime valueDate, DateTime unwindDate,
List<eod_swap_position> eodPositions, List<swap_position> positions,
decimal posiNotionalValue, decimal posiLongNotionalValue, decimal posiShortNotionalValue,
decimal closePosiNotionalValue, decimal closePrecent,
int eventType, bool tdClose, bool needPrice,
decimal grossPrice, decimal orginPv,
bool add = false, bool settment = true, bool newCalcLast = false,
List<swap_flow_event> closeList = null)
{
return new SwapDealService(this).GetInterests(td, tradeExtend, valueDate, unwindDate,
eodPositions, positions, posiNotionalValue, posiLongNotionalValue, posiShortNotionalValue,
closePosiNotionalValue, closePrecent, eventType, tdClose, needPrice,
grossPrice, orginPv, add, settment, newCalcLast, closeList);
}
// FindTrade 已上提到基类 SwapTradeBaseService(三子类实现一致,消除重复)
/// <summary>查找交易扩展(生产: DbContext.trade_extend;测试: 内存字典)</summary>
protected virtual trade_extend FindTradeExtend(int tradeId)
{
return DbContext.trade_extend.FirstOrDefault(x => x.TradeId == tradeId);
}
/// <summary>查找指定日期范围的 eod 持仓(生产: DbContext.eod_swap_position.Where;测试: 内存列表)</summary>
protected virtual List<eod_swap_position> FindEodSwapPositions(int swapTradeId, DateTime preSettleDate)
{
return DbContext.eod_swap_position.Where(x => x.SwapTradeId == swapTradeId && !x.Invalid && x.ValueDate >= preSettleDate).ToList();
}
/// <summary>查找交易持仓(生产: DbContext.swap_position.Where;测试: 内存列表)</summary>
protected virtual List<swap_position> FindSwapPositions(int swapTradeId)
{
return DbContext.swap_position.Where(x => x.SwapTradeId == swapTradeId && !x.Invalid).ToList();
}
/// <summary>查找框架合约日终汇总(生产: DbContext.eod_swap.FirstOrDefault;测试: 内存字典)</summary>
protected virtual eod_swap FindEodSwap(int swapTradeId, DateTime valueDate)
{
return DbContext.eod_swap.FirstOrDefault(x => x.SwapTradeId == swapTradeId && x.ValueDate == valueDate);
}
/// <summary>添加互换事件(生产: new SwapEventService(this).AddSwapEventDate;测试: 收集到列表)</summary>
protected virtual swap_event AddSwapEvent(DateTime tradeDate, int swapTradeId, int eventType, string data, int clientCashId, bool save, string reason)
{
return new SwapEventService(this).AddSwapEventDate(tradeDate, swapTradeId, eventType, data, clientCashId, save, reason);
}
/// <summary>在事务中执行(生产: BeginTransaction/Commit/Rollback;测试: 直接执行不包事务)</summary>
protected virtual void ExecuteInTransaction(Action action)
{
var trans = DbContext.Database.BeginTransaction();
try
{
action();
trans.Commit();
}
catch
{
trans.Rollback();
throw;
}
finally
{
trans.Dispose();
}
}
/// <summary>添加资金记录(生产: AddClientCashInCashOut;测试: 收集到计数器)</summary>
protected virtual int AddClientCash(trade td, double amount, string action, DateTime valueDate)
{
return AddClientCashInCashOut(td, amount, action, valueDate);
}
/// <summary>保存框架合约日终汇总(生产: SaveEodSwap私有方法;测试: 收集到列表)</summary>
protected virtual void SaveEodSwapRecord(trade td, DateTime settleDate, DateTime preSettleDate)
{
SaveEodSwap(td, settleDate, preSettleDate);
}
/// <summary>清理旧持仓事件(生产: ClearSwapPositions;测试: 空操作)</summary>
protected virtual void ClearSwapPositionsForCompose(trade td, DateTime tradeDate, List<int> eventTypes)
{
ClearSwapPositions(td, tradeDate, eventTypes, false);
}
/// <summary>获取标的市场价格(生产: UnderlyingCodePrice查缓存+中债估值;测试: 返回固定值)</summary>
protected virtual decimal GetUnderlyingPrice(string code, DateTime settleDate, out decimal vobp)
{
return UnderlyingCodePrice(code, settleDate, out vobp);
}
/// <summary>获取标的缓存数据(生产: DataCacheProvider;测试: 返回内存对象)</summary>
protected virtual underlying_manager GetUnderlyingData(string underlyingCode)
{
return DataCacheProvider.GetUnderlyingDataSource().GetData(underlyingCode);
}
/// <summary>计算债券付息(生产: BondPaymentService;测试: 返回固定值)</summary>
protected virtual decimal CalcBondPayment(string underlyingCode, DateTime fromDate, DateTime toDate, decimal qty, int shortRatio, int directionRatio)
{
return new BondPaymentService(UserInfo).CalcPayment(underlyingCode, fromDate, toDate, qty, shortRatio, directionRatio);
}
// ---- SwapPositionCompose 路径专用 seam(借鉴 testable 分支)----
/// <summary>查找收盘所需的活跃互换交易(生产: DbContext.trade.Where;测试: 内存列表)</summary>
protected virtual List<trade> FindActiveSwapTrades(DateTime settleDate, IEnumerable<int> clientIds)
{
var tradePredicate = PredicateBuilder.Create<trade>(n => n.ValidState != ConsGlobal.InValid
&& n.TradeType == "收益互换"
&& n.TradeDate <= settleDate
&& n.ExerciseDate >= settleDate
&& (n.TradeStatus == ConsTrade.确认成交 || n.UnWindDate >= settleDate)
);
if (clientIds != null && clientIds.Any())
{
tradePredicate = tradePredicate.And(x => clientIds.Contains(x.ClientId));
}
return DbContext.trade.Where(tradePredicate).ToList();
}
/// <summary>查找交易的所有持仓(含初始+实际,生产: DbContext.swap_position;测试: 内存列表)</summary>
protected virtual List<swap_position> FindAllSwapPositions(List<int> tradeIds)
{
return DbContext.swap_position.Where(t => tradeIds.Contains(t.SwapTradeId) && !t.Invalid).ToList();
}
/// <summary>批量查找交易扩展(生产: DbContext.trade_extend;测试: 内存列表)</summary>
protected virtual List<trade_extend> FindTradeExtends(List<int> tradeIds)
{
return DbContext.trade_extend.Where(x => tradeIds.Contains(x.TradeId)).ToList();
}
/// <summary>查找指定日期的日终汇总(生产: DbContext.eod_swap;测试: 内存列表)</summary>
protected virtual List<eod_swap> FindEodSwapsByDate(DateTime valueDate)
{
return DbContext.eod_swap.Where(x => x.ValueDate == valueDate).ToList();
}
/// <summary>查找交易在指定日期的完成流水事件(生产: DbContext.swap_flow_event;测试: 内存列表)</summary>
protected virtual List<swap_flow_event> FindFlowEvents(int swapTradeId, DateTime settleDate)
{
Expression<Func<swap_flow_event, bool>> eventExpression = x => x.SwapTradeId == swapTradeId
&& x.DataState == (int)SwapFlowDateStateEnum.完成
&& x.EventDate == settleDate;
return DbContext.swap_flow_event.Where(eventExpression).ToList();
}
#endregion
/// <summary>
/// 多空组合 互换流水合成持仓
/// </summary>
/// <param name="tradeDate">清算日期</param>
public void SwapFlowEventCompose(DateTime tradeDate)
{
new SysJobService(UserInfo).UpdateJob("互换流水合成持仓", 41, "互换流水合成持仓进行中");
var eventQueryGroup = DbContext.swap_flow_event.Where(n => n.DataState == (int)SwapFlowDateStateEnum.等待完成 && n.EventDate == tradeDate).AsEnumerable().GroupBy(g => g.SwapTradeId);
foreach (var eventQueryGroupItem in eventQueryGroup)
{
ComposePage(eventQueryGroupItem.Key, eventQueryGroupItem.ToList(), tradeDate);
}
new SysJobService(UserInfo).UpdateJob("互换流水合成持仓", 42, "互换流水合成持仓完成");
}
/// <summary>
/// 多空组合 互换流水合成持仓
/// </summary>
/// <param name="tradeDate">清算日期</param>
public void SwapFlowEventCompose(List<long> flowEventIds, DateTime tradeDate)
{
new SysJobService(UserInfo).UpdateJob("互换流水合成持仓", 41, "互换流水合成持仓进行中");
var eventQueryGroup = DbContext.swap_flow_event.Where(n => flowEventIds.Contains(n.id)).ToList().GroupBy(g => g.SwapTradeId);
foreach (var eventQueryGroupItem in eventQueryGroup)
{
ComposePage(eventQueryGroupItem.Key, eventQueryGroupItem.ToList(), tradeDate, false);
}
new SysJobService(UserInfo).UpdateJob("互换流水合成持仓", 42, "互换流水合成持仓完成");
}
/// <summary>
/// 收盘生成归档信息
/// </summary>
/// <param name="settleDate">结算日期</param>
public void SwapPositionCompose(DateTime settleDate, DateTime preSettleDate, IEnumerable<int> ClientIds)
{
var dateStr = settleDate.ToString("yyyy-MM-dd");
Log.Info("SwapPositionCompose:" + "settleDate:" + settleDate + " preSettleDate:" + preSettleDate + " ClientIds:" + JsonHelper.Serialize(ClientIds));
var tradeQueryList = FindActiveSwapTrades(settleDate, ClientIds);
var tradeIds = tradeQueryList.Select(s => s.id).ToList();
var allTradePositionList = FindAllSwapPositions(tradeIds);
var tradePositionList = allTradePositionList.Where(t => t.IsInitial).ToList();
var tradeRealPositionList = allTradePositionList.Where(t => !t.IsInitial).ToList();
var tradeExtendList = FindTradeExtends(tradeIds);
var eodSwapList = FindEodSwapsByDate(preSettleDate);
List<int> eventTyps = new List<int>() { (int)SwapEventTypeEnum.平仓, (int)SwapEventTypeEnum.互换, (int)SwapEventTypeEnum.自动互换 };
foreach (var td in tradeQueryList)
{
ExecuteInTransaction(() =>
{
List<int> removeEventTyps = new List<int>() { (int)SwapEventTypeEnum.自动互换 };
bool longShort = td.StructureType == ClientMarginTypeEnum.多空组合.ToString();
ClearSwapPositions(td, settleDate, removeEventTyps, true);
var positions = tradePositionList.Where(x => x.SwapTradeId == td.id);
var realPositions = tradeRealPositionList.Where(s => s.SwapTradeId == td.id);
var posiList = positions.Where(x => x.PosiQuantity > 0).ToList();
var realPosiList = realPositions.ToList();
var interestList = positions.Where(x => x.InterestDirection > 0).ToList();
DateTime posiDate = td.TradeDate.Value;//交易日期
var lastEodSwap = eodSwapList.FirstOrDefault(x => x.SwapTradeId == td.id);
//上一交易日无日终归档,且不是交易日期,且当前收盘日期不是交易日期,报错
if (lastEodSwap == null && settleDate > posiDate)
{
throw new Exception($"交易{td.TradeNumber}在上一交易日【{preSettleDate:yyyy-MM-dd}】未收盘");
}
var allEodPositions = FindEodSwapPositions(td.id, preSettleDate);
var eodPositions = allEodPositions.Where(x => x.ValueDate == preSettleDate).ToList();//上一日终持仓信息
var tradeExtend = tradeExtendList.FirstOrDefault(x => x.TradeId == td.id);
td.trade_extend = tradeExtend;
var todyEodPositions = allEodPositions.Where(x => x.ValueDate == settleDate).ToList();
var allPositionQty = realPositions.Sum(x => x.PosiQuantity);//总剩余持仓数量
var orginPv = eodPositions.Sum(s => s.PosiNotionalValue);
if (longShort && td.ExerciseDate.Value == settleDate && allPositionQty != 0)//多空组合判断是否已到到期日且无持仓信息
{
throw new Exception($"交易【{td.TradeNumber}】到期扔有持仓信息");
}
var flowEvents = FindFlowEvents(td.id, settleDate);
var preDealDate = GetPreDealDate(td.id, settleDate, eventTyps);//上一次平仓/互换/自动互换处理日期
List<swap_flow_event> autoInterests = new List<swap_flow_event>();//自动互换利息腿信息
//处理浮动腿
var curEodPosis = DealFloatPositions(posiList, realPosiList, eodPositions, todyEodPositions, settleDate, td, preSettleDate, flowEvents);
var posiLongNotional = curEodPosis.Where(s => s.PositionType == (int)PositionTypeFlag.Long).Sum(s => s.PosiNotionalValue);
var posiShortNotional = curEodPosis.Where(s => s.PositionType == (int)PositionTypeFlag.Short).Sum(s => s.PosiNotionalValue);
var closePosiNotional = curEodPosis.Where(s => s.TdCloseQty > 0).Sum(s => s.TdCloseQty * s.ContractSize * s.PosiGrossPrice);
var grossPrice = curEodPosis.Where(x => x.PosiDirection > 0).FirstOrDefault()?.PosiGrossPrice ?? 0;
//处理利息腿
DealInterests(interestList, eodPositions, todyEodPositions, settleDate, td, flowEvents, autoInterests, lastEodSwap, posiLongNotional, posiShortNotional, closePosiNotional, grossPrice, orginPv);
//获取自动互换的 interval 信息,用于确定结算日期
IntervalModel autoInterval = null;
foreach (var interest in interestList)
{
autoInterval = interest.SwapIntervalList.FirstOrDefault(x => x.Date == settleDate && x.Settlement == 1);
if (autoInterval != null)
break;
}
// 自动互换(仅利息/预付金,不含分红)
DealAutoInterests(autoInterests, td, settleDate, preDealDate, posiLongNotional + posiShortNotional, autoInterval);
// 分红独立处理:只要当天有债券需要分红,则生成分红自动互换,与利息互换无关
DealDividends(curEodPosis, td, settleDate, tradeExtend);
//多空组合判断是否已到到期日且无持仓信息
if (longShort && td.ExerciseDate.Value == settleDate && allPositionQty == 0)
{
td.TradeStatus = "已到期";
td.UnWindDate = settleDate;
}
SaveAllChanges();
});
}
}
/// <summary>
/// 框架合约汇总
/// </summary>
/// <param name="settleDate"></param>
/// <param name="preSettleDate"></param>
/// <param name="ClientIds"></param>
public void SwapEodCompose(DateTime settleDate, DateTime preSettleDate, IEnumerable<int> ClientIds)
{
Log.Info("SwapEodCompose:" + "settleDate:" + settleDate + " preSettleDate:" + preSettleDate + " ClientIds:" + JsonHelper.Serialize(ClientIds));
var tradePredicate = PredicateBuilder.Create<trade>(n => n.ValidState != ConsGlobal.InValid
&& n.TradeType == "收益互换"
&& n.TradeDate <= settleDate
&& n.ExerciseDate >= settleDate
&& (n.TradeStatus == ConsTrade.确认成交 || n.UnWindDate >= settleDate)
);
if (ClientIds != null && ClientIds.Any())
{
tradePredicate = tradePredicate.And(x => ClientIds.Contains(x.ClientId));
}
var tradeQueryList = DbContext.trade.Where(tradePredicate).ToList();
foreach (var td in tradeQueryList)
{
Log.Info("【框架合约汇总】处理合约ID为:[" + td.id + "]的数据,收盘时间为:[" + settleDate + "]。开始");
SaveEodSwap(td, settleDate, preSettleDate);
Log.Info("【框架合约汇总】处理合约ID为:[" + td.id + "]的数据,收盘时间为:[" + settleDate + "]。结束");
}
DbContext.SaveChanges();
}
/// <summary>
/// 处理利息腿归档
/// </summary>
/// <param name="interestList">利息腿持仓信息</param>
/// <param name="eodPositions">上一日终归档持仓信息</param>
/// <param name="todyEodPositions">当日归档持仓信息</param>
/// <param name="settleDate">当前结算日期</param>
/// <param name="td">互换交易主信息</param>
/// <param name="swapDeals">当日平仓/互换信息</param>
/// <param name="autoInterests">自动互换集合</param>
/// <param name="longshortCloseInterests">多空组合平仓利息腿信息</param>
/// <param name="lastEodSwap">上一日终框架合约</param>
protected void DealInterests(List<swap_position> interestList,
List<eod_swap_position> eodPositions,
List<eod_swap_position> todyEodPositions,
DateTime settleDate,
trade td,
List<swap_flow_event> flowEvents,
List<swap_flow_event> autoInterests,
eod_swap lastEodSwap,
decimal posiLongNational,
decimal posiShortNational,
decimal closeNational,
decimal grossPrice,
decimal orginPv)
{
Log.Info("===================处理利息腿归档====================");
// 添加详细的参数验证日志
Log.Info($"[DealInterests] 参数验证 - settleDate: {settleDate:yyyy-MM-dd}, td.id: {td?.id}, td.TradeNumber: {td?.TradeNumber}");
Log.Info($"[DealInterests] 参数验证 - interestList.Count: {interestList?.Count ?? 0}, eodPositions.Count: {eodPositions?.Count ?? 0}, todyEodPositions.Count: {todyEodPositions?.Count ?? 0}");
Log.Info($"[DealInterests] 参数验证 - flowEvents.Count: {flowEvents?.Count ?? 0}, autoInterests.Count: {autoInterests?.Count ?? 0}");
Log.Info($"[DealInterests] 参数验证 - posiLongNational: {posiLongNational}, posiShortNational: {posiShortNational}, closeNational: {closeNational}, grossPrice: {grossPrice}, orginPv: {orginPv}");
// 验证关键参数
if (td == null)
{
Log.Info("[DealInterests] 参数验证: td (trade) 为 null");
throw new ArgumentNullException(nameof(td), "交易对象不能为null");
}
if (interestList == null)
{
Log.Info($"[DealInterests] 参数验证: interestList 为 null, td.id: {td.id}");
throw new ArgumentNullException(nameof(interestList), "利息腿列表不能为null");
}
if (flowEvents == null)
{
Log.Info($"[DealInterests] 参数验证: flowEvents 为 null, td.id: {td.id}");
throw new ArgumentNullException(nameof(flowEvents), "流水事件列表不能为null");
}
if (autoInterests == null)
{
Log.Info($"[DealInterests] 参数验证: autoInterests 为 null, td.id: {td.id}");
throw new ArgumentNullException(nameof(autoInterests), "自动互换列表不能为null");
}
var hasClose = flowEvents.Any(x => x.EventType == (int)SwapEventTypeEnum.平仓);
var hasSwap = flowEvents.Any(x => x.EventType == (int)SwapEventTypeEnum.互换);
foreach (var interest in interestList)
{
Log.Info($"InterestMode is {interest.InterestMode},HappenDate is {interest.HappenDate},settleDate is {settleDate}");
if (interest.InterestMode == (int)InterestModeEnum.追加预付金 && interest.HappenDate > settleDate)
{
continue;
}
var eodPosition = eodPositions.FirstOrDefault(x => x.PositionId == interest.id);//上一日日终利息信息 可能不存在
var tdEodPosition = todyEodPositions.FirstOrDefault(x => x.PositionId == interest.id);//当前结算日日终利息信息
var insterval = interest.SwapIntervalList.FirstOrDefault(x => x.Date == settleDate && x.Settlement == 1);//自动互换观察日信息
List<swap_flow_event> dealInterests = new List<swap_flow_event>();
dealInterests.AddRange(flowEvents);
var dealInterest = dealInterests.FirstOrDefault(n => n.PositionId == interest.id);//当日是否做过互换或平仓
var swapEvents = flowEvents.Where(x => (x.EventType == (int)SwapEventTypeEnum.互换 || x.EventType == (int)SwapEventTypeEnum.平仓) && x.PositionId == interest.id).ToList();
//如果当日有互换/当日有平仓 不再重新生成或更新
Log.Info($"insterval is {insterval},hasSwap is {hasSwap},hasClose is {hasClose}");
if (insterval != null && !hasSwap)
{
if (!hasClose)//当日无平仓
{
var _autoInterests = SaveAutoEodInterestPosition(eodPosition, tdEodPosition, interest, td, settleDate, insterval, lastEodSwap, posiLongNational, posiShortNational, grossPrice, orginPv);
if (_autoInterests.Count > 0)
{
autoInterests.AddRange(_autoInterests);
}
}
else
{
var _autoInterests = SaveAutoEodWithCloseInterestPosition(eodPosition, tdEodPosition, interest, td, settleDate, insterval, posiLongNational, posiShortNational, swapEvents, closeNational, true, grossPrice, orginPv);
if (_autoInterests.Count > 0)
{
autoInterests.AddRange(_autoInterests);
}
}
}
else if (hasSwap)//当日有互换,根据互换事件重新生成
{
SaveEodInterestPosition(eodPosition, tdEodPosition, interest, td, settleDate, swapEvents);
}
else if (hasClose)
{
SaveAutoEodWithCloseInterestPosition(eodPosition, tdEodPosition, interest, td, settleDate, insterval, posiLongNational, posiShortNational, swapEvents, closeNational, false, grossPrice, orginPv);
}
else//无自动互换、互换/平仓,复制上一日终信息,并计算当日新增利息
{
SaveEodInterestPositionCopy(eodPosition, tdEodPosition, settleDate, td, interest, lastEodSwap, true, posiLongNational, posiShortNational, grossPrice, orginPv);
}
}
}
/// <summary>
/// 处理浮动腿归档
/// </summary>
/// <param name="posiList"></param>
/// <param name="eodPositions"></param>
/// <param name="todyEodPositions"></param>
/// <param name="settleDate"></param>
/// <param name="td"></param>
/// <param name="longShort"></param>
/// <param name="flowEvents"></param>
/// <param name="unwindEvent"></param>
protected List<eod_swap_position> DealFloatPositions(List<swap_position> posiList,
List<swap_position> realPosiList,
List<eod_swap_position> eodPositions,
List<eod_swap_position> todyEodPositions,
DateTime settleDate,
trade td,
DateTime preSettleDate,
List<swap_flow_event> flowEvents)
{
string settleDateStr = settleDate.ToString("yyyy-MM-dd");
string preSettleDateStr = preSettleDate.ToString("yyyy-MM-dd");
Log.Info($"================开始处理{settleDateStr}浮动腿归档==================");
List<eod_swap_position> list = new List<eod_swap_position>();
Log.Info($"浮动腿归档各项参数如下:\n " +
$"settleDate为:{settleDateStr} \n" +
$"preSettleDate为:{preSettleDateStr} \n " +
$"td为:{td.id} \n " +
$"posiList为:{JsonHelper.Serialize(posiList)} \n " +
$"realPosiList为:{JsonHelper.Serialize(realPosiList)} \n " +
$"eodPositions为:{JsonHelper.Serialize(eodPositions)} \n " +
$"todyEodPositions为:{JsonHelper.Serialize(todyEodPositions)} \n " +
$"flowEvents为:{JsonHelper.Serialize(flowEvents)} \n ");
foreach (var posi in posiList)
{
Log.Info($"posi为:{JsonHelper.Serialize(posi, false)}");
var eodPosition = eodPositions.Where(x => x.PositionId == posi.id).FirstOrDefault();//上一日日终持仓信息
var tdEodPosition = todyEodPositions.FirstOrDefault(x => x.PositionId == posi.id);//当前结算日日终持仓信息
var unwindEvents = flowEvents.Where(x => x.PositionId == posi.id).ToList();//当前日平仓信息
var realPosition = realPosiList.FirstOrDefault(s => s.PositionId == posi.id);
eod_swap_position eodPosi = new eod_swap_position();
if (eodPosition == null)
{
eodPosi = SaveCurrentEodInitalPosi(posi, td, settleDate, preSettleDate, unwindEvents);
}
else if (unwindEvents.Count() == 0)
{
eodPosi = CopyEodPosition(eodPosition, tdEodPosition, td, settleDate, preSettleDate);
}
else
{
eodPosi = UpdateEodPosition(posi, eodPosition, tdEodPosition, td, settleDate, preSettleDate, unwindEvents);
}
Log.Info($"eodPosi为:{JsonHelper.Serialize(eodPosi, false)}");
list.Add(eodPosi);
}
Log.Info($"================{settleDateStr}浮动腿归档结束==================");
return list;
}
/// <summary>
/// 处理自动互换数据
/// </summary>
/// <param name="autoInterests"></param>
/// <param name="td"></param>
/// <param name="settleDate"></param>
/// <param name="swapDeals"></param>
/// <param name="interval">自动互换观察日信息,用于获取结算日期</param>
/// <param name="curEodPositions">当日浮动端EOD持仓</param>
/// <param name="tradeExtend">交易扩展信息</param>
/// <summary>
/// 自动互换(仅利息/预付金,不含分红)
/// </summary>
private void DealAutoInterests(List<swap_flow_event> autoInterests, trade td, DateTime settleDate, DateTime? preDealDate, decimal StockEqvNotional, IntervalModel interval)
{
if (autoInterests.Count == 0) return;
UnwindData unwindData = new UnwindData();
unwindData.SwapTradeId = td.id;
unwindData.ValueDate = settleDate;
unwindData.StartDate = preDealDate ?? td.StartDate.Value;
unwindData.NotionalValue = Convert.ToDecimal(td.OriginalStockEqvNotional ?? 0);
unwindData.PosiNotionalValue = StockEqvNotional;
unwindData.PayDate = settleDate;
autoInterests.ForEach(x => x.PayDate = settleDate);
var premiumModes = new List<int>() { (int)InterestModeEnum.初始预付金, (int)InterestModeEnum.追加预付金 };
var premiumInterests = autoInterests.Where(x => premiumModes.Contains(x.InterestMode)).ToList();
var interestLegs = autoInterests.Where(x => !premiumModes.Contains(x.InterestMode)).ToList();
decimal premiumTotal = 0;
premiumInterests.ForEach(x =>
{
var ratio = x.InterestDirection == (int)SwapDirectionEnum.收取 ? -1 : 1;
premiumTotal += x.InterestClosePnL * ratio;
});
unwindData.SwapMarginRebatePnl = premiumTotal;
decimal interestTotal = 0;
interestLegs.ForEach(x =>
{
var ratio = x.InterestDirection == (int)SwapDirectionEnum.收取 ? 1 : -1;
interestTotal += x.InterestClosePnL * ratio;
});
unwindData.SwapCloseAmount = interestTotal ;
unwindData.SwapDividendPnl = 0;
unwindData.SwapRealizedPnL = unwindData.SwapCloseAmount + unwindData.SwapMarginRebatePnl;
SaveAutoSwapDeal(td, autoInterests, unwindData, interval);
}
/// <summary>
/// 分红独立处理:当天有债券需要分红时,生成独立的分红自动互换事件
/// </summary>
private void DealDividends(List<eod_swap_position> curEodPositions, trade td, DateTime settleDate, trade_extend tradeExtend)
{
if (curEodPositions == null) return;
var hasDividend = curEodPositions.Any(x => x.PosiDividendSum != 0);
if (!hasDividend) return;
var dividendPayDateOffset = tradeExtend?.ExtendObj?.DividendPayDate ?? 1;
if (dividendPayDateOffset <= 0) return;
var payDays = dividendPayDateOffset - 1;
var dividendPayDate = QdpCalendarHelper.GetNonHoliday(settleDate.AddDays(payDays));
List<swap_flow_event> dividendEvents = new List<swap_flow_event>();
decimal dividendTotal = 0;
foreach (var eodPosi in curEodPositions.Where(x => x.PosiDividendSum != 0))
{
var dividendEvent = new swap_flow_event
{
SwapTradeId = td.id,
SwapTradeNo = td.TradeNumber,
EventType = (int)SwapEventTypeEnum.自动互换,
EventReason = "系统操作-分红",
EventDate = settleDate,
UnwindDate = settleDate,
PayDate = dividendPayDate,
PositionId = eodPosi.PositionId,
UnderlyingCode = eodPosi.UnderlyingCode,
UnderlyingInstrumentType = eodPosi.UnderlyingInstrumentType,
PayDirection = eodPosi.PosiDirection,
PositionType = eodPosi.PositionType,
PositionQty = eodPosi.PosiQuantity,
Quantity = 0,
ContractSize = eodPosi.ContractSize,
TradingAmountAvg = eodPosi.PosiGrossPrice,
TradingAmountNetAvg = eodPosi.PosiNetNoFeePrice,
PosiGrossPrice = eodPosi.PosiGrossPrice,
PosiNetPrice = eodPosi.PosiNetPrice,
MarkClosePnl = 0,//当日盯市不要计算分红
DividendIn = eodPosi.PosiDividendSum,
CloseFee = 0,
TradingFee = 0,
TradingFeePending = 0,
ClientId = td.ClientId,
DataState = (int)SwapFlowDateStateEnum.完成,
};
dividendEvents.Add(dividendEvent);
dividendTotal += eodPosi.PosiDividendSum;
eodPosi.TdCloseDividend += eodPosi.PosiDividendSum;
//当日也要展示 eodPosi.TdPosiDividend = 0;
eodPosi.RealizedDividend += eodPosi.PosiDividendSum;
//互换持仓价值要去掉已实现的
eodPosi.SwapPositionValue -= eodPosi.PosiDividendSum;
//已实现盈亏要加上已实现的
eodPosi.RealizedPnl += eodPosi.PosiDividendSum;
eodPosi.PosiDividendSum = 0;
eodPosi.PosiProfitSum -= eodPosi.TdCloseDividend;
}
UnwindData unwindData = new UnwindData();
unwindData.SwapTradeId = td.id;
unwindData.ValueDate = settleDate;
unwindData.StartDate = td.StartDate.Value;
unwindData.NotionalValue = Convert.ToDecimal(td.OriginalStockEqvNotional ?? 0);
unwindData.PosiNotionalValue = curEodPositions.Sum(x => x.PosiNotionalValue);
unwindData.PayDate = dividendPayDate;
unwindData.SwapDividendPnl = dividendTotal;
unwindData.SwapCloseAmount = 0;
unwindData.SwapMarginRebatePnl = 0;
unwindData.SwapRealizedPnL = dividendTotal;
SaveAutoSwapDeal(td,null , unwindData,null, dividendEvents:dividendEvents);
}
/// <summary>
/// 保存自动互换数据信息
/// </summary>
/// <param name="td"></param>
/// <param name="swap_Deal"></param>
/// <param name="interval">自动互换观察日信息,用于获取结算日期</param>
private long SaveAutoSwapDeal(trade td, List<swap_flow_event> flowEvents, UnwindData unwindData, IntervalModel interval, List<swap_flow_event> dividendEvents = null)
{
//td.UnWindDate = unwindData.ValueDate;
//优先使用 interval.SettlementDate 作为资金记录发生日期,如果没有则使用 ValueDate
var cashHappenDate = interval?.SettlementDate ?? unwindData.ValueDate;
int clientCashId = 0;
var clientCashIds = new List<int>();
// 利息腿:插入资金记录(使用系统操作_互换)
if (unwindData.SwapCloseAmount != 0)
{
clientCashId = AddClientCashInCashOut(td, Convert.ToDouble(-unwindData.SwapCloseAmount), ClientCashInCashOut.系统操作_互换, cashHappenDate);
clientCashIds.Add(clientCashId);
}
// 预付金腿:单独插入一条资金记录(系统操作_预付金返息)
if (unwindData.SwapMarginRebatePnl != 0)
{
clientCashId = AddClientCashInCashOut(td, Convert.ToDouble(-unwindData.SwapMarginRebatePnl), ClientCashInCashOut.系统操作_预付金返息, unwindData.ValueDate);
clientCashIds.Add(clientCashId);
}
unwindData.SwapCloseAmount = unwindData.SwapRealizedPnL;//需要算上预付金利息 和 分红; 只是不算预付金返还
// 分红:使用派息支付日偏移记录资金记录
if (unwindData.SwapDividendPnl != 0)
{
var dividendPayDate = (dividendEvents != null && dividendEvents.Count > 0)
? dividendEvents.First().PayDate.Value
: unwindData.ValueDate;
clientCashId = AddClientCashInCashOut(td, Convert.ToDouble(-unwindData.SwapDividendPnl), ClientCashInCashOut.系统操作_互换, dividendPayDate);
clientCashIds.Add(clientCashId);
}
unwindData.ClientCashIds = clientCashIds;
string data = JsonConvert.SerializeObject(unwindData);
var swapEvent = new SwapEventService(this).AddSwapEventDate(unwindData.ValueDate, unwindData.SwapTradeId, (int)SwapEventTypeEnum.自动互换, data, clientCashId, true, "系统操作-自动互换");//将互换总额存入事件
if (flowEvents!=null)
{
flowEvents.ForEach(x =>
{
x.EventId = swapEvent.id;
DbContext.swap_flow_event.Add(x);
});
UpdateInitalPostion(flowEvents, td.id);
}
// 保存分红事件
if (dividendEvents != null)
{
dividendEvents.ForEach(x =>
{
x.EventId = swapEvent.id;
DbContext.swap_flow_event.Add(x);
});
UpdateInitalPostion(dividendEvents, td.id);
}
return swapEvent.id;
}
/// <summary>
/// 互换更新实时持仓信息
/// </summary>
/// <param name="flowEvents"></param>
private void UpdateInitalPostion(List<swap_flow_event> flowEvents, int swapTradeId)
{
var positions = DbContext.swap_position.Where(x => !x.IsInitial && x.SwapTradeId == swapTradeId && !x.Invalid);
foreach (var position in positions)
{
var interest = flowEvents.FirstOrDefault(x => x.PositionId == position.id);
if (interest != null)
{
position.InterestAmount += interest.InterestAmount;
UpdateDbOption(position);
}
}
}
/// <summary>
/// 分页合成互换流水开平仓事件,暂时只按加权平均处理
/// </summary>
/// <param name="swapTradeId">互换交易id</param>
/// <param name="tradeDate">清算日期</param>
protected void ComposePage(int swapTradeId, List<swap_flow_event> flowEvents, DateTime tradeDate, bool needTrans = true)
{
// 同一标的 事件编码一致
var eventQuery = flowEvents.OrderBy(o => o.PositionId).ToList();
if (eventQuery.Count == 0)
{
return;
}
SwapTradeService swapTradeService = new SwapTradeService(this);
var trans = needTrans ? DbContext.Database.BeginTransaction() : null;
try
{
UnwindData unwindData = new UnwindData();
unwindData.SwapTradeId = swapTradeId;
var swapEvent = AddSwapEvent(tradeDate, swapTradeId, (int)SwapEventTypeEnum.合成持仓, string.Empty, 0, true, "系统操作-自动合成持仓");
var td = FindTrade(swapTradeId);
var preSettleDate = GetPreValueDate(tradeDate);//上一交易日期
List<int> eventTyps = new List<int>() { (int)SwapEventTypeEnum.平仓, (int)SwapEventTypeEnum.互换, (int)SwapEventTypeEnum.自动互换 };
List<int> removeEventTyps = new List<int>() { (int)SwapEventTypeEnum.平仓, (int)SwapEventTypeEnum.互换, (int)SwapEventTypeEnum.自动互换 };
ClearSwapPositionsForCompose(td, tradeDate, removeEventTyps);
td.trade_extend = FindTradeExtend(swapTradeId);
var allEodPositions = FindEodSwapPositions(swapTradeId, preSettleDate);
var eodPositions = allEodPositions.Where(x => x.ValueDate == preSettleDate).ToList();//上一日终持仓信息
var positions = FindSwapPositions(swapTradeId);
var oriPositions = positions.Where(x => x.IsInitial).ToList();
var realPositions = positions.Where(x => !x.IsInitial).ToList();
var fpositions = positions.Where(x => x.PosiDirection > 0).ToList();
decimal tdCloseQty = 0;
decimal totalPosiNotionalValue = 0;//总剩余名义本金
decimal tdCloseNotionalValue = 0;//当日平仓名义本金
var preDealDate = GetPreDealDate(swapTradeId, tradeDate, eventTyps);//上一次平仓/互换/自动互换事件日期
var lastEodSwap = FindEodSwap(swapTradeId, preSettleDate);
decimal stockEqvNotional = lastEodSwap == null ? Convert.ToDecimal(td.OriginalStockEqvNotional ?? 0) : lastEodSwap.NotionalValue;//上一日名义本金
unwindData.NotionalValue = stockEqvNotional;
List<swap_flow_event> longshortCloseInterests = new List<swap_flow_event>();
decimal tradePrice = 0;//开仓费
decimal allPosiNotionalValue = 0;
decimal longNotionalValue = realPositions.Where(s => s.PositionType == (int)PositionTypeFlag.Long).Sum(s => s.PosiNotionalValue);//剩余多头名义本金规模
decimal shortNotionalValue = realPositions.Where(s => s.PositionType == (int)PositionTypeFlag.Short).Sum(s => s.PosiNotionalValue);//剩余空头名义本金规模
foreach (var eventGroup in eventQuery.GroupBy(g => g.PositionId))//持仓标的腿合成持仓
{
var eventList = eventGroup.ToList();
var eventFlow = eventGroup.Last();
var fposition = fpositions.FirstOrDefault(n => n.PositionId == eventGroup.Key);
var position = fpositions.FirstOrDefault(n => n.id == eventGroup.Key);
var eodPayPosition = allEodPositions.Where(x => x.PositionId == eventFlow.PositionId).OrderByDescending(o => o.ValueDate).FirstOrDefault();//浮动腿 日终持仓信息
decimal netPrice = fposition == null ? 0 : fposition.PosiNetPrice;//持仓均价
decimal grossPrice = fposition == null ? 0 : fposition.PosiGrossPrice;//持仓均价-不含费
decimal netFeePrice = fposition == null ? 0 : fposition.PosiNetFeePrice ?? 0;//持仓净价-含费
decimal netNoFeePrice = fposition == null ? 0 : fposition.PosiNetNoFeePrice ?? 0;//持仓净价-不含费
decimal tradingFee = fposition == null ? 0 : fposition.PosiTradingFeePending;//持仓交易费用
decimal payQty = fposition == null ? 0 : fposition.PosiQuantity;//持仓数量
decimal posiNotionalValue = fposition == null ? 0 : fposition.PosiNotionalValue;//剩余名义本金
decimal dividendIn = 0;//当日浮动端分红
decimal tdDividendIn = 0;//当日浮动端平仓盈亏分红
decimal openQty = fposition == null ? 0 : fposition.PosiQuantity;//开仓数量
decimal openAmount = fposition == null ? 0 : openQty * grossPrice;//开仓累计成交金额不含费
decimal openAmountFee = fposition == null ? 0 : openQty * netPrice;//开仓累计成交金额含费
decimal openAmountNetFee = fposition == null ? 0 : openQty * netFeePrice;//开仓累计成交净价金额含费
decimal openAmountNet = fposition == null ? 0 : openQty * netNoFeePrice;//开仓累计成交净价金额不含费
decimal closeQty = 0;//当日平仓数量
decimal closeFee = 0;//当日平仓费用
decimal closeMtmPnl = 0;//当日平仓盈亏
var posiType = fposition == null ? 0 : fposition.PositionType;
var swapInsertEnum = SwapPushDataEnum.收益互换交易新增;
var swapPosiEnum = SwapPushDataEnum.互换资产交易新增;
if (fposition != null)
{
swapInsertEnum = SwapPushDataEnum.收益互换交易修改;
swapPosiEnum = SwapPushDataEnum.互换资产交易修改;
}
eventList.ForEach(x =>
{
x.EventId = swapEvent.id;
decimal ratio = x.EventType == 1 ? 1 : -1;//开仓为加法,平仓为减法
tradingFee = tradingFee + x.TradingFeePending;//开仓累计
if (x.EventType == 1)
{
openAmountFee = openAmountFee + x.TradingAmountFeeAvg * x.Quantity;
openAmount = openAmount + x.TradingAmountAvg * x.Quantity;
openAmountNetFee = openAmountNetFee + (x.TradingAmountNetFeeAvg * x.Quantity) ?? 0;
openAmountNet = openAmountNet + (x.TradingAmountNetAvg * x.Quantity) ?? 0;
openQty = openQty + x.Quantity;
if (posiType != x.PositionType)
{
payQty = x.Quantity;
posiType = x.PositionType;
}
else
{
payQty = payQty + x.Quantity;
}
}
else
{
decimal amount = x.MarkClosePnl + x.CloseFee + x.DividendIn;
//记录资金记录
if (amount != 0)
{
int clientCashId = AddClientCash(td, Convert.ToDouble(x.MarkClosePnl), ClientCashInCashOut.系统操作_平仓费, x.UnwindDate.Value);
x.ClientCashId = clientCashId;
}
payQty = payQty - x.Quantity;
closeQty = closeQty + x.Quantity;
closeFee = closeFee + x.CloseFee;
tdDividendIn = tdDividendIn + x.DividendIn;
tdCloseNotionalValue = tdCloseNotionalValue + x.TradingAmount;
}
closeMtmPnl = closeMtmPnl + x.MarkClosePnl;
x.PositionQty = payQty;
dividendIn = dividendIn + x.DividendIn;
x.DataState = (int)SwapFlowDateStateEnum.完成;
});
tdCloseQty += closeQty;
if (eventFlow.EventType == 1)//最后一条是开仓
{
payQty = eventFlow.Quantity;
}
bool newOpen = openQty != 0 && closeQty == 0;
if (openQty != 0 && closeQty == 0)//只有开仓,价格加权平均
{
netPrice = openAmountFee / openQty;//持仓均价=((上一日持仓含费均价*上一日持仓数量)+(开仓成交均价*开仓数量))/所有开仓数量
grossPrice = openAmount / openQty;//持仓均价-不含费=((上一日持仓不含费均价*上一日持仓数量)+(开仓成交均价-不含费*开仓数量))/所有开仓数量
netFeePrice = openAmountNetFee / openQty;
netNoFeePrice = openAmountNet / openQty;
}
else if (posiType != fposition?.PositionType)//平仓完新开仓
{
netPrice = eventFlow.TradingAmountFeeAvg;
grossPrice = eventFlow.TradingAmountAvg;
netFeePrice = eventFlow.TradingAmountNetFeeAvg ?? 0;
netNoFeePrice = eventFlow.TradingAmountNetAvg ?? 0;
}
if (eodPayPosition == null)//无日终持仓
{
eodPayPosition = new eod_swap_position();
eodPayPosition.PosiStartDate = eventFlow.PayDate.Value;
eodPayPosition.ClientId = td.ClientId;
eodPayPosition.SwapTradeId = td.id;
eodPayPosition.ContractSize = eventFlow.ContractSize;
}
posiNotionalValue = eventFlow.ContractSize * netPrice * Math.Abs(payQty);
totalPosiNotionalValue = totalPosiNotionalValue + posiNotionalValue;
allPosiNotionalValue += posiNotionalValue;
tradePrice += SaveEodPosition(eodPayPosition, td, eventFlow, netPrice, grossPrice, netFeePrice, netNoFeePrice, payQty, tradingFee, posiNotionalValue, dividendIn, tdDividendIn, closeQty, closeFee, closeMtmPnl, posiType, fposition == null);
}
if (tdCloseQty != 0)
{
td.HasPartialUnWind = 1;
}
// td.StockEqvNotional += Convert.ToDouble(totalPosiNotionalValue);
td.TradePrice += Convert.ToDouble(tradePrice);
unwindData.PosiNotionalValue = allPosiNotionalValue;
unwindData.CloseNotionalValue = tdCloseNotionalValue;
swapEvent.EventData = JsonHelper.Serialize(unwindData);
SaveAllChanges();
SaveEodSwapRecord(td, tradeDate, preSettleDate);
SaveAllChanges();
trans?.Commit();
}
catch (Exception ex)
{
trans?.Rollback();
throw new Exception(ex.Message, ex);
}
finally
{
trans?.Dispose();
}
}
/// <summary>
/// 产生互换用
/// </summary>
/// <param name="eodPayPosition">上一日日终归档</param>
/// <param name="newEodPayPosition">当日归档</param>
/// <param name="position">持仓腿</param>
/// <param name="td">主体交易</param>
/// <param name="startDate">计息开始日</param>
/// <param name="valueDate">计息结束日</param>
/// <param name="closeAmount">平仓金额</param>
protected void SaveEodInterestPosition(eod_swap_position eodPayPosition,
eod_swap_position newEodPayPosition,
swap_position position,
trade td,
DateTime valueDate,
List<swap_flow_event> flowEvents)
{
Log.Info($"eodPayPosition is {JsonHelper.Serialize(eodPayPosition, false)},newEodPayPosition is {JsonHelper.Serialize(newEodPayPosition, false)}");
if (eodPayPosition == null)
{
eodPayPosition = new eod_swap_position();
eodPayPosition.ClientId = td.ClientId;
eodPayPosition.SwapTradeId = td.id;
eodPayPosition.PosiStartDate = position.PosiStartDate;
eodPayPosition.PosiMatuirityDate = td.ExerciseDate.Value;
}
var tradeExtend = td.trade_extend.ExtendObj;
decimal ratio = position.InterestDirection == (int)SwapDirectionEnum.收取 ? 1m : -1m;//收取为正,支付为负
if (marginTypes.Contains(position.InterestMode))
{
ratio = -ratio;
}
if (newEodPayPosition == null)
{
newEodPayPosition = new eod_swap_position();
newEodPayPosition.ClientId = td.ClientId;
newEodPayPosition.SwapTradeId = td.id;
newEodPayPosition.PosiStartDate = position.PosiStartDate;
eodPayPosition.PosiMatuirityDate = td.ExerciseDate.Value;
}
newEodPayPosition.ValueDate = valueDate;
newEodPayPosition.PositionId = position.id;
newEodPayPosition.ClientId = td.ClientId;
newEodPayPosition.SwapTradeId = td.id;
UpdateDbOption(newEodPayPosition);
newEodPayPosition.PosiStatus = 0;
newEodPayPosition.Invalid = false;
//持仓内容-利息腿
newEodPayPosition.InterestDirection = position.InterestDirection;
newEodPayPosition.InterestMode = position.InterestMode;
newEodPayPosition.InterestPrincipalFix = position.InterestPrincipalFix;
newEodPayPosition.InterestRateDefault = position.InterestRateDefault;
newEodPayPosition.InterestSwapInterval = position.InterestSwapInterval;
newEodPayPosition.IsAnnualized = position.IsAnnualized;
newEodPayPosition.HappenDate = position.HappenDate;
newEodPayPosition.Currency = position.Currency;
newEodPayPosition.InterestType = position.InterestType;
newEodPayPosition.interest_rest_days = position.interest_rest_days;
newEodPayPosition.interest_rule = position.interest_rule;
newEodPayPosition.FloatRate = flowEvents.FirstOrDefault()?.FloatRate ?? 0;
newEodPayPosition.FloatRateUnderlyingCode = position.FloatRateUnderlyingCode;
newEodPayPosition.InterestFeePending = 0;
//利息端估值用信息
newEodPayPosition.TdInterestPrincipal = flowEvents.FirstOrDefault()?.InterestPrincipal ?? 0;
newEodPayPosition.TdInterestRate = flowEvents.FirstOrDefault()?.InterestRate ?? 0;
//当日已实现
newEodPayPosition.TdInterestFee = flowEvents.Sum(x => x.InterestFee);
newEodPayPosition.TdCloseInterest = flowEvents.Sum(x => x.InterestAmount);
newEodPayPosition.TdCloseInterestFee = newEodPayPosition.TdInterestFee;
//持仓内容-利息腿-损益统计(本方视角)
var intersetAcmount = newEodPayPosition.TdInterestPrincipal * (newEodPayPosition.TdInterestRate + newEodPayPosition.FloatRate);
if (position.IsAnnualized)
{
intersetAcmount /= tradeExtend.AnnualDays;
}
newEodPayPosition.TdInterestIncome = intersetAcmount;// 要算一下当天产生的利息
newEodPayPosition.InterestIncomeSum = eodPayPosition.InterestIncomeSum + newEodPayPosition.TdInterestIncome - newEodPayPosition.TdCloseInterest; //上一天待实现 + 当天产生的利息 - flowEvents的利息
newEodPayPosition.InterestFeeSum = eodPayPosition.InterestFeeSum + newEodPayPosition.TdInterestFee - newEodPayPosition.TdCloseInterestFee;
newEodPayPosition.InterestProfitSum = newEodPayPosition.InterestIncomeSum + newEodPayPosition.InterestFeeSum;
//持仓价值
newEodPayPosition.SwapPositionValue = newEodPayPosition.InterestProfitSum * ratio + newEodPayPosition.PosiProfitSum;
//累计已实现
newEodPayPosition.RealizedInterest = eodPayPosition.RealizedInterest + newEodPayPosition.TdCloseInterest * ratio;
newEodPayPosition.RealizedInterestFee = eodPayPosition.RealizedInterestFee + newEodPayPosition.TdCloseInterestFee;
var currencyRate = GetCurrencyRate(td.QuoteCurrency, td.SettlementCurrency, valueDate, true,
position.InterestDirection == (int)SwapDirectionEnum.收取 ? CurrencyRateType.Buy : CurrencyRateType.Sell);
newEodPayPosition.TdCurrency = Convert.ToDecimal(currencyRate);
PersistEodSwapPosition(newEodPayPosition);
}
/// <summary>
/// 自动互换用,当日无互换,当日无平仓
/// </summary>
/// <param name="eodPayPosition">上一日日终持仓</param>
/// <param name="newEodPayPosition">当前收盘日日终持仓</param>
/// <param name="position">利息腿信息</param>
/// <param name="td">框架合约</param>
/// <param name="valueDate">当前收盘日</param>
/// <param name="interval">当前观察日</param>
/// <param name="preDealDate">上一平仓/互换日期</param>
/// <param name="closeAmount">当日平仓金额</param>
/// <param name="lastEodSwap">上一日终框架合约估值</param>
protected List<swap_flow_event> SaveAutoEodInterestPosition(eod_swap_position eodPayPosition, eod_swap_position newEodPayPosition, swap_position position, trade td, DateTime valueDate, IntervalModel interval, eod_swap lastEodSwap, decimal posiLongNotional, decimal posiShortNational, decimal grossPrice, decimal orginPv)
{
Log.Info($"[SaveAutoEodInterestPosition] 开始执行 - valueDate: {valueDate:yyyy-MM-dd}, td.id: {td?.id}, position.id: {position?.id}");
// 详细的参数验证
if (td == null)
{
Log.Info("[SaveAutoEodInterestPosition] 参数验证: td (trade) 为 null");
throw new ArgumentNullException(nameof(td), "交易对象不能为null");
}
if (position == null)
{
Log.Info($"[SaveAutoEodInterestPosition] 参数验证: position 为 null, td.id: {td.id}");
throw new ArgumentNullException(nameof(position), "持仓对象不能为null");
}
if (interval == null)
{
Log.Info($"[SaveAutoEodInterestPosition] 参数验证: interval 为 null, td.id: {td.id}, position.id: {position.id}");
throw new ArgumentNullException(nameof(interval), "观察日信息不能为null");
}
if (td.trade_extend == null)
{
Log.Info($"[SaveAutoEodInterestPosition] 参数验证: td.trade_extend 为 null, td.id: {td.id}, td.TradeNumber: {td.TradeNumber}");
throw new ArgumentNullException("td.trade_extend", "交易扩展信息不能为null");
}
Log.Info($"eodPayPosition is {JsonHelper.Serialize(eodPayPosition, false)},newEodPayPosition is {JsonHelper.Serialize(newEodPayPosition, false)}");
// 验证 ExtendObj
if (td.trade_extend.ExtendObj == null)
{
Log.Info($"[SaveAutoEodInterestPosition] 参数验证: td.trade_extend.ExtendObj 为 null, td.id: {td.id}");
throw new ArgumentNullException("td.trade_extend.ExtendObj", "交易扩展对象不能为null");
}
var tradeExtend = td.trade_extend.ExtendObj;
decimal oriPosiNotionalValue = posiLongNotional + posiShortNational;
decimal posiNotionalValue = oriPosiNotionalValue;
bool longShort = td.StructureType == ClientMarginTypeEnum.多空组合.ToString();
if (lastEodSwap != null)
{
posiNotionalValue = lastEodSwap.NotionalValue;
}
decimal closePercent = 1;
decimal ratio = position.InterestDirection == (int)SwapDirectionEnum.收取 ? 1m : -1m;//收取为正,支付为负
if (marginTypes.Contains(position.InterestMode))
{
ratio = -ratio;
}
if (eodPayPosition == null)
{
eodPayPosition = new eod_swap_position();
eodPayPosition.ClientId = td.ClientId;
eodPayPosition.SwapTradeId = td.id;
eodPayPosition.PosiStartDate = td.StartDate.Value;
eodPayPosition.PosiMatuirityDate = td.ExerciseDate.Value;
}
if (newEodPayPosition == null)
{
newEodPayPosition = eodPayPosition.Clone();
newEodPayPosition.id = 0;
}
List<swap_position> positions = new List<swap_position>();
positions.Add(position);
List<eod_swap_position> preEodPositions = new List<eod_swap_position>();
preEodPositions.Add(eodPayPosition);
var interestModes = new List<int>() { (int)InterestModeEnum.固定值, (int)InterestModeEnum.初始预付金, (int)InterestModeEnum.追加预付金 };
if (interestModes.Contains(position.InterestMode))
{
orginPv = eodPayPosition.InterestPrincipalFix;
}
var interests = CalcSwapInterests(td, td.trade_extend, valueDate, valueDate, preEodPositions, positions, posiNotionalValue, posiLongNotional, posiShortNational, posiNotionalValue, closePercent, (int)SwapEventTypeEnum.自动互换, false, true, grossPrice, orginPv, true);
decimal InterestAmount = interests.Sum(x => x.InterestAmount);
decimal TdInterestAmount = interests.Sum(x => x.TdInterestAmount);
newEodPayPosition.ValueDate = valueDate;
newEodPayPosition.PositionId = position.id;
UpdateDbOption(newEodPayPosition);
newEodPayPosition.Invalid = false;
//持仓内容-利息腿
newEodPayPosition.InterestDirection = position.InterestDirection;
newEodPayPosition.InterestMode = position.InterestMode;
newEodPayPosition.InterestPrincipalFix = position.InterestPrincipalFix;
newEodPayPosition.InterestRateDefault = position.InterestRateDefault;
newEodPayPosition.InterestSwapInterval = position.InterestSwapInterval;
newEodPayPosition.IsAnnualized = position.IsAnnualized;
newEodPayPosition.HappenDate = position.HappenDate;
newEodPayPosition.Currency = position.Currency;
newEodPayPosition.InterestType = position.InterestType;
newEodPayPosition.interest_rest_days = position.interest_rest_days;
newEodPayPosition.interest_rule = position.interest_rule;
newEodPayPosition.FloatRate = interests.Count > 0 ? interests.First().FloatRate ?? 0 : 0;
newEodPayPosition.FloatRateUnderlyingCode = position.FloatRateUnderlyingCode;
newEodPayPosition.InterestFeePending = 0;
//利息端估值用信息
newEodPayPosition.TdInterestPrincipal = interestModes.Contains(position.InterestMode) ? eodPayPosition.InterestPrincipalFix : posiNotionalValue;
newEodPayPosition.TdInterestRate = interval.Rate;
//当日已实现
//newEodPayPosition.TdInterestFee = 0;
newEodPayPosition.TdCloseInterest = InterestAmount;
// newEodPayPosition.TdCloseInterestFee = newEodPayPosition.TdInterestFee;
//持仓内容-利息腿-损益统计(本方视角)
newEodPayPosition.TdInterestIncome = TdInterestAmount;
Log.Info($"InterestFeeSum is {eodPayPosition.InterestFeeSum},TdInterestFee is {newEodPayPosition.TdInterestFee},TdCloseInterestFee is {newEodPayPosition.TdCloseInterestFee}");
newEodPayPosition.InterestIncomeSum = 0;
newEodPayPosition.InterestFeeSum = eodPayPosition.InterestFeeSum + newEodPayPosition.TdInterestFee - newEodPayPosition.TdCloseInterestFee;
newEodPayPosition.InterestProfitSum = newEodPayPosition.InterestIncomeSum + newEodPayPosition.InterestFeeSum;
//持仓价值
newEodPayPosition.SwapPositionValue = newEodPayPosition.InterestProfitSum * ratio + newEodPayPosition.PosiProfitSum;
//累计已实现
newEodPayPosition.RealizedInterest = eodPayPosition.RealizedInterest + newEodPayPosition.TdCloseInterest * ratio;
newEodPayPosition.RealizedInterestFee = eodPayPosition.RealizedInterestFee + newEodPayPosition.TdCloseInterestFee;
newEodPayPosition.RealizedPnl = newEodPayPosition.RealizedInterest + newEodPayPosition.RealizedInterestFee;
var currencyRate = GetCurrencyRate(td.QuoteCurrency, td.SettlementCurrency, valueDate, true,
position.InterestDirection == (int)SwapDirectionEnum.收取 ? CurrencyRateType.Buy : CurrencyRateType.Sell);
newEodPayPosition.TdCurrency = Convert.ToDecimal(currencyRate);
PersistEodSwapPosition(newEodPayPosition);
Log.Info($"the last newEodPayPosition is {JsonHelper.Serialize(newEodPayPosition, false)}");
return interests;
}
/// <summary>
/// 自动互换用,当日无互换,当日有平仓
/// </summary>
/// <param name="eodPayPosition">上一日日终持仓</param>
/// <param name="newEodPayPosition">当前收盘日日终持仓 不可能为空</param>
/// <param name="position">利息腿信息</param>
/// <param name="td">框架合约</param>
/// <param name="valueDate">当前收盘日</param>
/// <param name="interval">当前观察日</param>
/// <param name="preDealDate">上一平仓/互换日期</param>
/// <param name="closeAmount">当日平仓金额</param>
/// <param name="lastEodSwap">上一日终框架合约估值</param>
/// <param name="unwintotal">平仓主信息</param>
protected List<swap_flow_event> SaveAutoEodWithCloseInterestPosition(eod_swap_position eodPayPosition, eod_swap_position newEodPayPosition, swap_position position, trade td, DateTime valueDate, IntervalModel interval, decimal posiLongNotional, decimal posiShortNational, List<swap_flow_event> flowEvents, decimal closeNational, bool autoSwap, decimal grossPrice, decimal orginPv)
{
Log.Info($"eodPayPosition is {JsonHelper.Serialize(eodPayPosition, false)},newEodPayPosition is {JsonHelper.Serialize(newEodPayPosition, false)}");
var tradeExtend = td.trade_extend.ExtendObj;
decimal oriPosiNotionalValue = posiLongNotional + posiShortNational + closeNational;
decimal posiNotionalValue = posiLongNotional + posiShortNational;
decimal ratio = position.InterestDirection == (int)SwapDirectionEnum.收取 ? 1m : -1m;//收取为正,支付为负
if (marginTypes.Contains(position.InterestMode))
{
ratio = -ratio;
}
// 首次日终结算可能包含当日收盘,因此尚无先前的日终利息持仓。
var lastInterestIncomeSum = eodPayPosition?.InterestIncomeSum ?? 0m;
eodPayPosition = new eod_swap_position();
eodPayPosition.ClientId = td.ClientId;
eodPayPosition.SwapTradeId = td.id;
eodPayPosition.PosiStartDate = td.StartDate.Value;
eodPayPosition.PosiMatuirityDate = td.ExerciseDate.Value;
if (newEodPayPosition == null)
{
newEodPayPosition = eodPayPosition.Clone();
newEodPayPosition.id = 0;
}
var interestModes = new List<int>() { (int)InterestModeEnum.固定值, (int)InterestModeEnum.初始预付金, (int)InterestModeEnum.追加预付金 };
if (interestModes.Contains(position.InterestMode))
{
orginPv = eodPayPosition.InterestPrincipalFix;
}
else
{
orginPv = posiNotionalValue;
}
decimal closePercent = oriPosiNotionalValue == 0 ? 0 : closeNational / oriPosiNotionalValue;
var eventType = autoSwap ? (int)SwapEventTypeEnum.自动互换 : (int)SwapEventTypeEnum.平仓;
bool longShort = td.StructureType == ClientMarginTypeEnum.多空组合.ToString();
if (!autoSwap)
{
closePercent = oriPosiNotionalValue == 0 ? 0 : closeNational / oriPosiNotionalValue;
if (longShort)
{
closePercent = 0;
}
}
List<swap_position> positions = new List<swap_position>();
positions.Add(position);
List<eod_swap_position> preEodPositions = new List<eod_swap_position>();
preEodPositions.Add(eodPayPosition);
var interests = CalcSwapInterests(td, td.trade_extend, valueDate, valueDate, preEodPositions, positions, posiNotionalValue, posiLongNotional, posiShortNational, closeNational, 1, eventType, false, true, grossPrice, orginPv, true, settment: false, newCalcLast: true);
decimal TdInterestAmount = interests.Sum(x => x.TdInterestAmount);
decimal InterestAmount = interests.Sum(x => x.InterestAmount);
newEodPayPosition.ValueDate = valueDate;
newEodPayPosition.PositionId = position.id;
UpdateDbOption(newEodPayPosition);
newEodPayPosition.Invalid = false;
//持仓内容-利息腿
newEodPayPosition.InterestDirection = position.InterestDirection;
newEodPayPosition.InterestMode = position.InterestMode;
newEodPayPosition.InterestPrincipalFix = position.InterestPrincipalFix;
newEodPayPosition.InterestPrincipalFix *= (1 - closePercent);
newEodPayPosition.InterestRateDefault = position.InterestRateDefault;
newEodPayPosition.InterestSwapInterval = position.InterestSwapInterval;
newEodPayPosition.IsAnnualized = position.IsAnnualized;
newEodPayPosition.HappenDate = position.HappenDate;
newEodPayPosition.Currency = position.Currency;
newEodPayPosition.InterestType = position.InterestType;
newEodPayPosition.FloatRate = interests.Count > 0 ? interests.First().FloatRate ?? 0 : 0;
newEodPayPosition.FloatRateUnderlyingCode = position.FloatRateUnderlyingCode;
newEodPayPosition.interest_rest_days = position.interest_rest_days;
newEodPayPosition.interest_rule = position.interest_rule;
//利息端估值用信息
newEodPayPosition.TdInterestPrincipal = interests.Count > 0 ? interests.First().InterestPrincipal : 0;
if (interval != null)
{
newEodPayPosition.TdInterestRate = interval.Rate;
}
else
{
newEodPayPosition.TdInterestRate = flowEvents.FirstOrDefault()?.InterestRate ?? 0;
}
//当日已实现,平仓时已处理
newEodPayPosition.TdInterestFee = flowEvents.Sum(s => s.InterestFee);
newEodPayPosition.TdCloseInterestFee = newEodPayPosition.TdInterestFee;
newEodPayPosition.TdCloseInterest = flowEvents.Sum(s => s.InterestClosePnL);
var intersetAcmount = newEodPayPosition.TdInterestPrincipal * (newEodPayPosition.TdInterestRate + newEodPayPosition.FloatRate);
if (position.IsAnnualized)
{
intersetAcmount /= tradeExtend.AnnualDays;
}
newEodPayPosition.TdInterestIncome = intersetAcmount;
Log.Info($"InterestIncomeSum is {lastInterestIncomeSum},TdInterestIncome is {newEodPayPosition.TdInterestIncome}" +
$",TdCloseInterest is {newEodPayPosition.TdCloseInterest}");
Log.Info($"InterestFeeSum is {eodPayPosition.InterestFeeSum},TdInterestFee is {newEodPayPosition.TdInterestFee}" +
$",TdCloseInterestFee is {newEodPayPosition.TdCloseInterestFee}");
if (closePercent == 1)
{
newEodPayPosition.InterestIncomeSum = 0;
}
else
{
newEodPayPosition.InterestIncomeSum = InterestAmount;
}
//持仓内容-利息腿-损益统计(本方视角)
newEodPayPosition.InterestFeeSum = eodPayPosition.InterestFeeSum + newEodPayPosition.TdInterestFee - newEodPayPosition.TdCloseInterestFee;
newEodPayPosition.InterestProfitSum = newEodPayPosition.InterestIncomeSum + newEodPayPosition.InterestFeeSum;
//持仓价值
newEodPayPosition.SwapPositionValue = newEodPayPosition.InterestProfitSum * ratio + newEodPayPosition.PosiProfitSum;
Log.Info($"InterestIncomeSum is {eodPayPosition.InterestIncomeSum},TdInterestIncome is {newEodPayPosition.TdInterestIncome}" +
$",TdCloseInterest is {newEodPayPosition.TdCloseInterest}");
Log.Info($"InterestFeeSum is {eodPayPosition.InterestFeeSum},TdInterestFee is {newEodPayPosition.TdInterestFee}" +
$",TdCloseInterestFee is {newEodPayPosition.TdCloseInterestFee}");
//累计已实现
newEodPayPosition.RealizedInterest = eodPayPosition.RealizedInterest + newEodPayPosition.TdCloseInterest * ratio;
newEodPayPosition.RealizedInterestFee = eodPayPosition.RealizedInterestFee + newEodPayPosition.TdCloseInterestFee;
newEodPayPosition.RealizedPnl = newEodPayPosition.RealizedInterest + newEodPayPosition.RealizedInterestFee; ;
var currencyRate = GetCurrencyRate(td.QuoteCurrency, td.SettlementCurrency, valueDate, true,
position.InterestDirection == (int)SwapDirectionEnum.收取 ? CurrencyRateType.Buy : CurrencyRateType.Sell);
newEodPayPosition.TdCurrency = Convert.ToDecimal(currencyRate);
Log.Info($"即将插入数据库的 newEodPayPosition is {JsonHelper.Serialize(newEodPayPosition, false)}");
PersistEodSwapPosition(newEodPayPosition);
return interests;
}
/// <summary>
/// 利息腿复制上一日终归档信息
/// </summary>
/// <param name="eodPayPosition">上一日终利息腿持仓信息</param>
/// <param name="position">利息腿信息</param>
/// <param name="preSettleDate">上一交易日</param>
/// <param name="valueDate">当前结算日</param>
/// <param name="td">互换交易主干</param>
protected void SaveEodInterestPositionCopy(eod_swap_position eodPayPosition, eod_swap_position newEodPayPosition, DateTime valueDate, trade td, swap_position position, eod_swap lastEodSwap, bool needPrice, decimal posiLongNational, decimal posiShortNational, decimal grossPrice, decimal orginPv)
{
Log.Info($"eodPayPosition is {JsonHelper.Serialize(eodPayPosition, false)},newEodPayPosition is {JsonHelper.Serialize(newEodPayPosition, false)}");
List<IntervalModel> intervals = position.SwapIntervalList;
var tradeExtend = td.trade_extend.ExtendObj;
var interestModes = new List<int>() { (int)InterestModeEnum.固定值, (int)InterestModeEnum.初始预付金, (int)InterestModeEnum.追加预付金 };
if (eodPayPosition == null)
{
//if (position.PosiStartDate > valueDate)
//{
// return;
//}
eodPayPosition = new eod_swap_position();
eodPayPosition.InterestDirection = position.InterestDirection;
eodPayPosition.ClientId = td.ClientId;
eodPayPosition.SwapTradeId = td.id;
//eodPayPosition.PositionId = position.id; 为了算利息时找不到给日期重新赋值
eodPayPosition.InterestMode = position.InterestMode;
eodPayPosition.InterestPrincipalFix = position.InterestPrincipalFix;
eodPayPosition.InterestRateDefault = position.InterestRateDefault;
eodPayPosition.InterestSwapInterval = position.InterestSwapInterval;
eodPayPosition.TdInterestPrincipal = interestModes.Contains(position.InterestMode) ? eodPayPosition.InterestPrincipalFix : posiLongNational + posiShortNational;
eodPayPosition.PosiStartDate = td.StartDate.Value;
eodPayPosition.PosiMatuirityDate = td.ExerciseDate.Value;
eodPayPosition.IsAnnualized = position.IsAnnualized;
eodPayPosition.HappenDate = position.HappenDate;
eodPayPosition.Currency = position.Currency;
eodPayPosition.InterestType = position.InterestType;
eodPayPosition.FloatRate = position.FloatRate;
eodPayPosition.FloatRateUnderlyingCode = position.FloatRateUnderlyingCode;
eodPayPosition.interest_rest_days = position.interest_rest_days;
eodPayPosition.interest_rule = position.interest_rule;
}
if (newEodPayPosition == null)
{
newEodPayPosition = eodPayPosition.Clone();
newEodPayPosition.id = 0;
newEodPayPosition.PositionId = position.id;
}
if (interestModes.Contains(position.InterestMode))
{
orginPv = eodPayPosition.InterestPrincipalFix;
}
bool longShort = td.StructureType == ClientMarginTypeEnum.多空组合.ToString();
decimal oriPosiNotionalValue = posiLongNational + posiShortNational;
decimal posiNotionalValue = oriPosiNotionalValue;
if (lastEodSwap == null)
{
lastEodSwap = new eod_swap();
}
decimal closePercent = oriPosiNotionalValue == 0 ? 0 : posiNotionalValue / oriPosiNotionalValue;
if (longShort)
{
closePercent = 0;
}
if (td.ExerciseDate == valueDate)
{
closePercent = 1;
}
decimal ratio = eodPayPosition.InterestDirection == (int)SwapDirectionEnum.收取 ? 1m : -1m;//收取为正,支付为负
if (marginTypes.Contains(position.InterestMode))
{
ratio = -ratio;
}
List<swap_position> positions = new List<swap_position>
{
position
};
List<eod_swap_position> preEodPositions = new List<eod_swap_position>();
if (eodPayPosition.id != 0)
{
preEodPositions.Add(eodPayPosition);
}
var interests = CalcSwapInterests(td, td.trade_extend, valueDate, valueDate, preEodPositions, positions, posiNotionalValue, posiLongNational, posiShortNational, posiNotionalValue, closePercent, 0, false, needPrice, grossPrice, orginPv);
UpdateDbOption(newEodPayPosition);
newEodPayPosition.PosiStatus = 0;
newEodPayPosition.Invalid = false;
newEodPayPosition.ValueDate = valueDate;
decimal InterestAmount = interests.Sum(s => s.InterestAmount);
decimal TdInterestAmount = interests.Sum(x => x.TdInterestAmount);
//利息端估值用信息
newEodPayPosition.TdInterestPrincipal = interests.Count > 0 ? interests.First().InterestPrincipal : 0;
newEodPayPosition.TdInterestRate = interests.Count > 0 ? interests.First().InterestRate : 0;
newEodPayPosition.FloatRate = interests.Count > 0 ? interests.First().FloatRate ?? 0 : 0;
//当日已实现
newEodPayPosition.TdCloseInterest = 0;
newEodPayPosition.TdCloseInterestFee = 0;
//持仓内容-利息腿-损益统计(本方视角)
newEodPayPosition.TdInterestIncome = TdInterestAmount;
newEodPayPosition.TdInterestFee = 0;
Log.Info($"InterestIncomeSum is {eodPayPosition.InterestIncomeSum},TdInterestIncome is {newEodPayPosition.TdInterestIncome}" +
$",TdCloseInterest is {newEodPayPosition.TdCloseInterest}");
Log.Info($"InterestFeeSum is {eodPayPosition.InterestFeeSum},TdInterestFee is {newEodPayPosition.TdInterestFee}" +
$",TdCloseInterestFee is {newEodPayPosition.TdCloseInterestFee}");
newEodPayPosition.InterestIncomeSum = eodPayPosition.InterestIncomeSum + newEodPayPosition.TdInterestIncome - newEodPayPosition.TdCloseInterest;
newEodPayPosition.InterestFeeSum = eodPayPosition.InterestFeeSum + newEodPayPosition.TdInterestFee - newEodPayPosition.TdCloseInterestFee;
newEodPayPosition.InterestProfitSum = newEodPayPosition.InterestIncomeSum + newEodPayPosition.InterestFeeSum;
//持仓价值
newEodPayPosition.SwapPositionValue = newEodPayPosition.InterestProfitSum * ratio + newEodPayPosition.PosiProfitSum;
//累计已实现
newEodPayPosition.RealizedInterest = eodPayPosition.RealizedInterest + newEodPayPosition.TdCloseInterest * ratio;
newEodPayPosition.RealizedInterestFee = eodPayPosition.RealizedInterestFee + newEodPayPosition.TdCloseInterestFee;
newEodPayPosition.RealizedPnl = newEodPayPosition.RealizedInterest + newEodPayPosition.RealizedInterestFee;
var currencyRate = GetCurrencyRate(td.QuoteCurrency, td.SettlementCurrency, valueDate, true,
eodPayPosition.InterestDirection == (int)SwapDirectionEnum.收取 ? CurrencyRateType.Buy : CurrencyRateType.Sell);
newEodPayPosition.TdCurrency = Convert.ToDecimal(currencyRate);
Log.Info($"the last newEodPayPosition is {JsonHelper.Serialize(newEodPayPosition, false)}");
PersistEodSwapPosition(newEodPayPosition);
}
/// <summary>
/// 持仓腿生成期初持仓及归档信息
/// </summary>
/// <param name="eodPayPosition">上一日终持仓信息</param>
/// <param name="td">合约框架</param>
/// <param name="eventFlow">最后一条事件流水</param>
/// <param name="netPrice">期初价格</param>
/// <param name="grossPrice">期初价格不含费</param>
/// <param name="payQty">剩余持仓数量</param>
/// <param name="tradingFee">开仓交易佣金费用</param>
/// <param name="posiNotionalValue">当前剩余名义本金</param>
/// <param name="dividendIn">当日浮动端分红</param>
/// <param name="tdDividendIn">当日浮动端平仓盈亏分红</param>
/// <param name="closeQty">当日平仓数量</param>
/// <param name="closeFee">当日平仓费用</param>
/// <param name="closeMtmPnl">当日浮动盈亏</param>
protected virtual decimal SaveEodPosition(eod_swap_position newEodPayPosition,
trade td,
swap_flow_event eventFlow,
decimal netPrice,
decimal grossPrice,
decimal netFeePrice,
decimal netNoFeePrice,
decimal payQty,
decimal tradingFee,
decimal posiNotionalValue,
decimal dividendIn,
decimal tdDividendIn,
decimal closeQty,
decimal closeFee,
decimal closeMtmPnl,
int posiType,
bool open)
{
payQty = Math.Abs(payQty);
decimal ratio = eventFlow.PayDirection == (int)SwapDirectionEnum.收取 ? 1 : -1;//收取为正,支付为负
decimal shortRatio = newEodPayPosition.PositionType == (int)PositionTypeFlag.Long ? 1 : -1;//多空方向
newEodPayPosition.ValueDate = eventFlow.PayDate.Value;
newEodPayPosition.PositionId = eventFlow.PositionId;
newEodPayPosition.ClientId = td.ClientId;
newEodPayPosition.SwapTradeId = td.id;
//持仓内容-浮动收益腿
newEodPayPosition.PosiDirection = eventFlow.PayDirection;
newEodPayPosition.PositionType = posiType;
newEodPayPosition.UnderlyingCode = eventFlow.UnderlyingCode;
newEodPayPosition.UnderlyingInstrumentType = eventFlow.UnderlyingInstrumentType;
newEodPayPosition.ContractSize = eventFlow.ContractSize;
newEodPayPosition.CountRatio = eventFlow.CountRatio;
newEodPayPosition.PosiNetPrice = netPrice;
newEodPayPosition.PosiGrossPrice = grossPrice;
newEodPayPosition.PosiNetFeePrice = netFeePrice;
newEodPayPosition.PosiNetNoFeePrice = netNoFeePrice;
newEodPayPosition.PosiQuantity = payQty;
newEodPayPosition.PosiNotionalValue = posiNotionalValue;
newEodPayPosition.PosiMatuirityDate = td.ExerciseDate.Value;
if (newEodPayPosition.PosiQuantity == 0)
{
newEodPayPosition.PosiMatuirityDate = eventFlow.PayDate.Value;
}
//else
//{
// newEodPayPosition.PosiMatuirityDate = td.ExerciseDate.Value;
//}
newEodPayPosition.PosiFeePending = tradingFee;
//浮动端估值用信息
newEodPayPosition.UnderlyingPrice = UnderlyingCodePrice(newEodPayPosition.UnderlyingCode, eventFlow.EventDate, out decimal vobp);
newEodPayPosition.dv01 = Dv01Helper.CalcDv01(newEodPayPosition.UnderlyingCode, newEodPayPosition.PosiQuantity, newEodPayPosition.PosiDirection, newEodPayPosition.PositionType, vobp);
newEodPayPosition.UnderlyingMarketValue = newEodPayPosition.UnderlyingPrice * newEodPayPosition.PosiQuantity * newEodPayPosition.ContractSize * shortRatio;
//当日已实现
newEodPayPosition.TdCloseQty = closeQty;
newEodPayPosition.TdChangedQty = 0;
newEodPayPosition.TdCloseMtmPnl = closeMtmPnl * ratio;
newEodPayPosition.TdCloseDividend = tdDividendIn * ratio;
newEodPayPosition.TdCloseFee = closeFee * ratio;
//持仓内容-浮动收益腿-损益统计(本方视角
newEodPayPosition.TdPosiDividend = Math.Round(dividendIn * ratio, 2);
newEodPayPosition.PosiMtmPnL = (newEodPayPosition.UnderlyingPrice - newEodPayPosition.PosiGrossPrice) * newEodPayPosition.PosiQuantity * newEodPayPosition.ContractSize * shortRatio * ratio;
newEodPayPosition.PosiDividendSum = Math.Round(newEodPayPosition.TdPosiDividend - newEodPayPosition.TdCloseDividend, 2);
newEodPayPosition.PosiProfitSum = newEodPayPosition.PosiMtmPnL + newEodPayPosition.PosiDividendSum + newEodPayPosition.PosiFeePending;
//持仓价值
newEodPayPosition.SwapPositionValue = newEodPayPosition.InterestProfitSum + newEodPayPosition.PosiProfitSum;
//累计已实现
newEodPayPosition.RealizedFee = closeFee;
newEodPayPosition.RealizedMtmPnL = newEodPayPosition.TdCloseMtmPnl;
newEodPayPosition.RealizedDividend = newEodPayPosition.TdCloseDividend;
newEodPayPosition.RealizedPnl = newEodPayPosition.TdCloseMtmPnl;
newEodPayPosition.PosiStatus = payQty == 0 ? 1 : 0;
UpdateDbOption(newEodPayPosition);
newEodPayPosition.Invalid = false;
var currencyRate = new EodCurrencyRateService(UserInfo).GetCurrencyRate(td.QuoteCurrency, td.SettlementCurrency, eventFlow.EventDate
, seekPreday: true, currencyRateType: posiNotionalValue < 0 ? CurrencyRateType.Buy : CurrencyRateType.Sell);
newEodPayPosition.TdCurrency = Convert.ToDecimal(currencyRate);
decimal posiTradingFee = 0;
if (open)//更新新开仓持仓腿信息,因为在生成开仓事件时,先生成了空的持仓腿信息
{
if (td.trade_extend.ExtendObj.NeedOpenFee && td.TradeDate == eventFlow.EventDate)//开仓
{
posiTradingFee = Math.Abs(newEodPayPosition.PosiTradingFee) * Convert.ToDecimal(currencyRate);
}
UpdateSwapPosition(newEodPayPosition, td.TradeNumber);
}
UpdateSwapPositionWithRealTime(newEodPayPosition);
PersistEodSwapPosition(newEodPayPosition);
return posiTradingFee;
}
/// <summary>
///当日无平仓,无互换,生成持仓腿日终归档,适用于上一日终存在
/// </summary>
/// <param name="eod_Swap_Positions">上一日日终归档信息</param>
/// <param name="todayPositions">当日日终归档信息</param>
/// <param name="swap_Deals">当日平仓/互换事件信息</param>
/// <param name="td">交易信息</param>
protected eod_swap_position CopyEodPosition(eod_swap_position eod, eod_swap_position curretEod, trade td, DateTime valueDate, DateTime preSettleDate)
{
if (curretEod == null)
{
curretEod = eod.Clone();
curretEod.id = 0;
curretEod.ValueDate = valueDate;
}
var um = GetUnderlyingData(eod.UnderlyingCode);
if (um == null)
{
return curretEod;
}
var dealDate = curretEod.ValueDate;
int shortRatio = eod.PositionType == (int)PositionTypeFlag.Long ? 1 : -1;
int directionRatio = eod.PosiDirection == (int)SwapDirectionEnum.收取 ? 1 : -1;
curretEod.PosiStatus = curretEod.PosiQuantity == 0 ? 1 : 0;
var price = GetUnderlyingPrice(eod.UnderlyingCode, dealDate, out decimal vobp);
curretEod.dv01 = Dv01Helper.CalcDv01(eod.UnderlyingCode, curretEod.PosiQuantity, eod.PosiDirection, eod.PositionType, vobp);
decimal tax = um.ValueAddedTax ?? 0;
if (valueDate > td.StartDate.Value && curretEod.PosiQuantity > 0)
{
decimal payment = CalcBondPayment(curretEod.UnderlyingCode, eod.ValueDate, valueDate, curretEod.PosiQuantity, shortRatio, directionRatio);
// 考虑增值税
curretEod.TdPosiDividend = Math.Round(payment / (1 + tax) * (1 - tax), 2);
}
curretEod.PosiDividendSum = eod.PosiQuantity > 0 ? Math.Round(eod.PosiDividendSum + curretEod.TdPosiDividend, 2) : 0;
curretEod.PosiQuantity = eod.PosiQuantity;
if (curretEod.PosiStatus == 1)
{
curretEod.PosiNotionalValue = 0;
}
curretEod.UnderlyingPrice = price;
curretEod.UnderlyingMarketValue = curretEod.UnderlyingPrice * curretEod.PosiQuantity * curretEod.ContractSize * shortRatio;
curretEod.PosiMtmPnL = (curretEod.UnderlyingPrice - curretEod.PosiGrossPrice) * curretEod.PosiQuantity * curretEod.ContractSize * shortRatio * directionRatio;
//curretEod.TdPosiDividend = 0;
//curretEod.PosiDividendSum = eod.PosiDividendSum + curretEod.TdPosiDividend;
curretEod.PosiProfitSum = curretEod.PosiMtmPnL + curretEod.PosiDividendSum + curretEod.PosiFeePending;
curretEod.TdCloseFee = 0;
curretEod.TdCloseQty = 0;
curretEod.TdCloseMtmPnl = 0;
// 需要计算平仓盈亏分红
curretEod.TdCloseDividend = 0;
curretEod.RealizedMtmPnL = eod.RealizedMtmPnL + curretEod.TdCloseMtmPnl;
curretEod.RealizedDividend = eod.RealizedDividend + curretEod.TdCloseDividend;
curretEod.RealizedFee = eod.RealizedFee + curretEod.TdCloseFee;
curretEod.RealizedPnl = eod.RealizedPnl + curretEod.TdCloseMtmPnl;
var currencyRate = new EodCurrencyRateService(UserInfo).GetCurrencyRate(td.QuoteCurrency, td.SettlementCurrency, td.StartDate.Value
, seekPreday: true, currencyRateType: curretEod.PosiDirection == (int)SwapDirectionEnum.收取 ? CurrencyRateType.Buy : CurrencyRateType.Sell);
curretEod.TdCurrency = Convert.ToDecimal(currencyRate);
//持仓价值
curretEod.SwapPositionValue = curretEod.InterestProfitSum + curretEod.PosiProfitSum;
UpdateDbOption(curretEod);
curretEod.Invalid = false;
if (curretEod.id == 0)
{
PersistEodSwapPosition(curretEod);
}
return curretEod;
}
/// <summary>
/// 更新虚拟交易费用
/// </summary>
/// <param name="curretEod"></param>
private void UpdateVTradingFee(eod_swap_position curretEod)
{
//int ratio = curretEod.PositionType == (int)PositionTypeFlag.Long ? -1 : 1;
//int shortRatio = curretEod.PositionType == (int)PositionTypeFlag.Long ? 1 : -1;
//var priceFee = curretEod.PosiQuantity == 0 ? 0 : curretEod.UnderlyingPrice + curretEod.PosiFeePending / curretEod.PosiQuantity * ratio;
//curretEod.VTradingFee = -(priceFee - curretEod.PosiNetPrice - curretEod.UnderlyingPrice + curretEod.PosiGrossPrice) * curretEod.PosiNotionalValue * shortRatio;
curretEod.VTradingFee = curretEod.PosiFeePending * 2;
}
/// <summary>
/// 当日有平仓/互换,生成持仓腿日终归档,适用于上一日终存在
/// </summary>
/// <param name="eod"></param>
/// <param name="curretEod"></param>
/// <param name="td"></param>
/// <param name="valueDate"></param>
protected eod_swap_position UpdateEodPosition(swap_position swapPosition, eod_swap_position eod, eod_swap_position curretEod, trade td, DateTime valueDate, DateTime preSettleDate, List<swap_flow_event> unwindEvents)
{
if (curretEod == null)
{
curretEod = eod.Clone();
curretEod.TdPosiDividend = 0;
curretEod.id = 0;
curretEod.ValueDate = valueDate;
}
var um = GetUnderlyingData(eod.UnderlyingCode);
if (um == null)
{
return curretEod;
}
var dealDate = curretEod.ValueDate;
int shortRatio = eod.PositionType == (int)PositionTypeFlag.Long ? 1 : -1;
int directionRatio = eod.PosiDirection == (int)SwapDirectionEnum.收取 ? 1 : -1;
var price = GetUnderlyingPrice(eod.UnderlyingCode, dealDate, out decimal vobp);
var todayConsumedDividend = CalcConsumedDividend(curretEod, unwindEvents);
var originNotional = (decimal)td.OriginalStockEqvNotional / swapPosition.PosiNetPrice;
decimal totalPayment = CalcBondPayment(curretEod.UnderlyingCode, td.StartDate.Value, valueDate, (decimal)originNotional, shortRatio, directionRatio);
decimal tax = um.ValueAddedTax ?? 0;
decimal totalInterest = totalPayment / (1 + tax) * (1 - tax);
SetPriceInfoByFlowEvent(eod, curretEod, unwindEvents, swapPosition);
curretEod.dv01 = Dv01Helper.CalcDv01(eod.UnderlyingCode, curretEod.PosiQuantity, eod.PosiDirection, eod.PositionType, vobp);
curretEod.UnderlyingPrice = price;
curretEod.UnderlyingMarketValue = curretEod.UnderlyingPrice * curretEod.PosiQuantity * curretEod.ContractSize * shortRatio;
curretEod.PosiMtmPnL = (curretEod.UnderlyingPrice - curretEod.PosiGrossPrice) * curretEod.PosiQuantity * curretEod.ContractSize * shortRatio * directionRatio;
curretEod.TdPosiDividend = 0;
// 分红与互换无关,只要持仓>0且起始日早于当前日,正常计算当日分红
// 修改,互换事件会影响待实现的分红的,现在要算上
if (valueDate > td.StartDate.Value && (curretEod.PosiQuantity > 0))
{
decimal payment = CalcBondPayment(curretEod.UnderlyingCode, eod.ValueDate, valueDate, curretEod.PosiQuantity, shortRatio, directionRatio);
curretEod.TdPosiDividend = Math.Round(payment / (1 + tax) * (1 - tax), 2);
}
curretEod.RealizedMtmPnL = eod.RealizedMtmPnL + curretEod.TdCloseMtmPnl;
// 当日浮动端平仓盈亏·分红(仅来自平仓事件 和 互换 中已实现的分红)
curretEod.TdCloseDividend = unwindEvents.Sum(e => e.DividendIn);
curretEod.RealizedFee = eod.RealizedFee + curretEod.TdCloseFee;
curretEod.RealizedPnl = eod.RealizedPnl + curretEod.TdCloseMtmPnl;
curretEod.PosiStatus = curretEod.PosiQuantity == 0 ? 1 : 0;
var closeQty = unwindEvents.Where(x => x.EventType == (int)SwapFlowEventTypeEnum.平仓).ToList().Sum(s => s.Quantity);
curretEod.RealizedDividend = curretEod.RealizedDividend + curretEod.TdCloseDividend;
// 分红与互换解耦:持仓>0时待实现分红用递增模式(前日+当天新计-当天实现),
// 与 CopyEodPosition 的逐天递增口径一致,避免从头重算的舍入累积差异。
if (curretEod.PosiQuantity > 0)
{
curretEod.PosiDividendSum = eod.PosiDividendSum + curretEod.TdPosiDividend - curretEod.TdCloseDividend;
}
else
{
curretEod.PosiDividendSum = 0;
}
curretEod.RealizedPnl += curretEod.TdCloseDividend;
curretEod.SwapPositionValue -= curretEod.TdCloseDividend;
curretEod.PosiProfitSum = curretEod.PosiMtmPnL + curretEod.PosiDividendSum + curretEod.PosiFeePending;
if (curretEod.PosiStatus == 1)
{
curretEod.PosiNotionalValue = 0;
}
var currencyRate = new EodCurrencyRateService(UserInfo).GetCurrencyRate(td.QuoteCurrency, td.SettlementCurrency, td.StartDate.Value
, seekPreday: true, currencyRateType: curretEod.PosiDirection == (int)SwapDirectionEnum.收取 ? CurrencyRateType.Buy : CurrencyRateType.Sell);
curretEod.TdCurrency = Convert.ToDecimal(currencyRate);
//持仓价值
curretEod.SwapPositionValue = curretEod.InterestProfitSum + curretEod.PosiProfitSum;
UpdateDbOption(curretEod);
curretEod.Invalid = false;
if (curretEod.id == 0)
{
PersistEodSwapPosition(curretEod);
}
return curretEod;
}
private decimal CalcConsumedDividend(eod_swap_position curretEod, List<swap_flow_event> events)
{
decimal consumedDividend = 0;
List<int> swapEventTypes = new List<int>() { (int)SwapEventTypeEnum.互换, (int)SwapEventTypeEnum.自动互换 };
//这里要剔除掉平仓产生的分红
consumedDividend = events
.Where(x => x.SwapTradeId == curretEod.SwapTradeId
&& swapEventTypes.Contains(x.EventType)
&& x.DataState == (int)SwapFlowDateStateEnum.完成)
.Sum(s => s.DividendIn);
return consumedDividend;
}
/// <summary>
/// 根据开平仓事件算价格及后付费用
/// </summary>
/// <param name="eod"></param>
/// <param name="curretEod"></param>
/// <param name="unwindEvents"></param>
public void SetPriceInfoByFlowEvent(eod_swap_position eod, eod_swap_position curretEod, List<swap_flow_event> unwindEvents, swap_position position)
{
if (eod.PosiDirection == 0)
{
return;
}
int shortRatio = eod.PositionType == (int)PositionTypeFlag.Long ? 1 : -1;
int directionRatio = eod.PosiDirection == (int)SwapDirectionEnum.收取 ? 1 : -1;
var unwindFlowEvents = unwindEvents.Where(x => x.EventType == (int)SwapFlowEventTypeEnum.平仓).ToList();
var openFlowEvents = unwindEvents.Where(x => x.EventType == (int)SwapFlowEventTypeEnum.开仓).ToList();
decimal unwindQty = unwindFlowEvents.Sum(s => s.Quantity);
decimal openQty = openFlowEvents.Sum(s => s.Quantity);
var qty = eod.PosiQuantity + openQty - unwindQty;
curretEod.PosiQuantity = qty < 0 ? 0 : Math.Abs(qty);
if (unwindEvents.Count == 0)
{
curretEod.PosiNetPrice = position.PosiNetPrice;
curretEod.PosiGrossPrice = position.PosiGrossPrice;
curretEod.PosiNetFeePrice = position.PosiNetFeePrice;
curretEod.PosiNetNoFeePrice = position.PosiNetNoFeePrice;
curretEod.PosiQuantity = position.PosiQuantity;
curretEod.PosiFeePending = -position.PosiTradingFeePending * directionRatio;
}
else
{
var eventTradingFee = unwindEvents.Where(x => x.EventType == (int)SwapFlowEventTypeEnum.开仓 || x.EventType == (int)SwapFlowEventTypeEnum.平仓).Sum(s => s.TradingFeePending * (s.EventType == (int)SwapFlowEventTypeEnum.开仓 ? 1m : -1m));
curretEod.PosiFeePending = eod.PosiFeePending + eventTradingFee;
if (openFlowEvents.Count() == 0)
{
curretEod.PosiNetPrice = eod.PosiNetPrice;
curretEod.PosiGrossPrice = eod.PosiGrossPrice;
curretEod.PosiNetFeePrice = eod.PosiNetFeePrice;
curretEod.PosiNetNoFeePrice = eod.PosiNetNoFeePrice;
}
else //平仓数量一定<持仓数量
{
var posiQty = eod.PosiQuantity - unwindQty;
if (posiQty < 0)
{
posiQty = 0;
}
curretEod.PosiGrossPrice = (eod.PosiGrossPrice * eod.PosiQuantity + openFlowEvents.Sum(a => a.Quantity * a.TradingAmountAvg)) / (eod.PosiQuantity + openQty);
curretEod.PosiGrossPrice = Math.Round(curretEod.PosiGrossPrice, ConsGlobal.PriceRound, MidpointRounding.AwayFromZero);
curretEod.PosiNetPrice = (eod.PosiNetPrice * eod.PosiQuantity + openFlowEvents.Sum(a => a.Quantity * a.TradingAmountFeeAvg)) / (eod.PosiQuantity + openQty);
curretEod.PosiNetPrice = Math.Round(curretEod.PosiNetPrice, ConsGlobal.PriceRound, MidpointRounding.AwayFromZero);
curretEod.PosiNetNoFeePrice = (eod.PosiNetNoFeePrice * eod.PosiQuantity + openFlowEvents.Sum(a => a.Quantity * a.TradingAmountNetAvg)) / (eod.PosiQuantity + openQty);
curretEod.PosiNetNoFeePrice = Math.Round(curretEod.PosiNetNoFeePrice ?? 0, ConsGlobal.PriceRound, MidpointRounding.AwayFromZero);
curretEod.PosiNetFeePrice = (eod.PosiNetFeePrice * eod.PosiQuantity + openFlowEvents.Sum(a => a.Quantity * a.TradingAmountNetFeeAvg)) / (eod.PosiQuantity + openQty);
curretEod.PosiNetFeePrice = Math.Round(curretEod.PosiNetFeePrice ?? 0, ConsGlobal.PriceRound, MidpointRounding.AwayFromZero);
}
curretEod.PosiNotionalValue = curretEod.PosiGrossPrice * curretEod.PosiQuantity * curretEod.ContractSize;
curretEod.PosiNotionalValue = Math.Round(curretEod.PosiNotionalValue, ConsGlobal.MoneyRound, MidpointRounding.AwayFromZero);
curretEod.TdCloseDividend = unwindEvents.Sum(x => x.DividendIn);
curretEod.TdCloseFee = unwindFlowEvents.Sum(x => x.TradingFee + x.TradingFeePending);
curretEod.TdCloseQty = unwindQty;
curretEod.TdCloseMtmPnl = unwindEvents.Sum(x => x.MarkClosePnl);
}
if (curretEod.PosiQuantity == 0)
{
curretEod.PosiFeePending = 0;
}
UpdateVTradingFee(curretEod);
}
/// <summary>
/// 单标的浮动腿 首次归档
/// </summary>
/// <param name="position"></param>
/// <param name="td"></param>
/// <param name="settleDate"></param>
protected eod_swap_position SaveCurrentEodInitalPosi(swap_position position, trade td, DateTime settleDate, DateTime preSettleDate, List<swap_flow_event> unwindEvents)
{
eod_swap_position curretEod = new eod_swap_position();
var um = GetUnderlyingData(position.UnderlyingCode);
if (um == null)
{
return curretEod;
}
var dealDate = settleDate;
curretEod.ValueDate = settleDate;
curretEod.PosiStartDate = position.PosiStartDate;
curretEod.PosiMatuirityDate = td.ExerciseDate.Value;
curretEod.SwapTradeId = td.id;
curretEod.PositionId = position.id;
curretEod.ClientId = td.ClientId;
int shortRatio = position.PositionType == (int)PositionTypeFlag.Long ? 1 : -1;
int directionRatio = position.PosiDirection == (int)SwapDirectionEnum.收取 ? 1 : -1;
curretEod.PositionType = position.PositionType;
var eod = new eod_swap_position()
{
ContractSize = position.ContractSize,
PositionType = position.PositionType,
PosiDirection = position.PosiDirection,
PosiFeePending = 0,
PosiNetPrice = position.PosiNetPrice,
PosiGrossPrice = position.PosiGrossPrice,
PosiNetFeePrice = position.PosiNetFeePrice,
PosiNetNoFeePrice = position.PosiNetNoFeePrice,
};
curretEod.PosiDirection = position.PosiDirection;
curretEod.UnderlyingCode = position.UnderlyingCode;
curretEod.UnderlyingInstrumentType = position.UnderlyingInstrumentType;
curretEod.SwapTradeId = position.SwapTradeId;
curretEod.ContractSize = position.ContractSize;
curretEod.CountRatio = position.CountRatio;
curretEod.PosiTradingFee = position.PosiTradingFee;
curretEod.UnderlyingPrice = GetUnderlyingPrice(position.UnderlyingCode, dealDate, out decimal vobp);
SetPriceInfoByFlowEvent(eod, curretEod, unwindEvents, position);
curretEod.dv01 = Dv01Helper.CalcDv01(curretEod.UnderlyingCode, curretEod.PosiQuantity, curretEod.PosiDirection, curretEod.PositionType, vobp);
//if (settleDate == td.TradeDate)
//{
// curretEod.UnderlyingPrice = curretEod.PosiGrossPrice;
// //curretEod.TdCloseMtmPnl = 0;
// //curretEod.TdCloseFee = 0;
//}
// TdCloseDividend 已由 SetPriceInfoByFlowEvent 设置
// 当日新增分红及待实现分红(有互换全量归0,开仓首日两者相同)
curretEod.TdPosiDividend = 0;
var hasSwapEvent = unwindEvents.Any(e => e.EventType == (int)SwapFlowEventTypeEnum.互换 || e.EventType == (int)SwapFlowEventTypeEnum.自动互换);
if (!hasSwapEvent && settleDate > td.StartDate.Value && curretEod.PosiQuantity > 0)
{
decimal tax = um.ValueAddedTax ?? 0;
decimal payment = CalcBondPayment(curretEod.UnderlyingCode, td.StartDate.Value, settleDate, curretEod.PosiQuantity, shortRatio, directionRatio);
payment = Math.Round(payment / (1 + tax) * (1 - tax), 2);
//var consumedDividend = CalcConsumedDividend(curretEod, unwindEvents); 首日应该没有分红
curretEod.TdPosiDividend = payment;
curretEod.PosiDividendSum = payment;
}
curretEod.UnderlyingMarketValue = curretEod.UnderlyingPrice * curretEod.PosiQuantity * curretEod.ContractSize * shortRatio;
curretEod.PosiMtmPnL = (curretEod.UnderlyingPrice - curretEod.PosiGrossPrice) * curretEod.PosiQuantity * curretEod.ContractSize * shortRatio * directionRatio;
curretEod.PosiProfitSum = curretEod.PosiMtmPnL + curretEod.PosiDividendSum + curretEod.PosiFeePending;
curretEod.RealizedMtmPnL = curretEod.TdCloseMtmPnl;
curretEod.RealizedDividend = curretEod.TdCloseDividend;
curretEod.RealizedFee = curretEod.TdCloseFee;
curretEod.RealizedPnl = curretEod.TdCloseMtmPnl;
curretEod.PosiStatus = curretEod.PosiQuantity == 0 ? 1 : 0;
if (curretEod.PosiStatus == 1)
{
curretEod.PosiNotionalValue = 0;
}
//持仓价值
curretEod.SwapPositionValue = curretEod.InterestProfitSum + curretEod.PosiProfitSum;
var currencyRate = new EodCurrencyRateService(UserInfo).GetCurrencyRate(td.QuoteCurrency, td.SettlementCurrency, td.StartDate.Value
, seekPreday: true, currencyRateType: curretEod.PosiDirection == (int)SwapDirectionEnum.收取 ? CurrencyRateType.Buy : CurrencyRateType.Sell);
curretEod.TdCurrency = Convert.ToDecimal(currencyRate);
UpdateDbOption(curretEod);
curretEod.Invalid = false;
PersistEodSwapPosition(curretEod);
return curretEod;
}
/// <summary>
/// 获取标的收盘价格
/// </summary>
/// <param name="code">标的代码</param>
/// <param name="settleDate">收盘日</param>
/// <returns></returns>
public decimal UnderlyingCodePrice(string code, DateTime settleDate, out decimal vobp)
{
vobp = 0;
var data = DataCacheProvider.GetUnderlyingDataSource().GetData(code);
if (data == null)
{
return 0;
}
if (data.IsBond())
{
return BondPrice(data, settleDate, out vobp);
}
var price = data.Price ?? 0;
if (EodPriceQueryService.TryGetEodPrice(settleDate, code, out var eodPrice))
{
price = eodPrice.GetPrice(SettlementTypeEnum.ClosePrice);
}
return Convert.ToDecimal(price);
}
/// <summary>
/// 获取债券收盘价格
/// </summary>
/// <param name="code"></param>
/// <param name="settleDate"></param>
/// <returns></returns>
public decimal BondPrice(underlying_manager data, DateTime settleDate, out decimal vobp)
{
vobp = 0;
var price = data.Price ?? 0;
if (EodPriceQueryService.TryGetBondEodPrice(settleDate, data.UnderlyingCode, out var eodPrice))
{
price = eodPrice.GetPrice(SettlementTypeEnum.ClosePrice);
vobp = eodPrice.Vobp ?? 0;
}
else
{
price = price * Convert.ToDouble(ConsGlobal.bondPriceMultiple);
}
return Convert.ToDecimal(price);
}
/// <summary>
/// 框架合约估值
/// </summary>
/// <param name="td">互换交易</param>
/// <param name="settleDate">收盘日</param>
private void SaveEodSwap(trade td, DateTime settleDate, DateTime preSettleDate)
{
var eod_Swaps = DbContext.eod_swap.Where(x => x.SwapTradeId == td.id && x.ValueDate >= preSettleDate && x.ValueDate <= settleDate).ToList();
var eod_Swap = eod_Swaps.FirstOrDefault(x => x.ValueDate == settleDate);
var preEodSwap = eod_Swaps.FirstOrDefault(x => x.ValueDate == preSettleDate);
if (eod_Swap == null)
{
eod_Swap = new eod_swap();
}
var tradeSpan = DbContext.trade_span.FirstOrDefault(x => x.TradeId == td.id && x.ValueDate == settleDate);
// eod_swap 是交易级汇总;eod_swap_position 是浮动腿、利息腿和保证金腿的明细。
// 以下先按日终明细拆腿,再按框架合约展示口径汇总。
var eodSwapPositions = DbContext.eod_swap_position.Where(x => x.SwapTradeId == td.id && x.ValueDate == settleDate && !x.Invalid).ToList();
var interestPositions = eodSwapPositions.Where(x => string.IsNullOrEmpty(x.UnderlyingCode)).ToList();//利息腿
var positions = eodSwapPositions.Where(x => !string.IsNullOrEmpty(x.UnderlyingCode)).ToList();//持仓腿
// 框架合约的方向约定:多头为正、空头为负;总名义本金取交易原始规模,
// 不能直接用多空腿相加,否则会把对冲方向误当成合约规模变化。
eod_Swap.NotionalValueLong = positions.Where(x => x.PositionType == (int)PositionTypeFlag.Long).Sum(s => s.PosiNotionalValue);
eod_Swap.NotionalValueShort = -Math.Abs(positions.Where(x => x.PositionType == (int)PositionTypeFlag.Short).Sum(s => s.PosiNotionalValue));
eod_Swap.NotionalValue = Convert.ToDecimal(td.OriginalStockEqvNotional ?? td.StockEqvNotional);
eod_Swap.SwapTradeId = td.id;
eod_Swap.SwapTradeNo = td.TradeNumber;
eod_Swap.ClientId = td.ClientId;
eod_Swap.BookId = td.AssetId;
eod_Swap.ValueDate = settleDate;
eod_Swap.StructureType = td.StructureType;
eod_Swap.MarketValueLong = positions.Where(x => x.PositionType == (int)PositionTypeFlag.Long).Sum(s => s.UnderlyingMarketValue);
eod_Swap.MarketValueShort = positions.Where(x => x.PositionType == (int)PositionTypeFlag.Short).Sum(s => s.UnderlyingMarketValue);
eod_Swap.FloatingPnL = positions.Sum(s => s.PosiProfitSum);
eod_Swap.dv01 = positions.Sum(s => s.dv01 ?? 0);
decimal interestPnL = 0;
// 利息腿按我方视角归集。保证金腿的利息现金流方向与普通利息腿相反,
// 因此保证金腿需要额外反转符号,确保 InterestPnL 表示我方的合约利率端收益。
interestPositions.ForEach(x =>
{
decimal ratio = x.InterestDirection == (int)SwapDirectionEnum.收取 ? 1 : -1;//收取为正,支付为负
if (marginTypes.Contains(x.InterestMode))
{
ratio = -ratio;
}
interestPnL += x.InterestProfitSum * ratio;
});
eod_Swap.InterestPnL = interestPnL;
eod_Swap.PostionValue = eodSwapPositions.Sum(s => s.SwapPositionValue);
eod_Swap.RealizedPnL = eodSwapPositions.Sum(s => s.RealizedPnl);
eod_Swap.TdRealizedPnL = eod_Swap.RealizedPnL - (preEodSwap?.RealizedPnL ?? 0);
eod_Swap.TdCloseQty = positions.Sum(s => s.TdCloseQty);
var initMargin = Convert.ToDecimal(tradeSpan?.InitialMargin ?? 0);
var maintainMargin = Convert.ToDecimal(tradeSpan?.WorstCastClientPayable ?? 0);
if (initMargin < 0)
{
eod_Swap.InitMarginLoss = Math.Abs(initMargin);
}
else
{
eod_Swap.InitMarginGain = Math.Abs(initMargin);
}
if (maintainMargin < 0)
{
eod_Swap.PostionMarginLoss = Math.Abs(maintainMargin);
}
else
{
eod_Swap.PostionMarginGain = Math.Abs(maintainMargin);
}
UpdateDbOption(eod_Swap);
if (eod_Swap.id == 0)
{
DbContext.eod_swap.Add(eod_Swap);
}
}
/// <summary>
/// 单标的修改当天 框架合约信息
/// </summary>
/// <param name="td"></param>
/// <param name="settleDate"></param>
private void UpdateEodSwap(trade td, DateTime settleDate)
{
eod_swap eod_Swap = DbContext.eod_swap.FirstOrDefault(x => x.SwapTradeId == td.id && x.ValueDate == settleDate);
if (eod_Swap == null)
{
eod_Swap = new eod_swap();
eod_Swap.SwapTradeId = td.id;
eod_Swap.ValueDate = settleDate;
DbContext.eod_swap.Add(eod_Swap);
}
// 单标的调整与首次归档使用同一套框架合约汇总口径,避免重算后多空和名义本金展示不一致。
var eodSwapPositions = DbContext.eod_swap_position.Where(x => x.SwapTradeId == td.id && x.ValueDate == settleDate && !x.Invalid).ToList();
var interestPositions = eodSwapPositions.Where(x => string.IsNullOrEmpty(x.UnderlyingCode)).ToList();//利息腿
var positions = eodSwapPositions.Where(x => !string.IsNullOrEmpty(x.UnderlyingCode)).ToList();//持仓腿
eod_Swap.NotionalValue = Convert.ToDecimal(td.OriginalStockEqvNotional ?? td.StockEqvNotional);
eod_Swap.NotionalValueLong = positions.Where(x => x.PositionType == (int)PositionTypeFlag.Long).Sum(s => s.PosiNotionalValue);
eod_Swap.NotionalValueShort = -Math.Abs(positions.Where(x => x.PositionType == (int)PositionTypeFlag.Short).Sum(s => s.PosiNotionalValue));
eod_Swap.MarketValueLong = positions.Where(x => x.PositionType == (int)PositionTypeFlag.Long).Sum(s => s.UnderlyingMarketValue);
eod_Swap.MarketValueShort = positions.Where(x => x.PositionType == (int)PositionTypeFlag.Short).Sum(s => s.UnderlyingMarketValue);
eod_Swap.FloatingPnL = positions.Sum(s => s.PosiProfitSum);
eod_Swap.dv01 = positions.Sum(s => s.dv01 ?? 0);
interestPositions.ForEach(x =>
{
decimal ratio = x.InterestDirection == (int)SwapDirectionEnum.收取 ? 1 : -1;//收取为正,支付为负
if (marginTypes.Contains(x.InterestMode))
{
ratio = -ratio;
}
eod_Swap.InterestPnL += x.InterestProfitSum * ratio;
});
eodSwapPositions.ForEach(x =>
{
decimal ratio = x.InterestDirection == (int)SwapDirectionEnum.收取 ? 1 : -1;//收取为正,支付为负
if (marginTypes.Contains(x.InterestMode))
{
ratio = -ratio;
}
eod_Swap.TdRealizedPnL += x.TdCloseMtmPnl + x.TdCloseDividend + x.TdCloseFee + x.TdCloseInterest * ratio + x.TdCloseInterestFee;
});
eod_Swap.PostionValue = eodSwapPositions.Sum(s => s.SwapPositionValue);
eod_Swap.RealizedPnL = eodSwapPositions.Sum(s => s.RealizedMtmPnL + s.RealizedDividend + s.RealizedFee + s.RealizedInterest + s.RealizedInterestFee);
eod_Swap.TdCloseQty = positions.Sum(s => s.TdCloseQty);
var tradeInitMarginObj = DbContext.trade_initial_margin.FirstOrDefault(x => x.TradeId == td.id);
var initMarginList = interestPositions.Where(x => x.InterestMode == (int)InterestModeEnum.初始预付金 && x.HappenDate == settleDate).ToList();
var addMarginList = interestPositions.Where(x => x.InterestMode == (int)InterestModeEnum.追加预付金 && x.HappenDate == settleDate).ToList();
eod_Swap.InitMarginGain += initMarginList.Where(s => s.InterestDirection == (int)SwapDirectionEnum.收取).ToList().Sum(s => s.InterestPrincipalFix);
eod_Swap.InitMarginLoss += initMarginList.Where(s => s.InterestDirection == (int)SwapDirectionEnum.支付).ToList().Sum(s => s.InterestPrincipalFix);
eod_Swap.PostionMarginGain += addMarginList.Where(s => s.InterestDirection == (int)SwapDirectionEnum.收取).ToList().Sum(s => s.InterestPrincipalFix);
eod_Swap.PostionMarginLoss += addMarginList.Where(s => s.InterestDirection == (int)SwapDirectionEnum.支付).ToList().Sum(s => s.InterestPrincipalFix);
UpdateDbOption(eod_Swap);
DbContext.SaveChanges();
}
/// <summary>
/// 获取多空组合 平仓详细
/// </summary>
/// <param name="tradeId"></param>
/// <param name="valueDate"></param>
/// <returns></returns>
public SwapLongShortCloseModel GetCloseDetails(int tradeId, DateTime valueDate)
{
SwapLongShortCloseModel closeModel = new SwapLongShortCloseModel();
var eodPositions = DbContext.eod_swap_position.Where(x => x.SwapTradeId == tradeId && x.ValueDate == valueDate && !x.Invalid).ToList();
var flowEvents = DbContext.swap_flow_event.Where(x => x.SwapTradeId == tradeId && x.EventDate == valueDate && x.DataState == (int)SwapFlowDateStateEnum.完成 && x.EventType == (int)SwapEventTypeEnum.平仓 && string.IsNullOrEmpty(x.UnderlyingCode)).ToList();
closeModel.DealPositions = eodPositions.Where(x => x.TdCloseQty != 0).ToList();
closeModel.DealInterests = flowEvents;
return closeModel;
}
/// <summary>
/// 查询互换日终持仓风险-明细
/// </summary>
/// <param name="req"></param>
/// <returns></returns>
public SearchListResult<SwapPositionResponse> SearchEodPositionList(EodSwapPositionQueryRequest req)
{
var predicate = PredicateBuilder.Create<eod_swap_position>(n => !n.Invalid);
var tradePredicate = PredicateBuilder.Create<trade>(n => n.TradeType == "收益互换"
&& n.ValidState != "InValid");
if (req.ValueDate.HasValue)
{
predicate = predicate.And(n => n.ValueDate == req.ValueDate);
//tradePredicate = tradePredicate.And(n => n.StartDate <= req.ValueDate);
}
if (!string.IsNullOrEmpty(req.TradeNumber))
{
tradePredicate = tradePredicate.And(n => n.TradeNumber.Contains(req.TradeNumber.Trim()));
}
if (req.UserAssets != null || req.UserClients != null)
{
tradePredicate = tradePredicate.And(q => req.UserAssets.Contains(q.AssetId) || req.UserClients.Contains(q.ClientId));
}
if (req.ClientIds != null && req.ClientIds.Any(x => x > 0))
{
tradePredicate = tradePredicate.And(q => req.ClientIds.Contains(q.ClientId));
}
if (req.BookIds != null && req.BookIds.Any(x => x > 0))
{
tradePredicate = tradePredicate.And(q => req.BookIds.Contains(q.AssetId));
}
if (req.UnderlyingIds != null)
{
var underlyingCodes = new List<string>();
foreach (var id in req.UnderlyingIds)
{
var un = UnderlyingDataProvider.GetUnderlying(id);
if (un != null)
{
underlyingCodes.Add(un.UnderlyingCode);
}
}
predicate = predicate.And(n => underlyingCodes.Contains(n.UnderlyingCode));
}
var positionQuery = DbContext.eod_swap_position.Where(predicate);
var tradeQuery = DbContext.trade.Where(tradePredicate);
var query = from position in positionQuery
join td in tradeQuery on position.SwapTradeId equals td.id
select new SwapPositionResponse
{
eodPosition = position,
TradeDate = td.StartDate.Value,
SwapTradeNo = td.TradeNumber,
StructureType = td.StructureType,
ClientName = td.ClientName,
ClientId = td.ClientId,
};
if (string.IsNullOrEmpty(req.sidx))
{
req.sidx = "SwapTradeNo,eodPosition.id";
req.sord = "asc";
}
var retListResult = query.ToSearchList(req);
foreach (var item in retListResult.rows)
{
var client = DataCacheProvider.GetClientDataSource().GetData(item.ClientId);
item.SwapTradeTypeStr = client?.SwapTradeTypeStr;
var multiplier = ConsGlobal.InstrumentType.IsBond(item.eodPosition.UnderlyingInstrumentType) ? 100 : 1;
item.eodPosition.PosiNetPrice *= multiplier;
item.eodPosition.PosiGrossPrice *= multiplier;
item.eodPosition.PosiNetFeePrice *= multiplier;
item.eodPosition.PosiNetNoFeePrice *= multiplier;
item.eodPosition.UnderlyingPrice *= multiplier;
}
return retListResult;
}
/// <summary>
/// 查询互换日终持仓风险-框架合约
/// </summary>
/// <param name="req"></param>
/// <returns></returns>
public SearchListResult<EodSwapResponse> SearchEodSwapList(EodSwapQueryRequest req)
{
var predicate = PredicateBuilder.Create<eod_swap>(n => 1 == 1);
var tradePredicate = PredicateBuilder.Create<trade>(n => n.TradeType == "收益互换"
&& n.ValidState != "InValid");
if (req.ValueDate.HasValue)
{
predicate = predicate.And(n => n.ValueDate == req.ValueDate);
// tradePredicate = tradePredicate.And(n=>n.StartDate<=req.ValueDate);
}
if (!string.IsNullOrEmpty(req.TradeNumber))
{
tradePredicate = tradePredicate.And(n => n.TradeNumber.Contains(req.TradeNumber.Trim()));
}
if (req.UserAssets != null || req.UserClients != null)
{
tradePredicate = tradePredicate.And(q => req.UserAssets.Contains(q.AssetId) || req.UserClients.Contains(q.ClientId));
}
if (req.ClientIds != null && req.ClientIds.Any(x => x > 0))
{
tradePredicate = tradePredicate.And(q => req.ClientIds.Contains(q.ClientId));
}
if (req.BookIds != null && req.BookIds.Any(x => x > 0))
{
tradePredicate = tradePredicate.And(q => req.BookIds.Contains(q.AssetId));
}
var positionQuery = DbContext.eod_swap.Where(predicate);
var tradeQuery = DbContext.trade.Where(tradePredicate);
var query = from position in positionQuery
join td in tradeQuery on position.SwapTradeId equals td.id
select new EodSwapResponse
{
position = position,
TradeDate = td.StartDate.Value,
SwapTradeNo = td.TradeNumber,
StructureType = td.StructureType,
ClientName = td.ClientName,
AssetBookName = td.AssetBookName,
ClientId = td.ClientId
};
if (string.IsNullOrEmpty(req.sidx))
{
req.sidx = "SwapTradeNo,position.id";
req.sord = "asc";
}
DbContext.SetDebugLog();
var retListResult = query.ToSearchList(req);
var tradeIds = retListResult.rows.Select(x => x.position.SwapTradeId).Distinct().ToList();
var valueDates = retListResult.rows.Select(x => x.position.ValueDate).Distinct().ToList();
var tradeNotionals = DbContext.trade
.Where(x => tradeIds.Contains(x.id))
.Select(x => new { x.id, x.OriginalStockEqvNotional, x.StockEqvNotional })
.ToDictionary(x => x.id);
var eodPositionDetails = DbContext.eod_swap_position
.Where(x => tradeIds.Contains(x.SwapTradeId) && valueDates.Contains(x.ValueDate) && !x.Invalid)
.ToList();
var tradeExtends = DbContext.trade_extend.Where(x => tradeIds.Contains(x.TradeId)).ToList();
var underlyingDataSource = DataCacheProvider.GetUnderlyingDataSource();
var varietyDataSource = DataCacheProvider.GetVarietyDataSource();
foreach (var item in retListResult.rows)
{
item.position.NotionalValueShort = -Math.Abs(item.position.NotionalValueShort);
if (tradeNotionals.TryGetValue(item.position.SwapTradeId, out var tradeNotional))
{
item.position.NotionalValue = Convert.ToDecimal(tradeNotional.OriginalStockEqvNotional ?? tradeNotional.StockEqvNotional);
}
var client = DataCacheProvider.GetClientDataSource().GetData(item.ClientId);
item.SwapTradeTypeStr = client?.SwapTradeTypeStr;
var details = eodPositionDetails
.Where(x => x.SwapTradeId == item.position.SwapTradeId && x.ValueDate == item.position.ValueDate)
.ToList();
var floatingLegs = details.Where(x => !string.IsNullOrEmpty(x.UnderlyingCode)).ToList();
var marginLegs = details.Where(x => marginTypes.Contains(x.InterestMode)).ToList();
var tradeExtend = tradeExtends.FirstOrDefault(x => x.TradeId == item.position.SwapTradeId);
var dividendPayDate = tradeExtend?.ExtendObj?.DividendPayDate ?? 1;
item.UnderlyingType = string.Join(",", floatingLegs
.Select(x =>
{
var underlying = underlyingDataSource.GetData(x.UnderlyingCode);
return varietyDataSource.GetData(underlying?.UnderlyingTypeId ?? 0)?.AssetType
?? underlying?.UnderlyingInstrumentTypeCn
?? underlying?.UnderlyingType;
})
.Where(x => !string.IsNullOrWhiteSpace(x))
.Distinct());
item.PeriodAmount = floatingLegs.Sum(x => x.RealizedDividend + x.PosiDividendSum);
item.FloatingUnrealizedPnl = floatingLegs.Sum(x => x.PosiMtmPnL);
item.InterestPaymentMethod = dividendPayDate == 0 ? "到期轧差" : "派息日支付";
if (dividendPayDate == 0)
{
item.MaturityNettingValuation = item.FloatingUnrealizedPnl + item.position.InterestPnL + item.PeriodAmount;
}
else
{
item.PeriodPaymentValuation = item.FloatingUnrealizedPnl + item.position.InterestPnL;
}
item.MarginInterestGain = marginLegs
.Where(x => x.InterestDirection == (int)SwapDirectionEnum.收取)
.Sum(x => Math.Abs(x.InterestIncomeSum));
item.MarginInterestLoss = marginLegs
.Where(x => x.InterestDirection == (int)SwapDirectionEnum.支付)
.Sum(x => Math.Abs(x.InterestIncomeSum));
}
var dv01 = query.Sum(O => O.position.dv01??0);
retListResult.Sum = new {DV = dv01 };
return retListResult;
}
/// <summary>
/// 获取互换交易日终持仓数据
/// </summary>
/// <param name="tradeId"></param>
/// <param name="valueDate"></param>
/// <returns></returns>
public List<eod_swap_position> GetPreEodPositions(int tradeId, DateTime valueDate)
{
return DbContext.eod_swap_position.Where(x => x.SwapTradeId == tradeId && x.ValueDate == valueDate && !x.Invalid).ToList();
}
/// <summary>
/// 获取互换交易日终持仓数据集合
/// </summary>
/// <param name="tradeId"></param>
/// <param name="valueDate"></param>
/// <returns></returns>
public List<eod_swap> GetEodSwaps(List<int> tradeIds, DateTime valueDate)
{
return DbContext.eod_swap.Where(x => tradeIds.Contains(x.SwapTradeId) && x.ValueDate == valueDate).ToList();
}
/// <summary>
/// 获取互换交易某交易日区间框架合约数据
/// </summary>
/// <param name="tradeIds"></param>
/// <param name="valueDate"></param>
/// <param name="preValueDate"></param>
/// <returns></returns>
public List<eod_swap> GetEodSwaps(List<int> tradeIds, DateTime valueDate, DateTime preValueDate)
{
return DbContext.eod_swap.Where(x => tradeIds.Contains(x.SwapTradeId) && x.ValueDate <= valueDate && x.ValueDate >= preValueDate).ToList();
}
/// <summary>
/// 获取互换交易某日终持仓数据
/// </summary>
/// <param name="tradeIds"></param>
/// <param name="valueDate"></param>
/// <param name="preValueDate"></param>
/// <returns></returns>
public List<eod_swap_position> GetEodPositions(List<int> tradeIds, DateTime valueDate, DateTime preValueDate)
{
return DbContext.eod_swap_position.Where(x => tradeIds.Contains(x.SwapTradeId) && !string.IsNullOrEmpty(x.UnderlyingCode) && x.ValueDate <= valueDate && x.ValueDate >= preValueDate && !x.Invalid).ToList();
}
/// <summary>
/// 获取互换交易某区间日终持仓估值-按产品要求
/// </summary>
/// <param name="req"></param>
/// <returns></returns>
public SearchListResult<SwapPositionResponse> SearchPositionList(ClientSwapPositionRequest req)
{
var retListResult = GetSearchPositionList(req);
var clientDataSource = DataCacheProvider.GetClientDataSource();
var underlyDataSource = DataCacheProvider.GetUnderlyingDataSource();
foreach (var item in retListResult.rows)
{
var client = clientDataSource.GetData(item.ClientId);
item.ClientNumber = client.Number;
if (!string.IsNullOrEmpty(item.eodPosition.UnderlyingCode))
{
var underly = underlyDataSource.GetData(item.eodPosition.UnderlyingCode);
if (underly != null)
{
item.eodPosition.UnderlyingName = underly.UnderlyingName;
}
}
}
return retListResult;
}
/// <summary>
/// 获取互换交易某区间日终持仓估值-按山证要求
/// </summary>
/// <param name="req"></param>
/// <returns></returns>
public SearchListResult<EodSwapPositionResponse> SearchEodPositionList(ClientSwapPositionRequest req)
{
var retListResult = GetSearchEodPositionList(req);
return retListResult;
}
/// <summary>
/// 互换持仓明细查询
/// </summary>
/// <param name="req"></param>
/// <returns></returns>
private SearchListResult<SwapPositionResponse> GetSearchPositionList(ClientSwapPositionRequest req)
{
var predicate = PredicateBuilder.Create<eod_swap_position>(n => !n.Invalid && n.PositionType > 0 && n.PosiStatus == 0);
var tradePredicate = PredicateBuilder.Create<trade>(n => n.TradeType == "收益互换"
&& n.ValidState != "InValid");
if (req.ClientId > 0)
{
predicate = predicate.And(x => x.ClientId == req.ClientId);
}
if (req.ValueDate != null)
{
predicate = predicate.And(x => x.ValueDate == req.ValueDate);
tradePredicate = tradePredicate.And(n => n.StartDate <= req.ValueDate);
}
var positionQuery = DbContext.eod_swap_position.Where(predicate);
var tradeQuery = DbContext.trade.Where(tradePredicate);
var query = from position in positionQuery
join td in tradeQuery on position.SwapTradeId equals td.id
select new SwapPositionResponse
{
eodPosition = position,
TradeDate = td.StartDate.Value,
SwapTradeNo = td.TradeNumber,
StructureType = td.StructureType,
ClientName = td.ClientName,
ClientId = td.ClientId,
InitialMarginDirection = position.PosiDirection
};
if (string.IsNullOrEmpty(req.sidx))
{
req.sidx = "SwapTradeNo,eodPosition.id";
req.sord = "asc";
}
var retListResult = query.ToSearchList(req);
foreach (var item in retListResult.rows)
{
SetClientEodPosition(item.eodPosition);
SetPosiPrice(item.eodPosition);
}
return retListResult;
}
private SearchListResult<EodSwapPositionResponse> GetSearchEodPositionList(ClientSwapPositionRequest req)
{
// 每日估值报告以有数量的浮动腿为主记录;利息腿和保证金腿仅作为同交易、同估值日的辅助数据参与汇总。
var predicate = PredicateBuilder.Create<eod_swap_position>(n => !n.Invalid && n.PosiQuantity > 0);
var interestPredicate = PredicateBuilder.Create<eod_swap_position>(n => !n.Invalid && n.InterestDirection > 0);
var tradePredicate = PredicateBuilder.Create<trade>(n => n.ValidState != "InValid");
// TODO 这里暂时忽略前端传的值 暂时使用临时方案:同时查普通债券类收益互换和普通收益互换
tradePredicate = tradePredicate.And(n => n.StructureType == "普通债券类收益互换" || n.StructureType == "普通收益互换");
if (req.ClientId > 0)
{
predicate = predicate.And(x => x.ClientId == req.ClientId);
}
if (req.BookId > 0)
{
tradePredicate = tradePredicate.And(x => x.AssetId == req.BookId.Value);
}
// ValueDateFrom 保留在请求模型中,但当前互换估值查询按 ValueDate 单日取数。
// if (req.ValueDateFrom != null)
// {
// predicate = predicate.And(x => x.ValueDate >= req.ValueDateFrom);
// }
if (req.ValueDate != null)
{
predicate = predicate.And(x => x.ValueDate == req.ValueDate);
tradePredicate = tradePredicate.And(x => req.ValueDate >= x.TradeDate);
}
var positionQuery = DbContext.eod_swap_position.Where(predicate);
var tradeQuery = DbContext.trade.Where(tradePredicate);
var query = from position in positionQuery
join td in tradeQuery on position.SwapTradeId equals td.id
join tcrConfirm in DbContext.trade_contract_r.Where(O => O.Type == ContractTypeEnum.Trade && O.IsValid) on td.id equals tcrConfirm.TradeId into tcrConfirms
from tcrConfirm in tcrConfirms.DefaultIfEmpty()
select new EodSwapPositionResponse
{
position = position,
ClientName = td.ClientName,
ConfrimNo = tcrConfirm.ContractCode,
TradeNumber = td.TradeNumber,
StructureType = td.StructureType,
UnwindDate = td.UnWindDate,
TradeStatus = td.TradeStatus,
InitYtm = td.InitYtm
};
if (string.IsNullOrEmpty(req.sidx))
{
req.sidx = "position.id";
req.sord = "asc";
}
var retListResult = query.ToSearchList(req);
var tradeIds = retListResult.rows.Select(s => s.position.SwapTradeId).ToList();
if (!tradeIds.Any())
{
return retListResult;
}
interestPredicate = interestPredicate.And(x => tradeIds.Contains(x.SwapTradeId));
var valueDates = retListResult.rows.Select(s => s.position.ValueDate).Distinct().ToList();
interestPredicate = interestPredicate.And(x => valueDates.Contains(x.ValueDate));
// 主查询分页后再取同交易、同估值日的全部辅助腿,避免利息/保证金归集跨估值日串数据。
var eodPositions = DbContext.eod_swap_position.Where(interestPredicate).ToList();
var marginPositions = DbContext.swap_position
.Where(x => tradeIds.Contains(x.SwapTradeId) && marginTypes.Contains(x.InterestMode) && x.IsInitial && !x.Invalid)
.ToList();
var tradeExtends = DbContext.trade_extend.Where(x => tradeIds.Contains(x.TradeId)).ToList();
Dictionary<string, bool> tradeDic = new Dictionary<string, bool>();
foreach (var item in retListResult.rows)
{
var tradeExtend = tradeExtends.FirstOrDefault(x => x.TradeId == item.position.SwapTradeId);
// 到期结算日按合同到期日展示;实际期限按自然日且包含起始日,二者均不使用结算规则偏移。
item.MaturitySettlementDate = item.position.PosiMatuirityDate;
item.DayCount = Math.Max(0, (item.position.ValueDate - item.position.PosiStartDate).Days + 1);
//item.position.PosiProfitSum += item.position.VTradingFee-item.position.PosiFeePending;
SetClientEodPosition(item.position);
//item.position.PosiProfitSum += item.TradingFee;
var posiProfitSum = item.position.PosiProfitSum;
//item.position.PosiProfitSum 不需要加交易费用
// PosiProfitSum 原值包含交易费用和期间付息/分红。先拆出这两部分,
// 使“浮动收益金额”仅反映标的盯市收益,后续净额公式再按支付方式决定是否加回期间金额。
var pendingDividend = item.position.PosiDividendSum;
item.position.PosiProfitSum = item.position.PosiProfitSum - item.position.PosiFeePending - pendingDividend;
// 现券仅展示期间付息和期初成交收益率;ETF(标的主数据类型 Fund)仅展示期间分红。
// 其余标的的三列均不适用,返回 null 使页面和 Excel 模板保持空白,而不是展示 0。
var isCashBond = ConsGlobal.InstrumentType.IsBond(item.position.UnderlyingInstrumentType);
var isEtf = ConsGlobal.InstrumentType.Fund.Equals(
item.position.UnderlyingInstrumentType,
StringComparison.OrdinalIgnoreCase);
if (isCashBond)
{
item.PeriodAmount = pendingDividend;
item.DividendAmount = null;
// 期初标的成交收益率是债券现券成交口径,非现券不展示该交易录入值。
}
else if (isEtf)
{
item.PeriodAmount = null;
item.DividendAmount = pendingDividend;
}
else
{
item.PeriodAmount = null;
item.DividendAmount = null;
}
if (!isCashBond)
{
item.InitYtm = null;
}
// 预付金本金和利率来自交易腿,并以发生日判断在估值日是否已生效;
// 预付金利息则来自当日日终腿,以获得截至估值日的 InterestIncomeSum。
var tradeMargins = marginPositions
.Where(x => x.SwapTradeId == item.position.SwapTradeId
&& (!x.HappenDate.HasValue || x.HappenDate.Value <= item.position.ValueDate))
.ToList();
var interests = eodPositions.Where(x => x.SwapTradeId == item.position.SwapTradeId && x.ValueDate == item.position.ValueDate);
var eodMargins = interests.Where(x => marginTypes.Contains(x.InterestMode)).ToList();
var eodInterests = interests.Where(x => !marginTypes.Contains(x.InterestMode)).ToList();
var initialMargins = tradeMargins.Where(x => x.InterestMode == (int)InterestModeEnum.初始预付金).ToList();
var additionalMargins = tradeMargins.Where(x => x.InterestMode == (int)InterestModeEnum.追加预付金).ToList();
var floatRateInterest = eodInterests.Where(x => !string.IsNullOrEmpty(x.FloatRateUnderlyingCode)).FirstOrDefault();
item.position.FloatRateUnderlyingCode = floatRateInterest?.FloatRateUnderlyingCode;
item.position.FloatRate = floatRateInterest?.FloatRate ?? 0;
item.OpenMarginAmount = initialMargins.Sum(s => s.InterestPrincipalFix * (s.InterestDirection == (int)SwapDirectionEnum.收取 ? 1 : -1));
item.OpenMarginRate = CalculateWeightedMarginRate(tradeMargins);
item.AdditionalMarginAmount = additionalMargins.Sum(s => s.InterestPrincipalFix * (s.InterestDirection == (int)SwapDirectionEnum.收取 ? 1 : -1));
item.MarginInterestAmount = CalculateWeightedMarginInterest(eodMargins);
item.InterestAmount = eodInterests.Sum(s => s.InterestIncomeSum * (s.InterestDirection == (int)SwapDirectionEnum.收取 ? -1 : 1));
item.InterestRate = eodInterests.Sum(s => s.InterestRateDefault);
// 到期轧差才把期间付息/分红并入净额结算;派息日支付已在现金流层独立结算,不能重复计入估值。
var nettingDividend = (tradeExtend?.ExtendObj?.DividendPayDate ?? 1) == 0 ? pendingDividend : 0m;
item.NetSettmentAmount = item.InterestAmount
+ item.position.PosiProfitSum
+ item.position.PosiFeePending
+ item.MarginInterestAmount
+ nettingDividend;
item.NetSettmentAmount = Math.Round(item.NetSettmentAmount, ConsGlobal.MoneyRound, MidpointRounding.AwayFromZero);
item.TrsValue = Math.Round(item.NetSettmentAmount + item.OpenMarginAmount + item.AdditionalMarginAmount, ConsGlobal.MoneyRound, MidpointRounding.AwayFromZero);
if (item.position.PosiNotionalValue != 0 && item.position.PosiNetPrice != 0)
{
item.FloatRateAbs = item.position.PosiNotionalValue == 0 ? 0 : item.InterestAmount / item.position.PosiNotionalValue;
}
// 交易录入的债券类收益互换价格以小数保存,展示时转为百分比价格;
// 普通收益互换录入的是数量/原始数值,不做乘 100 转换。
SetPosiPrice(item.position, item.StructureType == "普通债券类收益互换");
}
return retListResult;
}
/// <summary>
/// 计算预付金利率。多条初始/追加预付金腿按本金绝对值加权,
/// 不按收付方向轧差,避免相反方向本金抵消后放大利率。
/// </summary>
private static decimal CalculateWeightedMarginRate(IEnumerable<swap_position> margins)
{
var marginList = margins.ToList();
var totalWeight = marginList.Sum(x => Math.Abs(x.InterestPrincipalFix));
return totalWeight == 0
? 0
: marginList.Sum(x => x.InterestRateDefault * Math.Abs(x.InterestPrincipalFix)) / totalWeight;
}
/// <summary>
/// 计算预付金利息。先按收取为正、支付为负转换为我方视角,
/// 再按日终本金绝对值加权平均;本金合计为零时返回零。
/// </summary>
private static decimal CalculateWeightedMarginInterest(IEnumerable<eod_swap_position> margins)
{
var marginList = margins.ToList();
var totalWeight = marginList.Sum(x => Math.Abs(x.InterestPrincipalFix));
return totalWeight == 0
? 0
: marginList.Sum(x => x.InterestIncomeSum
* (x.InterestDirection == (int)SwapDirectionEnum.收取 ? 1 : -1)
* Math.Abs(x.InterestPrincipalFix)) / totalWeight;
}
/// <summary>
/// 将数据库中以公司/交易簿记方向保存的日终字段转换为客户视角。
/// 该转换必须在拆分浮动收益、费用和期间付息/分红之前完成,
/// 否则页面、Excel 和净额结算金额会出现相反符号。
/// </summary>
/// <param name="position"></param>
private void SetClientEodPosition(eod_swap_position position)
{
position.TdCloseDividend = -position.TdCloseDividend;
position.TdCloseMtmPnl = -position.TdCloseMtmPnl;
position.TdCloseFee = -position.TdCloseFee;
position.TdCloseInterest = -position.TdCloseInterest;
position.TdCloseInterestFee = -position.TdCloseInterestFee;
position.RealizedMtmPnL = -position.RealizedMtmPnL;
position.RealizedDividend = -position.RealizedDividend;
position.RealizedFee = -position.RealizedFee;
position.RealizedInterest = -position.RealizedInterest;
position.RealizedInterestFee = -position.RealizedInterestFee;
position.RealizedPnl = -position.RealizedPnl;
position.InterestProfitSum = -position.InterestProfitSum;
position.PosiProfitSum = -position.PosiProfitSum;
position.VTradingFee = -position.VTradingFee;
position.PosiFeePending = -position.PosiFeePending;
position.SwapPositionValue = -position.SwapPositionValue;
position.PosiDividendSum = -position.PosiDividendSum;
}
private void SetPosiPrice(eod_swap_position position, bool? useBondPriceScale = null)
{
var um = DataCacheProvider.GetUnderlyingDataSource().GetData(position.UnderlyingCode);
if (useBondPriceScale ?? (um != null && um.IsBond()))
{
position.PosiNetPrice *= 100;
position.UnderlyingPrice *= 100;
position.PosiGrossPrice *= 100;
position.PosiNetFeePrice *= 100;
position.PosiNetNoFeePrice *= 100;
return;
}
}
/// <summary>
/// 获取客户互换持仓信息
/// </summary>
/// <param name="clientId"></param>
/// <param name="valueDate"></param>
/// <returns></returns>
public List<eod_position> GetSwapPositions(int clientId, DateTime valueDate)
{
var trades = DbContext.trade.Where(x => x.TradeType == "收益互换"
&& x.ClientId == clientId
&& ConsTrade.LiveTradeStatusList.Contains(x.TradeStatus)
&& x.ValidState != "InValid").ToList();
var tradeIds = trades.Select(s => s.id).ToList();
var eodSwaps = DbContext.eod_swap_position.Where(x => tradeIds.Contains(x.SwapTradeId) && x.ValueDate == valueDate).ToList();
var tradeExtends = DbContext.trade_extend.Where(x => tradeIds.Contains(x.TradeId)).ToList();
return ConvertEodPnl(eodSwaps, trades, tradeExtends);
}
private List<eod_position> ConvertEodPnl(List<eod_swap_position> eodSwaps, List<trade> trades, List<trade_extend> tradeExtends)
{
List<eod_position> list = new List<eod_position>();
foreach (var item in eodSwaps)
{
var tradeOrigin = trades.First(x => x.id == item.SwapTradeId);
var realizedPnL = item.RealizedMtmPnL + item.RealizedInterest;
var tdExtend = tradeExtends.First(x => x.TradeId == item.SwapTradeId);
eod_position model = new eod_position()
{
TradeId = tradeOrigin.id,
TradeType = tradeOrigin.TradeType,
ClientId = tradeOrigin.ClientId,
TradeNumber = tradeOrigin.TradeNumber,
TradeDate = tradeOrigin.TradeDate,
ExerciseDate = tradeOrigin.ExerciseDate,
PrincipalRate = tradeOrigin.PrincipalRate ?? 0,
BasisUnderlyingCode = tradeOrigin.BasisUnderlyingCode,
UnderlyingCode = item.UnderlyingCode,
BasisGap = tradeOrigin.BasisGap ?? 0,
Lots = Convert.ToDouble(item.PosiQuantity),
ParticipationRate = tradeOrigin.ParticipationRate ?? 0,
NoRiskRate = tradeOrigin.NoRiskRate ?? 0,
UnderlyingPrice = Convert.ToDouble(item.UnderlyingPrice),
Pv = Convert.ToDouble(item.UnderlyingMarketValue) * -1,
RoundedPv = Math.Round(Convert.ToDouble(item.UnderlyingMarketValue), 2) * -1,
Pnl = Convert.ToDouble(realizedPnL) * -1,
RoundedPnl = Math.Round(Convert.ToDouble(realizedPnL), 2) * -1,
ValueDate = item.ValueDate,
PvDouble = Convert.ToDouble(item.UnderlyingMarketValue),
PnlDouble = Convert.ToDouble(realizedPnL),
PositionRelizedAmount = Convert.ToDouble(realizedPnL) * -1,
InstrumentType = tradeOrigin.UnderlyingInstrumentType,
IsGroup = tradeOrigin.IsGroup,
SettlementType = tradeOrigin.SettlementType,
SettlementFlag = tradeOrigin.SettlementFlag,
tradeOrigin = tradeOrigin.Clone(),
Vol = 0,
Delta = 0,
Gamma = 0,
Theta = 0,
Vega = 0,
Rho = 0,
GammaCash = 0
};
SetDicValue(model, item, tdExtend.ExtendObj.AnnualDays);
list.Add(model);
}
return list;
}
/// <summary>
/// 设置持仓导出字典信息
/// </summary>
/// <param name="model"></param>
/// <param name="item"></param>
/// <param name="annualDays"></param>
private void SetDicValue(eod_position model, eod_swap_position item, int annualDays)
{
var extDic = model.trade.MetaDic;
if (!string.IsNullOrEmpty(item.UnderlyingCode))
{
var underlyingAssetName = DataCacheProvider.GetUnderlyingDataSource().GetData(item.UnderlyingCode)?.UnderlyingName;
decimal posiTradingFeeUnit = 0;
if (item.PosiQuantity != 0)
{
posiTradingFeeUnit = item.PosiTradingFee / item.PosiQuantity;
}
if (item.PosiDirection == (int)SwapDirectionEnum.支付)
{
extDic["互换_支付方标的代码"] = item.UnderlyingCode;
extDic["互换_支付方标的名称"] = underlyingAssetName;
extDic["互换_支付方期初标的价格"] = item.PosiGrossPrice.OtcFormatUmPrice();
extDic["互换_支付方交易数量"] = item.PosiQuantity.OtcFormatNotional();
extDic["互换_支付方到期标的价格"] = item.UnderlyingPrice.OtcFormatUmPrice();
extDic["互换_支付方单位交易费用"] = posiTradingFeeUnit.OtcFormatUmPrice();
extDic["互换_支付方初始预付金"] = item.RealizedFee.OtcFormatPercent();
extDic["互换_支付方交易费用"] = item.PosiTradingFee.OtcFormatUmPrice();
extDic["互换_支付方多空方向"] = item.PositionType == (int)PositionTypeFlag.Long ? "多头" : "空头";
}
else
{
extDic["互换_收取方标的代码"] = item.UnderlyingCode;
extDic["互换_收取方标的名称"] = underlyingAssetName;
extDic["互换_收取方期初标的价格"] = item.PosiGrossPrice.OtcFormatUmPrice();
extDic["互换_收取方交易数量"] = item.PosiQuantity.OtcFormatNotional();
extDic["互换_收取方到期标的价格"] = item.UnderlyingPrice.OtcFormatUmPrice();
extDic["互换_收取方单位交易费用"] = posiTradingFeeUnit.OtcFormatUmPrice();
extDic["互换_收取方初始预付金"] = "";
extDic["互换_收取方交易费用"] = item.PosiTradingFee.OtcFormatUmPrice();
extDic["互换_收取方多空方向"] = item.PositionType == (int)PositionTypeFlag.Long ? "多头" : "空头";
}
}
else
{
if (item.InterestDirection == (int)SwapDirectionEnum.支付)
{
extDic["互换_支付方互换利率"] = item.InterestRateDefault.OtcFormatPercent();
extDic["互换_支付方固定收益"] = "";
}
else
{
extDic["互换_收取方互换利率"] = item.InterestRateDefault.OtcFormatPercent();
extDic["互换_收取方固定收益"] = "";
}
}
extDic["互换_互换日期"] = "";
extDic["年化天数"] = annualDays.ToString();
}
}
}