100 lines
2.5 KiB
Dart
100 lines
2.5 KiB
Dart
import 'package:decimal/decimal.dart';
|
||
|
||
/// K 线数据模型
|
||
class Candle {
|
||
final int timestamp; // 毫秒时间戳
|
||
final Decimal open;
|
||
final Decimal high;
|
||
final Decimal low;
|
||
final Decimal close;
|
||
final Decimal volume;
|
||
|
||
const Candle({
|
||
required this.timestamp,
|
||
required this.open,
|
||
required this.high,
|
||
required this.low,
|
||
required this.close,
|
||
required this.volume,
|
||
});
|
||
|
||
/// 从 JSON 解析,使用 Decimal 避免精度丢失
|
||
factory Candle.fromJson(Map<String, dynamic> json) {
|
||
return Candle(
|
||
timestamp: json['timestamp'] as int? ?? json['time'] as int? ?? 0,
|
||
open: _parseDecimal(json['open']),
|
||
high: _parseDecimal(json['high']),
|
||
low: _parseDecimal(json['low']),
|
||
close: _parseDecimal(json['close']),
|
||
volume: _parseDecimal(json['volume']),
|
||
);
|
||
}
|
||
|
||
/// 安全解析 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;
|
||
}
|
||
|
||
Map<String, dynamic> toJson() => {
|
||
'timestamp': timestamp,
|
||
'open': open.toDouble(),
|
||
'high': high.toDouble(),
|
||
'low': low.toDouble(),
|
||
'close': close.toDouble(),
|
||
'volume': volume.toDouble(),
|
||
};
|
||
|
||
/// 转换为 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);
|
||
}
|
||
}
|