436 lines
16 KiB
C#
436 lines
16 KiB
C#
using System.Linq.Expressions;
|
|
using YLErp.DBModels.Enums;
|
|
using YLErp.DBModels.Helpers;
|
|
using YLErp.Modules.DataProviderModule;
|
|
|
|
namespace YLErp.Modules.ExchangeTradeModule
|
|
{
|
|
/// <summary>
|
|
/// 场内交易保存基类
|
|
/// </summary>
|
|
public class ExchangeTradeSaveServiceBase : YLBaseService
|
|
{
|
|
public ExchangeTradeSaveServiceBase(YLBaseService baseService) : base(baseService)
|
|
{
|
|
|
|
}
|
|
|
|
public ExchangeTradeSaveServiceBase(OptUserInfo userInfo) : base(userInfo)
|
|
{
|
|
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// 保存对冲交易持仓(dbTradOld为null说明reqTrad是新增交易)
|
|
/// </summary>
|
|
public void SaveExchangeTradePosition(ExchangeTrade reqTrad, ExchangeTrade dbTradOld = null)
|
|
{
|
|
TradePosition dbPosition;
|
|
TradePositionParam para;
|
|
|
|
//比较请求的数据和数据库中的旧数据的基本要素是否相同
|
|
var isSame = dbTradOld == null || reqTrad.AssetBookId == dbTradOld.AssetBookId
|
|
&& reqTrad.TradeSide == dbTradOld.TradeSide && reqTrad.TradeType == dbTradOld.TradeType
|
|
&& string.Equals(reqTrad.UnderlyingCode, dbTradOld.UnderlyingCode, StringComparison.OrdinalIgnoreCase)
|
|
&& (reqTrad.TradeType != "场内期权" || string.Equals(reqTrad.OptionCode, dbTradOld.OptionCode, StringComparison.OrdinalIgnoreCase));
|
|
|
|
//如果不相同就从旧持仓份额中减去
|
|
if (!isSame)
|
|
{
|
|
//准备旧数据参数查询持仓
|
|
para = PrepareTradePositionParam(dbTradOld);
|
|
dbPosition = DbContext.TradePosition.FirstOrDefault(para.predicate);
|
|
if (dbPosition != null)
|
|
{
|
|
var dbNotional = TradeHelper.GetPositionNotional(para.tradeSide, dbTradOld.Notional);
|
|
dbPosition.Position -= dbNotional;
|
|
dbPosition.PositionCost -= dbNotional * dbTradOld.TradeSinglePrice;
|
|
dbPosition.UpdateTime = OptDate;
|
|
}
|
|
}
|
|
|
|
//准备新的参数进行查询持仓
|
|
para = PrepareTradePositionParam(reqTrad);
|
|
|
|
var reqNotional = TradeHelper.GetPositionNotional(para.tradeSide, reqTrad.Notional);
|
|
|
|
dbPosition = DbContext.TradePosition.FirstOrDefault(para.predicate);
|
|
|
|
//如果数据库中不存在对应的持仓就使用请求数据创建持仓数据
|
|
if (dbPosition == null)
|
|
{
|
|
DateTime maturityDate;
|
|
|
|
if (reqTrad.TradeType == "股票")
|
|
{
|
|
maturityDate = DateTime.Today.AddYears(10);
|
|
}
|
|
else if (reqTrad.TradeType == "场内期权")
|
|
{
|
|
if (!ExchangeOptionDataProvider.TryGetMaturityDate(reqTrad.OptionCode, out maturityDate) &&
|
|
!UnderlyingDataProvider.TryGetMaturityDate(reqTrad.UnderlyingCode, out maturityDate))
|
|
{
|
|
maturityDate = DateTime.Today.AddMonths(1);
|
|
}
|
|
}
|
|
else if (!UnderlyingDataProvider.TryGetMaturityDate(reqTrad.UnderlyingCode, out maturityDate))
|
|
{
|
|
maturityDate = DateTime.Today.AddMonths(1);
|
|
}
|
|
|
|
dbPosition = new TradePosition
|
|
{
|
|
TradeType = reqTrad.TradeType,
|
|
BookId = reqTrad.AssetBookId,
|
|
InstrumentCode = reqTrad.OptionCode,
|
|
PositionType = para.positionType,
|
|
Position = reqNotional,
|
|
TradeType1 = para.tradeType1,
|
|
UnderlyingId = reqTrad.UnderlyingId,
|
|
UnderlyingCode = reqTrad.UnderlyingCode,
|
|
CreateTime = OptDate,
|
|
MaturityDate = maturityDate
|
|
};
|
|
dbPosition.PositionCost = dbPosition.Position * reqTrad.TradeSinglePrice;
|
|
DbContext.TradePosition.Add(dbPosition);
|
|
}
|
|
//如果数据库中不存在旧交易数据在现有持仓数据上增加
|
|
else if (dbTradOld == null)
|
|
{
|
|
dbPosition.Position += reqNotional;
|
|
dbPosition.PositionCost += reqNotional * reqTrad.TradeSinglePrice;
|
|
}
|
|
else
|
|
{
|
|
//如果基本要素相同就删除旧的持仓份额改用新的持仓份额
|
|
|
|
if (isSame)
|
|
{
|
|
var dbNotional = TradeHelper.GetPositionNotional(dbTradOld.TradeSide, dbTradOld.Notional);
|
|
dbPosition.Position -= dbNotional;
|
|
dbPosition.PositionCost -= dbNotional * dbTradOld.TradeSinglePrice;
|
|
}
|
|
|
|
dbPosition.Position += reqNotional;
|
|
dbPosition.PositionCost += reqNotional * reqTrad.TradeSinglePrice;
|
|
}
|
|
|
|
if (dbPosition != null)
|
|
{
|
|
dbPosition.UpdateTime = OptDate;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 根据trde对象生成的持仓参数
|
|
/// </summary>
|
|
protected static TradePositionParam PrepareTradePositionParam(ExchangeTrade td)
|
|
{
|
|
var para = new TradePositionParam
|
|
{
|
|
tradeSide = TradeHelper.GetTradeSide(td.TradeSide),
|
|
tradeType1 = TradeHelper.GetTradeType1(td.TradeType)
|
|
};
|
|
|
|
para.positionType = TradeHelper.GetPositionType(para.tradeSide);
|
|
|
|
var predicate = PredicateBuilder.Create<TradePosition>(n => n.BookId == td.AssetBookId
|
|
&& n.TradeType1 == para.tradeType1 && n.UnderlyingCode == td.UnderlyingCode);
|
|
|
|
if (td.TradeType == "场内期权")
|
|
{
|
|
predicate = predicate.And(n => n.InstrumentCode == td.OptionCode);
|
|
}
|
|
|
|
para.predicate = predicate;
|
|
|
|
return para;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 检查交易的交易类型
|
|
/// </summary>
|
|
protected string CheckTradeType(string tradeType)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(tradeType))
|
|
{
|
|
throw new ServiceException("交易类型 必须填写");
|
|
}
|
|
|
|
switch (tradeType)
|
|
{
|
|
case "股票":
|
|
case "商品现货":
|
|
case "场内期权":
|
|
case "商品期货":
|
|
case "信用债":
|
|
case "利率债":
|
|
case "其它债券":
|
|
return tradeType;
|
|
|
|
case "期货": return "商品期货";
|
|
|
|
default: throw new ServiceException("交易类型未能识别:" + tradeType);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 检查交易的交易方向
|
|
/// </summary>
|
|
protected void CheckTradeSide(string tradeSide)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(tradeSide))
|
|
{
|
|
throw new ServiceException("交易方向 必须填写");
|
|
}
|
|
|
|
switch (tradeSide)
|
|
{
|
|
case ConsGlobal.TradeSide.多头开仓:
|
|
case ConsGlobal.TradeSide.多头平仓:
|
|
case ConsGlobal.TradeSide.空头开仓:
|
|
case ConsGlobal.TradeSide.空头平仓: break;
|
|
|
|
default: throw new ServiceException("交易方向未能识别:" + tradeSide);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 设置交易的场内期权信息
|
|
/// </summary>
|
|
protected void SetTradeOptionInfo(ExchangeTrade td, string optionCode, out ExchangeListOption option, bool useCachedDataSource = true)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(optionCode))
|
|
{
|
|
throw new ServiceException("场内期权交易必须有对应的期权合约存在");
|
|
}
|
|
|
|
if (useCachedDataSource)
|
|
{
|
|
option = DataCacheProvider.GetExchangeListOptionDataSource().GetData(optionCode);
|
|
}
|
|
else
|
|
{
|
|
option = DbContext.exchange_list_option.AsNoTracking().FirstOrDefault(e => e.ContractCode == optionCode);
|
|
}
|
|
|
|
if (option == null)
|
|
{
|
|
throw new ServiceException("场内期权合约信息不存在:" + optionCode);
|
|
}
|
|
|
|
if (option.MaturityDate == DateTime.MinValue)
|
|
{
|
|
throw new ServiceException("场内期权合约到期日不存在:" + optionCode);
|
|
}
|
|
|
|
if (option.MaturityDate < td.TradeDate)
|
|
{
|
|
throw new ServiceException($"交易日期'{td.TradeDate:yyyy-MM-dd}'不能大于场内期权合约到期日'{option.MaturityDate:yyyy-MM-dd}'");
|
|
}
|
|
|
|
td.OptionCode = option.ContractCode;
|
|
td.UnderlyingCode = option.UnderlyingCode;
|
|
td.OptionStrike = option.Strike;
|
|
td.OptionType = option.OptionType;
|
|
td.MaturityDate = option.MaturityDate;
|
|
td.ExerciseMode = option.ExerciseMode.TrimToNull() ?? "European";
|
|
|
|
if (optionCode.StartsWith("IO", StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
td.UnderlyingCode = "000300.SH";
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 设置交易的标的信息
|
|
/// </summary>
|
|
protected void SetTradeUnderlyingInfo(ExchangeTrade td, string underlyingCode, out underlying_manager um, bool useCachedDataSource = true)
|
|
{
|
|
System.Diagnostics.Debug.Assert(!string.IsNullOrEmpty(td.TradeType));
|
|
|
|
if (string.IsNullOrWhiteSpace(underlyingCode))
|
|
{
|
|
throw new ServiceException("标的代码 必须填写");
|
|
}
|
|
|
|
if (useCachedDataSource)
|
|
{
|
|
um = DataCacheProvider.GetUnderlyingDataSource().GetData(underlyingCode);
|
|
}
|
|
else
|
|
{
|
|
um = DbContext.underlying_manager.AsNoTracking().FirstOrDefault(un => un.UnderlyingCode == underlyingCode);
|
|
}
|
|
|
|
if (um == null)
|
|
{
|
|
throw new ServiceException("标的信息不存在:" + underlyingCode);
|
|
}
|
|
|
|
if (td.TradeType != "场内期权")
|
|
{
|
|
if (um.IsFutures())
|
|
{
|
|
if (um.MaturityDate == null)
|
|
{
|
|
throw new ServiceException("商品期货合约到期日不存在:" + underlyingCode);
|
|
}
|
|
|
|
if (um.MaturityDate.Value < td.TradeDate)
|
|
{
|
|
throw new ServiceException($"交易日期'{td.TradeDate:yyyy-MM-dd}'不能大于商品期货标的到期日'{um.MaturityDate:yyyy-MM-dd}'");
|
|
}
|
|
}
|
|
|
|
td.MaturityDate = um.MaturityDate;
|
|
}
|
|
|
|
td.UnderlyingId = um.id;
|
|
td.UnderlyingCode = um.UnderlyingCode;
|
|
td.InstrumentType = um.UnderlyingInstrumentType;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 设置交易的数量信息
|
|
/// </summary>
|
|
protected void SetTradeLotsInfo(ExchangeTrade td, double lots, double contractSize, int varietyId, bool useCachedDataSource = true)
|
|
{
|
|
System.Diagnostics.Debug.Assert(!string.IsNullOrEmpty(td.TradeType));
|
|
|
|
td.TradeLots = lots;
|
|
|
|
if (td.TradeType == "股票")
|
|
{
|
|
td.Notional = td.TradeAmount = lots * (contractSize < 1 ? 100 : contractSize);
|
|
}
|
|
else
|
|
{
|
|
Variety variety = null;
|
|
|
|
if (useCachedDataSource)
|
|
{
|
|
variety = DataCacheProvider.GetVarietyDataSource().GetData(varietyId);
|
|
}
|
|
else
|
|
{
|
|
variety = DbContext.variety.AsNoTracking().FirstOrDefault(n => n.id == varietyId);
|
|
}
|
|
|
|
if (variety == null)
|
|
{
|
|
throw new ServiceException("标的的品种信息不存在:" + td.UnderlyingCode);
|
|
}
|
|
|
|
if (contractSize < 1)
|
|
{
|
|
contractSize = VarietyHelper.GetTradeUnitValue(variety.VarietyCode, variety.TradeUnit) ?? 1;
|
|
}
|
|
|
|
var countRatio = VarietyHelper.GetCountRatio(variety.QuoteUnit);
|
|
|
|
td.Notional = lots * contractSize;
|
|
td.TradeAmount = td.Notional / countRatio;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 设置交易员信息
|
|
/// </summary>
|
|
protected void SetTraderInfo(ExchangeTrade td, string traderName)
|
|
{
|
|
td.TraderId = UserId;
|
|
td.TraderName = UserName;
|
|
|
|
if (!string.IsNullOrWhiteSpace(traderName))
|
|
{
|
|
using (var sysdb = DbContextFactory.GetErpBaseContext())
|
|
{
|
|
var systemUsers = sysdb.SystemUsers.Where(su => su.LoginName == traderName || su.Name == traderName)
|
|
.Select(n => new { n.Id, n.Name }).ToArray();
|
|
|
|
if (!systemUsers.Any())
|
|
{
|
|
throw new ServiceException("交易员不存在:" + traderName);
|
|
}
|
|
else
|
|
{
|
|
var systemUser = sysdb.SystemUsers.FirstOrDefault(su => su.LoginName == traderName)
|
|
?? sysdb.SystemUsers.FirstOrDefault(su => su.Name == traderName);
|
|
|
|
td.TraderId = systemUser.Id;
|
|
td.TraderName = systemUser.Name;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 设置簿记账户和对冲账户
|
|
/// </summary>
|
|
protected void SetAssetAndExchangeAccount(ExchangeTrade td, string assetBookName, string exchangeAccountCode, int varietyId)
|
|
{
|
|
//簿记账户
|
|
if (!string.IsNullOrWhiteSpace(assetBookName))
|
|
{
|
|
var book = DataCacheProvider.GetAssetUnitDataSource().AsQueryable().FirstOrDefault(n => n.Name == assetBookName);
|
|
|
|
if (book == null)
|
|
{
|
|
throw new ServiceException($"簿记账户'{assetBookName}'没有找到");
|
|
}
|
|
|
|
td.AssetBookId = book.id;
|
|
}
|
|
|
|
//对冲账户
|
|
if (!string.IsNullOrWhiteSpace(exchangeAccountCode))
|
|
{
|
|
var exAccounts = DataCacheProvider.GetExchangeAccountDataSource().AsQueryable()
|
|
.Where(a => a.AccountCode == exchangeAccountCode && a.Status == 1).ToArray();
|
|
|
|
if (!exAccounts.Any())
|
|
{
|
|
throw new ServiceException($"对冲账户'{exchangeAccountCode}'在系统中不存在");
|
|
}
|
|
|
|
var exAccount = exAccounts[0];
|
|
|
|
if (varietyId > 0)
|
|
{
|
|
var commaVarietyId = $",{varietyId},";
|
|
exAccount = exAccounts.FirstOrDefault(n => string.IsNullOrWhiteSpace(n.VarietyIds)
|
|
|| string.Concat(",", n.VarietyIds, ",").Contains(commaVarietyId)) ?? exAccounts[0];
|
|
}
|
|
|
|
//对冲账户
|
|
td.ExchangeAccountId = exAccount.id;
|
|
|
|
//使用对冲账户绑定的簿记账户
|
|
if (string.IsNullOrWhiteSpace(assetBookName))
|
|
{
|
|
if ((exAccount.DefaultBookId ?? 0) < 1 ||
|
|
DataCacheProvider.GetAssetUnitDataSource().GetData(exAccount.DefaultBookId.Value) == null)
|
|
{
|
|
throw new ServiceException($"对冲账户'{exchangeAccountCode}'没有设置簿记账户");
|
|
}
|
|
|
|
td.AssetBookId = exAccount.DefaultBookId.Value;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 根据trde对象生成的持仓参数
|
|
/// </summary>
|
|
protected class TradePositionParam
|
|
{
|
|
public TradeSideFlag tradeSide;
|
|
public TradeTypeFlag tradeType1;
|
|
public PositionTypeFlag positionType;
|
|
public Expression<Func<TradePosition, bool>> predicate;
|
|
}
|
|
}
|
|
}
|