- 添加 swapPricePrecisionHelper.js 工具类处理价格精度格式化 - 新增 swappriceprecision.js 配置文件定义各类金融产品的精度规则 - 在 EodPositionRisks.cshtml 和 SwapIncome.cshtml 中引入新的价格格式化脚本 - 替换原有的价格格式化函数为基于产品类型的动态精度控制 - 移除旧的价格验证和标准化逻辑,改用新的精度控制机制 - 添加 vue-swap-price-input 组件用于精确的价格输入控制 - 更新 Controller 中的价格处理逻辑以支持新精度格式化方式
1750 lines
80 KiB
C#
1750 lines
80 KiB
C#
using BaseOUDAL;
|
|
using ClosedXML.Report.Options;
|
|
using Confluent.Kafka;
|
|
using CsvHelper;
|
|
using MoreLinq;
|
|
using Newtonsoft.Json;
|
|
using NPOI.POIFS.NIO;
|
|
using Org.BouncyCastle.Ocsp;
|
|
using Qdp.ComputeServiceV2.Data.CommonModels.TradeInfos;
|
|
using Qdp.Foundation.Implementations;
|
|
using Qdp.Pricing.Ecosystem.Trade.FixedIncome;
|
|
using Qdp.Pricing.Library.Base.Utilities;
|
|
using Qdp.Pricing.Library.Common.Products.Rates;
|
|
using System;
|
|
using System.Data;
|
|
using System.Linq.Expressions;
|
|
using System.Text;
|
|
using YLErp.BLL;
|
|
using YLErp.Configuration;
|
|
using YLErp.Configuration.Enums;
|
|
using YLErp.CustomizedBizLogic;
|
|
using YLErp.DBModels;
|
|
using YLErp.DBModels.Enums;
|
|
using YLErp.Enums;
|
|
using YLErp.Helpers;
|
|
using YLErp.Model;
|
|
using YLErp.Model.Enum;
|
|
using YLErp.Models;
|
|
using YLErp.Modules.DataProviderModule;
|
|
using YLErp.Modules.EodModule;
|
|
using YLErp.Modules.RiskModule;
|
|
using YLErp.Modules.SalesModule;
|
|
using YLErp.Modules.TradeModule;
|
|
using YLErp.Modules.TradeModule.DealModule;
|
|
using YLErp.Modules.TradeModule.QueryModule;
|
|
using YLErp.Modules.UnderlyingModule;
|
|
using YLErp.QdpModule;
|
|
using static YLErp.ConsGlobal;
|
|
|
|
namespace YLErp.Modules.SwapModule
|
|
{
|
|
/// <summary>
|
|
/// 新版互换交易服务
|
|
/// </summary>
|
|
public class SwapTradeService : SwapTradeBaseService
|
|
{
|
|
public SwapTradeService(OptUserInfo optUser) : base(optUser)
|
|
{
|
|
}
|
|
public SwapTradeService(YLBaseService baseService) : base(baseService)
|
|
{
|
|
|
|
}
|
|
|
|
private static decimal? RoundSwapBondNetPriceAndYtm(decimal? value)
|
|
{
|
|
return value.HasValue
|
|
? Math.Round(value.Value, ConsGlobal.PriceRound, MidpointRounding.AwayFromZero)
|
|
: null;
|
|
}
|
|
|
|
#region 互换交易保存
|
|
/// <summary>
|
|
/// 新版收益互换预付金校验
|
|
/// </summary>
|
|
public string ValidateMargin(trade req)
|
|
{
|
|
var errorMsg = string.Empty;
|
|
var client = DataCacheProvider.GetClientDataSource().GetData(req.ClientId);
|
|
if (!client.DerivativesInvestmentVarieties.Contains((int)DerivativesInvestmentVarietiesEnum.场外互换 + ""))
|
|
{
|
|
return $"客户:{client.Name}未设置交易种类“场外互换”,无法生成互换交易!";
|
|
}
|
|
|
|
//收益互换初始预付金校验
|
|
errorMsg = checkMarginRate(req);
|
|
return errorMsg;
|
|
}
|
|
/// <summary>
|
|
/// 互换交易保存
|
|
/// </summary>
|
|
/// <param name="td"></param>
|
|
/// <returns></returns>
|
|
public trade SaveTrade(trade req)
|
|
{
|
|
var um = checkUnderlying(req);
|
|
trade dbTrade = new trade();
|
|
//交易保存处理
|
|
var tradeNumberGenerated = PrepareTrade(req, TradeSourceEnum.系统交易, um);
|
|
|
|
var trans = DbContext.Database.BeginTransaction();
|
|
try
|
|
{
|
|
var isAddNew = req.id == 0;
|
|
dbTrade = isAddNew ? InnerSaveNewTrade(req, tradeNumberGenerated) : InnerSaveEditTrade(req, out var changeConfirmStatus);
|
|
if (req.SalesCommission != null)
|
|
{
|
|
req.SalesCommission.ClientId = dbTrade.ClientId;
|
|
}
|
|
if (PS.Config.ErpElement.SalesCommissionCalculation == "默认" || PS.Config.ErpElement.SalesCommissionCalculation == "" || PS.Config.ErpElement.SalesCommissionCalculation == null)
|
|
{
|
|
new SalesCommissionDataService(this).SaveTradeCommission(dbTrade.id, req.SalesCommission, false);
|
|
}
|
|
else if (PS.Config.ErpElement.SalesCommissionCalculation == "公式1")
|
|
{
|
|
if (req.SalesCommission?.SalesIds != null && req.SalesCommission.SalesIds.Any())
|
|
{
|
|
new SalesCommissionDetailDataService(this).SaveTradeCommissionDetail(dbTrade.id, req.SalesCommission.ClientId, req.SalesCommission.Commission, req.SalesCommission.CommissionFixed, req.SalesCommission.SalesIds, false);
|
|
}
|
|
else
|
|
{
|
|
new SalesCommissionDetailDataService(this).RemoveTradeCommissionDetail(dbTrade.id);
|
|
}
|
|
}
|
|
trans.Commit();
|
|
}
|
|
finally
|
|
{
|
|
trans.Dispose();
|
|
}
|
|
|
|
return dbTrade;
|
|
}
|
|
/// <summary>
|
|
/// 单标的生成开仓事件
|
|
/// </summary>
|
|
/// <param name="td"></param>
|
|
public void AddPositionEvent(trade td, string optLog)
|
|
{
|
|
var initials = DbContext.swap_position.Where(x => x.SwapTradeId == td.id && x.IsInitial && !x.Invalid).ToList();
|
|
if (td.StructureType == "多空组合")
|
|
{
|
|
initials = initials.Where(x => x.PosiDirection == 0).ToList();
|
|
}
|
|
new SwapFlowEventService(this).InitEvent(initials, td, optLog);
|
|
}
|
|
/// <summary>
|
|
/// 删除互换交易
|
|
/// </summary>
|
|
/// <param name="id"></param>
|
|
public void deleteTrade(int id, bool addLog = true)
|
|
{
|
|
var dbTrade = DbContext.trade.Find(id);
|
|
if (dbTrade == null)
|
|
{
|
|
throw new ServiceException("找不到交易信息");
|
|
}
|
|
dbTrade.OptId = UserId;
|
|
dbTrade.OptName = UserName;
|
|
dbTrade.OptDate = OptDate;
|
|
dbTrade.ValidState = ConsGlobal.InValid;
|
|
var tradeNumber = dbTrade.TradeNumber;
|
|
if (!string.IsNullOrWhiteSpace(dbTrade.TradeNumber))
|
|
{
|
|
dbTrade.TradeNumber = dbTrade.TradeNumber.Insert(0, "XX");
|
|
var contractBll = new TradeContractBLL(UserId, UserName);
|
|
contractBll.DeleteConfirmBook(dbTrade.id);
|
|
}
|
|
var swapPositions = DbContext.swap_position.Where(x => x.SwapTradeId == id).ToList();
|
|
swapPositions.ForEach(x =>
|
|
{
|
|
x.Invalid = true;
|
|
x.OptTime = OptDate;
|
|
});
|
|
new SwapEventService(this).AddSwapEventDate(DateTime.Now.Date, id, (int)SwapEventTypeEnum.删除, string.Empty, 0, false);
|
|
DeleteTradeDetials(id);
|
|
DeleteTradeCashInCashOut(id);
|
|
DbContext.SaveChanges();
|
|
|
|
}
|
|
private void DeleteTradeDetials(int tradeId)
|
|
{
|
|
var eodSwapPosiSql = $"{nameof(eod_swap_position.SwapTradeId)}={tradeId}";
|
|
var eodSwapSql = $"{nameof(eod_swap.SwapTradeId)}={tradeId}";
|
|
DbContext.BulkDelete<eod_swap_position>(eodSwapPosiSql);
|
|
DbContext.BulkDelete<eod_swap>(eodSwapSql);
|
|
}
|
|
public void DeleteTradeCashInCashOut(int tradeId)
|
|
{
|
|
var predicate_cashIncashOut = PredicateBuilder.Create<ClientCashInCashOut>(x => x.TradeId == tradeId);
|
|
var delCashInCashOutArr = DbContext.ClientCashInCashOut.Where(predicate_cashIncashOut);
|
|
|
|
DbContext.ClientCashInCashOut.RemoveRange(delCashInCashOutArr);
|
|
}
|
|
/// <summary>
|
|
/// 单标的初始化实时持仓
|
|
/// </summary>
|
|
/// <param name="tradeId"></param>
|
|
public void InitialPosition(trade td, bool save = false)
|
|
{
|
|
var swapPostions = DbContext.swap_position.Where(x => x.SwapTradeId == td.id);
|
|
var initials = swapPostions.Where(x => x.IsInitial);
|
|
var positions = swapPostions.Where(x => !x.IsInitial);
|
|
if (td.StructureType == "多空组合")
|
|
{
|
|
initials = initials.Where(x => x.PosiDirection == 0);
|
|
positions = swapPostions.Where(x => !x.IsInitial && x.PosiDirection == 0);
|
|
}
|
|
List<swap_position> _Positions = new List<swap_position>(positions);
|
|
DbContext.swap_position.RemoveRange(positions);
|
|
foreach (var item in initials)
|
|
{
|
|
if (string.IsNullOrEmpty(item.PosiNumber))
|
|
{
|
|
item.PosiNumber = $"{td.TradeNumber}-{item.id}";
|
|
}
|
|
if (td.StructureType == "多空组合")
|
|
{
|
|
var position = _Positions.FirstOrDefault(x => x.PositionId == item.id);
|
|
if (position == null)
|
|
{
|
|
position = item.Clone();
|
|
position.id = 0;
|
|
position.PositionId = item.id;
|
|
position.IsInitial = false;
|
|
DbContext.swap_position.Add(position);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
var position = item.Clone();
|
|
position.id = 0;
|
|
position.PositionId = item.id;
|
|
position.IsInitial = false;
|
|
DbContext.swap_position.Add(position);
|
|
}
|
|
}
|
|
if (save)
|
|
{
|
|
DbContext.SaveChanges();
|
|
}
|
|
|
|
}
|
|
//新增交易保存
|
|
private trade InnerSaveNewTrade(trade req, bool tradeNumberGenerated)
|
|
{
|
|
var dbTrade = req.Clone();
|
|
// 新增场景同样从 swap_positions 取 InitYtm,无则默认 0,避免数据库 NOT NULL 约束报错
|
|
dbTrade.InitYtm = req.swap_positions.FirstOrDefault(p => p.InitYtm != null)?.InitYtm ?? 0;
|
|
DbContext.trade.Add(dbTrade);
|
|
InnerSaveTrade(true, dbTrade, "");
|
|
|
|
return dbTrade;
|
|
}
|
|
/// <summary>
|
|
/// 新增互换交易并确认开仓
|
|
/// </summary>
|
|
/// <param name="flowMerge"></param>
|
|
/// <param name="matuirityDate"></param>
|
|
/// <param name="client"></param>
|
|
/// <param name="asset"></param>
|
|
/// <param name="underlying"></param>
|
|
/// <param name="swapFloatRate"></param>
|
|
/// <returns></returns>
|
|
public trade NewSwapTrade(swap_flow_merge flowMerge,
|
|
Client client,
|
|
AssetUnit asset,
|
|
underlying_manager underlying,
|
|
SwapFloatRate swapFloatRate,
|
|
string clearingAgency,
|
|
string structureType = "普通债券类收益互换",
|
|
bool cashNeedAfter = false)
|
|
{
|
|
int SwapEndDays = UnderlyingHelper.GetApplicableMarginRate(client.id,underlying.UnderlyingCode,flowMerge.OccurTime)?.swap_days??14;
|
|
var td = PrepareTrade(flowMerge, client, asset, underlying, SwapEndDays, structureType);
|
|
PrepareTradeExtend(flowMerge, td, underlying, swapFloatRate);
|
|
td.TradeNumber = BizLogicSingleton.Instance.GenerateTradeNumberBeforeConfirm(td, DbContext);
|
|
flowMerge.SwapTradeNo = td.TradeNumber;
|
|
PrepareSwapTrade(td, TradeSourceEnum.系统交易, underlying);
|
|
DbContext.trade.Add(td);
|
|
DbContext.SaveChanges();
|
|
td.trade_Initial_Margin = new trade_initial_margin()
|
|
{
|
|
TradeId = td.id,
|
|
MarginType = 1,
|
|
Direction = (int)SwapDirectionEnum.收取,
|
|
MarginValue = 0,
|
|
};
|
|
AddTradeMeta(false, td.id, YLErp.DBModels.Consts.ConsTradeMetaKey.MainProtocolCode, client.MainProtocolCode);
|
|
AddTradeMeta(false, td.id, YLErp.DBModels.Consts.ConsTradeMetaKey.SupProtocolCode, client.SupProtocolCode);
|
|
AddTradeMeta(false, td.id, YLErp.DBModels.Consts.ConsTradeMetaKey.TradingPlace, "柜台市场");
|
|
if (!string.IsNullOrEmpty(clearingAgency))
|
|
{
|
|
AddTradeMeta(false, td.id, YLErp.DBModels.Consts.ConsTradeMetaKey.ClearingAgency, clearingAgency);
|
|
}
|
|
TradeExtendJson tradeExtendJson = new TradeExtendJson()
|
|
{
|
|
FlowBookMode = (int)FlowBookModeEnum.先进先出,
|
|
FloatingPnlAnnualized = false,
|
|
NeedOpenFee = true,
|
|
OpenFeeType = 1,
|
|
Direction = 1,
|
|
InterestCalcMode = "10",
|
|
SettlementRules = cashNeedAfter ? 1 : 0,
|
|
};
|
|
if (structureType != "普通债券类收益互换")
|
|
{
|
|
tradeExtendJson.FlowBookMode = (int)FlowBookModeEnum.加权平均;
|
|
}
|
|
td.trade_extend = new trade_extend()
|
|
{
|
|
TradeId = td.id,
|
|
ExtendJson = JsonHelper.Serialize(tradeExtendJson)
|
|
};
|
|
DbContext.trade_initial_margin.Add(td.trade_Initial_Margin);
|
|
DbContext.trade_extend.Add(td.trade_extend);
|
|
foreach (var item in td.swap_positions)
|
|
{
|
|
item.SwapTradeId = td.id;
|
|
DbContext.swap_position.Add(item);
|
|
}
|
|
DbContext.SaveChanges();
|
|
new TradeConfirmService(UserInfo).SwapTradeConfirm(td, "流水自动簿记确认交易", true, flowMerge.OccurTime, "流水自动");
|
|
return td;
|
|
}
|
|
/// <summary>
|
|
/// 准备框架合约数据
|
|
/// </summary>
|
|
/// <param name="flowMerge"></param>
|
|
/// <param name="client"></param>
|
|
/// <param name="asset"></param>
|
|
/// <param name="underlying"></param>
|
|
/// <returns></returns>
|
|
private trade PrepareTrade(swap_flow_merge flowMerge, Client client, AssetUnit asset, underlying_manager underlying,int swapEndDays, string structureType = "普通债券类收益互换")
|
|
{
|
|
trade td = new trade()
|
|
{
|
|
TradeType = "收益互换",
|
|
BuySell = "卖出",
|
|
StartDate = flowMerge.OccurTime,
|
|
TradeDate = flowMerge.OccurTime,
|
|
TraderId = asset.TraderIdsInt.FirstOrDefault(),
|
|
TraderName = asset.TraderNamesList.FirstOrDefault(),
|
|
MarginTemplateName = null,
|
|
OpponentRole = "乙方",
|
|
StructureType = structureType,
|
|
InitialMargin = 0,
|
|
ClientId = client.id,
|
|
ClientNumber = client.Number,
|
|
ClientName = client.Name,
|
|
AssetId = asset.id,
|
|
AssetBookName = asset.Name,
|
|
Notional = Convert.ToDouble(flowMerge.TradingQtyAbs),
|
|
TradeAmount = Convert.ToDouble(flowMerge.TradingQtyAbs),
|
|
StockEqvNotional = Math.Round(Convert.ToDouble(flowMerge.TradingAmount), ConsGlobal.MoneyRound, MidpointRounding.AwayFromZero),
|
|
IsAutoGenerate = true,
|
|
};
|
|
if (flowMerge.SettleDate.HasValue)
|
|
{
|
|
td.StartDate = flowMerge.SettleDate.Value;
|
|
}
|
|
td.ExerciseDate = QdpCalendarHelper.GetNonHolidayDefore(td.StartDate.Value.AddDays(swapEndDays));
|
|
PrepareUnderlying(td, underlying);
|
|
PrepareSwapTradeClient(td, underlying);
|
|
td.TradeOldStatus = ConsTrade.新增待确认;
|
|
td.OptId = UserId;
|
|
td.OptName = UserName;
|
|
td.OptDate = OptDate;
|
|
td.CreateDate = OptDate;
|
|
td.ValidState = "Valid";
|
|
td.TradeSource = "系统交易";
|
|
td.TradeStatus = ConsTrade.确认成交;
|
|
td.InitYtm = RoundSwapBondNetPriceAndYtm(flowMerge.InitYtm) ?? 0;
|
|
return td;
|
|
}
|
|
/// <summary>
|
|
/// 准备框架合约明细数据
|
|
/// </summary>
|
|
/// <param name="flowMerge"></param>
|
|
/// <param name="td"></param>
|
|
/// <param name="underlying"></param>
|
|
/// <param name="swapFloatRate"></param>
|
|
private void PrepareTradeExtend(swap_flow_merge flowMerge, trade td, underlying_manager underlying, SwapFloatRate swapFloatRate)
|
|
{
|
|
swap_position floatPosition = new swap_position()
|
|
{
|
|
PosiDirection = (int)SwapDirectionEnum.支付,
|
|
PositionType = flowMerge.BsType,
|
|
UnderlyingCode = flowMerge.UnderlyingCode,
|
|
CountRatio = underlying.CountRatio,
|
|
ContractSize = Convert.ToDecimal(underlying.ContractSize),
|
|
PosiNetPrice = flowMerge.TradingAmountFeeAvgAbs,
|
|
PosiGrossPrice = Math.Round(
|
|
flowMerge.TradingAmountAvg,
|
|
underlying.IsBond() ? ConsGlobal.PriceRound : ConsGlobal.SwapDeliveryPriceRound,
|
|
MidpointRounding.AwayFromZero),
|
|
PosiNetFeePrice = flowMerge.TradingAmountNetFeeAvg ?? 0,
|
|
PosiNetNoFeePrice = flowMerge.TradingAmountNetAvg ?? 0,
|
|
PosiQuantity = flowMerge.TradingQtyAbs,
|
|
PosiNotionalValue = Math.Round(flowMerge.TradingAmount, ConsGlobal.MoneyRound, MidpointRounding.AwayFromZero),
|
|
PosiTradingFeePending = flowMerge.TradingFeePending,
|
|
PosiTradingFee = 0,
|
|
PosiTradingFeeUnit = 0,
|
|
SwapTradeId = td.id,
|
|
PosiMatuirityDate = td.ExerciseDate.Value,
|
|
PosiStartDate = td.StartDate.Value,
|
|
IsInitial = true,
|
|
OptTime = DateTime.Now,
|
|
OptId = UserInfo.UserId,
|
|
OptName = UserInfo.UserName,
|
|
UnderlyingInstrumentType = underlying.UnderlyingInstrumentType,
|
|
InitYtm = RoundSwapBondNetPriceAndYtm(flowMerge.InitYtm)
|
|
};
|
|
td.swap_positions.Add(floatPosition);
|
|
swap_position interestPosition = new swap_position()
|
|
{
|
|
InterestDirection = flowMerge.BsType == (int)PositionTypeFlag.Long ? (int)SwapDirectionEnum.收取 : (int)SwapDirectionEnum.支付,
|
|
InterestRateDefault = 0,
|
|
InterestMode = (int)InterestModeEnum.标的期初全价,
|
|
InterestPrincipalFix = 0,
|
|
InterestType = 1,
|
|
IsAnnualized = true,
|
|
SwapTradeId = td.id,
|
|
PosiMatuirityDate = td.ExerciseDate.Value,
|
|
PosiStartDate = td.StartDate.Value,
|
|
IsInitial = true,
|
|
interest_rest_days = 7,
|
|
OptTime = DateTime.Now,
|
|
OptId = UserInfo.UserId,
|
|
OptName = UserInfo.UserName,
|
|
};
|
|
if (swapFloatRate != null)
|
|
{
|
|
interestPosition.InterestRateDefault = flowMerge.BsType == (int)PositionTypeFlag.Long ? (swapFloatRate.LongPricePoint ?? 0) : (swapFloatRate.ShortPricePoint ?? 0) * -1;
|
|
interestPosition.InterestRateDefault = interestPosition.InterestRateDefault * 0.0001m;
|
|
interestPosition.FloatRateUnderlyingCode = swapFloatRate.BaseUnderlyingCode;
|
|
interestPosition.interest_rule = swapFloatRate.interest_rule;
|
|
interestPosition.interest_rest_days = swapFloatRate.interest_rest_days;
|
|
interestPosition.IsAnnualized = swapFloatRate.is_annualized ?? false;
|
|
interestPosition.InterestType = swapFloatRate.interest_type??0;
|
|
}
|
|
var interval = new IntervalModel()
|
|
{
|
|
Date = td.ExerciseDate.Value,
|
|
Rate = interestPosition.InterestRateDefault,
|
|
Settlement = 0
|
|
};
|
|
List<IntervalModel> intervalModels = new List<IntervalModel>() { interval };
|
|
interestPosition.InterestSwapInterval = JsonConvert.SerializeObject(intervalModels);
|
|
td.swap_positions.Add(interestPosition);
|
|
|
|
}
|
|
//修改交易保存
|
|
private trade InnerSaveEditTrade(trade req, out bool changeConfirmStatus)
|
|
{
|
|
var dbTrade = DbContext.trade.Find(req.id);
|
|
|
|
if (dbTrade == null)
|
|
{
|
|
throw new ServiceException("保存失败,数据不存在");
|
|
}
|
|
|
|
dbTrade.SalesCommission = new SalesCommissionDataService(OptUser).GetTradeCommissionInfo(dbTrade.id);
|
|
if (dbTrade.SalesCommission?.Commission == null && req.SalesCommission == null)
|
|
{
|
|
dbTrade.SalesCommission = null;
|
|
}
|
|
|
|
if (!ConsGlobal.IsValid(dbTrade.ValidState))
|
|
{
|
|
throw new NotSupportedException("不支持对已无效的交易进行修改:" + dbTrade.TradeNumber);
|
|
}
|
|
|
|
if (valuedateBLL.SystemDate.EditTradeNeedRemark)
|
|
{
|
|
req.MetaDic.TryGetValue("remark", out var remark);
|
|
req.MetaDic.Remove("remark");
|
|
|
|
}
|
|
|
|
//使用明确的IsUsePremiumRate布尔值
|
|
if (!req.IsUsePremiumRate.HasValue && dbTrade.IsUsePremiumRate.HasValue)
|
|
{
|
|
req.IsUsePremiumRate = false;
|
|
}
|
|
|
|
req.CalcFlag = dbTrade.CalcFlag;
|
|
|
|
var oldClientId = dbTrade.ClientId;
|
|
var oldTradeStatus = dbTrade.TradeStatus;
|
|
var tradeDateChanged = dbTrade.TradeDate != req.TradeDate;
|
|
|
|
//移除价格观察
|
|
if (dbTrade.UnderlyingId != req.UnderlyingId)
|
|
{
|
|
RemoveEntities<manually_trade_observation_price>(x => x.TradeId == req.id);
|
|
}
|
|
//交易编号调整
|
|
var canGenerateTradeNumber = false;
|
|
if (req.ValidState == "InValid")
|
|
{
|
|
if (!string.IsNullOrWhiteSpace(dbTrade.TradeNumber))
|
|
{
|
|
new TradeDocumentDataService(this).DeleteConfirmBook(dbTrade.id);
|
|
|
|
if (dbTrade.TradeNumber.StartsWith("CW"))
|
|
{
|
|
req.TradeNumber = dbTrade.TradeNumber.Replace("CW", "XX");
|
|
}
|
|
else // if (PS.Config.Is光大光子)
|
|
{
|
|
req.TradeNumber = dbTrade.TradeNumber.Insert(0, "XX");
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
canGenerateTradeNumber = string.IsNullOrWhiteSpace(req.TradeNumber);
|
|
if (canGenerateTradeNumber)
|
|
{
|
|
req.TradeNumber = dbTrade.TradeNumber;
|
|
}
|
|
}
|
|
|
|
req.ContractVersion = dbTrade.ContractVersion;
|
|
req.PairTrade = dbTrade.PairTrade;
|
|
req.CreateDate = dbTrade.CreateDate;
|
|
//确认成交状态的交易修改后变为原始标记
|
|
req.DividendDate = new DateTime(2000, 1, 1);
|
|
|
|
//记录交易变更信息
|
|
var changsStr = string.Empty;
|
|
changeConfirmStatus = true;
|
|
|
|
var changes = DataChangeHelper.GetDataChanges(dbTrade, req);
|
|
|
|
var dbTradeMeta = DbContext.TradeMeta.Where(a => a.TradeId == dbTrade.id).ToDictionary(a => a.MetaKey, b => b.MetaValue);
|
|
|
|
var changMetas = new List<List<string>>();
|
|
var keys = new string[] { "交易场所", "清算机构", "主协议编号", "补充协议编号" };
|
|
|
|
foreach (var key in keys)
|
|
{
|
|
bool isdbMeta = dbTradeMeta.TryGetValue(key, out string oldVal);
|
|
bool isreqMeta = req.MetaDic.TryGetValue(key, out string newVal);
|
|
if (!isdbMeta && !isreqMeta || oldVal == newVal || (string.IsNullOrWhiteSpace(oldVal) && string.IsNullOrWhiteSpace(newVal)))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
var changeMeta = new List<string>() { "", key, oldVal, newVal };
|
|
changMetas.Add(changeMeta);
|
|
}
|
|
if (changMetas.Any())
|
|
{
|
|
changes.AddRange(changMetas);
|
|
}
|
|
changsStr = changes.ToJson();
|
|
if (dbTrade.TradeStatus != ConsTrade.新增待确认)
|
|
{
|
|
//修改备注和簿记不需要提交审核和审批
|
|
changeConfirmStatus = !changes.All(n => string.IsNullOrEmpty(n[0]) || n[0] == nameof(trade.Comments)
|
|
|| n[0] == nameof(trade.AssetId) || n[0] == nameof(trade.AssetBookName));
|
|
}
|
|
|
|
//更新数据库实体
|
|
UpdateEntity(dbTrade, req);
|
|
//交易状态处理
|
|
if (dbTrade.CheckTradeUpdate == Convert.ToInt32(TradeCheckEnum.StatusOfNew) || dbTrade.TradeStatus == ConsTrade.确认成交)
|
|
{
|
|
if (changeConfirmStatus)
|
|
{
|
|
dbTrade.TradeStatus = ConsTrade.修改待确认;
|
|
dbTrade.CheckTradeUpdate = Convert.ToInt32(TradeCheckEnum.StatusOfOld);
|
|
dbTrade.CheckStatus = null;
|
|
}
|
|
}
|
|
else if (dbTrade.TradeStatus != ConsTrade.修改待确认)
|
|
{
|
|
dbTrade.TradeStatus = ConsTrade.新增待确认;
|
|
dbTrade.CheckTradeUpdate = Convert.ToInt32(TradeCheckEnum.StatusOfOld);
|
|
dbTrade.CheckStatus = null;
|
|
//如果是新增待确认并且修改了客户,需要重新生成交易编号
|
|
if (oldClientId != dbTrade.ClientId && canGenerateTradeNumber)
|
|
{
|
|
dbTrade.TradeNumber = BizLogicSingleton.Instance.GenerateTradeNumberBeforeConfirm(dbTrade, DbContext);
|
|
}
|
|
}
|
|
dbTrade.TradeOldStatus = oldTradeStatus;
|
|
|
|
//重置历史数据(20210928:如果是当天修改也支持重置)
|
|
var resetHisData = dbTrade.TradeStatus == "新增待确认" || tradeDateChanged || valuedateBLL.ValueDate == dbTrade.TradeDate;
|
|
if (resetHisData)
|
|
{
|
|
dbTrade.TradeSavedVol = PS.Config.Is厦门象屿 ? dbTrade.TradeCloseVolatility : dbTrade.TradeOpenVolatility;
|
|
if (PS.Config.ErpElement.VolMode == Configuration.VolModeEnum.VolSurface)
|
|
{
|
|
dbTrade.TradeSavedVol = dbTrade.Vol;
|
|
}
|
|
}
|
|
dbTrade.trade_Initial_Margin = req.trade_Initial_Margin;
|
|
dbTrade.trade_extend = req.trade_extend;
|
|
dbTrade.swap_positions = req.swap_positions;
|
|
dbTrade.MetaDic = req.MetaDic;
|
|
dbTrade.InitYtm = req.swap_positions.FirstOrDefault(p => p.InitYtm != null)?.InitYtm;
|
|
InnerSaveTrade(false, dbTrade, changsStr, changeConfirmStatus);
|
|
|
|
return dbTrade;
|
|
}
|
|
|
|
//用于交易(不能是结构化主交易)入库后续处理
|
|
//changeConfirmStatus:保存交易修改时需要
|
|
private void InnerSaveTrade(bool isAddNew, trade dbTrade, string changsStr, bool changeConfirmStatus = false)
|
|
{
|
|
//保存修改
|
|
DbContext.SaveChanges();
|
|
SaveTradeExend(dbTrade);
|
|
SaveTradeMargin(dbTrade);
|
|
SaveSwapPositions(dbTrade.swap_positions, dbTrade);
|
|
ClearSwapPositions(dbTrade);
|
|
//交易元数据
|
|
if (dbTrade.MetaDic.Any())
|
|
{
|
|
foreach (var kv in dbTrade.MetaDic)
|
|
{
|
|
if (isAddNew)
|
|
{
|
|
if (!string.IsNullOrEmpty(kv.Value))
|
|
{
|
|
AddTradeMeta(false, dbTrade.id, kv.Key, kv.Value);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
AddOrUpdateTradeMeta(false, dbTrade.id, kv.Key, kv.Value);
|
|
}
|
|
}
|
|
}
|
|
if (changeConfirmStatus)
|
|
{
|
|
//删除交易确认书 TODO
|
|
new TradeDocumentDataService(this).DeleteBooksAfterEditTrade(dbTrade);
|
|
}
|
|
int eventType = isAddNew ? (int)SwapEventTypeEnum.新增交易 : (int)SwapEventTypeEnum.修改交易;
|
|
new SwapEventService(UserInfo).AddSwapEventDate(dbTrade.TradeDate.Value, dbTrade.id, eventType, "", 0, true, "", true);
|
|
DbContext.SaveChanges();
|
|
}
|
|
/// <summary>
|
|
/// 添加交易元数据
|
|
/// </summary>
|
|
private void AddTradeMeta(bool saveChanges, int tradeId, string metaKey, string metaValue)
|
|
{
|
|
DbContext.TradeMeta.Add(new TradeMeta
|
|
{
|
|
TradeId = tradeId,
|
|
MetaKey = metaKey,
|
|
MetaValue = metaValue,
|
|
CreateTime = DateTime.Now
|
|
});
|
|
if (saveChanges)
|
|
{
|
|
DbContext.SaveChanges();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 添加或更新交易元数据
|
|
/// </summary>
|
|
private void AddOrUpdateTradeMeta(bool saveChanges, int tradeId, string metaKey, string metaValue)
|
|
{
|
|
new TradeMetaService(this).AddTradeMeta(tradeId, metaKey, metaValue, saveChanges);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 保存交易扩展信息
|
|
/// </summary>
|
|
/// <param name="dbTrade"></param>
|
|
private void SaveTradeExend(trade dbTrade)
|
|
{
|
|
RemoveEntities<trade_extend>(x => x.TradeId == dbTrade.id);
|
|
var trade_extend = dbTrade.trade_extend.Clone();
|
|
trade_extend.TradeId = dbTrade.id;
|
|
DbContext.trade_extend.Add(trade_extend);
|
|
}
|
|
/// <summary>
|
|
/// 保存交易预付金信息
|
|
/// </summary>
|
|
public void SaveTradeMargin(trade dbTrade)
|
|
{
|
|
RemoveEntities<trade_initial_margin>(x => x.TradeId == dbTrade.id);
|
|
var trade_Initial_Margin = dbTrade.trade_Initial_Margin.Clone();
|
|
trade_Initial_Margin.TradeId = dbTrade.id;
|
|
DbContext.trade_initial_margin.Add(trade_Initial_Margin);
|
|
}
|
|
|
|
private bool PrepareTrade(trade req, TradeSourceEnum dataSource, underlying_manager um)
|
|
{
|
|
bool tradeNumberGenerated = false;
|
|
PrepareInitialMargin(req);
|
|
var isAddNew = req.id == 0;
|
|
if (isAddNew)
|
|
{
|
|
req.TradeStatus = ConsTrade.新增待确认;
|
|
req.TradeSource = dataSource.ToString();
|
|
if (string.IsNullOrWhiteSpace(req.TradeNumber) || (PS.Config.Company == CompanyEnum.光大光子 && (req.TradeNumber == "STG" || req.TradeNumber == "STD" || req.TradeNumber == "FWD" || req.TradeNumber == "SPD")))
|
|
{
|
|
tradeNumberGenerated = true;
|
|
if (req.UnderlyingCode != null && req.UnderlyingCode != "")
|
|
{
|
|
PrepareUnderlying(req, um);
|
|
}
|
|
|
|
req.TradeNumber = BizLogicSingleton.Instance.GenerateTradeNumberBeforeConfirm(req, DbContext);
|
|
}
|
|
else if (DbContext.trade.Any(n => n.TradeNumber == req.TradeNumber))
|
|
{
|
|
throw new ServiceException($"交易编号'{req.TradeNumber}'已经存在");
|
|
}
|
|
else if (PS.Config.ErpElement.UpperTradeNumber)
|
|
{
|
|
req.TradeNumber = req.TradeNumber.ToUpperInvariant();
|
|
}
|
|
}
|
|
else if (!string.IsNullOrWhiteSpace(req.TradeNumber))
|
|
{
|
|
if (DbContext.trade.Any(n => n.id != req.id && n.TradeNumber == req.TradeNumber))
|
|
{
|
|
throw new ServiceException($"交易编号'{req.TradeNumber}'已经存在");
|
|
}
|
|
|
|
if (PS.Config.ErpElement.UpperTradeNumber)
|
|
{
|
|
req.TradeNumber = req.TradeNumber.ToUpperInvariant();
|
|
}
|
|
}
|
|
|
|
PrepareSingleTrade(req, dataSource, isAddNew, um);
|
|
req.PrincipalRateWrite = req.PrincipalRateWrite.IsNormalize() ? req.PrincipalRateWrite : null;
|
|
return tradeNumberGenerated;
|
|
|
|
}
|
|
|
|
private static void PrepareInitialMargin(trade req)
|
|
{
|
|
// 初始预付金依赖最终入库的名义本金,须先统一金额精度,避免两者无法勾稽。
|
|
req.StockEqvNotional = Math.Round(req.StockEqvNotional, ConsGlobal.MoneyRound, MidpointRounding.AwayFromZero);
|
|
req.InitialMargin = Convert.ToDouble(req.trade_Initial_Margin.MarginValue);
|
|
if (req.trade_Initial_Margin.MarginType == 0)
|
|
{
|
|
req.InitialMargin = req.StockEqvNotional == 0 ? 0 : Convert.ToDouble(req.trade_Initial_Margin.MarginValue) * req.StockEqvNotional;
|
|
}
|
|
}
|
|
//准备单个交易
|
|
private trade PrepareSingleTrade(trade req, TradeSourceEnum dataSource, bool isAddNew, underlying_manager um)
|
|
{
|
|
//准备交易基础信息
|
|
PrepareBaseInfo(req, um);
|
|
PrepareSwapTrade(req, dataSource, um);
|
|
req.OptId = UserId;
|
|
req.OptName = UserName;
|
|
req.OptDate = OptDate;
|
|
if (isAddNew)
|
|
{
|
|
req.ValidState = ConsGlobal.Valid;
|
|
req.CreateDate = DateTime.Now;
|
|
req.TradeSavedVol = PS.Config.Is厦门象屿 ? req.TradeCloseVolatility : req.TradeOpenVolatility;
|
|
if (PS.Config.ErpElement.VolMode == Configuration.VolModeEnum.VolSurface)
|
|
{
|
|
req.TradeSavedVol = req.Vol;
|
|
}
|
|
}
|
|
return req;
|
|
}
|
|
//准备互换交易
|
|
private void PrepareSwapTrade(trade req, TradeSourceEnum dataSource, underlying_manager um)
|
|
{
|
|
PrepareSwapTradeClient(req, um);
|
|
req.OriginalNotional = req.Notional;
|
|
req.BuySell = "买入";
|
|
if (req.StructureType == ClientMarginTypeEnum.多空组合.ToString())
|
|
{
|
|
req.BuySell = req.trade_extend.ExtendObj.Direction == 1 ? "买入" : "卖出";
|
|
}
|
|
else
|
|
{
|
|
var swapPosition = req.swap_positions.FirstOrDefault(x => !string.IsNullOrEmpty(x.UnderlyingCode));
|
|
req.BuySell = swapPosition.PosiDirection == 1 ? "买入" : "卖出";
|
|
var currencyRate = new EodCurrencyRateService(UserInfo).GetCurrencyRate(req.QuoteCurrency, req.SettlementCurrency, req.TradeDate.Value
|
|
, seekPreday: dataSource == TradeSourceEnum.系统交易);
|
|
var tradingFees = req.swap_positions.Where(x => !string.IsNullOrEmpty(x.UnderlyingCode)).Sum(x => x.PosiTradingFee * (x.PosiDirection == 1 ? -1 : 1));
|
|
req.TradePrice = Convert.ToDouble(tradingFees) * currencyRate;
|
|
req.SpotPrice = Convert.ToDouble(swapPosition.PosiNetPrice);
|
|
}
|
|
req.Strike = null;
|
|
req.StockEqvNotional = Math.Round(req.StockEqvNotional, ConsGlobal.MoneyRound, MidpointRounding.AwayFromZero);
|
|
req.OriginalStockEqvNotional = req.StockEqvNotional;
|
|
req.StockEqvNotionalReal = req.StockEqvNotional;
|
|
|
|
}
|
|
//互换交易
|
|
private void PrepareSwapTradeClient(trade req, underlying_manager _underlying)
|
|
{
|
|
var client = PrepareBaseTradeClient(req);
|
|
|
|
if (client.EvaluateExpireDate < req.TradeDate)
|
|
{
|
|
throw new ServiceException("适当性评估已经过期,只有在适当性有效期内才可以新开仓");
|
|
}
|
|
req.ClientName = client.Name;
|
|
req.SettlementCurrency = client.SettlementCurrency;
|
|
}
|
|
//客户信息
|
|
private Client PrepareBaseTradeClient(trade req)
|
|
{
|
|
if (req.ClientId < 1)
|
|
{
|
|
throw new ServiceException("客户信息不存在");
|
|
}
|
|
|
|
var client = DbContextFactory.GetClientDbContext(OptUser).client.FirstOrDefault(n => n.id == req.ClientId);
|
|
|
|
if (client == null)
|
|
{
|
|
throw new ServiceException("客户信息不存在");
|
|
}
|
|
|
|
if (client.ProcessStatus == "已休眠" || client.ProcessStatus == "已销户")
|
|
{
|
|
if (!client.ProcessOptDate.HasValue || client.ProcessOptDate.Value < req.TradeDate.Value)
|
|
{
|
|
var tradeDateStr = req.TradeDate.Value.ToString("yyyy-MM-dd");
|
|
var processDateStr = client.ProcessOptDate?.ToString("yyyy-MM-dd HH:mm:ss");
|
|
throw new ServiceException($"客户'{client.Name}'{client.ProcessStatus}并且处理日期{processDateStr}早于交易日期'{tradeDateStr}',无法保存交易!");
|
|
}
|
|
}
|
|
else if (client.ProcessStatus != "已开户")
|
|
{
|
|
throw new ServiceException($"客户'{client.Name}'尚未完成开户!");
|
|
}
|
|
|
|
return client;
|
|
}
|
|
//准备交易基础信息(标的、簿记、交易日期)
|
|
private void PrepareBaseInfo(trade req, underlying_manager um)
|
|
{
|
|
if (!req.TradeDate.HasValue)
|
|
{
|
|
throw new ServiceException("交易日期 必须填写");
|
|
}
|
|
|
|
if (req.TradeDate.Value.Year < 2000)
|
|
{
|
|
throw new ServiceException("交易日期 填写错误:" + req.TradeDate.Value.ToString("yyyy-MM-dd"));
|
|
}
|
|
|
|
if (QdpCalendarHelper.IsHoliday(req.TradeDate.Value) && req.TradeType != "现金流交易")
|
|
{
|
|
throw new ServiceException("交易日期不能是假日:" + req.TradeDate.Value.ToString("yyyy-MM-dd"));
|
|
}
|
|
|
|
//如果起始日没有值,默认赋值交易日
|
|
if (!req.StartDate.HasValue)
|
|
{
|
|
req.StartDate = req.TradeDate;
|
|
}
|
|
|
|
//--------------------------------------
|
|
// 标的信息
|
|
//--------------------------------------
|
|
if (req.StructureType != ClientMarginTypeEnum.多空组合.ToString())
|
|
{
|
|
PrepareUnderlying(req, um);
|
|
}
|
|
|
|
|
|
//--------------------------------------
|
|
// 簿记帐户
|
|
//--------------------------------------
|
|
if (req.AssetId < 1)
|
|
{
|
|
throw new ServiceException("请选择簿记账户");
|
|
}
|
|
|
|
var _assetUnit = DbContext.assetunit.Where(n => n.id == req.AssetId)
|
|
.Select(n => new AssetUnitDto { id = n.id, Name = n.Name, TraderIds = n.TraderIds, UserGroup = n.UserGroup }).FirstOrDefault();
|
|
if (_assetUnit == null)
|
|
{
|
|
throw new ServiceException("簿记账户不存在");
|
|
}
|
|
|
|
req.AssetBookName = _assetUnit.Name;
|
|
|
|
//--------------------------------------
|
|
// 交易员信息(如果没有交易员,查找簿记账户中的交易员)
|
|
//--------------------------------------
|
|
if (string.IsNullOrWhiteSpace(req.TraderName))
|
|
{
|
|
//有交易员id,通过id查名字
|
|
if (req.TraderId > 0)
|
|
{
|
|
req.TraderName = UserBLL.GetNameById(req.TraderId);
|
|
}
|
|
else
|
|
{
|
|
//当前登录用户作为交易员
|
|
req.TraderId = UserId;
|
|
req.TraderName = UserName;
|
|
}
|
|
}
|
|
req.TradeAmount = Convert.ToDouble(req.swap_positions.Sum(s => s.PosiQuantity));
|
|
}
|
|
//准备交易标的信息
|
|
public underlying_manager PrepareUnderlying(trade req, underlying_manager _underlying)
|
|
{
|
|
var underlyingCode = req.UnderlyingCode ?? string.Empty;
|
|
|
|
if (_underlying == null)
|
|
{
|
|
_underlying = UnderlyingDataProvider.GetUnderlying(underlyingCode);
|
|
}
|
|
|
|
if (_underlying == null)
|
|
{
|
|
throw new ServiceException("标的信息不存在:" + req.UnderlyingCode);
|
|
}
|
|
|
|
if (_underlying.IsFutures() && _underlying.MaturityDate == null)
|
|
{
|
|
throw new ServiceException("标的到期日不存在:" + req.UnderlyingCode);
|
|
}
|
|
req.UnderlyingId = _underlying.id;
|
|
req.UnderlyingCode = _underlying.UnderlyingCode;
|
|
req.VarietyId = _underlying.UnderlyingTypeId;
|
|
req.UnderlyingAssetClass = _underlying.UnderlyingType;
|
|
req.UnderlyingAssetName = _underlying.UnderlyingName;
|
|
req.MaturityDate = _underlying.IsFutures() ? _underlying.MaturityDate : null;
|
|
req.UnderlyingInstrumentType = _underlying.GetMainType();
|
|
req.CountRatio = _underlying.CountRatio;
|
|
|
|
//权益类标的关联的交易如果没有设置分红率则取标的上的分红率,如果标的分红率为空则取0
|
|
if (!req.DividendRate.HasValue && _underlying.CalcTypeIsStock())
|
|
{
|
|
req.DividendRate = _underlying.DividendRate ?? 0;
|
|
}
|
|
|
|
return _underlying;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 收益互换初始预付金校验
|
|
/// </summary>
|
|
/// <param name="trade"></param>
|
|
/// <returns></returns>
|
|
private string checkMarginRate(trade trade)
|
|
{
|
|
var errorMsg = "";
|
|
var marginValue = trade.trade_Initial_Margin.MarginValue;
|
|
if (trade.trade_Initial_Margin.MarginType == (int)TradeMarginTypeEnum.固定金额)
|
|
{
|
|
marginValue = trade.StockEqvNotional == 0 ? 0 : trade.trade_Initial_Margin.MarginValue / Convert.ToDecimal(trade.StockEqvNotional);
|
|
}
|
|
//多空组合
|
|
if (ClientMarginTypeEnum.多空组合.ToString().Equals(trade.StructureType))
|
|
{
|
|
if (marginValue < 0.25m)
|
|
{
|
|
errorMsg = $"根据监管要求权益类多空组合型收益互换预付金不得低于25%,当前交易预付金率为:{marginValue * 100}%,不符合监管要求!";
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//获取标的品种
|
|
var underlingData = new UnderlyingDataProvider();
|
|
var swapposition = trade.swap_positions.FirstOrDefault(x => !string.IsNullOrEmpty(x.UnderlyingCode));
|
|
trade.UnderlyingCode = swapposition == null ? "" : swapposition.UnderlyingCode;
|
|
var underlying = underlingData.GetUnderlying(trade.UnderlyingCode);
|
|
var variety = underlingData.GetVariety(underlying.UnderlyingTypeId);
|
|
|
|
if (variety == null)
|
|
{
|
|
errorMsg = $"所设置标的未对应品种,请设置对应品种后再进行交易!";
|
|
}
|
|
else
|
|
{
|
|
//股票、窄基股票指数及其产品、信用债 初始预付金比率 必须大于等于100%
|
|
if ((variety.AssetType == "个股" || (variety.AssetType == "指数" && "窄基指数".Equals(variety.IndexType))) && marginValue < 1)
|
|
{
|
|
errorMsg = $"所交易标的为股票或窄基指数及其产品、信用债,初始预付金比例不得低于100%!,当前交易预付金率为:{marginValue * 100}%,不符合监管要求!";
|
|
}
|
|
else if ((variety.AssetType == "指数" && "宽基指数".Equals(variety.IndexType)) && Convert.ToDecimal(marginValue) < 0.5m)
|
|
{
|
|
//挂钩宽基股票指数及其产品,无对应期货品种的 向单一交易对手方收取的预付金比例不得低于50%
|
|
errorMsg = $"所交易标的挂钩宽基股票指数及其产品,无对应期货品种的,初始预付金比例不得低于50%!,当前交易预付金率为:{marginValue * 100}%,不符合监管要求!";
|
|
}
|
|
else if (marginValue < Convert.ToDecimal(variety.Margin ?? 0.0))
|
|
{
|
|
//有对应期货或集中交易品种的 不得低于品种设置的最低预付金比例
|
|
errorMsg = $"所交易标的对应期货或集中交易品种的,初始预付金比例不得低于品种[{variety.VarietyName}]所设置最低预付金比例{variety.Margin * 100}%!,当前交易预付金率为:{marginValue * 100}%,不符合监管要求!";
|
|
}
|
|
}
|
|
}
|
|
return errorMsg;
|
|
}
|
|
/// <summary>
|
|
/// 标的校验
|
|
/// </summary>
|
|
/// <param name="trade"></param>
|
|
/// <exception cref="ServiceException"></exception>
|
|
private underlying_manager checkUnderlying(trade trade)
|
|
{
|
|
if (trade.StructureType == ClientMarginTypeEnum.多空组合.ToString())
|
|
{
|
|
return null;
|
|
}
|
|
var underlingData = new UnderlyingDataProvider();
|
|
var posi = trade.swap_positions.Where(x => x.PosiDirection > 0).FirstOrDefault();
|
|
if (posi == null)
|
|
{
|
|
throw new ServiceException("缺少标的信息");
|
|
}
|
|
var underlying = underlingData.GetUnderlying(posi.UnderlyingCode);
|
|
// 标的代码
|
|
if (underlying == null)
|
|
{
|
|
throw new ServiceException("标的代码 必须在系统中存在");
|
|
}
|
|
if (underlying.UnderlyingInstrumentType == "Stock" && !underlying.IsCombined())
|
|
{
|
|
var limit = LimitRangeEnum.Swap;
|
|
var limitStatus = new StockBlackWhiteService(UserInfo).GetStockBlackWhiteList(limit, out var Codes);
|
|
if (Codes != null)
|
|
{
|
|
if (limitStatus == Codes.Contains(underlying.UnderlyingCode))
|
|
{
|
|
if (limitStatus)
|
|
{
|
|
throw new ServiceException("标的代码 填写错误,存在于黑名单中,请求值:" + underlying.UnderlyingCode);
|
|
}
|
|
else
|
|
{
|
|
throw new ServiceException("标的代码 填写错误,不存在于白名单中,请求值:" + underlying.UnderlyingCode);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
trade.UnderlyingCode = underlying.UnderlyingCode;
|
|
trade.UnderlyingId = underlying.id;
|
|
trade.UnderlyingAssetClass = underlying.UnderlyingType;
|
|
trade.UnderlyingInstrumentType = underlying.UnderlyingInstrumentType;
|
|
trade.UnderlyingAssetName = underlying.UnderlyingName;
|
|
return underlying;
|
|
}
|
|
#endregion
|
|
#region 互换交易查询
|
|
/// <summary>
|
|
/// 互换交易列表查询
|
|
/// </summary>
|
|
/// <param name="req"></param>
|
|
/// <param name="gsum"></param>
|
|
/// <returns></returns>
|
|
public SearchListResult<trade> SearchList(SwapTradeQueryRequest req, out tradeGridSum gsum)
|
|
{
|
|
var query = CreateTradeQuery(req);
|
|
var retListResult = new SearchListResult<trade>();
|
|
if (req.ValueDate != null)
|
|
{
|
|
var dateStr = req.ValueDate?.ToString("yyyy-MM-dd");
|
|
var _query = from t in query
|
|
join sp in DbContext.swap_position on t.id equals sp.SwapTradeId
|
|
where sp.InterestSwapInterval.Contains(dateStr) && !sp.Invalid
|
|
select t;
|
|
retListResult = _query.ToSearchList(req);
|
|
}
|
|
else
|
|
{
|
|
retListResult = query.ToSearchList(req);
|
|
}
|
|
gsum = new tradeGridSum();
|
|
if (query.Any())
|
|
{
|
|
gsum.TradePriceSum = query.Sum(q => q.TradePrice);
|
|
}
|
|
var tradeids = retListResult.rows.Select(r => r.id).ToList();
|
|
var list = DbContext.trade_contract_r.Where(O => tradeids.Contains(O.TradeId) && O.IsValid && O.Type == "交易确认书").AsEnumerable();
|
|
var contractCodeDict = list.GroupBy(O => O.TradeId).ToDictionary(K => K.Key, V => V.LastOrDefault().ContractCode);
|
|
//获取销售提成信息
|
|
foreach (var item in retListResult.rows)
|
|
{
|
|
item.ContractCode = contractCodeDict.TryGetValue(item.id, out var code) ? code : "";
|
|
item.SalesCommission = new SalesModule.SalesCommissionDataService(OptUser).GetTradeCommissionInfo(item.id);
|
|
}
|
|
retListResult.Sum = gsum;
|
|
return retListResult;
|
|
}
|
|
|
|
private Expression<Func<trade, bool>> BuildTradeQuery(TradeReq req)
|
|
{
|
|
var predicate = PredicateBuilder.Create<trade>(n => n.ValidState != "InValid" && n.TradeType == "收益互换");
|
|
if (req.UserAssets != null && req.UserClients != null)
|
|
{
|
|
predicate = predicate.And(n => req.UserAssets.Contains(n.AssetId) || req.UserClients.Contains(n.ClientId));
|
|
}
|
|
if (req.AssetIdList.Any())
|
|
{
|
|
predicate = predicate.And(d => req.AssetIdList.Contains(d.AssetId));
|
|
}
|
|
if (!string.IsNullOrEmpty(req.TraderNames))
|
|
{
|
|
predicate = predicate.And(d => req.TraderNamesList.Contains(d.TraderId));
|
|
}
|
|
|
|
if (!string.IsNullOrEmpty(req.TradeNumber))
|
|
{
|
|
predicate = predicate.And(d => d.TradeNumber.Contains(req.TradeNumber));
|
|
}
|
|
if (req.TradeStatusList != null)
|
|
{
|
|
predicate = predicate.And(d => req.TradeStatusList.Contains(d.TradeStatus));
|
|
}
|
|
|
|
if (req.AssetIdList.Any())
|
|
{
|
|
predicate = predicate.And(d => req.AssetIdList.Contains(d.AssetId));
|
|
}
|
|
|
|
if (req.ClientIdsInt.Any())
|
|
{
|
|
predicate = predicate.And(d => req.ClientIdsInt.Contains(d.ClientId));
|
|
}
|
|
if (req.StartDateStart != DateTime.MinValue)
|
|
{
|
|
predicate = predicate.And(d => d.StartDate >= req.StartDateStart);
|
|
}
|
|
|
|
if (req.StartDateEnd != DateTime.MinValue)
|
|
{
|
|
var StartDateTemp = req.StartDateEnd.AddDays(1);
|
|
predicate = predicate.And(d => d.StartDate < StartDateTemp);
|
|
}
|
|
if (req.ExerciseDateEnd == null)
|
|
{
|
|
req.ExerciseDateEnd = DateTime.MaxValue;
|
|
}
|
|
|
|
if (req.ExerciseDateStart == null)
|
|
{
|
|
req.ExerciseDateStart = DateTime.MinValue;
|
|
}
|
|
predicate = predicate.And(d => d.ExerciseDate >= req.ExerciseDateStart && d.ExerciseDate <= req.ExerciseDateEnd);
|
|
return predicate;
|
|
}
|
|
/// <summary>
|
|
/// 获取互换交易详情
|
|
/// </summary>
|
|
/// <param name="intid"></param>
|
|
/// <returns></returns>
|
|
public trade GetSwapTrade(int intid)
|
|
{
|
|
var tradeObj = DbContext.trade.Find(intid);
|
|
if (tradeObj == null)
|
|
{
|
|
return null;
|
|
}
|
|
//需要审批或者复核的交易都会显示行权审核提交按钮
|
|
tradeObj.MetaDic = new TradeMetaService(UserInfo).GetTradeMeta(tradeObj.id);
|
|
tradeObj.trade_Initial_Margin = DbContext.trade_initial_margin.FirstOrDefault(x => x.TradeId == intid);
|
|
if (tradeObj.trade_Initial_Margin == null)
|
|
{
|
|
tradeObj.trade_Initial_Margin = new trade_initial_margin();
|
|
}
|
|
tradeObj.trade_extend = DbContext.trade_extend.FirstOrDefault(x => x.TradeId == intid);
|
|
tradeObj.swap_positions = DbContext.swap_position.Where(x => x.SwapTradeId == intid && !x.Invalid).ToList();
|
|
tradeObj.swap_positions = tradeObj.swap_positions.Where(x => x.PosiQuantity > 0 || x.InterestDirection > 0).ToList();
|
|
var intervalPositions = tradeObj.swap_positions.Where(x => string.IsNullOrEmpty(x.UnderlyingCode) && x.IsInitial).ToList();
|
|
var intervalPositionIds = intervalPositions.Select(s => s.id).ToList();
|
|
var tradeObervations = DbContext.trade_obervation.Where(x => intervalPositionIds.Contains(x.PositionId));
|
|
foreach (var position in intervalPositions)
|
|
{
|
|
position.Obervation = tradeObervations.FirstOrDefault(s => s.PositionId == position.id);
|
|
}
|
|
var positionIds = tradeObj.swap_positions.Select(x => x.id);
|
|
tradeObj.ClientCashInCashOutList = DbContext.ClientCashInCashOut.Where(x => x.ValidState != ConsGlobal.InValid && x.TradeId == intid).OrderBy(o => o.HappenDate).ToList();
|
|
List<int> eventTyps = new List<int>() { (int)SwapEventTypeEnum.平仓, (int)SwapEventTypeEnum.互换, (int)SwapEventTypeEnum.自动互换 };
|
|
var allSwapEvents = GetSwapEvents(intid, eventTyps).OrderBy(o => o.id).ToList();//历史平仓/互换记录
|
|
tradeObj.swap_Events.AddRange(allSwapEvents);
|
|
if (tradeObj.swap_Events.Count > 0)
|
|
{
|
|
tradeObj.eod_swaps = DbContext.eod_swap.Where(x => x.SwapTradeId == intid).ToList();
|
|
tradeObj.eod_swaps.ForEach(x =>
|
|
{
|
|
var eod = tradeObj.eod_swaps.Where(t => t.ValueDate < x.ValueDate && t.id != x.id).OrderBy(o => o.ValueDate).FirstOrDefault();
|
|
if (eod != null)
|
|
{
|
|
x.CloseStartDate = eod.ValueDate;
|
|
}
|
|
else
|
|
{
|
|
x.CloseStartDate = x.ValueDate;
|
|
}
|
|
});
|
|
}
|
|
|
|
foreach (var item in tradeObj.swap_positions.Where(x => x.IsInitial))
|
|
{
|
|
if (!string.IsNullOrEmpty(item.UnderlyingCode))
|
|
{
|
|
item.underlying = DataCacheProvider.GetUnderlyingDataSource().GetData(item.UnderlyingCode);
|
|
if (item.underlying != null && item.underlying.IsBond() && !string.IsNullOrEmpty(item.underlying.ExJson))
|
|
{
|
|
var bond = JsonHelper.Deserialize<UnderlyingBond>(item.underlying.ExJson);
|
|
item.underlying.UnderlyingFullName = bond.UnderlyingFullName;
|
|
item.underlying.UnderlyingIssuer = bond.UnderlyingIssuer;
|
|
item.underlying.IssueSize = bond.IssueSize;
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
|
|
return tradeObj;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 撤回审批
|
|
/// </summary>
|
|
/// <param name="intid"></param>
|
|
public void TradeRevoke(int intid)
|
|
{
|
|
var tradeObj = DbContext.trade.Find(intid);
|
|
if (tradeObj == null)
|
|
{
|
|
return;
|
|
}
|
|
var eventTypes = new List<int>() { (int)SwapEventTypeEnum.修改交易, (int)SwapEventTypeEnum.新增交易, (int)SwapEventTypeEnum.平仓, (int)SwapEventTypeEnum.互换 };
|
|
var lastEvent = DbContext.swap_event.Where(x => x.SwapTradeId == intid && eventTypes.Contains(x.EventType) && !x.Invalid).OrderByDescending(o => o.OptTime).FirstOrDefault();
|
|
if (tradeObj.TradeStatus == ConsTrade.平仓待复核 || tradeObj.TradeStatus == ConsTrade.互换待复核)
|
|
{
|
|
tradeObj.TradeStatus = ConsTrade.确认成交;
|
|
}
|
|
else
|
|
{
|
|
if (lastEvent != null && lastEvent.EventType == (int)SwapEventTypeEnum.修改交易)
|
|
{
|
|
tradeObj.TradeStatus = ConsTrade.修改待确认;
|
|
}
|
|
else
|
|
{
|
|
tradeObj.TradeStatus = ConsTrade.新增待确认;
|
|
}
|
|
}
|
|
tradeObj.CheckStatus = null;
|
|
tradeObj.ProcessOrderId = 0;
|
|
tradeObj.IsApproval = false;
|
|
if (lastEvent != null)
|
|
{
|
|
lastEvent.Invalid = true;
|
|
}
|
|
DbContext.SaveChanges();
|
|
return;
|
|
}
|
|
private IQueryable<trade> CreateTradeQuery(SwapTradeQueryRequest req)
|
|
{
|
|
var predicate = PredicateBuilder.Create<trade>(n => n.ValidState != "InValid" && n.TradeType == "收益互换");
|
|
var query = DbContext.trade.Where(predicate);
|
|
|
|
if (req.ClientIds != null && req.ClientIds.Count > 0)
|
|
{
|
|
query = query.Where(d => req.ClientIds.Contains(d.ClientId));
|
|
}
|
|
if (req.TradeIds != null && req.TradeIds.Count > 0)
|
|
{
|
|
query = query.Where(d => req.TradeIds.Contains(d.TraderId));
|
|
}
|
|
|
|
if (req.AssetIds != null && req.AssetIds.Count > 0)
|
|
{
|
|
query = query.Where(d => req.AssetIds.Contains(d.AssetId));
|
|
}
|
|
|
|
if (req.TradeDateStart.HasValue)
|
|
{
|
|
query = query.Where(d => d.TradeDate >= req.TradeDateStart);
|
|
}
|
|
|
|
if (req.TradeDateEnd.HasValue)
|
|
{
|
|
var TradeDateTemp = req.TradeDateEnd.Value.AddDays(1);
|
|
query = query.Where(d => d.TradeDate < TradeDateTemp);
|
|
}
|
|
if (req.ExerciseDateEnd.HasValue)
|
|
{
|
|
query = query.Where(d => d.ExerciseDate <= req.ExerciseDateEnd);
|
|
}
|
|
|
|
if (req.ExerciseDateStart.HasValue)
|
|
{
|
|
query = query.Where(d => d.ExerciseDate >= req.ExerciseDateStart);
|
|
}
|
|
if (!string.IsNullOrEmpty(req.TradeNumber))
|
|
{
|
|
query = query.Where(d => d.TradeNumber.Contains(req.TradeNumber));
|
|
}
|
|
|
|
if (req.TradeStatus != null && req.TradeStatus.Count > 0)
|
|
{
|
|
query = query.Where(d => req.TradeStatus.Contains(d.TradeStatus));
|
|
}
|
|
if (string.IsNullOrEmpty(req.sidx))
|
|
{
|
|
req.sidx = "id";
|
|
req.sord = "desc";
|
|
};
|
|
query = query.OrderByDescending(s => s.OptDate);
|
|
|
|
return query;
|
|
}
|
|
|
|
#endregion
|
|
/// <summary>
|
|
/// 获取互换交易对手方 交易编码
|
|
/// </summary>
|
|
/// <param name="clientId"></param>
|
|
/// <returns></returns>
|
|
public List<SelectItem> GetSwapTradeNumbers(int clientId)
|
|
{
|
|
var tradeNumbers = DbContext.trade.Where(t => t.ClientId == clientId
|
|
&& t.TradeType == "收益互换"
|
|
&& t.TradeStatus == ConsTrade.确认成交
|
|
&& t.StructureType == ClientMarginTypeEnum.多空组合.ToString()
|
|
&& t.ValidState != "InValid").Select(s => new SelectItem { Text = s.TradeNumber, Value = s.id.ToString() }).ToList();
|
|
return tradeNumbers;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 保存交易腿信息
|
|
/// </summary>
|
|
/// <param name="swap_positions">持仓腿信息</param>
|
|
/// <param name="tradeId">交易id</param>
|
|
/// <param name="startDate">成交日期</param>
|
|
public void SaveSwapPositions(List<swap_position> swap_positions, trade td)
|
|
{
|
|
var swapPositionList = DbContext.swap_position.Where(x => x.SwapTradeId == td.id);
|
|
var swapPositionIds = swap_positions.Select(s => s.id).ToList();
|
|
var intervalPositionIds = swapPositionList.Where(x => string.IsNullOrEmpty(x.UnderlyingCode) && x.IsInitial).Select(s => s.id);
|
|
var obervations = DbContext.trade_obervation.Where(o => intervalPositionIds.Contains(o.PositionId));
|
|
var originalSwapPositions = swapPositionList.Where(x => x.IsInitial);
|
|
var realTimeSwapPositions = swapPositionList.Where(x => !x.IsInitial);
|
|
var deleteSwapPositions = swapPositionList.Where(x => !swapPositionIds.Contains(x.id));
|
|
if (td.StructureType == "多空组合")
|
|
{
|
|
deleteSwapPositions = swapPositionList.Where(x => !swapPositionIds.Contains(x.id) && x.PosiDirection == 0);
|
|
realTimeSwapPositions = swapPositionList.Where(x => !swapPositionIds.Contains(x.id) && x.PosiDirection == 0 && !x.IsInitial);
|
|
var positions = swapPositionList.Where(x => x.PosiDirection > 0);
|
|
var direction = td.trade_extend.ExtendObj.Direction;
|
|
foreach (var item in positions)
|
|
{
|
|
item.PosiDirection = direction;
|
|
}
|
|
}
|
|
DbContext.swap_position.RemoveRange(realTimeSwapPositions);
|
|
DbContext.swap_position.RemoveRange(deleteSwapPositions);
|
|
DbContext.trade_obervation.RemoveRange(obervations);
|
|
foreach (var swap in swap_positions)
|
|
{
|
|
var position = originalSwapPositions.FirstOrDefault(x => x.id == swap.id);
|
|
if (position == null)
|
|
{
|
|
position = new swap_position();
|
|
}
|
|
position.ContractSize = swap.ContractSize;
|
|
position.CountRatio = swap.CountRatio;
|
|
var ratio = swap.PositionType == (int)PositionTypeFlag.Short ? -1 : 1;
|
|
position.id = swap.id;
|
|
position.PositionType = swap.PositionType;
|
|
position.PosiTradingFee = swap.PosiTradingFee;
|
|
position.PosiTradingFee=Math.Round(position.PosiTradingFee, ConsGlobal.MoneyRound, MidpointRounding.AwayFromZero);
|
|
position.PosiTradingFeeUnit = swap.PosiTradingFeeUnit;
|
|
position.PosiFeeType = swap.PosiFeeType;
|
|
position.PosiTradingFeePending = swap.PosiTradingFeePending;
|
|
position.PosiTradingFeePending = Math.Round(position.PosiTradingFeePending, ConsGlobal.MoneyRound, MidpointRounding.AwayFromZero);
|
|
position.UnderlyingCode = swap.UnderlyingCode;
|
|
position.UnderlyingInstrumentType = swap.UnderlyingInstrumentType;
|
|
position.PosiDirection = swap.PosiDirection;
|
|
position.PosiGrossPrice = swap.PosiGrossPrice;
|
|
position.PosiNetPrice = swap.PosiQuantity == 0 ? 0 : (position.PosiGrossPrice + (position.PosiTradingFeePending / swap.PosiQuantity) * ratio);
|
|
position.PosiNetPrice = Math.Round(position.PosiNetPrice, ConsGlobal.PriceRound, MidpointRounding.AwayFromZero);
|
|
position.PosiNetNoFeePrice = swap.PosiNetNoFeePrice;
|
|
position.PosiNetFeePrice = swap.PosiQuantity == 0 ? 0 : (swap.PosiNetNoFeePrice + (position.PosiTradingFeePending / swap.PosiQuantity) * ratio);
|
|
position.PosiNetFeePrice = Math.Round(position.PosiNetFeePrice??0, ConsGlobal.PriceRound, MidpointRounding.AwayFromZero);
|
|
position.PosiNotionalValue = Math.Round(swap.PosiNotionalValue, ConsGlobal.MoneyRound, MidpointRounding.AwayFromZero);
|
|
position.PosiQuantity = swap.PosiQuantity;
|
|
position.InterestDirection = swap.InterestDirection;
|
|
position.InterestMode = swap.InterestMode;
|
|
position.InterestRateDefault = swap.InterestRateDefault;
|
|
position.InterestPrincipalFix = swap.InterestPrincipalFix;
|
|
position.InterestSwapInterval = swap.InterestSwapInterval;
|
|
position.PosiStartDate = td.StartDate.Value;
|
|
position.PosiMatuirityDate = td.ExerciseDate.Value;
|
|
position.SwapTradeId = td.id;
|
|
position.IsInitial = true;
|
|
position.OptTime = DateTime.Now;
|
|
position.OptId = UserInfo.UserId;
|
|
position.OptName = UserInfo.UserName;
|
|
position.IsAnnualized = swap.IsAnnualized;
|
|
position.HappenDate = swap.HappenDate;
|
|
position.Currency = swap.Currency;
|
|
position.InterestType = swap.InterestType;
|
|
position.FloatRate = swap.FloatRate;
|
|
position.FloatRateUnderlyingCode = swap.FloatRateUnderlyingCode;
|
|
position.interest_rest_days = swap.interest_rest_days;
|
|
position.interest_rule = swap.interest_rule;
|
|
position.category_tag = string.IsNullOrEmpty(swap.category_tag) ? "互换利率" : swap.category_tag;
|
|
position.InitYtm = swap.InitYtm;
|
|
if (position.InitYtm != null && position.InitYtm > 0)
|
|
{
|
|
td.InitYtm = position.InitYtm;
|
|
}
|
|
|
|
if (position.id == 0)
|
|
{
|
|
DbContext.swap_position.Add(position);
|
|
DbContext.SaveChanges();
|
|
position.PosiNumber = $"{td.TradeNumber}-{position.id}";
|
|
}
|
|
if (swap.Obervation != null)
|
|
{
|
|
swap.Obervation.OptTime = DateTime.Now;
|
|
swap.Obervation.OptId = UserInfo.UserId;
|
|
swap.Obervation.OptName = UserInfo.UserName;
|
|
swap.Obervation.PositionId = position.id;
|
|
DbContext.trade_obervation.Add(swap.Obervation);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 修改清除互换持仓所有信息
|
|
/// </summary>
|
|
/// <param name="tradeId"></param>
|
|
public void ClearSwapPositions(trade td)
|
|
{
|
|
var tradeId = td.id;
|
|
var swapFlowEvents = DbContext.swap_flow_event.Where(x => x.SwapTradeId == tradeId && x.DataState != (int)SwapFlowDateStateEnum.废弃);
|
|
var eodSwapPositions = DbContext.eod_swap_position.Where(x => x.SwapTradeId == tradeId);
|
|
var eodSwaps = DbContext.eod_swap.Where(x => x.SwapTradeId == tradeId);
|
|
var clientcashinouts = DbContext.ClientCashInCashOut.Where(x => x.TradeId == tradeId && x.Action == ClientCashInCashOut.系统操作_应付预付金);
|
|
swapFlowEvents.ForEach(x =>
|
|
{
|
|
x.DataState = (int)SwapFlowDateStateEnum.废弃;
|
|
});
|
|
//DbContext.swap_flow_event.RemoveRange(swapFlowEvents);
|
|
DbContext.eod_swap_position.RemoveRange(eodSwapPositions);
|
|
DbContext.eod_swap.RemoveRange(eodSwaps);
|
|
DbContext.ClientCashInCashOut.RemoveRange(clientcashinouts);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 修改展期信息,互换观察日到期日期修改
|
|
/// </summary>
|
|
/// <param name="tradeId">互换交易id</param>
|
|
/// <param name="oldExerciseDate">旧到期日</param>
|
|
/// <param name="exerciseDate">新到期日</param>
|
|
public void UpdateObservationDay(int tradeId, DateTime oldExerciseDate, DateTime exerciseDate)
|
|
{
|
|
var dateStr = exerciseDate.ToString("yyyy-MM-dd");
|
|
var oldDateStr = oldExerciseDate.ToString("yyyy-MM-dd");
|
|
var interestPostions = DbContext.swap_position.Where(x => x.SwapTradeId == tradeId && string.IsNullOrEmpty(x.UnderlyingCode) && !x.Invalid).ToList();
|
|
interestPostions.ForEach(x =>
|
|
{
|
|
if (x.SwapIntervalList.Count == 1 && x.SwapIntervalList.Any(t => t.Date == oldExerciseDate))
|
|
{
|
|
x.InterestSwapInterval = x.InterestSwapInterval.Replace(oldDateStr, dateStr);
|
|
}
|
|
});
|
|
}
|
|
/// <summary>
|
|
/// 交易回退
|
|
/// </summary>
|
|
/// <param name="tradeId"></param>
|
|
/// <returns></returns>
|
|
public trade GetTrade(int tradeId)
|
|
{
|
|
var td = DbContext.trade.Find(tradeId);
|
|
return td;
|
|
}
|
|
/// <summary>
|
|
/// 互换交易回退
|
|
/// </summary>
|
|
/// <param name="tradeId">交易id</param>
|
|
/// <param name="valueDate">回退日期</param>
|
|
/// <param name="backToBegin">回到开仓</param>
|
|
public void TradeBack(int tradeId, DateTime valueDate, bool del = false)
|
|
{
|
|
|
|
var td = DbContext.trade.Find(tradeId);
|
|
if (td == null)
|
|
{
|
|
throw new ServiceException("交易不存在");
|
|
}
|
|
bool backToBegin = td.TradeDate == valueDate;
|
|
var swapPositions = DbContext.swap_position.Where(x => x.SwapTradeId == tradeId && !x.Invalid).ToList();
|
|
td.trade_extend = DbContext.trade_extend.FirstOrDefault(x => x.TradeId == td.id);
|
|
|
|
//展期
|
|
var swapEvent = DbContext.swap_event.Where(x => x.SwapTradeId == tradeId && x.EventType == (int)SwapEventTypeEnum.展期 && !x.Invalid && x.ValueDate <= valueDate).OrderByDescending(o => o.ValueDate).FirstOrDefault();
|
|
var trans = DbContext.Database.BeginTransaction();
|
|
try
|
|
{
|
|
var swapEventService = new SwapEventService(this);
|
|
td.TradeStatus = "确认成交";
|
|
td.ProcessOrderId = 0;
|
|
td.ProcessStatus = null;
|
|
if (backToBegin)
|
|
{
|
|
td.StockEqvNotional = Math.Round(td.OriginalStockEqvNotional ?? 0, ConsGlobal.MoneyRound, MidpointRounding.AwayFromZero);
|
|
td.UnWindDate = null;
|
|
td.HasPartialUnWind = null;
|
|
SingleTradeBackToBegin(td, swapPositions);
|
|
}
|
|
else
|
|
{
|
|
TradeBackByDate(td, valueDate, swapPositions);
|
|
}
|
|
if (swapEvent != null)//展期
|
|
{
|
|
swapEventService.DeleteExtensionTime(swapEvent.id);
|
|
}
|
|
InvalidTradeOptionDatasByDate(tradeId, valueDate, backToBegin);
|
|
swapEventService.AddSwapEventDate(valueDate, tradeId, (int)SwapEventTypeEnum.回退, string.Empty, 0, false, $"交易回退至{valueDate:yyyy年MM月dd日}");
|
|
DbContext.SaveChanges();
|
|
if (del)
|
|
{
|
|
new TradeRiskCheckLogService(UserInfo).DeleteLog(td.TradeNumber, valueDate);//重置风控记录
|
|
}
|
|
if (backToBegin) //重置开仓记录
|
|
{
|
|
DeleteTradeCashInCashOut(tradeId);
|
|
DeleteTradeDetials(tradeId);
|
|
DbContext.SaveChanges();
|
|
if (del)
|
|
{
|
|
swapPositions.ForEach(x =>
|
|
{
|
|
x.Invalid = true;
|
|
x.OptTime = OptDate;
|
|
});
|
|
td.ValidState = "InValid";
|
|
var tradeNumber = td.TradeNumber;
|
|
td.TradeNumber = td.TradeNumber.Insert(0, "XX");
|
|
td.OptDate = OptDate;
|
|
swapEventService.AddSwapEventDate(valueDate, tradeId, (int)SwapEventTypeEnum.删除, string.Empty, 0, false);
|
|
DbContext.SaveChanges();
|
|
new TradeDocumentDataService(this).InnerDeleteConfirmBook(tradeId);
|
|
trans.Commit();
|
|
return;
|
|
}
|
|
//new SwapEodPositionService(this).InitSaveEodSwapPosition(td);
|
|
var happenDate = td.TradeDate.Value;
|
|
if (td.trade_extend != null && !td.trade_extend.ExtendObj.NeedOpenFee)
|
|
{
|
|
happenDate = td.ExerciseDate.Value;
|
|
}
|
|
ResetMarginAmount(td, happenDate, swapPositions);
|
|
}
|
|
//new SwapFlowService(this).DeleteSwapFlow(tradeId, valueDate, backToBegin);
|
|
trans.Commit();
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
trans.Rollback();
|
|
throw ex;
|
|
}
|
|
finally
|
|
{
|
|
trans.Dispose();
|
|
}
|
|
|
|
}
|
|
/// <summary>
|
|
/// 重新补录预付金记录
|
|
/// </summary>
|
|
/// <param name="td"></param>
|
|
/// <param name="happenDate"></param>
|
|
/// <param name="swapPositions"></param>
|
|
private void ResetMarginAmount(trade td, DateTime happenDate, List<swap_position> swapPositions)
|
|
{
|
|
new ClientCashInCashOutService(this).SaveSwapTradeClientCash(td, td.TradePrice ?? 0, happenDate, 0);
|
|
foreach (var marginPositions in swapPositions.Where(x => x.HappenDate != null && x.IsInitial && x.InterestMode != (int)InterestModeEnum.追加预付金).GroupBy(g => g.HappenDate))
|
|
{
|
|
var marginHappenDate = marginPositions.Key;
|
|
foreach (var marginPosition in marginPositions)
|
|
{
|
|
marginPosition.Obervation = DbContext.trade_obervation.FirstOrDefault(x => x.PositionId == marginPosition.id);
|
|
bool generateMargin = true;
|
|
if (marginPosition.Obervation != null)
|
|
{
|
|
if (!marginPosition.Obervation.IsDeductPrincipal)
|
|
{
|
|
generateMargin = false;
|
|
}
|
|
}
|
|
if (generateMargin)
|
|
{
|
|
var marginAmount = marginPosition.InterestPrincipalFix * (marginPosition.InterestDirection == 1 ? -1 : 1);
|
|
new ClientCashInCashOutService(this).SaveSwapTradeClientCash(td, Convert.ToDouble(marginAmount), marginHappenDate.HasValue ? marginHappenDate.Value : td.TradeDate.Value, marginPosition.id, ClientCashInCashOut.系统操作_应付预付金);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
/// <summary>
|
|
/// 获取持仓腿信息
|
|
/// </summary>
|
|
/// <param name="tradeIds">叫一起集合</param>
|
|
/// <param name="_underlyingCodeFilterSet">标的代码</param>
|
|
/// <returns></returns>
|
|
public IEnumerable<swap_position> GetSwapPositions(List<int> tradeIds, HashSet<string> _underlyingCodeFilterSet)
|
|
{
|
|
Expression<Func<swap_position, bool>> expression = x => tradeIds.Contains(x.SwapTradeId) && !string.IsNullOrEmpty(x.UnderlyingCode) && x.IsInitial && !x.Invalid;
|
|
if (_underlyingCodeFilterSet != null)
|
|
{
|
|
expression = expression.And(x => _underlyingCodeFilterSet.Contains(x.UnderlyingCode));
|
|
}
|
|
return DbContext.swap_position.Where(expression);
|
|
}
|
|
/// <summary>
|
|
/// 单标的回退到初始
|
|
/// </summary>
|
|
/// <param name="td"></param>
|
|
/// <param name="swapPositions"></param>
|
|
private void SingleTradeBackToBegin(trade td, List<swap_position> swapPositions)
|
|
{
|
|
var positions = swapPositions.Where(x => !x.IsInitial).ToList();
|
|
var marginPositions = swapPositions.Where(x => x.IsInitial && x.InterestMode == (int)InterestModeEnum.追加预付金).ToList();
|
|
var orignPositions = swapPositions.Where(x => x.IsInitial && x.InterestMode != (int)InterestModeEnum.追加预付金).ToList();
|
|
DbContext.swap_position.RemoveRange(positions);
|
|
DbContext.swap_position.RemoveRange(marginPositions);
|
|
td.TradeAmount = 0;
|
|
foreach (var item in orignPositions)
|
|
{
|
|
var interest = item.Clone();
|
|
interest.id = 0;
|
|
interest.PositionId = item.id;
|
|
interest.IsInitial = false;
|
|
td.TradeAmount = td.TradeAmount + Convert.ToDouble(interest.PosiQuantity);
|
|
DbContext.swap_position.Add(interest);
|
|
}
|
|
}
|
|
/// <summary>
|
|
/// 回到某个事件之前
|
|
/// </summary>
|
|
/// <param name="td"></param>
|
|
/// <param name="swapEvent"></param>
|
|
private void TradeBackByDate(trade td, DateTime valueDate, List<swap_position> swapPositions)
|
|
{
|
|
SwapEodPositionService eodPositionService = new SwapEodPositionService(this);
|
|
SwapDealService swapDealService = new SwapDealService(this);
|
|
var preDay = valueDate.AddDays(-1);
|
|
var eodSwapPositionList = DbContext.eod_swap_position.Where(x => x.ValueDate == preDay && x.SwapTradeId == td.id).ToList();
|
|
var swapFlowEvents = DbContext.swap_flow_event.Where(x => x.SwapTradeId == td.id && x.EventDate >= valueDate && x.DataState > (int)SwapFlowDateStateEnum.废弃).ToList();
|
|
var positions = swapPositions.Where(x => x.PosiDirection > 0 && !x.IsInitial).ToList();
|
|
|
|
foreach (var posi in positions)
|
|
{
|
|
var eodPosi = eodSwapPositionList.FirstOrDefault(x => x.PositionId == posi.PositionId);
|
|
if (eodPosi != null)
|
|
{
|
|
posi.InterestAmount = eodPosi.RealizedInterest;
|
|
posi.InterestFeePending = eodPosi.InterestFeePending;
|
|
posi.InterestPrincipalFix= eodPosi.InterestPrincipalFix;
|
|
posi.PosiTradingFeePending = eodPosi.PosiFeePending;
|
|
posi.PosiDividendIncome = eodPosi.PosiDividendSum;
|
|
posi.PosiQuantity = eodPosi.PosiQuantity;
|
|
posi.PosiNetPrice = eodPosi.PosiNetPrice;
|
|
posi.PosiGrossPrice = eodPosi.PosiGrossPrice;
|
|
posi.PosiNetFeePrice = eodPosi.PosiNetFeePrice;
|
|
posi.PosiNetNoFeePrice = eodPosi.PosiNetNoFeePrice;
|
|
posi.PosiNotionalValue = Math.Round(eodPosi.PosiNotionalValue, ConsGlobal.MoneyRound, MidpointRounding.AwayFromZero);
|
|
if (posi.PosiDirection > 0)
|
|
{
|
|
td.StockEqvNotional = Math.Round(Convert.ToDouble(posi.PosiNotionalValue), ConsGlobal.MoneyRound, MidpointRounding.AwayFromZero);
|
|
td.TradeAmount = Convert.ToDouble(posi.PosiQuantity);
|
|
}
|
|
}
|
|
}
|
|
td.UnWindDate = null;
|
|
td.UnWindNotional = null;
|
|
//DbContext.swap_position.RemoveRange(positions);
|
|
//swapDealService.UpdateInitalPosition(valueDate,td, swapPositions, swapFlowEvents);
|
|
DbContext.swap_flow_event.RemoveRange(swapFlowEvents);
|
|
var clientCashs = DbContext.ClientCashInCashOut.Where(x => x.TradeId == td.id && x.HappenDate >= valueDate).ToList();
|
|
DbContext.ClientCashInCashOut.RemoveRange(clientCashs);
|
|
DbContext.SaveChanges();
|
|
}
|
|
/// <summary>
|
|
/// 多空组合回到初始
|
|
/// </summary>
|
|
/// <param name="swapPositions"></param>
|
|
private void LongshortTradeBackToBegin(List<swap_position> swapPositions)
|
|
{
|
|
var positions = swapPositions.Where(x => !x.IsInitial || x.InterestDirection == 0).ToList();
|
|
var orignPositions = swapPositions.Where(x => x.IsInitial && x.InterestDirection > 0).ToList();
|
|
|
|
foreach (var item in positions)
|
|
{
|
|
item.Invalid = true;
|
|
}
|
|
//DbContext.swap_position.RemoveRange(positions);
|
|
foreach (var item in orignPositions)
|
|
{
|
|
var interest = item.Clone();
|
|
interest.id = 0;
|
|
interest.PositionId = item.id;
|
|
interest.IsInitial = false;
|
|
DbContext.swap_position.Add(interest);
|
|
}
|
|
}
|
|
/// <summary>
|
|
/// 废弃或删除回退日期之后的数据
|
|
/// </summary>
|
|
/// <param name="tradeId"></param>
|
|
/// <param name="valueDate"></param>
|
|
private void InvalidTradeOptionDatasByDate(int tradeId, DateTime valueDate, bool backToBegin)
|
|
{
|
|
var swapEvents = DbContext.swap_event.Where(x => !x.Invalid && x.SwapTradeId == tradeId && x.ValueDate >= valueDate).ToList();
|
|
var swapEodPositions = DbContext.eod_swap_position.Where(x => x.SwapTradeId == tradeId && x.ValueDate >= valueDate);
|
|
var swapEods = DbContext.eod_swap.Where(x => x.SwapTradeId == tradeId && x.ValueDate >= valueDate);
|
|
var swapFlowEvents = DbContext.swap_flow_event.Where(x => x.SwapTradeId == tradeId && x.EventDate >= valueDate);
|
|
var firstConfirm = false;
|
|
swapEvents.ForEach(x =>
|
|
{
|
|
if (backToBegin && !firstConfirm && x.EventType == (int)SwapEventTypeEnum.确认交易)
|
|
{
|
|
firstConfirm = true;
|
|
}
|
|
else
|
|
{
|
|
x.Invalid = true;
|
|
}
|
|
|
|
});
|
|
swapFlowEvents.ForEach(x =>
|
|
{
|
|
if (backToBegin && x.EventType == (int)SwapFlowEventTypeEnum.开仓)
|
|
{
|
|
|
|
}
|
|
else
|
|
{
|
|
x.DataState = (int)SwapFlowDateStateEnum.废弃;
|
|
}
|
|
|
|
});
|
|
//DbContext.swap_flow_event.RemoveRange(swapFlowEvents);
|
|
DbContext.eod_swap_position.RemoveRange(swapEodPositions);
|
|
DbContext.eod_swap.RemoveRange(swapEods);
|
|
}
|
|
|
|
}
|
|
}
|