spot_service.dart 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219
  1. import 'package:dio/dio.dart';
  2. /// 现货 API 服务 — 对接 /spot/v1/... 接口体系
  3. ///
  4. /// 接口对照(全部带 spot/ 前缀):
  5. /// GET spot/v1/config/coin/list 已开启币种列表(含图标/精度)
  6. /// GET spot/v1/config/symbol/list 已开启交易对列表(含精度/手续费/最小量)
  7. /// GET spot/v1/account/asset/list 用户现货资产列表(含今日盈亏)
  8. /// GET spot/v1/account/transfer/symbols 可划转币种列表
  9. /// POST spot/v1/account/transfer 资金账户 ↔ 现货账户划转
  10. /// POST spot/v1/order/create 创建订单
  11. /// POST spot/v1/order/cancel 撤单
  12. /// POST spot/v1/order/current 当前委托
  13. /// POST spot/v1/order/history 历史委托
  14. /// POST spot/v1/order/trades 成交记录
  15. /// POST spot/v1/account_records 资金流水
  16. class SpotService {
  17. const SpotService(this._dio);
  18. final Dio _dio;
  19. // ── 配置 ────────────────────────────────────────────
  20. /// 获取已开启的现货币种列表(含图标/显示精度/标签)
  21. Future<List<Map<String, dynamic>>> getCoins() async {
  22. final resp =
  23. await _dio.get<Map<String, dynamic>>('spot/v1/config/coin/list');
  24. return _extractList(resp.data);
  25. }
  26. /// 获取已开启且已发布的交易对列表(含精度/手续费/最小下单量/排序权重)
  27. Future<List<Map<String, dynamic>>> getSymbols() async {
  28. final resp =
  29. await _dio.get<Map<String, dynamic>>('spot/v1/config/symbol/list');
  30. return _extractList(resp.data);
  31. }
  32. // ── 资产 ────────────────────────────────────────────
  33. /// 获取用户现货资产列表(含 totalAmount/todayPnl/todayPnlRate)
  34. ///
  35. /// [hideZero] 传 true 时服务端过滤 0 余额币种(对齐 Web hideZero 逻辑)
  36. Future<Map<String, dynamic>> getAssets({bool? hideZero}) async {
  37. final query = <String, dynamic>{};
  38. if (hideZero != null) {
  39. query['hideZero'] = hideZero;
  40. }
  41. final resp = await _dio.get<Map<String, dynamic>>(
  42. 'spot/v1/account/asset/list',
  43. queryParameters: query.isEmpty ? null : query,
  44. );
  45. final data = resp.data?['data'];
  46. if (data is Map<String, dynamic>) return data;
  47. return {};
  48. }
  49. /// 获取可划转的开放币种列表
  50. Future<List<Map<String, dynamic>>> getTransferSymbols() async {
  51. final resp = await _dio
  52. .get<Map<String, dynamic>>('spot/v1/account/transfer/symbols');
  53. return _extractList(resp.data);
  54. }
  55. /// 账户间划转:资金账户 ↔ 现货账户
  56. ///
  57. /// [direction] 1=资金账户→现货账户,2=现货账户→资金账户
  58. Future<void> transfer({
  59. required String symbol,
  60. required double amount,
  61. required int direction,
  62. }) async {
  63. await _dio.post<dynamic>(
  64. 'spot/v1/account/transfer',
  65. data: {'symbol': symbol, 'amount': amount, 'direction': direction},
  66. );
  67. }
  68. // ── 订单 ────────────────────────────────────────────
  69. /// 创建订单
  70. ///
  71. /// [type] 1=限价单,2=市价单
  72. /// [side] BUY / SELL
  73. /// [price] 限价单传价格;市价单传 0
  74. /// [volume] 限价单传基础币数量;市价买传 USDT 金额;市价卖传基础币数量
  75. Future<Map<String, dynamic>> placeOrder({
  76. required String symbol,
  77. required String side,
  78. required int type,
  79. required double price,
  80. required double volume,
  81. }) async {
  82. final resp = await _dio.post<Map<String, dynamic>>(
  83. 'spot/v1/order/create',
  84. data: {
  85. 'symbol': symbol,
  86. 'side': side,
  87. 'type': type,
  88. 'price': price,
  89. 'volume': volume,
  90. 'source': 2,
  91. 'orderType': 1,
  92. },
  93. );
  94. final data = resp.data?['data'];
  95. if (data is Map<String, dynamic>) return data;
  96. return {};
  97. }
  98. /// 撤单
  99. Future<void> cancelOrder(int orderId) async {
  100. await _dio.post<dynamic>(
  101. 'spot/v1/order/cancel',
  102. data: {'orderId': orderId},
  103. );
  104. }
  105. /// 查询当前委托(未完成挂单)
  106. Future<Map<String, dynamic>> getCurrentOrders({
  107. String? symbol,
  108. String? side,
  109. int page = 1,
  110. int size = 50,
  111. int? ctimeBegin,
  112. int? ctimeEnd,
  113. }) async {
  114. final body = <String, dynamic>{'page': page, 'size': size};
  115. if (symbol != null && symbol.isNotEmpty) body['symbol'] = symbol;
  116. if (side != null && side.isNotEmpty) body['side'] = side;
  117. if (ctimeBegin != null) body['ctimeBegin'] = ctimeBegin;
  118. if (ctimeEnd != null) body['ctimeEnd'] = ctimeEnd;
  119. final resp = await _dio.post<Map<String, dynamic>>('spot/v1/order/current',
  120. data: body);
  121. return _extractPageData(resp.data);
  122. }
  123. /// 查询历史委托(已完成或已撤单)
  124. Future<Map<String, dynamic>> getHistoryOrders({
  125. String? symbol,
  126. String? side,
  127. int? type,
  128. int? status,
  129. int page = 1,
  130. int size = 20,
  131. int? ctimeBegin,
  132. int? ctimeEnd,
  133. }) async {
  134. final body = <String, dynamic>{'page': page, 'size': size};
  135. if (symbol != null && symbol.isNotEmpty) body['symbol'] = symbol;
  136. if (side != null && side.isNotEmpty) body['side'] = side;
  137. if (type != null) body['type'] = type;
  138. if (status != null) body['status'] = status;
  139. if (ctimeBegin != null) body['ctimeBegin'] = ctimeBegin;
  140. if (ctimeEnd != null) body['ctimeEnd'] = ctimeEnd;
  141. final resp = await _dio.post<Map<String, dynamic>>('spot/v1/order/history',
  142. data: body);
  143. return _extractPageData(resp.data);
  144. }
  145. /// 查询成交记录(有成交量的订单)
  146. Future<Map<String, dynamic>> getTrades({
  147. String? symbol,
  148. String? side,
  149. int? type,
  150. int? status,
  151. int page = 1,
  152. int size = 20,
  153. int? ctimeBegin,
  154. int? ctimeEnd,
  155. }) async {
  156. final body = <String, dynamic>{'page': page, 'size': size};
  157. if (symbol != null && symbol.isNotEmpty) body['symbol'] = symbol;
  158. if (side != null && side.isNotEmpty) body['side'] = side;
  159. if (type != null) body['type'] = type;
  160. if (status != null) body['status'] = status;
  161. if (ctimeBegin != null) body['ctimeBegin'] = ctimeBegin;
  162. if (ctimeEnd != null) body['ctimeEnd'] = ctimeEnd;
  163. final resp = await _dio.post<Map<String, dynamic>>('spot/v1/order/trades',
  164. data: body);
  165. return _extractPageData(resp.data);
  166. }
  167. // ── 资金流水 ─────────────────────────────────────────
  168. /// 分页查询资金流水
  169. ///
  170. /// [type] 流水类型:1=划转转入,2=划转转出,5=成交,12=手续费
  171. Future<Map<String, dynamic>> getAccountRecords({
  172. String? symbol,
  173. int? type,
  174. int page = 1,
  175. int size = 20,
  176. int? startTime,
  177. int? endTime,
  178. }) async {
  179. final body = <String, dynamic>{'page': page, 'size': size};
  180. if (symbol != null && symbol.isNotEmpty) body['symbol'] = symbol;
  181. if (type != null) body['type'] = type;
  182. if (startTime != null) body['startTime'] = startTime;
  183. if (endTime != null) body['endTime'] = endTime;
  184. final resp = await _dio
  185. .post<Map<String, dynamic>>('spot/v1/account_records', data: body);
  186. return _extractPageData(resp.data);
  187. }
  188. // ── 工具 ────────────────────────────────────────────
  189. static List<Map<String, dynamic>> _extractList(Map<String, dynamic>? body) {
  190. final raw = body?['data'];
  191. if (raw is List) return raw.whereType<Map<String, dynamic>>().toList();
  192. return [];
  193. }
  194. static Map<String, dynamic> _extractPageData(Map<String, dynamic>? body) {
  195. final raw = body?['data'];
  196. if (raw is Map<String, dynamic>) return raw;
  197. return {};
  198. }
  199. }