232 lines
8.3 KiB
C#
232 lines
8.3 KiB
C#
using YLErp.DBModels.Enums;
|
|
|
|
namespace YLErp.Modules.EodModule.SettlementModule
|
|
{
|
|
/// <summary>
|
|
/// 远期预付金收盘结算
|
|
/// </summary>
|
|
class EodForwardMarginSettlement : EodSettleServiceBaseV2
|
|
{
|
|
public const string Step = "远期预付金结算";
|
|
|
|
public EodForwardMarginSettlement(EodSettlementContextV2 context) : base(context)
|
|
{
|
|
|
|
}
|
|
|
|
/// <summary>
|
|
/// 远期预付金收盘结算
|
|
/// </summary>
|
|
public void Execute()
|
|
{
|
|
var settleDate = _context.SettleDate;
|
|
|
|
//计息天数--如果大于1时判断中间的日期是否遗漏了收盘
|
|
var interestDays = (settleDate - _context.PreSettleDate).Days;
|
|
|
|
//--------------------------------------------
|
|
// 删除旧数据
|
|
//--------------------------------------------
|
|
if (_context.ClienIds!=null&& _context.ClienIds.Any())
|
|
{
|
|
var forwardTradeId = _context.OtcTrades.Where(t => t.TradeType == "远期").Where(l => _context.ClienIds.Contains(l.ClientId)).Select(l => l.id);
|
|
|
|
var forwardTradeIdStr = string.Join(",", forwardTradeId);
|
|
if (!string.IsNullOrEmpty(forwardTradeIdStr))
|
|
{
|
|
DbContext.BulkDelete<eod_forward_margin>($" TradeId in({forwardTradeIdStr}) and ValueDate='{settleDate:yyyy-MM-dd}'");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
DbContext.BulkDelete<eod_forward_margin>($"ValueDate='{settleDate:yyyy-MM-dd}'");
|
|
}
|
|
//--------------------------------------------
|
|
// 基础数据查询 -- 构建查询时需要考虑历史收盘
|
|
// 最后一天为特殊场景,需要考虑到和上一交易日中间有假期的情况
|
|
//--------------------------------------------
|
|
|
|
var tradeInfos = _context.OtcTrades.Where(t => t.TradeType == "远期").Select(GetTradeInfo).ToArray();
|
|
|
|
_context.CheckCanceled();
|
|
|
|
if (!tradeInfos.Any())
|
|
{
|
|
return;
|
|
}
|
|
|
|
var eodLastDic = DbContext.eod_forward_margin.Where(a => a.ValueDate == _context.PreSettleDate).ToDictionary(n => n.TradeId);
|
|
|
|
foreach (var td in tradeInfos)
|
|
{
|
|
eodLastDic.TryGetValue(td.TradeId, out var lastMargin);
|
|
|
|
if (lastMargin == null && _context.PreSettleDate >= td.TradeDate)
|
|
{
|
|
_context.RaiseError(Step, $"交易'{td.TradeNumber}'缺少上一交易日({_context.PreSettleDate.OtcFormatDate()})结算数据");
|
|
}
|
|
|
|
var margin = CalcMargin(td, lastMargin, interestDays);
|
|
|
|
DbContext.eod_forward_margin.Add(margin);
|
|
}
|
|
|
|
DbContext.SaveChanges(_context.CancellationToken);
|
|
}
|
|
|
|
private eod_forward_margin CalcMargin(TradeInfo td, eod_forward_margin lastMargin, int interestDays)
|
|
{
|
|
var margin = new eod_forward_margin
|
|
{
|
|
AnnualRate = td.AnnualMarginRate,
|
|
Notional = td.Notional,
|
|
TradeId = td.TradeId,
|
|
ValueDate = _context.SettleDate,
|
|
MarginRate = 0,
|
|
SettlePrice = 0,
|
|
MarginSum = 0,
|
|
CloseMarginSum = 0,
|
|
InterestDays = 0,
|
|
MarginCost = 0
|
|
};
|
|
|
|
double? settlePrice = null;
|
|
|
|
switch (td.MarginType)
|
|
{
|
|
case MarginTypeEnum.NONE:
|
|
margin.MarginRate = 0;
|
|
break;
|
|
case MarginTypeEnum.FIXED:
|
|
margin.MarginRate = td.PositionMarginRate;
|
|
settlePrice = td.SpotPrice;
|
|
break;
|
|
case MarginTypeEnum.FLOAT:
|
|
margin.MarginRate = td.MarginRate;
|
|
break;
|
|
default:
|
|
var um = UnderlyingDataProvider.GetUnderlying(td.UnderlyingCode);
|
|
if (um == null)
|
|
{
|
|
_context.RaiseError(Step, $"交易'{td.TradeNumber}'未找到标的数据:{td.UnderlyingCode}");
|
|
}
|
|
margin.MarginRate = um.MarginRate ?? 0;
|
|
break;
|
|
case MarginTypeEnum.FLOATP:
|
|
margin.MarginRate = td.PositionMarginRate;
|
|
break;
|
|
}
|
|
|
|
if (settlePrice == null)
|
|
{
|
|
if (_context.GetEodPriceProvider().TryGetPrice(td.UnderlyingCode, SettlementTypeEnum.ClosePrice, out var price))
|
|
{
|
|
settlePrice = price;
|
|
}
|
|
else
|
|
{
|
|
_context.RaiseError(Step, $"交易'{td.TradeNumber}'未找到标的结算价:{td.UnderlyingCode}");
|
|
}
|
|
}
|
|
|
|
margin.SettlePrice = settlePrice.Value;
|
|
|
|
if (lastMargin != null)
|
|
{
|
|
margin.MarginSum = lastMargin.MarginSum;
|
|
margin.CloseMarginSum = lastMargin.CloseMarginSum;
|
|
|
|
//首先结算假期日的预付金成本
|
|
if (interestDays > 1)
|
|
{
|
|
margin.InterestDays = interestDays - 1;
|
|
//使用上一交易日结算数据重算(因为上一交易日也可能会有假期日的计息)
|
|
margin.MarginCost = lastMargin.Notional * settlePrice.Value * lastMargin.MarginRate * lastMargin.AnnualRate * margin.InterestDays / 365;
|
|
}
|
|
}
|
|
|
|
//当前交易日持仓预付金成本结算
|
|
if (td.IsCompleted)
|
|
{
|
|
margin.Notional = 0;
|
|
}
|
|
else
|
|
{
|
|
margin.InterestDays += 1;
|
|
|
|
if (lastMargin != null && lastMargin.Notional > td.Notional)
|
|
{
|
|
//算法:昨日预付金累计+(昨日持仓预付金累计=昨日预付金累计-昨日平仓预付金累计)*(今日平仓数量=昨日持仓份额-今日持仓份额)/昨日持仓份额
|
|
margin.CloseMarginSum += (lastMargin.MarginSum - lastMargin.CloseMarginSum) * (lastMargin.Notional - td.Notional) / lastMargin.Notional;
|
|
}
|
|
|
|
margin.MarginCost += margin.Notional * settlePrice.Value * margin.MarginRate * margin.AnnualRate / 365;
|
|
}
|
|
|
|
margin.MarginSum += margin.MarginCost;
|
|
|
|
return margin;
|
|
}
|
|
|
|
class TradeInfo
|
|
{
|
|
public int TradeId { get; set; }
|
|
|
|
public string TradeNumber { get; set; }
|
|
|
|
public DateTime TradeDate { get; set; }
|
|
|
|
public double Notional { get; set; }
|
|
|
|
public MarginTypeEnum MarginType { get; set; }
|
|
|
|
public double AnnualMarginRate { get; set; }
|
|
|
|
public string UnderlyingCode { get; set; }
|
|
|
|
public double SpotPrice { get; set; }
|
|
|
|
public double MarginRate { get; set; }
|
|
|
|
public double PositionMarginRate { get; set; }
|
|
|
|
public bool IsCompleted { get; set; }
|
|
|
|
public override string ToString()
|
|
{
|
|
return $"{UnderlyingCode}--{TradeId}--{TradeNumber}";
|
|
}
|
|
}
|
|
|
|
private TradeInfo GetTradeInfo(trade td)
|
|
{
|
|
if (td.TradeDate == null)
|
|
{
|
|
_context.RaiseError(Step, "缺少交易日期:" + td.TradeNumber);
|
|
return null;
|
|
}
|
|
|
|
if (td.trade_forward == null)
|
|
{
|
|
_context.RaiseError(Step, "缺少远期交易数据:" + td.TradeNumber);
|
|
return null;
|
|
}
|
|
|
|
return new TradeInfo
|
|
{
|
|
TradeId = td.id,
|
|
TradeNumber = td.TradeNumber,
|
|
TradeDate = td.TradeDate.Value,
|
|
MarginType = td.MarginType,
|
|
Notional = td.Notional,
|
|
AnnualMarginRate = td.trade_forward.AnnualMarginRate,
|
|
UnderlyingCode = td.UnderlyingCode,
|
|
SpotPrice = td.SpotPrice ?? 0,
|
|
MarginRate = td.MarginRate,
|
|
PositionMarginRate = td.PositionMarginRate,
|
|
IsCompleted = ConsTrade.TradeCompleteStatus.Contains(td.TradeStatus)
|
|
};
|
|
}
|
|
}
|
|
}
|