335 lines
12 KiB
C#
335 lines
12 KiB
C#
using YLErp.Modules.CalculationModule;
|
|
using YLErp.Modules.CalculationModule.Abstract;
|
|
using YLErp.Modules.DataProviderModule;
|
|
|
|
namespace YLErp.Modules.TradeRiskCalcModule
|
|
{
|
|
/// <summary>
|
|
/// 实时场内持仓计算服务(用于API)
|
|
/// </summary>
|
|
public class ExchangePositionCalcService
|
|
{
|
|
readonly DateTime _valueDate;
|
|
readonly IExchangeTradeCommissionCalc _commissionCalc;
|
|
|
|
public ExchangePositionCalcService(DateTime valueDate, IExchangeTradeCommissionCalc commissionCalc)
|
|
{
|
|
_valueDate = valueDate.Date;
|
|
_commissionCalc = commissionCalc ?? throw new ArgumentNullException(nameof(commissionCalc));
|
|
}
|
|
|
|
/// <summary>
|
|
/// 计算对冲交易Pnl信息
|
|
/// </summary>
|
|
/// <param name="newHedgeTrades">当日对冲交易数据</param>
|
|
/// <param name="eodPositions">上日持仓数据</param>
|
|
public IEnumerable<ExchangePositionItem> Calculate(IEnumerable<ExchangeTrade> newHedgeTrades
|
|
, IEnumerable<EodTradePosition> eodPositions, ExchangeOptionPriceProvider exchangeOptionPriceProvider)
|
|
{
|
|
if (newHedgeTrades == null && eodPositions == null)
|
|
{
|
|
return Enumerable.Empty<ExchangePositionItem>();
|
|
}
|
|
|
|
var resultDic = new Dictionary<string, ExchangePositionItem>();
|
|
|
|
var exchangeOptionSource = DataCacheProvider.GetExchangeListOptionDataSource();
|
|
|
|
//-----------------------------------------
|
|
// 处理昨日持仓
|
|
//-----------------------------------------
|
|
|
|
if (eodPositions != null)
|
|
{
|
|
foreach (var eod in eodPositions)
|
|
{
|
|
if (eod.TradeType == "场内期权")
|
|
{
|
|
var exchangeOption = exchangeOptionSource.GetData(eod.ExchangeOptionCode);
|
|
|
|
//剔除已到期场内期权持仓
|
|
if (exchangeOption == null || exchangeOption.MaturityDate < _valueDate)
|
|
{
|
|
continue;
|
|
}
|
|
}
|
|
|
|
var uniqueCode = GetUniqueCode(eod.BookId, eod.TradeType, eod.UnderlyingCode, eod.ExchangeOptionCode);
|
|
|
|
if (!resultDic.TryGetValue(uniqueCode, out var result))
|
|
{
|
|
resultDic[uniqueCode] = result = new ExchangePositionItem()
|
|
{
|
|
//合成唯一标记
|
|
BookId = eod.BookId,
|
|
TradeType = eod.TradeType,
|
|
UnderlyingCode = eod.UnderlyingCode,
|
|
ExchangeOptionCode = eod.ExchangeOptionCode,
|
|
|
|
BookName = null,
|
|
ExchangeId = null,
|
|
LastPriceTime = null
|
|
};
|
|
}
|
|
|
|
//合并汇总
|
|
result.Commission += eod.Commission;
|
|
result.PositionProfit += eod.TotalPnL;
|
|
|
|
if (Math.Abs(eod.Amount) > 0)
|
|
{
|
|
if ("short" == eod.PositionType)
|
|
{
|
|
result.Volume_Short += eod.Amount;
|
|
//因为TotalPnL已经包括了Cost所以用PV
|
|
result.PositionCost_Short += eod.Pv;
|
|
}
|
|
else
|
|
{
|
|
result.Volume_Long += eod.Amount;
|
|
result.PositionCost_Long += eod.Pv;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------
|
|
// 处理当日交易
|
|
//-----------------------------------------
|
|
|
|
if (newHedgeTrades != null && newHedgeTrades.Any())
|
|
{
|
|
//对冲交易手续费计算
|
|
var tradeCommissionDict = _commissionCalc.GetTradeCommission(newHedgeTrades);
|
|
|
|
foreach (var newTrade in newHedgeTrades)
|
|
{
|
|
//手续费
|
|
var commission = tradeCommissionDict.GetTradeCommission(newTrade.id);
|
|
|
|
//对冲唯一编码(簿记账户ID_结构类型_持仓类型_合约代码)
|
|
var uniqueCode = GetUniqueCode(newTrade.AssetBookId, newTrade.TradeType, newTrade.UnderlyingCode, newTrade.OptionCode);
|
|
|
|
//当日交易数量/成本(根据交易方向取正负号)
|
|
var volume = newTrade.Notional * TradeCalcHelper.GetSign(newTrade.TradeSide);
|
|
|
|
var cost = newTrade.TradeSinglePrice * volume + commission;
|
|
|
|
//获取是否存在对应uniqueCode的对冲信息
|
|
if (!resultDic.TryGetValue(uniqueCode, out var result))
|
|
{
|
|
resultDic[uniqueCode] = result = new ExchangePositionItem()
|
|
{
|
|
BookId = newTrade.AssetBookId,
|
|
TradeType = newTrade.TradeType,
|
|
UnderlyingCode = newTrade.UnderlyingCode,
|
|
ExchangeOptionCode = newTrade.OptionCode,
|
|
|
|
BookName = null,
|
|
ExchangeId = null,
|
|
LastPriceTime = null
|
|
};
|
|
}
|
|
|
|
//合并汇总
|
|
result.Commission += commission;
|
|
|
|
if (newTrade.TradeSide != null && newTrade.TradeSide.Contains("空头"))
|
|
{
|
|
result.Volume_Short += volume;
|
|
result.PositionCost_Short += cost;
|
|
}
|
|
else
|
|
{
|
|
result.Volume_Long += volume;
|
|
result.PositionCost_Long += cost;
|
|
}
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------
|
|
// 计算持仓盈亏
|
|
//-----------------------------------------
|
|
|
|
var underlyingSource = DataCacheProvider.GetUnderlyingDataSource();
|
|
var assetBookSource = DataCacheProvider.GetAssetUnitDataSource();
|
|
|
|
if (exchangeOptionPriceProvider == null)
|
|
{
|
|
exchangeOptionPriceProvider = new ExchangeOptionPriceProvider();
|
|
}
|
|
|
|
foreach (var result in resultDic.Values)
|
|
{
|
|
var um = underlyingSource.GetData(result.UnderlyingCode);
|
|
|
|
result.ExchangeId = um.MarketCode;
|
|
|
|
var hasPrice = false;
|
|
|
|
if (result.TradeType == "场内期权")
|
|
{
|
|
if (exchangeOptionPriceProvider.TryGetPriceModel(result.ExchangeOptionCode, out var model))
|
|
{
|
|
hasPrice = true;
|
|
result.LastPrice = model.Price;
|
|
result.LastPriceTime = model.PriceTime?.ToString("yyyy-MM-dd'T'HH:mm:ss.fff");
|
|
}
|
|
}
|
|
else if (um != null)
|
|
{
|
|
hasPrice = true;
|
|
result.LastPrice = um.Price ?? 0;
|
|
result.LastPriceTime = um.LastUpdateTime?.ToString("yyyy-MM-dd'T'HH:mm:ss.fff");
|
|
}
|
|
|
|
if (hasPrice)
|
|
{
|
|
var longProfit = result.LastPrice * result.Volume_Long - result.PositionCost_Long;
|
|
var shortProfit = result.LastPrice * result.Volume_Short - result.PositionCost_Short;
|
|
result.PositionProfit += longProfit + shortProfit;
|
|
}
|
|
|
|
result.BookName = assetBookSource.GetData(result.BookId)?.Name;
|
|
}
|
|
|
|
return resultDic.Values;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 根据对冲账号 簿记账户I 结构类型 买卖方向 标的代码 场内期权代码 编制对冲唯一编码
|
|
/// </summary>
|
|
private static string GetUniqueCode(int BookId, string TradeType, string UnderlyingCode, string ExchangeOptionCode = null)
|
|
{
|
|
return $"{BookId}_{TradeType}_{("场内期权".Equals(TradeType) ? ExchangeOptionCode : UnderlyingCode)}";
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 场内持仓计算单项结果
|
|
/// </summary>
|
|
public class ExchangePositionItem
|
|
{
|
|
/// <summary>
|
|
/// [unique]簿记ID
|
|
/// </summary>
|
|
public int BookId { get; set; }
|
|
|
|
/// <summary>
|
|
/// 簿记账户
|
|
/// </summary>
|
|
public string BookName { get; set; }
|
|
|
|
/// <summary>
|
|
/// [unique]交易类型
|
|
/// </summary>
|
|
public string TradeType { get; set; }
|
|
|
|
/// <summary>
|
|
/// [unique]标的代码
|
|
/// </summary>
|
|
public string UnderlyingCode { get; set; }
|
|
|
|
/// <summary>
|
|
/// [unique]场内期权代码
|
|
/// </summary>
|
|
public string ExchangeOptionCode { get; set; }
|
|
|
|
/// <summary>
|
|
/// 持仓数量
|
|
/// </summary>
|
|
public double Volume => Volume_Long + Volume_Short;
|
|
|
|
/// <summary>
|
|
/// 手续费
|
|
/// </summary>
|
|
public double Commission { get; set; }
|
|
|
|
/// <summary>
|
|
/// 持仓成本
|
|
/// </summary>
|
|
public double PositionCost => PositionCost_Long + PositionCost_Short;
|
|
|
|
/// <summary>
|
|
/// 最新价
|
|
/// </summary>
|
|
public double LastPrice { get; set; }
|
|
|
|
/// <summary>
|
|
/// 最新价更新时间(格式:yyyy-MM-ddTHH:mm:ss.fff)
|
|
/// </summary>
|
|
public string LastPriceTime { get; set; }
|
|
|
|
/// <summary>
|
|
/// 持仓盈亏
|
|
/// </summary>
|
|
public double PositionProfit { get; set; }
|
|
|
|
/// <summary>
|
|
/// 交易所ID
|
|
/// </summary>
|
|
public string ExchangeId { get; set; }
|
|
|
|
/// <summary>
|
|
/// 多头持仓成本(使用了正号处理)
|
|
/// </summary>
|
|
public double Volume_Long { get; set; }
|
|
|
|
/// <summary>
|
|
/// 空头持仓成本(使用了负号处理)
|
|
/// </summary>
|
|
public double Volume_Short { get; set; }
|
|
|
|
/// <summary>
|
|
/// 多头持仓成本(使用了正号处理)
|
|
/// </summary>
|
|
public double PositionCost_Long { get; set; }
|
|
|
|
/// <summary>
|
|
/// 空头持仓成本(使用了负号处理)
|
|
/// </summary>
|
|
public double PositionCost_Short { get; set; }
|
|
|
|
public override string ToString()
|
|
{
|
|
return $"{BookName}--{UnderlyingCode}--{Volume}--{LastPrice}--{PositionProfit}--{PositionCost}";
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 场内持仓计算结果
|
|
/// </summary>
|
|
public class ExchangePositionCalcResult
|
|
{
|
|
/// <summary>
|
|
/// 当前结算日
|
|
/// </summary>
|
|
public DateTime ValueDate { get; set; }
|
|
|
|
/// <summary>
|
|
/// 上个结算日
|
|
/// </summary>
|
|
public DateTime LastDate { get; set; }
|
|
|
|
/// <summary>
|
|
/// 结果数据集合
|
|
/// </summary>
|
|
public IEnumerable<ExchangePositionItem> Items { get; set; }
|
|
|
|
/// <summary>
|
|
/// 开始运行时间
|
|
/// </summary>
|
|
public DateTime StartTime { get; }
|
|
|
|
/// <summary>
|
|
/// 结束运行时间
|
|
/// </summary>
|
|
public DateTime EndTime { get; } = DateTime.Now;
|
|
|
|
public ExchangePositionCalcResult(DateTime startTime)
|
|
{
|
|
StartTime = startTime;
|
|
}
|
|
}
|
|
}
|