1419 lines
75 KiB
C#
1419 lines
75 KiB
C#
using ClosedXML.Report.Utils;
|
|
using CsvHelper;
|
|
using Microsoft.EntityFrameworkCore.Diagnostics;
|
|
using Microsoft.Extensions.Logging;
|
|
using MoreLinq.Extensions;
|
|
using Newtonsoft.Json;
|
|
using OfficeOpenXml.Drawing;
|
|
using OfficeOpenXml.Drawing.Controls;
|
|
using Org.BouncyCastle.Asn1.Ocsp;
|
|
using Qdp.Pricing.Base.Implementations;
|
|
using Qdp.Pricing.Core.Modules;
|
|
using System.Linq.Expressions;
|
|
using YLErp.BLL;
|
|
using YLErp.BLL.Eod;
|
|
using YLErp.DBModels;
|
|
using YLErp.DBModels.Enums;
|
|
using YLErp.Model;
|
|
using YLErp.Models;
|
|
using YLErp.Modules.DataProviderModule;
|
|
using YLErp.Modules.EodModule;
|
|
using YLErp.Modules.EodModule.QueryModule;
|
|
using YLErp.Modules.TradeModule;
|
|
using YLErp.Modules.TradeModule.DealModule;
|
|
using YLErp.QdpModule;
|
|
using static Microsoft.IO.RecyclableMemoryStreamManager;
|
|
using static YLErp.ConsGlobal;
|
|
|
|
namespace YLErp.Modules.SwapModule
|
|
{
|
|
public class SwapDealService : SwapTradeBaseService
|
|
{
|
|
public SwapDealService(OptUserInfo optUser) : base(optUser)
|
|
{
|
|
|
|
}
|
|
public SwapDealService(YLBaseService baseService) : base(baseService)
|
|
{
|
|
|
|
}
|
|
/// <summary>
|
|
/// 平仓初始化
|
|
/// </summary>
|
|
/// <param name="tradeId"></param>
|
|
/// <returns></returns>
|
|
/// <exception cref="ServiceException"></exception>
|
|
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<int> eventTyps = new List<int>() { (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.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;
|
|
}
|
|
/// <summary>
|
|
/// 校验上日是否收盘
|
|
/// </summary>
|
|
/// <param name="tradeId"></param>
|
|
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);
|
|
}
|
|
/// <summary>
|
|
/// 多空组合 平仓初始化
|
|
/// </summary>
|
|
/// <param name="tradeId"></param>
|
|
/// <returns></returns>
|
|
/// <exception cref="ServiceException"></exception>
|
|
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<int> eventTyps = new List<int>() { (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 = unwindData.UnwindDate;
|
|
unwindData.PayDate = QdpCalendarHelper.GetNonHoliday(unwindData.UnwindDate.Value.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;
|
|
}
|
|
/// <summary>
|
|
/// 平仓初始化
|
|
/// </summary>
|
|
/// <param name="tradeId"></param>
|
|
/// <returns></returns>
|
|
/// <exception cref="ServiceException"></exception>
|
|
public UnwindData InitIncome(int tradeId)
|
|
{
|
|
var checkEventTypes = new List<int>() { (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<int> eventTypes = new List<int>() { (int)SwapFlowEventTypeEnum.互换, (int)SwapFlowEventTypeEnum.自动互换 };
|
|
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 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.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;
|
|
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.EventType = (int)SwapEventTypeEnum.互换;
|
|
floatEvent.EventReason = "交易";
|
|
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;
|
|
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;
|
|
}
|
|
/// <summary>
|
|
/// 获取平仓利息端信息
|
|
/// </summary>
|
|
/// <param name="valueDate">平仓日期</param>
|
|
/// <param name="tradeId">交易id</param>
|
|
/// <param name="closePercent">平仓比例</param>
|
|
/// <returns></returns>
|
|
/// <exception cref="ServiceException"></exception>
|
|
public List<swap_flow_event> GetUnwindInterests(DateTime valueDate, DateTime unwindDate, int tradeId, decimal closePercent, int eventType)
|
|
{
|
|
List<swap_flow_event> interests = new List<swap_flow_event>();
|
|
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<int> eventTypes = new List<int>() { (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 orginPv = lastEod != null ? lastEod.NotionalValue : 0;
|
|
var _preSetteDate = lastEod == null ? unwindDate.AddDays(-1) : lastEod.ValueDate;
|
|
List<eod_swap_position> 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 grossPrice = realPostitions.Where(x => x.PosiDirection > 0).FirstOrDefault()?.PosiGrossPrice;
|
|
bool tdClose = DbContext.swap_flow_event.Any(x => x.SwapTradeId == tradeId && x.UnwindDate == unwindDate && eventTypes.Contains(x.EventType) && x.DataState == (int)SwapFlowDateStateEnum.完成);
|
|
interests = GetInterests(td, tradeExtend, valueDate, unwindDate, lastEodPositions, positions, stockEqvNotional, posiLongNotionalValue, posiShortNotionalValue, posiNotionalValue, closePercent, eventType, tdClose, false, grossPrice ?? 0, orginPv, true, false, false);
|
|
return interests;
|
|
}
|
|
/// <summary>
|
|
/// 计算利息腿计息详细
|
|
/// </summary>
|
|
/// <param name="td">交易</param>
|
|
/// <param name="tradeExtend">交易扩展数据</param>
|
|
/// <param name="valueDate">操作日期</param>
|
|
/// <param name="eodPositions">上一日终持仓</param>
|
|
/// <param name="positions">期初利率端</param>
|
|
/// <param name="posiNotionalValue">持仓名义本金</param>
|
|
/// <param name="posiLongNotionalValue">多头持仓名义本金</param>
|
|
/// <param name="posiShortNotionalValue">空头持仓名义本金</param>
|
|
/// <param name="closePosiNotionalValue">平仓名义本金</param>
|
|
/// <param name="closePrecent"></param>
|
|
/// <param name="eventType"></param>
|
|
/// <param name="tdClose"></param>
|
|
/// <param name="add"></param>
|
|
/// <returns></returns>
|
|
public List<swap_flow_event> GetInterests(
|
|
trade td,
|
|
trade_extend tradeExtend,
|
|
DateTime valueDate,
|
|
DateTime unwindDate,
|
|
List<eod_swap_position> eodPositions,
|
|
List<swap_position> positions,
|
|
decimal posiNotionalValue,
|
|
decimal posiLongNotionalValue,
|
|
decimal posiShortNotionalValue,
|
|
decimal closePosiNotionalValue,
|
|
decimal closePrecent,
|
|
int eventType,
|
|
bool tdClose,
|
|
bool needPrice,
|
|
decimal grossPrice,
|
|
decimal orginPv,
|
|
bool add = false,
|
|
bool calcLast = true,
|
|
bool settment = true)
|
|
{
|
|
List<swap_flow_event> interests = new List<swap_flow_event>();
|
|
var annualDays = tradeExtend == null ? 365 : tradeExtend.ExtendObj.AnnualDays;
|
|
foreach (var position in positions)
|
|
{
|
|
var _closePosiNotionalValue = closePosiNotionalValue;
|
|
var _posiNotionalValue = posiNotionalValue;
|
|
var preEodPosition = eodPositions.FirstOrDefault(x => x.PositionId == position.id);
|
|
DateTime? preDealDate = null;
|
|
var positionClone = position.Clone();
|
|
var newClosePercent = closePrecent;
|
|
if (preEodPosition != null)
|
|
{
|
|
preDealDate = preEodPosition.ValueDate;
|
|
}
|
|
var swap = InitInterestDate(unwindDate, preDealDate, td, tdClose, calcLast, out DateTime startDate, out DateTime endDate);//不算头或不算尾情况,无利息
|
|
if (!preDealDate.HasValue)
|
|
{
|
|
preEodPosition = new eod_swap_position();
|
|
preEodPosition.PosiStartDate = position.PosiStartDate;
|
|
preEodPosition.ValueDate = position.PosiStartDate;
|
|
}
|
|
var swapIntervalToday = position.SwapIntervalList.Where(x => x.Date <= startDate).OrderByDescending(o => o.Date).FirstOrDefault();
|
|
if (position.InterestMode == (int)InterestModeEnum.固定值)
|
|
{
|
|
_closePosiNotionalValue = position.InterestPrincipalFix;
|
|
_posiNotionalValue = position.InterestPrincipalFix;
|
|
newClosePercent = 1m;
|
|
}
|
|
else if (position.InterestMode == (int)InterestModeEnum.多头存续名义本金)
|
|
{
|
|
_closePosiNotionalValue = posiLongNotionalValue * closePrecent;
|
|
_posiNotionalValue = posiLongNotionalValue;
|
|
}
|
|
else if (position.InterestMode == (int)InterestModeEnum.空头存续名义本金)
|
|
{
|
|
_closePosiNotionalValue = posiShortNotionalValue * closePrecent;
|
|
_posiNotionalValue = posiShortNotionalValue;
|
|
}
|
|
else if (position.InterestMode == (int)InterestModeEnum.标的期初全价)
|
|
{
|
|
_closePosiNotionalValue = _posiNotionalValue * closePrecent;
|
|
_posiNotionalValue = _posiNotionalValue;
|
|
}
|
|
else if (position.InterestMode == (int)InterestModeEnum.追加预付金 || position.InterestMode == (int)InterestModeEnum.初始预付金)
|
|
{
|
|
_closePosiNotionalValue = position.InterestPrincipalFix * closePrecent;
|
|
_posiNotionalValue = position.InterestPrincipalFix * closePrecent;
|
|
positionClone.InterestDirection = position.InterestDirection == (int)SwapDirectionEnum.收取 ? (int)SwapDirectionEnum.支付 : (int)SwapDirectionEnum.收取;
|
|
}
|
|
if (!string.IsNullOrEmpty(position.FloatRateUnderlyingCode))
|
|
{
|
|
// 获取重置频率,如果为空则默认为1
|
|
int interestPeriod = position.interest_rest_days ?? 1;
|
|
|
|
// 计算从 td.StartDate 到 endDate 的天数
|
|
var days = (endDate - td.StartDate.Value).Days;
|
|
// 获取合适的 rateDate
|
|
DateTime rateDate = GetRateDate(position.interest_rule, td.StartDate.Value, endDate, days, interestPeriod);
|
|
|
|
// 如果不需要重置,并且上一日已有 FloatRate,则不再查找
|
|
if (preEodPosition.id != 0 && days % interestPeriod != 0)
|
|
{
|
|
position.FloatRate = preEodPosition.FloatRate;
|
|
positionClone.FloatRate = preEodPosition.FloatRate;
|
|
}
|
|
else
|
|
{
|
|
// 如果没有 preEodPosition 数据或需要查找新 Rate,则去查询最新的浮动利率
|
|
if (EodPriceQueryService.TryGetPrice(rateDate, position.FloatRateUnderlyingCode, out double floatRate))
|
|
{
|
|
position.FloatRate = Convert.ToDecimal(floatRate);
|
|
positionClone.FloatRate = position.FloatRate;
|
|
}
|
|
else if(!swap)
|
|
{
|
|
throw new Exception($"获取不到{position.FloatRateUnderlyingCode}在{rateDate:yyyy年MM月dd日}的价格");
|
|
}
|
|
}
|
|
}
|
|
decimal rate = position.InterestRateDefault;
|
|
if (swapIntervalToday == null)//当日无适用观察日
|
|
{
|
|
var swapInterval = position.SwapIntervalList.Where(x => x.Date > startDate).OrderBy(o => o.Date).FirstOrDefault();
|
|
if (swapInterval != null)
|
|
{
|
|
rate = swapInterval.Rate;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
rate = swapIntervalToday.Rate;
|
|
}
|
|
if (preEodPosition.id == 0)
|
|
{
|
|
preEodPosition.FloatRate = positionClone.FloatRate;
|
|
preEodPosition.TdInterestPrincipal = _posiNotionalValue;
|
|
preEodPosition.PosiNotionalValue = _posiNotionalValue;
|
|
}
|
|
swap_flow_event interest = InitSwapDealInterest(td, valueDate, endDate, rate, positionClone, add, swap, _posiNotionalValue, _closePosiNotionalValue, newClosePercent, annualDays, eventType, preEodPosition, needPrice, settment, orginPv);
|
|
interests.Add(interest);
|
|
}
|
|
return interests;
|
|
}
|
|
/// <summary>
|
|
/// 根据给定条件获取 rateDate
|
|
/// </summary>
|
|
private DateTime GetRateDate(int? interest_rule, DateTime startDate, DateTime endDate, int days, int interestPeriod)
|
|
{
|
|
// 判断是否达到重置周期
|
|
if (days % interestPeriod == 0)
|
|
{
|
|
return QdpCalendarHelper.GetNonHolidayDefore(endDate.AddDays(interest_rule ?? 0));
|
|
}
|
|
|
|
// 如果不在重置周期内,使用 td.StartDate 来获取 rateDate
|
|
return QdpCalendarHelper.GetNonHolidayDefore(startDate.AddDays(interest_rule ?? 0));
|
|
}
|
|
/// <summary>
|
|
/// 初始化利息腿信息
|
|
/// </summary>
|
|
/// <param name="tradeId">交易编码</param>
|
|
/// <param name="valueDate">计息开始日期</param>
|
|
/// <param name="endDate">计息结束日期</param>
|
|
/// <param name="rate">计息年化利率</param>
|
|
/// <param name="position">利息腿</param>
|
|
/// <param name="add">是否新增</param>
|
|
/// <param name="swap">是否已互换</param>
|
|
/// <param name="preEodPosition">上一日终归档</param>
|
|
/// <param name="posiNotionalValue">当日适用名义本金</param>
|
|
/// <param name="closePosiNotionalValue">当日平仓名义本金</param>
|
|
/// <param name="annualDays">年化天数</param>
|
|
/// <returns></returns>
|
|
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,
|
|
bool settment,
|
|
decimal orginPv
|
|
)
|
|
{
|
|
DateTime lastSwapDate = preEodPosition.ValueDate;
|
|
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;
|
|
var itemDays = (endDate - lastSwapDate).Days;
|
|
itemDays = itemDays == 0 ? 1 : itemDays;
|
|
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;
|
|
if (position.InterestType == (int)InterestTypeEnum.复利)
|
|
{
|
|
var floateRate = preEodPosition.FloatRate;
|
|
if (settment)//收盘利息计算
|
|
{
|
|
CalcDailyCompoundInterestByEod(preEodPosition, endDate, td.StartDate.Value, position, closePosiNotionalValue, posiNotionalValue, interest, annualDays, needPrice, floateRate, closePrecent, orginPv, ref InterestAmount, ref TdInterestAmount);
|
|
}
|
|
else
|
|
{
|
|
CalcDailyCompoundInterest(preEodPosition, endDate, td.StartDate.Value, position, closePosiNotionalValue, posiNotionalValue, interest, annualDays, needPrice, floateRate, closePrecent, orginPv, ref InterestAmount, ref TdInterestAmount);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
InterestAmount = closePosiNotionalValue * (interest.InterestRate + position.FloatRate);
|
|
TdInterestAmount = posiNotionalValue * (interest.InterestRate + position.FloatRate);
|
|
if (settment)
|
|
{
|
|
if (position.IsAnnualized)
|
|
{
|
|
InterestAmount = InterestAmount * ((decimal)itemDays / annualDays);
|
|
TdInterestAmount = TdInterestAmount * ((decimal)itemDays / annualDays);
|
|
}
|
|
InterestAmount = (interestProfitSum * closePrecent) + InterestAmount;
|
|
}
|
|
else
|
|
{
|
|
if (itemDays > 1)//日期超算情况
|
|
{
|
|
decimal days = (decimal)itemDays - 1m;
|
|
if (position.IsAnnualized)
|
|
{
|
|
InterestAmount = InterestAmount * (days / annualDays);
|
|
TdInterestAmount = TdInterestAmount * (days / annualDays);
|
|
InterestAmount += (interestProfitSum * closePrecent);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
InterestAmount = interestProfitSum * closePrecent;
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
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;
|
|
}
|
|
/// <summary>
|
|
/// 计算复利 盘中
|
|
/// </summary>
|
|
/// <param name="lastSwapDate">上一互换日</param>
|
|
/// <param name="endDate">结算日期</param>
|
|
/// <param name="tradeDate">开仓日</param>
|
|
/// <param name="floatUnderylingCode">浮动标的</param>
|
|
/// <param name="principal">计息基数</param>
|
|
/// <param name="interestRate">固定利率</param>
|
|
/// <param name="isAnnualized">是否年化</param>
|
|
/// <param name="annualDays">年化天数</param>
|
|
/// <returns></returns>
|
|
public void CalcDailyCompoundInterest(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)
|
|
{
|
|
DateTime lastSwapDate = preEodPosition.ValueDate;
|
|
decimal interestProfitSum = preEodPosition.InterestProfitSum;
|
|
var TdInterestPrincipal = preEodPosition.TdInterestPrincipal;
|
|
decimal interest = 0;
|
|
decimal tdinterest = 0;
|
|
int interestPeriod = position.interest_rest_days ?? 1;
|
|
decimal dynomicPrincipal = principal;
|
|
decimal tdDynomicPrincipal = posiPrincipal;
|
|
var calcDays = (endDate - tradeDate).Days;
|
|
double floatRate = Convert.ToDouble(floateRate);
|
|
for (int i = 0; i <= calcDays; i++)
|
|
{
|
|
var rateDate = tradeDate.AddDays(i);
|
|
if (rateDate > lastSwapDate || endDate == tradeDate)
|
|
{
|
|
if (i % interestPeriod == 0)
|
|
{
|
|
dynomicPrincipal = dynomicPrincipal + interest;
|
|
tdDynomicPrincipal = tdDynomicPrincipal + interest;
|
|
if (!string.IsNullOrEmpty(position.FloatRateUnderlyingCode))
|
|
{
|
|
var fr007RateDate = QdpCalendarHelper.GetNonHolidayDefore(endDate.AddDays(position.interest_rule ?? 0)); // 获取前一工作日;
|
|
if (EodPriceQueryService.TryGetPrice(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 = (preEodPosition.TdInterestPrincipal + posiPrincipal - orginPv) * closePercent;
|
|
tdDynomicPrincipal = flowEvent.InterestPrincipal;
|
|
TdInterestPrincipal = (preEodPosition.TdInterestPrincipal + posiPrincipal - orginPv);
|
|
}
|
|
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;
|
|
|
|
}
|
|
else if (lastSwapDate >= rateDate)
|
|
{
|
|
interest = interestProfitSum * closePercent;
|
|
tdinterest = interestProfitSum * closePercent;
|
|
}
|
|
|
|
}
|
|
InterestAmount = Math.Round(interest, ConsGlobal.PriceRound, MidpointRounding.AwayFromZero);
|
|
TdInterestAmount = Math.Round(tdinterest, ConsGlobal.PriceRound, MidpointRounding.AwayFromZero);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 计算复利 收盘
|
|
/// </summary>
|
|
/// <param name="lastSwapDate">上一互换日</param>
|
|
/// <param name="endDate">结算日期</param>
|
|
/// <param name="tradeDate">开仓日</param>
|
|
/// <param name="floatUnderylingCode">浮动标的</param>
|
|
/// <param name="principal">计息基数</param>
|
|
/// <param name="interestRate">固定利率</param>
|
|
/// <param name="isAnnualized">是否年化</param>
|
|
/// <param name="annualDays">年化天数</param>
|
|
/// <returns></returns>
|
|
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 = preEodPosition.TdInterestIncome;
|
|
decimal tdinterest = preEodPosition.TdInterestIncome;
|
|
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 (EodPriceQueryService.TryGetPrice(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;
|
|
interest = flowEvent.InterestPrincipal * (flowEvent.InterestRate + Convert.ToDecimal(floatRate));
|
|
tdinterest = tdDynomicPrincipal * (flowEvent.InterestRate + Convert.ToDecimal(floatRate));
|
|
if (position.IsAnnualized)
|
|
{
|
|
interest /= annualDays;
|
|
tdinterest /= annualDays;
|
|
}
|
|
}
|
|
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);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 单标的平仓
|
|
/// </summary>
|
|
/// <param name="unwindData"></param>
|
|
/// <exception cref="ServiceException"></exception>
|
|
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 = CloseTrade_ClientCashInCashOut(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<swap_flow_event>(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(() =>
|
|
{
|
|
RealtimePnlCalc.RealtimeSwapPosition(new OptUserInfo(0, "互换实时持仓服务", OptUserFrom.Service));
|
|
});
|
|
}
|
|
}
|
|
/// <summary>
|
|
/// 自动全平仓
|
|
/// </summary>
|
|
/// <param name="td"></param>
|
|
/// <param name="unwindPrice"></param>
|
|
/// <param name="unwindPriceFee"></param>
|
|
public void AuotoSwapUnwind(int tradeid, decimal unwindPrice, decimal unwindPriceFee, decimal unwindNetFee, decimal unwindNet, DateTime valueDate, decimal unwindQty)
|
|
{
|
|
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<int> eventTypes = new List<int>() { (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.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, 2, MidpointRounding.AwayFromZero);
|
|
floatEvent.TradingFee = floatEvent.TradingFeePending;
|
|
floatEvent.MarkClosePnl = (unwindPrice - position.PosiGrossPrice) * unwindQty * floatRatio * longRatio;
|
|
floatEvent.MarkClosePnl = Math.Round(floatEvent.MarkClosePnl + (floatEvent.TradingFeePending*2 * floatRatio * -1), ConsGlobal.MoneyRound, MidpointRounding.AwayFromZero);
|
|
floatEvent.TradingAmount = unwindPrice * floatEvent.Quantity * floatEvent.ContractSize;
|
|
floatEvent.OptLog = "流水自动";
|
|
floatEvent.ClientId = td.ClientId;
|
|
floatEvent.SetOpt(UserInfo);
|
|
}
|
|
unwindData.FlowEvents.Add(floatEvent);
|
|
var interestPositions = GetUnwindInterests(unwindData.ValueDate, unwindData.UnwindDate.Value, td.id, unwindPercent, (int)SwapEventTypeEnum.平仓);
|
|
interestPositions.ForEach(item =>
|
|
{
|
|
item.OptLog = "流水自动";
|
|
});
|
|
unwindData.FlowEvents.AddRange(interestPositions);
|
|
CalcCloseAmount(unwindData);
|
|
DealUnwind(unwindData, td);
|
|
}
|
|
/// <summary>
|
|
/// 衡泰新增平仓事件
|
|
/// </summary>
|
|
/// <param name="td"></param>
|
|
/// <param name="valueDate"></param>
|
|
/// <param name="markClosePnl"></param>
|
|
/// <param name="unwindQty"></param>
|
|
/// <param name="allClose"></param>
|
|
public void AutoSwapUnwindFromConsumer(trade td, DateTime valueDate, DateTime payDate, decimal markClosePnl, decimal tradeinfFee, decimal interestAmount, decimal fee, decimal unwindQty, bool allClose)
|
|
{
|
|
List<int> eventTypes = new List<int>() { (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<swap_flow_event> GetUnwindInterestsByHT(UnwindData unwindData, trade td, decimal interestAmount, decimal fee)
|
|
{
|
|
List<swap_flow_event> interests = new List<swap_flow_event>();
|
|
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 = CloseTrade_ClientCashInCashOut(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<swap_flow_event>(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;
|
|
DbContext.SaveChanges();
|
|
}
|
|
/// <summary>
|
|
/// 计算平仓总额
|
|
/// </summary>
|
|
/// <param name="unwindData"></param>
|
|
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.MarkClosePnl;
|
|
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 = decimal.Parse(unwindData.SwapCloseAmount.ToString("0.00"));
|
|
unwindData.SwapRealizedPnL = unwindData.SwapCloseAmount;
|
|
}
|
|
/// <summary>
|
|
/// 多空组合平仓
|
|
/// </summary>
|
|
/// <param name="unwindData"></param>
|
|
/// <exception cref="ServiceException"></exception>
|
|
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 = CloseTrade_ClientCashInCashOut(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 ex;
|
|
}
|
|
finally
|
|
{
|
|
trans.Dispose();
|
|
}
|
|
|
|
}
|
|
/// <summary>
|
|
/// 多空组合互换
|
|
/// </summary>
|
|
/// <param name="swap_Deal"></param>
|
|
/// <exception cref="ServiceException"></exception>
|
|
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 = CloseTrade_ClientCashInCashOut(td, Convert.ToDouble(unwindData.SwapCloseAmount), ClientCashInCashOut.系统操作_互换, unwindData.ValueDate);
|
|
SaveSwapDeal(unwindData, (int)SwapEventTypeEnum.互换, clientCashId, "系统操作_互换");
|
|
trans.Commit();
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
trans.Rollback();
|
|
throw ex;
|
|
}
|
|
finally
|
|
{
|
|
trans.Dispose();
|
|
}
|
|
}
|
|
/// <summary>
|
|
/// 互换
|
|
/// </summary>
|
|
/// <param name="swap_Deal"></param>
|
|
/// <exception cref="ServiceException"></exception>
|
|
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 = CloseTrade_ClientCashInCashOut(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 ex;
|
|
}
|
|
finally
|
|
{
|
|
trans.Dispose();
|
|
}
|
|
}
|
|
/// <summary>
|
|
/// 互换/平仓审核通过
|
|
/// </summary>
|
|
/// <param name="td"></param>
|
|
/// <param name="eventType"></param>
|
|
/// <exception cref="Exception"></exception>
|
|
public void ApproveSwapTrade(trade td, int eventType)
|
|
{
|
|
Expression<Func<swap_event, bool>> 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<UnwindData>(swapEvent.EventData);
|
|
var flowList = DbContext.swap_flow_event.Where(x => x.EventId == swapEvent.id).ToList();
|
|
string action = eventType == (int)SwapEventTypeEnum.互换 ? ClientCashInCashOut.系统操作_互换 : ClientCashInCashOut.系统操作_平仓费;
|
|
int clientCashId = CloseTrade_ClientCashInCashOut(td, Convert.ToDouble(swapEvent.unwindData.SwapCloseAmount), action, swapEvent.unwindData.ValueDate);
|
|
swapEvent.ClientCashId = clientCashId;
|
|
if (td.StructureType != "多空组合")
|
|
{
|
|
if (eventType == (int)SwapEventTypeEnum.互换)
|
|
{
|
|
if (td.ExerciseDate <= swapEvent.unwindData.ValueDate)
|
|
{
|
|
td.Notional = 0;
|
|
td.StockEqvNotional = 0;
|
|
td.TradeStatus = ConsTrade.已到期;
|
|
}
|
|
else
|
|
{
|
|
td.TradeStatus = ConsTrade.确认成交;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (swapEvent.unwindData.CloseMethod == (int)CloseMethodEnum.全部平仓)
|
|
{
|
|
td.TradeStatus = ConsTrade.已平仓;
|
|
td.Notional = 0;
|
|
td.TradeAmount = 0;
|
|
}
|
|
else
|
|
{
|
|
td.TradeStatus = ConsTrade.确认成交;
|
|
td.HasPartialUnWind = 1;
|
|
td.TradeAmount -= Convert.ToDouble(swapEvent.unwindData.CloseQty);
|
|
}
|
|
td.StockEqvNotional -= Convert.ToDouble(swapEvent.unwindData.CloseNotionalValue);
|
|
}
|
|
td.UnWindDate = swapEvent.unwindData.UnwindDate;
|
|
UpdateInitalPosition(flowList, swapEvent.unwindData, eventType);
|
|
DbContext.SaveChanges();
|
|
}
|
|
else
|
|
{
|
|
if (eventType == (int)SwapEventTypeEnum.平仓)
|
|
{
|
|
td.UnWindDate = swapEvent.unwindData.UnwindDate;
|
|
td.StockEqvNotional = 0;
|
|
td.TradeStatus = "已平仓";
|
|
}
|
|
UpdateInitalPosition(flowList, swapEvent.unwindData, eventType);
|
|
DbContext.SaveChanges();
|
|
}
|
|
|
|
|
|
}
|
|
/// <summary>
|
|
/// 互换/平仓提交审核
|
|
/// </summary>
|
|
/// <param name="unwindData"></param>
|
|
/// <param name="eventType"></param>
|
|
/// <exception cref="ServiceException"></exception>
|
|
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 ex;
|
|
}
|
|
finally
|
|
{
|
|
trans.Dispose();
|
|
}
|
|
}
|
|
/// <summary>
|
|
/// 保存平仓/互换事件
|
|
/// </summary>
|
|
/// <param name="swap_Deal"></param>
|
|
/// <param name="eventType"></param>
|
|
private long SaveSwapDeal(UnwindData unwindData, int eventType, int clientCashId, string eventResason = "", bool approve = false)
|
|
{
|
|
var flowList = new List<swap_flow_event>(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;
|
|
item.EventId = swapEvent.id;
|
|
DbContext.swap_flow_event.Add(item);
|
|
}
|
|
if (!approve)
|
|
{
|
|
UpdateInitalPosition(flowList, unwindData, eventType);
|
|
}
|
|
DbContext.SaveChanges();
|
|
return swapEvent.id;
|
|
}
|
|
/// <summary>
|
|
/// 计算平仓数据
|
|
/// </summary>
|
|
/// <param name="unwindData"></param>
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
/// <summary>
|
|
/// 单标的互换更新实时持仓信息
|
|
/// </summary>
|
|
/// <param name="swap_Deal"></param>
|
|
private void UpdateInitalPosition(List<swap_flow_event> 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))
|
|
{
|
|
position.PosiQuantity -= unwindData.CloseQty;
|
|
position.PosiNotionalValue = position.PosiGrossPrice * position.PosiQuantity * position.ContractSize;
|
|
position.PosiTradingFee -= position.PosiTradingFee * 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;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
}
|