using Org.BouncyCastle.Asn1.Ocsp;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using YLErp.Model;
namespace YLErp.Helpers
{
///
/// 计算器帮助类
/// 调用链:zszq-trs → zszq-bond-oms(/calc/cal_bond_value) → bond-calc 微服务。
/// 契约(与 bond-oms-ui / zszq-bond-oms 保持一致):
/// 请求体 { bondId, price, priceType(DP全价/CP净价/YD收益率), targetDate?(估值日 yyyy-MM-dd,缺省代理取 T+1) }
/// 成功响应 data:{ errCode:0, errMsg:null, dirtyPrice(全价), cleanPrice(净价), ytm(收益率%) }
/// 失败响应:success=false 且 message=中文原因(债券不存在/信息不全/参数非法/服务异常)
///
public class BondCalcHepler
{
private const string CalcUrl = "/calc/cal_bond_value";
///
/// 计算器(债券净价/全价/收益率互算)。
/// 兼容旧调用方(如 RealtimePnlCalc):返回 CalBondResult,失败返回 null。
/// targetDate 可选:估值日(yyyy-MM-dd)。前端(fe)已在债券互换录入页强制要求 StartDate(开始日)必填、
/// 为空则报错不调用本接口;故经 UI 的计算请求总会带上估值日,此处的"代理默认 T+1"仅兜底非 UI 调用方(如 RealtimePnlCalc)。
///
public static CalBondResult BondCalc(string underlyingCode, decimal price, string priceType = "DP", string targetDate = null)
{
string _err;
return BondCalc(underlyingCode, price, priceType, out _err, targetDate);
}
///
/// 计算器(带错误信息输出)。
/// 任何失败(地址未配/无响应/success=false/业务错误码 errCode!=0/异常)都返回 null,
/// 并通过 errorMsg 带上**真实原因**,供上层(BondController)清晰反馈给用户。
/// 关键:即使 success=true,只要内部 errCode!=0(债券不存在/信息不全/参数非法)也视为失败,
/// 避免把 0/异常价格静默回写覆盖用户手工输入。
/// targetDate 可选:估值日(yyyy-MM-dd),透传给代理的 targetDate(底层 bond-calc 的 settlementDate);
/// 不传则代理默认取 T+1(下一工作日)。
///
public static CalBondResult BondCalc(string underlyingCode, decimal price, string priceType, out string errorMsg, string targetDate = null)
{
errorMsg = null;
var baseUrl = Environment.GetEnvironmentVariable("BondOmsInterface_BaseUrl");
CalcBondRequest request = new CalcBondRequest()
{
bondId = underlyingCode,
price = price.ToString(),
priceType = priceType,
targetDate = targetDate,
};
if (string.IsNullOrEmpty(baseUrl))
{
errorMsg = "计算器服务地址未配置(BondOmsInterface_BaseUrl)";
return null;
}
try
{
LogFactory.GetLogger("BondCalcHepler").Info(
$"债券计算器请求: url={baseUrl}{CalcUrl} bondId={underlyingCode} price={price} priceType={priceType} targetDate={(string.IsNullOrEmpty(targetDate) ? "(空→代理默认T+1)" : targetDate)}");
var httpHelper = new HttpHelper(baseUrl, null);
// http 请求 Web项目接口
var result = httpHelper.PostRequestNoAuth(CalcUrl, request).Result;
if (result == null)
{
errorMsg = "计算器服务无响应";
LogFactory.GetLogger("BondCalcHepler").Error("债券计算器无响应,地址:" + baseUrl + CalcUrl);
return null;
}
if (!result.success)
{
errorMsg = result.message ?? "债券计算失败";
LogFactory.GetLogger("BondCalcHepler").Error("债券计算失败:" + errorMsg);
return null;
}
if (result.data == null)
{
errorMsg = "计算器返回数据为空";
LogFactory.GetLogger("BondCalcHepler").Error("债券计算器返回 data 为空");
return null;
}
// 业务层错误码(债券不存在 / 债券信息不全 / 参数非法),zszq-bond-oms 在 success=true 时仍可能带 errCode!=0
if (result.data.errCode != 0)
{
errorMsg = result.data.errMsg ?? "债券计算业务错误";
LogFactory.GetLogger("BondCalcHepler").Error("债券计算业务错误(code=" + result.data.errCode + "):" + errorMsg);
return null;
}
// 防御性值域校验:errCode=0 但数值离谱(如净价变负 / 收益率量级爆炸 / 行权收益率哨兵 -999999)。
// 这类"成功但不可信"的响应现有 errCode 守卫拦不住,此处兜底:宁可返回 null+提示用户核对,
// 也绝不把垃圾值回写覆盖手工输入。
string absurdReason;
if (IsResultAbsurd(result.data, out absurdReason))
{
errorMsg = absurdReason;
LogFactory.GetLogger("BondCalcHepler").Error(
$"债券计算返回离谱值(bondId={underlyingCode}):{absurdReason} | cleanPrice={result.data.cleanPrice} dirtyPrice={result.data.dirtyPrice} ytm={result.data.ytm}");
return null;
}
LogFactory.GetLogger("BondCalcHepler").Info(
$"债券计算器成功: bondId={underlyingCode} targetDate={targetDate} cleanPrice={result.data.cleanPrice} dirtyPrice={result.data.dirtyPrice} ytm={result.data.ytm}");
return result.data;
}
catch (Exception ex)
{
errorMsg = "请求债券计算器异常:" + ex.Message;
LogFactory.GetLogger("BondCalcHepler").Error("请求债券计算器时发生异常", ex);
return null;
}
}
///
/// 按结算日计算(自动结算路径用)。与 BondCalc 同款健壮处理:
/// 地址未配/无响应/success=false/业务错误码/异常 均返回 null(不回写坏数据)。
///
public static CalBondResult BondCalcByDate(string underlyingCode, decimal price, String targetDate, string priceType = "DP")
{
var baseUrl = Environment.GetEnvironmentVariable("BondOmsInterface_BaseUrl");
CalcBondRequest request = new CalcBondRequest()
{
bondId = underlyingCode,
price = price.ToString(),
priceType = priceType,
targetDate = targetDate,
};
if (!string.IsNullOrEmpty(baseUrl))
{
var httpHelper = new HttpHelper(baseUrl, null);
try
{
// http 请求 Web项目接口
var result = httpHelper.PostRequestNoAuth(CalcUrl, request).Result;
if (result == null || !result.success || result.data == null || result.data.errCode != 0)
{
var msg = result != null ? (result.message ?? (result.data != null ? result.data.errMsg : null)) : "计算器服务无响应";
LogFactory.GetLogger("BondCalcHelper").Error("债券计算失败:" + msg);
return null;
}
// 防御性值域校验(同 BondCalc):拦截"成功但数值离谱"的响应,不回写坏数据
string absurdReason;
if (IsResultAbsurd(result.data, out absurdReason))
{
LogFactory.GetLogger("BondCalcHelper").Error(
$"债券计算返回离谱值(bondId={underlyingCode}):{absurdReason} | cleanPrice={result.data.cleanPrice} dirtyPrice={result.data.dirtyPrice} ytm={result.data.ytm}");
return null;
}
return result.data;
}
catch (Exception ex)
{
LogFactory.GetLogger("BondCalcHelper").Error("请求计算器时发生异常", ex);
}
}
return null;
}
///
/// 防御性值域校验:判断计算器返回值是否"离谱"(errCode=0 但净价变负 / 收益率量级爆炸 / 命中哨兵值)。
/// 命中则上层返回 null、不回写,避免把不可信结果覆盖用户手工输入。规则:
/// - 净价/全价:占面值百分比,正常约 20~300,必须为正且不破千(≤0 或 >1000 视为离谱);
/// - 收益率(ytm,百分数口径):|ytm| > 100 视为爆炸(允许负利率债,但量级须合理);
/// - 哨兵值:任一字段命中 -999999 或 -999999×100(如 yieldToCP 不可用哨兵透传)。
/// 与前端 swapCalc.js::getBondCalcErrorMessage 的值域闸门保持一致口径。
///
private static bool IsResultAbsurd(CalBondResult data, out string reason)
{
reason = null;
if (data == null) return false;
const decimal SENTINEL = -999999m;
bool Absurd(decimal v) => v == SENTINEL || v == SENTINEL * 100m;
if (Absurd(data.cleanPrice) || Absurd(data.dirtyPrice) || (data.ytm.HasValue && Absurd(data.ytm.Value)))
{
reason = "债券计算返回哨兵值(部分指标不可用),请检查估值日/价格输入或联系管理员核对债券计算服务";
return true;
}
if (data.cleanPrice <= 0m || data.cleanPrice > 1000m || data.dirtyPrice <= 0m || data.dirtyPrice > 1000m)
{
reason = "债券计算净价/全价超出合理范围(应为正值且接近面值百分比),请检查估值日/价格输入或联系管理员核对债券计算服务";
return true;
}
if (data.ytm.HasValue && Math.Abs(data.ytm.Value) > 100m)
{
reason = "债券计算收益率量级异常(" + data.ytm.Value + "),请检查估值日/价格输入或联系管理员核对债券计算服务";
return true;
}
return false;
}
}
}