2488 lines
148 KiB
C#
2488 lines
148 KiB
C#
using CsvHelper;
|
||
using DocumentFormat.OpenXml.Bibliography;
|
||
using DocumentFormat.OpenXml.Drawing;
|
||
using DocumentFormat.OpenXml.Drawing.Charts;
|
||
using DocumentFormat.OpenXml.Spreadsheet;
|
||
using iTextSharp.text.pdf.security;
|
||
using NPOI.SS.Formula.Functions;
|
||
using Org.BouncyCastle.Asn1.Ocsp;
|
||
using Org.BouncyCastle.Math.EC.Multiplier;
|
||
using Qdp.Pricing.Base.Implementations;
|
||
using System.IO;
|
||
using System.Security.Cryptography.Xml;
|
||
using YLErp.Abstract;
|
||
using YLErp.Abstract.DataProviders;
|
||
using YLErp.BLL.Calculation;
|
||
using YLErp.BLL.MarginCalculation;
|
||
using YLErp.Cache;
|
||
using YLErp.Commons;
|
||
using YLErp.DataBase;
|
||
using YLErp.DBModels;
|
||
using YLErp.DBModels.Enums;
|
||
using YLErp.DBModels.Helpers;
|
||
using YLErp.Helpers;
|
||
using YLErp.Model;
|
||
using YLErp.Model.Enum;
|
||
using YLErp.Models;
|
||
using YLErp.Modules;
|
||
using YLErp.Modules.CalculationModule;
|
||
using YLErp.Modules.DataProviderModule;
|
||
using YLErp.Modules.EodModule;
|
||
using YLErp.Modules.EodModule.QueryModule;
|
||
using YLErp.Modules.SwapModule;
|
||
using YLErp.Modules.TradeDalModule;
|
||
using YLErp.QdpModule;
|
||
|
||
namespace YLErp.BLL.Eod
|
||
{
|
||
public class RealtimePnlCalc
|
||
{
|
||
public static bool IsListOld = true;
|
||
|
||
protected static object _RefrshLock = new object();
|
||
|
||
private static IYLCache _yLCache;
|
||
private static IKafkaProduce kafkaProduceHelper;
|
||
///系统参数
|
||
protected static valuedate _SystemDate = null;
|
||
///当前计算日期
|
||
protected static DateTime _ValueDate = DateTime.MinValue;
|
||
///最后交易日
|
||
protected static DateTime _LastSettleDate = DateTime.MinValue;
|
||
///最后结算日日终持仓信息
|
||
protected static List<eod_trade_position> _LastTradePositionList = new List<eod_trade_position>();
|
||
//
|
||
protected static List<trade> _TradeAllList;
|
||
protected static List<trade> _TradeSwapList;
|
||
protected static DateTime _TradeLastUpdateTime;
|
||
static IYcLogger _logger = LogFactory.GetLogger("RealtimePnlCalc");
|
||
/// 设置为false时 节假日当天
|
||
protected static bool IsFirstHoliday = true;
|
||
public static void InitCache(IYLCache yLCache)
|
||
{
|
||
_yLCache = yLCache;
|
||
}
|
||
public static void InitKafka(IKafkaProduce kafkaProduce)
|
||
{
|
||
kafkaProduceHelper = kafkaProduce;
|
||
}
|
||
/// <summary>
|
||
/// 刷新当日静态数据
|
||
/// </summary>
|
||
public static void RestInitData()
|
||
{
|
||
//刷新系统日期
|
||
valuedateBLL.ResetValueDate();
|
||
|
||
var isReload = false;
|
||
|
||
if (_ValueDate == DateTime.MinValue || _ValueDate != valuedateBLL.ValueDate)
|
||
{
|
||
isReload = true;
|
||
_SystemDate = valuedateBLL.SystemDate;
|
||
_ValueDate = _SystemDate.ValueDate;
|
||
}
|
||
|
||
InitLastTradePosition(isReload);
|
||
|
||
//增量初始化交易信息
|
||
InitTradeList();
|
||
}
|
||
|
||
/// <summary>
|
||
/// 初始化最后结算日结算信息
|
||
/// </summary>
|
||
private static void InitLastTradePosition(bool isReload)
|
||
{
|
||
if (isReload)
|
||
{
|
||
//获取昨日持仓
|
||
_LastSettleDate = EodOperationBase.GetLastSettlementDate(_ValueDate, true);
|
||
//获取昨日持仓
|
||
_LastTradePositionList = new EodSettlementService(OptUserInfo.SystemUser).GetEodTradePosition<eod_trade_position>(_LastSettleDate).ToList();
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 初始化交易信息
|
||
/// </summary>
|
||
private static void InitTradeList()
|
||
{
|
||
using (var db = new YLContext())
|
||
{
|
||
//获取停牌信息
|
||
var suspensionUnderlyingIdList = DataCacheProvider.GetUnderlyingDataSource().AsQueryable()
|
||
.Where(t => t.UnderlyingStatus == underlying_manager.Status_Suspension).Select(t => t.id).ToList();
|
||
|
||
//昨日持仓交易
|
||
var tradeIds = _LastTradePositionList.Where(t => 0 != t.TradeId).Select(t => t.TradeId).ToList();
|
||
|
||
//初始化待交易交易列表
|
||
_TradeAllList = db.trade.AsNoTracking().Where(t => t.ValidState != "InValid" && t.TradeDate <= _ValueDate && ConsTrade.NeedMarginTradeStatusList.Contains(t.TradeStatus)).ToList(); //有效的交易
|
||
//if (PS.Config.IsGuoJun)
|
||
//{
|
||
// _TradeAllList = _TradeAllList.Where(x=>x.TradeType!="收益互换").ToList();
|
||
//}
|
||
|
||
if (_TradeAllList != null && _TradeAllList.Any())
|
||
{
|
||
var allTradeIds = _TradeAllList.Select(d => d.id).Distinct().ToList();
|
||
var metaList = db.TradeMeta.AsNoTracking().Where(d => allTradeIds.Contains(d.TradeId)).ToList();
|
||
_TradeAllList.ForEach(p =>
|
||
{
|
||
p.MetaDic = metaList.Where(x => x.TradeId == p.id).ToDictionary(g => g.MetaKey, g => g.MetaValue);
|
||
});
|
||
}
|
||
_TradeSwapList = _TradeAllList.Where(x => x.TradeType == "收益互换").ToList();
|
||
_TradeAllList = _TradeAllList.Where(x => x.TradeType != "收益互换").ToList();
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 计算实时持仓并返回结果
|
||
/// </summary>
|
||
public static List<intraday_trade_position> RealtimePosition(OptUserInfo optUser)
|
||
{
|
||
if (optUser is null)
|
||
{
|
||
throw new ArgumentNullException(nameof(optUser));
|
||
}
|
||
|
||
lock (_RefrshLock)
|
||
{
|
||
using (var db = new YLContext())
|
||
{
|
||
RestInitData();
|
||
|
||
#region 获取昨日持仓 标的当前价格 当前持仓以及当日行权交易 并计算Risk信息
|
||
|
||
//标的
|
||
var underlyingPrice = DataCacheProvider.GetUnderlyingDataSource();
|
||
//获取待计算交列表
|
||
var tradeList = _TradeAllList;
|
||
var tradeIdList = tradeList.Select(t => t.id).ToList();
|
||
//设置option info
|
||
tradeBLL.SetFieldsByTradeType(tradeList);
|
||
|
||
//OTC 交易
|
||
var otcTradeList = tradeList.Where(t => !ConsTrade.TradeTypesForHedge.Contains(t.TradeType)).ToList();
|
||
new TradeDalService(optUser).SetSubTradeList(otcTradeList);
|
||
|
||
//OTC持仓交易
|
||
//过滤结构化交易,不参与计算预付金逻辑
|
||
var positionTradeList = otcTradeList.Where(t => ConsTrade.NeedMarginTradeStatusList.Contains(t.TradeStatus) && t.TradeType != "结构化交易").ToList();
|
||
//还未了结的交易
|
||
var liveTradeList = otcTradeList.Where(t => ConsTrade.LiveTradeStatusList.Contains(t.TradeStatus)).ToList();
|
||
|
||
//预付金计算
|
||
var req = new RunMarginCalculationReq(optUser)
|
||
{
|
||
tradeList = positionTradeList,
|
||
settleDate = _ValueDate,
|
||
PriceProvider = underlyingPrice,
|
||
hasOptionInfo = true
|
||
};
|
||
|
||
var tradeSpans = MarginDefault.RunMarginCalculation(req.Clone(forOtherSide: false));
|
||
//试算出交易买卖方向反向的tradeSpan(目前只有国君有这个需求,做过处理)
|
||
List<trade_span> tradeSpansOtherSide = new List<trade_span>();
|
||
|
||
if (MarginDefault.IsMarginCalcNeedSpecial(_ValueDate))
|
||
{
|
||
//试算出交易买卖方向反向的tradeSpan(目前只有国君有这个需求,做过处理)
|
||
tradeSpansOtherSide = MarginDefault.RunMarginCalculation(req.Clone(forOtherSide: true));
|
||
}
|
||
|
||
//根据交易预付金合计客户预付金计入client_span
|
||
var calcClientMarginReq = new CalcClientMarginReq(optUser)
|
||
{
|
||
settleDate = _ValueDate,
|
||
tradeSpans = tradeSpans,
|
||
tradeSpansOtherSide = tradeSpansOtherSide,
|
||
SpanType = ClientSpan.SpanType_RealTime
|
||
};
|
||
tradeSpans = MarginDefault.CalcClientMargin(calcClientMarginReq);
|
||
|
||
if (PS.Config.Company == Configuration.CompanyEnum.渤海)
|
||
{
|
||
//预付金计算
|
||
tradeSpans = MarginDefault.RunMarginCalculation(req.Clone(forOtherSide: false));
|
||
}
|
||
|
||
//实时计算rsik
|
||
var volTypes = new List<string> { "持仓" };
|
||
if (PS.Config.Is国投)
|
||
{
|
||
volTypes.Add("开仓");
|
||
}
|
||
var riskList = RealTimeRiskCalc(_ValueDate, liveTradeList, underlyingPrice, volTypes);
|
||
|
||
#endregion
|
||
|
||
#region 平仓费行权费 交易预付金计算 昨日持仓对冲交易过滤
|
||
var unWindTradeCashAction = new List<string> { ClientCashInCashOut.系统操作_平仓费, ClientCashInCashOut.系统操作_行权费, ClientCashInCashOut.系统操作_期权费 };
|
||
|
||
//获取平仓到期交易的trade_cash;
|
||
var tradeCashList = db.trade_cash.Where(t => tradeIdList.Contains(t.TradeId) && unWindTradeCashAction.Contains(t.Action) && t.ValidState != "InValid" && !t.IsDeleted).ToList();
|
||
|
||
//实时结果对象类
|
||
var realtimePositionList = new List<intraday_trade_position>();
|
||
|
||
//旧持仓对冲交易
|
||
var lastHedgingPositionList = _LastTradePositionList.Where(t => ConsTrade.TradeTypesForHedge.Contains(t.TradeType)).ToList();
|
||
|
||
#endregion
|
||
|
||
#region 处理昨日持仓今日为未交易股票 商品期货
|
||
|
||
//2021-02-02 实时持仓服务不再计算对冲交易,统一从 trade_Position 获取;
|
||
////对冲 交易
|
||
//var hedgeTradeList = db.ExchangeTrade.Where(t => t.IsValid && t.TradeDate <= _ValueDate && t.TradeDate > _LastSettleDate).ToList();
|
||
//var hedgePnlCalc = GetRealTimeHedgePnlCalc(_ValueDate, volType: "交易", optUser: optUser);
|
||
//var hedgePnl = hedgePnlCalc.Calculate(hedgeTradeList, lastHedgingPositionList);
|
||
//if (hedgePnl != null && hedgePnl.Any())
|
||
//{
|
||
// foreach (var t in hedgePnl)
|
||
// {
|
||
// var realtimePosition = new intraday_trade_position
|
||
// {
|
||
// ValueDate = _ValueDate,
|
||
// BookId = t.BookId,
|
||
// TradeType = t.TradeType,
|
||
// //ClientId = t.ClientId,
|
||
// PositionType = t.PositionType,
|
||
// UnderlyingCode = t.UnderlyingCode,
|
||
// BuySell = t.BuySell,
|
||
// Cost = Convert.ToDouble(t.Cost),
|
||
// Notional = Convert.ToDouble(t.Notional),
|
||
// Amount = Convert.ToDouble(t.Notional),
|
||
// LastPv = t.LastPv,
|
||
// Pv = t.Pv,
|
||
// DailyPnL = t.DailyPnL,
|
||
// TotalPnL = Convert.ToDouble(t.TotalPnl),
|
||
// PositionPnL = Convert.ToDouble(t.TotalPnl),
|
||
// ActualPv = Convert.ToDouble(t.Pv),
|
||
// ActualLastPv = Convert.ToDouble(t.LastPv),
|
||
// ActualDailyPnL = Convert.ToDouble(t.DailyPnL),
|
||
// ActualTotalPnL = Convert.ToDouble(t.TotalPnl),
|
||
// RealizedPnL = Convert.ToDouble(t.RealizedPnL),
|
||
// TradeStatus = "确认成交",
|
||
// OptDate = DateTime.Now,
|
||
// OptId = optUser.UserId,
|
||
// OptName = optUser.UserName,
|
||
// ParentTradeId = 0
|
||
// };
|
||
// realtimePositionList.Add(realtimePosition);
|
||
// }
|
||
//}
|
||
#endregion
|
||
|
||
#region 当日持仓期权交易 已行权已到期期权交易 以及当日对冲交易计算持仓
|
||
|
||
var tradeIds = otcTradeList.Select(x => x.id).ToArray();
|
||
var positionRealizedTradeCashs = db.trade_cash.Where(x => (x.Action == "系统操作-票息" || x.Action == "系统操作-互换") && x.ValidState != "InValid" && x.ValueDate <= _ValueDate && tradeIds.Contains(x.id) && !x.IsLastAction).ToList();
|
||
|
||
//循环交易 OTC交易
|
||
otcTradeList.ForEach(t =>
|
||
{
|
||
var Pv = 0.0; //当日pv
|
||
var RoundedPv = 0.0;
|
||
var LastPv = 0.0; //昨日pv
|
||
var Quantity = 0.0; //数量
|
||
var RealizedPnL = 0.0;//已归因盈亏
|
||
var DailyPnl = 0.0; //当日盈亏
|
||
var UnRealizedPnL = 0.0;//未归因盈亏
|
||
var TotalPnL = 0.0; //总盈亏
|
||
var PositionPnL = 0.0; //持仓盈亏
|
||
var RoundedPositionPnL = 0.0;
|
||
var NotionalPrincipal = 0.0;
|
||
var ActualPv = 0.0; //当日实际pv
|
||
var ActualLastPv = 0.0; //昨日实际pv
|
||
var ActualDailyPnl = 0.0; //当日实际浮动盈亏
|
||
var ActualTotalPnL = 0.0; //实际总盈亏
|
||
//部分平仓计算
|
||
var tempTradeCashList = tradeCashList.Where(a => a.TradeId == t.id);
|
||
//已实现
|
||
var RealizeProfitAndloss = tempTradeCashList.Any() ? tempTradeCashList.Sum(a => a.Amount) : 0.0;
|
||
|
||
//预付金
|
||
var tradeSpan = tradeSpans.FirstOrDefault(a => a.TradeId == t.id);
|
||
|
||
if (ConsTrade.LiveTradeStatusList.Contains(t.TradeStatus))
|
||
{
|
||
if (riskList.Any(a => a.TradeId == t.id))
|
||
{
|
||
var valueResult = riskList.FirstOrDefault(a => a.TradeId == t.id);
|
||
//最新pv
|
||
Pv = valueResult.Pv ?? 0;
|
||
RoundedPv = valueResult.RoundedPv ?? 0;
|
||
PositionPnL = valueResult.PositionPnl ?? 0.0;
|
||
RoundedPositionPnL = valueResult.RoundedPositionPnl ?? 0.0;
|
||
}
|
||
else
|
||
{
|
||
Pv = 0.0;
|
||
RoundedPv = 0.0;
|
||
}
|
||
//var valueResult = holdRisResult.ContainsKey(t.id) ? holdRisResult[t.id] : null;
|
||
//if (valueResult == null) continue;
|
||
var lastEodPosition = _LastTradePositionList.FirstOrDefault(e => e.TradeId == t.id);
|
||
//if (lastEodPosition != null) _LastTradePositionList.Remove(lastEodPosition);
|
||
//昨日pv
|
||
LastPv = lastEodPosition == null ? 0 : Convert.ToDouble(lastEodPosition.Pv);
|
||
//ActualLastPv = lastEodPosition == null ? 0 : Convert.ToDouble(lastEodPosition.ActualPv ?? 0.0);
|
||
//总持仓量
|
||
Quantity = lastEodPosition == null ? t.Notional : lastEodPosition.Amount;
|
||
//归因盈亏
|
||
RealizedPnL = lastEodPosition != null ? 0 : ((t.TradeSinglePrice ?? 0) * t.Notional * TradeCalcHelper.GetSign(t.BuySell) * (-1));
|
||
//未归因盈亏
|
||
UnRealizedPnL = Pv - LastPv;
|
||
//估值盈亏
|
||
DailyPnl = RealizedPnL + UnRealizedPnL;
|
||
//名义本金 如果是股票则取名义本金字段 如果为商品期货则去 spotprice * 份额
|
||
NotionalPrincipal = t.OriginalStockEqvNotional > 0
|
||
? ((t.OriginalStockEqvNotional * t.Notional / t.OriginalNotional) ?? 0) : TradeHelper.GetStockEqvNotional((t.SpotPrice ?? 0.0) * t.Notional, t.ParticipationRate, t.AnnualizeFactor);
|
||
//根据看涨看跌以及当前价格与行权价涨跌判断是否有实际pv
|
||
var callPutFlag = "Call".Equals(t.CallPut);
|
||
var unSpotPrice = underlyingPrice.GetPrice(t.UnderlyingCode);
|
||
var StrikeFlag = unSpotPrice > (t.IsMoneynessOptionData ? (t.Strike * t.SpotPrice) : (t.Strike ?? 0.0));
|
||
//实值pv计算
|
||
ActualPv = 0.0;
|
||
if (callPutFlag == StrikeFlag)
|
||
{
|
||
var Strike = t.IsMoneynessOptionData ? ((t.Strike ?? 0.0) * t.SpotPrice ?? 0.0) : t.Strike ?? 0.0;
|
||
var SpotPrice = (t.SpotPrice ?? 0);
|
||
if (SpotPrice > 0)
|
||
{
|
||
ActualPv = t.StockEqvNotional * Math.Max((unSpotPrice - Strike) * (callPutFlag ? 1 : -1), 0) / SpotPrice * TradeCalcHelper.GetSign(t.BuySell);
|
||
}
|
||
}
|
||
//实值浮动盈亏
|
||
//ActualDailyPnl = ActualPv - ActualLastPv;
|
||
//实值总盈亏
|
||
//ActualTotalPnL = (lastEodPosition == null ? 0 : lastEodPosition.ActualTotalPnL ?? 0.0) + ActualDailyPnl;
|
||
//总盈亏
|
||
TotalPnL = (lastEodPosition == null ? 0 : lastEodPosition.TotalPnL) + DailyPnl;
|
||
//PositionPnL = Pv + RealizeProfitAndloss;
|
||
}
|
||
else if (ConsTrade.TradeCompleteStatus.Contains(t.TradeStatus))
|
||
{//已执行 已平仓 已到期的交易计算持仓信息
|
||
|
||
var tradeCash = tradeCashList.FirstOrDefault(a => a.TradeId == t.id && a.Action != ClientCashInCashOut.系统操作_期权费);
|
||
Pv = ActualPv = tradeCash == null ? 0.0 : tradeCash.Amount;
|
||
RoundedPv = Pv;
|
||
var lastEodPosition = _LastTradePositionList.FirstOrDefault(e => e.TradeId == t.id);
|
||
//if (lastEodPosition != null) _LastTradePositionList.Remove(lastEodPosition);
|
||
//昨日pv
|
||
LastPv = ActualLastPv = lastEodPosition == null ? 0 : Convert.ToDouble(lastEodPosition.Pv);
|
||
//总持仓量
|
||
Quantity = lastEodPosition == null ? t.Notional : lastEodPosition.Amount;
|
||
//归因盈亏
|
||
RealizedPnL = Pv;
|
||
//未归因盈亏
|
||
UnRealizedPnL = -LastPv;
|
||
//估值盈亏
|
||
DailyPnl = RealizedPnL + UnRealizedPnL;
|
||
//名义本金 如果是股票则取名义本金字段 如果为商品期货则去 spotprice * 份额
|
||
NotionalPrincipal = t.OriginalStockEqvNotional > 0
|
||
? ((t.OriginalStockEqvNotional * t.Notional / t.OriginalNotional) ?? 0) : TradeHelper.GetStockEqvNotional((t.SpotPrice ?? 0.0) * t.Notional, t.ParticipationRate, t.AnnualizeFactor);
|
||
//根据看涨看跌以及当前价格与行权价涨跌判断是否有实际pv
|
||
//实值pv计算
|
||
ActualDailyPnl = ActualPv - ActualLastPv;
|
||
//总盈亏
|
||
ActualTotalPnL = TotalPnL = Pv - ((t.TradeSinglePrice ?? 0.0) * t.Notional * TradeCalcHelper.GetSign(t.BuySell));
|
||
PositionPnL = RealizeProfitAndloss;
|
||
RoundedPositionPnL = RealizeProfitAndloss;
|
||
}
|
||
if (double.IsNaN(NotionalPrincipal))
|
||
{
|
||
NotionalPrincipal = 0;
|
||
}
|
||
|
||
var Margin = tradeSpan == null ? 0.0 : double.IsNaN(tradeSpan.WorstCastClientPayable ?? double.NaN) ? 0 : tradeSpan.WorstCastClientPayable.Value;
|
||
|
||
|
||
realtimePositionList.Add(
|
||
new intraday_trade_position
|
||
{
|
||
TradeId = t.id,
|
||
TradeNumber = t.TradeNumber,
|
||
ValueDate = _ValueDate,
|
||
BookId = t.AssetId,
|
||
TradeType = t.TradeType,
|
||
ClientId = t.ClientId,
|
||
UnderlyingCode = t.UnderlyingCode,
|
||
BuySell = t.BuySell,
|
||
Cost = (t.OriginalNotional > 0 ? t.TradePrice * t.Notional / t.OriginalNotional : t.TradePrice) ?? 0,
|
||
Notional = NotionalPrincipal,
|
||
Amount = Quantity,
|
||
LastPv = (decimal)LastPv,
|
||
Pv = (decimal)Pv,
|
||
RoundedPv = (decimal)RoundedPv,
|
||
DailyPnL = (decimal)DailyPnl,
|
||
RealizedPnL = RealizedPnL,
|
||
TotalPnL = TotalPnL,
|
||
PositionPnL = PositionPnL,
|
||
RoundedPositionPnl = RoundedPositionPnL,
|
||
ActualPv = ActualPv,
|
||
ActualLastPv = ActualLastPv,
|
||
ActualDailyPnL = ActualDailyPnl,
|
||
ActualTotalPnL = ActualTotalPnL,
|
||
Margin = Margin,
|
||
PositionRelizedAmount = positionRealizedTradeCashs.Where(x => x.TradeId == t.id).Sum(x => x.Amount),
|
||
TradeStatus = t.TradeStatus,
|
||
OptDate = DateTime.Now,
|
||
OptId = optUser.UserId,
|
||
OptName = optUser.UserName,
|
||
ParentTradeId = t.ParentTradeId,
|
||
//收益互换 应缴预付金 初始预付金 盈亏
|
||
SwapInitMargin = 0,
|
||
SwapWinLoss = 0,
|
||
SwapUnMargin = 0
|
||
});
|
||
});
|
||
#endregion
|
||
|
||
//删除过期和无效的交易
|
||
var intradayTradeIds = db.intraday_trade_position.Where(t => t.TradeId > 0).Select(t => t.TradeId).ToList();
|
||
var inValidTradeIds = db.trade.Where(t => intradayTradeIds.Contains(t.id) && t.ValidState == "InValid").Select(t => t.id).ToList();
|
||
var removePosition = db.intraday_trade_position.Where(t => t.ValueDate != _ValueDate || inValidTradeIds.Contains(t.TradeId));
|
||
if (removePosition.Any())
|
||
{
|
||
db.intraday_trade_position.RemoveRange(removePosition);
|
||
db.SaveChanges();
|
||
}
|
||
|
||
//插入数据库还未有过的数据(根据TradeId,ValueDate筛选)
|
||
var intradayTradeIdsExist = db.intraday_trade_position.Where(x => x.ValueDate == _ValueDate).Select(x => x.TradeId).ToList();
|
||
var realtimePositionListInsert = realtimePositionList.Where(x => !intradayTradeIdsExist.Contains(x.TradeId));
|
||
MySqlBulkExtensions.BulkInsert(db, realtimePositionListInsert);
|
||
|
||
//根据TradeId,ValueDate更新已经存在的数据
|
||
var tradeids = realtimePositionList.Select(t => t.TradeId).ToList();
|
||
var intradayTradePositions = db.intraday_trade_position.Where(x => tradeids.Contains(x.TradeId) && x.ValueDate == _ValueDate).ToList();
|
||
for (var i = 0; i < intradayTradePositions.Count; i++)
|
||
{
|
||
var intradayTradePosition = realtimePositionList.FirstOrDefault(x => x.TradeId == intradayTradePositions[i].TradeId)?.Clone();
|
||
intradayTradePosition.id = intradayTradePositions[i].id;
|
||
db.Entry(intradayTradePositions[i]).CurrentValues.SetValues(intradayTradePosition);
|
||
}
|
||
|
||
//删除不该存在的场外交易,例如到期日被修改到了今天之前的某一天
|
||
var intradayTradePositionDelete = db.intraday_trade_position.Where(x => !tradeids.Contains(x.TradeId) && x.TradeId > 0).ToList();
|
||
if (intradayTradePositionDelete.Any())
|
||
{
|
||
db.intraday_trade_position.RemoveRange(intradayTradePositionDelete);
|
||
}
|
||
|
||
db.SaveChanges();
|
||
|
||
return realtimePositionList;
|
||
}
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 计算实时持仓并返回结果
|
||
/// </summary>
|
||
public static List<ClientPosition> RealtimeSwapPosition(OptUserInfo optUser)
|
||
{
|
||
string bondCalcTopic = Environment.GetEnvironmentVariable("KafkaConfig_ReqCalcBondTopic");
|
||
bool.TryParse(Environment.GetEnvironmentVariable("KafkaConfig_EnableCalcBongd"), out bool enableCalcBongd);
|
||
if (optUser is null)
|
||
{
|
||
throw new ArgumentNullException(nameof(optUser));
|
||
}
|
||
RestInitData();
|
||
lock (_RefrshLock)
|
||
{
|
||
using (var db = new YLContext())
|
||
{
|
||
|
||
#region 获取当前持仓 标的当前价格 当前未簿记流水 并计算Risk信息
|
||
|
||
//标的
|
||
var underlyingPrice = DataCacheProvider.GetUnderlyingDataSource();
|
||
//获取待计算交列表
|
||
var tradeList = db.trade.AsNoTracking().Where(t => t.ValidState != "InValid" && t.TradeDate <= _ValueDate//有效的交易
|
||
&& (ConsTrade.NeedMarginTradeStatusList.Contains(t.TradeStatus))
|
||
&& t.TradeType == "收益互换").ToList();
|
||
//OTC持仓交易
|
||
var tradeIdList = tradeList.Select(t => t.id).ToList();
|
||
var positions = db.swap_position.Where(x => tradeIdList.Contains(x.SwapTradeId) && !x.IsInitial && x.PosiQuantity > 0 && !x.Invalid).ToList();
|
||
var swapFlows = db.swap_flow.Where(x => x.DataState == (int)SwapFlowDateStateEnum.等待完成).ToList();
|
||
using var bondDb = new BondOmsDBContext();
|
||
var clientPositions = bondDb.client_position.AsEnumerable();
|
||
var eodSwaps = db.eod_swap_position.Where(x => tradeIdList.Contains(x.SwapTradeId) && x.PosiDirection > 0).AsEnumerable().GroupBy(p => p.SwapTradeId)
|
||
.Select(g => g.OrderByDescending(p => p.ValueDate).First()).ToList();
|
||
#endregion
|
||
|
||
#region 实时持仓计算
|
||
|
||
//实时结果对象类
|
||
var realtimePositionList = new List<ClientPosition>();
|
||
|
||
#endregion
|
||
|
||
#region 当日持仓期权交易 以及当前交易流水计算持仓
|
||
var query = from td in tradeList
|
||
join p in positions on td.id equals p.SwapTradeId
|
||
select new
|
||
{
|
||
td,
|
||
p
|
||
};
|
||
var positionGroup = query.AsEnumerable().GroupBy(x => new { x.p.UnderlyingCode, x.td.ClientId, x.p.PosiDirection });
|
||
var dealSwapFlowIds = new List<long>();
|
||
SwapTradeAutoService swapTradeAutoService = new SwapTradeAutoService(optUser);
|
||
var datenow = DateTime.Now;
|
||
foreach (var pair in positionGroup)
|
||
{
|
||
var client = DataCacheProvider.GetClientDataSource().GetData(pair.Key.ClientId);
|
||
if (client == null || string.IsNullOrEmpty(pair.Key.UnderlyingCode))
|
||
{
|
||
continue;
|
||
}
|
||
var clientPosition = clientPositions.FirstOrDefault(x => x.client_id == pair.Key.ClientId && x.security_id == pair.Key.UnderlyingCode && x.direction == pair.Key.PosiDirection);
|
||
var trades = pair.Select(s => s.td).ToList();
|
||
var tradeIds = trades.Select(x => x.id);
|
||
var positionGroupItems = positions.Where(x => tradeIds.Contains(x.SwapTradeId) && x.UnderlyingCode == pair.Key.UnderlyingCode && x.PosiDirection == pair.Key.PosiDirection);
|
||
var lastEodSwaps = eodSwaps.Where(x => tradeIds.Contains(x.SwapTradeId)).ToList();
|
||
var lastPv = lastEodSwaps.Sum(s => s.UnderlyingMarketValue);
|
||
if (!positionGroupItems.Any())
|
||
{
|
||
continue;
|
||
}
|
||
var comminsions = positionGroupItems.Sum(s => s.PosiTradingFeePending);
|
||
var lastPosi = positionGroupItems.OrderByDescending(x => x.id).First();
|
||
var newSwapFlows = swapFlows.Where(x => x.ClientId == pair.Key.ClientId && x.UnderlyingCode == pair.Key.UnderlyingCode).ToList();
|
||
var cunrentPositions = positionGroupItems.Sum(x => x.PosiQuantity * (x.PositionType == (int)PositionTypeFlag.Long ? 1 : -1));
|
||
var positionType = cunrentPositions > 0 ? PositionTypeFlag.Long : PositionTypeFlag.Short;
|
||
var posiQty = Math.Abs(cunrentPositions);
|
||
var multiplier = ConsGlobal.InstrumentType.IsBond(lastPosi.UnderlyingInstrumentType) ? ConsGlobal.bondShowPriceMultiple : 1;
|
||
|
||
// 计算加权平均价格(区分债券和非债券)
|
||
var weightedPrice = posiQty == 0 ? 0 : positionGroupItems
|
||
.Sum(s =>
|
||
{
|
||
decimal price = ConsGlobal.InstrumentType.IsBond(lastPosi.UnderlyingInstrumentType)
|
||
? s.PosiGrossPrice * ConsGlobal.bondShowPriceMultiple
|
||
: s.PosiGrossPrice;
|
||
return s.PosiQuantity * price;
|
||
}) / posiQty;
|
||
// 使用标的期初价格不含费计算
|
||
var weightedNetPrice = posiQty == 0 ? 0 : positionGroupItems
|
||
.Sum(s =>
|
||
{
|
||
decimal price = ConsGlobal.InstrumentType.IsBond(lastPosi.UnderlyingInstrumentType)
|
||
? (s.PosiNetNoFeePrice ?? 0) * ConsGlobal.bondShowPriceMultiple
|
||
: s.PosiNetNoFeePrice ?? 0;
|
||
return s.PosiQuantity * price;
|
||
}) / posiQty;
|
||
// 替换原代码中的 price 和 netPrice
|
||
var price = weightedPrice;
|
||
var netPrice = weightedNetPrice;
|
||
var totalNotional = positionGroupItems.Sum(s => s.PosiNotionalValue);
|
||
var um = DataCacheProvider.GetUnderlyingDataSource().GetData(pair.Key.UnderlyingCode);
|
||
clientPosition = CreateClientPosition(clientPosition, pair.Key.ClientId, pair.Key.UnderlyingCode, netPrice, price, posiQty / 10000, comminsions, positionType == PositionTypeFlag.Long ? 0 : 1, lastPosi.ContractSize, pair.Key.PosiDirection);
|
||
clientPosition.position_notional_principal = totalNotional;
|
||
if (pair.Key.PosiDirection == (int)SwapDirectionEnum.支付)
|
||
{
|
||
var flowMerges = MergeSwapFlow(newSwapFlows, multiplier);
|
||
dealSwapFlowIds.AddRange(newSwapFlows.Select(s => s.id));
|
||
if (flowMerges.Any())
|
||
{
|
||
MergeSwapPosition(flowMerges, positionType, clientPosition, posiQty);
|
||
}
|
||
}
|
||
clientPosition.update_user = 0;
|
||
SetClientPositionPrice(clientPosition);
|
||
// full_price_now 为债券报价(面值百分比,×100形式);×100 还原市值数量级
|
||
clientPosition.swap_market_value = clientPosition.full_price_now * clientPosition.position_qty * (clientPosition.side == 0 ? 1 : -1) * ConsGlobal.bondShowPriceMultiple;
|
||
// 盈亏 = (现价−成本) × 价差系数(÷100,bondPriceMultiple) × 数量(万手×10000) × 方向。此处 0.01m 与 10000 分属价格/数量两个维度,不宜合并为 BondPriceConverter
|
||
clientPosition.position_profit_loss = (clientPosition.full_price_now - clientPosition.deal_full_price_avg) * ConsGlobal.bondPriceMultiple * (clientPosition.position_qty * 10000) * (clientPosition.side == 0 ? 1 : -1) - clientPosition.commission;
|
||
clientPosition.position_profit_loss = Math.Round(clientPosition.position_profit_loss ?? 0, 2, MidpointRounding.AwayFromZero);
|
||
clientPosition.today_profit_loss = clientPosition.swap_market_value - lastPv;
|
||
if (clientPosition.deal_full_price_avg > 0 && enableCalcBongd)//发kafka 获取成交收益率
|
||
{
|
||
CalcBandPrice(clientPosition);
|
||
}
|
||
if (clientPosition.id == 0 && clientPosition.position_qty > 0)
|
||
{
|
||
clientPosition.update_time = DateTime.Now;
|
||
bondDb.client_position.Add(clientPosition);
|
||
}
|
||
else if (clientPosition.id > 0 && (clientPosition.position_qty > 0 || clientPosition.today_profit_loss != 0))
|
||
{
|
||
clientPosition.update_time = DateTime.Now;
|
||
}
|
||
bondDb.SaveChanges();
|
||
}
|
||
var sql = $"{nameof(ClientPosition.create_time)}<'{datenow.AddSeconds(-1):yyyy-MM-dd HH:mm:ss}' or {nameof(ClientPosition.position_qty)}=0";
|
||
bondDb.BulkDelete<ClientPosition>(sql);
|
||
bondDb.SaveChanges();
|
||
var nodealSwapFlows = swapFlows.Where(x => !dealSwapFlowIds.Contains(x.id));
|
||
if (nodealSwapFlows.Any())
|
||
{
|
||
var nodealSwapFlowGroup = nodealSwapFlows.AsEnumerable().GroupBy(g => new { g.ClientId, g.UnderlyingCode });
|
||
foreach (var swapFlowGroup in nodealSwapFlowGroup)
|
||
{
|
||
var client = DataCacheProvider.GetClientDataSource().GetData(swapFlowGroup.Key.ClientId ?? 0);
|
||
if (client == null)
|
||
{
|
||
continue;
|
||
}
|
||
var um = DataCacheProvider.GetUnderlyingDataSource().GetData(swapFlowGroup.Key.UnderlyingCode);
|
||
var multiplier = um != null && um.IsBond() ? ConsGlobal.bondShowPriceMultiple : 1;
|
||
var flowMerges = MergeSwapFlow(swapFlowGroup.ToList(), multiplier);
|
||
var flowMergeMax = flowMerges.OrderByDescending(s => s.TradingQty).First();
|
||
var flowMergeMin = flowMerges.FirstOrDefault(x => x.BsType != flowMergeMax.BsType);
|
||
var comminsions = flowMerges.Sum(s => s.TradingFee);
|
||
var qty = flowMergeMax.TradingQty;
|
||
if (flowMergeMin != null)
|
||
{
|
||
qty -= flowMergeMin.TradingQty;
|
||
}
|
||
var clientPosition = clientPositions.FirstOrDefault(x => x.client_id == swapFlowGroup.Key.ClientId && x.security_id == swapFlowGroup.Key.UnderlyingCode);
|
||
clientPosition = CreateClientPosition(clientPosition, swapFlowGroup.Key.ClientId ?? 0, swapFlowGroup.Key.UnderlyingCode, flowMergeMax.TradingAmountNetAvg ?? 0, flowMergeMax.TradingAmountAvg, qty / 10000, comminsions, flowMergeMax.BsType == (int)PositionTypeFlag.Long ? 0 : 1, flowMergeMax.ContractSize, (int)SwapDirectionEnum.支付);
|
||
clientPosition.position_qty = qty / 10000;
|
||
clientPosition.update_user = 0;
|
||
if (clientPosition.id == 0)
|
||
{
|
||
clientPosition.update_time = DateTime.Now;
|
||
bondDb.client_position.Add(clientPosition);
|
||
}
|
||
SetClientPositionPrice(clientPosition);
|
||
clientPosition.swap_market_value = clientPosition.full_price_now * clientPosition.position_qty * (clientPosition.side == 0 ? 1 : -1);
|
||
clientPosition.position_profit_loss = (clientPosition.full_price_now - clientPosition.deal_full_price_avg) * clientPosition.position_qty * (clientPosition.side == 0 ? 1 : -1);
|
||
clientPosition.position_profit_loss = Math.Round(clientPosition.position_profit_loss ?? 0, 2, MidpointRounding.AwayFromZero);
|
||
clientPosition.today_profit_loss = clientPosition.swap_market_value;
|
||
//发kafka 获取成交收益率
|
||
if (clientPosition.deal_full_price_avg > 0 && enableCalcBongd)
|
||
{
|
||
CalcBandPrice(clientPosition);
|
||
}
|
||
else if (clientPosition.id > 0 && (clientPosition.position_qty > 0 || clientPosition.today_profit_loss != 0))
|
||
{
|
||
clientPosition.update_time = DateTime.Now;
|
||
}
|
||
bondDb.SaveChanges();
|
||
}
|
||
}
|
||
sql = $"{nameof(ClientPosition.create_time)}<'{datenow.AddSeconds(-1):yyyy-MM-dd HH:mm:ss}' or {nameof(ClientPosition.position_qty)}=0";
|
||
bondDb.BulkDelete<ClientPosition>(sql);
|
||
bondDb.SaveChanges();
|
||
#endregion
|
||
return realtimePositionList;
|
||
}
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 判断是否有新流水
|
||
/// </summary>
|
||
/// <param name="calcTime"></param>
|
||
/// <returns></returns>
|
||
public static bool HasNewFlow(DateTime calcTime)
|
||
{
|
||
using (var db = new YLContext())
|
||
{
|
||
return db.swap_flow.Any(x => x.OptTime >= calcTime);
|
||
}
|
||
|
||
}
|
||
/// <summary>
|
||
/// 计算RealtimeRisk
|
||
/// </summary>
|
||
public static void RiskCalc()
|
||
{
|
||
lock (_RefrshLock)
|
||
{
|
||
using (var db = new YLContext())
|
||
{
|
||
RestInitData();
|
||
|
||
#region 获取当前持仓 标的当前价格 当前未簿记流水 并计算Risk信息
|
||
|
||
//标的
|
||
var underlyingPrice = DataCacheProvider.GetUnderlyingDataSource();
|
||
//获取待计算交列表
|
||
var tradeList = _TradeSwapList;
|
||
//OTC持仓交易
|
||
//实时计算rsik
|
||
var volTypes = new List<string> { "持仓" };
|
||
var riskList = RealTimeRiskCalc(_ValueDate, tradeList, underlyingPrice, volTypes);
|
||
#endregion
|
||
}
|
||
}
|
||
}
|
||
/// <summary>
|
||
/// DMA预付金计算
|
||
/// </summary>
|
||
public static void CalcDMAMargin()
|
||
{
|
||
var baseUrl = Environment.GetEnvironmentVariable("BondOmsInterface_BaseUrl");
|
||
var calculateMarginUrl = "/marginAlgorithm/realTimeMarginCalc";
|
||
var balanceDate = valuedateBLL.ValueDate;
|
||
var preSettleDate = BLL.valuedateBLL.GetNonHolidayDefore(balanceDate.AddDays(-1));
|
||
CalculateMarginRequest request = new CalculateMarginRequest()
|
||
{
|
||
currentDate = balanceDate.ToString("yyyy-MM-dd"),
|
||
preSettleDate = preSettleDate.ToString("yyyy-MM-dd")
|
||
};
|
||
if (!string.IsNullOrEmpty(baseUrl))
|
||
{
|
||
var httpHelper = new HttpHelper(baseUrl, null);
|
||
// http 请求 Web项目接口
|
||
var result = httpHelper.PostRequestNoAuth<CalculateMarginRequest, CalculateMarginResponse>(calculateMarginUrl, request).Result;
|
||
if (result != null && !result.success)
|
||
{
|
||
LogFactory.GetLogger("DMA预付金计算").Info("互换DMA预付金计算失败:" + result.message);
|
||
}
|
||
}
|
||
}
|
||
#region 新互换实时持仓私有方法
|
||
private static void BondCalcApi(ClientPosition clientPosition)
|
||
{
|
||
var resp = BondCalcHepler.BondCalc(clientPosition.security_id, clientPosition.deal_full_price_avg * ConsGlobal.bondShowPriceMultiple ?? 0, "DP");
|
||
if (resp != null)
|
||
{
|
||
clientPosition.deal_yield_avg = resp.ytm * ConsGlobal.bondPriceMultiple;
|
||
_yLCache.StringSetWithNoPrefix<CalBondResult>("TRS-BondFullPrice:" + clientPosition.security_id, resp, TimeSpan.FromHours(1));
|
||
}
|
||
}
|
||
/// <summary>
|
||
/// 创建持仓
|
||
/// </summary>
|
||
/// <param name="clientId"></param>
|
||
/// <param name="underlyingCode"></param>
|
||
/// <param name="underlyingName"></param>
|
||
/// <param name="price"></param>
|
||
/// <param name="fullPrice"></param>
|
||
/// <param name="qty"></param>
|
||
/// <param name="comminsion"></param>
|
||
/// <param name="side"></param>
|
||
/// <returns></returns>
|
||
private static ClientPosition CreateClientPosition(ClientPosition clientPosition, int clientId, string underlyingCode, decimal price, decimal fullPrice, decimal qty, decimal comminsion, int side, decimal contractsize, int direction)
|
||
{
|
||
var underlyingName = DataCacheProvider.GetUnderlyingDataSource().GetData(underlyingCode)?.UnderlyingName;
|
||
var client = DataCacheProvider.GetClientDataSource().GetData(clientId);
|
||
if (clientPosition == null)
|
||
{
|
||
clientPosition = new ClientPosition()
|
||
{
|
||
create_time = DateTime.Now,
|
||
};
|
||
}
|
||
clientPosition.client_id = clientId;
|
||
clientPosition.client_name = client?.Name;
|
||
clientPosition.security_id = underlyingCode;
|
||
clientPosition.symbol = underlyingName;
|
||
clientPosition.deal_price_avg = price;
|
||
clientPosition.deal_full_price_avg = fullPrice;
|
||
clientPosition.position_qty = qty;
|
||
clientPosition.commission = comminsion;
|
||
clientPosition.side = side;
|
||
clientPosition.create_time = DateTime.Now;
|
||
clientPosition.client_user_id = 0;
|
||
clientPosition.position_notional_principal = fullPrice * qty * 10000 * contractsize * ConsGlobal.bondPriceMultiple;
|
||
clientPosition.direction = direction;
|
||
return clientPosition;
|
||
}
|
||
/// <summary>
|
||
/// 合并互换流水
|
||
/// </summary>
|
||
/// <param name="swapFlows"></param>
|
||
/// <returns></returns>
|
||
private static List<swap_flow_merge> MergeSwapFlow(List<swap_flow> swapFlows, decimal multiplier)
|
||
{
|
||
List<swap_flow_merge> list = new List<swap_flow_merge>();
|
||
var newSwapFlowsGroups = swapFlows.GroupBy(g => g.BsType);
|
||
foreach (var gourpItem in newSwapFlowsGroups)
|
||
{
|
||
var swapflow = gourpItem.First();
|
||
swap_flow_merge swap_flow_summary = new swap_flow_merge()
|
||
{
|
||
OccurTime = swapflow.OccurTime.Value,
|
||
FundAccount = swapflow.FundAccount,
|
||
SwapTradeId = swapflow.SwapTradeId,
|
||
SwapTradeNo = swapflow.SwapTradeNo,
|
||
UnderlyingCode = swapflow.UnderlyingCode,
|
||
BsType = swapflow.BsType,
|
||
TradingQty = gourpItem.Sum(s => s.TradingQty),
|
||
TradingFeePending = gourpItem.Sum(s => s.TradingFee),
|
||
DataState = (int)SwapFlowDateStateEnum.等待完成,
|
||
ContractSize = swapflow.ContractSize,
|
||
ClientId = swapflow.ClientId,
|
||
TradingAmount = gourpItem.Sum(s => s.TradingAmount)
|
||
};
|
||
int tradeSide = swap_flow_summary.BsType == (int)EnumDirection.Long ? 1 : -1;
|
||
swap_flow_summary.FirstFlowTime = swapflow.OptTime;
|
||
swap_flow_summary.SettleDate = gourpItem.Max(s => s.SettleDate);
|
||
swap_flow_summary.TradingAmountAvg = swap_flow_summary.TradingQty == 0 ? 0 : gourpItem.Sum(s => s.TradingAmountAvg * s.TradingQty) / swap_flow_summary.TradingQty;
|
||
swap_flow_summary.TradingAmountAvg = Math.Round(swap_flow_summary.TradingAmountAvg, ConsGlobal.PriceRound, MidpointRounding.AwayFromZero);
|
||
swap_flow_summary.TradingAmountFeeAvg = swap_flow_summary.TradingQty == 0 ? swap_flow_summary.TradingAmountAvg : swap_flow_summary.TradingAmountAvg + swap_flow_summary.TradingFeePending * tradeSide / swap_flow_summary.TradingQty;
|
||
swap_flow_summary.TradingAmountFeeAvg = Math.Round(swap_flow_summary.TradingAmountFeeAvg, ConsGlobal.PriceRound, MidpointRounding.AwayFromZero);
|
||
swap_flow_summary.TradingAmountNetAvg = swap_flow_summary.TradingQty == 0 ? 0 : gourpItem.Sum(s => s.TradingAmountNet * s.TradingQty) / swap_flow_summary.TradingQty;
|
||
swap_flow_summary.TradingAmountNetAvg = Math.Round(swap_flow_summary.TradingAmountNetAvg ?? 0, ConsGlobal.PriceRound);
|
||
swap_flow_summary.TradingAmountNetFeeAvg = swap_flow_summary.TradingQty == 0 ? swap_flow_summary.TradingAmountNetAvg : swap_flow_summary.TradingAmountNetAvg + swap_flow_summary.TradingFeePending * tradeSide / swap_flow_summary.TradingQty;
|
||
swap_flow_summary.TradingAmountNetFeeAvg = Math.Round(swap_flow_summary.TradingAmountNetFeeAvg ?? 0, ConsGlobal.PriceRound);
|
||
swap_flow_summary.TradingAmountAvg *= multiplier;
|
||
swap_flow_summary.TradingAmountFeeAvg *= multiplier;
|
||
swap_flow_summary.TradingAmountNetAvg *= multiplier;
|
||
swap_flow_summary.TradingAmountNetFeeAvg *= multiplier;
|
||
list.Add(swap_flow_summary);
|
||
}
|
||
return list.OrderByDescending(o => o.TradingQty).ToList();
|
||
}
|
||
/// <summary>
|
||
/// 流水合并持仓
|
||
/// </summary>
|
||
/// <param name="flowMerges"></param>
|
||
/// <param name="positionType"></param>
|
||
/// <param name="clientPosition"></param>
|
||
/// <param name="posiQty"></param>
|
||
private static void MergeSwapPosition(List<swap_flow_merge> flowMerges, PositionTypeFlag positionType, ClientPosition clientPosition, decimal posiQty)
|
||
{
|
||
var flowMergeSame = flowMerges.FirstOrDefault(x => x.BsType == (int)positionType);
|
||
var flowMergeNagetive = flowMerges.FirstOrDefault(x => x.BsType != (int)positionType);
|
||
var sameQty = posiQty;
|
||
if (flowMergeSame != null)
|
||
{
|
||
sameQty += flowMergeSame.TradingQty;
|
||
}
|
||
var allPosiQty = sameQty;
|
||
if (flowMergeNagetive != null)
|
||
{
|
||
allPosiQty -= flowMergeNagetive.TradingQty;
|
||
if (allPosiQty >= 0)
|
||
{
|
||
var priceResult = CalcWeightedAverage(clientPosition, flowMergeSame);
|
||
clientPosition.deal_full_price_avg = priceResult.Item1;
|
||
}
|
||
else
|
||
{
|
||
clientPosition.deal_full_price_avg = flowMergeNagetive.TradingAmountAvg;
|
||
}
|
||
}
|
||
else
|
||
{
|
||
var priceResult = CalcWeightedAverage(clientPosition, flowMergeSame);
|
||
clientPosition.deal_full_price_avg = priceResult.Item1;
|
||
}
|
||
clientPosition.commission = flowMerges.Sum(s => s.TradingFee);
|
||
clientPosition.position_qty = Math.Abs(allPosiQty / 10000);
|
||
clientPosition.position_notional_principal = Math.Abs(allPosiQty) * clientPosition.deal_full_price_avg * ConsGlobal.bondPriceMultiple;
|
||
if (allPosiQty < 0)
|
||
{
|
||
clientPosition.side = clientPosition.side == 0 ? 1 : 0;
|
||
}
|
||
}
|
||
private static (decimal, decimal) CalcWeightedAverage(ClientPosition clientPosition, swap_flow_merge flowMergeSame)
|
||
{
|
||
var originalAmount = (clientPosition.deal_full_price_avg ?? 0) * (clientPosition.position_qty ?? 0);
|
||
var originalNetAmount = (clientPosition.deal_price_avg ?? 0) * (clientPosition.position_qty ?? 0);
|
||
if (flowMergeSame == null)
|
||
{
|
||
return (clientPosition.deal_full_price_avg ?? 0, clientPosition.deal_price_avg ?? 0);
|
||
}
|
||
var sameAmount = flowMergeSame.TradingAmountAvg * flowMergeSame.TradingQty;
|
||
var sameNetAmount = (flowMergeSame.TradingAmountNetAvg ?? 0) * flowMergeSame.TradingQty;
|
||
var totalQty = (clientPosition.position_qty ?? 0) + flowMergeSame.TradingQty;
|
||
if (totalQty == 0)
|
||
{
|
||
return (0, 0);
|
||
}
|
||
var price = (originalAmount + sameAmount) / totalQty;
|
||
var priceFee = (originalNetAmount + sameNetAmount) / totalQty;
|
||
price = Math.Round(price, ConsGlobal.PriceRound, MidpointRounding.AwayFromZero);
|
||
priceFee = Math.Round(priceFee, ConsGlobal.PriceRound, MidpointRounding.AwayFromZero);
|
||
return (price, priceFee);
|
||
}
|
||
/// <summary>
|
||
/// 从缓存中拿取债券价格
|
||
/// </summary>
|
||
/// <param name="clientPosition"></param>
|
||
private static void SetClientPositionPrice(ClientPosition clientPosition)
|
||
{
|
||
if (clientPosition.position_qty <= 0)
|
||
{
|
||
return;
|
||
}
|
||
try
|
||
{
|
||
//TRS-BondDepthMarket:160010.IB-0
|
||
var bondPrice = EodPriceQueryService.GetChinaBondPrice(valuedateBLL.ValueDate, clientPosition.security_id);
|
||
if (bondPrice != null)
|
||
{
|
||
clientPosition.full_price_now = bondPrice.dirty_price_close;
|
||
clientPosition.swap_market_value = clientPosition.full_price_now * clientPosition.position_qty * 10000 * (clientPosition.side == 0 ? 1 : -1) * ConsGlobal.bondPriceMultiple;
|
||
clientPosition.position_profit_loss = (clientPosition.full_price_now - clientPosition.deal_full_price_avg) * clientPosition.position_qty * 10000 * ConsGlobal.bondPriceMultiple * (clientPosition.side == 0 ? 1 : -1);
|
||
clientPosition.price_now = bondPrice.net_price;
|
||
clientPosition.yield_now = bondPrice.yield * ConsGlobal.bondPriceMultiple;
|
||
}
|
||
else
|
||
{
|
||
clientPosition.full_price_now = 100;
|
||
clientPosition.swap_market_value = clientPosition.full_price_now * clientPosition.position_qty * 10000 * (clientPosition.side == 0 ? 1 : -1) * ConsGlobal.bondPriceMultiple;
|
||
clientPosition.position_profit_loss = (clientPosition.full_price_now - clientPosition.deal_full_price_avg) * clientPosition.position_qty * 10000 * ConsGlobal.bondPriceMultiple * (clientPosition.side == 0 ? 1 : -1);
|
||
clientPosition.price_now = 100;
|
||
clientPosition.yield_now = 0.01m;
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
|
||
}
|
||
|
||
}
|
||
/// <summary>
|
||
/// 计算债券价格
|
||
/// </summary>
|
||
/// <param name="clientPosition"></param>
|
||
private static void CalcBandPrice(ClientPosition clientPosition)
|
||
{
|
||
if (clientPosition.position_qty <= 0 || _yLCache == null)
|
||
{
|
||
return;
|
||
}
|
||
try
|
||
{
|
||
//TRS-BondDepthMarket:160010.IB-0
|
||
CalBondResult bondPrice = _yLCache.StringGetWithNoPrefix<CalBondResult>("TRS-BondFullPrice:" + clientPosition.security_id);
|
||
if (bondPrice?.dirtyPrice != clientPosition.deal_full_price_avg)
|
||
{
|
||
BondCalcApi(clientPosition);
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
|
||
}
|
||
}
|
||
#endregion
|
||
/// <summary>
|
||
/// 实时risk计算
|
||
/// </summary>
|
||
public static List<realtime_trade_risk> RealTimeRiskCalc(DateTime valueDate, List<trade> tradeList, IPriceProvider priceProvider, List<string> volTypes = null, bool useSave = true, PricingRequest pricingRequest = QdpPricingRequest.BASIC_GREEKS)
|
||
{
|
||
// LogFactory.GetLogger<RealtimePnlCalc>().Info("CalcTradeValue Run Time:" + DateTime.Now.ToString());
|
||
//获取所有已确认交易信息
|
||
using (var db = new YLContext())
|
||
{
|
||
var risks = new List<realtime_trade_risk>();
|
||
if (tradeList == null || !tradeList.Any())
|
||
{
|
||
return risks;
|
||
}
|
||
|
||
//计算Risk
|
||
if (volTypes == null || !volTypes.Any())
|
||
{
|
||
volTypes = new List<string> { ConsGlobal.VolType.PositionVol };
|
||
}
|
||
var tradeIds = tradeList.Select(t => t.id).ToList();
|
||
var tradeCashList = db.trade_cash.AsNoTracking().Where(t => t.ValidState != "InValid" && !t.IsDeleted && tradeIds.Contains(t.TradeId) && (t.Action == ClientCashInCashOut.系统操作_平仓费 || t.Action == ClientCashInCashOut.系统操作_行权费)).ToList();
|
||
foreach (var volType in volTypes)
|
||
{
|
||
var resultRisks = new List<realtime_trade_risk>();
|
||
var isUseTradeVol = (volType == "持仓" || volType == "对冲") && PS.Config.IsTradeVol;
|
||
var tradeRiskResult = CalculatorHelper.CalculateRisksForTrades(new CalculateRisksForTradesReq
|
||
{
|
||
valueDate = valueDate,
|
||
tradeList = tradeList,
|
||
priceProvider = priceProvider,
|
||
pricingRequest = pricingRequest,
|
||
volType = volType,
|
||
isUseTradeVol = isUseTradeVol,
|
||
addVolRateDic = null,
|
||
isAddVolPercent = true,
|
||
isMarginCalc = false,
|
||
overrideVolsForTrade = null,
|
||
PreciseTimeMode = true,
|
||
calcScenario = Enums.CalcScenarioEnum.RealtimePosition,
|
||
canUseManual = true
|
||
});
|
||
|
||
if (tradeRiskResult != null)
|
||
{
|
||
var eodForwardMargins = db.eod_forward_margin.Where(x => x.ValueDate == valueDate && tradeIds.Contains(x.TradeId)).ToList();
|
||
var forwards = db.trade_forward.Where(n => tradeIds.Contains(n.TradeId)).ToList();
|
||
foreach (var result in tradeRiskResult.Results)
|
||
{
|
||
try
|
||
{
|
||
var tempTradeCashList = tradeCashList.Where(t => t.TradeId == result.Trade.id).ToList();
|
||
var pnl = 0.0;// 当日盈亏
|
||
var tradePosition = _LastTradePositionList.FirstOrDefault(t => t.TradeId == result.Trade.id);
|
||
if (tradePosition != null)
|
||
{
|
||
pnl = (double.IsNaN(result.ValueResult.Pv) ? 0.0 : (result.ValueResult.Pv)) - Convert.ToDouble(tradePosition.Pv);
|
||
}
|
||
else
|
||
{
|
||
//var futureCommission = tradeCommissionDict.ContainsKey(result.Trade.id) ? tradeCommissionDict[result.Trade.id] : 0;
|
||
var isUnOption = ConsTrade.TradeTypesForHedge.Contains(result.Trade.TradeType);
|
||
var cost = (isUnOption ? (result.Trade.TradePrice ?? 0.0) : (result.Trade.TradeSinglePrice ?? 0)) * result.Trade.Notional;
|
||
if (result.Trade.TradeType == "远期")
|
||
{
|
||
pnl = NumberHelper.Normalize(result.ValueResult.Pv) + cost;// - futureCommission;
|
||
}
|
||
else
|
||
{
|
||
pnl = NumberHelper.Normalize(result.ValueResult.Pv) + cost * TradeCalcHelper.GetSign(result.Trade.BuySell) * -1;// - futureCommission;
|
||
}
|
||
}
|
||
for (var i = 0; i < result.Underlyings.Length; ++i)
|
||
{
|
||
resultRisks.Add(createRisk(result.Trade, valueDate, result.Underlyings[i], null, volType, result.ValueResult, pnl, i, priceProvider.GetPrice(result.Trade.UnderlyingCode), tempTradeCashList, eodForwardMargins, forwards));
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
System.Diagnostics.Debug.WriteLine(ex);
|
||
}
|
||
}
|
||
}
|
||
if (useSave)
|
||
{
|
||
//删除非交易日当天的实时持仓数据
|
||
var sql = $"{nameof(realtime_trade_risk.ValueDate)}!='{valueDate.ToSqlDate()}'";
|
||
db.BulkDelete<realtime_trade_risk>(sql);
|
||
|
||
//插入数据库还未有过的实时持仓服务计算的数据(根据TradeId,VolType,ValueDate筛选)
|
||
var tradeIdsExist = db.realtime_trade_risk.Where(x => x.VolType == volType && x.ValueDate == valueDate).Select(x => x.TradeId).ToList();
|
||
var resultRisksInsert = resultRisks.Where(x => !tradeIdsExist.Contains(x.TradeId));
|
||
MySqlBulkExtensions.BulkInsert(db, resultRisksInsert);
|
||
|
||
//根据TradeId,VolType,ValueDate更新已经存在的数据
|
||
var tradeids = resultRisks.Select(t => t.TradeId).ToList();
|
||
var tradeRisks = db.realtime_trade_risk.Where(x => tradeids.Contains(x.TradeId) && x.VolType == volType && x.ValueDate == valueDate).ToList();
|
||
var delTradeRisks = db.realtime_trade_risk.Where(x => !tradeids.Contains(x.TradeId) && x.VolType == volType && x.ValueDate == valueDate).ToList();
|
||
db.realtime_trade_risk.RemoveRange(delTradeRisks);
|
||
for (var i = 0; i < tradeRisks.Count; i++)
|
||
{
|
||
var tradeRisk = resultRisks.FirstOrDefault(x => x.TradeId == tradeRisks[i].TradeId)?.Clone();
|
||
tradeRisk.id = tradeRisks[i].id;
|
||
db.Entry(tradeRisks[i]).CurrentValues.SetValues(tradeRisk);
|
||
}
|
||
db.SaveChanges();
|
||
}
|
||
if (volType == "持仓")
|
||
{
|
||
risks = resultRisks;
|
||
}
|
||
}
|
||
return risks;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 刷新RealTimeRisk表对应交易记录和实时持仓信息(适用于OTC场内期权交易)
|
||
/// </summary>
|
||
public static void RefreshRiskAndIntradayPositionForOtc(List<trade> tradeList, OptUserInfo user)
|
||
{
|
||
if (tradeList is null)
|
||
{
|
||
throw new ArgumentNullException(nameof(tradeList));
|
||
}
|
||
|
||
if (user is null)
|
||
{
|
||
throw new ArgumentNullException(nameof(user));
|
||
}
|
||
|
||
if (tradeList != null && tradeList.Any())
|
||
{
|
||
new TradeDalService(user).SetSubTradeList(tradeList);
|
||
using (var db = new YLContext())
|
||
{
|
||
RestInitData();
|
||
|
||
//获取停牌信息
|
||
var suspensionUnderlyingIdList = underlying_managerBLL.GetQuery().Where(t => t.UnderlyingStatus == underlying_manager.Status_Suspension).Select(t => t.id).ToList();
|
||
|
||
#region 获取昨日持仓 标的当前价格 当前持仓以及当日行权交易 并计算Risk信息
|
||
var clientTrades = db.trade.AsNoTracking().Where(t => t.ValidState != "InValid" && t.TradeDate <= valuedateBLL.ValueDate//有效的交易
|
||
&& ((ConsTrade.LiveTradeStatusList.Contains(t.TradeStatus) && (t.ExerciseDate >= _ValueDate || (suspensionUnderlyingIdList.Contains(t.UnderlyingId) && !ConsTrade.TradeTypesForHedge.Contains(t.TradeType))) && !ConsTrade.TradeTypesForHedge.Contains(t.TradeType)) //未过期的(包括股票停牌)期权持仓交易
|
||
|| (t.TradeDate > _LastSettleDate && ConsTrade.TradeTypesForHedge.Contains(t.TradeType)) //最后结算日到当日介于区间的对冲交易
|
||
|| (t.UnWindDate > _LastSettleDate && t.UnWindDate <= _ValueDate && ConsTrade.TradeCompleteStatus.Contains(t.TradeStatus)) //最后结算日到当日区间内平仓到期执行的交易
|
||
)
|
||
&& ConsTrade.TradeTypesForOtcOptions.Contains(t.TradeType)).ToList();
|
||
|
||
//昨日持仓交易
|
||
var tradeIdList = tradeList.Select(t => t.id).ToList();
|
||
var underlyingIds = tradeList.Select(t => t.UnderlyingId).Distinct().ToList();
|
||
|
||
//标的
|
||
var underlyingPrice = DataCacheProvider.GetUnderlyingDataSource();
|
||
//设置option info
|
||
tradeBLL.SetFieldsByTradeType(tradeList);
|
||
|
||
//OTC 交易
|
||
var otcTradeList = tradeList.Where(t => t.TradeType != "结构化交易" && t.ClientId > 0).ToList();
|
||
|
||
//预付金计算
|
||
//远期不参与计算预付金逻辑
|
||
var req = new RunMarginCalculationReq(user)
|
||
{
|
||
tradeList = otcTradeList,
|
||
settleDate = _ValueDate,
|
||
PriceProvider = underlyingPrice,
|
||
hasOptionInfo = true
|
||
};
|
||
|
||
|
||
var tradeSpans = MarginDefault.RunMarginCalculation(req.Clone(forOtherSide: false));
|
||
//试算出交易买卖方向反向的tradeSpan(目前只有国君有这个需求,做过处理)
|
||
List<trade_span> tradeSpansOtherSide = new List<trade_span>();
|
||
|
||
if (MarginDefault.IsMarginCalcNeedSpecial(_ValueDate))
|
||
{
|
||
//试算出交易买卖方向反向的tradeSpan(目前只有国君有这个需求,做过处理)
|
||
tradeSpansOtherSide = MarginDefault.RunMarginCalculation(req.Clone(forOtherSide: true));
|
||
}
|
||
|
||
//根据交易预付金合计客户预付金计入client_span
|
||
var req2 = new CalcClientMarginReq(user)
|
||
{
|
||
settleDate = _ValueDate,
|
||
tradeSpans = tradeSpans,
|
||
tradeSpansOtherSide = tradeSpansOtherSide,
|
||
SpanType = ClientSpan.SpanType_RealTime
|
||
};
|
||
MarginDefault.CalcClientMargin(req2);
|
||
|
||
//实时计算rsik
|
||
var volTypes = new List<string> { "持仓" };
|
||
if (PS.Config.Is国投)
|
||
{
|
||
volTypes.Add("开仓");
|
||
}
|
||
var riskList = RealTimeRiskCalc(_ValueDate, otcTradeList, underlyingPrice, volTypes);
|
||
|
||
#endregion
|
||
|
||
#region 平仓费行权费 交易预付金计算 昨日持仓对冲交易过滤
|
||
var unWindTradeCashAction = new List<string> { ClientCashInCashOut.系统操作_平仓费, ClientCashInCashOut.系统操作_行权费, ClientCashInCashOut.系统操作_期权费 };
|
||
|
||
//获取平仓到期交易的trade_cash;
|
||
var tradeCashList = db.trade_cash.Where(t => tradeIdList.Contains(t.TradeId) && unWindTradeCashAction.Contains(t.Action) && !t.IsDeleted).ToList();
|
||
|
||
//实时结果对象类
|
||
var realtimePositionList = new List<intraday_trade_position>();
|
||
#endregion
|
||
|
||
#region 当日持仓期权交易 已行权已到期期权交易 以及当日对冲交易计算持仓
|
||
//循环交易 OTC交易
|
||
otcTradeList.ForEach(t =>
|
||
{
|
||
var Pv = 0.0; //当日pv
|
||
var RoundedPv = 0.0;
|
||
var LastPv = 0.0; //昨日pv
|
||
var Quantity = 0.0; //数量
|
||
var RealizedPnL = 0.0;//已归因盈亏
|
||
var DailyPnl = 0.0; //当日盈亏
|
||
var UnRealizedPnL = 0.0;//未归因盈亏
|
||
var TotalPnL = 0.0; //总盈亏
|
||
var PositionPnL = 0.0; //持仓盈亏
|
||
var RoundedPositionPnL = 0.0;
|
||
var NotionalPrincipal = 0.0;
|
||
var ActualPv = 0.0; //当日实际pv
|
||
var ActualLastPv = 0.0; //昨日实际pv
|
||
var ActualDailyPnl = 0.0; //当日实际浮动盈亏
|
||
var ActualTotalPnL = 0.0; //实际总盈亏
|
||
|
||
//部分平仓计算
|
||
var tempTradeCashList = tradeCashList.Where(a => a.TradeId == t.id);
|
||
//已实现
|
||
var RealizeProfitAndloss = (tempTradeCashList.Any() ? tempTradeCashList.Sum(a => a.Amount) : 0.0);
|
||
|
||
//预付金
|
||
var tradeSpan = tradeSpans.FirstOrDefault(a => a.TradeId == t.id);
|
||
//已实现盈亏
|
||
var positionRealizedTradeCashs = db.trade_cash.Where(x => (x.Action == "系统操作-票息" || x.Action == "系统操作-互换") && t.ValidState != "InValid" && x.ValueDate <= _ValueDate && (x.TradeId == t.id || x.ParentTradeId == t.id)).ToList();
|
||
|
||
if (ConsTrade.LiveTradeStatusList.Contains(t.TradeStatus))
|
||
{
|
||
if (riskList.Any(a => a.TradeId == t.id))
|
||
{
|
||
var openValueResult = riskList.FirstOrDefault(a => a.TradeId == t.id && a.VolType == "开仓");
|
||
if (PS.Config.Is国投 && openValueResult != null)
|
||
{
|
||
Pv = openValueResult.Pv ?? 0;
|
||
RoundedPv = openValueResult.RoundedPv ?? 0;
|
||
PositionPnL = openValueResult.PositionPnl ?? 0;
|
||
RoundedPositionPnL = openValueResult.RoundedPositionPnl ?? 0;
|
||
}
|
||
else
|
||
{
|
||
var valueResult = riskList.FirstOrDefault(a => a.TradeId == t.id);
|
||
Pv = valueResult.Pv ?? 0;
|
||
RoundedPv = valueResult.RoundedPv ?? 0;
|
||
PositionPnL = valueResult.PositionPnl ?? 0;
|
||
RoundedPositionPnL = valueResult.RoundedPositionPnl ?? 0;
|
||
}
|
||
}
|
||
else
|
||
{
|
||
Pv = 0.0;
|
||
RoundedPv = 0.0;
|
||
}
|
||
//var valueResult = holdRisResult.ContainsKey(t.id) ? holdRisResult[t.id] : null;
|
||
//if (valueResult == null) continue;
|
||
var lastEodPosition = _LastTradePositionList.FirstOrDefault(e => e.TradeId == t.id);
|
||
//if (lastEodPosition != null) _LastTradePositionList.Remove(lastEodPosition);
|
||
//昨日pv
|
||
LastPv = lastEodPosition == null ? 0 : Convert.ToDouble(lastEodPosition.Pv);
|
||
//ActualLastPv = lastEodPosition == null ? 0 : Convert.ToDouble(lastEodPosition.ActualPv ?? 0.0);
|
||
//总持仓量
|
||
Quantity = lastEodPosition == null ? t.Notional : lastEodPosition.Amount;
|
||
//归因盈亏
|
||
RealizedPnL = lastEodPosition != null ? 0 : ((t.TradeSinglePrice ?? 0) * t.Notional * TradeCalcHelper.GetSign(t.BuySell) * (-1));
|
||
//未归因盈亏
|
||
UnRealizedPnL = Pv - LastPv;
|
||
//估值盈亏
|
||
DailyPnl = RealizedPnL + UnRealizedPnL;
|
||
//名义本金 如果是股票则取名义本金字段 如果为商品期货则去 spotprice * 份额
|
||
NotionalPrincipal = "Stock".Equals(t.UnderlyingInstrumentType) ? t.StockEqvNotional : (t.Notional * (t.SpotPrice ?? 0.0));
|
||
//根据看涨看跌以及当前价格与行权价涨跌判断是否有实际pv
|
||
var callPutFlag = "Call".Equals(t.CallPut);
|
||
var unSpotPrice = underlyingPrice.GetPrice(t.UnderlyingCode);
|
||
var StrikeFlag = unSpotPrice > (t.IsMoneynessOptionData ? (t.Strike * t.SpotPrice) : t.Strike ?? 0.0);
|
||
//实值pv计算
|
||
ActualPv = 0.0;
|
||
if (callPutFlag == StrikeFlag)
|
||
{
|
||
var Strike = t.IsMoneynessOptionData ? ((t.Strike ?? 0.0) * t.SpotPrice ?? 0.0) : t.Strike ?? 0.0;
|
||
var SpotPrice = t.SpotPrice ?? 0;
|
||
if (SpotPrice > 0)
|
||
{
|
||
ActualPv = t.StockEqvNotional * Math.Max((unSpotPrice - Strike) * (callPutFlag ? 1 : -1), 0) / SpotPrice * TradeCalcHelper.GetSign(t.BuySell);
|
||
}
|
||
}
|
||
//实值浮动盈亏
|
||
//ActualDailyPnl = ActualPv - ActualLastPv;
|
||
//实值总盈亏
|
||
//ActualTotalPnL = (lastEodPosition == null ? 0 : lastEodPosition.ActualTotalPnL ?? 0.0) + ActualDailyPnl;
|
||
//总盈亏
|
||
TotalPnL = (lastEodPosition == null ? 0 : lastEodPosition.TotalPnL) + DailyPnl;
|
||
//PositionPnL = Pv + RealizeProfitAndloss;
|
||
}
|
||
else if (ConsTrade.TradeCompleteStatus.Contains(t.TradeStatus))
|
||
{//已执行 已平仓 已到期的交易计算持仓信息
|
||
|
||
var tradeCash = tradeCashList.FirstOrDefault(a => a.TradeId == t.id && a.Action != ClientCashInCashOut.系统操作_期权费);
|
||
Pv = ActualPv = tradeCash == null ? 0.0 : tradeCash.Amount;
|
||
RoundedPv = Pv;
|
||
var lastEodPosition = _LastTradePositionList.FirstOrDefault(e => e.TradeId == t.id);
|
||
//if (lastEodPosition != null) _LastTradePositionList.Remove(lastEodPosition);
|
||
//昨日pv
|
||
LastPv = ActualLastPv = lastEodPosition == null ? 0 : Convert.ToDouble(lastEodPosition.Pv);
|
||
//总持仓量
|
||
Quantity = lastEodPosition == null ? t.Notional : lastEodPosition.Amount;
|
||
//归因盈亏
|
||
RealizedPnL = Pv;
|
||
//未归因盈亏
|
||
UnRealizedPnL = -LastPv;
|
||
//估值盈亏
|
||
DailyPnl = RealizedPnL + UnRealizedPnL;
|
||
//名义本金 如果是股票则取名义本金字段 如果为商品期货则去 spotprice * 份额
|
||
NotionalPrincipal = "Stock".Equals(t.UnderlyingInstrumentType) ? t.StockEqvNotional : (t.Notional * (t.SpotPrice ?? 0.0));
|
||
//根据看涨看跌以及当前价格与行权价涨跌判断是否有实际pv
|
||
//实值pv计算
|
||
ActualDailyPnl = ActualPv - ActualLastPv;
|
||
//总盈亏
|
||
ActualTotalPnL = TotalPnL = Pv - ((t.TradeSinglePrice ?? 0.0) * t.Notional * TradeCalcHelper.GetSign(t.BuySell));
|
||
TotalPnL = PositionPnL = RoundedPositionPnL = Pv + RealizeProfitAndloss;
|
||
}
|
||
|
||
var Margin = tradeSpan == null ? 0.0 : double.IsNaN(tradeSpan.WorstCastClientPayable ?? double.NaN) ? 0 : tradeSpan.WorstCastClientPayable.Value;
|
||
//收益互换 应缴预付金 初始预付金 盈亏
|
||
var SwapMargin = t.TradeType == "收益互换" ? (tradeSpan == null ? 0.0 : double.IsNaN(tradeSpan.WorstCastClientPayable ?? double.NaN) ? 0 : tradeSpan.WorstCastClientPayable.Value) : 0.0;
|
||
var SwapInitMargin = t.TradeType == "收益互换" ? (tradeSpan == null ? 0.0 : double.IsNaN(tradeSpan.SwapInitMargin ?? double.NaN) ? 0 : tradeSpan.SwapInitMargin.Value) : 0.0;
|
||
var SwapWinLoss = t.TradeType == "收益互换" ? (tradeSpan == null ? 0.0 : double.IsNaN(tradeSpan.SwapWinLoss ?? double.NaN) ? 0 : tradeSpan.SwapWinLoss.Value) : 0.0;
|
||
var SwapUnMargin = 0d;
|
||
|
||
//收益互换子交易 根据父交易预付金 按名义本金比例计算子交易预付金
|
||
if (t.TradeType == "收益互换")
|
||
{
|
||
//trade_span 追保==0 时 取客户浮亏作为互换可容忍金额
|
||
SwapUnMargin = tradeSpan == null || double.IsNaN(tradeSpan.SwapWinLoss ?? double.NaN) ? 0.0 : Math.Max(tradeSpan.Margin == 0 ? tradeSpan.SwapWinLoss.Value : 0.0, 0);
|
||
//收益互换名义本金
|
||
NotionalPrincipal = t.StockEqvNotional;
|
||
if (t.IsGroup == 2 && tradeSpans.Any(span => span.TradeId == t.ParentTradeId) && otcTradeList.Any(o => o.id == t.ParentTradeId))
|
||
{
|
||
var parentTrade = otcTradeList.Find(o => o.id == t.ParentTradeId);
|
||
var parentSpan = tradeSpans.Find(span => span.TradeId == t.ParentTradeId);
|
||
if (parentTrade.StockEqvNotional != 0)
|
||
{
|
||
SwapMargin = Margin = (parentSpan.WorstCastClientPayable ?? 0.0) * t.StockEqvNotional / parentTrade.StockEqvNotional;
|
||
SwapInitMargin = (parentSpan.SwapInitMargin ?? 0.0) * t.StockEqvNotional / parentTrade.StockEqvNotional;
|
||
SwapWinLoss = (parentSpan.SwapWinLoss ?? 0.0) * t.StockEqvNotional / parentTrade.StockEqvNotional;
|
||
}
|
||
}
|
||
}
|
||
|
||
realtimePositionList.Add(
|
||
new intraday_trade_position
|
||
{
|
||
TradeId = t.id,
|
||
TradeNumber = t.TradeNumber,
|
||
ValueDate = _ValueDate,
|
||
BookId = t.AssetId,
|
||
TradeType = t.TradeType,
|
||
ClientId = t.ClientId,
|
||
UnderlyingCode = t.UnderlyingCode,
|
||
BuySell = t.BuySell,
|
||
Cost = (t.OriginalNotional > 0 ? t.TradePrice * t.Notional / t.OriginalNotional : t.TradePrice) ?? 0,
|
||
Notional = NotionalPrincipal,
|
||
Amount = Quantity,
|
||
LastPv = (decimal)LastPv,
|
||
Pv = (decimal)Pv,
|
||
RoundedPv = (decimal)RoundedPv,
|
||
DailyPnL = (decimal)DailyPnl,
|
||
RealizedPnL = RealizedPnL,
|
||
TotalPnL = TotalPnL,
|
||
PositionPnL = PositionPnL,
|
||
RoundedPositionPnl = RoundedPositionPnL,
|
||
ActualPv = ActualPv,
|
||
ActualLastPv = ActualLastPv,
|
||
ActualDailyPnL = ActualDailyPnl,
|
||
ActualTotalPnL = ActualTotalPnL,
|
||
Margin = Margin,
|
||
PositionRelizedAmount = positionRealizedTradeCashs.Sum(x => x.Amount),
|
||
TradeStatus = t.TradeStatus,
|
||
OptDate = DateTime.Now,
|
||
OptId = user.UserId,
|
||
OptName = user.UserName,
|
||
ParentTradeId = t.ParentTradeId,
|
||
//收益互换 应缴预付金 初始预付金 盈亏
|
||
SwapInitMargin = SwapInitMargin,
|
||
SwapWinLoss = SwapWinLoss,
|
||
SwapUnMargin = SwapUnMargin
|
||
});
|
||
});
|
||
#endregion
|
||
//对冲唯一标识
|
||
//var hedgeUniqueCodeList = realtimePositionList.Where(t => trade.TradeTypesForHedge.Contains(t.TradeType)).Select(t => t.HedgeUniqueCode).ToList();
|
||
var removePosition = db.intraday_trade_position.Where(t => t.ValueDate == _ValueDate && (tradeIdList.Contains(t.TradeId)));
|
||
if (removePosition.Any())
|
||
{
|
||
db.intraday_trade_position.RemoveRange(removePosition);
|
||
}
|
||
|
||
db.intraday_trade_position.AddRange(realtimePositionList);
|
||
//if (removePosition != null) MySqlBulkExtensions.BulkDelete(db, removePosition);
|
||
//MySqlBulkExtensions.BulkInsert(db, realtimePositionList);
|
||
db.SaveChanges();
|
||
}
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 实时风险计算模型转换
|
||
/// </summary>
|
||
private static realtime_trade_risk createRisk(trade tradeObj, DateTime valueDate, underlying_manager udm, Variety variety, string volType, TradeValueResult optionValueResult, double pnl, int i, double underlyingPrice, List<trade_cash> tradeCashList, List<eod_forward_margin> eodForwardMargins, List<trade_forward> forwards)
|
||
{
|
||
//todo:感觉这个表应该加上计算时使用的期末价格
|
||
if (udm == null)
|
||
{
|
||
udm = new underlying_manager();
|
||
}
|
||
|
||
var result = new realtime_trade_risk
|
||
{
|
||
ValueDate = valueDate,
|
||
TradeId = tradeObj.id,
|
||
Exposure = udm.UnderlyingCode,
|
||
VolType = volType,
|
||
BookId = tradeObj.AssetId + "",
|
||
ClientId = tradeObj.ClientId,
|
||
Pv = NumberExtensions.Norm(optionValueResult.Pv),
|
||
RoundedPv = NumberExtensions.Norm(optionValueResult.RoundedPv),
|
||
Theta = NumberExtensions.Norm(optionValueResult.Theta),
|
||
Rho = NumberExtensions.Norm(optionValueResult.Rho),
|
||
DdeltaDt = NumberExtensions.Norm(optionValueResult.DDeltaDt),
|
||
DdeltaDvol = NumberExtensions.Norm(optionValueResult.DDeltaDVol),
|
||
DvegaDt = NumberExtensions.Norm(optionValueResult.DVegaDt),
|
||
DvegaDvol = NumberExtensions.Norm(optionValueResult.DVegaDVol),
|
||
Vol = NumberExtensions.Norm(optionValueResult.Vol),
|
||
Pnl = pnl,
|
||
OptId = 1,
|
||
OptName = "管理员",
|
||
OptDate = DateTime.Now,
|
||
|
||
Delta = NumberExtensions.Norm(optionValueResult.GetDelta(i)),
|
||
Gamma = NumberExtensions.Norm(optionValueResult.GetGamma(i)),
|
||
Vega = NumberExtensions.Norm(optionValueResult.GetVega(i)),
|
||
DeltaCash = NumberExtensions.Norm(optionValueResult.GetDeltaCash(i)),
|
||
GammaCash = NumberExtensions.Norm(optionValueResult.GetGammaCash(i)),
|
||
VegaCash = NumberExtensions.Norm(optionValueResult.VegaCash),
|
||
|
||
UnderlyingPrice = optionValueResult.SpotPrice,
|
||
};
|
||
|
||
result.DeltaInLots = TradeLotsCalc.CalcDeltaInLots(result.Delta ?? 0, variety, udm);
|
||
var pvShift = tradeObj.TradeType == "雪球期权" && tradeObj.trade_snowball.PrepaymentUsed
|
||
? -tradeObj.Notional * (tradeObj.SpotPrice ?? 0) * (tradeObj.trade_snowball.PrepaymentRatio ?? 0) * (tradeObj.BuySell == "卖出" ? -1 : 1)
|
||
: 0;
|
||
if (tradeObj.TradeType == "收益互换")
|
||
{
|
||
result.PositionPnl = optionValueResult.Pv;
|
||
result.RoundedPositionPnl = optionValueResult.RoundedPv;
|
||
}
|
||
else
|
||
{
|
||
result.PositionPnl = EodOperationBase.GetPositionPnl((result.Pv ?? 0.0) + pvShift, tradeObj.TradePrice ?? 0.0, tradeObj.Notional, tradeObj.OriginalNotional ?? 0, tradeObj.BuySell);
|
||
result.RoundedPositionPnl = EodOperationBase.GetPositionPnl((result.RoundedPv ?? 0.0) + pvShift, tradeObj.TradePrice ?? 0.0, tradeObj.Notional, tradeObj.OriginalNotional ?? 0, tradeObj.BuySell);
|
||
}
|
||
result.RealizedPnl = optionValueResult.ExtendInfo.RealPnl;
|
||
|
||
|
||
if (tradeObj.TradeType == "远期")
|
||
{
|
||
using (var db = new YLContext())
|
||
{
|
||
var marginCost = eodForwardMargins.Where(x => x.TradeId == tradeObj.id)
|
||
.Select(n => (double?)n.MarginCost).FirstOrDefault() ?? 0;
|
||
|
||
var forward = forwards.FirstOrDefault(n => n.TradeId == tradeObj.id);
|
||
var unwindTradeCashList = tradeCashList.Where(t => t.TradeId == tradeObj.id && t.ValidState != "InValid" && !t.IsDeleted && t.Action != ClientCashInCashOut.系统操作_期权费 && t.ValueDate == valueDate).ToList();
|
||
//开仓费用 + 平仓费用
|
||
var positionCost = forward.OpenCommission * (tradeObj.Lots ?? 0) + unwindTradeCashList.Sum(a => (a.UnwindPrice ?? 0) * a.UnwindNotional.Value / tradeObj.OriginalNotional.Value * (tradeObj.Lots ?? 0));
|
||
//平仓比例
|
||
double unwindRatio = unwindTradeCashList.Any() ? unwindTradeCashList.Sum(a => a.UnwindNotional.Value) / tradeObj.OriginalNotional.Value : 0;
|
||
//持仓比例
|
||
double positionRatio = 1 - unwindRatio;
|
||
//实现盈亏 = (交易已平仓) 开仓总费用 * 平仓比例 + 平仓总费用
|
||
//实现盈亏 = (交易未平仓) 0
|
||
double totalFee = unwindTradeCashList.Any() ? (tradeObj.TradePrice ?? 0) * unwindRatio + unwindTradeCashList.Sum(a => a.Amount) : 0;
|
||
//持仓市值 = 交易员视角的合约总价值(远期合约价值+开仓费用+平仓费用)-交易员视角的已实现盈亏
|
||
result.Pv = result.Pv.Value;
|
||
result.RoundedPv = result.RoundedPv.Value;
|
||
//持仓盈亏 = 持仓市值+开仓总费用 * 持仓比例
|
||
result.PositionPnl = result.Pv.Value + (tradeObj.TradePrice ?? 0) * positionRatio + marginCost;
|
||
result.RoundedPositionPnl = result.RoundedPv + (tradeObj.TradePrice ?? 0) * positionRatio + marginCost;
|
||
result.RealizedPnl = totalFee;
|
||
result.Pnl += marginCost;
|
||
|
||
}
|
||
}
|
||
double? nowPrice = 0;
|
||
if (!string.IsNullOrWhiteSpace(tradeObj.UnderlyingCode))
|
||
{
|
||
var underlying = DataCacheProvider.GetUnderlyingDataSource().GetData(tradeObj.UnderlyingCode);
|
||
if (underlying == null)
|
||
{
|
||
throw new Exception($"未找到标的:{tradeObj.UnderlyingCode}");
|
||
}
|
||
else
|
||
{
|
||
nowPrice = underlying.Price;
|
||
}
|
||
}
|
||
|
||
result.CreditExposure = EodPositionRisksQueryService.ExposureCalc(tradeObj, nowPrice ?? 0, BLL.valuedateBLL.ValueDate);
|
||
if (result.UnderlyingPrice == null)
|
||
{
|
||
result.UnderlyingPrice = underlyingPrice;
|
||
}
|
||
if (tradeCashList != null && tradeCashList.Any() && tradeObj.TradeType != "远期")
|
||
{
|
||
tradeCashList.ForEach(t =>
|
||
{
|
||
result.RealizedPnl += t.Amount - ((tradeObj.TradePrice ?? 0.0) * (t.UnwindPercentRate ?? 0.0) * TradeCalcHelper.GetSign(tradeObj.BuySell));
|
||
});
|
||
}
|
||
return result;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 计算客户实时资金
|
||
/// </summary>
|
||
public static List<ClientSettleBalance> GetRealTimeClientBanlance(List<int> clientIdList, DateTime? startDate = null)
|
||
{
|
||
if (null == clientIdList || !clientIdList.Any())
|
||
{
|
||
return null;
|
||
}
|
||
|
||
if (!startDate.HasValue)
|
||
{
|
||
startDate = DateTime.MinValue;
|
||
}
|
||
var cashSwapActions = new List<string>() { ClientCashInCashOut.系统操作_应付预付金, ClientCashInCashOut.系统操作_预付金返息, ClientCashInCashOut.系统操作_期权费 };
|
||
using (var db = DbContextFactory.GetYLDbContext())
|
||
using (var db2 = DbContextFactory.GetClientDbContext(null))
|
||
{
|
||
//获取系统参数
|
||
var systemDate = valuedateBLL.SystemDate;
|
||
//系统交易日
|
||
var valueDate = systemDate.ValueDate;
|
||
|
||
//客户信息
|
||
var clientList = db2.client.Where(t => clientIdList.Contains(t.id)).ToList();
|
||
var clientIds = clientList.Select(t => t.id).ToList();
|
||
var clientparentId = clientList.Select(t => t.ParentId).ToList();
|
||
|
||
//获取客户预付金(实时计算更新)
|
||
var clientSpans = db.client_span.Where(t =>
|
||
t.ValueDate == valueDate && t.SpanType == ClientSpan.SpanType_RealTime);
|
||
|
||
//获取客户昨日资金结算信息
|
||
var clientBanlances = new List<ClientBalanceDaily>();
|
||
var lastSettletDate = DateTime.MinValue;
|
||
var query = db.ClientBalanceDaily.Where(
|
||
t => t.BalanceDate < valueDate && t.BalanceDate.HasValue);
|
||
if (query.Any())
|
||
{
|
||
lastSettletDate = query.Max(t => t.BalanceDate).Value;
|
||
//基数行按客户各自最近一次结算日取(找不到全局最新结算日的行时向上回溯)——
|
||
//无持仓/无交易的客户不参与每日EOD日结(如休眠客户、仅入金客户),其日结行会落后于全局结算日,
|
||
//严格取全局日期的行会把结存算成0(2026-08-27 客户84实证:日结只到8-24、全局已到8-26,100万入金被漏)。
|
||
//lastSettletDate 仍保留全局口径供下方交易窗口等既有逻辑使用。
|
||
clientBanlances = db.ClientBalanceDaily.Where(t =>
|
||
t.BalanceDate < valueDate && clientIds.Contains(t.ClientId)).ToList()
|
||
.GroupBy(t => t.ClientId)
|
||
.Select(g => g.OrderByDescending(x => x.BalanceDate).First())
|
||
.ToList();
|
||
}
|
||
//各客户自身基数日(+1为其流水窗口下界);无任何结算行的客户须全量流水
|
||
var baseDateByClient = clientBanlances
|
||
.Where(t => t.BalanceDate.HasValue)
|
||
.GroupBy(t => t.ClientId)
|
||
.ToDictionary(g => g.Key, g => g.Max(t => t.BalanceDate.Value));
|
||
var anyClientWithoutBaseRow = clientIds.Any(id => !baseDateByClient.ContainsKey(id));
|
||
DateTime? flowWindowStart = null;
|
||
if (!anyClientWithoutBaseRow && baseDateByClient.Count > 0)
|
||
{
|
||
//窗口下界放宽到最早基数+1(≤原全局结算日+1,超集拉取),消费点按各客户自身基数过滤防双算
|
||
flowWindowStart = baseDateByClient.Values.Min().AddDays(1);
|
||
}
|
||
|
||
//获取实时持仓
|
||
var positionList = db.intraday_trade_position
|
||
.Where(t => t.ValueDate == valueDate && clientIds.Contains(t.ClientId)).ToList();
|
||
var tradeIds = positionList.Where(t => t.TradeId != 0).Select(t => t.TradeId).ToList();
|
||
|
||
var positionTrades = db.trade.AsNoTracking().Where(t => tradeIds.Contains(t.id)).ToList();
|
||
var swapFlows = db.swap_flow.Where(x => x.DataState == (int)SwapFlowDateStateEnum.等待完成 && x.OccurTime == valueDate).ToList(); ;
|
||
//获取当日所有出入金记录
|
||
var newValuedate = valueDate.AddDays(1);
|
||
|
||
//var ClientCashList = from cash in db.ClientCashInCashOut.Where(t => t.ValidState != "InValid" && t.HappenDate >= lastSettletDateAddOne && t.HappenDate < newValuedate && (t.State == ClientCashInCashOut.已结算 || t.State == ClientCashInCashOut.已确认 || (t.Direction == "出金" && ClientCashInCashOut.outCashCals.Contains(t.State))) && clientIds.Contains(t.ClientId ?? 0))
|
||
var ClientCashList = from cash in db.ClientCashInCashOut.Where(t => t.ValidState != "InValid"
|
||
&& (flowWindowStart == null || t.HappenDate >= flowWindowStart)
|
||
&& t.HappenDate < newValuedate && (t.State == ClientCashInCashOut.已结算 || t.State == ClientCashInCashOut.已确认) && clientIds.Contains(t.ClientId ?? 0))
|
||
join trade in db.trade on cash.TradeId equals trade.id into trade
|
||
from td in trade.DefaultIfEmpty()
|
||
where td.TradeType != "收益互换"
|
||
select cash;
|
||
|
||
var ClientCashSwapList = from cash in db.ClientCashInCashOut.Where(t => t.ValidState != "InValid"
|
||
&& (flowWindowStart == null || t.HappenDate >= flowWindowStart)
|
||
&& t.HappenDate < newValuedate && (t.State == ClientCashInCashOut.已结算 || t.State == ClientCashInCashOut.已确认) && clientIds.Contains(t.ClientId ?? 0))
|
||
join trade in db.trade.Where(x => x.TradeType == "收益互换") on cash.TradeId equals trade.id
|
||
select cash;
|
||
|
||
//获取客户所有现存(抵押状态)抵押品信息
|
||
var clientProductList = db.clientcashincashout_product.Where(t =>
|
||
(t.Status == Clientcashincashout_productStatusEnum.抵押.ToString() && t.OptStatus == ClientCashInCashOut.已确认
|
||
|| t.Status == Clientcashincashout_productStatusEnum.赎回.ToString() && t.OptStatus != ClientCashInCashOut.已确认
|
||
|| t.Status == Clientcashincashout_productStatusEnum.赎回.ToString() && t.OptStatus == ClientCashInCashOut.已确认 && t.BackDate >= newValuedate)
|
||
&& clientIds.Contains(t.ClientId) && t.HappenDate < newValuedate).ToList();
|
||
var productUnderlyingIds = clientProductList.Select(t => t.UnderlyingId ?? 0).ToHashSet();
|
||
|
||
var umList = DataCacheProvider.GetUnderlyingDataSource();
|
||
var eodPriceProvider = EodPriceProviderFactory.Get(valueDate).GetPriceProvider(TradeCalcHelper.GetSettlementType());
|
||
|
||
var allTradeList = db.trade.Where(t => t.ValidState != "InValid" &&
|
||
t.TradeDate <= valueDate &&
|
||
clientIdList.Contains(t.ClientId) &&
|
||
ConsTrade.TradeStatusAfterConfirmed
|
||
.Contains(t.TradeStatus) &&
|
||
t.ParentTradeId == 0
|
||
).ToList();
|
||
|
||
var finishedTradeCash = from tc in db.trade_cash
|
||
join td in db.trade on tc.TradeId equals td.id
|
||
where (tc.ValueDate >= startDate && tc.ValueDate <= valueDate && tc.HappenedDate == null || tc.HappenedDate >= startDate && tc.HappenedDate <= valueDate)
|
||
&& tc.ValidState != ConsGlobal.InValid && !tc.IsDeleted
|
||
&& (tc.Action == ClientCashInCashOut.系统操作_平仓费 || tc.Action == ClientCashInCashOut.系统操作_行权费 || tc.Action == ClientCashInCashOut.系统操作_票息 || tc.Action == ClientCashInCashOut.系统操作_互换 || tc.Action == ClientCashInCashOut.人工操作_其他)
|
||
&& td.IsGroup != 1
|
||
select tc;
|
||
|
||
var finishedTradeNotionalList = finishedTradeCash.ToList();
|
||
|
||
var finishedTradeNotionalDic = finishedTradeNotionalList.GroupBy(tc => tc.TradeId).ToDictionary(g => g.Key,
|
||
g => g.Sum(tc =>
|
||
tc.Action == ClientCashInCashOut.系统操作_平仓费 ? (tc.UnwindNotional ?? 0) : tc.Notional));
|
||
|
||
//所有当日待结算的trade_cash记录(当日为应付日的冻结资金)
|
||
//var payDateTradeIds = db.trade.Where(t => t.TradeDate != valuedate && t.PremiumPayDate == valuedate).Select(t => t.id).ToList();
|
||
//var tradeCashList = db.trade_cash.Where(t => payDateTradeIds.Contains(t.TradeId ?? 0) && t.Status == TradeCashStatusEnum.冻结).ToList();
|
||
//获取当日所有执行的交易 交易日为当前交易日或者行权日为当前交易日
|
||
var positionTradeList = db.trade.Where(t => clientIds.Contains(t.ClientId))
|
||
.Where(t => t.TradeType != "场内期权")
|
||
.Where(t => t.ValidState != "InValid") //有效的交易
|
||
.Where(t => (t.TradeDate > lastSettletDate && t.TradeDate <= valueDate) &&
|
||
ConsTrade.NeedMarginTradeStatusList.Contains(t.TradeStatus)) //最后结算日到当日成交的交易
|
||
.Where(t => GlobalDicionary.SupportTradeTypes.Contains(t.TradeType));
|
||
//当日成交
|
||
var todayTradeList = positionTradeList.Where(t => t.TradeDate == valueDate).ToList();
|
||
|
||
var todayTradesAfterConfirmed = db.trade.Where(t => clientIds.Contains(t.ClientId))
|
||
.Where(t => t.TradeType != "场内期权")
|
||
.Where(t => t.ValidState != "InValid")
|
||
.Where(t => (t.TradeDate == valueDate) &&
|
||
ConsTrade.TradeStatusAfterConfirmed.Contains(t.TradeStatus))
|
||
.Where(t => (GlobalDicionary.SupportTradeTypes.Contains(t.TradeType) || t.TradeType == "结构化交易") && t.ParentTradeId == 0);
|
||
//positionTradeList.Where(t => t.TradeDate == valuedate || (unWindTradeStatus.Contains(t.TradeStatus) && t.UnWindDate==valuedate)).ToList();
|
||
|
||
|
||
var creditList = new List<CreditTable>();
|
||
|
||
//获取所有客户授信
|
||
creditList = db.credit.Where(t =>
|
||
clientIds.Contains(t.ClientId ?? 0) && t.ProcessStatus == "已审批" &&
|
||
(!t.CreditDeadLine.HasValue || t.CreditDeadLine >= valueDate) && (!t.CreditStartDate.HasValue || t.CreditStartDate <= valueDate)).ToList();
|
||
|
||
var clients = DataCacheProvider.GetClientDataSource().AsQueryable().ToList();
|
||
var parentexit = clients.Where(c => clientparentId.Contains(c.id)).ToList();
|
||
if (creditList.Count() == 0 && parentexit != null)
|
||
{
|
||
creditList = db.credit.Where(t =>
|
||
clientparentId.Contains(t.ClientId ?? 0) && t.ProcessStatus == "已审批" &&
|
||
(!t.CreditDeadLine.HasValue || t.CreditDeadLine >= valueDate) && (!t.CreditStartDate.HasValue || t.CreditStartDate <= valueDate)).ToList();
|
||
}
|
||
|
||
//获取所有资信等级
|
||
var creditRatingList = db2.credit_rating.Where(t => t.ValidState != "InValid").ToList();
|
||
|
||
//获取冻结资金信息
|
||
var clientFrozenFunds = new ClientFrozenFundsService(OptUserInfo.SystemUser).GetDatas(valueDate, clientIdList);
|
||
|
||
//预付金比率
|
||
var marginRation = systemDate.MarginRatio ?? 0.15;
|
||
//最大可提取预付金上限比率
|
||
var marginMaxRation = marginRation + 0.02;
|
||
|
||
DateTime? UpdateDate = null;
|
||
var ClientBanlanceList = new List<ClientSettleBalance>();
|
||
//客户盘中结算
|
||
clientList.ForEach(client =>
|
||
{
|
||
//当日应付冻结权利金
|
||
var FreezePremium = 0.0;
|
||
//冻结出金
|
||
var FrozenOutFund = 0.0;
|
||
//冻结抵押品赎回
|
||
var FrozenRedeemFunds = 0.0;
|
||
var OptionPremium = 0.0;
|
||
var OptionPremiumSwap = 0.0;
|
||
//平仓行权收益
|
||
var SettlementBalance = 0.0;
|
||
//平仓收益
|
||
var UnwindBalance = 0.0;
|
||
//行权收益
|
||
var ExerciseBalance = 0.0;
|
||
//互换收益
|
||
var SwapBalance = 0.0;
|
||
//当日应收冻结权利金
|
||
var ReceivablesPremium = 0.0;
|
||
//当日资金净出入
|
||
var NetFund = 0.0;
|
||
//其他收支
|
||
var OtherFund = 0.0;
|
||
//票息
|
||
var Coupon = 0.0;
|
||
//当日入金
|
||
var InFund = 0.0;
|
||
//当日出金
|
||
var OutFund = 0.0;
|
||
//其他收入
|
||
var InFundOther = 0.0;
|
||
//其他支出
|
||
var OutFundOther = 0.0;
|
||
//账户资金
|
||
var AmountFund = 0.0;
|
||
//前日账户资金
|
||
var LastDayRemainFund = 0.0;
|
||
//今日可用资金
|
||
var AvailableAmount = 0.0;
|
||
//授信额度
|
||
var lineOfCredit = 0.0;
|
||
//交易总数
|
||
var totalTradeCount = 0;
|
||
//交易笔数(当日成交)
|
||
var todayTradeCount = 0;
|
||
//交易总额
|
||
//var TradeTotalFund = 0.0;
|
||
//卖权交易数
|
||
var sellCount = 0;
|
||
//名义本金(卖出)
|
||
var SellNotionalPrincipal = 0.0;
|
||
//买权交易数
|
||
var buyCount = 0;
|
||
//名义本金(买入)
|
||
var BuyNotionalPrincipal = 0.0;
|
||
//名义本金总额
|
||
var totalNotionalPrincipal = 0.0;
|
||
//当日交易名义本金
|
||
var todayNotionalPrincipal = 0.0;
|
||
//持仓笔数
|
||
var PositionCount = 0;
|
||
//持仓市值
|
||
var PositionPv = 0.0;
|
||
var RoundedPositionPv = 0.0;
|
||
//空头持仓市值
|
||
var SellPv = 0.0;
|
||
//客户持仓交易预付金总和,客户买入为正,客户卖出为负
|
||
double PrepaymentAmount = 0.0;
|
||
//当日盈亏
|
||
var DaliyPnl = 0.0;
|
||
//持仓盈亏
|
||
var PositionPnl = 0.0;
|
||
var RoundedPositionPnl = 0.0;
|
||
//期权空头浮动盈利=∑max(期权空头持仓*(期权合约成本价-期权合约现价), 0) 从客户角度看的
|
||
var ClientSellPositionPnl = 0.0;
|
||
//抵押品
|
||
//应付预付金(要么为负要么为零)
|
||
//最新概念:负数代表客户应缴预付金,正数代表客户应收预付金
|
||
var PayableMargin = 0.0;
|
||
//互换占用预付金
|
||
var SwapPayableMargin = 0.0;
|
||
//互换容忍金额
|
||
var SwapUnMargin = 0.0;
|
||
//互换追保金额
|
||
var SwapMargin = 0d;
|
||
//双向预付金
|
||
var TwoSideMargin = 0.0;
|
||
//当日新增应付预付金
|
||
var TodayNewMargin = 0.0;
|
||
//潜在行权盈余
|
||
var PotentialSurpluses = 0.0;
|
||
//预付金余额
|
||
var MarginBalance = 0.0;
|
||
//追保金额
|
||
var Margin = 0.0;
|
||
//可取预付金
|
||
//可取预付金为预付金余额 - 应缴预付金(上浮2%)
|
||
var AdvisableMargin = 0.0;
|
||
//授信是否可用于期权费
|
||
var IsTradeCredit = true;
|
||
//实现盈亏
|
||
var WinLoss = 0.0;
|
||
//持仓期权费净额(客户角度卖出为负,买入为正)
|
||
var PositionPremiumNetCash = 0.0;
|
||
//权利金冻结:当日该客户所有持仓的卖出期权权利金
|
||
var SellTradePrice = 0.0;
|
||
// 期初持仓交易净额
|
||
var LastDayPositionPremiumNetCash = 0.0;
|
||
//可用名义本金规模
|
||
double? AvailableStockEqvNotional = null;
|
||
//当前互换流水费用
|
||
decimal curSwapFee = 0;
|
||
//获取客户授信
|
||
var credits = creditList?.Where(a => a.ClientId == client.id || a.ClientId == client.ParentId);
|
||
if (credits.Any())
|
||
{
|
||
lineOfCredit = credits.Sum(t => t.Credit ?? 0);
|
||
AvailableStockEqvNotional = creditList.Sum(O => O.StockEqvNotional) ?? 0;
|
||
}
|
||
else
|
||
{
|
||
lineOfCredit = 0.0;
|
||
AvailableStockEqvNotional = null;
|
||
}
|
||
|
||
var creditRatingId = 0;
|
||
var clientRatingQuery = from cr in db2.Client_Rating.Where(x => !x.IsDeleted && x.ProcessStatus == "已审批")
|
||
where cr.RatingStartDate <= valueDate && cr.RatingDeadLine >= valueDate && cr.ClientId == client.id
|
||
orderby cr.RatingDeadLine descending
|
||
orderby cr.ProcessOptDate descending
|
||
select cr.CreditRatingId;
|
||
|
||
if (clientRatingQuery.Any())
|
||
{
|
||
creditRatingId = clientRatingQuery.FirstOrDefault();
|
||
}
|
||
|
||
//资信等级授信额度
|
||
var creditRating = creditRatingList?.FirstOrDefault(a => a.id == creditRatingId);
|
||
|
||
var clientBanlance = clientBanlances.FirstOrDefault(a => a.ClientId == client.id);
|
||
AmountFund = clientBanlance == null ? 0.0 : clientBanlance.ToDayRemainFund ?? 0.0;
|
||
//上日资金余额
|
||
LastDayRemainFund = AmountFund;
|
||
//昨日抵押品总价值
|
||
var lastGuaranteesTotalAmount = clientBanlance == null
|
||
? 0.0
|
||
: clientBanlance.TodayRemianFundProduct ?? 0.0;
|
||
//资金净流入 + 权利金收支 + 期权费收支
|
||
//流水按该客户自身基数日过滤(窗口下界放宽到最早基数+1 拉取,此处防已结算进基数行的流水双算)
|
||
var clientFlowStart = baseDateByClient.TryGetValue(client.id, out var clientBaseDate) ? clientBaseDate.AddDays(1) : DateTime.MinValue;
|
||
var clientCashSwap = ClientCashSwapList.Where(a => a.ClientId == client.id && a.HappenDate >= clientFlowStart).ToList();
|
||
|
||
if (clientCashSwap.Any())
|
||
{
|
||
if (clientCashSwap.Any(a => a.Direction == "应收" && cashSwapActions.Contains(a.Action)))
|
||
{
|
||
OptionPremiumSwap = clientCashSwap.Where(a => a.Direction == "应收" && cashSwapActions.Contains(a.Action)).Sum(a => a.Money ?? 0.0);
|
||
}
|
||
|
||
if (clientCashSwap.Any(a => a.Direction == "应收" && (a.Action == ClientCashInCashOut.系统操作_平仓费 || a.Action == ClientCashInCashOut.系统操作_互换)))
|
||
{
|
||
SwapBalance = clientCashSwap.Where(a => a.Direction == "应收" && (a.Action == ClientCashInCashOut.系统操作_平仓费 || a.Action == ClientCashInCashOut.系统操作_互换)).Sum(a => a.Money ?? 0.0);
|
||
}
|
||
}
|
||
curSwapFee = swapFlows.Where(x => x.ClientId == client.id).Sum(s => s.TradingFee);
|
||
OptionPremiumSwap += Convert.ToDouble(curSwapFee);
|
||
var clientCash = ClientCashList.Where(a => a.ClientId == client.id && a.HappenDate >= clientFlowStart).ToList();
|
||
if (clientCash.Any())
|
||
{
|
||
if (clientCash.Any(a =>
|
||
a.Direction == "应收" && a.Action == ClientCashInCashOut.系统操作_期权费))
|
||
{
|
||
OptionPremium = clientCash
|
||
.Where(a => a.Direction == "应收" && a.Action == ClientCashInCashOut.系统操作_期权费)
|
||
.Sum(a => a.Money ?? 0.0);
|
||
}
|
||
|
||
if (clientCash.Any(a =>
|
||
a.Direction == "应收" &&
|
||
(a.Action == ClientCashInCashOut.系统操作_行权费 ||
|
||
a.Action == ClientCashInCashOut.系统操作_平仓费)))
|
||
{
|
||
var settlementCashList = clientCash.Where(a =>
|
||
a.Direction == "应收" &&
|
||
(a.Action == ClientCashInCashOut.系统操作_行权费 ||
|
||
a.Action == ClientCashInCashOut.系统操作_平仓费));
|
||
SettlementBalance = settlementCashList.Sum(a => a.Money ?? 0.0);
|
||
UnwindBalance = settlementCashList.Where(x => x.Action == ClientCashInCashOut.系统操作_平仓费).Sum(a => a.Money ?? 0.0);
|
||
ExerciseBalance = settlementCashList.Where(x => x.Action == ClientCashInCashOut.系统操作_行权费).Sum(a => a.Money ?? 0.0);
|
||
}
|
||
|
||
if (clientCash.Any(a => a.Action == ClientCashInCashOut.系统操作_票息))
|
||
{
|
||
Coupon = clientCash
|
||
.Where(a => a.Action == ClientCashInCashOut.系统操作_票息)
|
||
.Sum(a => a.Money ?? 0.0);
|
||
}
|
||
|
||
#region 计算实现盈亏
|
||
|
||
var todayFinishedTrades = allTradeList.Where(t => finishedTradeNotionalDic.Select(d => d.Key).Contains(t.id) && t.ClientId == client.id).ToList();
|
||
var todayFinishedTradeCash = from tc in finishedTradeCash
|
||
join td in db.trade on tc.TradeId equals td.id
|
||
where td.IsGroup != 1
|
||
group tc by td.ClientId into g
|
||
select new
|
||
{
|
||
ClientId = g.Key,
|
||
amountSum = g.Sum(n => n.Amount)
|
||
};
|
||
|
||
WinLoss = todayFinishedTrades.Sum(t => t.TradeType != "远期" ? (TradeCalcHelper.GetSign(t.BuySell) * (t.TradeSinglePrice ?? 0) * finishedTradeNotionalDic[t.id])
|
||
: (-(t.TradePrice ?? 0) * (finishedTradeNotionalDic[t.id] / (t.OriginalNotional ?? 0)))) //远期开仓总费用所占比重
|
||
- (todayFinishedTradeCash.FirstOrDefault(x => x.ClientId == client.id)?.amountSum ?? 0);
|
||
|
||
#endregion
|
||
|
||
if (clientCash.Any(a => a.Direction == "入金" || a.Direction == "出金"))
|
||
{
|
||
var inFunds = clientCash.Where(a => a.Direction == "入金").AsEnumerable().GroupBy(x => x.CurrencyCode).Select(x => new { x.Key, Money = x.Sum(y => y.Money ?? 0) }).ToList();
|
||
inFunds.ForEach(x =>
|
||
{
|
||
new EodCurrencyProvider(valueDate, false).TryGetCurrencyRate(x.Key, client.SettlementCurrency, out var rateObj);
|
||
|
||
if (rateObj == null)
|
||
{
|
||
throw new Exception($"{valueDate.ToString("yyyy-MM-dd")}未找到汇率:{x.Key}{client.SettlementCurrency}");
|
||
}
|
||
|
||
InFund += x.Money * rateObj.Rate;
|
||
});
|
||
|
||
var outFunds = clientCash.Where(a => a.Direction == "出金").AsEnumerable().GroupBy(x => x.CurrencyCode).Select(x => new { x.Key, Money = x.Sum(y => y.Money ?? 0) }).ToList();
|
||
outFunds.ForEach(x =>
|
||
{
|
||
new EodCurrencyProvider(valueDate, false).TryGetCurrencyRate(x.Key, client.SettlementCurrency, out var rateObj);
|
||
|
||
if (rateObj == null)
|
||
{
|
||
throw new Exception($"{valueDate.ToString("yyyy-MM-dd")}未找到汇率:{x.Key}{client.SettlementCurrency}");
|
||
}
|
||
|
||
OutFund += x.Money * rateObj.Rate;
|
||
});
|
||
|
||
NetFund = InFund - OutFund;
|
||
}
|
||
|
||
if (clientCash.Any(a => a.Direction == "其他收入" || a.Direction == "其他支出"))
|
||
{
|
||
var inFundOthers = clientCash.Where(a => a.Direction == "其他收入").AsEnumerable().GroupBy(x => x.CurrencyCode).Select(x => new { x.Key, Money = x.Sum(y => y.Money ?? 0) }).ToList();
|
||
inFundOthers.ForEach(x =>
|
||
{
|
||
new EodCurrencyProvider(valueDate, false).TryGetCurrencyRate(x.Key, client.SettlementCurrency, out var rateObj);
|
||
|
||
if (rateObj == null)
|
||
{
|
||
throw new Exception($"{valueDate.ToString("yyyy-MM-dd")}未找到汇率:{x.Key}{client.SettlementCurrency}");
|
||
}
|
||
|
||
InFundOther += x.Money * rateObj.Rate;
|
||
});
|
||
|
||
var outFundOthers = clientCash.Where(a => a.Direction == "其他支出").AsEnumerable().GroupBy(x => x.CurrencyCode).Select(x => new { x.Key, Money = x.Sum(y => y.Money ?? 0) }).ToList();
|
||
outFundOthers.ForEach(x =>
|
||
{
|
||
new EodCurrencyProvider(valueDate, false).TryGetCurrencyRate(x.Key, client.SettlementCurrency, out var rateObj);
|
||
|
||
if (rateObj == null)
|
||
{
|
||
throw new Exception($"{valueDate.ToString("yyyy-MM-dd")}未找到汇率:{x.Key}{client.SettlementCurrency}");
|
||
}
|
||
|
||
OutFundOther += x.Money * rateObj.Rate;
|
||
});
|
||
|
||
OtherFund = InFundOther + OutFundOther;
|
||
}
|
||
}
|
||
|
||
//抵押品资金价值
|
||
var tempClientProductList = clientProductList.Where(a => a.ClientId == client.id).ToList();
|
||
var GuaranteesTotalAmount = 0.0;
|
||
if (tempClientProductList.Any())
|
||
{
|
||
foreach (var t in tempClientProductList)
|
||
{
|
||
var um = umList.GetData(t.UnderlyingId ?? 0);
|
||
if (um != null)
|
||
{
|
||
if (!eodPriceProvider.TryGetPrice(um.UnderlyingCode, out var price_cp))
|
||
{
|
||
price_cp = umList.GetPrice(um.UnderlyingCode);
|
||
}
|
||
GuaranteesTotalAmount += (t.ProductAmount ?? 0) * (t.Rate ?? 0.0) * price_cp;
|
||
}
|
||
}
|
||
}
|
||
|
||
//持仓
|
||
//潜在行权盈余
|
||
PotentialSurpluses = 0.0;
|
||
var clientPositionList = positionList.Where(a =>
|
||
a.ClientId == client.id && ConsTrade.NeedMarginTradeStatusList.Contains(a.TradeStatus))
|
||
.ToList();
|
||
//持仓笔数
|
||
var clientPositionTrades = positionTrades.Where(t => t.ClientId == client.id &&
|
||
ConsTrade.NeedMarginTradeStatusList.Contains(t.TradeStatus));
|
||
PositionCount = clientPositionTrades.Count(t => t.ParentTradeId == 0) +
|
||
clientPositionTrades.Where(t => t.ParentTradeId > 0)
|
||
.AsEnumerable()
|
||
.GroupBy(t => t.ParentTradeId).Count();
|
||
//应付预付金
|
||
SellNotionalPrincipal = 0.0;
|
||
BuyNotionalPrincipal = 0.0;
|
||
totalNotionalPrincipal = 0.0;
|
||
UpdateDate = null;
|
||
if (PositionCount > 0)
|
||
{
|
||
|
||
UpdateDate = clientPositionList.Max(a => a.OptDate);
|
||
if (valuedate.PotentialSurplusCalcMode_ActualPv.Equals(systemDate
|
||
.PotentialSurplusCalcMode))
|
||
{
|
||
PotentialSurpluses =
|
||
clientPositionList.Where(x => x.TradeType != "结构化交易").Sum(a => a.ActualPv) * (-1); //潜在行权收益等于实值额
|
||
}
|
||
else
|
||
{
|
||
PotentialSurpluses =
|
||
Convert.ToDouble(clientPositionList.Where(x => x.TradeType != "结构化交易").Sum(a => a.Pv) * (-1)); //潜在行权收益等于实值额
|
||
}
|
||
|
||
PositionPv = Convert.ToDouble(clientPositionList.Where(x => x.TradeType != "结构化交易").Sum(a => a.Pv));
|
||
RoundedPositionPv = Convert.ToDouble(clientPositionList.Where(x => x.TradeType != "结构化交易").Sum(a => a.RoundedPv));
|
||
SellPv = Convert.ToDouble(clientPositionList.Where(x => x.TradeType != "结构化交易").Where(x => x.BuySell == "买入").Sum(a => a.Pv));
|
||
//客户持仓交易预付金总和,客户买入为正,客户卖出为负
|
||
PrepaymentAmount = positionTrades.Sum(x => x.StockEqvNotional * (x.trade_snowball?.PrepaymentRatio ?? 0) * (x.BuySell == "卖出" ? 1 : -1));
|
||
|
||
DaliyPnl = Convert.ToDouble(clientPositionList.Where(x => x.TradeType != "结构化交易").Sum(a =>
|
||
a.DailyPnL));
|
||
PositionPnl = clientPositionList.Where(x => x.TradeType != "结构化交易").Sum(a => a.PositionPnL);
|
||
RoundedPositionPnl = clientPositionList.Where(x => x.TradeType != "结构化交易").Sum(a => a.RoundedPositionPnl);
|
||
|
||
#region 期权空头浮动盈利=∑max(期权空头持仓*(期权合约成本价-期权合约现价), 0) 从客户角度看的, 结构化交易需要将两条腿的空头Pnl合计
|
||
|
||
var parentIds = clientPositionList.Where(x => x.TradeType == "结构化交易").Select(x => x.TradeId).ToList();
|
||
var parentPositionPnlList = from t in clientPositionList.Where(x => parentIds.Contains(x.ParentTradeId))
|
||
group t by new { t.ParentTradeId } into g
|
||
select new
|
||
{
|
||
parentTradeId = g.Key.ParentTradeId,
|
||
ClientSellPositionPnl = Math.Max(g.Sum(n => n.BuySell == "买入" ? -n.PositionPnL : 0), 0)
|
||
};
|
||
var parentPositionPnlTotal = parentPositionPnlList.Sum(x => x.ClientSellPositionPnl);
|
||
var singlePositionPnlTotal = clientPositionList.Where(x => x.TradeType != "结构化交易" && x.ParentTradeId == 0).Sum(n => n.BuySell == "买入" ? Math.Max(-n.PositionPnL, 0) : 0);
|
||
ClientSellPositionPnl = parentPositionPnlTotal + singlePositionPnlTotal;
|
||
|
||
#endregion
|
||
}
|
||
|
||
//获取tradespan 追保金额 = (当日资金余额 - 维持预付金) + 授信额度
|
||
var clientSpan = clientSpans.FirstOrDefault(t => t.ClientId == client.id);
|
||
PayableMargin = clientSpan == null || clientSpan.WorstCastClientPayable == null ? 0.0 : clientSpan.WorstCastClientPayable.Value;
|
||
TwoSideMargin = clientSpan == null || clientSpan.WorstCastClientPayable == null ? 0.0 : clientSpan.WorstCastClientPayable.Value;
|
||
SwapPayableMargin = clientSpan == null || clientSpan.SwapWorstCastClientPayable == null ? 0.0 : clientSpan.SwapWorstCastClientPayable.Value;
|
||
SwapUnMargin = clientSpan == null || clientSpan.SwapUnMargin == null ? 0.0 : clientSpan.SwapUnMargin.Value;
|
||
//存量交易持仓名义本金规模
|
||
AvailableStockEqvNotional -= allTradeList.Where(O => O.ClientId == client.id && ConsTrade.PositionTradeStatusList.Contains(O.TradeStatus) && O.ParentTradeId == 0).Sum(O => (double?)O.StockEqvNotional) ?? 0;
|
||
|
||
//交易数量
|
||
var clientAllTradeList = allTradeList.Where(t => t.ClientId == client.id).ToList();
|
||
if (clientAllTradeList.Count > 0)
|
||
{
|
||
//名义本金取 客户买入(交易员为卖出)的客户
|
||
SellNotionalPrincipal = clientAllTradeList.Sum(a =>
|
||
(a.OriginalStockEqvNotional > 0
|
||
? (a.OriginalStockEqvNotional ?? 0.0)
|
||
: ((a.SpotPrice ?? 0.0) * (a.OriginalNotional ?? 0))) *
|
||
("买入".Equals(a.BuySell) ? 1 : 0));
|
||
BuyNotionalPrincipal = clientAllTradeList.Sum(a =>
|
||
(a.OriginalStockEqvNotional > 0
|
||
? (a.OriginalStockEqvNotional ?? 0.0)
|
||
: ((a.SpotPrice ?? 0.0) * (a.OriginalNotional ?? 0))) *
|
||
("卖出".Equals(a.BuySell) ? 1 : 0));
|
||
totalNotionalPrincipal = SellNotionalPrincipal + BuyNotionalPrincipal;
|
||
buyCount = clientAllTradeList.Where(p => p.BuySell == "卖出").Count();
|
||
sellCount = clientAllTradeList.Where(p => p.BuySell == "买入").Count();
|
||
totalTradeCount = buyCount + sellCount;
|
||
}
|
||
|
||
//当日成交 新增交易
|
||
var todayClientTradeList = todayTradeList.Where(a => a.ClientId == client.id).ToList();
|
||
var addNewTradeIds = todayClientTradeList.Select(a => a.id).ToList();
|
||
if (addNewTradeIds.Count > 0 &&
|
||
clientPositionList.Where(x => x.ParentTradeId == 0).Any(a => addNewTradeIds.Contains(a.TradeId)))
|
||
{
|
||
//当日新增预付金
|
||
TodayNewMargin =
|
||
clientPositionList.Where(x => x.ParentTradeId == 0).Sum(a => (a.Margin) * (a.BuySell == "买入" ? -1 : 0));
|
||
}
|
||
var todayClientTradesAfterConfirmed = todayTradesAfterConfirmed.Where(a => a.ClientId == client.id).ToList();
|
||
//交易笔数
|
||
todayTradeCount = todayClientTradesAfterConfirmed.Count;
|
||
todayNotionalPrincipal = todayClientTradesAfterConfirmed.Sum(a =>
|
||
(a.OriginalStockEqvNotional > 0
|
||
? (a.OriginalStockEqvNotional ?? 0.0)
|
||
: ((a.SpotPrice ?? 0.0) * (a.OriginalNotional ?? 0))));
|
||
|
||
//应付冻结权利金
|
||
var clientFrozenFund = clientFrozenFunds[client.id];
|
||
FreezePremium = clientFrozenFund.FrozenPayableOptionMoney;
|
||
ReceivablesPremium = clientFrozenFund.FrozenReceivableOptionMoney;
|
||
FrozenOutFund = clientFrozenFund.OutFunds;
|
||
FrozenRedeemFunds = clientFrozenFund.RedeemFunds;
|
||
//冻结预付金(绝对值)
|
||
var FrozenMarginMoney = clientFrozenFund.FrozenMarginMoney;
|
||
//var tempTrades = positionTrade.Where(a => a.ClientId == client.id && trade.NeedMarginTradeStatusList.Contains(a.TradeStatus) && a.PremiumPayDate > valueDate).ToList();
|
||
//if (tempTrades.Any())
|
||
//{
|
||
// //应付冻结权利金
|
||
// if (tempTrades.Where(a => a.BuySell == "卖出").Any())
|
||
// {
|
||
// FreezePremium = tempTrades.Where(a => a.BuySell == "卖出").Sum(a => a.TradePrice ?? 0.0);
|
||
// }
|
||
// //应收冻结权利金
|
||
// if (tempTrades.Where(a => a.BuySell == "买入").Any())
|
||
// {
|
||
// ReceivablesPremium = tempTrades.Where(a => a.BuySell == "买入").Sum(a => a.TradePrice ?? 0.0);
|
||
// }
|
||
//}
|
||
|
||
//当前账号资金
|
||
AmountFund = AmountFund + NetFund + OtherFund + Coupon + SwapBalance + OptionPremium + OptionPremiumSwap + SettlementBalance;
|
||
//当日可用资金 账户总资金 - 应付冻结权利金 - 冻结出金 - 冻结抵押品赎回 + 应收冻结权利金 - 应付预付金 - 冻结预付金
|
||
AvailableAmount = AmountFund - FreezePremium - FrozenOutFund - FrozenRedeemFunds +
|
||
ReceivablesPremium + PayableMargin - FrozenMarginMoney;
|
||
//预付金余额
|
||
MarginBalance = AmountFund - FreezePremium - FrozenOutFund - FrozenRedeemFunds +
|
||
ReceivablesPremium + GuaranteesTotalAmount;
|
||
|
||
//判断潜在行权盈余是否计入预付金余额
|
||
if ((systemDate.IsPotentialSurplusUseMargin ?? 0) == 1)
|
||
{
|
||
MarginBalance += PotentialSurpluses;
|
||
AvailableAmount += PotentialSurpluses;
|
||
}
|
||
|
||
////追保金额
|
||
//Margin = Math.Max(PayableMargin - MarginBalance, 0);
|
||
|
||
// 除收益互换预付金资金占用后剩余资金
|
||
var marginBalanceSurplus = 0d;
|
||
|
||
//计算追保金额
|
||
//是否期权费授信
|
||
if ((client.IsTradeCredit ?? 0) == 1)
|
||
{
|
||
IsTradeCredit = true;
|
||
|
||
//授信是否可用于互换
|
||
if (client.creditCanApplySwap)
|
||
{
|
||
///期权费授信可以支付权利金 则预付金余额未0时 资金从 互换剔除互换容忍金额
|
||
Margin = Math.Min(MarginBalance + lineOfCredit + (PayableMargin - SwapUnMargin), 0);
|
||
}
|
||
else
|
||
{
|
||
//互换最大可用资金
|
||
marginBalanceSurplus = Math.Max(Math.Min(Math.Max(MarginBalance - OptionPremiumSwap + lineOfCredit + (PayableMargin - SwapPayableMargin), 0), Math.Max(MarginBalance - OptionPremiumSwap, 0)), 0);
|
||
|
||
//收益互换追保金额 剩余可支付互换资金 - (互换占用预付金 - 互换容忍预付金额度)
|
||
SwapMargin = Math.Min(marginBalanceSurplus + OptionPremiumSwap + (SwapPayableMargin - SwapUnMargin), 0);
|
||
|
||
//计算期权追保
|
||
Margin = Math.Min(MarginBalance - OptionPremiumSwap + lineOfCredit + (PayableMargin - SwapPayableMargin), 0) + SwapMargin;
|
||
}
|
||
}
|
||
else
|
||
{
|
||
IsTradeCredit = false;
|
||
|
||
//授信是否可用于互换
|
||
if (client.creditCanApplySwap)
|
||
{
|
||
//期权费授信不可以支付权力金时
|
||
Margin = Math.Min(Math.Max(MarginBalance, 0) + lineOfCredit + (PayableMargin - SwapUnMargin), 0) + Math.Min(MarginBalance, 0);
|
||
}
|
||
else
|
||
{
|
||
//互换最大可用资金
|
||
marginBalanceSurplus = Math.Max(Math.Min(Math.Max(Math.Max(MarginBalance - OptionPremiumSwap, 0) + lineOfCredit + (PayableMargin - SwapUnMargin), 0), Math.Max(MarginBalance - OptionPremiumSwap, 0)), 0);
|
||
|
||
//收益互换追保金额 剩余可支付互换资金 - (互换占用预付金 - 互换容忍预付金额度)
|
||
SwapMargin = Math.Min(marginBalanceSurplus + OptionPremiumSwap + (SwapPayableMargin - SwapUnMargin), 0) + Math.Min(marginBalanceSurplus + OptionPremiumSwap, 0);
|
||
|
||
//计算期权追保
|
||
Margin = Math.Min(Math.Max(MarginBalance - OptionPremiumSwap, 0) + lineOfCredit + (PayableMargin - SwapUnMargin), 0) + Math.Min(MarginBalance - OptionPremiumSwap, 0) + SwapMargin;
|
||
}
|
||
|
||
}
|
||
|
||
//可取预付金为预付金余额 - 应缴预付金(上浮 + 0.02)
|
||
AdvisableMargin = Math.Max(MarginBalance + PayableMargin * marginMaxRation / marginRation, 0);
|
||
|
||
LastDayPositionPremiumNetCash = clientBanlance == null ? 0.0 : clientBanlance.PositionPremiumNetCash ?? 0.0;
|
||
PositionPremiumNetCash = clientPositionList.Sum(t =>
|
||
t.Cost * (-TradeCalcHelper.GetSign(t.BuySell)));
|
||
SellTradePrice = clientPositionList.Where(x => x.BuySell == "买入").Sum(t => (t.Cost));
|
||
|
||
#region 新交易权利金, 新交易预付金,新交易初保,额外追保,总追保金额
|
||
|
||
var todayNewClientPostionList = clientPositionList
|
||
.Where(a => addNewTradeIds.Contains(a.TradeId)).ToList();
|
||
|
||
#endregion
|
||
|
||
ClientBanlanceList.Add(new ClientSettleBalance
|
||
{
|
||
ValueDate = valueDate,
|
||
ClientId = client.id,
|
||
FreezePremium = FreezePremium,
|
||
FrozenOutFund = FrozenOutFund,
|
||
FrozenRedeemFunds = FrozenRedeemFunds,
|
||
ReceivablesPremium = ReceivablesPremium,
|
||
NetFund = NetFund,
|
||
OtherFund = OtherFund,
|
||
AmountFund = AmountFund,
|
||
LastDayRemainFund = LastDayRemainFund,
|
||
LastDayRemainFundWithProduct = LastDayRemainFund + lastGuaranteesTotalAmount,
|
||
AvailableAmount = AvailableAmount,
|
||
CreditRating = creditRating == null ? "" : creditRating.CreditName,
|
||
TotalCredit = lineOfCredit,
|
||
//CreditUsed = lineOfCredit > 0 ? Math.Max(-AvailableAmount / lineOfCredit, 0.0) : 0.0,
|
||
TradeCount = todayTradeCount,
|
||
SellNotionalPrincipal = SellNotionalPrincipal,
|
||
BuyNotionalPrincipal = BuyNotionalPrincipal,
|
||
PositionCount = PositionCount,
|
||
PayableMargin = PayableMargin,
|
||
TwoSideMargin = TwoSideMargin,
|
||
PotentialSurpluses = PotentialSurpluses,
|
||
MarginBalance = MarginBalance,
|
||
Margin = Margin,
|
||
AdvisableMargin = AdvisableMargin,
|
||
UpdateDate = UpdateDate,
|
||
IsTradeCredit = IsTradeCredit,
|
||
OptionPremium = OptionPremium,
|
||
OptionPremiumSwap = OptionPremiumSwap,
|
||
SwapPayableMargin = SwapPayableMargin,
|
||
SwapUnMargin = SwapUnMargin,
|
||
SettlementBalance = SettlementBalance,
|
||
UnwindBalance = UnwindBalance,
|
||
ExerciseBalance = ExerciseBalance,
|
||
SwapBalance = SwapBalance,
|
||
Coupon = Coupon,
|
||
TodayNewMargin = TodayNewMargin,
|
||
GuaranteesTotalAmount = GuaranteesTotalAmount,
|
||
PositionPv = PositionPv * (-1), //客户方向取反
|
||
RoundedPositionPv = RoundedPositionPv * (-1),
|
||
SellPv = SellPv * (-1),
|
||
PrepaymentAmount = PrepaymentAmount,
|
||
DaliyPnl = DaliyPnl * (-1),
|
||
PositionPnl = PositionPnl * (-1),
|
||
RoundedPositionPnl = RoundedPositionPnl * (-1),
|
||
ClientSellPositionPnl = ClientSellPositionPnl,
|
||
LastGuaranteesTotalAmount = lastGuaranteesTotalAmount,
|
||
CashInCashOutProductChange = GuaranteesTotalAmount - lastGuaranteesTotalAmount,
|
||
TotalTradeCount = totalTradeCount,
|
||
SellCount = sellCount,
|
||
BuyCount = buyCount,
|
||
TotalNotionalPrincipal = totalNotionalPrincipal,
|
||
TodayNotionalPrincipal = todayNotionalPrincipal,
|
||
PositionPremiumNetCash = PositionPremiumNetCash,
|
||
SellTradePrice = SellTradePrice,
|
||
LastDayPositionPremiumNetCash = LastDayPositionPremiumNetCash,
|
||
WinLoss = WinLoss,
|
||
AvailableStockEqvNotional = AvailableStockEqvNotional ?? 0,
|
||
});
|
||
});
|
||
|
||
return ClientBanlanceList;
|
||
}
|
||
}
|
||
|
||
public static bool TradeCanBeConfirm(int clientId, trade trade, out string errorMsg)
|
||
{
|
||
errorMsg = "";
|
||
if (trade.TradeType == "现金流交易" || (trade.TradeType == "结构化交易" && trade.UnderlyingCode == null))
|
||
{
|
||
return true;
|
||
}
|
||
var clientBalances = GetRealTimeClientBanlance(new List<int> { clientId });
|
||
if (clientBalances == null || clientBalances.Count <= 0)
|
||
{
|
||
errorMsg = "客户实时资金错误!";
|
||
return false;
|
||
}
|
||
if (trade.Warning)
|
||
{
|
||
new TradeDalService(OptUserInfo.SystemUser).RollbackToBeforeSettle(trade, valuedateBLL.ValueDate);
|
||
}
|
||
if (trade.IsOption() || trade.TradeType == "结构化交易" || trade.TradeType == "自定义交易")
|
||
{
|
||
//交易为买入 即:客户为卖出
|
||
if (EnumTradeBuySell.Buy.Equals(trade.BuySell))
|
||
{
|
||
var clientBalance = clientBalances[0];
|
||
using (var db = new YLContext())
|
||
{
|
||
var underlying = DataCacheProvider.GetUnderlyingDataSource().GetData(trade.UnderlyingId);
|
||
if (underlying == null)
|
||
{
|
||
errorMsg = "未找到标的信息";
|
||
return false;
|
||
}
|
||
if (trade.ExerciseDate.Value.Date >= valuedateBLL.ValueDate.Date)
|
||
{
|
||
var tradePrice = trade.TradePrice * (-TradeCalcHelper.GetSign(trade.BuySell));
|
||
var margin = trade.InitialMargin ?? 0;
|
||
var endMoney = margin + tradePrice;
|
||
|
||
var AvailableAmount = Math.Max(clientBalance.AvailableAmount + clientBalance.TotalCredit, 0);
|
||
|
||
if (AvailableAmount < endMoney)
|
||
{
|
||
errorMsg = $"当前交易应付预付金:{margin:#,##0.000},应付成交金额:{tradePrice:#,##0.000},总应付资金:{endMoney:#,##0.000}。当前剩余资金:{clientBalance.AmountFund:F3},冻结资金:{clientBalance.AllFreezeBalance():F3},抵押品价值:{clientBalance.GuaranteesTotalAmount:F3},授信额度:{clientBalance.TotalCredit:F3},可用总额度:{AvailableAmount:F3}。不足以支付上述金额。";
|
||
return false;
|
||
}
|
||
}
|
||
return true;
|
||
}
|
||
}
|
||
else//交易为卖出 即:客户为买入
|
||
{
|
||
var clientBalance = clientBalances[0];
|
||
var tradePrice = trade.TradePrice;
|
||
var AvailableAmount = clientBalance.AvailablePremium();
|
||
|
||
var endMoney = tradePrice;
|
||
if (AvailableAmount >= tradePrice)
|
||
{
|
||
return true;
|
||
}
|
||
else
|
||
{
|
||
errorMsg = $"当前交易应付预付金:{0:#,##0.000},应付成交金额:{tradePrice:#,##0.000},总应付资金:{endMoney:#,##0.000}。当前剩余资金:{clientBalance.AmountFund:F3},冻结资金:{clientBalance.AllFreezeBalance():F3},抵押品价值:{clientBalance.GuaranteesTotalAmount:F3},授信额度:{clientBalance.TotalCredit:F3},可用总额度:{AvailableAmount:F3}。不足以支付上述金额。";
|
||
return false;
|
||
}
|
||
}
|
||
}
|
||
else if (trade.TradeType == "收益互换")
|
||
{
|
||
var clientBalance = clientBalances[0];
|
||
using (var db = new YLContext())
|
||
{
|
||
//修改/复核已簿记交易(trade.id!=0)按增量口径校验:本笔自身已簿记的成交金额(期权费记录)
|
||
//与初始预付金流水已计入现金结存、初始授信占用已计入已使用授信,比较前先剔除——
|
||
//否则改备注等无关字段保存会被存量预付金二次拦截(例:入金60万、初始预付金已付50万、余额10万)。
|
||
//只剔除确认时会重写的记录;追保/票息/平仓等生命周期记录确认后仍保留,不剔除。
|
||
var ownCashBooked = 0d;
|
||
var ownCreditBooked = 0d;
|
||
if (trade.id != 0)
|
||
{
|
||
ownCashBooked = db.ClientCashInCashOut
|
||
.Where(x => x.TradeId == trade.id && x.ValidState != "InValid")
|
||
.ToList()
|
||
.Where(x => (x.State == ClientCashInCashOut.已结算 || x.State == ClientCashInCashOut.已确认)
|
||
&& (x.Action == ClientCashInCashOut.系统操作_期权费 || x.Action == ClientCashInCashOut.系统操作_应付预付金))
|
||
.Sum(x => x.Money ?? 0);
|
||
ownCreditBooked = db.client_credit_inout
|
||
.Where(x => x.trade_id == trade.id)
|
||
.ToList()
|
||
.Where(x => !Modules.SwapModule.ClientCreditInoutService.IsAdditionalMarginRecord(x))
|
||
.Sum(x => x.amount);
|
||
clientBalance.AmountFund -= ownCashBooked;
|
||
clientBalance.AvailableAmount -= ownCashBooked;
|
||
}
|
||
var ownBookedNote = trade.id != 0
|
||
? $"(已剔除本笔已簿记资金{ownCashBooked:#,##0.000}与初始授信占用{ownCreditBooked:#,##0.000})"
|
||
: "";
|
||
var tradePrice = trade.TradePrice * (-TradeCalcHelper.GetSign(trade.BuySell));
|
||
var AvailableAmount = clientBalance.AvailablePremium();
|
||
|
||
if (AvailableAmount < tradePrice && tradePrice > 0)
|
||
{
|
||
var endMoney = tradePrice;
|
||
errorMsg = $"当前交易应付预付金:{0:#,##0.000},应付成交金额:{tradePrice:#,##0.000},总应付资金:{endMoney:#,##0.000}。当前剩余资金:{clientBalance.AmountFund:F3},冻结资金:{clientBalance.AllFreezeBalance():F3},抵押品价值:{clientBalance.GuaranteesTotalAmount:F3},授信额度:{clientBalance.TotalCredit:F3},可用总额度:{AvailableAmount:F3}。不足以支付上述金额。";
|
||
return false;
|
||
}
|
||
else
|
||
{
|
||
if (trade.StructureType != "多空组合")
|
||
{
|
||
var underlying = DataCacheProvider.GetUnderlyingDataSource().GetData(trade.UnderlyingId);
|
||
if (underlying == null)
|
||
{
|
||
errorMsg = "未找到标的信息";
|
||
return false;
|
||
}
|
||
}
|
||
if (trade.ExerciseDate.Value.Date >= valuedateBLL.ValueDate.Date)
|
||
{
|
||
// R4 簿记资金校验口径(2026-08-21 业务强调"走了资金的就不能占用授信"):
|
||
// 按腿的资金走向分流——走现金的部分(选现金/未选且交易级资金来源非授信 + 成交金额)只认现金结存;
|
||
// 按授信的腿(腿选授信,或腿未选回退交易级 margin_fund_source=授信,ConsFundTag.PreferCredit)
|
||
// 认 剩余可用授信(有效授信−已使用授信,授信出入表 Σ(amount)),
|
||
// 授信不够覆盖的部分回落现金,同样只认现金结存。杜绝"现金腿拿授信垫付校验→现金透支"。
|
||
var marginModes = new[] { (int)InterestModeEnum.追加预付金, (int)InterestModeEnum.初始预付金 };
|
||
var legs = trade.swap_positions?.Where(x => marginModes.Contains(x.InterestMode)).ToList();
|
||
if (legs == null || legs.Count == 0)
|
||
{
|
||
legs = db.swap_position.Where(x => x.SwapTradeId == trade.id && x.IsInitial && !x.Invalid
|
||
&& marginModes.Contains(x.InterestMode)).ToList();
|
||
}
|
||
//客户应付为正:收取方向(dir=1)腿 fix 为正应付额;支付方向为客户收钱不参与
|
||
double creditPayable = 0, cashPayable = 0;
|
||
foreach (var leg in legs)
|
||
{
|
||
var payable = Convert.ToDouble(leg.InterestPrincipalFix) * (leg.InterestDirection == 1 ? 1 : -1);
|
||
if (payable <= 0)
|
||
{
|
||
continue;
|
||
}
|
||
if (YLErp.DBModels.ConsFundTag.PreferCredit(leg.FundTag, trade.MarginFundSource))
|
||
{
|
||
creditPayable += payable;
|
||
}
|
||
else
|
||
{
|
||
cashPayable += payable;
|
||
}
|
||
}
|
||
var usedCredit = Modules.SwapModule.ClientCreditInoutService.GetUsedCredit(clientId, db);
|
||
//授信上限剔除本笔自身初始占用(ownCreditBooked,增量口径,见上方净扣说明)
|
||
var creditCap = Math.Max(clientBalance.TotalCredit - (usedCredit - ownCreditBooked), 0);
|
||
var creditCovered = Math.Min(creditPayable, creditCap);
|
||
//授信覆盖不足的回落现金部分 + 走现金部分 + 成交金额,合计必须 ≤ 现金结存
|
||
var cashNeed = tradePrice + cashPayable + (creditPayable - creditCovered);
|
||
if (cashNeed > clientBalance.AmountFund)
|
||
{
|
||
var totalPayable = tradePrice + cashPayable + creditPayable;
|
||
errorMsg = $"当前交易应付总额:{totalPayable:#,##0.000}(走现金:{cashPayable + tradePrice:#,##0.000},选授信:{creditPayable:#,##0.000})。当前现金结存:{clientBalance.AmountFund:F3}(走现金部分只认现金结存),授信额度:{clientBalance.TotalCredit:F3},已使用授信:{usedCredit - ownCreditBooked:F3},剩余授信:{creditCap:F3}(授信仅覆盖选授信部分,不足回落现金)。现金不足以覆盖应付的现金部分。{ownBookedNote}";
|
||
return false;
|
||
}
|
||
}
|
||
return true;
|
||
}
|
||
}
|
||
}
|
||
else if (trade.TradeType == "远期")
|
||
{
|
||
using (var db = new YLContext())
|
||
{
|
||
var forwardTrade = db.trade_forward.FirstOrDefault(x => x.TradeId == trade.id);
|
||
if (forwardTrade != null)
|
||
{
|
||
//远期验资,验证客户余额>= 开仓总费用
|
||
var clientBalance = clientBalances[0];
|
||
var totalOpenCommission = trade.TradePrice ?? 0;
|
||
var AvailableAmount = clientBalance.AvailablePremium();
|
||
if (AvailableAmount >= totalOpenCommission || totalOpenCommission < 0)
|
||
{
|
||
return true;
|
||
}
|
||
else
|
||
{
|
||
errorMsg = $"当前剩余资金:{clientBalance.AmountFund:F3},冻结资金:{clientBalance.AllFreezeBalance():F3},应付预付金:{clientBalance.PayableMargin:F3},抵押品价值:{clientBalance.GuaranteesTotalAmount:F3},授信额度:{clientBalance.TotalCredit:F3},可用总额度:{AvailableAmount:F3}。不足以支付开仓总金额:{totalOpenCommission:F3}";
|
||
return false;
|
||
}
|
||
}
|
||
else
|
||
{
|
||
errorMsg = "客户远期交易数据未找到!";
|
||
return false;
|
||
}
|
||
}
|
||
|
||
}
|
||
else
|
||
{
|
||
errorMsg = "非期权交易,无需检查!";
|
||
return false;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 实时计算收益互换实时持仓
|
||
/// </summary>
|
||
/// <param name="td"></param>
|
||
/// <returns></returns>
|
||
public static intraday_trade_position RealTimeSwapCalc(trade td, YLContext db, List<swap_flow> swapFlows)
|
||
{
|
||
intraday_trade_position posi = new intraday_trade_position();
|
||
var position = db.swap_position.FirstOrDefault(x => x.SwapTradeId == td.id && x.PosiDirection > 0 && x.IsInitial);
|
||
if (position == null)
|
||
{
|
||
return null;
|
||
}
|
||
var swapFlowItems = swapFlows.Where(x => x.ClientId == td.ClientId && x.UnderlyingCode == position.UnderlyingCode);
|
||
return posi;
|
||
}
|
||
|
||
public static ClientBalanceForTrsResponse GetClientBalance(int clientId)
|
||
{
|
||
if (_yLCache != null)
|
||
{
|
||
return _yLCache.StringGet<ClientBalanceForTrsResponse>("ClientBalance:" + clientId);
|
||
}
|
||
return null;
|
||
}
|
||
}
|
||
}
|