- 统一债券价格四舍五入规则,使用AwayFromZero模式 - 为债券类型添加特殊的精度处理逻辑 - 修复前端价格显示精度计算问题 - 优化价格存储精度控制,区分债券和其他产品类型 - 修复初始化YTM和净价精度处理问题 - 更新价格验证逻辑以支持动态精度设置
631 lines
37 KiB
C#
631 lines
37 KiB
C#
using BaseOUDAL;
|
|
using ClosedXML.Report.Utils;
|
|
using Confluent.Kafka;
|
|
using Microsoft.Office.Interop.Excel;
|
|
using MoreLinq;
|
|
using NPOI.OpenXmlFormats.Spreadsheet;
|
|
using System.Collections.Generic;
|
|
using YLErp.BLL.Calculation;
|
|
using YLErp.DBModels;
|
|
using YLErp.DBModels.Enums;
|
|
using YLErp.Model;
|
|
using YLErp.Modules.AppModule;
|
|
using YLErp.Modules.EodModule.QueryModule;
|
|
using YLErp.QdpModule;
|
|
using static YLErp.ConsGlobal;
|
|
|
|
namespace YLErp.Modules.SwapModule
|
|
{
|
|
/// <summary>
|
|
/// 互换流水开平仓事件服务
|
|
/// </summary>
|
|
public class SwapFlowEventService : SwapTradeBaseService
|
|
{
|
|
|
|
public SwapFlowEventService(OptUserInfo optUser) : base(optUser)
|
|
{
|
|
|
|
}
|
|
public SwapFlowEventService(YLBaseService baseService) : base(baseService)
|
|
{
|
|
|
|
}
|
|
|
|
#region 可测试化接缝(Seams)——借鉴 refactor-swap-event-testable 分支,override 可在测试中替换 DB/外部调用,生产代码行为不变
|
|
|
|
// FindTrade 已上提到基类 SwapTradeBaseService(三子类实现一致,消除重复)
|
|
|
|
protected virtual trade_extend FindTradeExtend(int swapTradeId)
|
|
=> DbContext.trade_extend.First(x => x.TradeId == swapTradeId);
|
|
|
|
protected virtual List<swap_position> FindPositions(int swapTradeId)
|
|
=> DbContext.swap_position.Where(x => x.SwapTradeId == swapTradeId && !x.IsInitial && !x.Invalid).AsNoTracking().ToList();
|
|
|
|
protected virtual List<swap_flow_event> FindAndInvalidateFutureEvents(int swapTradeId, DateTime tradeDate)
|
|
{
|
|
var olds = DbContext.swap_flow_event.Where(x => x.EventDate > tradeDate && x.SwapTradeId == swapTradeId && x.DataState > 0);
|
|
olds.ForEach(x => x.DataState = (int)SwapFlowDateStateEnum.废弃);
|
|
return olds.ToList();
|
|
}
|
|
|
|
protected virtual underlying_manager GetUnderlying(string underlyingCode)
|
|
=> DataCacheProvider.GetUnderlyingDataSource().GetData(underlyingCode);
|
|
|
|
private int GetStorageDeliveryPriceRound(string underlyingInstrumentType, string underlyingCode)
|
|
{
|
|
if (ConsGlobal.InstrumentType.IsBond(underlyingInstrumentType))
|
|
{
|
|
return ConsGlobal.PriceRound;
|
|
}
|
|
if (string.IsNullOrEmpty(underlyingCode))
|
|
{
|
|
return ConsGlobal.SwapDeliveryPriceRound;
|
|
}
|
|
return GetUnderlying(underlyingCode)?.IsBond() == true
|
|
? ConsGlobal.PriceRound
|
|
: ConsGlobal.SwapDeliveryPriceRound;
|
|
}
|
|
|
|
protected virtual DateTime GetNextBusinessDay(DateTime date)
|
|
=> QdpCalendarHelper.GetNonHoliday(date);
|
|
|
|
protected virtual long ResolvePositionId(swap_flow_merge merge, DateTime maturityDate, int direction, string tradeNumber)
|
|
=> GetMaxPositionId(merge, maturityDate, direction, tradeNumber);
|
|
|
|
protected virtual void PersistEvents(List<swap_flow_event> events)
|
|
{
|
|
foreach (var evt in events)
|
|
{
|
|
if (!string.IsNullOrEmpty(evt.UnderlyingCode))
|
|
{
|
|
evt.TradingAmountAvg = Math.Round(
|
|
evt.TradingAmountAvg,
|
|
GetStorageDeliveryPriceRound(evt.UnderlyingInstrumentType, evt.UnderlyingCode),
|
|
MidpointRounding.AwayFromZero);
|
|
}
|
|
DbContext.swap_flow_event.Add(evt);
|
|
}
|
|
DbContext.SaveChanges();
|
|
}
|
|
|
|
protected virtual IDisposable BeginTransaction()
|
|
=> DbContext.Database.BeginTransaction();
|
|
|
|
protected virtual void CommitTransaction(IDisposable transaction)
|
|
=> (transaction as Microsoft.EntityFrameworkCore.Storage.IDbContextTransaction)?.Commit();
|
|
|
|
protected virtual void RollbackTransaction(IDisposable transaction)
|
|
=> (transaction as Microsoft.EntityFrameworkCore.Storage.IDbContextTransaction)?.Rollback();
|
|
|
|
/// <summary>MergePageEvent 业务逻辑执行前钩子(默认空,录制golden用)。在DB查询完成、合成逻辑执行前触发</summary>
|
|
protected virtual void OnBeforeMergePageEvent(int swapTradeId, DateTime tradeDate,
|
|
trade trade, trade_extend tradeExtend,
|
|
List<swap_flow_merge> merges, List<swap_position> positions) { }
|
|
|
|
/// <summary>MergePageEvent 业务逻辑执行后钩子(默认空,录制golden用)。在PersistEvents之后触发,捕获输出事件</summary>
|
|
protected virtual void OnAfterMergePageEvent(int swapTradeId, DateTime tradeDate,
|
|
List<swap_flow_event> resultEvents) { }
|
|
|
|
#endregion
|
|
|
|
/// <summary>
|
|
/// 互换流水开平仓事件
|
|
/// </summary>
|
|
public void SwapFlowEvent(DateTime tradeDate)
|
|
{
|
|
|
|
new SysJobService(UserInfo).UpdateJob("互换流水合成持仓", 31, "互换流水开平仓事件进行中");
|
|
//同一交易代码、交易日期、标的、买卖方向 最多存在2条记录
|
|
var flowqueryGroup = DbContext.swap_flow_merge.Where(n => n.DataState == (int)SwapFlowDateStateEnum.等待完成 && n.OccurTime == tradeDate).AsEnumerable().GroupBy(g => g.SwapTradeId);
|
|
foreach (var flowMergeGroupItem in flowqueryGroup)
|
|
{
|
|
MergePageEvent(flowMergeGroupItem.Key ?? 0, flowMergeGroupItem.ToList(), tradeDate);
|
|
}
|
|
|
|
new SysJobService(UserInfo).UpdateJob("互换流水合成持仓", 32, "互换流水开平仓事件完成");
|
|
|
|
}
|
|
/// <summary>
|
|
/// 互换流水开平仓事件
|
|
/// </summary>
|
|
public List<swap_flow_event> SwapFlowEvent(List<swap_flow_merge> mergeList, DateTime tradeDate)
|
|
{
|
|
List<swap_flow_event> flowEvents = new List<swap_flow_event>();
|
|
new SysJobService(UserInfo).UpdateJob("互换流水合成持仓", 31, "互换流水开平仓事件进行中");
|
|
//同一交易代码、交易日期、标的、买卖方向 最多存在2条记录
|
|
var flowqueryGroup = mergeList.Where(n => n.DataState == (int)SwapFlowDateStateEnum.等待完成).GroupBy(g => g.SwapTradeId);
|
|
foreach (var flowMergeGroupItem in flowqueryGroup)
|
|
{
|
|
flowEvents.AddRange(MergePageEvent(flowMergeGroupItem.Key ?? 0, flowMergeGroupItem.ToList(), tradeDate, false));
|
|
}
|
|
|
|
new SysJobService(UserInfo).UpdateJob("互换流水合成持仓", 32, "互换流水开平仓事件完成");
|
|
return flowEvents;
|
|
|
|
}
|
|
/// <summary>
|
|
/// 分页处理互换流水开平仓事件,暂时只按加权平均处理
|
|
/// </summary>
|
|
/// <param name="pageSize"></param>
|
|
protected virtual List<swap_flow_event> MergePageEvent(int swapTradeId, List<swap_flow_merge> flowMergeList, DateTime tradeDate, bool needTrans = true)
|
|
{
|
|
List<swap_flow_event> flowEvents = new List<swap_flow_event>();
|
|
//按照同一互换编码、标的、买卖方向排序,一条买,一条卖//会存在买卖不在同一页
|
|
var flowquery = flowMergeList.OrderBy(o => o.SwapTradeId).ThenBy(o => o.UnderlyingCode).ThenBy(o => o.BsType).ToList();
|
|
if (flowquery.Count == 0)
|
|
{
|
|
return flowEvents;
|
|
}
|
|
var trade = FindTrade(swapTradeId);
|
|
var tradeExtend = FindTradeExtend(swapTradeId);
|
|
var trans = needTrans ? BeginTransaction() : null;
|
|
try
|
|
{
|
|
var eodPositions = FindPositions(swapTradeId);//上一日终持仓信息
|
|
FindAndInvalidateFutureEvents(swapTradeId, tradeDate);//废弃当前清算日期及之后的开平仓事件
|
|
OnBeforeMergePageEvent(swapTradeId, tradeDate, trade, tradeExtend, flowquery, eodPositions);
|
|
var mergeUnderlyingGroup = flowquery.GroupBy(g => g.UnderlyingCode);
|
|
int direction = tradeExtend.ExtendObj.Direction;
|
|
foreach (var underlyingGroup in mergeUnderlyingGroup)
|
|
{
|
|
var mergeList = underlyingGroup.OrderByDescending(o => o.TradingQty).ToList();//先按数量最大的排序
|
|
var flowMerge = mergeList.First();
|
|
var underlying = GetUnderlying(flowMerge.UnderlyingCode);
|
|
var matuirityDate = trade.ExerciseDate;
|
|
var positionId = ResolvePositionId(flowMerge, matuirityDate.Value, direction, trade.TradeNumber);
|
|
var payPosition = eodPositions.FirstOrDefault(x => x.PositionId == positionId);//浮动腿 日终持仓信息
|
|
bool hasPayPosition = payPosition != null;//是否存在日终持仓
|
|
if (mergeList.Count == 1)//只有一条流水
|
|
{
|
|
if (!hasPayPosition)//无日终持仓
|
|
{
|
|
var flowEvent = InitEvent((int)SwapFlowEventTypeEnum.开仓, flowMerge, direction, positionId, flowMerge.BsType, flowMerge.TradingQty, flowMerge.TradingAmount, flowMerge.TradingFee, 0, 0, flowMerge.TradingAmountAvg, flowMerge.TradingAmountFeeAvg, flowMerge.TradingAmountNetFeeAvg, matuirityDate, underlying.UnderlyingInstrumentType, tradeExtend.ExtendObj.SettlementRules);
|
|
flowEvents.Add(flowEvent);
|
|
}
|
|
else
|
|
{
|
|
if (flowMerge.BsType == payPosition.PositionType)//同向开仓
|
|
{
|
|
var qty = flowMerge.TradingQtyAbs;
|
|
var amount = flowMerge.TradingAmountAbs;
|
|
var flowEvent = InitEvent((int)SwapFlowEventTypeEnum.开仓, flowMerge, direction, positionId, flowMerge.BsType, qty, amount, flowMerge.TradingFeeAbs, 0, 0, flowMerge.TradingAmountAvg, flowMerge.TradingAmountFeeAvg, flowMerge.TradingAmountNetFeeAvg, matuirityDate, underlying.UnderlyingInstrumentType, tradeExtend.ExtendObj.SettlementRules);
|
|
flowEvents.Add(flowEvent);
|
|
}
|
|
else//反向平仓
|
|
{
|
|
var qty = flowMerge.TradingQtyAbs - payPosition.PosiQuantity;//平仓剩余数量
|
|
var bsType = qty > 0 ? flowMerge.BsType : payPosition.PositionType;
|
|
var unwindQty = qty > 0 ? payPosition.PosiQuantity : flowMerge.TradingQtyAbs;//平仓数量
|
|
var fee = payPosition.PosiQuantity == 0 ? 0 : flowMerge.TradingFeeAbs * -1 + (unwindQty / payPosition.PosiQuantity) * payPosition.PosiTradingFee;//平仓费用=平仓流水的交易费用佣金+平仓数量/平仓前的数量*平仓对象的(交易佣金费用+后付费用)
|
|
var amount = (flowMerge.TradingAmountAvgAbs - payPosition.PosiGrossPrice) * unwindQty * flowMerge.ContractSize;//平仓剩余金额=(平仓流水的成交均价-平仓对象的期初价格不含费)*平仓流水的成交数量*合约乘数
|
|
var flowEvent = InitEvent((int)SwapFlowEventTypeEnum.平仓, flowMerge, direction, positionId, payPosition.PositionType, unwindQty, flowMerge.TradingAmountAbs, flowMerge.TradingFeeAbs, amount, fee, flowMerge.TradingAmountAvg, flowMerge.TradingAmountFeeAvg, flowMerge.TradingAmountNetFeeAvg, matuirityDate, underlying.UnderlyingInstrumentType, tradeExtend.ExtendObj.SettlementRules);
|
|
flowEvents.Add(flowEvent);
|
|
if (qty > 0)//平仓有剩余,开仓
|
|
{
|
|
qty = Math.Abs(qty);
|
|
amount = flowMerge.TradingAmountAvgAbs * qty * flowMerge.ContractSize;
|
|
//positionId = GetPositionId(flowMerge, matuirityDate.Value, direction, trade.TradeNumber);
|
|
var flowEvent2 = InitEvent((int)SwapFlowEventTypeEnum.开仓, flowMerge, direction, positionId, bsType, qty, Math.Abs(amount), flowMerge.TradingFeeAbs, 0, 0, flowMerge.TradingAmountAvg, flowMerge.TradingAmountFeeAvg, flowMerge.TradingAmountNetFeeAvg, matuirityDate, underlying.UnderlyingInstrumentType, tradeExtend.ExtendObj.SettlementRules);
|
|
flowEvents.Add(flowEvent2);
|
|
}
|
|
}
|
|
}
|
|
flowMerge.DataState = 100;
|
|
}
|
|
else
|
|
{
|
|
var flowMerge2 = mergeList.Last();
|
|
if (!hasPayPosition)//无日终持仓
|
|
{
|
|
//先将数量最大的开仓
|
|
var flowEvent = InitEvent((int)SwapFlowEventTypeEnum.开仓, flowMerge, direction, positionId, flowMerge.BsType, flowMerge.TradingQtyAbs, flowMerge.TradingAmountAbs, flowMerge.TradingFeeAbs, 0, 0, flowMerge.TradingAmountAvg, flowMerge.TradingAmountFeeAvg, flowMerge.TradingAmountNetFeeAvg, matuirityDate, underlying.UnderlyingInstrumentType, tradeExtend.ExtendObj.SettlementRules);
|
|
//再将数量小的那条平仓
|
|
var qty = flowMerge.TradingQtyAbs - flowMerge2.TradingQtyAbs;//平仓剩余数量
|
|
var bsType = qty > 0 ? flowMerge.BsType : flowMerge2.BsType;
|
|
var unwindQty = qty > 0 ? flowMerge2.TradingQtyAbs : flowMerge.TradingQtyAbs;//平仓数量
|
|
var fee = flowMerge2.TradingFeeAbs * -1 + (unwindQty / flowMerge.TradingQtyAbs) * flowMerge.TradingFeeAbs * -1;//平仓费用=平仓流水的交易费用佣金+平仓数量/平仓前的数量*平仓对象的(交易佣金费用+后付费用)
|
|
var amount = (flowMerge2.TradingAmountAvgAbs - flowMerge.TradingAmountAvgAbs) * unwindQty * flowMerge2.ContractSize;//平仓剩余金额=(平仓流水的成交均价-平仓对象的期初价格不含费)*平仓流水的成交数量*合约乘数
|
|
var flowEvent2 = InitEvent((int)SwapFlowEventTypeEnum.平仓, flowMerge2, direction, positionId, flowMerge.BsType, unwindQty, flowMerge2.TradingAmountAbs, flowMerge2.TradingFeeAbs, amount, fee, flowMerge2.TradingAmountAvg, flowMerge2.TradingAmountFeeAvg, flowMerge2.TradingAmountNetFeeAvg, matuirityDate, underlying.UnderlyingInstrumentType, tradeExtend.ExtendObj.SettlementRules);
|
|
flowEvents.Add(flowEvent);
|
|
flowEvents.Add(flowEvent2);
|
|
}
|
|
else//有日终持仓,先平仓方向相反的流水,再处理另一条流水
|
|
{
|
|
flowMerge = mergeList.First(x => x.BsType != payPosition.PositionType);//反向流水
|
|
flowMerge2 = mergeList.Last(x => x.BsType == payPosition.PositionType);//同向流水
|
|
var qty = flowMerge.TradingQtyAbs - payPosition.PosiQuantity;//平仓剩余数量
|
|
var bsType = qty > 0 ? flowMerge.BsType : payPosition.PositionType;
|
|
var unwindQty = qty > 0 ? payPosition.PosiQuantity : flowMerge.TradingQtyAbs;//平仓数量
|
|
var fee = payPosition.PosiQuantity == 0 ? 0 : flowMerge.TradingFeeAbs * -1 + (unwindQty / payPosition.PosiQuantity) * payPosition.PosiTradingFee;//平仓费用=平仓流水的交易费用佣金+平仓数量/平仓前的数量*平仓对象的(交易佣金费用+后付费用)
|
|
var amount = (flowMerge.TradingAmountAvgAbs - payPosition.PosiGrossPrice) * unwindQty * flowMerge.ContractSize;//平仓剩余金额=(平仓流水的成交均价-平仓对象的期初价格不含费)*平仓流水的成交数量*合约乘数
|
|
var flowEvent = InitEvent((int)SwapFlowEventTypeEnum.平仓, flowMerge, direction, positionId, payPosition.PositionType, unwindQty, flowMerge.TradingAmountAbs, flowMerge.TradingFeeAbs, amount, fee, flowMerge.TradingAmountAvg, flowMerge.TradingAmountFeeAvg, flowMerge.TradingAmountNetFeeAvg, matuirityDate, underlying.UnderlyingInstrumentType, tradeExtend.ExtendObj.SettlementRules);
|
|
flowEvents.Add(flowEvent);
|
|
if (qty > 0)//反向流水平仓有剩余,开仓
|
|
{
|
|
//positionId = GetPositionId(flowMerge, matuirityDate.Value, direction, trade.TradeNumber);
|
|
amount = flowMerge.TradingAmountAvgAbs * qty * flowMerge.ContractSize;
|
|
var flowEvent2 = InitEvent((int)SwapFlowEventTypeEnum.开仓, flowMerge, direction, positionId, bsType, qty, amount, Math.Abs(fee), 0, 0, flowMerge.TradingAmountAvg, flowMerge.TradingAmountFeeAvg, flowMerge.TradingAmountNetFeeAvg, matuirityDate, underlying.UnderlyingInstrumentType, tradeExtend.ExtendObj.SettlementRules);
|
|
flowEvents.Add(flowEvent2);
|
|
if (bsType == flowMerge2.BsType)//平仓剩余与第二条流水同向
|
|
{
|
|
var flowEvent3 = InitEvent((int)SwapFlowEventTypeEnum.开仓, flowMerge2, direction, positionId, bsType, flowMerge2.TradingQtyAbs, flowMerge2.TradingAmountAbs, flowMerge2.TradingFeeAbs, 0, 0, flowMerge2.TradingAmountAvg, flowMerge2.TradingAmountFeeAvg, flowMerge2.TradingAmountNetFeeAvg, matuirityDate, underlying.UnderlyingInstrumentType, tradeExtend.ExtendObj.SettlementRules);
|
|
flowEvents.Add(flowEvent3);
|
|
}
|
|
else //方向相反,先平仓
|
|
{
|
|
var qty2 = qty - flowMerge2.TradingQtyAbs;
|
|
unwindQty = qty2 > 0 ? flowMerge2.TradingQtyAbs : qty;//平仓数量
|
|
bsType = qty2 > 0 ? bsType : flowMerge2.BsType;
|
|
var amount2 = (flowMerge2.TradingAmountAvgAbs - flowMerge.TradingAmountAvgAbs) * unwindQty * flowMerge2.ContractSize;
|
|
var fee2 = flowMerge2.TradingFeeAbs * -1 + (unwindQty / qty) * fee;
|
|
var flowEvent3 = InitEvent((int)SwapFlowEventTypeEnum.平仓, flowMerge2, direction, positionId, flowMerge2.BsType, unwindQty, flowMerge2.TradingAmountAbs, flowMerge2.TradingFeeAbs, amount2, fee2, flowMerge2.TradingAmountAvg, flowMerge2.TradingAmountFeeAvg, flowMerge2.TradingAmountNetFeeAvg, matuirityDate, underlying.UnderlyingInstrumentType, tradeExtend.ExtendObj.SettlementRules);
|
|
flowEvents.Add(flowEvent3);
|
|
if (qty2 != 0)//平仓有剩余,开仓
|
|
{
|
|
qty2 = Math.Abs(qty2);
|
|
//positionId = GetPositionId(flowMerge2, matuirityDate.Value, direction, trade.TradeNumber);
|
|
amount2 = flowMerge2.TradingAmountAvgAbs * qty2 * flowMerge2.ContractSize;
|
|
var flowEvent4 = InitEvent((int)SwapFlowEventTypeEnum.开仓, flowMerge2, direction, positionId, bsType, qty2, amount2, Math.Abs(fee2), 0, 0, flowMerge2.TradingAmountAvg, flowMerge2.TradingAmountFeeAvg, flowMerge2.TradingAmountNetFeeAvg, matuirityDate, underlying.UnderlyingInstrumentType, tradeExtend.ExtendObj.SettlementRules);
|
|
flowEvents.Add(flowEvent4);
|
|
}
|
|
}
|
|
|
|
}
|
|
else //无平仓剩余,开仓
|
|
{
|
|
var flowEvent2 = InitEvent((int)SwapFlowEventTypeEnum.开仓, flowMerge2, direction, positionId, flowMerge2.BsType, flowMerge2.TradingQtyAbs, flowMerge2.TradingAmountAbs, flowMerge2.TradingFeeAbs, 0, 0, flowMerge2.TradingAmountAvg, flowMerge2.TradingAmountFeeAvg, flowMerge2.TradingAmountNetFeeAvg, matuirityDate, underlying.UnderlyingInstrumentType, tradeExtend.ExtendObj.SettlementRules);
|
|
flowEvents.Add(flowEvent2);
|
|
}
|
|
}
|
|
flowMerge.DataState = (int)SwapFlowDateStateEnum.完成;
|
|
flowMerge2.DataState = (int)SwapFlowDateStateEnum.完成;
|
|
|
|
}
|
|
}
|
|
PersistEvents(flowEvents);
|
|
OnAfterMergePageEvent(swapTradeId, tradeDate, flowEvents);
|
|
if (trans != null) CommitTransaction(trans);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
if (trans != null) RollbackTransaction(trans);
|
|
throw new Exception(ex.Message, ex);
|
|
}
|
|
finally
|
|
{
|
|
trans?.Dispose();
|
|
}
|
|
return flowEvents;
|
|
}
|
|
/// <summary>
|
|
/// 保存流水事件
|
|
/// </summary>
|
|
/// <param name="underlying">标的信息</param>
|
|
/// <param name="eventType">事件类型</param>
|
|
/// <param name="flow_merge">汇总流水</param>
|
|
/// <param name="direction">浮动端收支方向</param>
|
|
/// <param name="swapPositionId">持仓编码</param>
|
|
/// <param name="positionType">多空方向</param>
|
|
/// <param name="TradingQty">开平仓数量</param>
|
|
/// <param name="TradingAmount">开平仓金额</param>
|
|
/// <param name="TradingFee">开平仓费用</param>
|
|
/// <param name="PayMarkUnwindPnl">平仓浮动盈亏</param>
|
|
/// <param name="PayFeeUnwindPnl">平仓浮动费用</param>
|
|
/// <param name="allUnwind">是否完全平仓</param>
|
|
/// <param name="settleRules">0 T+0 1 T+1</param>
|
|
protected virtual swap_flow_event InitEvent(
|
|
int eventType,
|
|
swap_flow_merge flow_merge,
|
|
int direction,
|
|
long swapPositionId,
|
|
int positionType,
|
|
decimal TradingQty,
|
|
decimal TradingAmount,
|
|
decimal TradingFee,
|
|
decimal PayMarkUnwindPnl,
|
|
decimal PayFeeUnwindPnl,
|
|
decimal TradingAmountAvg,
|
|
decimal TradingAmountFeeAvg,
|
|
decimal? TradingAmountNetFeeAvg,
|
|
DateTime? matuirityDate,
|
|
string underlyingInstrumentType, int settleRules)
|
|
{
|
|
swap_flow_event flow_Event = new swap_flow_event()
|
|
{
|
|
EventReason = "交易",
|
|
EventType = eventType,
|
|
SwapTradeId = flow_merge.SwapTradeId ?? 0,
|
|
SwapTradeNo = flow_merge.SwapTradeNo,
|
|
ContractSize = flow_merge.ContractSize,
|
|
CountRatio = 1,
|
|
UnderlyingCode = flow_merge.UnderlyingCode,
|
|
UnderlyingInstrumentType = underlyingInstrumentType,
|
|
DataState = 1,
|
|
EventDate = flow_merge.OccurTime,
|
|
UnwindDate = QdpCalendarHelper.GetNonHoliday(flow_merge.OccurTime.AddDays(1)),
|
|
TradingAmountAvg = Math.Round(TradingAmountAvg, GetStorageDeliveryPriceRound(underlyingInstrumentType, flow_merge.UnderlyingCode), MidpointRounding.AwayFromZero),
|
|
TradingAmountFeeAvg = TradingAmountFeeAvg,
|
|
TradingAmountNetFeeAvg = TradingAmountNetFeeAvg,
|
|
TradingAmountNetAvg = flow_merge.TradingAmountNetAvg,
|
|
ClientId = flow_merge.ClientId,
|
|
TradingFeePending = flow_merge.TradingFeePending,
|
|
};
|
|
UpdateDbOption(flow_Event);
|
|
flow_Event.MatuirityDate = matuirityDate;
|
|
flow_Event.PayDirection = direction;
|
|
flow_Event.PositionType = positionType;
|
|
flow_Event.PositionId = swapPositionId;
|
|
flow_Event.PayDate = flow_Event.UnwindDate.Value.AddDays(settleRules); //.todo 支付日期
|
|
flow_Event.Quantity = TradingQty;
|
|
flow_Event.TradingAmount = TradingAmount;
|
|
flow_Event.TradingFee = TradingFee;//费用先按负数处理
|
|
flow_Event.MarkClosePnl = PayMarkUnwindPnl;
|
|
flow_Event.CloseFee = PayFeeUnwindPnl;
|
|
flow_Event.DataState = (int)SwapFlowDateStateEnum.等待完成;
|
|
return flow_Event;
|
|
|
|
}
|
|
/// <summary>
|
|
/// 新增开平仓事件(实时持仓用)
|
|
/// </summary>
|
|
/// <param name="eventType"></param>
|
|
/// <param name="flow_merge"></param>
|
|
/// <param name="direction"></param>
|
|
/// <param name="positionType"></param>
|
|
/// <param name="TradingQty"></param>
|
|
/// <param name="TradingAmount"></param>
|
|
/// <param name="TradingFee"></param>
|
|
/// <param name="TradingAmountAvg"></param>
|
|
/// <param name="TradingAmountFeeAvg"></param>
|
|
/// <returns></returns>
|
|
public swap_flow_event AddEvent(int eventType, swap_flow_merge flow_merge, int direction,
|
|
int positionType,
|
|
decimal TradingQty,
|
|
decimal TradingAmount,
|
|
decimal TradingFee,
|
|
long positionId)
|
|
{
|
|
swap_flow_event flow_Event = new swap_flow_event()
|
|
{
|
|
EventReason = "交易",
|
|
EventType = eventType,
|
|
SwapTradeId = flow_merge.SwapTradeId ?? 0,
|
|
SwapTradeNo = flow_merge.SwapTradeNo,
|
|
ContractSize = flow_merge.ContractSize,
|
|
CountRatio = 1,
|
|
UnderlyingCode = flow_merge.UnderlyingCode,
|
|
DataState = 1,
|
|
EventDate = flow_merge.OccurTime,
|
|
UnwindDate = QdpCalendarHelper.GetNonHoliday(flow_merge.OccurTime.AddDays(1)),
|
|
TradingAmountAvg = Math.Round(
|
|
flow_merge.TradingAmountAvg,
|
|
GetStorageDeliveryPriceRound(null, flow_merge.UnderlyingCode),
|
|
MidpointRounding.AwayFromZero),
|
|
TradingAmountFeeAvg = flow_merge.TradingAmountFeeAvg,
|
|
TradingFeePending = flow_merge.TradingFeePending,
|
|
ClientId = flow_merge.ClientId
|
|
};
|
|
flow_Event.PayDirection = direction;
|
|
flow_Event.PositionType = positionType;
|
|
flow_Event.Quantity = TradingQty;
|
|
flow_Event.TradingAmount = TradingAmount;
|
|
flow_Event.TradingFee = TradingFee;//费用先按负数处理
|
|
flow_Event.PositionId = positionId;
|
|
return flow_Event;
|
|
}
|
|
/// <summary>
|
|
/// 确认交易开仓事件
|
|
/// </summary>
|
|
/// <param name="positions"></param>
|
|
/// <param name="td"></param>
|
|
public void InitEvent(List<swap_position> positions, trade td, string optLog)
|
|
{
|
|
foreach (swap_position position in positions)
|
|
{
|
|
swap_flow_event flow_Event = new swap_flow_event()
|
|
{
|
|
EventReason = "交易",
|
|
EventType = (int)SwapFlowEventTypeEnum.开仓,
|
|
SwapTradeId = position.SwapTradeId,
|
|
SwapTradeNo = td.TradeNumber,
|
|
ContractSize = position.ContractSize,
|
|
CountRatio = position.CountRatio,
|
|
UnderlyingCode = position.UnderlyingCode,
|
|
DataState = (int)SwapFlowDateStateEnum.完成,
|
|
EventDate = td.TradeDate.Value,
|
|
UnwindDate = td.StartDate.Value,
|
|
TradingAmountAvg = Math.Round(
|
|
position.PosiGrossPrice,
|
|
GetStorageDeliveryPriceRound(position.UnderlyingInstrumentType, position.UnderlyingCode),
|
|
MidpointRounding.AwayFromZero),
|
|
TradingAmountFeeAvg = position.PosiNetPrice,
|
|
TradingAmountNetFeeAvg = position.PosiNetFeePrice,
|
|
TradingAmountNetAvg = position.PosiNetNoFeePrice,
|
|
TradingFeePending = position.PosiTradingFeePending,
|
|
OptLog = optLog
|
|
};
|
|
UpdateDbOption(flow_Event);
|
|
flow_Event.ClientId = td.ClientId;
|
|
flow_Event.MatuirityDate = td.ExerciseDate;
|
|
flow_Event.PayDirection = position.PosiDirection;
|
|
flow_Event.PositionType = position.PositionType;
|
|
flow_Event.PositionId = position.id;
|
|
|
|
flow_Event.Quantity = position.PosiQuantity;
|
|
flow_Event.PositionQty = flow_Event.Quantity;
|
|
flow_Event.TradingAmount = position.PosiNotionalValue;
|
|
flow_Event.TradingFee = position.PosiTradingFee;
|
|
flow_Event.MarkClosePnl = 0;
|
|
flow_Event.CloseFee = 0;
|
|
|
|
flow_Event.InterestDirection = position.InterestDirection;
|
|
flow_Event.InterestRate = position.InterestRateDefault;
|
|
flow_Event.InterestPrincipal = position.InterestPrincipalFix;
|
|
flow_Event.InterestSwapInterval = position.InterestSwapInterval;
|
|
flow_Event.InterestMode = position.InterestMode;
|
|
flow_Event.UnderlyingInstrumentType = position.UnderlyingInstrumentType;
|
|
flow_Event.PayDate = flow_Event.UnwindDate.Value;
|
|
DbContext.swap_flow_event.Add(flow_Event);
|
|
}
|
|
}
|
|
/// <summary>
|
|
/// 确认交易开仓事件
|
|
/// </summary>
|
|
/// <param name="flowMerge"></param>
|
|
/// <param name="td"></param>
|
|
/// <param name="realPosition"></param>
|
|
/// <param name="underlyingInstrumentType"></param>
|
|
/// <returns></returns>
|
|
public swap_flow_event InitEvent(swap_flow_merge flowMerge, trade td, swap_position realPosition, string underlyingInstrumentType)
|
|
{
|
|
swap_flow_event flow_Event = new swap_flow_event()
|
|
{
|
|
EventReason = "交易",
|
|
EventType = (int)SwapFlowEventTypeEnum.开仓,
|
|
SwapTradeId = td.id,
|
|
SwapTradeNo = td.TradeNumber,
|
|
ContractSize = flowMerge.ContractSize,
|
|
CountRatio = 1,
|
|
UnderlyingCode = flowMerge.UnderlyingCode,
|
|
DataState = 100,
|
|
EventDate = td.TradeDate.Value,
|
|
UnwindDate = QdpCalendarHelper.GetNonHoliday(td.TradeDate.Value.AddDays(1)),
|
|
TradingAmountAvg = Math.Round(flowMerge.TradingAmountAvg, GetStorageDeliveryPriceRound(underlyingInstrumentType, flowMerge.UnderlyingCode), MidpointRounding.AwayFromZero),
|
|
TradingAmountFeeAvg = flowMerge.TradingAmountFeeAvg,
|
|
TradingAmountNetFeeAvg = flowMerge.TradingAmountNetFeeAvg,
|
|
TradingAmountNetAvg = flowMerge.TradingAmountNetAvg,
|
|
TradingFeePending = flowMerge.TradingFeePending,
|
|
};
|
|
UpdateDbOption(flow_Event);
|
|
flow_Event.ClientId = td.ClientId;
|
|
flow_Event.MatuirityDate = td.ExerciseDate;
|
|
flow_Event.PayDirection = 2;
|
|
flow_Event.PositionType = flowMerge.BsType;
|
|
flow_Event.PositionId = realPosition.PositionId;
|
|
|
|
flow_Event.Quantity = flowMerge.TradingQty;
|
|
flow_Event.PositionQty = realPosition.PosiQuantity;
|
|
flow_Event.TradingAmount = realPosition.PosiNotionalValue;
|
|
flow_Event.MarkClosePnl = 0;
|
|
flow_Event.CloseFee = 0;
|
|
flow_Event.UnderlyingInstrumentType = underlyingInstrumentType;
|
|
|
|
DbContext.swap_flow_event.Add(flow_Event);
|
|
DbContext.SaveChanges();
|
|
return flow_Event;
|
|
}
|
|
/// <summary>
|
|
/// 互换交易流水查询
|
|
/// </summary>
|
|
/// <param name="req"></param>
|
|
/// <returns></returns>
|
|
public SearchListResult<ClientSwapPositionResponse> SearchPositionFlowEvent(ClientSwapPositionRequest req)
|
|
{
|
|
var retListResult = GetSearchPositionEventList(req);
|
|
var clientDataSource = DataCacheProvider.GetClientDataSource();
|
|
foreach (var item in retListResult.rows)
|
|
{
|
|
var client = clientDataSource.GetData(item.ClientId);
|
|
item.ClientNumber = client.Number;
|
|
}
|
|
return retListResult;
|
|
}
|
|
/// <summary>
|
|
/// 互换交易流水查询
|
|
/// </summary>
|
|
/// <param name="req"></param>
|
|
/// <returns></returns>
|
|
private SearchListResult<ClientSwapPositionResponse> GetSearchPositionEventList(ClientSwapPositionRequest req)
|
|
{
|
|
// List<int> eventTyps = new List<int>() { (int)SwapEventTypeEnum.平仓, (int)SwapEventTypeEnum.互换, (int)SwapEventTypeEnum.自动互换 };
|
|
var predicate = PredicateBuilder.Create<swap_flow_event>(n => n.DataState == (int)SwapFlowDateStateEnum.完成 );
|
|
var tradePredicate = PredicateBuilder.Create<trade>(n => n.TradeType == "收益互换"
|
|
&& n.ValidState == ConsGlobal.Valid);
|
|
if (req.ClientId > 0)
|
|
{
|
|
tradePredicate = tradePredicate.And(x => x.ClientId == req.ClientId);
|
|
}
|
|
if (req.ValueDateFrom != null)
|
|
{
|
|
predicate = predicate.And(x => x.EventDate >= req.ValueDateFrom);
|
|
}
|
|
if (req.ValueDate != null)
|
|
{
|
|
predicate = predicate.And(x => x.EventDate <= req.ValueDate);
|
|
}
|
|
DbContext.SetDebugLog();
|
|
var positionQuery = DbContext.swap_flow_event.Where(predicate);
|
|
var tradeQuery = DbContext.trade.Where(tradePredicate);
|
|
var query = from td in tradeQuery
|
|
//join posi in DbContext.swap_position.Where(s=>!s.Invalid&&s.IsInitial) on td.id equals posi.SwapTradeId
|
|
join flow in positionQuery on td.id equals flow.SwapTradeId
|
|
join tr in DbContext.trade_contract_r.Where(x=>x.IsValid&&x.Type=="交易确认书") on td.id equals tr.TradeId into tradeContractGroup
|
|
from tradeContract in tradeContractGroup.DefaultIfEmpty()
|
|
select new ClientSwapPositionResponse
|
|
{
|
|
FlowEvent = flow,
|
|
SwapTradeNo = td.TradeNumber,
|
|
StructureType = td.StructureType,
|
|
ClientName = td.ClientName,
|
|
ClientId = td.ClientId,
|
|
ContractCode = tradeContract.ContractCode,
|
|
// FloatRateUnderlyingCode=posi.FloatRateUnderlyingCode,
|
|
StartDate = td.StartDate.Value,
|
|
InitYtm = td.InitYtm,
|
|
};
|
|
if (string.IsNullOrEmpty(req.sidx))
|
|
{
|
|
req.sidx = "FlowEvent.EventDate,SwapTradeNo";
|
|
req.sord = "asc";
|
|
}
|
|
var retListResult = query.ToSearchList(req);
|
|
var tds= retListResult.rows.Select(x => x.FlowEvent.SwapTradeId).Distinct().ToList();
|
|
var posiList = DbContext.swap_position.Where(s => !s.Invalid && s.IsInitial && tds.Contains(s.SwapTradeId) && !string.IsNullOrEmpty(s.FloatRateUnderlyingCode)).ToList();
|
|
// 查询成交收益率
|
|
Dictionary<int, decimal?> ytmMap = DbContext.trade.AsNoTracking().Where(p => tds.Contains(p.id)).ToList().ToDictionary(t => t.id, t => t.InitYtm);
|
|
foreach (var item in retListResult.rows)
|
|
{
|
|
item.FlowEvent.DividendPending = -item.FlowEvent.DividendPending;
|
|
item.FlowEvent.MarkClosePnl = -item.FlowEvent.MarkClosePnl;
|
|
item.FlowEvent.DividendIn = -item.FlowEvent.DividendIn;
|
|
item.FlowEvent.CloseFee = -item.FlowEvent.CloseFee;
|
|
item.FlowEvent.InterestFee = -item.FlowEvent.InterestFee;
|
|
item.FlowEvent.TradingFee = -item.FlowEvent.TradingFee;
|
|
item.FlowEvent.TradingFeePending = -item.FlowEvent.TradingFeePending;
|
|
item.FlowEvent.InterestClosePnL = -item.FlowEvent.InterestClosePnL;
|
|
item.FlowEvent.InitYtm = ytmMap.GetValueOrDefault(item.FlowEvent.SwapTradeId);
|
|
var posi= posiList.FirstOrDefault(s => s.id == item.FlowEvent.PositionId);
|
|
item.FloatRateUnderlyingCode= posi?.FloatRateUnderlyingCode;
|
|
if (item.FlowEvent.EventType==(int)SwapFlowEventTypeEnum.开仓)
|
|
{
|
|
item.TradeFee = 0;
|
|
item.TradingFee= item.FlowEvent.TradingFeePending;
|
|
}
|
|
else
|
|
{
|
|
item.TradeFee = item.FlowEvent.TradingFee;
|
|
item.TradingFee = item.FlowEvent.TradingFeePending;
|
|
item.PosiPnl= item.FlowEvent.MarkClosePnl;//MarkClosePnl 纯盯市不计算交易费用和分红
|
|
item.NetSettmentAmount = item.FlowEvent.FloatPnlSum + item.FlowEvent.InterestClosePnL;
|
|
item.Days = (item.FlowEvent.UnwindDate - item.StartDate).Value.Days;
|
|
}
|
|
|
|
}
|
|
return retListResult;
|
|
}
|
|
|
|
private void SetPosiPrice(swap_flow_event position)
|
|
{
|
|
var um = DataCacheProvider.GetUnderlyingDataSource().GetData(position.UnderlyingCode);
|
|
if (um != null && um.IsBond())
|
|
{
|
|
position.PosiNetPrice *= 100;
|
|
position.TradingAmountAvg *= 100;
|
|
position.TradingAmountFeeAvg *= 100;
|
|
position.PosiGrossPrice *= 100;
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|