Files
zszq-trs/YLErpDAL/Modules/SwapModule/SwapFlowImportService.cs
T
张名锐 bc961c3411 fix(swap): 修复互换交易中债券价格精度处理问题,扩充到11位计算
- 统一债券价格四舍五入规则,使用AwayFromZero模式
- 为债券类型添加特殊的精度处理逻辑
- 修复前端价格显示精度计算问题
- 优化价格存储精度控制,区分债券和其他产品类型
- 修复初始化YTM和净价精度处理问题
- 更新价格验证逻辑以支持动态精度设置
2026-07-23 11:06:50 +08:00

344 lines
16 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using DotNetDBF;
using ICSharpCode.SharpZipLib.Zip;
using System;
using System.Collections.Generic;
using System.Data;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using System.Web;
using YLErp.Helpers;
using YLErp.DBModels;
using Org.BouncyCastle.Ocsp;
using YLErp.BLL.Eod;
using CsvHelper;
using YLErp.BLL;
namespace YLErp.Modules.SwapModule
{
/// <summary>
/// 互换成交流水服务
/// </summary>
public class SwapFlowImportService : YLBaseService
{
public SwapFlowImportService(OptUserInfo optUser) : base(optUser)
{
}
public SwapFlowImportService(YLBaseService baseService) : base(baseService)
{
}
/// <summary>
/// 成交流水导入 dbf
/// </summary>
/// <param name="filePath">压缩包文件路径</param>
/// <param name="fileDir">压缩包解压路径</param>
/// <param name="totalNum">总条数</param>
/// <param name="successNum">成功条数</param>
[Obsolete]
public void ImportSwapTradesFromZip(string filePath, string fileDir, out int totalNum, out int successNum)
{
totalNum = 0;
successNum = 0;
var fileDirPath = ZipHelper.unZipFile(filePath, fileDir, out var msg);
var dbfFiles = GetDbfFiles(fileDirPath);
var swapFlowList = GetDbfDatas(dbfFiles);
if (swapFlowList.Count > 0)
{
DbContext.swap_flow.AddRange(swapFlowList);
DbContext.SaveChanges();
new SysJobService(UserInfo).UpdateJob("互换流水合成持仓", 11, "互换流水导入完成");
Task.Run(() =>
{
RealtimePnlCalc.RealtimeSwapPosition(new OptUserInfo(0, "互换实时持仓服务", OptUserFrom.Service));
});
}
}
/// <summary>
/// 成交流水导入 excel
/// </summary>
/// <param name="streamIn">上传的文件</param>
/// <param name="totalNum">总条数</param>
/// <param name="successNum">成功条数</param>
public void ImportSwapTradesFromExcel(Stream streamIn, out int totalNum, out int successNum)
{
totalNum = 0;
successNum = 0;
var rowIndex = 0;
try
{
var ds = Office.ExcelHelper.ReadExcelAsDataSet(streamIn, new[] { 0 }, 0);
if (ds.Tables.Count < 1 || ds.Tables[0].Rows.Count < 2)
{
throw new ServiceException("读取导入数据失败:数据为空") { Tag = "111" };
}
var table = ds.Tables[0];
var reader = new DataRowReaderHelper(table);
rowIndex = 1;
totalNum = table.Rows.Count - rowIndex;
Dictionary<long, List<string>> clientUmsDic = new Dictionary<long, List<string>>();
foreach (var row in table.Rows.Cast<DataRow>().Skip(rowIndex))
{
rowIndex++;
if (row.ItemArray.All(n => string.IsNullOrWhiteSpace(n?.ToString())))
{
totalNum--;
continue;
}
reader.SetDataRow(row,rowIndex);
var swap_flow = new swap_flow();
swap_flow.FundAccount = reader.GetString("资金账号");
var uPattern = "^[0 - 9]{6,20}$";
Regex regex = new Regex(uPattern);
if (!string.IsNullOrEmpty(swap_flow.FundAccount)&&regex.IsMatch(swap_flow.FundAccount))
{
throw new ServiceException($"第{rowIndex}行资金账号应为20位以内的数字");
}
swap_flow.UnderlyingCode = reader.GetString("标的代码", true);
var underlying = DataCacheProvider.GetUnderlyingDataSource().GetData(swap_flow.UnderlyingCode);
swap_flow.UnderlyingName = underlying?.UnderlyingName;
var clientName = reader.GetString("交易对手方", true);
swap_flow.OccurTime = reader.GetDate("交易日期", true);
swap_flow.SettleDate = valuedateBLL.GetNonHoliday(swap_flow.OccurTime.Value).Date;
swap_flow.BsType = reader.GetString("买卖方向", true) == "B" ? (int)EnumDirection.Long : (int)EnumDirection.Short;
swap_flow.TradingAmount = reader.GetDecimal("成交金额") ?? 0;
swap_flow.TradingFee = reader.GetDecimal("交易费用") ?? 0;
swap_flow.TradingQty = reader.GetDecimal("成交数量") ?? 0;
if (swap_flow.TradingQty<=0)
{
throw new ServiceException($"第{rowIndex}行成交数量必须大于0");
}
swap_flow.DataState = (int)SwapFlowDateStateEnum.等待完成;
swap_flow.ContractSize= reader.GetDecimal("乘数") ?? 0;
swap_flow.TradingAmountAvg = reader.GetDecimal("成交全价") ?? 0;
swap_flow.TradingAmountFeeAvg = TradeFeeHelper.CalcPriceWithFee(swap_flow.TradingFee, swap_flow.TradingAmountAvg, swap_flow.TradingQty, swap_flow.BsType);
swap_flow.ytm = reader.GetDecimalOrPercent("成交收益率",false,true) ?? 0;
swap_flow.TradingAmountNet = reader.GetDecimal("成交净价") ?? 0;
swap_flow.TradingAmountNetFee = TradeFeeHelper.CalcPriceWithFee(swap_flow.TradingFee, swap_flow.TradingAmountNet??0, swap_flow.TradingQty, swap_flow.BsType);
var isBond = underlying != null && underlying.IsBond();
if (isBond)
{
// 成交流水债券报价(×100形式)转入库小数(×0.01),统一走 BondPriceConverter
swap_flow.TradingAmountAvg = BondPriceConverter.ToStorage(swap_flow.TradingAmountAvg);
swap_flow.TradingAmountFeeAvg = BondPriceConverter.ToStorage(swap_flow.TradingAmountFeeAvg);
// TradingAmountNet/NetFee 可空,null 时保持 null 语义(与原 *= 一致)
if (swap_flow.TradingAmountNet.HasValue)
swap_flow.TradingAmountNet = BondPriceConverter.ToStorage(swap_flow.TradingAmountNet.Value);
if (swap_flow.TradingAmountNetFee.HasValue)
swap_flow.TradingAmountNetFee = BondPriceConverter.ToStorage(swap_flow.TradingAmountNetFee.Value);
}
swap_flow.TradingAmountAvg = Math.Round(
swap_flow.TradingAmountAvg,
isBond ? ConsGlobal.PriceRound : ConsGlobal.SwapDeliveryPriceRound,
MidpointRounding.AwayFromZero);
if (!string.IsNullOrEmpty(clientName))
{
var client = DataCacheProvider.GetClientDataSource().AsQueryable(x=>x.Name== clientName).FirstOrDefault();
swap_flow.ClientId = client?.id;
swap_flow.ClientName = clientName;
}
swap_flow.OptId = UserId;
swap_flow.OptName = UserName;
swap_flow.OptTime = DateTime.Now;
DbContext.swap_flow.Add(swap_flow);
if (!clientUmsDic.ContainsKey(swap_flow.ClientId ?? 0))
{
clientUmsDic.Add(swap_flow.ClientId ?? 0, new List<string> { swap_flow.UnderlyingCode });
}
else
{
if (!clientUmsDic[swap_flow.ClientId ?? 0].Contains(swap_flow.UnderlyingCode))
{
clientUmsDic[swap_flow.ClientId ?? 0].Add(swap_flow.UnderlyingCode);
}
}
successNum++;
}
DbContext.SaveChanges();
Task.Run(() =>
{
RealtimePnlCalc.RealtimeSwapPosition(new OptUserInfo(0, "互换实时持仓服务", OptUserFrom.Service));
});
new RiskCacheService().refreshRiskCache(clientUmsDic);
}
catch (Exception ex)
{
LogFactory.GetLogger("导入互换成交流水").Error(ex);
throw new ServiceException($"第{rowIndex}行,发生错误:{ex.Message}", ex);
}
}
/// <summary>
/// 获取解压路径下所有符合的dbf文件
/// </summary>
/// <param name="filePath"></param>
/// <returns></returns>
private List<string> GetDbfFiles(string filePath)
{
List<string> files = new List<string>();
var allFiles = Directory.GetFiles(filePath, ".", SearchOption.AllDirectories);
foreach (var file in allFiles)
{
FileInfo _file = new FileInfo(file);
var fileName = _file.Name.ToLower();
//SZ:SJSMX20518.DBF SH:jsmx03_jsx73.518
if ((fileName.StartsWith("sjsmx") && fileName.Contains(".dbf")) || fileName.StartsWith("jsmx"))
{
files.Add(file);
}
}
return files;
}
/// <summary>
/// dbf文件中获取流水数据
/// </summary>
/// <param name="files"></param>
/// <returns></returns>
private List<swap_flow> GetDbfDatas(List<string> files)
{
List<swap_flow> swap_Flows = new List<swap_flow>();
for (var i = 0; i < files.Count; i++)
{
using (var dbf = new DBFReader(files[i]))
{
var columnCount = dbf.Fields.Length;
if (columnCount != 48)
{
continue;
}
bool dbffile = dbf.Fields[0].Name == "MXJSZH";
var swapflows = new List<swap_flow>();
if (dbffile)//SZ
{
swapflows = GetSwapFlowData_SZ(dbf);
}
else //SH
{
swapflows = GetSwapFlowData_SH(dbf);
}
swap_Flows.AddRange(swapflows);
}
}
return swap_Flows;
}
/// <summary>
/// 获取SZ流水数据
/// </summary>
/// <param name="dbf"></param>
/// <returns></returns>
private List<swap_flow> GetSwapFlowData_SZ(DBFReader dbf)
{
List<swap_flow> swap_Flows = new List<swap_flow>();
var count = dbf.RecordCount;
for (var j = 0; j < count; j++)
{
var dbfRecord = dbf.NextRecord();
if (dbfRecord != null)
{
string MXZQLB = dbfRecord[18].ToString().Trim();//证券类别
if (MXZQLB != "00")//A股
{
continue;
}
decimal.TryParse(dbfRecord[12].ToString().Trim(), out decimal tradingQty);//成交数量(MXCJSL)
decimal.TryParse(dbfRecord[23].ToString().Trim(), out decimal tradingAmount);//清算本金(MXQSBJ)
decimal.TryParse(dbfRecord[33].ToString().Trim(), out decimal tradingFee);//收付净额(MXSXF)
var underlyingCode = dbfRecord[4].ToString().Trim() + ".SZ";// 证券代码(MXSFJE)
var date = dbfRecord[34].ToString().Trim();//成交日期MXCJRQ
var bstype = dbfRecord[17].ToString().Trim();//平仓标识(MXPCBS)
var bsType = 0;
if (bstype == "2" || bstype == "3")//2平仓 3强制平仓
{
bsType = 2;
}
else if (bstype == "1")//开仓
{
bsType = 1;
}
var tradeDate = ConvertDateTime(date);
swap_Flows.Add(new swap_flow()
{
FundAccount = dbfRecord[7].ToString().Trim(),//证券账户号码(MXZQZH)
OccurTime = tradeDate,
UnderlyingCode = underlyingCode,
BsType = bsType,
TradingQty = tradingQty,
TradingAmount = tradingAmount,
TradingFee = tradingFee - tradingAmount,//收付净额-清算本金
DataState = 1
});
}
}
return swap_Flows;
}
/// <summary>
/// 获取SH流水数据
/// </summary>
/// <param name="fields"></param>
/// <returns></returns>
private List<swap_flow> GetSwapFlowData_SH(DBFReader dbf)
{
List<swap_flow> swap_Flows = new List<swap_flow>();
var count = dbf.RecordCount;
for (var j = 0; j < count; j++)
{
var dbfRecord = dbf.NextRecord();
if (dbfRecord != null)
{
string SCDM = dbfRecord[0].ToString().Trim();//市场代码
if (SCDM != "01")//A股
{
continue;
}
var bstype = dbfRecord[29].ToString().Trim();//买卖标识(MMBZ)
decimal.TryParse(dbfRecord[31].ToString().Trim(), out decimal tradingQty);//成交数量(CJSL)
decimal.TryParse(dbfRecord[36].ToString().Trim(), out decimal tradingAmount);//清算金额(QSJE)
decimal.TryParse(dbfRecord[45].ToString().Trim(), out decimal tradingPay);//实际收付(SJSF)
var underlyingCode = dbfRecord[23].ToString().Trim() + ".SH";// 证券代码1(ZQDM1)
var date = dbfRecord[11].ToString().Trim();//交易日期(JYRQ)
var tradeDate = ConvertDateTime(date);
var bsType = 0;
if (bstype == "B")
{
bsType = 1;
}
else if (bstype == "S")
{
bsType = 2;
}
swap_Flows.Add(new swap_flow()
{
FundAccount = dbfRecord[32].ToString().Trim(),//资金账号(ZJZH)
OccurTime = tradeDate,
UnderlyingCode = underlyingCode,
BsType = bsType,
TradingQty = tradingQty,
TradingAmount = tradingAmount,
TradingFee = tradingPay - tradingAmount,
DataState = 1
});
}
}
return swap_Flows;
}
/// <summary>
/// 6位字符串日期转换
/// </summary>
/// <param name="date"></param>
/// <returns></returns>
private DateTime? ConvertDateTime(string date)
{
if (date.Length != 8)
{
return null;
}
DateTime.TryParse(date.Substring(0, 4) + "-" + date.Substring(4, 2) + "-" + date.Substring(6), out DateTime tradeDate);
return tradeDate;
}
}
}