466 lines
17 KiB
C#
466 lines
17 KiB
C#
using Qdp.ComputeServiceV2.Data.CommonModels.TradeInfos;
|
|
using Qdp.Foundation.Implementations;
|
|
using Qdp.Pricing.Base.Utilities;
|
|
using System.Runtime.CompilerServices;
|
|
using YLErp.BLL;
|
|
using YLErp.BLL.Calculation;
|
|
using YLErp.QdpModule;
|
|
|
|
namespace YLErp.Modules.CalculationModule
|
|
{
|
|
/// <summary>
|
|
/// 交易计算帮助类
|
|
/// </summary>
|
|
public static class TradeCalcHelper
|
|
{
|
|
/// <summary>
|
|
/// [交易员角度]根据交易方向判断了结金额是否需要加符号
|
|
/// <para>开仓费不适用 -- 开仓费是客户方向</para>
|
|
/// </summary>
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
public static int GetBuySellSign(string buySell)
|
|
{
|
|
switch (buySell)
|
|
{
|
|
case "卖出":
|
|
case "融券卖出":
|
|
case "多头平仓":
|
|
case "空头开仓":
|
|
return -1;
|
|
default:
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// [交易员角度]根据交易方向判断了结金额是否需要加符号
|
|
/// <para>开仓费不适用 -- 开仓费是客户方向</para>
|
|
/// </summary>
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
public static int GetSign(string buySell)
|
|
{
|
|
return GetBuySellSign(buySell);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 远期计算(从交易员角度计算)
|
|
/// </summary>
|
|
public static void CalcForwardValue(OtcTrade trade, double spotPrice, out double pv, out double pnl)
|
|
{
|
|
//1.结算报告,期权价格 / 单价: 现价 - 交割价格
|
|
//2.持仓市值: 期权价格* 持仓数量
|
|
//3.持仓盈亏: 持仓市值 - 持仓数量比率 * 开仓总费用
|
|
|
|
pv = pnl = 0;
|
|
|
|
if (trade.Notional <= 1e-7)
|
|
{
|
|
return;
|
|
}
|
|
|
|
var priceChange = 0d;
|
|
|
|
if (trade.OptionType == "看涨")
|
|
{
|
|
priceChange = spotPrice - (trade?.Strike ?? 0);
|
|
}
|
|
else if (trade.OptionType == "看跌")
|
|
{
|
|
priceChange = (trade?.Strike ?? 0) - spotPrice;
|
|
}
|
|
|
|
//以交易员角度
|
|
if (trade.BuySell == "卖出")
|
|
{
|
|
priceChange = -priceChange;
|
|
}
|
|
|
|
pv = priceChange * trade.Notional;
|
|
pnl = pv + (trade.OriginalNotional > 0 && trade.TradePrice >= 0 ? trade.TradePrice.Value * trade.Notional / trade.OriginalNotional.Value : 0);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 对冲交易 根据结构类型 买卖方向 看涨看跌 获取持仓long short
|
|
/// </summary>
|
|
public static string GetHedgeLongShort(string TradeType, string BuySell)
|
|
{
|
|
switch (TradeType)
|
|
{
|
|
case "信用债":
|
|
case "商品期货":
|
|
case "场内期权":
|
|
return BuySell.Contains("多头") ? "long" : "short";
|
|
case "股票":
|
|
default: return "long";
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 根据期权交易获取QDP Trade
|
|
/// </summary>
|
|
public static TradeBase GetQdpTrade(trade tr, DateTime? valueDate = null, string fixings = null)
|
|
{
|
|
if (!valueDate.HasValue)
|
|
{
|
|
valueDate = valuedateBLL.ValueDate;
|
|
}
|
|
|
|
TradeBase tempTrade = null;
|
|
|
|
var sysRiskRate = valuedateBLL.RiskFreeRate / 100;
|
|
|
|
OptionTradeParamRequest getRequest()
|
|
{
|
|
return new OptionTradeParamRequest(sysRiskRate)
|
|
{
|
|
tradeId = tr.TradeNumber,
|
|
fixings = null,
|
|
hasNightMarket = false,
|
|
maturityShift = 0,
|
|
ParamOverride = null,
|
|
preciseTimeMode = false,
|
|
timeToMaturityDays = double.NaN,
|
|
volSurfaceNames = null
|
|
};
|
|
}
|
|
|
|
switch (tr.TradeType)
|
|
{
|
|
case "自定义交易":
|
|
{
|
|
tempTrade = new ManualTrade(tr.id.ToString(), new Date(tr.TradeDate.Value.Date),
|
|
new Date(tr.StartDate ?? valueDate.Value), new Date(tr.MaturityDate ?? valueDate.Value),
|
|
QdpConverter.ConvertTradeType(tr.BuySell), tr.Notional, 0);
|
|
break;
|
|
}
|
|
|
|
case "收益互换":
|
|
{
|
|
tempTrade = new SwapTrade(tr.id.ToString(), new Date(tr.TradeDate.Value.Date),
|
|
new Date(tr.StartDate ?? valueDate.Value), new Date(tr.MaturityDate ?? valueDate.Value),
|
|
QdpConverter.ConvertTradeType(tr.BuySell), tr.Notional, tr.SpotPrice ?? 0);
|
|
break;
|
|
}
|
|
|
|
case "亚式期权":
|
|
{
|
|
var request = getRequest();
|
|
request.fixings = fixings.TrimToNull() ?? AsianOptionFixingService.GetFixingString(valueDate.Value, tr);
|
|
tempTrade = QdpTradeBuilder.GetAsianOptionTrade(tr, tr.trade_asian_option, request);
|
|
break;
|
|
}
|
|
|
|
case "彩虹期权":
|
|
tempTrade = QdpTradeBuilder.GetRainbowOptionTrade(tr, tr.trade_rainbow_option, getRequest());
|
|
break;
|
|
|
|
case "合成价差期权":
|
|
tempTrade = QdpTradeBuilder.GetSSpreadOptionTrade(tr, getRequest());
|
|
break;
|
|
|
|
case "香草期权":
|
|
tempTrade = QdpTradeBuilder.GetVanillaOptionTrade(tr, getRequest(), false);
|
|
break;
|
|
|
|
case "Risky期权":
|
|
tempTrade = QdpTradeBuilder.GetVanillaOptionTrade(tr, getRequest(), false);
|
|
break;
|
|
|
|
case "障碍期权":
|
|
tempTrade = QdpTradeBuilder.GetBarrierOptionTrade(tr, tr.trade_barrier_option, getRequest());
|
|
break;
|
|
|
|
case "二元期权":
|
|
|
|
tempTrade = QdpTradeBuilder.GetBinaryOptionTrade(tr, tr.trade_binary_option, getRequest());
|
|
break;
|
|
|
|
case "双鲨期权":
|
|
tempTrade = QdpTradeBuilder.GetDoubleSharkFinOptionTrade(tr, tr.trade_double_sharkfin_option, getRequest());
|
|
break;
|
|
|
|
case "凤凰期权":
|
|
tempTrade = QdpTradeBuilder.GetAutocallOptionTrade(tr, tr.trade_autocall, getRequest());
|
|
break;
|
|
|
|
case "雪球期权":
|
|
tempTrade = QdpTradeBuilder.GetSnowballOptionTrade(tr, tr.trade_snowball, getRequest());
|
|
break;
|
|
|
|
case "区间累积期权":
|
|
tempTrade = QdpTradeBuilder.GetRangeAccrualTrade(tr, tr.trade_rangeaccrual, getRequest());
|
|
break;
|
|
|
|
case "气囊结构":
|
|
tempTrade = QdpTradeBuilder.GetAirbagOptionTrade(tr, tr.trade_airbag, getRequest());
|
|
break;
|
|
|
|
case "累计期权":
|
|
{
|
|
var req = getRequest();
|
|
req.happenedObservations = ObservationDataService.QueryDatas(tr.id, valueDate.Value);
|
|
tempTrade = QdpTradeBuilder.GetAccumulatorOptionTrade(tr, tr.trade_accumulator_option, req);
|
|
}
|
|
break;
|
|
|
|
case "收益增强结构":
|
|
tempTrade = QdpTradeBuilder.GetUnderlyingEnhanceTrade(tr, tr.trade_underlying_enhance, getRequest());
|
|
break;
|
|
case "现金流交易":
|
|
tempTrade = QdpTradeBuilder.GetCashFlowTrade(tr, tr.trade_cashflow, getRequest());
|
|
break;
|
|
}
|
|
|
|
return tempTrade;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 根据valuedate配置判断是否使用收盘价进行结算
|
|
/// </summary>
|
|
public static bool UseClosePrice()
|
|
{
|
|
return ConsGlobal.SettlePriceMode.UseClosePrice(valuedateBLL.SystemDate.EodSettlePriceMode);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 根据valuedate配置获取收盘结算价类型
|
|
/// </summary>
|
|
public static SettlementTypeEnum GetSettlementType()
|
|
{
|
|
return ConsGlobal.SettlePriceMode.GetSettlementType(valuedateBLL.SystemDate.EodSettlePriceMode);
|
|
}
|
|
|
|
/// <summary>
|
|
///
|
|
/// </summary>
|
|
public static string GetTradeStatus(trade trade, trade_cash tradeCash)
|
|
{
|
|
if (tradeCash.Action == "系统操作-平仓费" && (tradeCash.UnwindType == "部分平仓" || tradeCash.UnwindType == "全部平仓"))
|
|
{
|
|
return "已平仓";
|
|
}
|
|
if (tradeCash.Action == "系统操作-行权费" && tradeCash.ExerciseWay == "到期行权" && tradeCash.UnwindType == "到期")
|
|
{
|
|
return "已到期";
|
|
}
|
|
if ((tradeCash.Action == "系统操作-平仓费" && tradeCash.UnwindType == "部分行权") || tradeCash.Action == "系统操作-行权费")
|
|
{
|
|
return "已行权";
|
|
}
|
|
return trade.TradeStatus;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 以交易员角度计算盈亏
|
|
/// </summary>
|
|
/// <param name="buySell">交易方向</param>
|
|
/// <param name="tradePrice">交易总额</param>
|
|
/// <param name="tcUnwindPercent">平仓比例</param>
|
|
/// <param name="tcAmount">平仓收支</param>
|
|
public static double CalcWinLoss(string tradeType, string buySell, double tradePrice, double tcUnwindPercent, double tcAmount)
|
|
{
|
|
int costSign = 1; //收入计为1,支出计为-1
|
|
|
|
if (tradeType != "远期")
|
|
{
|
|
switch (buySell)
|
|
{
|
|
case "卖出":
|
|
case "融券卖出":
|
|
case "多头平仓":
|
|
case "空头开仓": break;
|
|
default:
|
|
costSign = -1;
|
|
break;
|
|
}
|
|
}
|
|
|
|
return tcAmount + tradePrice * tcUnwindPercent * costSign;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 计算单笔交易或多笔交易实现盈亏
|
|
/// </summary>
|
|
/// <param name="td"></param>
|
|
/// <param name="tc"></param>
|
|
/// <param name="IsUsePremiumRate"></param>
|
|
/// <param name="CountRatio"></param>
|
|
/// <returns></returns>
|
|
public static double CalcChildTradeSumWinLoss(trade td, trade_cash tc, bool IsUsePremiumRate = false, int CountRatio = 0)
|
|
{
|
|
using (var db = DbContextFactory.GetYLDbContext())
|
|
{
|
|
double TcTradePrice = 0;
|
|
|
|
if (td.IsGroup == 1)
|
|
{
|
|
var childTradeCashs = db.trade_cash.Where(x => x.ParentTradeCashId == tc.id).ToList();
|
|
var childTradeIds = childTradeCashs.Select(x => x.TradeId).Distinct().ToList();
|
|
var childTrades = db.trade.Where(x => childTradeIds.Contains(x.id)).ToList();
|
|
|
|
childTradeCashs.ForEach(x =>
|
|
{
|
|
var trade = childTrades.FirstOrDefault(y => y.id == x.TradeId);
|
|
TcTradePrice += (x.UnwindPercentRate * trade?.TradePrice * (trade?.BuySell == "买入" ? -1 : 1)) ?? 0;
|
|
});
|
|
return tc.Amount + TcTradePrice;
|
|
}
|
|
else
|
|
{
|
|
if (IsUsePremiumRate)
|
|
{
|
|
return (tc.Amount + (td.BuySell == "卖出" ? 1 : -1) * ((tc?.UnwindTradeAmount ?? 0) * CountRatio / td.OriginalNotional) * td.TradePrice) ?? 0;
|
|
}
|
|
|
|
return (tc.Amount + (td.BuySell == "卖出" ? 1 : -1) * (tc?.UnwindPercentRate ?? 0) * td?.TradePrice) ?? 0;
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 计算TTM
|
|
/// </summary>
|
|
public static double CalculateTTMDays(DateTime from, DateTime to, int varietyid, bool precisionOfMinute, DateTime? serverDateTime = null)
|
|
{
|
|
var dayCount = valuedateBLL.TradeDayCount.ToDayCountImpl();
|
|
var variety = DataCacheProvider.GetVarietyDataSource().GetData(varietyid);
|
|
if (serverDateTime == null)
|
|
{
|
|
serverDateTime = DateTime.Now;
|
|
}
|
|
return QdpHelper.CalculateTTMDays(from, to, valuedateBLL.ValueDate, serverDateTime.Value, dayCount, variety != null && variety.HasNightMarket, precisionOfMinute);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 计算TTM
|
|
/// </summary>
|
|
public static double CalculateTTMDaysForXiangYu(DateTime from, DateTime to, int varietyid, bool precisionOfMinute, DateTime? serverDateTime = null)
|
|
{
|
|
var dayCount = valuedateBLL.TradeDayCount.ToDayCountImpl();
|
|
var variety = DataCacheProvider.GetVarietyDataSource().GetData(varietyid);
|
|
if (serverDateTime == null)
|
|
{
|
|
serverDateTime = DateTime.Now;
|
|
}
|
|
return QdpHelper.CalculateTTMDaysForXiangYu(from, to, valuedateBLL.ValueDate, serverDateTime.Value, dayCount, variety != null && variety.HasNightMarket, precisionOfMinute);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 传入OTC买卖方向判断是否买入方向
|
|
/// </summary>
|
|
public static bool IsBuy(string buySell)
|
|
{
|
|
return buySell == "Buy" || buySell == "买入" || string.IsNullOrWhiteSpace(buySell);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 用于计算成交数量(虚拟),如果countRatio传入的是1,就可以用来计算成交份额
|
|
/// </summary>
|
|
public static double GetTradeAmountV(OtcTradeBase trade, int? countRatio = null)
|
|
{
|
|
return InnerGetTradeAmountV(trade, null, countRatio);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 用于计算成交数量(虚拟),如果countRatio传入的是1,就可以用来计算成交份额
|
|
/// </summary>
|
|
public static double GetTradeAmountV(OtcTradeBase trade, double notional, int? countRatio = null)
|
|
{
|
|
return InnerGetTradeAmountV(trade, notional, countRatio);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 用于计算成交数量(实际),如果countRatio传入的是1,就可以用来计算成交份额
|
|
/// </summary>
|
|
public static double GetTradeAmount(OtcTradeBase trade, double notional, int? countRatio = null)
|
|
{
|
|
return InnerGetTradeAmount(trade, notional, countRatio);
|
|
}
|
|
|
|
/// <summary>
|
|
///
|
|
/// </summary>
|
|
private static double InnerGetTradeAmountV(OtcTradeBase trade, double? notional, int? countRatio)
|
|
{
|
|
if (trade is null)
|
|
{
|
|
throw new ArgumentNullException(nameof(trade));
|
|
}
|
|
|
|
var CountRatio = countRatio ?? trade.CountRatio ?? 0;
|
|
|
|
if (CountRatio < 1)
|
|
{
|
|
CountRatio = DataCacheProvider.GetUnderlyingDataSource().GetData(trade.UnderlyingCode)?.CountRatio ?? 1;
|
|
}
|
|
|
|
if (trade.TradeType == "累计期权")
|
|
{
|
|
return (notional ?? trade.OriginalNotional ?? 0) / CountRatio;
|
|
}
|
|
|
|
if (!notional.HasValue)
|
|
{
|
|
var SpotPrice = trade.SpotPrice ?? 0;
|
|
|
|
if (Math.Abs(SpotPrice) > 0)
|
|
{
|
|
return (trade.OriginalStockEqvNotional ?? 0) / Math.Abs(SpotPrice) / CountRatio;
|
|
}
|
|
}
|
|
|
|
var annRate = (trade.ParticipationRate ?? 1) * (trade.AnnualizeFactor ?? 1);
|
|
|
|
if (annRate < 1e-8)
|
|
{
|
|
annRate = 1;
|
|
}
|
|
|
|
return (notional ?? trade.OriginalNotional ?? 0) / annRate / CountRatio;
|
|
}
|
|
|
|
/// <summary>
|
|
///
|
|
/// </summary>
|
|
private static double InnerGetTradeAmount(OtcTradeBase trade, double? notional, int? countRatio)
|
|
{
|
|
if (trade is null)
|
|
{
|
|
throw new ArgumentNullException(nameof(trade));
|
|
}
|
|
|
|
var CountRatio = countRatio ?? trade.CountRatio ?? 0;
|
|
|
|
if (CountRatio < 1)
|
|
{
|
|
CountRatio = DataCacheProvider.GetUnderlyingDataSource().GetData(trade.UnderlyingCode)?.CountRatio ?? 1;
|
|
}
|
|
|
|
if (trade.TradeType == "累计期权")
|
|
{
|
|
return (notional ?? trade.OriginalNotional ?? 0) / CountRatio;
|
|
}
|
|
|
|
if (!notional.HasValue)
|
|
{
|
|
var SpotPrice = trade.SpotPrice ?? 0;
|
|
|
|
if (Math.Abs(SpotPrice) > 0)
|
|
{
|
|
return (trade.OriginalStockEqvNotional ?? 0) / Math.Abs(SpotPrice) / CountRatio;
|
|
}
|
|
}
|
|
|
|
var annRate = (trade.ParticipationRate ?? 1) * (trade.AnnualizeFactor ?? 1);
|
|
|
|
if (annRate < 1e-8)
|
|
{
|
|
annRate = 1;
|
|
}
|
|
|
|
return (notional ?? trade.OriginalNotional ?? 0) * annRate / CountRatio;
|
|
}
|
|
}
|
|
}
|