407 lines
18 KiB
C#
407 lines
18 KiB
C#
using BaseOUDAL;
|
|
using YLErp.BLL;
|
|
using YLErp.Model;
|
|
using YLErp.QdpModule;
|
|
|
|
namespace YLErp.Modules.TradeModule.SwapModule
|
|
{
|
|
public class SwapTradeFlowCheckService : TradeServiceBase
|
|
{
|
|
public SwapTradeFlowCheckService(OptUserInfo userInfo) : base(userInfo)
|
|
{
|
|
|
|
}
|
|
|
|
public SwapTradeFlowCheckService(YLBaseService baseService) : base(baseService)
|
|
{
|
|
|
|
}
|
|
|
|
public void CheckSwapTradeFlow(string tradeNumber)
|
|
{
|
|
var valueDateConst = valuedateBLL.ValueDate;
|
|
var flowsAll = DbContext.trade_swap_flow.Where(x => x.TradeDate == valueDateConst).ToList();
|
|
var flows = flowsAll.Where(x => x.Status != "已完成").ToList();
|
|
var positionTrades = (from td in DbContext.trade.Where(x => x.TradeStatus == "确认成交" && x.TradeType == "收益互换" && x.ValidState != "InValid")
|
|
join ts in DbContext.trade_swap.Where(x => x.SwapType == "多空组合")
|
|
on td.id equals ts.TradeId
|
|
select new { td, ts }).ToList();
|
|
|
|
var existList = DbContext.trade_swap_flow_check_result.ToList();
|
|
|
|
if (!string.IsNullOrWhiteSpace(tradeNumber))
|
|
{
|
|
positionTrades = positionTrades.Where(x => x.td.TradeNumber == tradeNumber).ToList();
|
|
|
|
if (!positionTrades.Any())
|
|
{
|
|
throw new Exception($"未找到交易编号{tradeNumber}对应的多空组合持仓交易");
|
|
}
|
|
|
|
var tradeIds = positionTrades.Select(x => x.td.id).ToList();
|
|
existList = existList.Where(x => tradeIds.Contains(x.TradeId)).ToList();
|
|
}
|
|
|
|
if (existList.Any())
|
|
{
|
|
DbContext.trade_swap_flow_check_result.RemoveRange(existList);
|
|
}
|
|
|
|
positionTrades.ForEach(x =>
|
|
{
|
|
//创建一笔默认为成功的result
|
|
var result = new trade_swap_flow_check_result()
|
|
{
|
|
IsSuccess = true,
|
|
TradeId = x.td.id,
|
|
OptId = UserId,
|
|
OptName = UserName,
|
|
OptDate = DateTime.Now
|
|
};
|
|
|
|
//当天开仓的交易
|
|
if (x.ts.OriginalTradeId == null && x.td.TradeDate == valueDateConst)
|
|
{
|
|
if (!flowsAll.Any(y => y.TradeNumber == x.td.TradeNumber))
|
|
{
|
|
//验证失败
|
|
result.IsSuccess = false;
|
|
result.Detail += "该新开仓的多空组合交易未找到对应的操作流水;<br>";
|
|
}
|
|
else
|
|
{
|
|
var flowsThis = flows.Where(y => y.TradeNumber == x.td.TradeNumber).ToList();
|
|
//开始验证逻辑
|
|
DoCheck(x.td, flowsThis, result);
|
|
}
|
|
}
|
|
//前一天持仓的交易
|
|
else
|
|
{
|
|
var originalTradeNumber = x.td.TradeNumber;
|
|
|
|
if (x.ts.OriginalTradeId != null)
|
|
{
|
|
originalTradeNumber = DbContext.trade.Find(x.ts.OriginalTradeId).TradeNumber;
|
|
}
|
|
|
|
var flowsThis = flows.Where(y => y.TradeNumber == originalTradeNumber).ToList();
|
|
//开始验证逻辑
|
|
DoCheck(x.td, flowsThis, result);
|
|
}
|
|
|
|
SaveCheckResult(result);
|
|
});
|
|
}
|
|
|
|
public string CheckSwapTradeGroup(string tradeNumber)
|
|
{
|
|
var valueDateConst = valuedateBLL.ValueDate;
|
|
var flowsAll = DbContext.trade_swap_flow.Where(x => x.TradeDate == valueDateConst).ToList();
|
|
var flows = flowsAll.Where(x => x.Status != "已完成").ToList();
|
|
var positionTrade = (from td in DbContext.trade.Where(x => x.TradeStatus == "确认成交" && x.TradeNumber == tradeNumber && x.TradeType == "收益互换" && x.ValidState != "InValid")
|
|
join ts in DbContext.trade_swap.Where(x => x.SwapType == "多空组合")
|
|
on td.id equals ts.TradeId
|
|
select new { td, ts }).FirstOrDefault();
|
|
|
|
if (positionTrade == null)
|
|
{
|
|
throw new Exception($"未找到交易编号{tradeNumber}对应的多空组合持仓交易");
|
|
}
|
|
else
|
|
{
|
|
var existList = DbContext.trade_swap_flow_check_result.Where(x => x.TradeId == positionTrade.td.id).ToList();
|
|
if (existList.Any())
|
|
{
|
|
DbContext.trade_swap_flow_check_result.RemoveRange(existList);
|
|
}
|
|
}
|
|
|
|
//创建一笔默认为成功的result
|
|
var result = new trade_swap_flow_check_result()
|
|
{
|
|
IsSuccess = true,
|
|
TradeId = positionTrade.td.id,
|
|
OptId = UserId,
|
|
OptName = UserName,
|
|
OptDate = DateTime.Now
|
|
};
|
|
|
|
//当天开仓的交易
|
|
if (positionTrade.ts.OriginalTradeId == null && positionTrade.td.TradeDate == valueDateConst)
|
|
{
|
|
if (!flowsAll.Any(y => y.TradeNumber == positionTrade.td.TradeNumber))
|
|
{
|
|
//验证失败
|
|
result.IsSuccess = false;
|
|
result.Detail += "该新开仓的多空组合交易未找到对应的操作流水;<br>";
|
|
}
|
|
else
|
|
{
|
|
var flowsThis = flows.Where(y => y.TradeNumber == positionTrade.td.TradeNumber).ToList();
|
|
//开始验证逻辑
|
|
DoCheck(positionTrade.td, flowsThis, result);
|
|
}
|
|
}
|
|
//前一天持仓的交易
|
|
else
|
|
{
|
|
var originalTradeNumber = positionTrade.td.TradeNumber;
|
|
|
|
if (positionTrade.ts.OriginalTradeId != null)
|
|
{
|
|
originalTradeNumber = DbContext.trade.Find(positionTrade.ts.OriginalTradeId).TradeNumber;
|
|
}
|
|
|
|
var flowsThis = flows.Where(y => y.TradeNumber == originalTradeNumber).ToList();
|
|
//开始验证逻辑
|
|
DoCheck(positionTrade.td, flowsThis, result);
|
|
}
|
|
|
|
SaveCheckResult(result);
|
|
|
|
return string.IsNullOrEmpty(result.Detail) ? "检测成功" : result.Detail;
|
|
}
|
|
|
|
public SearchListResult<TradeFlowCheckResultResponse> SearchFlowCheckResultList(TradeFlowCheckResultReq req)
|
|
{
|
|
var query = from result in DbContext.trade_swap_flow_check_result
|
|
join trade in DbContext.trade
|
|
on result.TradeId equals trade.id
|
|
select new TradeFlowCheckResultResponse() { trade = trade, trade_swap_flow_check_result = result };
|
|
|
|
if (!string.IsNullOrEmpty(req.TradeNumber))
|
|
{
|
|
query = query.Where(d => d.trade.TradeNumber == req.TradeNumber);
|
|
}
|
|
|
|
if (req.ClientIds != null && req.ClientIds.Any())
|
|
{
|
|
query = query.Where(d => req.ClientIds.Contains(d.trade.ClientId));
|
|
}
|
|
|
|
if (req.IsSuccessList != null && req.IsSuccessList.Any())
|
|
{
|
|
query = query.Where(d => req.IsSuccessList.Contains(d.trade_swap_flow_check_result.IsSuccess));
|
|
}
|
|
|
|
if (string.IsNullOrEmpty(req.sidx))
|
|
{
|
|
req.sidx = "trade_swap_flow_check_result.id";
|
|
req.sord = "asc";
|
|
}
|
|
|
|
var retListResult = query.ToSearchList(req);
|
|
|
|
return retListResult;
|
|
}
|
|
|
|
private void DoCheck(trade trade, List<trade_swap_flow> flows, trade_swap_flow_check_result result)
|
|
{
|
|
var valueDateConst = valuedateBLL.ValueDate;
|
|
|
|
List<trade> childTrades = new List<trade>();
|
|
var children = (from td in DbContext.trade.Where(x => x.ParentTradeId == trade.id && x.ValidState != "InValid")
|
|
join ts in DbContext.trade_swap
|
|
on td.id equals ts.TradeId
|
|
join un in DbContext.underlying_manager
|
|
on td.UnderlyingId equals un.id
|
|
select new { td.UnderlyingCode, td.Notional, ts.IsGetFloatingProfit, ts.GetLongShort, ts.PayLongShort, un.Price }).ToList();
|
|
if (children.Any())
|
|
{
|
|
children.ForEach(x =>
|
|
{
|
|
var child = new trade()
|
|
{
|
|
UnderlyingCode = x.UnderlyingCode,
|
|
SpotPrice = x.Price,
|
|
Notional = x.Notional,
|
|
BuySell = x.IsGetFloatingProfit ? x.GetLongShort : x.PayLongShort
|
|
};
|
|
childTrades.Add(child);
|
|
});
|
|
}
|
|
|
|
childTrades.ForEach(x =>
|
|
{
|
|
var um = DataCacheProvider.GetUnderlyingDataSource().GetData(x.UnderlyingCode);
|
|
var unFlows = flows.Where(y => y.UnderlyingCode == x.UnderlyingCode);
|
|
var notional = x.Notional * (x.BuySell == "多头" ? 1 : -1) + unFlows.Sum(y => y.Lots * um.ContractSize * (y.BuySell == "买入" || y.BuySell == "B" ? 1 : -1));
|
|
x.Notional = Math.Abs(notional);
|
|
x.BuySell = notional > 0 ? "多头" : "空头";
|
|
});
|
|
|
|
var childTradesUnderlyingCode = childTrades.Select(x => x.UnderlyingCode).ToList();
|
|
var addFlows = flows.Where(x => !childTradesUnderlyingCode.Contains(x.UnderlyingCode));
|
|
if (addFlows.Any())
|
|
{
|
|
var addUnderlyingCodes = addFlows.Select(x => x.UnderlyingCode).Distinct().ToList();
|
|
addUnderlyingCodes.ForEach(x =>
|
|
{
|
|
var um = DataCacheProvider.GetUnderlyingDataSource().GetData(x);
|
|
var unFlows = addFlows.Where(y => y.UnderlyingCode == x);
|
|
var notional = unFlows.Sum(y => y.Lots * um.ContractSize * (y.BuySell == "买入" || y.BuySell == "B" ? 1 : -1));
|
|
|
|
if (notional != 0)
|
|
{
|
|
//var spotPrice = Math.Abs(unFlows.Sum(y => (y.SpotPrice ?? 0) * y.Lots * (y.BuySell == "买入" || y.BuySell == "B" ? 1 : -1)) / unFlows.Sum(y => y.Lots));
|
|
var child = new trade()
|
|
{
|
|
UnderlyingCode = x,
|
|
SpotPrice = um.Price,
|
|
Notional = Math.Abs(notional),
|
|
BuySell = notional > 0 ? "多头" : "空头"
|
|
};
|
|
childTrades.Add(child);
|
|
}
|
|
});
|
|
}
|
|
|
|
//验证多头或空头挂钩标的数量不少于50只
|
|
var countLong = childTrades.Where(x => x.BuySell == "多头").Select(x => x.UnderlyingCode).Distinct().Count();
|
|
var countShort = childTrades.Where(x => x.BuySell == "空头").Select(x => x.UnderlyingCode).Distinct().Count();
|
|
if (countLong < 50 && countShort < 50)
|
|
{
|
|
//验证失败
|
|
result.IsSuccess = false;
|
|
result.Detail += $"多头挂钩标的数量【{countLong}】和空头挂钩标的数量【{countShort}】均少于50只;<br>";
|
|
}
|
|
|
|
var buyStockEqvNotional = childTrades.Sum(x => x.Notional * (x.SpotPrice ?? 0) * (x.BuySell == "多头" ? 1 : 0));
|
|
var sellStockEqvNotional = childTrades.Sum(x => x.Notional * (x.SpotPrice ?? 0) * (x.BuySell == "空头" ? 1 : 0));
|
|
if (buyStockEqvNotional == 0 || sellStockEqvNotional == 0)
|
|
{
|
|
//验证失败
|
|
result.IsSuccess = false;
|
|
result.Detail += $"多头收益互换名义本金与空头收益互换名义本金的比例不低于80%且不高于120%,多头名义本金[{buyStockEqvNotional}],空头名义本金[{sellStockEqvNotional}];<br>";
|
|
}
|
|
else
|
|
{
|
|
//验证 多头收益互换名义本金与空头收益互换名义本金的比例不低于80%且不高于120%
|
|
var rateTotal = buyStockEqvNotional / sellStockEqvNotional;
|
|
if (rateTotal > 1.2 || rateTotal < 0.8)
|
|
{
|
|
//验证失败
|
|
result.IsSuccess = false;
|
|
result.Detail += $"多头收益互换名义本金与空头收益互换名义本金的比例不低于80%且不高于120%,多头名义本金[{buyStockEqvNotional}],空头名义本金[{sellStockEqvNotional}];<br>";
|
|
}
|
|
}
|
|
|
|
//验证单一股票对应的合约名义本金占多方或空方股票对应的合约名义本金的比例不高于5%
|
|
childTrades.ForEach(x =>
|
|
{
|
|
var stockEqvNotional = (x.SpotPrice ?? 0) * x.Notional;
|
|
var totalStockEqvNotional = (x.BuySell == "多头" ? buyStockEqvNotional : sellStockEqvNotional);
|
|
var rate = stockEqvNotional / totalStockEqvNotional;
|
|
if (rate > 0.05)
|
|
{
|
|
//验证失败
|
|
result.IsSuccess = false;
|
|
result.Detail += $"单一股票对应的合约[{x.UnderlyingCode}]名义本金[{stockEqvNotional}]占多方或空方股票对应的合约名义本金[{totalStockEqvNotional}]的比例不高于5%;<br>";
|
|
}
|
|
});
|
|
|
|
var underlyingCodes = childTrades.Select(x => x.UnderlyingCode).ToList();
|
|
var startDate = valueDateConst.AddYears(-1);
|
|
var eodStockPricesDic = DbContext.eod_stock_price.Where(x => x.ValueDate >= startDate && x.ValueDate < valueDateConst && underlyingCodes.Contains(x.UnderlyingCode)).ToDictionary(x => new { x.ValueDate, x.UnderlyingCode }, x => x.ClosePrice);
|
|
var eodCommodityFuturePricesDic = DbContext.eod_commodity_future_price.Where(x => x.ValueDate >= startDate && x.ValueDate < valueDateConst && underlyingCodes.Contains(x.UnderlyingCode)).ToDictionary(x => new { x.ValueDate, x.UnderlyingCode }, x => x.ClosePrice);
|
|
List<TimeXY> list = new List<TimeXY>();
|
|
for (var time = startDate; time < valueDateConst;)
|
|
{
|
|
if (!QdpCalendarHelper.IsHoliday(time))
|
|
{
|
|
List<trade> timeChildTrades = new List<trade>();
|
|
childTrades.ForEach(x =>
|
|
{
|
|
if (eodStockPricesDic.ContainsKey(new { ValueDate = time, x.UnderlyingCode }))
|
|
{
|
|
timeChildTrades.Add(new trade()
|
|
{
|
|
UnderlyingCode = x.UnderlyingCode,
|
|
SpotPrice = eodStockPricesDic[new { ValueDate = time, x.UnderlyingCode }],
|
|
Notional = x.Notional,
|
|
BuySell = x.BuySell
|
|
});
|
|
}
|
|
else if (eodCommodityFuturePricesDic.ContainsKey(new { ValueDate = time, x.UnderlyingCode }))
|
|
{
|
|
timeChildTrades.Add(new trade()
|
|
{
|
|
UnderlyingCode = x.UnderlyingCode,
|
|
SpotPrice = eodCommodityFuturePricesDic[new { ValueDate = time, x.UnderlyingCode }],
|
|
Notional = x.Notional,
|
|
BuySell = x.BuySell
|
|
});
|
|
}
|
|
});
|
|
var buyChildTrades = timeChildTrades.Where(x => x.BuySell == "多头").ToList();
|
|
var sellChildTrades = timeChildTrades.Where(x => x.BuySell == "空头").ToList();
|
|
|
|
var item = new TimeXY()
|
|
{
|
|
Time = time,
|
|
Px = buyChildTrades.Sum(x => (x.SpotPrice ?? 0) * x.Notional) / buyChildTrades.Sum(x => x.Notional),
|
|
Py = sellChildTrades.Sum(x => (x.SpotPrice ?? 0) * x.Notional) / sellChildTrades.Sum(x => x.Notional),
|
|
};
|
|
|
|
list.Add(item);
|
|
}
|
|
|
|
time = time.AddDays(1);
|
|
}
|
|
|
|
list = list.OrderBy(x => x.Time).ToList();
|
|
var n = list.Count;
|
|
var index = 0;
|
|
//价格变化率只有n-1组
|
|
foreach (var item in list)
|
|
{
|
|
index++;
|
|
if (index < n)
|
|
{
|
|
item.x = list[index].Px / item.Px - 1;
|
|
item.y = list[index].Py / item.Py - 1;
|
|
}
|
|
}
|
|
|
|
var averageX = list.Sum(x => x.x) / (n - 1);
|
|
var averageY = list.Sum(x => x.y) / (n - 1);
|
|
var varX = list.Sum(x => Math.Pow(x.x - averageX, 2)) / ((n - 1) - 1);
|
|
var varY = list.Sum(x => Math.Pow(x.y - averageY, 2)) / ((n - 1) - 1);
|
|
var covXY = list.Sum(x => (x.x - averageX) * (x.y - averageY)) / ((n - 1) - 1);
|
|
var relation = covXY / Math.Sqrt(varX * varY);
|
|
if (relation < 0.8)
|
|
{
|
|
//验证失败
|
|
result.IsSuccess = false;
|
|
result.Detail += $"多头与空头收益互换挂钩标的的过去一年相关系数[{relation}]低于80%;<br>";
|
|
}
|
|
}
|
|
|
|
private void SaveCheckResult(trade_swap_flow_check_result result)
|
|
{
|
|
var existList = DbContext.trade_swap_flow_check_result.Where(x => x.TradeId == result.TradeId);
|
|
if (existList.Any())
|
|
{
|
|
DbContext.trade_swap_flow_check_result.RemoveRange(existList);
|
|
}
|
|
|
|
DbContext.trade_swap_flow_check_result.Add(result);
|
|
DbContext.SaveChanges();
|
|
}
|
|
}
|
|
|
|
public class TimeXY
|
|
{
|
|
public DateTime Time { get; set; }
|
|
|
|
public double Px { get; set; }
|
|
|
|
public double x { get; set; }
|
|
|
|
public double Py { get; set; }
|
|
|
|
public double y { get; set; }
|
|
}
|
|
}
|