2026-04-07 23:04:27 +08:00
|
|
|
|
import 'package:decimal/decimal.dart';
|
|
|
|
|
|
|
2026-04-07 16:43:48 +08:00
|
|
|
|
/// K 线数据模型
|
|
|
|
|
|
class Candle {
|
|
|
|
|
|
final int timestamp; // 毫秒时间戳
|
2026-04-07 23:04:27 +08:00
|
|
|
|
final Decimal open;
|
|
|
|
|
|
final Decimal high;
|
|
|
|
|
|
final Decimal low;
|
|
|
|
|
|
final Decimal close;
|
|
|
|
|
|
final Decimal volume;
|
2026-04-07 16:43:48 +08:00
|
|
|
|
|
|
|
|
|
|
const Candle({
|
|
|
|
|
|
required this.timestamp,
|
|
|
|
|
|
required this.open,
|
|
|
|
|
|
required this.high,
|
|
|
|
|
|
required this.low,
|
|
|
|
|
|
required this.close,
|
|
|
|
|
|
required this.volume,
|
|
|
|
|
|
});
|
|
|
|
|
|
|
2026-04-07 23:04:27 +08:00
|
|
|
|
/// 从 JSON 解析,使用 Decimal 避免精度丢失
|
2026-04-07 16:43:48 +08:00
|
|
|
|
factory Candle.fromJson(Map<String, dynamic> json) {
|
|
|
|
|
|
return Candle(
|
|
|
|
|
|
timestamp: json['timestamp'] as int? ?? json['time'] as int? ?? 0,
|
2026-04-07 23:04:27 +08:00
|
|
|
|
open: _parseDecimal(json['open']),
|
|
|
|
|
|
high: _parseDecimal(json['high']),
|
|
|
|
|
|
low: _parseDecimal(json['low']),
|
|
|
|
|
|
close: _parseDecimal(json['close']),
|
|
|
|
|
|
volume: _parseDecimal(json['volume']),
|
2026-04-07 16:43:48 +08:00
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-07 23:04:27 +08:00
|
|
|
|
/// 安全解析 Decimal
|
|
|
|
|
|
static Decimal _parseDecimal(dynamic value) {
|
|
|
|
|
|
if (value == null) return Decimal.zero;
|
|
|
|
|
|
if (value is Decimal) return value;
|
|
|
|
|
|
if (value is num) return Decimal.fromJson(value.toString());
|
|
|
|
|
|
if (value is String) {
|
|
|
|
|
|
try {
|
|
|
|
|
|
return Decimal.parse(value);
|
|
|
|
|
|
} catch (_) {
|
|
|
|
|
|
return Decimal.zero;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
return Decimal.zero;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-07 16:43:48 +08:00
|
|
|
|
Map<String, dynamic> toJson() => {
|
|
|
|
|
|
'timestamp': timestamp,
|
2026-04-07 23:04:27 +08:00
|
|
|
|
'open': open.toDouble(),
|
|
|
|
|
|
'high': high.toDouble(),
|
|
|
|
|
|
'low': low.toDouble(),
|
|
|
|
|
|
'close': close.toDouble(),
|
|
|
|
|
|
'volume': volume.toDouble(),
|
2026-04-07 16:43:48 +08:00
|
|
|
|
};
|
2026-04-07 23:04:27 +08:00
|
|
|
|
|
|
|
|
|
|
/// 转换为 double(供图表库使用)
|
|
|
|
|
|
double get openDouble => open.toDouble();
|
|
|
|
|
|
double get highDouble => high.toDouble();
|
|
|
|
|
|
double get lowDouble => low.toDouble();
|
|
|
|
|
|
double get closeDouble => close.toDouble();
|
|
|
|
|
|
double get volumeDouble => volume.toDouble();
|
|
|
|
|
|
|
|
|
|
|
|
/// 安全加法(防止溢出)
|
|
|
|
|
|
Decimal safeAdd(Decimal other) {
|
|
|
|
|
|
try {
|
|
|
|
|
|
return open + other;
|
|
|
|
|
|
} catch (_) {
|
|
|
|
|
|
return Decimal.zero;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// 安全乘法(防止溢出)
|
|
|
|
|
|
Decimal safeMultiply(Decimal other) {
|
|
|
|
|
|
try {
|
|
|
|
|
|
return open * other;
|
|
|
|
|
|
} catch (_) {
|
|
|
|
|
|
return Decimal.zero;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// 格式化价格显示
|
|
|
|
|
|
String formatPrice({int decimalPlaces = 2}) {
|
|
|
|
|
|
return close.toStringAsFixed(decimalPlaces);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// 格式化成交量显示
|
|
|
|
|
|
String formatVolume() {
|
|
|
|
|
|
final vol = volume.toDouble();
|
|
|
|
|
|
if (vol >= 1000000000) {
|
|
|
|
|
|
return '${(vol / 1000000000).toStringAsFixed(2)}B';
|
|
|
|
|
|
} else if (vol >= 1000000) {
|
|
|
|
|
|
return '${(vol / 1000000).toStringAsFixed(2)}M';
|
|
|
|
|
|
} else if (vol >= 1000) {
|
|
|
|
|
|
return '${(vol / 1000).toStringAsFixed(2)}K';
|
|
|
|
|
|
}
|
|
|
|
|
|
return vol.toStringAsFixed(2);
|
|
|
|
|
|
}
|
2026-04-07 16:43:48 +08:00
|
|
|
|
}
|