futures_service.dart 9.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302
  1. import 'package:dio/dio.dart';
  2. typedef PageResult = ({List<Map<String, dynamic>> items, bool hasMore});
  3. // type: 0=市价,1=限价,2=计划委托
  4. // direction: 0=做多,1=做空
  5. // 撤单传 entrustId(数字 ID)
  6. class FuturesService {
  7. const FuturesService(this._dio);
  8. final Dio _dio;
  9. static final _form = Options(
  10. contentType: 'application/x-www-form-urlencoded',
  11. );
  12. // ── 合约信息 ─────────────────────────────────────────────────
  13. Future<Map<String, dynamic>> getSymbolInfo(String symbol) async {
  14. final resp = await _dio.post<Map<String, dynamic>>(
  15. 'swap/symbol-info',
  16. data: {'symbol': symbol},
  17. options: _form,
  18. );
  19. final data = resp.data?['data'];
  20. if (data is Map<String, dynamic>) return data;
  21. return resp.data ?? {};
  22. }
  23. // ── 杠杆 ─────────────────────────────────────────────────────
  24. Future<int> getLeverage(int contractId) async {
  25. final resp = await _dio.post<Map<String, dynamic>>(
  26. 'swap/wallet-new/getLeverage',
  27. data: {'contractId': contractId},
  28. options: _form,
  29. );
  30. final data = resp.data?['data'];
  31. return (data is num ? data.toInt() : int.tryParse('$data')) ?? 20;
  32. }
  33. Future<void> modifyLeverage(int contractId, int leverage) async {
  34. await _dio.post<dynamic>(
  35. 'swap/wallet-new/modifyLeverage',
  36. data: {'contractId': contractId, 'leverage': leverage},
  37. options: _form,
  38. );
  39. }
  40. /// 切换仓位模式:0=合仓(全仓)1=分仓
  41. /// 后端要求有持仓/委托时禁止切换,失败会抛异常
  42. Future<void> modifyPositionType(int positionType) async {
  43. await _dio.post<dynamic>(
  44. 'swap/wallet-new/modifyType',
  45. data: {'positionType': positionType},
  46. options: _form,
  47. );
  48. }
  49. // ── 账户与仓位 ───────────────────────────────────────────────
  50. Future<Map<String, dynamic>> getWithPositions([String symbol = '']) async {
  51. final resp = await _dio.get<Map<String, dynamic>>(
  52. 'swap/wallet-new/get-with-positions',
  53. queryParameters: symbol.isNotEmpty ? {'symbol': symbol} : null,
  54. );
  55. return (resp.data?['data'] as Map<String, dynamic>?) ?? {};
  56. }
  57. // ── 委托单 ───────────────────────────────────────────────────
  58. Future<PageResult> getCurrentOrders({
  59. int pageNo = 1,
  60. int pageSize = 10,
  61. }) async {
  62. final resp = await _dio.get<Map<String, dynamic>>(
  63. 'swap/order/current',
  64. queryParameters: {'pageNo': pageNo, 'pageSize': pageSize},
  65. );
  66. return _extractPage(resp.data, pageSize);
  67. }
  68. static PageResult _extractPage(Map<String, dynamic>? body, int pageSize) {
  69. if (body == null) return (items: [], hasMore: false);
  70. List<Map<String, dynamic>> items = [];
  71. final direct = body['content'] ?? body['records'] ?? body['list'];
  72. if (direct is List) {
  73. items = direct.map((e) => Map<String, dynamic>.from(e as Map)).toList();
  74. final last = body['last'];
  75. final hasMore = last is bool ? !last : items.length >= pageSize;
  76. return (items: items, hasMore: hasMore);
  77. }
  78. final nested = body['data'];
  79. if (nested is Map) {
  80. final content = nested['content'] ?? nested['records'] ?? nested['list'];
  81. if (content is List) {
  82. items = content.map((e) => Map<String, dynamic>.from(e as Map)).toList();
  83. final last = nested['last'];
  84. final hasMore = last is bool ? !last : items.length >= pageSize;
  85. return (items: items, hasMore: hasMore);
  86. }
  87. }
  88. if (nested is List) {
  89. items = nested.map((e) => Map<String, dynamic>.from(e as Map)).toList();
  90. return (items: items, hasMore: items.length >= pageSize);
  91. }
  92. return (items: [], hasMore: false);
  93. }
  94. // ── 开仓 ─────────────────────────────────────────────────────
  95. Future<void> openOrder({
  96. required int contractCoinId,
  97. required int type,
  98. required int direction,
  99. required double volume,
  100. required int leverage,
  101. double? entrustPrice,
  102. double? triggerPrice,
  103. double? profitPrice,
  104. double? lossPrice,
  105. int? isPercentage, // 1=百分比模式
  106. int? positionType, // 0=全仓 1=分仓,由前端传递
  107. }) async {
  108. // 计划委托无 entrustPrice 时传 0(市价)
  109. final effectiveEntrustPrice = (type == 2 && (entrustPrice == null || entrustPrice <= 0))
  110. ? 0.0
  111. : entrustPrice;
  112. await _dio.post<dynamic>(
  113. 'swap/order/open/v3',
  114. data: {
  115. 'contractCoinId': contractCoinId,
  116. 'type': type,
  117. 'direction': direction,
  118. 'volume': volume.toString(),
  119. 'leverage': leverage,
  120. 'entrustPrice': effectiveEntrustPrice?.toString() ?? '',
  121. 'triggerPrice': triggerPrice?.toString() ?? '',
  122. 'profitPrice': profitPrice?.toString() ?? '',
  123. 'lossPrice': lossPrice?.toString() ?? '',
  124. if (isPercentage != null) 'isPercentage': isPercentage,
  125. if (positionType != null) 'positionType': positionType,
  126. },
  127. options: _form,
  128. );
  129. }
  130. // ── 平仓 ─────────────────────────────────────────────────────
  131. Future<void> closeMarket({
  132. required String positionId,
  133. required double volume,
  134. }) async {
  135. await _dio.post<dynamic>(
  136. 'swap/order/close',
  137. data: {
  138. 'positionId': positionId,
  139. 'volume': volume.toString(),
  140. 'type': 0,
  141. 'entrustPrice': '0',
  142. 'triggerPrice': '0',
  143. },
  144. options: _form,
  145. );
  146. }
  147. Future<void> closeLimit({
  148. required String positionId,
  149. required double price,
  150. required double volume,
  151. }) async {
  152. await _dio.post<dynamic>(
  153. 'swap/order/close',
  154. data: {
  155. 'positionId': positionId,
  156. 'entrustPrice': price.toString(),
  157. 'volume': volume.toString(),
  158. 'type': 1,
  159. 'triggerPrice': '0',
  160. },
  161. options: _form,
  162. );
  163. }
  164. Future<void> closeAllPositions() async {
  165. await _dio.post<dynamic>('swap/order/close-all-coins', options: _form);
  166. }
  167. // entrustPrice=0 → 计划市价;>0 → 计划限价
  168. Future<void> closeConditional({
  169. required String positionId,
  170. required double triggerPrice,
  171. required double volume,
  172. double entrustPrice = 0,
  173. }) async {
  174. await _dio.post<dynamic>(
  175. 'swap/order/close',
  176. data: {
  177. 'positionId': positionId,
  178. 'entrustPrice': entrustPrice.toString(),
  179. 'volume': volume.toString(),
  180. 'type': 2,
  181. 'triggerPrice': triggerPrice.toString(),
  182. },
  183. options: _form,
  184. );
  185. }
  186. Future<void> reverseOpenPosition(String positionId) async {
  187. await _dio.post<dynamic>(
  188. 'swap/order/close-and-open',
  189. data: {'positionId': positionId},
  190. options: _form,
  191. );
  192. }
  193. // ── 撤单 ─────────────────────────────────────────────────────
  194. Future<void> cancelOrder(int entrustId) async {
  195. await _dio.post<dynamic>(
  196. 'swap/order/cancel',
  197. data: {'entrustId': entrustId},
  198. options: _form,
  199. );
  200. }
  201. // entrustIds 以 JSON 数组字符串传递,如 "[1,2,3]"
  202. Future<void> cancelOrdersByIds(List<int> entrustIds) async {
  203. await _dio.post<dynamic>(
  204. 'swap/order/cancel-by-ids',
  205. data: {'entrustIds': entrustIds.toString()},
  206. options: _form,
  207. );
  208. }
  209. // ── 止盈止损 ─────────────────────────────────────────────────
  210. // 首次设置(无 cutEntrustId)
  211. Future<void> setTpsl({
  212. required String positionId,
  213. double? profitPrice,
  214. double? lossPrice,
  215. }) async {
  216. await _dio.post<dynamic>(
  217. 'swap/order/cut-position',
  218. data: {
  219. 'positionId': positionId,
  220. 'profitPrice': profitPrice?.toString() ?? '0',
  221. 'profitEntrustPrice': '0.0',
  222. 'lossPrice': lossPrice?.toString() ?? '0',
  223. 'lossEntrustPrice': '0.0',
  224. },
  225. options: _form,
  226. );
  227. }
  228. // 修改已有止盈止损委托(有 cutEntrustId)
  229. Future<void> modifyCutOrder({
  230. required int entrustId,
  231. double? profitPrice,
  232. double? lossPrice,
  233. }) async {
  234. await _dio.post<dynamic>(
  235. 'swap/order/modify-cut',
  236. data: {
  237. 'entrustId': entrustId,
  238. 'profitPrice': profitPrice?.toString() ?? '0',
  239. 'lossPrice': lossPrice?.toString() ?? '0',
  240. },
  241. options: _form,
  242. );
  243. }
  244. // ── 历史记录 ─────────────────────────────────────────────────
  245. Future<PageResult> getPositionHistory({
  246. int pageNo = 1,
  247. int pageSize = 10,
  248. }) async {
  249. final resp = await _dio.get<Map<String, dynamic>>(
  250. 'swap/position/history',
  251. queryParameters: {'pageNo': pageNo, 'pageSize': pageSize},
  252. );
  253. return _extractPage(resp.data, pageSize);
  254. }
  255. Future<PageResult> getOrderHistoryAll({
  256. int pageNo = 1,
  257. int pageSize = 10,
  258. }) async {
  259. final resp = await _dio.get<Map<String, dynamic>>(
  260. 'swap/order/history-all',
  261. queryParameters: {'pageNo': pageNo, 'pageSize': pageSize},
  262. );
  263. return _extractPage(resp.data, pageSize);
  264. }
  265. }