diff --git a/Framework/YLErp.Core/DBModels/PushStatus.cs b/Framework/YLErp.Core/DBModels/PushStatus.cs
new file mode 100644
index 00000000..547fd6cf
--- /dev/null
+++ b/Framework/YLErp.Core/DBModels/PushStatus.cs
@@ -0,0 +1,50 @@
+using System.ComponentModel.DataAnnotations;
+using System.ComponentModel.DataAnnotations.Schema;
+
+namespace YLErp.DBModels
+{
+ public enum PushStateEnum
+ {
+ 待推送 = 0,
+ 成功 = 1,
+ 失败 = 2
+ }
+
+ ///
+ /// 外发推送失败状态。只记录定位信息,不保存报文。
+ ///
+ [Table("push_status")]
+ public class PushStatus
+ {
+ [Key]
+ [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
+ public long id { get; set; }
+
+ [Column("value_date")]
+ public DateTime ValueDate { get; set; }
+
+ [Column("push_type")]
+ public int PushType { get; set; }
+
+ [Column("record_id")]
+ public long RecordId { get; set; }
+
+ [Column("state")]
+ public PushStateEnum State { get; set; }
+
+ [Column("retry_count")]
+ public int RetryCount { get; set; }
+
+ [Column("last_error")]
+ public string LastError { get; set; }
+
+ [Column("push_time")]
+ public DateTime? PushTime { get; set; }
+
+ [Column("create_time")]
+ public DateTime CreateTime { get; set; }
+
+ [Column("update_time")]
+ public DateTime? UpdateTime { get; set; }
+ }
+}
diff --git a/UnitTestProject/Modules/EodModule/TrsContractKafkaPushServiceTest.cs b/UnitTestProject/Modules/EodModule/TrsContractKafkaPushServiceTest.cs
new file mode 100644
index 00000000..88ffb133
--- /dev/null
+++ b/UnitTestProject/Modules/EodModule/TrsContractKafkaPushServiceTest.cs
@@ -0,0 +1,210 @@
+using YLErp.Abstract;
+using YLErp.Helpers;
+using YLErp.Modules.EodModule;
+
+namespace YLErp.Modules.EodModuleTests
+{
+ [TestClass]
+ public class TrsContractKafkaPushServiceTest
+ {
+ [TestMethod]
+ public void Push_空日快照_发送一条空消息并使用业务日期作为Key()
+ {
+ var valueDate = new DateTime(2026, 8, 24);
+ var producer = new RecordingKafkaProducer();
+ var service = CreateService(producer, valueDate);
+
+ service.Push(valueDate);
+
+ Assert.AreEqual(1, producer.Messages.Count);
+ Assert.AreEqual("onederiv.trs.contract.v1", producer.Messages[0].Topic);
+ Assert.AreEqual("2026-08-24", producer.Messages[0].Key);
+ var payload = JsonHelper.Deserialize(producer.Messages[0].Message);
+ Assert.AreEqual("2026-08-24", payload.ValueDate);
+ Assert.AreEqual(0, payload.ContractCount);
+ Assert.AreEqual(0, payload.Contracts.Count);
+ }
+
+ [TestMethod]
+ public void Push_区间内每天分别调用_每个日期各发送一条快照()
+ {
+ var valueDates = new[]
+ {
+ new DateTime(2026, 8, 20),
+ new DateTime(2026, 8, 21),
+ new DateTime(2026, 8, 24)
+ };
+ var producer = new RecordingKafkaProducer();
+ var service = new TestableTrsContractKafkaPushService(producer, valueDates.ToDictionary(x => x, CreateEmptySnapshot));
+
+ foreach (var valueDate in valueDates)
+ {
+ service.Push(valueDate);
+ }
+
+ CollectionAssert.AreEqual(
+ new[] { "2026-08-20", "2026-08-21", "2026-08-24" },
+ producer.Messages.Select(x => x.Key).ToArray());
+ CollectionAssert.AreEqual(
+ new[] { "2026-08-20", "2026-08-21", "2026-08-24" },
+ producer.Messages.Select(x => JsonHelper.Deserialize(x.Message).ValueDate).ToArray());
+ }
+
+ [TestMethod]
+ public void Push_首次失败后成功_停止重试且不记录最终失败()
+ {
+ var valueDate = new DateTime(2026, 8, 24);
+ var producer = new RecordingKafkaProducer { FailuresBeforeSuccess = 1 };
+ var service = CreateService(producer, valueDate);
+
+ service.Push(valueDate);
+
+ Assert.AreEqual(2, producer.AttemptCount);
+ Assert.AreEqual(1, producer.Messages.Count);
+ Assert.AreEqual(0, service.FailureRecords.Count);
+ }
+
+ [TestMethod]
+ public void Push_连续失败三次_记录最终失败和三次尝试()
+ {
+ var valueDate = new DateTime(2026, 8, 24);
+ var producer = new RecordingKafkaProducer { FailuresBeforeSuccess = int.MaxValue };
+ var service = CreateService(producer, valueDate);
+
+ service.Push(valueDate);
+
+ Assert.AreEqual(3, producer.AttemptCount);
+ Assert.AreEqual(0, producer.Messages.Count);
+ Assert.AreEqual(1, service.FailureRecords.Count);
+ Assert.AreEqual(valueDate, service.FailureRecords[0].ValueDate);
+ Assert.AreEqual(3, service.FailureRecords[0].RetryCount);
+ Assert.IsInstanceOfType(service.FailureRecords[0].Exception, typeof(InvalidOperationException));
+ }
+
+ [TestMethod]
+ public void BuildContract_字段使用日终快照和约定来源()
+ {
+ var valueDate = new DateTime(2026, 8, 24);
+ var eodSwap = new eod_swap
+ {
+ id = 10,
+ ValueDate = valueDate,
+ SwapTradeId = 7,
+ SwapTradeNo = "TRS-001",
+ BookId = 3,
+ ClientId = 8,
+ NotionalValue = 1000000m,
+ dv01 = 12.34m,
+ InitMarginGain = 100m,
+ InitMarginLoss = 0m
+ };
+ var trade = new trade
+ {
+ id = 7,
+ UnderlyingCode = "600000.SH",
+ UnderlyingAssetName = "浦发银行",
+ UnderlyingInstrumentType = "Stock",
+ StartDate = new DateTime(2026, 8, 1),
+ ExerciseDate = new DateTime(2027, 8, 1)
+ };
+ var positions = new List
+ {
+ new() { SwapTradeId = 7, PositionId = 101, UnderlyingCode = "600000.SH", PositionType = 1 },
+ new() { SwapTradeId = 7, PositionId = 102, InterestMode = (int)InterestModeEnum.固定值, InterestRateDefault = 0.0123m, InterestDirection = 2 }
+ };
+ var swapPositions = new Dictionary
+ {
+ [101] = new() { id = 101, category_tag = "互换利率" },
+ [102] = new() { id = 102, category_tag = "互换利率" }
+ };
+
+ var item = TrsContractKafkaPushService.BuildContract(
+ eodSwap,
+ new Dictionary { [7] = trade },
+ positions,
+ swapPositions);
+
+ Assert.AreEqual("2026-08-24", item.TradeDate);
+ Assert.AreEqual(3, item.BookId);
+ Assert.AreEqual("TRS-001", item.SwapTradeNo);
+ Assert.AreEqual(8, item.ClientId);
+ Assert.AreEqual("600000.SH", item.UnderlyingCode);
+ Assert.AreEqual("浦发银行", item.UnderlyingName);
+ Assert.AreEqual("Stock", item.UnderlyingInstrumentType);
+ Assert.AreEqual(1000000m, item.NotionalValue);
+ Assert.AreEqual("2026-08-01", item.StartDate);
+ Assert.AreEqual("2027-08-01", item.MaturityDate);
+ Assert.AreEqual(12.34m, item.Dv01);
+ Assert.AreEqual(0.0123m, item.FixedRate);
+ Assert.AreEqual(2, item.InterestDirection);
+ Assert.AreEqual(1, item.FloatingDirection);
+ Assert.AreEqual(100m, item.InitMarginGain);
+ Assert.AreEqual(0m, item.InitMarginLoss);
+ }
+
+ private static TestableTrsContractKafkaPushService CreateService(RecordingKafkaProducer producer, DateTime valueDate)
+ {
+ return new TestableTrsContractKafkaPushService(
+ producer,
+ new Dictionary { [valueDate] = CreateEmptySnapshot(valueDate) });
+ }
+
+ private static TrsContractSnapshot CreateEmptySnapshot(DateTime valueDate)
+ {
+ return new TrsContractSnapshot
+ {
+ SchemaVersion = "v1",
+ ValueDate = valueDate.ToString("yyyy-MM-dd"),
+ PushTime = "2026-08-24 12:00:00",
+ ContractCount = 0,
+ Contracts = new List()
+ };
+ }
+
+ private sealed class TestableTrsContractKafkaPushService : TrsContractKafkaPushService
+ {
+ private readonly IReadOnlyDictionary _snapshots;
+
+ public List<(DateTime ValueDate, int RetryCount, Exception Exception)> FailureRecords { get; } = new();
+
+ public TestableTrsContractKafkaPushService(IKafkaProduce producer, IReadOnlyDictionary snapshots)
+ : base(new YLContext(), producer, "onederiv.trs.contract.v1")
+ {
+ _snapshots = snapshots;
+ }
+
+ protected override TrsContractSnapshot BuildSnapshot(DateTime valueDate)
+ {
+ return _snapshots[valueDate];
+ }
+
+ protected override void RecordFailures(DateTime valueDate, int retryCount, Exception exception)
+ {
+ FailureRecords.Add((valueDate, retryCount, exception));
+ }
+ }
+
+ private sealed class RecordingKafkaProducer : IKafkaProduce
+ {
+ public int FailuresBeforeSuccess { get; set; }
+ public int AttemptCount { get; private set; }
+ public List<(string Topic, string Key, string Message)> Messages { get; } = new();
+
+ public void Produce(string topic, string message)
+ {
+ throw new NotSupportedException();
+ }
+
+ public void Produce(string topic, string key, string message)
+ {
+ AttemptCount++;
+ if (AttemptCount <= FailuresBeforeSuccess)
+ {
+ throw new InvalidOperationException("Kafka unavailable");
+ }
+
+ Messages.Add((topic, key, message));
+ }
+ }
+ }
+}
diff --git a/YLErpDAL/Abstract/IKafkaProduce.cs b/YLErpDAL/Abstract/IKafkaProduce.cs
index d95e6ddc..a0a78943 100644
--- a/YLErpDAL/Abstract/IKafkaProduce.cs
+++ b/YLErpDAL/Abstract/IKafkaProduce.cs
@@ -9,5 +9,7 @@ namespace YLErp.Abstract
public interface IKafkaProduce
{
void Produce(string topic, string message);
+
+ void Produce(string topic, string key, string message);
}
}
diff --git a/YLErpDAL/DataBase/YLContext.cs b/YLErpDAL/DataBase/YLContext.cs
index dbffc149..1c828a21 100644
--- a/YLErpDAL/DataBase/YLContext.cs
+++ b/YLErpDAL/DataBase/YLContext.cs
@@ -354,6 +354,7 @@ namespace YLErp.BLL
public DbSet eod_swap_position { get; set; }
public DbSet swap_event { get; set; }
public DbSet eod_swap { get; set; }
+ public DbSet push_status { get; set; }
public DbSet trade_obervation { get; set; }
public DbSet SystemLogs { get; set; }
diff --git a/YLErpDAL/Helpers/KafkaProduceHelper.cs b/YLErpDAL/Helpers/KafkaProduceHelper.cs
index b01955fd..4c5789a3 100644
--- a/YLErpDAL/Helpers/KafkaProduceHelper.cs
+++ b/YLErpDAL/Helpers/KafkaProduceHelper.cs
@@ -63,19 +63,29 @@ namespace YLErp.Helpers
public void Produce(string topic,string message)
{
- var kafkaMessage = new Message
- {
- Key=null,
- Value = message
- };
try
{
- _producer.ProduceAsync(topic, kafkaMessage).GetAwaiter().GetResult();
+ ProduceCore(topic, null, message);
}
catch (Exception ex)
{
_logger.Error($"Topic:{topic} send failed",ex);
}
}
+
+ public void Produce(string topic, string key, string message)
+ {
+ ProduceCore(topic, key, message);
+ }
+
+ private void ProduceCore(string topic, string key, string message)
+ {
+ var kafkaMessage = new Message
+ {
+ Key = key,
+ Value = message
+ };
+ _producer.ProduceAsync(topic, kafkaMessage).GetAwaiter().GetResult();
+ }
}
}
diff --git a/YLErpDAL/Model/KafkaConfig.cs b/YLErpDAL/Model/KafkaConfig.cs
index 341ba482..5cd9cf36 100644
--- a/YLErpDAL/Model/KafkaConfig.cs
+++ b/YLErpDAL/Model/KafkaConfig.cs
@@ -60,7 +60,7 @@ namespace YLErp.Model
///
/// TRS合约数据推送topic(对外,如onebp等)
///
- public string ContractTopic { get; set; } = "onederi.trs.onebp.contract.v1";
+ public string ContractTopic { get; set; } = "onederiv.trs.contract.v1";
}
}
diff --git a/YLErpDAL/Modules/EodModule/SettlementModule/EodTaskRunner.cs b/YLErpDAL/Modules/EodModule/SettlementModule/EodTaskRunner.cs
index eb3da751..0a9e65fe 100644
--- a/YLErpDAL/Modules/EodModule/SettlementModule/EodTaskRunner.cs
+++ b/YLErpDAL/Modules/EodModule/SettlementModule/EodTaskRunner.cs
@@ -10,6 +10,12 @@ using YLErp.Modules.SystemModule;
using YLErp.Modules.TradeDalModule;
using YLErp.Modules.TradeModule.DealModule;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Options;
+using YieldChain.Commons;
+using YLErp.Abstract;
+using YLErp.Model;
+
namespace YLErp.Modules.EodModule.SettlementModule
{
///
@@ -445,12 +451,35 @@ where {nameof(t.TaskStartTime)}>'{startDateStr}' and {nameof(t.TaskState)}={(int
}
ClientBalanceUtility.saveClientRiskMonitor(eodTask.ValueDate);
new EodFileService(this.OptUser).GenerateFileAfterEod(eodTask.ValueDate);
+ PushTrsContractSnapshot(eodTask.ValueDate);
//执行下一日
eodTask.ValueDate = eodTask.ValueDate.AddDays(1);
eodTask.TaskEndTime = DateTime.Now;
}
}
+ private void PushTrsContractSnapshot(DateTime valueDate)
+ {
+ try
+ {
+ var provider = YLServiceLocator.ServiceProvider;
+ var kafkaProduce = provider?.GetService();
+ var kafkaOptions = provider?.GetService>();
+ if (kafkaProduce == null || kafkaOptions?.Value == null)
+ {
+ LogFactory.GetLogger("TRS合约日终Kafka推送").Error("Kafka service or configuration is unavailable");
+ return;
+ }
+
+ using var pushDbContext = DbContextFactory.GetYLDbContext();
+ new TrsContractKafkaPushService(pushDbContext, kafkaProduce, kafkaOptions.Value.ContractTopic).Push(valueDate);
+ }
+ catch (Exception ex)
+ {
+ LogFactory.GetLogger("TRS合约日终Kafka推送").Error($"TRS contract snapshot task failed, valueDate:{valueDate:yyyy-MM-dd}", ex);
+ }
+ }
+
///
/// 查找第一个可用的任务
///
diff --git a/YLErpDAL/Modules/EodModule/TrsContractKafkaPushService.cs b/YLErpDAL/Modules/EodModule/TrsContractKafkaPushService.cs
new file mode 100644
index 00000000..0efc85e1
--- /dev/null
+++ b/YLErpDAL/Modules/EodModule/TrsContractKafkaPushService.cs
@@ -0,0 +1,233 @@
+using YLErp.Abstract;
+using YLErp.BLL;
+using YLErp.DBModels;
+using YLErp.Helpers;
+
+namespace YLErp.Modules.EodModule
+{
+ ///
+ /// 收盘后按交易日推送 TRS 合约全量快照。
+ ///
+ public class TrsContractKafkaPushService
+ {
+ private const string DateFormat = "yyyy-MM-dd";
+ private const string DateTimeFormat = "yyyy-MM-dd HH:mm:ss";
+ private const string InterestCategory = "互换利率";
+ private const int TrsContractPushType = 1;
+ private const int MaxAttempts = 3;
+
+ private readonly YLContext _dbContext;
+ private readonly IKafkaProduce _kafkaProduce;
+ private readonly string _topic;
+ private readonly IYcLogger _logger;
+
+ public TrsContractKafkaPushService(YLContext dbContext, IKafkaProduce kafkaProduce, string topic)
+ {
+ _dbContext = dbContext ?? throw new ArgumentNullException(nameof(dbContext));
+ _kafkaProduce = kafkaProduce ?? throw new ArgumentNullException(nameof(kafkaProduce));
+ _topic = string.IsNullOrWhiteSpace(topic) ? throw new ArgumentException("Kafka topic is empty", nameof(topic)) : topic;
+ _logger = LogFactory.GetLogger(nameof(TrsContractKafkaPushService));
+ }
+
+ public void Push(DateTime valueDate)
+ {
+ valueDate = valueDate.Date;
+
+ TrsContractSnapshot snapshot;
+ string payload;
+ try
+ {
+ snapshot = BuildSnapshot(valueDate);
+ payload = JsonHelper.Serialize(snapshot, true, true);
+ }
+ catch (Exception ex)
+ {
+ _logger.Error($"TRS contract snapshot build failed, valueDate:{valueDate:yyyy-MM-dd}", ex);
+ RecordFailures(valueDate, 0, ex);
+ return;
+ }
+
+ var key = valueDate.ToString(DateFormat);
+ Exception lastException = null;
+ for (var attempt = 1; attempt <= MaxAttempts; attempt++)
+ {
+ try
+ {
+ _kafkaProduce.Produce(_topic, key, payload);
+ _logger.Info($"TRS contract snapshot sent, valueDate:{key}, topic:{_topic}, count:{snapshot.ContractCount}, attempt:{attempt}");
+ return;
+ }
+ catch (Exception ex)
+ {
+ lastException = ex;
+ _logger.Error($"TRS contract snapshot send failed, valueDate:{key}, topic:{_topic}, attempt:{attempt}", ex);
+ }
+ }
+
+ _logger.Error($"TRS contract snapshot send exhausted retries, valueDate:{key}, topic:{_topic}, attempts:{MaxAttempts}");
+ RecordFailures(valueDate, MaxAttempts, lastException);
+ }
+
+ protected virtual void RecordFailures(DateTime valueDate, int retryCount, Exception exception)
+ {
+ try
+ {
+ var recordIds = _dbContext.eod_swap
+ .Where(x => x.ValueDate == valueDate)
+ .Select(x => x.id)
+ .ToList();
+ if (recordIds.Count == 0)
+ {
+ recordIds.Add(0);
+ }
+
+ var now = DateTime.Now;
+ var statuses = _dbContext.push_status
+ .Where(x => x.ValueDate == valueDate
+ && x.PushType == TrsContractPushType
+ && recordIds.Contains(x.RecordId))
+ .ToList();
+ var error = exception?.ToString();
+ if (error?.Length > 2000)
+ {
+ error = error.Substring(0, 2000);
+ }
+
+ foreach (var recordId in recordIds)
+ {
+ var status = statuses.FirstOrDefault(x => x.RecordId == recordId);
+ if (status == null)
+ {
+ status = new PushStatus
+ {
+ ValueDate = valueDate,
+ PushType = TrsContractPushType,
+ RecordId = recordId,
+ CreateTime = now
+ };
+ _dbContext.push_status.Add(status);
+ }
+
+ status.State = PushStateEnum.失败;
+ status.RetryCount = retryCount;
+ status.LastError = error;
+ status.PushTime = now;
+ status.UpdateTime = now;
+ }
+
+ _dbContext.SaveChanges();
+ }
+ catch (Exception ex)
+ {
+ _logger.Error($"TRS contract push failure status save failed, valueDate:{valueDate:yyyy-MM-dd}", ex);
+ }
+ }
+
+ protected virtual TrsContractSnapshot BuildSnapshot(DateTime valueDate)
+ {
+ var eodSwaps = _dbContext.eod_swap
+ .Where(x => x.ValueDate == valueDate)
+ .AsNoTracking()
+ .ToList();
+ var tradeIds = eodSwaps.Select(x => x.SwapTradeId).Distinct().ToList();
+ var trades = _dbContext.trade
+ .Where(x => tradeIds.Contains(x.id))
+ .AsNoTracking()
+ .ToDictionary(x => x.id);
+ var eodPositions = _dbContext.eod_swap_position
+ .Where(x => x.ValueDate == valueDate && tradeIds.Contains(x.SwapTradeId) && !x.Invalid)
+ .AsNoTracking()
+ .ToList();
+ var positionIds = eodPositions.Select(x => x.PositionId).Distinct().ToList();
+ var swapPositions = _dbContext.swap_position
+ .Where(x => positionIds.Contains(x.id) && !x.Invalid && x.category_tag == InterestCategory)
+ .AsNoTracking()
+ .ToDictionary(x => x.id);
+
+ var contracts = eodSwaps.Select(eodSwap => BuildContract(eodSwap, trades, eodPositions, swapPositions)).ToList();
+ return new TrsContractSnapshot
+ {
+ SchemaVersion = "v1",
+ ValueDate = valueDate.ToString(DateFormat),
+ PushTime = DateTime.Now.ToString(DateTimeFormat),
+ ContractCount = contracts.Count,
+ Contracts = contracts
+ };
+ }
+
+ internal static TrsContractSnapshotItem BuildContract(
+ eod_swap eodSwap,
+ IReadOnlyDictionary trades,
+ IReadOnlyCollection eodPositions,
+ IReadOnlyDictionary swapPositions)
+ {
+ if (!trades.TryGetValue(eodSwap.SwapTradeId, out var trade))
+ {
+ throw new InvalidOperationException($"TRS trade not found, swapTradeId:{eodSwap.SwapTradeId}");
+ }
+
+ var positions = eodPositions.Where(x => x.SwapTradeId == eodSwap.SwapTradeId).ToList();
+ var floating = positions.Where(x => !string.IsNullOrWhiteSpace(x.UnderlyingCode) && swapPositions.ContainsKey(x.PositionId)).ToList();
+ var interest = positions.Where(x => string.IsNullOrWhiteSpace(x.UnderlyingCode)
+ && ConsTrade.InterestModels.Contains(x.InterestMode)
+ && swapPositions.TryGetValue(x.PositionId, out var swapPosition)
+ && swapPosition.category_tag == InterestCategory).ToList();
+
+ if (floating.Count != 1 || interest.Count != 1)
+ {
+ throw new InvalidOperationException($"TRS legs invalid, swapTradeId:{eodSwap.SwapTradeId}, floating:{floating.Count}, interest:{interest.Count}");
+ }
+
+ var interestLeg = interest[0];
+ var floatingLeg = floating[0];
+ return new TrsContractSnapshotItem
+ {
+ TradeDate = eodSwap.ValueDate.ToString(DateFormat),
+ BookId = eodSwap.BookId,
+ SwapTradeNo = eodSwap.SwapTradeNo,
+ ClientId = eodSwap.ClientId,
+ UnderlyingCode = trade.UnderlyingCode,
+ UnderlyingName = trade.UnderlyingAssetName,
+ UnderlyingInstrumentType = trade.UnderlyingInstrumentType,
+ NotionalValue = eodSwap.NotionalValue,
+ Dv01 = eodSwap.dv01 ?? 0,
+ StartDate = trade.StartDate?.ToString(DateFormat),
+ MaturityDate = trade.ExerciseDate?.ToString(DateFormat),
+ FixedRate = interestLeg.InterestRateDefault,
+ InterestDirection = interestLeg.InterestDirection,
+ FloatingDirection = floatingLeg.PositionType,
+ InitMarginGain = eodSwap.InitMarginGain,
+ InitMarginLoss = eodSwap.InitMarginLoss
+ };
+ }
+ }
+
+ public class TrsContractSnapshot
+ {
+ public string SchemaVersion { get; set; }
+ public string ValueDate { get; set; }
+ public string PushTime { get; set; }
+ public int ContractCount { get; set; }
+ public List Contracts { get; set; }
+ }
+
+ public class TrsContractSnapshotItem
+ {
+ public string TradeDate { get; set; }
+ public int BookId { get; set; }
+ public string SwapTradeNo { get; set; }
+ public int ClientId { get; set; }
+ public string UnderlyingCode { get; set; }
+ public string UnderlyingName { get; set; }
+ public string UnderlyingInstrumentType { get; set; }
+ public decimal NotionalValue { get; set; }
+ public decimal Dv01 { get; set; }
+ public string StartDate { get; set; }
+ public string MaturityDate { get; set; }
+ public decimal FixedRate { get; set; }
+ public int InterestDirection { get; set; }
+ public int FloatingDirection { get; set; }
+ public decimal InitMarginGain { get; set; }
+ public decimal InitMarginLoss { get; set; }
+ }
+}
diff --git a/YLErpWeb/appsettings.dev.json b/YLErpWeb/appsettings.dev.json
index ecceba98..3e268d15 100644
--- a/YLErpWeb/appsettings.dev.json
+++ b/YLErpWeb/appsettings.dev.json
@@ -34,6 +34,7 @@
"CompressionType": 0, // 消息压缩方式,可以是 None(0)、Gzip(1)、Snappy(2)、Lz4(3)、Zstd(4) 中的一种,
"MessageTimeoutMs": 3000, // 控制生产者等待消息确认的时间,单位是毫秒
"ClientRateTopic": "ylClientRateTopic", //客户互换费率生产topic
+ "ContractTopic": "onederiv.trs.contract.v1", //TRS合约日终推送topic
"HedgingAccountTopic": "ylHedgingAccountTopic", //对冲账户生产topic
"ReqAccountCapitalTopic": "ReqAccountCapital", //账户资金请求topic
"OnRspAccountCapitalTopic": "OnRspAccountCapital", //账户资金请求返回topic
diff --git a/YLErpWeb/appsettings.local.json b/YLErpWeb/appsettings.local.json
index a42f229b..e2956912 100644
--- a/YLErpWeb/appsettings.local.json
+++ b/YLErpWeb/appsettings.local.json
@@ -34,6 +34,7 @@
"CompressionType": 0, // 消息压缩方式,可以是 None(0)、Gzip(1)、Snappy(2)、Lz4(3)、Zstd(4) 中的一种,
"MessageTimeoutMs": 3000, // 控制生产者等待消息确认的时间,单位是毫秒
"ClientRateTopic": "ylClientRateTopic", //客户互换费率生产topic
+ "ContractTopic": "onederiv.trs.contract.v1", //TRS合约日终推送topic
"HedgingAccountTopic": "ylHedgingAccountTopic", //对冲账户生产topic
"ReqAccountCapitalTopic": "ReqAccountCapital", //账户资金请求topic
"OnRspAccountCapitalTopic": "OnRspAccountCapital", //账户资金请求返回topic
diff --git a/YLErpWeb/appsettings.prod.json b/YLErpWeb/appsettings.prod.json
index 3df07b7a..a7264a53 100644
--- a/YLErpWeb/appsettings.prod.json
+++ b/YLErpWeb/appsettings.prod.json
@@ -36,6 +36,7 @@
"CompressionType": 0, // 消息压缩方式,可以是 None(0)、Gzip(1)、Snappy(2)、Lz4(3)、Zstd(4) 中的一种,
"MessageTimeoutMs": 3000, // 控制生产者等待消息确认的时间,单位是毫秒
"ClientRateTopic": "ylClientRateTopic", //客户互换费率生产topic
+ "ContractTopic": "onederiv.trs.contract.v1", //TRS合约日终推送topic
"HedgingAccountTopic": "ylHedgingAccountTopic", //对冲账户生产topic
"AccountCapitalTopicGroupId": "YiLian_OnRspAccountCapitalConsumer", //账户资金消费组
"ReqAccountCapitalTopic": "ReqAccountCapital", //账户资金请求topic
diff --git a/YLErpWeb/appsettings.uat.json b/YLErpWeb/appsettings.uat.json
index 87232cdd..fb6d933c 100644
--- a/YLErpWeb/appsettings.uat.json
+++ b/YLErpWeb/appsettings.uat.json
@@ -36,6 +36,7 @@
"CompressionType": 0, // 消息压缩方式,可以是 None(0)、Gzip(1)、Snappy(2)、Lz4(3)、Zstd(4) 中的一种,
"MessageTimeoutMs": 3000, // 控制生产者等待消息确认的时间,单位是毫秒
"ClientRateTopic": "ylClientRateTopic", //客户互换费率生产topic
+ "ContractTopic": "onederiv.trs.contract.v1", //TRS合约日终推送topic
"HedgingAccountTopic": "ylHedgingAccountTopic", //对冲账户生产topic
"AccountCapitalTopicGroupId": "YiLian_OnRspAccountCapitalConsumer", //账户资金消费组
"ReqAccountCapitalTopic": "ReqAccountCapital", //账户资金请求topic