using MoreLinq.Extensions;
using Newtonsoft.Json;
using Qdp.Pricing.Library.Base.Utilities;
using System.Linq.Expressions;
using YLErp.BLL;
using YLErp.BLL.Eod;
using YLErp.DBModels.Enums;
using YLErp.Modules.DataProviderModule;
using YLErp.Modules.EodModule;
using YLErp.Modules.TradeModule;
using YLErp.Modules.TradeModule.DealModule;
using YLErp.QdpModule;
namespace YLErp.Modules.SwapModule
{
public class SwapDealService : SwapTradeBaseService
{
protected virtual bool TryGetFloatRate(DateTime valueDate, string underlyingCode, out double rate)
{
return EodPriceQueryService.TryGetPrice(valueDate, underlyingCode, out rate);
}
public SwapDealService(OptUserInfo optUser) : base(optUser)
{
}
public SwapDealService(YLBaseService baseService) : base(baseService)
{
}
///
/// 平仓初始化
///
///
///
///
public UnwindData InitUnwind(int tradeId)
{
var td = DbContext.trade.Find(tradeId);
var positions = DbContext.swap_position.Where(x => x.SwapTradeId == tradeId && !x.Invalid);
var um = DataCacheProvider.GetUnderlyingDataSource().GetData(td.UnderlyingCode);
bool commodity = ConsGlobal.InstrumentType.CalcTypeIsFutures(um.UnderlyingInstrumentType);
List eventTyps = new List() { (int)SwapEventTypeEnum.自动互换, (int)SwapEventTypeEnum.互换 };
var dealDate = valuedateBLL.ValueDate <= td.ExerciseDate.Value ? valuedateBLL.ValueDate : td.ExerciseDate.Value;
//CheckLastEod(dealDate, td.TradeDate.Value, tradeId); //去掉平仓收盘限制
var tradeExtend = DbContext.trade_extend.FirstOrDefault(x => x.TradeId == tradeId);
td.trade_extend = tradeExtend;
var position = positions.Where(x => !string.IsNullOrEmpty(x.UnderlyingCode) && !x.IsInitial).FirstOrDefault();
var oriPosition = positions.Where(x => !string.IsNullOrEmpty(x.UnderlyingCode) && x.IsInitial).FirstOrDefault();
var preDealDate = GetPreDealDate(tradeId, dealDate, eventTyps);
var hasProcess = HasTradeProcess();
swap_flow_event floatEvent = new swap_flow_event();
UnwindData unwindData = new UnwindData();
if (((valuedateBLL.SystemDate.CloseReCheck == 1) || (valuedateBLL.SystemDate.CloseReApprove == 1 && hasProcess)) && td.TradeStatus == ConsTrade.平仓待复核)
{
var swapEvent = GetSwapEvent(tradeId, (int)SwapEventTypeEnum.平仓);
if (swapEvent == null)
{
throw new Exception("该笔交易状态为平仓待复核,未找到相关记录,请检查该笔交易是否有效");
}
unwindData = swapEvent.unwindData;
}
else
{
unwindData.TradeStartDate = td.StartDate;
unwindData.CloseType = commodity ? 1 : 2;
unwindData.StartDate = td.TradeDate.Value;
if (preDealDate.HasValue)
{
unwindData.StartDate = preDealDate.Value;
}
unwindData.ValueDate = dealDate;
unwindData.UnwindDate = dealDate;
floatEvent.EventDate = dealDate;
floatEvent.UnwindDate = unwindData.UnwindDate;
floatEvent.PayDate = QdpCalendarHelper.GetNonHoliday(unwindData.UnwindDate.Value.AddDays(td.trade_extend.ExtendObj.SettlementRules));
unwindData.PayDate = floatEvent.PayDate;
floatEvent.SwapTradeId = tradeId;
floatEvent.SwapTradeNo = td.TradeNumber;
unwindData.SwapTradeId = tradeId;
unwindData.StructureType = td.StructureType;
unwindData.NotionalValue = Convert.ToDecimal(td.OriginalStockEqvNotional ?? 0);
unwindData.NotionalQty = positions.Where(x => x.IsInitial).Sum(s => s.PosiQuantity);
unwindData.PosiNotionalValue = Convert.ToDecimal(td.StockEqvNotional);
unwindData.PositionQty = position != null ? position.PosiQuantity : Convert.ToDecimal(td.TradeAmount);
unwindData.AnnualDays = tradeExtend == null ? 365 : tradeExtend.ExtendObj.AnnualDays;
unwindData.CloseMethod = (int)CloseMethodEnum.全部平仓;
unwindData.ClosePercent = 1;
unwindData.CloseNotionalValue = unwindData.PosiNotionalValue;
unwindData.CloseQty = unwindData.PositionQty;
if (position != null)
{
floatEvent.PositionId = position.PositionId;
floatEvent.EventType = (int)SwapEventTypeEnum.平仓;
floatEvent.EventReason = "交易";
floatEvent.DividendIn = 0;
floatEvent.UnderlyingCode = position.UnderlyingCode;
floatEvent.UnderlyingInstrumentType = position.UnderlyingInstrumentType;
floatEvent.CloseFee = 0;
floatEvent.BeforeCloseFee = oriPosition.PosiTradingFeePending;
floatEvent.MarkClosePnl = 0;
floatEvent.PayDirection = position.PosiDirection;
floatEvent.PosiGrossPrice = position.PosiGrossPrice;
floatEvent.PosiNetPrice = position.PosiNetPrice;
floatEvent.TradingAmountNetAvg = position.PosiNetNoFeePrice;
floatEvent.PositionType = position.PositionType;
floatEvent.TradingAmountNetFeeAvg = position.PosiNetFeePrice;
floatEvent.Quantity = position.PosiQuantity;
floatEvent.PositionQty = 0;
floatEvent.ContractSize = position.ContractSize;
floatEvent.TradingAmount = floatEvent.Quantity * floatEvent.ContractSize;
var ratio = position.PosiDirection == (int)SwapDirectionEnum.收取 ? -1m : 1m;
floatEvent.TradingFeePending = position.PosiTradingFeePending;
floatEvent.DataState = (int)SwapFlowDateStateEnum.完成;
floatEvent.InterestMode = position.InterestMode;
floatEvent.ClientId = td.ClientId;
floatEvent.SetOpt(UserInfo);
}
unwindData.FlowEvents.Add(floatEvent);
}
return unwindData;
}
///
/// 校验上日是否收盘
///
///
public void CheckEodTrade(int tradeId)
{
var td = DbContext.trade.Find(tradeId);
var dealDate = valuedateBLL.ValueDate <= td.ExerciseDate.Value ? valuedateBLL.ValueDate : td.ExerciseDate.Value;
//CheckLastEod(dealDate, td.StartDate.Value, tradeId);
}
///
/// 校验收益结算操作(不检查收盘限制)
///
///
public void CheckEodTradeForIncome(int tradeId)
{
var td = DbContext.trade.Find(tradeId);
// 收益结算不检查收盘限制,只检查交易状态
if (td.TradeType != "收益互换")
{
throw new ServiceException("该交易不是收益互换类型");
}
if (td.ValidState == "InValid")
{
throw new ServiceException("该交易已无效");
}
if (td.TradeStatus != ConsTrade.确认成交 && td.TradeStatus != ConsTrade.提前终止拒绝)
{
throw new ServiceException($"该交易状态为【{td.TradeStatus}】,无法进行收益结算");
}
}
///
/// 多空组合 平仓初始化
///
///
///
///
public UnwindData InitLongShortUnwind(int tradeId, SwapEventTypeEnum eventTypeEnum)
{
var td = DbContext.trade.Find(tradeId);
if (td == null)
{
throw new ServiceException("未找到交易信息");
}
var positions = DbContext.swap_position.Where(x => x.SwapTradeId == tradeId && x.IsInitial && !x.Invalid);
List eventTyps = new List() { (int)SwapEventTypeEnum.平仓, (int)SwapEventTypeEnum.互换, (int)SwapEventTypeEnum.自动互换 };
var dealDate = valuedateBLL.ValueDate <= td.ExerciseDate.Value ? valuedateBLL.ValueDate : td.ExerciseDate.Value;
//CheckLastEod(dealDate, td.TradeDate.Value, tradeId); //去掉平仓收盘限制
var tradeExtend = DbContext.trade_extend.FirstOrDefault(x => x.TradeId == tradeId);
td.trade_extend = tradeExtend;
var preDealDate = GetPreDealDate(tradeId, dealDate, eventTyps);
double stockEqvNotional = td.StockEqvNotional;//剩余名义本金
var hasProcess = HasTradeProcess();
swap_flow_event floatEvent = new swap_flow_event();
UnwindData unwindData = new UnwindData();
if (((valuedateBLL.SystemDate.CloseReCheck == 1) || (valuedateBLL.SystemDate.CloseReApprove == 1 && hasProcess)) && (td.TradeStatus == ConsTrade.平仓待复核 || td.TradeStatus == ConsTrade.互换待复核))
{
var swapEvent = GetSwapEvent(tradeId, (int)eventTypeEnum);
if (swapEvent == null)
{
throw new Exception("该笔交易状态为平仓待复核,未找到相关记录,请检查该笔交易是否有效");
}
unwindData = swapEvent.unwindData;
}
else
{
unwindData.StartDate = td.TradeDate.Value;
if (preDealDate.HasValue)
{
unwindData.StartDate = preDealDate.Value;
}
unwindData.ValueDate = dealDate;
unwindData.UnwindDate = dealDate;
unwindData.PayDate = QdpCalendarHelper.GetNonHoliday(dealDate.AddDays(td.trade_extend.ExtendObj.SettlementRules));
unwindData.SwapTradeId = tradeId;
unwindData.NotionalValue = Convert.ToDecimal(td.OriginalStockEqvNotional ?? 0);
unwindData.NotionalQty = positions.Sum(s => s.PosiQuantity);
unwindData.PosiNotionalValue = Convert.ToDecimal(stockEqvNotional);
unwindData.PositionQty = 0;//平仓只做了结为0,互换用不上
unwindData.AnnualDays = tradeExtend == null ? 365 : tradeExtend.ExtendObj.AnnualDays;
if (eventTypeEnum == SwapEventTypeEnum.平仓)
{
unwindData.FlowEvents = GetUnwindInterests(dealDate, unwindData.UnwindDate.Value, tradeId, 1, (int)SwapEventTypeEnum.平仓);
}
}
return unwindData;
}
///
/// 平仓初始化
///
///
///
///
public UnwindData InitIncome(int tradeId)
{
var checkEventTypes = new List() { (int)SwapEventTypeEnum.互换, (int)SwapEventTypeEnum.自动互换 };
var td = DbContext.trade.Find(tradeId);
var positions = DbContext.swap_position.Where(x => x.SwapTradeId == tradeId && !x.Invalid);
var um = DataCacheProvider.GetUnderlyingDataSource().GetData(td.UnderlyingCode);
List eventTypes = new List() { (int)SwapFlowEventTypeEnum.互换, (int)SwapFlowEventTypeEnum.自动互换 };
var dealDate = valuedateBLL.ValueDate < td.ExerciseDate.Value ? valuedateBLL.ValueDate : td.ExerciseDate.Value;
// 收益结算不检查收盘限制
var tradeExtend = DbContext.trade_extend.FirstOrDefault(x => x.TradeId == tradeId);
td.trade_extend = tradeExtend;
var position = positions.Where(x => !string.IsNullOrEmpty(x.UnderlyingCode) && !x.IsInitial).FirstOrDefault();
//var preSettleDate = CheckLastEod(dealDate, td.StartDate.Value, tradeId);//上一交易日期
var preDealDate = GetPreDealDate(tradeId, dealDate, eventTypes);
var hasProcess = HasTradeProcess();
swap_flow_event floatEvent = new swap_flow_event();
UnwindData unwindData = new UnwindData();
if (((valuedateBLL.SystemDate.CloseReCheck == 1) || (valuedateBLL.SystemDate.CloseReApprove == 1 && hasProcess)) && td.TradeStatus == ConsTrade.互换待复核)
{
var swapEvent = GetSwapEvent(tradeId, (int)SwapEventTypeEnum.互换);
if (swapEvent == null)
{
throw new Exception("该笔交易状态为平仓待复核,未找到相关记录,请检查该笔交易是否有效");
}
unwindData = swapEvent.unwindData;
}
else
{
unwindData.StartDate = td.TradeDate.Value;
if (preDealDate.HasValue)
{
unwindData.StartDate = preDealDate.Value;
}
unwindData.TradeStartDate = td.StartDate;
unwindData.ValueDate = dealDate;
unwindData.UnwindDate = dealDate;
floatEvent.UnwindDate = unwindData.UnwindDate;
floatEvent.EventDate = dealDate;
unwindData.PayDate = QdpCalendarHelper.GetNonHoliday(unwindData.UnwindDate.Value.AddDays(td.trade_extend.ExtendObj.SettlementRules));
floatEvent.PayDate = unwindData.PayDate;
floatEvent.SwapTradeId = tradeId;
floatEvent.SwapTradeNo = td.TradeNumber;
floatEvent.EventType = (int)SwapFlowEventTypeEnum.互换;
floatEvent.EventReason = "交易";
unwindData.SwapTradeId = tradeId;
unwindData.StructureType = td.StructureType;
unwindData.NotionalValue = Convert.ToDecimal(td.OriginalStockEqvNotional ?? 0);
unwindData.NotionalQty = positions.Where(x => x.IsInitial).Sum(s => s.PosiQuantity);
unwindData.PosiNotionalValue = Convert.ToDecimal(td.StockEqvNotional);
unwindData.PositionQty = Convert.ToDecimal(td.TradeAmount);
unwindData.AnnualDays = tradeExtend == null ? 365 : tradeExtend.ExtendObj.AnnualDays;
unwindData.ClosePercent = unwindData.PosiNotionalValue / unwindData.NotionalValue;
unwindData.CloseNotionalValue = unwindData.PosiNotionalValue;
if (position != null)
{
floatEvent.PositionId = position.PositionId;
floatEvent.DividendIn = 0;
floatEvent.UnderlyingCode = position.UnderlyingCode;
floatEvent.UnderlyingInstrumentType = position.UnderlyingInstrumentType;
floatEvent.CloseFee = 0;
floatEvent.MarkClosePnl = 0;
floatEvent.PayDirection = position.PosiDirection;
floatEvent.PosiGrossPrice = position.PosiGrossPrice;
floatEvent.PosiNetPrice = position.PosiNetPrice;
// 注意:TradingAmountNetAvg 字段名为"成交净价(期末语义)",但收益结算/平仓初始化时装入的是期初净价(PosiNetNoFeePrice),前端展示期初净价时取此字段
floatEvent.TradingAmountNetAvg = position.PosiNetNoFeePrice;
floatEvent.TradingAmountNetFeeAvg = position.PosiNetFeePrice;
floatEvent.PositionType = position.PositionType;
floatEvent.Quantity = position.PosiQuantity;
floatEvent.PositionQty = 0;
floatEvent.ContractSize = position.ContractSize;
floatEvent.TradingAmount = floatEvent.Quantity * floatEvent.ContractSize;
floatEvent.ClientId = td.ClientId;
floatEvent.DataState = (int)SwapFlowDateStateEnum.完成;
}
unwindData.FlowEvents.Add(floatEvent);
}
return unwindData;
}
///
/// 获取平仓利息端信息
///
/// 平仓日期
/// 交易id
/// 平仓比例
///
///
public List GetUnwindInterests(DateTime valueDate, DateTime unwindDate, int tradeId, decimal closePercent, int eventType)
{
List interests = new List();
if (closePercent > 1)
{
closePercent = 1;//防篡改
}
else if (closePercent < 0)
{
closePercent = 0;
}
var td = DbContext.trade.Find(tradeId);
if (td == null)
{
throw new ServiceException("未找到交易信息");
}
var allpositions = DbContext.swap_position.Where(x => x.SwapTradeId == tradeId && !x.Invalid).ToList();
var origPositions = allpositions.Where(x => x.IsInitial).ToList();
var realPostitions = allpositions.Where(x => !x.IsInitial).ToList();
var positions = origPositions.Where(x => x.PosiDirection == 0).ToList();
var fpositions = origPositions.Where(x => x.PosiDirection > 0).ToList();
var longPositions = fpositions.Where(x => x.PositionType == (int)PositionTypeFlag.Long).ToList();
var shortPositions = fpositions.Where(x => x.PositionType == (int)PositionTypeFlag.Short).ToList();
var tradeExtend = DbContext.trade_extend.FirstOrDefault(x => x.TradeId == tradeId);
List eventTypes = new List() { (int)SwapEventTypeEnum.平仓, (int)SwapEventTypeEnum.互换, (int)SwapEventTypeEnum.自动互换 };
var lastEod = DbContext.eod_swap.Where(x => x.ValueDate < unwindDate && x.SwapTradeId == tradeId).OrderByDescending(o => o.ValueDate).FirstOrDefault();
var _preSetteDate = lastEod == null ? unwindDate.AddDays(-1) : lastEod.ValueDate;
List lastEodPositions = new SwapEodPositionService(this).GetPreEodPositions(tradeId, _preSetteDate);//上一交易数据
var posiLongNotionalValue = longPositions.Sum(s => s.PosiNotionalValue);// 剩余名义本金
var posiShortNotionalValue = shortPositions.Sum(s => s.PosiNotionalValue);// 剩余名义本金
var stockEqvNotional = realPostitions.Where(x => x.PosiDirection > 0).Sum(s => s.PosiNotionalValue);
var posiNotionalValue = stockEqvNotional * closePercent;//剩余名义本金
var orginPv = lastEod != null ? lastEod.NotionalValue : stockEqvNotional;
var grossPrice = realPostitions.Where(x => x.PosiDirection > 0).FirstOrDefault()?.PosiGrossPrice;
var closeList = DbContext.swap_flow_event.Where(x => x.SwapTradeId == tradeId && x.UnwindDate == unwindDate && eventTypes.Contains(x.EventType) && x.DataState == (int)SwapFlowDateStateEnum.完成).ToList();
bool tdClose = closeList.Count > 0;
interests = GetInterests(td, tradeExtend, valueDate, unwindDate, lastEodPositions, positions, stockEqvNotional, posiLongNotionalValue, posiShortNotionalValue, posiNotionalValue, closePercent, eventType, tdClose, false, grossPrice ?? 0, orginPv, true, false,false, closeList);
return interests;
}
///
/// 获取利息腿"已通过历史互换结出的累计利息"(用于复利重算时扣除,类比分红的 CalcConsumedDividend)。
/// 数据源为事件级 swap_flow_event.InterestAmount(互换/自动互换 完成态事件,互换当时即落库,不依赖日终归档)。
///
/// 交易id
/// 利息腿id
/// 结算日(不含,仅汇总此日之前的历史已结利息;当日事件由 closeList 去重逻辑单独处理)
/// 历史已结利息累计金额(绝对值)
public virtual decimal GetConsumedInterest(int tradeId, long positionId, DateTime beforeDate)
{
List swapEventTypes = new List() { (int)SwapEventTypeEnum.互换, (int)SwapEventTypeEnum.自动互换 };
var consumed = DbContext.swap_flow_event
.Where(x => x.SwapTradeId == tradeId && x.PositionId == positionId
&& swapEventTypes.Contains(x.EventType)
&& x.DataState == (int)SwapFlowDateStateEnum.完成
&& x.EventDate < beforeDate)
.Sum(s => (decimal?)s.InterestAmount) ?? 0m;
return consumed;
}
///
/// 计算利息腿计息详细
///
/// 交易
/// 交易扩展数据
/// 操作日期
/// 上一日终持仓
/// 期初利率端
/// 持仓名义本金
/// 多头持仓名义本金
/// 空头持仓名义本金
/// 平仓名义本金
///
///
///
///
///
public List GetInterests(
trade td,
trade_extend tradeExtend,
DateTime valueDate,
DateTime unwindDate,
List eodPositions,
List 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 closeList = null)
{
List interests = new List();
var annualDays = tradeExtend == null ? 365 : tradeExtend.ExtendObj.AnnualDays;
bool calcFirst = tradeExtend?.ExtendObj.InterestCalcMode?.StartsWith("1") ?? true;
bool calcLast = tradeExtend?.ExtendObj.InterestCalcMode.EndsWith("1") ?? true;
foreach (var position in positions)
{
// 初始化持仓信息
var preEodPosition = eodPositions.FirstOrDefault(x => x.PositionId == position.id) ?? new eod_swap_position();
var positionClone = position.Clone();
DateTime? preDealDate = preEodPosition.id != 0 ? preEodPosition.ValueDate : null;
// 计算计息区间
int interestPeriod = position.interest_rest_days ?? 1;
bool swap = InitInterestDate(unwindDate, preDealDate, td, tdClose, out DateTime startDate, out DateTime endDate);
// 计算名义本金
var (closePrincipal, posiPrincipal, newClosePercent) = CalcNotionalByMode(position, closePrecent, posiNotionalValue, posiLongNotionalValue, posiShortNotionalValue);
if ((InterestModeEnum)position.InterestMode == InterestModeEnum.追加预付金 || (InterestModeEnum)position.InterestMode == InterestModeEnum.初始预付金)
{
positionClone.InterestDirection = position.InterestDirection == (int)SwapDirectionEnum.收取 ? (int)SwapDirectionEnum.支付 : (int)SwapDirectionEnum.收取;
}
// 获取利率
decimal rate = GetFixedRate(position, unwindDate);
decimal floatRate = GetFloatRate(position, preEodPosition, td.StartDate.Value, endDate, interestPeriod, swap, positionClone);
// 根据场景计算利息
if (settment)
{
// 收盘归档场景,使用 CalcEodInterest
interests.Add(CalcEodInterest(td, valueDate, positionClone, rate, floatRate, closePrincipal, posiPrincipal, annualDays, calcFirst, calcLast, preEodPosition, eventType, add));
}
else
{
// 盘中互换场景,使用 CalcUnwindInterest
// 取历史已结利息(事件级,互换当时落库),供复利重算扣除(仅复利需要;单利基于日终快照自带状态)
var consumedInterest = position.InterestType == (int)InterestTypeEnum.复利
? GetConsumedInterest(td.id, position.id, endDate)
: 0m;
interests.Add(CalcUnwindInterest(td, valueDate, endDate, positionClone, rate, floatRate, posiPrincipal, closePrincipal, newClosePercent, annualDays, preEodPosition, eventType, add, swap, orginPv, calcFirst, calcLast||newCalcLast, consumedInterest));
}
}
//当日有平仓或互换记录时,避免重复结算
if (closeList != null && closeList.Count > 0)
{
foreach (var item in interests)
{
var closeEvent = closeList.Where(x => x.PositionId == item.PositionId);
if (eventType == (int)SwapEventTypeEnum.互换 || eventType == (int)SwapEventTypeEnum.自动互换)
{
// 互换:该仓位当天有同类型的完成事件,直接归0
var swapCloseEvent = closeEvent.Where(x => x.EventType == eventType);
if (swapCloseEvent.Any())
{
item.InterestAmount = 0;
item.TdInterestAmount = 0;
item.InterestClosePnL = 0;
}
}
else if (!calcLast && !newCalcLast)
{
// 平仓不算尾:扣除已结算的利息(算尾时利息已包含关闭日,无重叠)
var closePnl = closeEvent.Sum(s => s.InterestClosePnL);
var closeAmount = closeEvent.Sum(s => s.InterestAmount);
var closeTdAmount = closeEvent.Sum(s => s.TdInterestAmount);
item.InterestAmount = item.InterestAmount - closeAmount;
item.TdInterestAmount = item.TdInterestAmount - closeTdAmount;
item.InterestClosePnL = item.InterestClosePnL - closePnl;
}
}
}
return interests;
}
///
/// 根据计息模式计算名义本金
///
private (decimal close, decimal posi, decimal closePct) CalcNotionalByMode(swap_position position, decimal closePercent, decimal posiNotional, decimal posiLong, decimal posiShort)
{
decimal closePrincipal = posiNotional;
decimal posiPrincipal = posiNotional;
decimal newClosePercent = closePercent;
switch ((InterestModeEnum)position.InterestMode)
{
case InterestModeEnum.固定值:
closePrincipal = posiPrincipal = position.InterestPrincipalFix;
newClosePercent = 1m;
break;
case InterestModeEnum.多头存续名义本金:
closePrincipal = posiLong * closePercent;
posiPrincipal = posiLong;
break;
case InterestModeEnum.空头存续名义本金:
closePrincipal = posiShort * closePercent;
posiPrincipal = posiShort;
break;
case InterestModeEnum.标的期初全价:
closePrincipal = posiNotional * closePercent;
break;
case InterestModeEnum.追加预付金:
case InterestModeEnum.初始预付金:
closePrincipal = position.InterestPrincipalFix * closePercent;
posiPrincipal = position.InterestPrincipalFix;
break;
}
return (closePrincipal, posiPrincipal, newClosePercent);
}
///
/// 获取固定利率
///
private decimal GetFixedRate(swap_position position, DateTime startDate)
{
var swapIntervalToday = position.SwapIntervalList?.Where(x => x.Date <= startDate).OrderByDescending(o => o.Date).FirstOrDefault();
if (swapIntervalToday != null) return swapIntervalToday.Rate;
var nextInterval = position.SwapIntervalList?.Where(x => x.Date > startDate).OrderBy(o => o.Date).FirstOrDefault();
return nextInterval?.Rate ?? position.InterestRateDefault;
}
///
/// 获取浮动利率
///
private decimal GetFloatRate(swap_position position, eod_swap_position preEod, DateTime startDate, DateTime endDate, int period, bool swap, swap_position positionClone)
{
if (string.IsNullOrEmpty(position.FloatRateUnderlyingCode)) return position.FloatRate;
int days = (endDate - startDate).Days;
DateTime rateDate = days % period == 0
? QdpCalendarHelper.GetNonHolidayDefore(endDate.AddDays(position.interest_rule ?? 0))
: QdpCalendarHelper.GetNonHolidayDefore(startDate.AddDays(position.interest_rule ?? 0));
if (preEod.id != 0 && days % period != 0)
{
position.FloatRate = positionClone.FloatRate = preEod.FloatRate;
return preEod.FloatRate;
}
if (TryGetFloatRate(rateDate, position.FloatRateUnderlyingCode, out double rate))
{
position.FloatRate = positionClone.FloatRate = Convert.ToDecimal(rate);
return position.FloatRate;
}
if (!swap) throw new Exception($"获取不到{position.FloatRateUnderlyingCode}在{rateDate:yyyy年MM月dd日}的价格");
return 0m;
}
///
/// 计算收盘利息(EOD)
///
private swap_flow_event CalcEodInterest(trade td, DateTime valueDate, swap_position position, decimal rate, decimal floatRate, decimal closePrincipal, decimal posiPrincipal, int annualDays, bool calcFirst, bool calcLast, eod_swap_position preEod, int eventType, bool add)
{
// 判断当日是否计息:首日不算头或到期日不算尾则不计息
bool calcToday = true;
if (calcFirst == false && valueDate == td.StartDate.Value) calcToday = false; // 首日不算头
if (calcLast == false && valueDate == td.ExerciseDate.Value) calcToday = false; // 到期日不算尾
if (valueDate < position.PosiStartDate)
{
calcToday = false;
}
// 初始化EOD持仓信息
if (preEod.id == 0)
{
preEod.FloatRate = floatRate;
preEod.TdInterestPrincipal = posiPrincipal;
preEod.PosiNotionalValue = posiPrincipal;
}
// 构建利息事件
var interest = new swap_flow_event
{
SwapTradeId = td.id,
SwapTradeNo = td.TradeNumber,
EventType = eventType,
EventReason = "交易",
EventDate = valueDate,
PositionId = position.id,
InterestDirection = position.InterestDirection,
InterestRate = rate,
InterestPrincipal = closePrincipal,
InterestSwapInterval = position.InterestSwapInterval,
InterestMode = position.InterestMode,
FloatRate = floatRate,
DataState = (int)SwapFlowDateStateEnum.完成,
ClientId = td.ClientId,
UnwindDate = valueDate
};
// 收盘场景使用 preEod.FloatRate(历史浮动利率),与 InitSwapDealInterest 收盘场景保持一致
decimal eodFloatRate = preEod.id != 0 ? preEod.FloatRate : floatRate;
decimal interestAmount = 0;
decimal tdInterestAmount = 0;
if (calcToday)
{
if (position.InterestType == (int)InterestTypeEnum.复利)
{
// 复利计算
CalcDailyCompoundInterestByEod(preEod, valueDate, td.StartDate.Value, position, closePrincipal, posiPrincipal, interest, annualDays, false, eodFloatRate, 1m, posiPrincipal, ref interestAmount, ref tdInterestAmount);
}
else
{
// 单利计算
CalcDailySimpleInterestByEod(preEod, valueDate, td.StartDate.Value, position, closePrincipal, posiPrincipal, interest, annualDays, false, eodFloatRate, 1m, posiPrincipal, ref interestAmount, ref tdInterestAmount);
}
}
// 四舍五入并赋值
interest.InterestAmount = Math.Round(interestAmount, ConsGlobal.PriceRound, MidpointRounding.AwayFromZero);
interest.TdInterestAmount = Math.Round(tdInterestAmount, ConsGlobal.PriceRound, MidpointRounding.AwayFromZero);
// 计算InterestClosePnL(方向:收取=1为正,支付=-1为负)
var interestRatio = position.InterestDirection == 1 ? 1m : -1m;
interest.InterestClosePnL = interest.InterestAmount * interestRatio;
if (add) UpdateDbOption(interest);
return interest;
}
///
/// 计算盘中利息(平仓/互换)
///
private swap_flow_event CalcUnwindInterest(trade td, DateTime valueDate, DateTime endDate, swap_position position, decimal rate, decimal floatRate, decimal posiPrincipal, decimal closePrincipal, decimal closePercent, int annualDays, eod_swap_position preEod, int eventType, bool add, bool swap, decimal orginPv, bool calcFirst, bool calcLast, decimal consumedInterest = 0m)
{
if (preEod.id == 0)
{
preEod.FloatRate = floatRate;
preEod.TdInterestPrincipal = posiPrincipal;
preEod.PosiNotionalValue = posiPrincipal;
preEod.ValueDate = td.StartDate.Value;
if (calcFirst)
{
preEod.ValueDate= preEod.ValueDate.AddDays(-1);
}
}
return InitSwapDealInterest(td, valueDate, endDate, rate, position, add, swap, posiPrincipal, closePrincipal, closePercent, annualDays, eventType, preEod, false, orginPv, calcFirst, calcLast, consumedInterest);
}
///
/// 初始化利息腿信息
///
/// 交易编码
/// 计息开始日期
/// 计息结束日期
/// 计息年化利率
/// 利息腿
/// 是否新增
/// 是否已互换
/// 上一日终归档
/// 当日适用名义本金
/// 当日平仓名义本金
/// 年化天数
///
private swap_flow_event InitSwapDealInterest(trade td,
DateTime valueDate,
DateTime endDate,
decimal rate,
swap_position position,
bool add,
bool swap,
decimal posiNotionalValue,
decimal closePosiNotionalValue,
decimal closePrecent,
int annualDays,
int eventType,
eod_swap_position preEodPosition,
bool needPrice,
decimal orginPv,
bool calcFirst,
bool calcLast,
decimal consumedInterest = 0m
)
{
decimal interestProfitSum = preEodPosition.InterestProfitSum;
swap_flow_event interest = new swap_flow_event();
interest.SwapTradeId = td.id;
interest.SwapTradeNo = td.TradeNumber;
interest.EventType = eventType;
interest.EventReason = "交易";
interest.EventDate = valueDate;
interest.PositionId = position.id;
interest.InterestDirection = position.InterestDirection;
interest.InterestRate = rate;
interest.InterestPrincipal = closePosiNotionalValue;
interest.InterestSwapInterval = position.InterestSwapInterval;
interest.InterestMode = position.InterestMode;
interest.FloatRate = position.FloatRate;
interest.DataState = (int)SwapFlowDateStateEnum.完成;
interest.ClientId = td.ClientId;
interest.UnwindDate = endDate;
if (swap)
{
interest.InterestAmount = 0;
interest.TdInterestAmount = 0;
interest.InterestAmount = 0;
interest.InterestClosePnL = 0;
}
else
{
decimal InterestAmount = 0;
decimal TdInterestAmount = 0;
var interestRatio = position.InterestDirection == 1 ? 1m : -1m;
var floateRate = preEodPosition.FloatRate;
if (position.InterestType == (int)InterestTypeEnum.复利)
{
CalcDailyCompoundInterest( endDate, position, closePosiNotionalValue, interest, annualDays, needPrice, floateRate, closePrecent, orginPv, calcFirst, calcLast, ref InterestAmount, ref TdInterestAmount, consumedInterest);
}
else
{
CalcDailySimpleInterest(preEodPosition, endDate, position, posiNotionalValue, interest, annualDays, needPrice, floateRate, closePrecent, orginPv, calcFirst, calcLast, ref InterestAmount, ref TdInterestAmount);
}
interest.InterestAmount = Math.Round(InterestAmount, ConsGlobal.PriceRound, MidpointRounding.AwayFromZero);
interest.TdInterestAmount = Math.Round(TdInterestAmount, ConsGlobal.PriceRound, MidpointRounding.AwayFromZero);
interest.InterestClosePnL = interest.InterestAmount * interestRatio;
}
if (add)
{
UpdateDbOption(interest);
}
return interest;
}
///
/// 计算复利 盘中
///
/// 上一互换日
/// 结算日期
/// 浮动标的
/// 计息基数
/// 固定利率
/// 是否年化
/// 年化天数
///
public void CalcDailyCompoundInterest( DateTime endDate, swap_position position, decimal principal, swap_flow_event flowEvent, int annualDays, bool needPrice, decimal floateRate, decimal closePercent, decimal orginPv, bool calcFirst, bool calcLast, ref decimal InterestAmount, ref decimal TdInterestAmount, decimal consumedInterest = 0m)
{
var startDate = position.PosiStartDate;
decimal interestProfitSum = 0;
decimal TdInterestPrincipal = 0;
decimal interest = interestProfitSum ;
decimal tdinterest = interestProfitSum ;
int interestPeriod = position.interest_rest_days ?? 1;
// 复利:只能用要平仓的名义本金从头开始算
decimal dynomicPrincipal = principal;
decimal tdDynomicPrincipal = dynomicPrincipal;
var calcDays = (endDate - startDate).Days;
double floatRate = Convert.ToDouble(floateRate);
for (int i = 0; i <= calcDays; i++)
{
var accrueDate = startDate.AddDays(i);
if (!calcFirst && accrueDate == startDate) continue; // 首日不算头
if (!calcLast && accrueDate == endDate) continue; // 到期日不算尾
if (accrueDate >= startDate)
{
if (i % interestPeriod == 0)
{
// 复利时:利息并入本金
dynomicPrincipal = principal + interest;
tdDynomicPrincipal = principal + interest;
// 获取新的浮动利率
if (!string.IsNullOrEmpty(position.FloatRateUnderlyingCode))
{
var fr007RateDate = QdpCalendarHelper.GetNonHolidayDefore(accrueDate.AddDays(position.interest_rule ?? 0));
if (TryGetFloatRate(fr007RateDate, position.FloatRateUnderlyingCode, out double floatRate1))
{
if (floatRate1 != 0) floatRate = floatRate1;
}
else
{
throw new Exception($"获取不到{position.FloatRateUnderlyingCode}在{fr007RateDate:yyyy年MM月dd日}的价格");
}
}
flowEvent.InterestPrincipal = tdDynomicPrincipal;
TdInterestPrincipal = tdDynomicPrincipal;
}
else
{
// 复利非重置日:利息不并入本金,不用closePercent缩放(principal已反映平仓比例)
flowEvent.InterestPrincipal = tdDynomicPrincipal;
TdInterestPrincipal = tdDynomicPrincipal;
}
flowEvent.FloatRate = Convert.ToDecimal(floatRate);
var interest1 = flowEvent.InterestPrincipal * (flowEvent.InterestRate + Convert.ToDecimal(floatRate));
var tdinterest1 = TdInterestPrincipal * (flowEvent.InterestRate + Convert.ToDecimal(floatRate));
if (position.IsAnnualized)
{
interest1 /= annualDays;
tdinterest1 /= annualDays;
}
interest += interest1;
tdinterest += tdinterest1;
}
}
// 复利从头重放得到的是"假设从未结出"的整段总利息,需扣除历史已通过互换结出的利息,
// 否则已结部分会重复计息(类比分红 PosiDividendSum = totalToDate − RealizedDividend)。
// consumedInterest 为绝对值口径(swap_flow_event.InterestAmount 之和),与 interest 口径一致。
interest -= consumedInterest;
tdinterest -= consumedInterest;
InterestAmount = Math.Round(interest, ConsGlobal.PriceRound, MidpointRounding.AwayFromZero);
TdInterestAmount = Math.Round(tdinterest, ConsGlobal.PriceRound, MidpointRounding.AwayFromZero);
}
///
/// 计算单利 盘中(按重置天数分段,每段使用对应浮动利率)
///
public void CalcDailySimpleInterest(eod_swap_position preEodPosition, DateTime endDate, swap_position position, decimal posiPrincipal, swap_flow_event flowEvent, int annualDays, bool needPrice, decimal floateRate, decimal closePercent, decimal orginPv, bool calcFirst, bool calcLast, ref decimal InterestAmount, ref decimal TdInterestAmount, decimal consumedInterest = 0m)
{
var startDate = position.PosiStartDate;
decimal interestProfitSum = preEodPosition.InterestProfitSum;
var TdInterestPrincipal = preEodPosition.TdInterestPrincipal;
decimal interest = interestProfitSum * closePercent;
decimal tdinterest = interestProfitSum * closePercent;
int interestPeriod = position.interest_rest_days ?? 1;
// 单利:可用上一日计息基数
decimal dynomicPrincipal = preEodPosition.TdInterestPrincipal + posiPrincipal - orginPv;
decimal tdDynomicPrincipal = dynomicPrincipal;
var calcDays = (endDate - startDate).Days;
double floatRate = Convert.ToDouble(floateRate);
for (int i = 0; i <= calcDays; i++)
{
var accrueDate = startDate.AddDays(i);
if (!calcFirst && accrueDate == startDate) continue; // 首日不算头
if (!calcLast && accrueDate == endDate) continue; // 到期日不算尾
if (accrueDate > preEodPosition.ValueDate)
{
if (i % interestPeriod == 0)
{
// 获取新的浮动利率
if (!string.IsNullOrEmpty(position.FloatRateUnderlyingCode))
{
var fr007RateDate = QdpCalendarHelper.GetNonHolidayDefore(accrueDate.AddDays(position.interest_rule ?? 0));
if (TryGetFloatRate(fr007RateDate, position.FloatRateUnderlyingCode, out double floatRate1))
{
if (floatRate1 != 0) floatRate = floatRate1;
}
else
{
throw new Exception($"获取不到{position.FloatRateUnderlyingCode}在{fr007RateDate:yyyy年MM月dd日}的价格");
}
}
flowEvent.InterestPrincipal = tdDynomicPrincipal * closePercent;
TdInterestPrincipal = tdDynomicPrincipal;
}
else
{
flowEvent.InterestPrincipal = tdDynomicPrincipal * closePercent;
tdDynomicPrincipal = flowEvent.InterestPrincipal;
TdInterestPrincipal = tdDynomicPrincipal;
}
flowEvent.FloatRate = Convert.ToDecimal(floatRate);
var interest1 = flowEvent.InterestPrincipal * (flowEvent.InterestRate + Convert.ToDecimal(floatRate));
var tdinterest1 = TdInterestPrincipal * (flowEvent.InterestRate + Convert.ToDecimal(floatRate));
if (position.IsAnnualized)
{
interest1 /= annualDays;
tdinterest1 /= annualDays;
}
interest += interest1;
tdinterest += tdinterest1;
}
}
InterestAmount = Math.Round(interest, ConsGlobal.PriceRound, MidpointRounding.AwayFromZero);
TdInterestAmount = Math.Round(tdinterest, ConsGlobal.PriceRound, MidpointRounding.AwayFromZero);
}
///
/// 计算复利 收盘
///
/// 上一互换日
/// 结算日期
/// 开仓日
/// 浮动标的
/// 计息基数
/// 固定利率
/// 是否年化
/// 年化天数
///
public void CalcDailyCompoundInterestByEod(eod_swap_position preEodPosition, DateTime endDate, DateTime tradeDate, swap_position position, decimal principal, decimal posiPrincipal, swap_flow_event flowEvent, int annualDays, bool needPrice, decimal floateRate, decimal closePercent, decimal orginPv, ref decimal InterestAmount, ref decimal TdInterestAmount)
{
decimal interestProfitSum = preEodPosition.InterestProfitSum;
decimal interest = interestProfitSum * closePercent;
decimal tdinterest = interestProfitSum * closePercent;
int interestPeriod = position.interest_rest_days ?? 1;
decimal tdDynomicPrincipal = posiPrincipal;
double floatRate = Convert.ToDouble(floateRate);
var days = (endDate - tradeDate).Days;
if (days % interestPeriod == 0)
{
tdDynomicPrincipal = tdDynomicPrincipal + interestProfitSum;
if (!string.IsNullOrEmpty(position.FloatRateUnderlyingCode))
{
// 获取合适的 rateDate
var fr007RateDate = QdpCalendarHelper.GetNonHolidayDefore(endDate.AddDays(position.interest_rule ?? 0)); // 获取前一工作日;
if (TryGetFloatRate(fr007RateDate, position.FloatRateUnderlyingCode, out double floatRate1))
{
if (floatRate1 != 0)
{
floatRate = floatRate1;
}
}
else
{
throw new Exception($"获取不到{position.FloatRateUnderlyingCode}在{fr007RateDate:yyyy年MM月dd日}的价格");
}
}
flowEvent.InterestPrincipal = tdDynomicPrincipal * closePercent;
var interest1 = flowEvent.InterestPrincipal * (flowEvent.InterestRate + Convert.ToDecimal(floatRate));
var tdinterest1 = tdDynomicPrincipal * (flowEvent.InterestRate + Convert.ToDecimal(floatRate));
if (position.IsAnnualized)
{
interest1 /= annualDays;
tdinterest1 /= annualDays;
}
interest += interest1;
tdinterest = tdinterest1;
}
else
{
flowEvent.InterestPrincipal = (preEodPosition.TdInterestPrincipal + tdDynomicPrincipal - orginPv) * closePercent;
var interest1 = flowEvent.InterestPrincipal * (flowEvent.InterestRate + Convert.ToDecimal(floatRate));
var tdinterest1 = (preEodPosition.TdInterestPrincipal + tdDynomicPrincipal - orginPv) * (flowEvent.InterestRate + Convert.ToDecimal(floatRate));
if (position.IsAnnualized)
{
interest1 /= annualDays;
tdinterest1 /= annualDays;
}
interest += interest1;
tdinterest = tdinterest1;
}
flowEvent.FloatRate = Convert.ToDecimal(floatRate);
InterestAmount = Math.Round(interest, ConsGlobal.PriceRound, MidpointRounding.AwayFromZero);
TdInterestAmount = Math.Round(tdinterest, ConsGlobal.PriceRound, MidpointRounding.AwayFromZero);
}
///
/// 计算单利 收盘(按重置天数分段,每段使用对应浮动利率)
///
public void CalcDailySimpleInterestByEod(eod_swap_position preEodPosition, DateTime endDate, DateTime tradeDate, swap_position position, decimal principal, decimal posiPrincipal, swap_flow_event flowEvent, int annualDays, bool needPrice, decimal floateRate, decimal closePercent, decimal orginPv, ref decimal InterestAmount, ref decimal TdInterestAmount)
{
decimal interestProfitSum = preEodPosition.InterestProfitSum;
int interestPeriod = position.interest_rest_days ?? 1;
double floatRate = Convert.ToDouble(floateRate);
var calcDays = (endDate - tradeDate).Days;
// 修复:首次操作时(preEodPosition.id == 0),TdInterestPrincipal 需要正确初始化
if (preEodPosition.id == 0)
{
preEodPosition.TdInterestPrincipal = posiPrincipal;
}
// 检查是否到达重置周期
if (calcDays % interestPeriod == 0)
{
// 获取新的浮动利率
if (!string.IsNullOrEmpty(position.FloatRateUnderlyingCode))
{
var fr007RateDate = QdpCalendarHelper.GetNonHolidayDefore(endDate.AddDays(position.interest_rule ?? 0));
if (TryGetFloatRate(fr007RateDate, position.FloatRateUnderlyingCode, out double newFloatRate))
{
if (newFloatRate != 0)
{
floatRate = newFloatRate;
}
}
else
{
throw new Exception($"获取不到{position.FloatRateUnderlyingCode}在{fr007RateDate:yyyy年MM月dd日}的价格");
}
}
}
flowEvent.FloatRate = Convert.ToDecimal(floatRate);
var baseTdInterestPrincipal = preEodPosition.TdInterestPrincipal + posiPrincipal - orginPv;
var baseInterestPrincipal = baseTdInterestPrincipal * closePercent;
// 修复:正确计算本次利息(基于实际持仓本金)
decimal interest = baseInterestPrincipal * (flowEvent.InterestRate + Convert.ToDecimal(floatRate));
decimal tdinterest = baseTdInterestPrincipal * (flowEvent.InterestRate + Convert.ToDecimal(floatRate));
if (position.IsAnnualized)
{
interest /= annualDays;
tdinterest /= annualDays;
}
InterestAmount = Math.Round(interestProfitSum+interest, ConsGlobal.PriceRound, MidpointRounding.AwayFromZero);
TdInterestAmount = Math.Round(tdinterest, ConsGlobal.PriceRound, MidpointRounding.AwayFromZero);
}
///
/// 单标的平仓
///
///
///
public void SwapUnwind(UnwindData unwindData)
{
var td = DbContext.trade.Find(unwindData.SwapTradeId);
if (td == null)
{
throw new ServiceException("未找到交易信息");
}
//CheckLastEod(unwindData.ValueDate, td.StartDate.Value, unwindData.SwapTradeId); //去掉平仓收盘限制
var trans = DbContext.Database.BeginTransaction();
bool cofirm = false;
try
{
int clientCashId = AddClientCashInCashOut(td, Convert.ToDouble(-unwindData.SwapRealizedPnL), ClientCashInCashOut.系统操作_平仓费, unwindData.ValueDate);
if (unwindData.SwapMarginAmount != 0)
{
AddClientCashInCashOut(td, Convert.ToDouble(unwindData.SwapMarginAmount), ClientCashInCashOut.系统操作_应付预付金, unwindData.ValueDate);
}
//if (unwindData.SwapMarginRebatePnl != 0)
//{
// AddClientCashInCashOut(td, Convert.ToDouble(-unwindData.SwapMarginRebatePnl), ClientCashInCashOut.系统操作_预付金返息, unwindData.ValueDate);
//}
DealFloatPosition(unwindData);
var flowList = new List(unwindData.FlowEvents);
var eventId = SaveSwapDeal(unwindData, (int)SwapEventTypeEnum.平仓, clientCashId, "系统操作_平仓");
if (unwindData.CloseMethod == (int)CloseMethodEnum.全部平仓 || unwindData.ClosePercent == 1)
{
td.TradeStatus = "已平仓";
td.trade_extend = DbContext.trade_extend.FirstOrDefault(x => x.TradeId == td.id);
if (td.trade_extend != null && !td.trade_extend.ExtendObj.NeedOpenFee)
{
new ClientCashInCashOutService(this).SaveSwapTradeClientCash(td, td.TradePrice ?? 0, unwindData.ValueDate, 0);
}
}
else
{
td.HasPartialUnWind = 1;
}
td.UnWindDate = unwindData.UnwindDate;
td.StockEqvNotional -= Convert.ToDouble(unwindData.CloseNotionalValue);
td.TradeAmount -= Convert.ToDouble(unwindData.CloseQty);
DbContext.SaveChanges();
trans.Commit();
cofirm = true;
}
catch (Exception ex)
{
trans.Rollback();
throw new Exception(ex.Message, ex);
}
finally
{
trans.Dispose();
}
if (cofirm)
{
Task.Run(() =>
{
try
{
RealtimePnlCalc.RealtimeSwapPosition(new OptUserInfo(0, "互换实时持仓服务", OptUserFrom.Service));
}
catch (Exception ex)
{
LogFactory.GetLogger().Error("互换实时持仓服务计算失败", ex);
}
});
}
}
///
/// 自动全平仓
///
///
///
///
public void AuotoSwapUnwind(int tradeid, decimal unwindPrice, decimal unwindPriceFee, decimal unwindNetFee, decimal unwindNet, DateTime valueDate, decimal unwindQty, decimal closeFee)
{
unwindPriceFee = decimal.Parse(unwindPriceFee.ToString("F10"));
var td = DbContext.trade.Find(tradeid);
var positions = DbContext.swap_position.Where(x => x.SwapTradeId == td.id && !x.Invalid);
List eventTypes = new List() { (int)SwapEventTypeEnum.平仓, (int)SwapEventTypeEnum.互换 };
var dealDate = valueDate;
var tradeExtend = DbContext.trade_extend.FirstOrDefault(x => x.TradeId == td.id);
td.trade_extend = tradeExtend;
var position = positions.Where(x => !string.IsNullOrEmpty(x.UnderlyingCode) && !x.IsInitial).FirstOrDefault();
var preDealDate = GetPreDealDate(td.id, dealDate, eventTypes);
swap_flow_event floatEvent = new swap_flow_event();
UnwindData unwindData = new UnwindData();
unwindData.CloseType = 2;
unwindData.StartDate = td.TradeDate.Value;
if (preDealDate.HasValue)
{
unwindData.StartDate = preDealDate.Value;
}
unwindData.ValueDate = dealDate;
unwindData.UnwindDate = QdpCalendarHelper.GetNonHoliday(dealDate.AddDays(1));
floatEvent.EventDate = dealDate;
floatEvent.UnwindDate = unwindData.UnwindDate;
floatEvent.PayDate = QdpCalendarHelper.GetNonHoliday(unwindData.UnwindDate.Value.AddDays(td.trade_extend.ExtendObj.SettlementRules));
unwindData.PayDate = floatEvent.PayDate;
floatEvent.SwapTradeId = td.id;
floatEvent.SwapTradeNo = td.TradeNumber;
unwindData.SwapTradeId = td.id;
unwindData.StructureType = td.StructureType;
unwindData.NotionalValue = Convert.ToDecimal(td.OriginalStockEqvNotional ?? 0);
unwindData.NotionalQty = positions.Where(x => x.IsInitial).Sum(s => s.PosiQuantity);
unwindData.PosiNotionalValue = position != null ? position.PosiNotionalValue : Convert.ToDecimal(td.StockEqvNotional);
unwindData.PositionQty = Convert.ToDecimal(td.TradeAmount);
var unwindPercent = unwindData.PositionQty == 0 ? 0 : unwindQty / unwindData.PositionQty;
unwindData.AnnualDays = tradeExtend == null ? 365 : tradeExtend.ExtendObj.AnnualDays;
unwindData.CloseMethod = unwindQty == unwindData.PositionQty ? (int)CloseMethodEnum.全部平仓 : (int)CloseMethodEnum.部分平仓;
unwindData.ClosePercent = unwindData.PositionQty == 0 ? 0 : unwindPercent;
unwindData.CloseNotionalValue = position == null ? 0 : unwindQty * position.PosiGrossPrice * position.ContractSize;
unwindData.CloseNotionalValue = unwindPercent >= 1 ? unwindData.PosiNotionalValue : Math.Round(unwindData.CloseNotionalValue, ConsGlobal.MoneyRound, MidpointRounding.AwayFromZero);
unwindData.CloseQty = unwindQty;
if (position != null)
{
decimal floatRatio = position.PosiDirection == 1 ? 1m : -1m;
decimal longRatio = position.PositionType == 1 ? 1m : -1m;
floatEvent.PositionId = position.PositionId;
floatEvent.EventType = (int)SwapFlowEventTypeEnum.平仓;
floatEvent.EventReason = "交易";
floatEvent.DividendIn = 0;
floatEvent.UnderlyingCode = position.UnderlyingCode;
floatEvent.UnderlyingInstrumentType = position.UnderlyingInstrumentType;
floatEvent.CloseFee = 0;
floatEvent.BeforeCloseFee = position.PosiTradingFee + position.PosiTradingFeePending;
floatEvent.PayDirection = position.PosiDirection;
floatEvent.PosiGrossPrice = position.PosiGrossPrice;
floatEvent.PosiNetPrice = position.PosiNetPrice;
floatEvent.PositionType = position.PositionType;
floatEvent.Quantity = unwindData.CloseQty;
floatEvent.PositionQty = unwindData.PositionQty - unwindData.CloseQty;
floatEvent.ContractSize = position.ContractSize;
floatEvent.DataState = (int)SwapFlowDateStateEnum.完成;
floatEvent.InterestMode = position.InterestMode;
floatEvent.TradingAmountAvg = unwindPrice;
floatEvent.TradingAmountFeeAvg = unwindPriceFee;
floatEvent.TradingAmountNetFeeAvg = unwindNetFee;
floatEvent.TradingAmountNetAvg = unwindNet;
floatEvent.TradingFeePending = position.PosiTradingFeePending * unwindData.ClosePercent;
floatEvent.TradingFeePending = Math.Round(floatEvent.TradingFeePending, ConsGlobal.MoneyRound, MidpointRounding.AwayFromZero);
floatEvent.TradingFee = closeFee;
floatEvent.MarkClosePnl = (unwindPrice - position.PosiGrossPrice) * unwindQty * floatRatio * longRatio;
//MarkClosePnl 纯盯市不要计算交易费用和分红
floatEvent.MarkClosePnl = Math.Round(floatEvent.MarkClosePnl, ConsGlobal.MoneyRound, MidpointRounding.AwayFromZero);
floatEvent.TradingAmount = unwindPrice * floatEvent.Quantity * floatEvent.ContractSize;
floatEvent.TradingAmount = Math.Round(floatEvent.TradingAmount, ConsGlobal.MoneyRound, MidpointRounding.AwayFromZero);
floatEvent.OptLog = "流水自动";
floatEvent.ClientId = td.ClientId;
floatEvent.SetOpt(UserInfo);
EnrichDividendIn(floatEvent, unwindQty, td);
}
unwindData.FlowEvents.Add(floatEvent);
var interestPositions = GetUnwindInterests(unwindData.ValueDate, unwindData.UnwindDate.Value, td.id, unwindPercent, (int)SwapEventTypeEnum.平仓);
interestPositions.ForEach(item =>
{
item.OptLog = "流水自动";
});
foreach (var item in interestPositions)
{
item.TdInterestAmount = Math.Round(item.TdInterestAmount, ConsGlobal.MoneyRound, MidpointRounding.AwayFromZero);
item.InterestAmount = Math.Round(item.InterestAmount, ConsGlobal.MoneyRound, MidpointRounding.AwayFromZero);
item.InterestClosePnL = Math.Round(item.InterestClosePnL, ConsGlobal.MoneyRound, MidpointRounding.AwayFromZero);
}
unwindData.FlowEvents.AddRange(interestPositions);
CalcCloseAmount(unwindData);
DealUnwind(unwindData, td);
}
private void EnrichDividendIn(swap_flow_event flowEvent, decimal unwindQty, trade td)
{
if (flowEvent.UnwindDate == null)
{
throw new ArgumentNullException("平仓日期缺失");
}
var date = flowEvent.EventDate;
BondPaymentService servie = new BondPaymentService(UserInfo);
var payments = servie.GetBondPayments(flowEvent.UnderlyingCode, td.StartDate.Value, date);
int shortRatio = flowEvent.PositionType == (int)PositionTypeFlag.Long ? 1 : -1;
int directionRatio = flowEvent.PayDirection == (int)SwapDirectionEnum.收取 ? 1 : -1;
// + 付息日>上日日终且小于等于平仓日期的分红数据
var dividendIn = servie.CalcPayment(payments, unwindQty, shortRatio, directionRatio);
var um = DataCacheProvider.GetUnderlyingDataSource().GetData(flowEvent.UnderlyingCode);
decimal tax = um.ValueAddedTax ?? 0;
dividendIn = dividendIn / (1 + tax) * (1 - tax);
flowEvent.DividendIn = Math.Round(dividendIn, 2, MidpointRounding.AwayFromZero);
}
public decimal GetUnderlyingTax(string code)
{
var data = DataCacheProvider.GetUnderlyingDataSource().GetData(code);
if (data == null)
{
return 0;
}
return data.ValueAddedTax ?? 0;
}
///
/// 衡泰新增平仓事件
///
///
///
///
///
///
public void AutoSwapUnwindFromConsumer(trade td, DateTime valueDate, DateTime payDate, decimal markClosePnl, decimal tradeinfFee, decimal interestAmount, decimal fee, decimal unwindQty, bool allClose)
{
List eventTypes = new List() { (int)SwapEventTypeEnum.平仓, (int)SwapEventTypeEnum.互换 };
var dealDate = valueDate;
var tradeExtend = DbContext.trade_extend.FirstOrDefault(x => x.TradeId == td.id);
td.trade_extend = tradeExtend;
var position = DbContext.swap_position.Where(x => !string.IsNullOrEmpty(x.UnderlyingCode) && x.IsInitial && !x.Invalid).FirstOrDefault();
var preDealDate = GetPreDealDate(td.id, dealDate, eventTypes);
swap_flow_event floatEvent = new swap_flow_event();
UnwindData unwindData = new UnwindData();
unwindData.CloseType = 2;
unwindData.StartDate = td.TradeDate.Value;
if (preDealDate.HasValue)
{
unwindData.StartDate = preDealDate.Value;
}
unwindData.ValueDate = dealDate;
floatEvent.EventDate = dealDate;
unwindData.UnwindDate = QdpCalendarHelper.GetNonHoliday(dealDate.AddDays(1));
floatEvent.UnwindDate = unwindData.UnwindDate;
floatEvent.PayDate = payDate;
unwindData.PayDate = floatEvent.PayDate;
floatEvent.SwapTradeId = td.id;
floatEvent.SwapTradeNo = td.TradeNumber;
unwindData.SwapTradeId = td.id;
unwindData.StructureType = td.StructureType;
unwindData.NotionalValue = Convert.ToDecimal(td.OriginalStockEqvNotional ?? 0);
unwindData.NotionalQty = position.PosiQuantity;
unwindData.PosiNotionalValue = Convert.ToDecimal(td.StockEqvNotional);
unwindData.PositionQty = Convert.ToDecimal(td.TradeAmount);
unwindData.AnnualDays = tradeExtend == null ? 365 : tradeExtend.ExtendObj.AnnualDays;
unwindData.CloseMethod = allClose ? (int)CloseMethodEnum.全部平仓 : (int)CloseMethodEnum.部分平仓;
unwindData.ClosePercent = allClose ? 1 : unwindQty / unwindData.NotionalQty;
unwindData.CloseNotionalValue = allClose ? unwindData.PosiNotionalValue : unwindQty;
unwindData.CloseQty = allClose ? unwindData.PositionQty : unwindQty;
if (position != null)
{
decimal floatRatio = position.PosiDirection == 1 ? 1m : -1m;
floatEvent.PositionId = position.id;
floatEvent.EventType = (int)SwapEventTypeEnum.平仓;
floatEvent.EventReason = "接口合约终止交易";
floatEvent.DividendIn = 0;
floatEvent.UnderlyingCode = position.UnderlyingCode;
floatEvent.UnderlyingInstrumentType = position.UnderlyingInstrumentType;
floatEvent.CloseFee = 0;
floatEvent.BeforeCloseFee = position.PosiTradingFee + position.PosiTradingFeePending;
floatEvent.PayDirection = position.PosiDirection;
floatEvent.PosiGrossPrice = position.PosiGrossPrice;
floatEvent.PosiNetPrice = position.PosiNetPrice;
floatEvent.TradingAmountNetAvg = position.PosiNetNoFeePrice;
floatEvent.TradingFeePending = position.PosiTradingFeePending * unwindData.ClosePercent;
floatEvent.TradingFee = tradeinfFee - floatEvent.TradingFeePending;
floatEvent.MarkClosePnl = markClosePnl;
floatEvent.TradingAmount = floatEvent.Quantity * floatEvent.ContractSize;
floatEvent.PositionType = position.PositionType;
floatEvent.Quantity = position.PosiQuantity;
floatEvent.PositionQty = 0;
floatEvent.ContractSize = position.ContractSize;
floatEvent.DataState = (int)SwapFlowDateStateEnum.完成;
floatEvent.InterestMode = position.InterestMode;
floatEvent.TradingAmount = unwindData.CloseQty;
floatEvent.ClientId = td.ClientId;
floatEvent.OptLog = "衡泰同步";
floatEvent.SetOpt(UserInfo);
}
unwindData.FlowEvents.Add(floatEvent);
var interestPositions = GetUnwindInterestsByHT(unwindData, td, interestAmount, fee);
unwindData.FlowEvents.AddRange(interestPositions);
CalcCloseAmount(unwindData);
DealUnwind(unwindData, td, "合约终止接口回执");
}
private List GetUnwindInterestsByHT(UnwindData unwindData, trade td, decimal interestAmount, decimal fee)
{
List interests = new List();
var allpositions = DbContext.swap_position.Where(x => x.SwapTradeId == unwindData.SwapTradeId && !x.Invalid && x.IsInitial && x.PosiDirection > 0).ToList();
var position = allpositions.Where(x => ConsTrade.InterestModels.Contains(x.InterestMode)).FirstOrDefault();
if (position == null)
{
return interests;
}
var grossPrice = allpositions.Where(x => x.PosiDirection > 0).FirstOrDefault()?.PosiGrossPrice ?? 0;
var _closePosiNotionalValue = unwindData.CloseNotionalValue;
var _posiNotionalValue = unwindData.PosiNotionalValue;
var newClosePercent = unwindData.ClosePercent;
foreach (var item in allpositions)
{
var positionClone = item.Clone();
var swapIntervalToday = position.SwapIntervalList.OrderByDescending(o => o.Date).FirstOrDefault();
if (item.InterestMode == (int)InterestModeEnum.固定值)
{
_closePosiNotionalValue = item.InterestPrincipalFix;
_posiNotionalValue = item.InterestPrincipalFix;
newClosePercent = 1m;
}
else if (item.InterestMode == (int)InterestModeEnum.标的期初全价)
{
_closePosiNotionalValue = _posiNotionalValue * grossPrice * newClosePercent;
_posiNotionalValue = _posiNotionalValue * grossPrice;
}
else if (item.InterestMode == (int)InterestModeEnum.追加预付金 || item.InterestMode == (int)InterestModeEnum.初始预付金)
{
_closePosiNotionalValue = 0;
positionClone.InterestDirection = position.InterestDirection == (int)SwapDirectionEnum.收取 ? (int)SwapDirectionEnum.支付 : (int)SwapDirectionEnum.收取;
}
decimal rate = item.InterestRateDefault;
if (swapIntervalToday != null)//当日无适用观察日
{
rate = swapIntervalToday.Rate;
}
swap_flow_event interest = new swap_flow_event();
interest.SwapTradeId = td.id;
interest.SwapTradeNo = td.TradeNumber;
interest.EventType = (int)SwapEventTypeEnum.平仓;
interest.EventReason = "衡泰同步平仓";
interest.EventDate = unwindData.ValueDate;
interest.PositionId = item.id;
interest.InterestDirection = positionClone.InterestDirection;
interest.InterestRate = rate;
interest.InterestPrincipal = _closePosiNotionalValue;
interest.InterestSwapInterval = item.InterestSwapInterval;
interest.InterestMode = item.InterestMode;
interest.FloatRate = item.FloatRate;
interest.DataState = (int)SwapFlowDateStateEnum.完成;
interest.ClientId = td.ClientId;
interest.UnwindDate = unwindData.ValueDate;
interest.PayDate = unwindData.PayDate;
if (position != null && item.id == position.id)
{
interest.InterestAmount = interestAmount;
interest.TdInterestAmount = interestAmount;
interest.InterestClosePnL = interestAmount;
interest.InterestFee = fee;
}
UpdateDbOption(interest);
interests.Add(interest);
}
return interests;
}
private void DealUnwind(UnwindData unwindData, trade td, string actionMsg = "系统操作_自动平仓")
{
int clientCashId = AddClientCashInCashOut(td, Convert.ToDouble(-unwindData.SwapRealizedPnL), ClientCashInCashOut.系统操作_平仓费, unwindData.ValueDate);
if (unwindData.SwapMarginAmount != 0)
{
AddClientCashInCashOut(td, Convert.ToDouble(unwindData.SwapMarginAmount), ClientCashInCashOut.系统操作_应付预付金, unwindData.ValueDate);
}
//if (unwindData.SwapMarginRebatePnl != 0)
//{
// AddClientCashInCashOut(td, Convert.ToDouble(-unwindData.SwapMarginRebatePnl), ClientCashInCashOut.系统操作_预付金返息, unwindData.ValueDate);
//}
var flowList = new List(unwindData.FlowEvents);
var eventId = SaveSwapDeal(unwindData, (int)SwapEventTypeEnum.平仓, clientCashId, actionMsg);
if (unwindData.CloseMethod == (int)CloseMethodEnum.全部平仓)
{
td.TradeStatus = "已平仓";
td.trade_extend = DbContext.trade_extend.FirstOrDefault(x => x.TradeId == td.id);
if (td.trade_extend != null && !td.trade_extend.ExtendObj.NeedOpenFee)
{
new ClientCashInCashOutService(this).SaveSwapTradeClientCash(td, td.TradePrice ?? 0, unwindData.ValueDate, 0);
}
}
else
{
td.HasPartialUnWind = 1;
}
td.UnWindDate = unwindData.UnwindDate;
td.StockEqvNotional -= Convert.ToDouble(unwindData.CloseNotionalValue);
td.TradeAmount -= Convert.ToDouble(unwindData.CloseQty);
td.Notional = td.TradeAmount;
td.OptDate = DateTime.Now;
td.OptId = UserId;
td.OptName = UserName;
DbContext.SaveChanges();
}
///
/// 计算平仓总额
///
///
private void CalcCloseAmount(UnwindData unwindData)
{
var floatPosition = unwindData.FlowEvents.FirstOrDefault(x => !string.IsNullOrEmpty(x.UnderlyingCode));
var interestList = unwindData.FlowEvents.Where(x => string.IsNullOrEmpty(x.UnderlyingCode));
decimal floatRatio = floatPosition.PayDirection == 1 ? 1m : -1m;
var pnl = floatPosition.FloatPnlSum;
unwindData.SwapCloseAmount = pnl;
unwindData.SwapRealizedPnL = pnl;
unwindData.SwapMarginRebatePnl = 0;
unwindData.SwapMarginAmount = 0;
if (interestList != null)
{
interestList.ForEach(x =>
{
if (x.InterestMode == (int)InterestModeEnum.追加预付金 || x.InterestMode == (int)InterestModeEnum.初始预付金)
{
decimal interestRatio = x.InterestDirection == 1 ? -1m : 1m;
unwindData.SwapMarginRebatePnl += x.InterestClosePnL;
unwindData.SwapMarginAmount += x.InterestPrincipal * interestRatio;
}
unwindData.SwapRealizedPnL += x.InterestClosePnL;
unwindData.SwapCloseAmount += x.InterestClosePnL;
});
}
unwindData.SwapCloseAmount = Math.Round(unwindData.SwapCloseAmount, 2, MidpointRounding.AwayFromZero);
unwindData.SwapRealizedPnL = Math.Round(unwindData.SwapRealizedPnL, 2, MidpointRounding.AwayFromZero);
}
///
/// 多空组合平仓
///
///
///
public void SwapLongShortUnwind(UnwindData unwindData)
{
var td = DbContext.trade.Find(unwindData.SwapTradeId);
if (td == null)
{
throw new ServiceException("未找到交易信息");
}
unwindData.SwapRealizedPnL = unwindData.SwapCloseAmount;
var trans = DbContext.Database.BeginTransaction();
try
{
int clientCashId = AddClientCashInCashOut(td, Convert.ToDouble(unwindData.SwapCloseAmount), ClientCashInCashOut.系统操作_平仓费, unwindData.ValueDate);
if (unwindData.SwapMarginAmount != 0)
{
AddClientCashInCashOut(td, Convert.ToDouble(unwindData.SwapMarginAmount), ClientCashInCashOut.系统操作_应付预付金, unwindData.ValueDate);
}
if (unwindData.SwapMarginRebatePnl != 0)
{
AddClientCashInCashOut(td, Convert.ToDouble(-unwindData.SwapMarginRebatePnl), ClientCashInCashOut.系统操作_预付金返息, unwindData.ValueDate);
}
SaveSwapDeal(unwindData, (int)SwapEventTypeEnum.平仓, clientCashId, "系统操作_平仓");
td.UnWindDate = unwindData.UnwindDate;
td.StockEqvNotional = 0;
td.TradeStatus = "已平仓";
DbContext.SaveChanges();
trans.Commit();
}
catch (Exception ex)
{
trans.Rollback();
throw;
}
finally
{
trans.Dispose();
}
}
///
/// 多空组合互换
///
///
///
public void SwapLongShort(UnwindData unwindData)
{
var td = DbContext.trade.Find(unwindData.SwapTradeId);
if (td == null)
{
throw new ServiceException("未找到交易信息");
}
unwindData.SwapRealizedPnL = unwindData.SwapCloseAmount;
var trans = DbContext.Database.BeginTransaction();
try
{
int clientCashId = AddClientCashInCashOut(td, Convert.ToDouble(unwindData.SwapCloseAmount), ClientCashInCashOut.系统操作_互换, unwindData.ValueDate);
SaveSwapDeal(unwindData, (int)SwapEventTypeEnum.互换, clientCashId, "系统操作_互换");
td.UnWindDate = unwindData.UnwindDate;
if (td.ExerciseDate <= unwindData.ValueDate)
{
td.Notional = 0;
td.StockEqvNotional = 0;
td.TradeStatus = "已到期";
}
DbContext.SaveChanges();
trans.Commit();
}
catch (Exception ex)
{
trans.Rollback();
throw;
}
finally
{
trans.Dispose();
}
}
///
/// 互换
///
///
///
public void SwapIncome(UnwindData unwindData)
{
var td = DbContext.trade.Find(unwindData.SwapTradeId);
if (td == null)
{
throw new ServiceException("未找到交易信息");
}
//CheckLastEod(unwindData.ValueDate, td.StartDate.Value, unwindData.SwapTradeId); //去掉平仓收盘限制
var trans = DbContext.Database.BeginTransaction();
bool confirm = false;
try
{
int clientCashId = AddClientCashInCashOut(td, Convert.ToDouble(-unwindData.SwapRealizedPnL), ClientCashInCashOut.系统操作_互换, unwindData.ValueDate);
if (unwindData.SwapMarginRebatePnl != 0)
{
AddClientCashInCashOut(td, Convert.ToDouble(-unwindData.SwapMarginRebatePnl), ClientCashInCashOut.系统操作_预付金返息, unwindData.ValueDate);
}
foreach (var item in unwindData.FlowEvents)
{
item.OptLog = "手工操作";
}
SaveSwapDeal(unwindData, (int)SwapEventTypeEnum.互换, clientCashId, "系统操作_互换");
if (td.ExerciseDate <= unwindData.ValueDate)
{
td.Notional = 0;
td.StockEqvNotional = 0;
td.TradeStatus = "已到期";
}
td.UnWindDate = unwindData.UnwindDate;
DbContext.SaveChanges();
trans.Commit();
confirm = true;
}
catch (Exception ex)
{
trans.Rollback();
throw;
}
finally
{
trans.Dispose();
}
}
///
/// 互换/平仓审核通过
///
///
///
///
public void ApproveSwapTrade(trade td, int eventType)
{
Expression> eventExpression = x => x.SwapTradeId == td.id && !x.Invalid && x.EventType == eventType;
var swapEvent = DbContext.swap_event.Where(eventExpression).OrderByDescending(o => o.id).FirstOrDefault();
if (swapEvent == null)
{
throw new Exception("该笔交易状态为平仓待复核,未找到相关记录,请检查该笔交易是否有效");
}
swapEvent.unwindData = JsonConvert.DeserializeObject(swapEvent.EventData);
var flowList = DbContext.swap_flow_event.Where(x => x.EventId == swapEvent.id).ToList();
string action = eventType == (int)SwapEventTypeEnum.互换 ? ClientCashInCashOut.系统操作_互换 : ClientCashInCashOut.系统操作_平仓费;
int clientCashId = AddClientCashInCashOut(td, Convert.ToDouble(-swapEvent.unwindData.SwapRealizedPnL), action, swapEvent.unwindData.ValueDate);
if (swapEvent.unwindData.SwapMarginAmount != 0)
{
AddClientCashInCashOut(td, Convert.ToDouble(swapEvent.unwindData.SwapMarginAmount), ClientCashInCashOut.系统操作_应付预付金, swapEvent.unwindData.ValueDate);
}
swapEvent.ClientCashId = clientCashId;
if (swapEvent.unwindData.CloseMethod == (int)CloseMethodEnum.全部平仓)
{
td.TradeStatus = "已平仓";
td.trade_extend = DbContext.trade_extend.FirstOrDefault(x => x.TradeId == td.id);
if (td.trade_extend != null && !td.trade_extend.ExtendObj.NeedOpenFee)
{
new ClientCashInCashOutService(this).SaveSwapTradeClientCash(td, td.TradePrice ?? 0, swapEvent.unwindData.ValueDate, 0);
}
}
else
{
td.TradeStatus = ConsTrade.确认成交;
td.HasPartialUnWind = 1;
}
td.UnWindDate = swapEvent.unwindData.UnwindDate;
if (eventType != (int)SwapEventTypeEnum.互换)
{
td.StockEqvNotional -= Convert.ToDouble(swapEvent.unwindData.CloseNotionalValue);
td.TradeAmount -= Convert.ToDouble(swapEvent.unwindData.CloseQty);
}
td.Notional = td.TradeAmount;
UpdateInitalPosition(flowList, swapEvent.unwindData, eventType);
DbContext.SaveChanges();
}
///
/// 互换/平仓提交审核
///
///
///
///
public void ApplySwapTrade(UnwindData unwindData, int eventType)
{
var td = DbContext.trade.Find(unwindData.SwapTradeId);
if (td == null)
{
throw new ServiceException("未找到交易信息");
}
unwindData.SwapRealizedPnL = unwindData.SwapCloseAmount;
var trans = DbContext.Database.BeginTransaction();
string action = eventType == (int)SwapEventTypeEnum.互换 ? ClientCashInCashOut.系统操作_互换 : ClientCashInCashOut.系统操作_平仓费;
try
{
new TradeUnwindService(this).CloseReCheck_SetTrade(unwindData.SwapTradeId, eventType == (int)SwapEventTypeEnum.互换, true);
SaveSwapDeal(unwindData, eventType, 0, action, true);
DbContext.SaveChanges();
trans.Commit();
}
catch (Exception ex)
{
trans.Rollback();
throw;
}
finally
{
trans.Dispose();
}
}
///
/// 保存平仓/互换事件
///
///
///
private long SaveSwapDeal(UnwindData unwindData, int eventType, int clientCashId, string eventResason = "", bool approve = false)
{
var td = DbContext.trade.Find(unwindData.SwapTradeId);
if (td == null)
{
throw new ServiceException("未找到交易信息");
}
var flowList = new List(unwindData.FlowEvents);
unwindData.FlowEvents.Clear();
string data = JsonConvert.SerializeObject(unwindData);
var swapEvent = new SwapEventService(this).AddSwapEventDate(unwindData.ValueDate, unwindData.SwapTradeId, eventType, data, clientCashId, true, eventResason);//将平仓、互换总额存入事件
foreach (var item in flowList)
{
if (item.PositionType > 0 && eventType == (int)SwapEventTypeEnum.平仓)
{
item.Quantity = unwindData.CloseQty;
item.PositionQty = unwindData.ClosePercent == 1 ? 0 : unwindData.PositionQty - unwindData.CloseQty;
}
item.PayDate = unwindData.PayDate;
item.UnwindDate = unwindData.UnwindDate;
item.EventDate = unwindData.ValueDate;
FillSwapFlowEvent(item, unwindData, td, eventType, swapEvent.id);
DbContext.swap_flow_event.Add(item);
}
if (!approve)
{
UpdateInitalPosition(flowList, unwindData, eventType);
}
DbContext.SaveChanges();
var savedFlowEvents = DbContext.swap_flow_event.Where(x => x.EventId == swapEvent.id).ToList();
foreach (var item in savedFlowEvents)
{
FillSwapFlowEvent(item, unwindData, td, eventType, swapEvent.id);
}
DbContext.SaveChanges();
return swapEvent.id;
}
private void FillSwapFlowEvent(swap_flow_event item, UnwindData unwindData, trade td, int eventType, long eventId)
{
item.EventId = eventId;
item.SwapTradeId = unwindData.SwapTradeId;
item.SwapTradeNo = td.TradeNumber;
item.EventType = eventType;
item.EventReason = string.IsNullOrWhiteSpace(item.EventReason) ? "交易" : item.EventReason;
UpdateDbOption(item);
}
///
/// 计算平仓数据
///
///
private void DealFloatPosition(UnwindData unwindData)
{
foreach (var item in unwindData.FlowEvents)
{
item.OptLog = "手工操作";
if (item.PositionType > 0 && item.EventType == (int)SwapEventTypeEnum.平仓)
{
decimal shortRatio = item.PositionType == (int)PositionTypeFlag.Long ? -1m : 1m;
item.TradingAmountFeeAvg = item.TradingAmountAvg + item.TradingFeePending / unwindData.CloseQty * shortRatio;
item.TradingAmountNetFeeAvg = item.TradingAmountNetAvg + item.TradingFeePending / unwindData.CloseQty * shortRatio;
item.TradingAmount = item.TradingAmountAvg * unwindData.CloseQty;
}
}
}
///
/// 单标的互换更新实时持仓信息
///
///
private void UpdateInitalPosition(List flowList, UnwindData unwindData, int eventType)
{
var positions = DbContext.swap_position.Where(x => !x.IsInitial && x.SwapTradeId == unwindData.SwapTradeId && !x.Invalid);
foreach (var position in positions)
{
if (!string.IsNullOrEmpty(position.UnderlyingCode))
{
// 收益结算(互换)不改变持仓数量和名义本金,只更新费用
if (eventType == (int)SwapEventTypeEnum.互换)
{
position.PosiTradingFee -= position.PosiTradingFee * unwindData.ClosePercent;
position.PosiTradingFeePending -= position.PosiTradingFeePending * unwindData.ClosePercent;
}
else
{
// 平仓时才扣减持仓
position.PosiQuantity -= unwindData.CloseQty;
position.PosiNotionalValue -= unwindData.CloseNotionalValue;
position.PosiTradingFee -= position.PosiTradingFee * unwindData.ClosePercent;
position.PosiTradingFeePending -= position.PosiTradingFeePending * unwindData.ClosePercent;
}
}
else
{
var interest = flowList.FirstOrDefault(x => x.PositionId == position.PositionId);
if (interest != null)
{
position.InterestAmount += interest.InterestAmount;
position.InterestFeePending += interest.InterestFee;
if ((interest.InterestMode == (int)InterestModeEnum.追加预付金 || interest.InterestMode == (int)InterestModeEnum.初始预付金) && eventType == (int)SwapEventTypeEnum.平仓)
{
position.InterestPrincipalFix -= interest.InterestPrincipal;
}
}
}
}
}
}
}