/// 行情 Ticker 数据模型(不可变) /// 实现 == / hashCode 让 .select() 能正确判断单个 ticker 是否变化, /// 只有该 symbol 的价格/涨跌幅真正变了才触发对应行重建。 class MarketTicker { final String symbol; // e.g. "BTCUSDT" final String baseAsset; // e.g. "BTC" final double lastPrice; final double change24h; // 24h 涨跌幅(百分比) final double volume24h; // 24h 成交额(USDT) final bool isFutures; // true = 永续合约,false = 现货 final bool isHot; // 是否热门 final String icon; // 币种图标 URL final int? pricePrecision; // 价格精度(小数位数),来自 coinScale final String? lastPriceStr; // WS 返回的原始价格字符串 const MarketTicker({ required this.symbol, required this.baseAsset, required this.lastPrice, required this.change24h, required this.volume24h, this.isFutures = true, this.isHot = false, this.icon = '', this.pricePrecision, this.lastPriceStr, }); /// 从 /swap/coin/enabled-list 接口解析 /// 接口字段: symbol("BTC/USDT"), coinSymbol("BTC"), baseSymbol("USDT"), isHot(1/0) /// 价格数据由 WebSocket 推送,初始为 0 factory MarketTicker.fromJson(Map json) { // symbol 格式 "BTC/USDT" → 转为 "BTCUSDT" final rawSymbol = json['symbol'] as String? ?? ''; final symbol = rawSymbol.replaceAll('/', ''); final coinSymbol = json['coinSymbol'] as String? ?? ''; final rawScale = (json['coinScale'] ?? json['minScale']) as num?; return MarketTicker( symbol: symbol, baseAsset: coinSymbol, lastPrice: 0, change24h: 0, volume24h: 0, isFutures: true, isHot: json['isHot'] == 1 || json['isHot'] == '1', icon: json['icon'] as String? ?? '', pricePrecision: rawScale?.toInt(), ); } @override bool operator ==(Object other) => identical(this, other) || other is MarketTicker && symbol == other.symbol && baseAsset == other.baseAsset && lastPrice == other.lastPrice && change24h == other.change24h && volume24h == other.volume24h && isFutures == other.isFutures && isHot == other.isHot && icon == other.icon && pricePrecision == other.pricePrecision && lastPriceStr == other.lastPriceStr; @override int get hashCode => Object.hash(symbol, baseAsset, lastPrice, change24h, volume24h, isFutures, isHot, icon, pricePrecision, lastPriceStr); MarketTicker copyWith({ String? symbol, String? baseAsset, double? lastPrice, double? change24h, double? volume24h, bool? isFutures, bool? isHot, String? icon, int? pricePrecision, String? lastPriceStr, }) { return MarketTicker( symbol: symbol ?? this.symbol, baseAsset: baseAsset ?? this.baseAsset, lastPrice: lastPrice ?? this.lastPrice, change24h: change24h ?? this.change24h, volume24h: volume24h ?? this.volume24h, isFutures: isFutures ?? this.isFutures, isHot: isHot ?? this.isHot, icon: icon ?? this.icon, pricePrecision: pricePrecision ?? this.pricePrecision, lastPriceStr: lastPriceStr ?? this.lastPriceStr, ); } }