C# WinForms中如何将价格列表与订单DataTable关联以自动更新盈亏
解决方案:将价格List与订单DataTable关联并自动更新盈亏
针对你遇到的问题,这里提供几个高效且贴合WinForms场景的实现方案,核心思路是通过事件驱动+快速查找替代原DataSet的关联机制,既保留List的高性能,又实现价格变化到订单盈亏的自动同步。
方案1:带属性通知的价格类 + 事件绑定(推荐)
这种方式通过给价格对象添加属性变化通知,让订单行直接监听对应标的的价格变动,实时更新盈亏,性能和实时性最优。
步骤1:定义可通知的价格类
创建实现INotifyPropertyChanged的价格类,价格变动时自动触发通知:
public class PriceItem : INotifyPropertyChanged { private decimal _lastPrice; // 标的代码,作为关联键 public string Symbol { get; set; } // 最新价格,变更时触发通知 public decimal LastPrice { get => _lastPrice; set { if (_lastPrice != value) { _lastPrice = value; OnPropertyChanged(nameof(LastPrice)); } } } public event PropertyChangedEventHandler PropertyChanged; protected void OnPropertyChanged(string propertyName) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } }
步骤2:维护价格字典用于快速查找
将价格List配合Dictionary<string, PriceItem>使用,通过标的Symbol快速定位价格对象,避免遍历List的性能损耗:
// 全局/类级别的价格容器 private List<PriceItem> _priceList = new List<PriceItem>(); private Dictionary<string, PriceItem> _priceDict = new Dictionary<string, PriceItem>(); // WebSocket收到价格更新时的处理逻辑 private void OnPriceUpdate(string symbol, decimal newPrice) { if (_priceDict.TryGetValue(symbol, out var priceItem)) { priceItem.LastPrice = newPrice; // 触发PropertyChanged事件 } else { // 新增标的价格 var newItem = new PriceItem { Symbol = symbol, LastPrice = newPrice }; _priceList.Add(newItem); _priceDict.Add(symbol, newItem); // 给已有该标的的订单绑定事件 BindPriceToOrders(newItem); } }
步骤3:给订单行绑定价格变化事件
初始化订单DataTable时,为每个订单行绑定对应标的价格的变化事件,实时更新盈亏:
// 初始化订单时绑定事件 private void InitializeOrders() { // 假设orders是已加载未平仓订单的DataTable,包含Symbol、EntryPrice、Quantity、CurrentPnL列 foreach (DataRow row in orders.Rows) { string symbol = row["Symbol"].ToString(); if (_priceDict.TryGetValue(symbol, out var priceItem)) { // 计算初始盈亏 UpdateOrderPnL(row, priceItem.LastPrice); // 绑定价格变化事件 priceItem.PropertyChanged += (sender, e) => { if (e.PropertyName == nameof(PriceItem.LastPrice)) { // WinForms必须在UI线程更新DataTable if (orders.InvokeRequired) { orders.Invoke(new Action(() => UpdateOrderPnL(row, ((PriceItem)sender).LastPrice) )); } else { UpdateOrderPnL(row, ((PriceItem)sender).LastPrice); } } }; } } } // 盈亏计算逻辑,根据你的交易规则调整 private void UpdateOrderPnL(DataRow orderRow, decimal currentPrice) { decimal entryPrice = (decimal)orderRow["EntryPrice"]; int quantity = (int)orderRow["Quantity"]; // 示例:多单盈亏计算,空单请自行调整符号 decimal pnl = (currentPrice - entryPrice) * quantity; orderRow["CurrentPnL"] = pnl; } // 新增价格时,给已有该标的的订单绑定事件 private void BindPriceToOrders(PriceItem priceItem) { var targetRows = orders.Select($"Symbol = '{priceItem.Symbol}'"); foreach (DataRow row in targetRows) { UpdateOrderPnL(row, priceItem.LastPrice); priceItem.PropertyChanged += (sender, e) => { if (e.PropertyName == nameof(PriceItem.LastPrice)) { if (orders.InvokeRequired) { orders.Invoke(new Action(() => UpdateOrderPnL(row, ((PriceItem)sender).LastPrice) )); } else { UpdateOrderPnL(row, ((PriceItem)sender).LastPrice); } } }; } }
方案2:DataTable表达式列 + 价格字典
如果不想绑定大量事件,可以利用DataTable的计算列,结合价格字典实现盈亏的自动计算,适合订单量不大的场景。
步骤1:定义价格访问辅助类
public static class PriceAccessor { // 全局价格字典,需确保线程安全 public static Dictionary<string, PriceItem> PriceDictionary { get; set; } // 供DataTable表达式调用的静态方法 public static decimal GetCurrentPrice(string symbol) { return PriceDictionary.TryGetValue(symbol, out var price) ? price.LastPrice : 0; } }
步骤2:给订单DataTable添加计算列
// 初始化订单DataTable时添加盈亏计算列 orders.Columns.Add("CurrentPnL", typeof(decimal), "([Quantity] * (PriceAccessor.GetCurrentPrice([Symbol]) - [EntryPrice]))"); // 初始化价格字典引用 PriceAccessor.PriceDictionary = _priceDict;
步骤3:价格更新时触发计算列刷新
当价格变化时,找到对应标的的订单行,通过BeginEdit/EndEdit触发计算列重新计算:
private void OnPriceUpdate(string symbol, decimal newPrice) { if (_priceDict.TryGetValue(symbol, out var priceItem)) { priceItem.LastPrice = newPrice; // 刷新对应标的的订单盈亏 var targetRows = orders.Select($"Symbol = '{symbol}'"); foreach (DataRow row in targetRows) { row.BeginEdit(); row.EndEdit(); // 触发计算列重新求值 } } // 新增标的逻辑同方案1 }
关于「给订单行设置价格引用」的问题
完全可以实现:给订单DataTable添加一个隐藏列,存储对应PriceItem的引用,后续直接通过该列获取最新价格。
// 添加隐藏列存储PriceItem引用 orders.Columns.Add("PriceReference", typeof(PriceItem)); orders.Columns["PriceReference"].ColumnMapping = MappingType.Hidden; // 初始化订单行时赋值 foreach (DataRow row in orders.Rows) { string symbol = row["Symbol"].ToString(); if (_priceDict.TryGetValue(symbol, out var priceItem)) { row["PriceReference"] = priceItem; } } // 后续获取最新价格时直接读取 var priceItem = (PriceItem)orderRow["PriceReference"]; decimal currentPrice = priceItem.LastPrice;
这种方式适合需要手动触发盈亏计算的场景,但仍需结合事件驱动才能实现自动更新。
注意事项
- 线程安全:WebSocket推送通常在后台线程,所有更新UI/DataTable的操作必须通过
Control.Invoke切换到WinForms主线程,避免跨线程异常。 - 性能优化:订单量较大时,优先选择方案1的事件绑定,避免遍历整个DataTable;价格字典的更新需加锁,防止多线程冲突。
内容的提问来源于stack exchange,提问作者doubler
相关产品推荐
相关产品推荐

