feat(okx): 支持多账号WebSocket连接管理
- 重构AccountWs、InstrumentsWs、OrderInfoWs和PositionsWs类,使用双层Map结构支持多账号数据存储
- 修改CaoZuoService接口和实现,增加accountName参数以区分不同账号的操作
- 更新OkxQuantWebSocketClient类,支持注入账号信息并传递给各个处理方法
- 新增OkxWebSocketClientManager类,用于管理多个账号的WebSocket客户端实例
- 调整ApiMessageServiceImpl类,根据账号名称动态获取对应的API密钥信息
- 修改ExchangeInfoEnum枚举,支持多个模拟盘账号配置
- 更新LoginWs类,支持根据不同账号信息进行WebSocket登录
- 在OkxWebSocketClientMain中引入Spring容器管理,通过OkxWebSocketClientManager统一管理客户端实例
12 files modified
1 files added
| | |
| | | * @author Administrator |
| | | */ |
| | | @Slf4j |
| | | @Component |
| | | @ConditionalOnProperty(prefix = "app", name = "quant", havingValue = "true") |
| | | public class OkxQuantWebSocketClient { |
| | | @Autowired |
| | | private WangGeService wangGeService; |
| | | @Autowired |
| | | private CaoZuoService caoZuoService; |
| | | @Autowired |
| | | private RedisUtils redisUtils; |
| | | private final WangGeService wangGeService; |
| | | private final CaoZuoService caoZuoService; |
| | | private final RedisUtils redisUtils; |
| | | private final ExchangeInfoEnum account; |
| | | |
| | | private WebSocketClient webSocketClient; |
| | | private ScheduledExecutorService heartbeatExecutor; |
| | |
| | | // 连接状态标志 |
| | | private final AtomicBoolean isConnected = new AtomicBoolean(false); |
| | | private final AtomicBoolean isConnecting = new AtomicBoolean(false); |
| | | |
| | | public OkxQuantWebSocketClient(ExchangeInfoEnum account, WangGeService wangGeService, |
| | | CaoZuoService caoZuoService, RedisUtils redisUtils) { |
| | | this.account = account; |
| | | this.wangGeService = wangGeService; |
| | | this.caoZuoService = caoZuoService; |
| | | this.redisUtils = redisUtils; |
| | | } |
| | | |
| | | private static final String WS_URL_MONIPAN = "wss://wspap.okx.com:8443/ws/v5/private"; |
| | | private static final String WS_URL_SHIPAN = "wss://ws.okx.com:8443/ws/v5/private"; |
| | |
| | | } |
| | | |
| | | try { |
| | | InstrumentsWs.handleEvent(); |
| | | InstrumentsWs.handleEvent(account.name()); |
| | | wangGeService.initWangGe(); |
| | | SSLConfig.configureSSL(); |
| | | System.setProperty("https.protocols", "TLSv1.2,TLSv1.3"); |
| | | String WS_URL = WS_URL_MONIPAN; |
| | | if (ExchangeInfoEnum.OKX_UAT.isAccountType()){ |
| | | if (account.isAccountType()){ |
| | | WS_URL = WS_URL_SHIPAN; |
| | | } |
| | | URI uri = new URI(WS_URL); |
| | |
| | | // 棜查应用是否正在关闭 |
| | | if (!sharedExecutor.isShutdown()) { |
| | | resetHeartbeatTimer(); |
| | | websocketLogin(); |
| | | websocketLogin(account); |
| | | } else { |
| | | log.warn("应用正在关闭,忽略WebSocket连接成功回调"); |
| | | } |
| | |
| | | } |
| | | } |
| | | |
| | | private void websocketLogin() { |
| | | LoginWs.websocketLogin(webSocketClient); |
| | | private void websocketLogin(ExchangeInfoEnum account) { |
| | | LoginWs.websocketLogin(webSocketClient, account); |
| | | } |
| | | |
| | | private void subscribeBalanceAndPositionChannel(String option) { |
| | |
| | | private void handleWebSocketMessage(String message) { |
| | | try { |
| | | if ("pong".equals(message)) { |
| | | log.debug("收到心跳响应"); |
| | | log.debug("{}: 收到心跳响应", account.name()); |
| | | cancelPongTimeout(); |
| | | return; |
| | | } |
| | |
| | | String code = response.getString("code"); |
| | | if ("0".equals(code)) { |
| | | String connId = response.getString("connId"); |
| | | log.info("WebSocket登录成功, connId: {}", connId); |
| | | log.info("{}: WebSocket登录成功, connId: {}", account.name(), connId); |
| | | subscribeAccountChannel(SUBSCRIBE); |
| | | subscribeOrderInfoChannel(SUBSCRIBE); |
| | | subscribePositionChannel(SUBSCRIBE); |
| | | } else { |
| | | log.error("WebSocket登录失败, code: {}, msg: {}", code, response.getString("msg")); |
| | | log.error("{}: WebSocket登录失败, code: {}, msg: {}", account.name(), code, response.getString("msg")); |
| | | } |
| | | } else if ("subscribe".equals(event)) { |
| | | subscribeEvent(response); |
| | | } else if ("error".equals(event)) { |
| | | log.error("订阅错误: code={}, msg={}", |
| | | response.getString("code"), response.getString("msg")); |
| | | log.error("{}: 订阅错误: code={}, msg={}", |
| | | account.name(), response.getString("code"), response.getString("msg")); |
| | | } else if ("channel-conn-count".equals(event)) { |
| | | log.info("连接限制更新: channel={}, connCount={}", |
| | | response.getString("channel"), response.getString("connCount")); |
| | | log.info("{}: 连接限制更新: channel={}, connCount={}", |
| | | account.name(), response.getString("channel"), response.getString("connCount")); |
| | | } else { |
| | | processPushData(response); |
| | | } |
| | | } catch (Exception e) { |
| | | log.error("处理WebSocket消息失败: {}", message, e); |
| | | log.error("{}: 处理WebSocket消息失败: {}", account.name(), message, e); |
| | | } |
| | | } |
| | | |
| | |
| | | return; |
| | | } |
| | | if (OrderInfoWs.ORDERINFOWS_CHANNEL.equals(channel)) { |
| | | OrderInfoWs.initEvent(response); |
| | | OrderInfoWs.initEvent(response, account.name()); |
| | | } |
| | | if (AccountWs.ACCOUNTWS_CHANNEL.equals(channel)) { |
| | | AccountWs.initEvent(response); |
| | | AccountWs.initEvent(response, account.name()); |
| | | } |
| | | if (PositionsWs.POSITIONSWS_CHANNEL.equals(channel)) { |
| | | PositionsWs.initEvent(response); |
| | | PositionsWs.initEvent(response, account.name()); |
| | | } |
| | | } |
| | | |
| | |
| | | if (TradeOrderWs.ORDERWS_CHANNEL.equals(op)) { |
| | | // 直接使用Object类型接收,避免强制类型转换 |
| | | Object data = response.get("data"); |
| | | log.info("收到下单推送结果: {}", JSON.toJSONString(data)); |
| | | log.info("{}: 收到下单推送结果: {}", account.name(), JSON.toJSONString(data)); |
| | | return; |
| | | } |
| | | } |
| | | JSONObject arg = response.getJSONObject("arg"); |
| | | if (arg == null) { |
| | | log.warn("无效的推送数据,缺少 'arg' 字段 :{}",response); |
| | | log.warn("{}: 无效的推送数据,缺少 'arg' 字段 :{}", account.name(), response); |
| | | return; |
| | | } |
| | | |
| | | String channel = arg.getString("channel"); |
| | | if (channel == null) { |
| | | log.warn("无效的推送数据,缺少 'channel' 字段{}",response); |
| | | log.warn("{}: 无效的推送数据,缺少 'channel' 字段{}", account.name(), response); |
| | | return; |
| | | } |
| | | |
| | | // 注意:当前实现中,OrderInfoWs等类使用静态Map存储数据 |
| | | // 这会导致多账号之间的数据冲突。需要进一步修改这些类的设计,让数据存储与特定账号关联 |
| | | if (OrderInfoWs.ORDERINFOWS_CHANNEL.equals(channel)) { |
| | | OrderInfoWs.handleEvent(response, redisUtils); |
| | | OrderInfoWs.handleEvent(response, redisUtils, account.name()); |
| | | }else if (AccountWs.ACCOUNTWS_CHANNEL.equals(channel)) { |
| | | AccountWs.handleEvent(response); |
| | | String side = caoZuoService.caoZuo(); |
| | | TradeOrderWs.orderEvent(webSocketClient, side); |
| | | AccountWs.handleEvent(response, account.name()); |
| | | String side = caoZuoService.caoZuo(account.name()); |
| | | TradeOrderWs.orderEvent(webSocketClient, side, account.name()); |
| | | } else if (PositionsWs.POSITIONSWS_CHANNEL.equals(channel)) { |
| | | PositionsWs.handleEvent(response); |
| | | PositionsWs.handleEvent(response, account.name()); |
| | | } else if (BalanceAndPositionWs.CHANNEL_NAME.equals(channel)) { |
| | | BalanceAndPositionWs.handleEvent(response); |
| | | } |
| | |
| | | package com.xcong.excoin.modules.okxNewPrice; |
| | | |
| | | import com.xcong.excoin.modules.okxNewPrice.celue.CaoZuoServiceImpl; |
| | | import com.xcong.excoin.modules.okxNewPrice.wangge.WangGeServiceImpl; |
| | | import com.xcong.excoin.utils.RedisUtils; |
| | | import org.springframework.context.ApplicationContext; |
| | | import org.springframework.context.support.ClassPathXmlApplicationContext; |
| | | |
| | | public class OkxWebSocketClientMain { |
| | | public static void main(String[] args) throws InterruptedException { |
| | | OkxQuantWebSocketClient clientV2 = new OkxQuantWebSocketClient(); |
| | | // 手动调用初始化方法 |
| | | clientV2.init(); |
| | | // 使用Spring上下文初始化管理器 |
| | | ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); |
| | | OkxWebSocketClientManager manager = context.getBean(OkxWebSocketClientManager.class); |
| | | |
| | | // 运行一段时间以观察结果 |
| | | Thread.sleep(1200000000L); // 运行一小时 |
| | | |
| | | // 关闭连接 |
| | | clientV2.destroy(); |
| | | |
| | | manager.destroy(); |
| | | } |
| | | } |
| New file |
| | |
| | | package com.xcong.excoin.modules.okxNewPrice; |
| | | |
| | | import com.xcong.excoin.modules.okxNewPrice.celue.CaoZuoService; |
| | | import com.xcong.excoin.modules.okxNewPrice.okxWs.enums.ExchangeInfoEnum; |
| | | import com.xcong.excoin.modules.okxNewPrice.wangge.WangGeService; |
| | | import com.xcong.excoin.utils.RedisUtils; |
| | | import lombok.extern.slf4j.Slf4j; |
| | | import org.springframework.beans.factory.annotation.Autowired; |
| | | import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; |
| | | import org.springframework.stereotype.Component; |
| | | |
| | | import javax.annotation.PostConstruct; |
| | | import javax.annotation.PreDestroy; |
| | | import java.util.*; |
| | | import java.util.concurrent.ConcurrentHashMap; |
| | | |
| | | /** |
| | | * 管理多个OKX WebSocket客户端实例,每个实例对应一个账号 |
| | | */ |
| | | @Slf4j |
| | | @Component |
| | | @ConditionalOnProperty(prefix = "app", name = "quant", havingValue = "true") |
| | | public class OkxWebSocketClientManager { |
| | | @Autowired |
| | | private WangGeService wangGeService; |
| | | @Autowired |
| | | private CaoZuoService caoZuoService; |
| | | @Autowired |
| | | private RedisUtils redisUtils; |
| | | |
| | | // 存储所有WebSocket客户端实例,key为账号类型名称 |
| | | private final Map<String, OkxQuantWebSocketClient> clientMap = new ConcurrentHashMap<>(); |
| | | |
| | | /** |
| | | * 初始化方法,在Spring Bean构造完成后执行 |
| | | * 创建并初始化所有账号的WebSocket客户端实例 |
| | | */ |
| | | @PostConstruct |
| | | public void init() { |
| | | log.info("开始初始化OkxWebSocketClientManager"); |
| | | |
| | | // 获取所有ExchangeInfoEnum枚举值 |
| | | ExchangeInfoEnum[] accounts = ExchangeInfoEnum.values(); |
| | | |
| | | // 为每个账号创建一个WebSocket客户端实例 |
| | | for (ExchangeInfoEnum account : accounts) { |
| | | try { |
| | | OkxQuantWebSocketClient client = new OkxQuantWebSocketClient(account, wangGeService, caoZuoService, redisUtils); |
| | | clientMap.put(account.name(), client); |
| | | client.init(); |
| | | log.info("已初始化账号 {} 的WebSocket客户端", account.name()); |
| | | } catch (Exception e) { |
| | | log.error("初始化账号 {} 的WebSocket客户端失败", account.name(), e); |
| | | } |
| | | } |
| | | |
| | | log.info("OkxWebSocketClientManager初始化完成"); |
| | | } |
| | | |
| | | /** |
| | | * 销毁方法,在Spring Bean销毁前执行 |
| | | * 关闭所有WebSocket客户端连接和相关资源 |
| | | */ |
| | | @PreDestroy |
| | | public void destroy() { |
| | | log.info("开始销毁OkxWebSocketClientManager"); |
| | | |
| | | // 关闭所有客户端实例 |
| | | for (Map.Entry<String, OkxQuantWebSocketClient> entry : clientMap.entrySet()) { |
| | | try { |
| | | OkxQuantWebSocketClient client = entry.getValue(); |
| | | client.destroy(); |
| | | log.info("已销毁账号 {} 的WebSocket客户端", entry.getKey()); |
| | | } catch (Exception e) { |
| | | log.error("销毁账号 {} 的WebSocket客户端失败", entry.getKey(), e); |
| | | } |
| | | } |
| | | |
| | | // 清空客户端映射 |
| | | clientMap.clear(); |
| | | |
| | | log.info("OkxWebSocketClientManager销毁完成"); |
| | | } |
| | | |
| | | /** |
| | | * 获取指定账号的WebSocket客户端实例 |
| | | * @param accountName 账号类型名称 |
| | | * @return WebSocket客户端实例 |
| | | */ |
| | | public OkxQuantWebSocketClient getClient(String accountName) { |
| | | return clientMap.get(accountName); |
| | | } |
| | | |
| | | /** |
| | | * 获取所有WebSocket客户端实例 |
| | | * @return 所有客户端实例的集合 |
| | | */ |
| | | public Collection<OkxQuantWebSocketClient> getAllClients() { |
| | | return clientMap.values(); |
| | | } |
| | | } |
| | |
| | | */ |
| | | public interface CaoZuoService { |
| | | |
| | | String caoZuo(); |
| | | String caoZuo(String accountName); |
| | | } |
| | |
| | | * @return 返回操作类型字符串(如买入BUY、卖出SELL等),如果无有效操作则返回null |
| | | */ |
| | | @Override |
| | | public String caoZuo() { |
| | | public String caoZuo(String accountName) { |
| | | log.info("开始执行操作CaoZuoServiceImpl......"); |
| | | String accountReadyState = AccountWs.ACCOUNTWSMAP.get(CoinEnums.READY_STATE.name()); |
| | | String accountReadyState = AccountWs.getAccountMap(accountName).get(CoinEnums.READY_STATE.name()); |
| | | if (!CoinEnums.READY_STATE_YES.getCode().equals(accountReadyState)) { |
| | | log.info("账户通道未就绪,取消发送"); |
| | | return null; |
| | | } |
| | | BigDecimal positionsReadyState = PositionsWs.POSITIONSWSMAP.get(CoinEnums.READY_STATE.name()) == null |
| | | ? BigDecimal.ZERO : PositionsWs.POSITIONSWSMAP.get(CoinEnums.READY_STATE.name()); |
| | | BigDecimal positionsReadyState = PositionsWs.getAccountMap(accountName).get(CoinEnums.READY_STATE.name()) == null |
| | | ? BigDecimal.ZERO : PositionsWs.getAccountMap(accountName).get(CoinEnums.READY_STATE.name()); |
| | | if (WsMapBuild.parseBigDecimalSafe(CoinEnums.READY_STATE_YES.getCode()).compareTo(positionsReadyState) != 0) { |
| | | log.info("仓位通道未就绪,取消发送"); |
| | | return null; |
| | | } |
| | | // 系统设置的开关,等于冷静中,则代表不开仓 |
| | | String outStr = InstrumentsWs.INSTRUMENTSWSMAP.get(CoinEnums.OUT.name()); |
| | | String outStr = InstrumentsWs.getAccountMap(accountName).get(CoinEnums.OUT.name()); |
| | | if (OrderParamEnums.OUT_YES.getValue().equals(outStr)){ |
| | | log.error("冷静中,不允许下单......"); |
| | | return null; |
| | | } |
| | | BigDecimal cashBal = WsMapBuild.parseBigDecimalSafe(AccountWs.ACCOUNTWSMAP.get("cashBal")); |
| | | BigDecimal cashBal = WsMapBuild.parseBigDecimalSafe(AccountWs.getAccountMap(accountName).get("cashBal")); |
| | | |
| | | // 判断账户余额是否充足 |
| | | if (cashBal.compareTo(BigDecimal.ZERO) <= 0){ |
| | |
| | | * 判断止损抗压 |
| | | */ |
| | | // 实际亏损金额 |
| | | BigDecimal realKuiSunAmount = WsMapBuild.parseBigDecimalSafe(AccountWs.ACCOUNTWSMAP.get("upl")); |
| | | BigDecimal realKuiSunAmount = WsMapBuild.parseBigDecimalSafe(AccountWs.getAccountMap(accountName).get("upl")); |
| | | log.info("未实现盈亏: {}", realKuiSunAmount); |
| | | String zhiSunPercent = InstrumentsWs.INSTRUMENTSWSMAP.get(CoinEnums.ZHI_SUN.name()); |
| | | String zhiSunPercent = InstrumentsWs.getAccountMap(accountName).get(CoinEnums.ZHI_SUN.name()); |
| | | BigDecimal zhiSunAmount = cashBal.multiply(new BigDecimal(zhiSunPercent)); |
| | | log.info("预期亏损金额: {}", zhiSunAmount); |
| | | String kangYaPercent = InstrumentsWs.INSTRUMENTSWSMAP.get(CoinEnums.KANG_CANG.name()); |
| | | String kangYaPercent = InstrumentsWs.getAccountMap(accountName).get(CoinEnums.KANG_CANG.name()); |
| | | BigDecimal kangYaAmount = cashBal.multiply(new BigDecimal(kangYaPercent)); |
| | | log.info("预期抗仓金额: {}", kangYaAmount); |
| | | |
| | |
| | | // 账户预期亏损金额比这个还小时,立即止损 |
| | | if (realKuiSunAmount.compareTo(zhiSunAmount) > 0){ |
| | | log.error("账户冷静止损......"); |
| | | WsMapBuild.saveStringToMap(InstrumentsWs.INSTRUMENTSWSMAP, CoinEnums.OUT.name(), OrderParamEnums.OUT_YES.getValue()); |
| | | WsMapBuild.saveStringToMap(InstrumentsWs.getAccountMap(accountName), CoinEnums.OUT.name(), OrderParamEnums.OUT_YES.getValue()); |
| | | return OrderParamEnums.OUT.getValue(); |
| | | } |
| | | // 判断抗压 |
| | |
| | | } |
| | | } |
| | | |
| | | if (PositionsWs.POSITIONSWSMAP.get("pos") == null){ |
| | | if (PositionsWs.getAccountMap(accountName).get("pos") == null){ |
| | | log.error("没有获取到持仓信息,等待初始化......"); |
| | | return null; |
| | | } |
| | | BigDecimal pos = PositionsWs.POSITIONSWSMAP.get("pos"); |
| | | BigDecimal pos = PositionsWs.getAccountMap(accountName).get("pos"); |
| | | if (BigDecimal.ZERO.compareTo( pos) >= 0) { |
| | | log.error("持仓数量为零,进行初始化订单"); |
| | | return OrderParamEnums.INIT.getValue(); |
| | | } |
| | | // 判断是否保证金超标 |
| | | if (PositionsWs.POSITIONSWSMAP.get("imr") == null){ |
| | | if (PositionsWs.getAccountMap(accountName).get("imr") == null){ |
| | | log.error("没有获取到持仓信息,等待初始化......"); |
| | | return null; |
| | | } |
| | | BigDecimal ordFrozImr = PositionsWs.POSITIONSWSMAP.get("imr"); |
| | | BigDecimal totalOrderUsdt = WsMapBuild.parseBigDecimalSafe(AccountWs.ACCOUNTWSMAP.get(CoinEnums.TOTAL_ORDER_USDT.name())); |
| | | BigDecimal ordFrozImr = PositionsWs.getAccountMap(accountName).get("imr"); |
| | | BigDecimal totalOrderUsdt = WsMapBuild.parseBigDecimalSafe(AccountWs.getAccountMap(accountName).get(CoinEnums.TOTAL_ORDER_USDT.name())); |
| | | if (ordFrozImr.compareTo(totalOrderUsdt) >= 0){ |
| | | log.error("已满仓......"); |
| | | return OrderParamEnums.HOLDING.getValue(); |
| | |
| | | |
| | | try { |
| | | // 获取标记价格和平均持仓价格 |
| | | BigDecimal markPx = PositionsWs.POSITIONSWSMAP.get("markPx"); |
| | | BigDecimal avgPx = PositionsWs.POSITIONSWSMAP.get("avgPx"); |
| | | BigDecimal markPx = PositionsWs.getAccountMap(accountName).get("markPx"); |
| | | BigDecimal avgPx = PositionsWs.getAccountMap(accountName).get("avgPx"); |
| | | log.info("开仓价格: {}, 当前价格:{},匹配队列中......", avgPx, markPx); |
| | | |
| | | // 初始化网格队列 |
| | |
| | | PriorityBlockingQueue<AscBigDecimal> queuePingCang = wangGeService.initPingCang(avgPx, queueAsc); |
| | | |
| | | // 处理订单价格在队列中的情况 |
| | | String orderPrice = OrderInfoWs.ORDERINFOWSMAP.get("orderPrice"); |
| | | String orderPrice = OrderInfoWs.getAccountMap(accountName).get("orderPrice"); |
| | | handleOrderPriceInQueues(orderPrice, queueKaiCang, queuePingCang); |
| | | // 判断是加仓还是减仓 |
| | | if (avgPx.compareTo(markPx) > 0) { |
| | |
| | | DescBigDecimal kaiCang = queueKaiCang.peek(); |
| | | if (kaiCang != null && markPx.compareTo(kaiCang.getValue()) <= 0 && avgPx.compareTo(kaiCang.getValue()) >= 0) { |
| | | log.info("开始加仓...开仓队列价格大于当前价格{}>{}", kaiCang.getValue(), markPx); |
| | | WsMapBuild.saveStringToMap(OrderInfoWs.ORDERINFOWSMAP, "orderPrice", String.valueOf(markPx)); |
| | | boolean buyCntTimeFlag = buyCntTimeEvent(avgPx, markPx); |
| | | WsMapBuild.saveStringToMap(OrderInfoWs.getAccountMap(accountName), "orderPrice", String.valueOf(markPx)); |
| | | boolean buyCntTimeFlag = buyCntTimeEvent(accountName, avgPx, markPx); |
| | | if (buyCntTimeFlag){ |
| | | log.info("加仓参数准备成功......"); |
| | | return OrderParamEnums.BUY.getValue(); |
| | |
| | | if (pingCang != null && markPx.compareTo(pingCang.getValue()) >= 0 && avgPx.compareTo(pingCang.getValue()) < 0) { |
| | | log.info("开始减仓...平仓队列价格小于当前价格{}<={}", pingCang.getValue(), markPx); |
| | | // 手续费 |
| | | BigDecimal feeValue = PositionsWs.POSITIONSWSMAP.get("fee").multiply(new BigDecimal("2")); |
| | | BigDecimal feeValue = PositionsWs.getAccountMap(accountName).get("fee").multiply(new BigDecimal("2")); |
| | | //未实现收益 |
| | | BigDecimal uplValue = PositionsWs.POSITIONSWSMAP.get("upl"); |
| | | BigDecimal uplValue = PositionsWs.getAccountMap(accountName).get("upl"); |
| | | //已实现收益 |
| | | BigDecimal realizedPnlValue = PositionsWs.POSITIONSWSMAP.get("realizedPnl"); |
| | | BigDecimal realizedPnlValue = PositionsWs.getAccountMap(accountName).get("realizedPnl"); |
| | | realizedPnlValue = realizedPnlValue.add(feeValue); |
| | | |
| | | //持仓保证金 |
| | | BigDecimal imr = PositionsWs.POSITIONSWSMAP.get("imr"); |
| | | String pingCangImr = InstrumentsWs.INSTRUMENTSWSMAP.get(CoinEnums.PING_CANG_SHOUYI.name()); |
| | | BigDecimal imr = PositionsWs.getAccountMap(accountName).get("imr"); |
| | | String pingCangImr = InstrumentsWs.getAccountMap(accountName).get(CoinEnums.PING_CANG_SHOUYI.name()); |
| | | BigDecimal imrValue = imr.multiply(new BigDecimal(pingCangImr)); |
| | | |
| | | if (realizedPnlValue.compareTo(BigDecimal.ZERO) <= 0) { |
| | | BigDecimal realizedPnlValueZheng = realizedPnlValue.multiply(new BigDecimal("-1")); |
| | | if (uplValue.compareTo(realizedPnlValue) > 0 && uplValue.compareTo(imrValue.add(realizedPnlValueZheng)) >= 0) { |
| | | log.info("当前未实现盈亏:{}大于预计收益>{},赚钱咯", uplValue, imrValue.add(realizedPnlValueZheng)); |
| | | WsMapBuild.saveStringToMap(OrderInfoWs.ORDERINFOWSMAP, "orderPrice", String.valueOf(markPx)); |
| | | WsMapBuild.saveStringToMap(OrderInfoWs.getAccountMap(accountName), "orderPrice", String.valueOf(markPx)); |
| | | return OrderParamEnums.SELL.getValue(); |
| | | }else{ |
| | | log.info("当前未实现盈亏:{}没有大于预计收益>{},钱在路上了", uplValue, imrValue.add(realizedPnlValueZheng)); |
| | |
| | | } |
| | | }else { |
| | | if (uplValue.compareTo(imrValue.add(feeValue)) >= 0) { |
| | | WsMapBuild.saveStringToMap(OrderInfoWs.ORDERINFOWSMAP, "orderPrice", String.valueOf(markPx)); |
| | | WsMapBuild.saveStringToMap(OrderInfoWs.getAccountMap(accountName), "orderPrice", String.valueOf(markPx)); |
| | | log.info("当前未实现盈亏:{}大于预计收益>{},赚钱咯", uplValue, imrValue.add(feeValue)); |
| | | return OrderParamEnums.SELL.getValue(); |
| | | }else{ |
| | |
| | | } |
| | | } |
| | | |
| | | private boolean buyCntTimeEvent(BigDecimal avgPx, BigDecimal markPx){ |
| | | private boolean buyCntTimeEvent(String accountName, BigDecimal avgPx, BigDecimal markPx){ |
| | | //判断当前价格和开仓价格直接间隔除以间距,取整,获取的数量是否大于等于0,如果大于0,则下单基础张数*倍数 |
| | | String buyCntTime = InstrumentsWs.INSTRUMENTSWSMAP.get(CoinEnums.BUY_CNT_TIME.name()); |
| | | String buyCntTime = InstrumentsWs.getAccountMap(accountName).get(CoinEnums.BUY_CNT_TIME.name()); |
| | | BigDecimal subtract = avgPx.subtract(markPx); |
| | | BigDecimal divide = subtract.divide(new BigDecimal(buyCntTime), 0, RoundingMode.DOWN); |
| | | if (divide.compareTo(BigDecimal.ZERO) <= 0){ |
| | | log.warn("加仓次数间隔时间小于0,不加仓"); |
| | | return false; |
| | | } |
| | | return WsMapBuild.saveStringToMap(TradeOrderWs.TRADEORDERWSMAP, "buyCntTime",String.valueOf(divide)); |
| | | return WsMapBuild.saveStringToMap(TradeOrderWs.getAccountMap(accountName), "buyCntTime",String.valueOf(divide)); |
| | | } |
| | | |
| | | /** |
| | |
| | | @Slf4j |
| | | public class AccountWs { |
| | | |
| | | public static final Map<String,String> ACCOUNTWSMAP = new ConcurrentHashMap<>(); |
| | | // 使用双层Map,第一层key为账号名称,第二层key为数据key |
| | | private static final Map<String, Map<String, String>> ACCOUNTWSMAP = new ConcurrentHashMap<>(); |
| | | |
| | | // 获取指定账号的Map,如果不存在则创建 |
| | | public static Map<String, String> getAccountMap(String accountName) { |
| | | return ACCOUNTWSMAP.computeIfAbsent(accountName, k -> new ConcurrentHashMap<>()); |
| | | } |
| | | /** |
| | | * 账户频道名称常量 |
| | | */ |
| | |
| | | } |
| | | } |
| | | |
| | | public static void initEvent(JSONObject response) { |
| | | public static void initEvent(JSONObject response, String accountName) { |
| | | // log.info("订阅成功: {}", response.getJSONObject("arg")); |
| | | JSONObject arg = response.getJSONObject("arg"); |
| | | initParam(arg); |
| | | initParam(arg, accountName); |
| | | } |
| | | |
| | | /** |
| | | * 处理账户频道推送的数据 |
| | | * |
| | | * @param response 推送的 JSON 数据对象 |
| | | * @param accountName 账号名称 |
| | | */ |
| | | public static void handleEvent(JSONObject response) { |
| | | public static void handleEvent(JSONObject response, String accountName) { |
| | | |
| | | |
| | | // log.info("开始执行AccountWs......{}",ACCOUNTWS_CHANNEL); |
| | |
| | | |
| | | for (int j = 0; j < detailsArray.size(); j++) { |
| | | JSONObject detail = detailsArray.getJSONObject(j); |
| | | initParam(detail); |
| | | initParam(detail, accountName); |
| | | } |
| | | } catch (Exception innerEx) { |
| | | log.warn("处理账户频道数据失败", innerEx); |
| | |
| | | public static final String cashBalKey = "cashBal"; |
| | | public static final String eqKey = "eq"; |
| | | public static final String uplKey = "upl"; |
| | | private static void initParam(JSONObject detail) { |
| | | private static void initParam(JSONObject detail, String accountName) { |
| | | Map<String, String> accountMap = getAccountMap(accountName); |
| | | |
| | | String ccy = WsMapBuild.parseStringSafe( detail.getString(ccyKey)); |
| | | WsMapBuild.saveStringToMap(ACCOUNTWSMAP, ccyKey, ccy); |
| | | WsMapBuild.saveStringToMap(accountMap, ccyKey, ccy); |
| | | |
| | | String availBal = WsMapBuild.parseStringSafe(detail.getString(availBalKey)); |
| | | WsMapBuild.saveStringToMap(ACCOUNTWSMAP, availBalKey, availBal); |
| | | WsMapBuild.saveStringToMap(accountMap, availBalKey, availBal); |
| | | |
| | | String cashBal = WsMapBuild.parseStringSafe(detail.getString(cashBalKey)); |
| | | WsMapBuild.saveStringToMap(ACCOUNTWSMAP, cashBalKey, cashBal); |
| | | WsMapBuild.saveStringToMap(accountMap, cashBalKey, cashBal); |
| | | |
| | | String eq = WsMapBuild.parseStringSafe(detail.getString(eqKey)); |
| | | WsMapBuild.saveStringToMap(ACCOUNTWSMAP, eqKey, eq); |
| | | WsMapBuild.saveStringToMap(accountMap, eqKey, eq); |
| | | |
| | | String upl = WsMapBuild.parseStringSafe(detail.getString(uplKey)); |
| | | WsMapBuild.saveStringToMap(ACCOUNTWSMAP, uplKey, upl); |
| | | WsMapBuild.saveStringToMap(accountMap, uplKey, upl); |
| | | |
| | | BigDecimal cashBalDecimal = WsMapBuild.parseBigDecimalSafe(cashBal); |
| | | // 根据可用余额计算下单总保证金 |
| | | String total_order_usdtpecent = InstrumentsWs.INSTRUMENTSWSMAP.get(CoinEnums.TOTAL_ORDER_USDTPECENT.name()); |
| | | String total_order_usdtpecent = InstrumentsWs.getAccountMap(accountName).get(CoinEnums.TOTAL_ORDER_USDTPECENT.name()); |
| | | BigDecimal total_order_usdt_factor = WsMapBuild.parseBigDecimalSafe(total_order_usdtpecent); |
| | | BigDecimal totalOrderUsdt = cashBalDecimal.multiply(total_order_usdt_factor).setScale(2, RoundingMode.DOWN); |
| | | WsMapBuild.saveStringToMap(ACCOUNTWSMAP, CoinEnums.TOTAL_ORDER_USDT.name(), String.valueOf(totalOrderUsdt)); |
| | | WsMapBuild.saveStringToMap(accountMap, CoinEnums.TOTAL_ORDER_USDT.name(), String.valueOf(totalOrderUsdt)); |
| | | |
| | | WsMapBuild.saveStringToMap(ACCOUNTWSMAP, CoinEnums.READY_STATE.name(), CoinEnums.READY_STATE_YES.getCode()); |
| | | WsMapBuild.saveStringToMap(accountMap, CoinEnums.READY_STATE.name(), CoinEnums.READY_STATE_YES.getCode()); |
| | | |
| | | log.info( |
| | | "账户详情-币种: {}, 可用余额: {}, 现金余额: {}, 余额: {}, 全仓未实现盈亏: {}, 下单总保证金: {}", |
| | | ccy, availBal, cashBal, eq, upl, totalOrderUsdt |
| | | "{}: 账户详情-币种: {}, 可用余额: {}, 现金余额: {}, 余额: {}, 全仓未实现盈亏: {}, 下单总保证金: {}", |
| | | accountName, ccy, availBal, cashBal, eq, upl, totalOrderUsdt |
| | | ); |
| | | } |
| | | } |
| | |
| | | @Slf4j |
| | | public class InstrumentsWs { |
| | | |
| | | public static final Map<String, String> INSTRUMENTSWSMAP = new ConcurrentHashMap<>(); |
| | | public static final Map<String, Map<String, String>> INSTRUMENTSWSMAP = new ConcurrentHashMap<>(); |
| | | |
| | | public static final String INSTRUMENTSWS_CHANNEL = "instruments"; |
| | | |
| | | public static void handleEvent() { |
| | | public static Map<String, String> getAccountMap(String accountName) { |
| | | return INSTRUMENTSWSMAP.computeIfAbsent(accountName, k -> new ConcurrentHashMap<>()); |
| | | } |
| | | |
| | | public static void handleEvent(String accountName) { |
| | | // log.info("开始执行InstrumentsWs......"); |
| | | |
| | | WsMapBuild.saveStringToMap(INSTRUMENTSWSMAP, CoinEnums.HE_YUE.name(), CoinEnums.HE_YUE.getCode()); |
| | | WsMapBuild.saveStringToMap(INSTRUMENTSWSMAP, CoinEnums.CTVAL.name(), CoinEnums.CTVAL.getCode()); |
| | | WsMapBuild.saveStringToMap(INSTRUMENTSWSMAP, CoinEnums.TICKSZ.name(), CoinEnums.TICKSZ.getCode()); |
| | | WsMapBuild.saveStringToMap(INSTRUMENTSWSMAP, CoinEnums.MINSZ.name(), CoinEnums.MINSZ.getCode()); |
| | | WsMapBuild.saveStringToMap(INSTRUMENTSWSMAP, CoinEnums.OUT.name(), OrderParamEnums.OUT_NO.getValue()); |
| | | WsMapBuild.saveStringToMap(INSTRUMENTSWSMAP, CoinEnums.LEVERAGE.name(), CoinEnums.LEVERAGE.getCode()); |
| | | WsMapBuild.saveStringToMap(INSTRUMENTSWSMAP, CoinEnums.BUY_CNT.name(), CoinEnums.BUY_CNT.getCode()); |
| | | WsMapBuild.saveStringToMap(INSTRUMENTSWSMAP, CoinEnums.BUY_CNT_TIME.name(), CoinEnums.BUY_CNT_TIME.getCode()); |
| | | WsMapBuild.saveStringToMap(INSTRUMENTSWSMAP, CoinEnums.ZHI_SUN.name(), CoinEnums.ZHI_SUN.getCode()); |
| | | WsMapBuild.saveStringToMap(INSTRUMENTSWSMAP, CoinEnums.KANG_CANG.name(), CoinEnums.KANG_CANG.getCode()); |
| | | WsMapBuild.saveStringToMap(INSTRUMENTSWSMAP, CoinEnums.PING_CANG_SHOUYI.name(), CoinEnums.PING_CANG_SHOUYI.getCode()); |
| | | WsMapBuild.saveStringToMap(INSTRUMENTSWSMAP, CoinEnums.TOTAL_ORDER_USDTPECENT.name(), CoinEnums.TOTAL_ORDER_USDTPECENT.getCode()); |
| | | Map<String, String> accountMap = getAccountMap(accountName); |
| | | WsMapBuild.saveStringToMap(accountMap, CoinEnums.HE_YUE.name(), CoinEnums.HE_YUE.getCode()); |
| | | WsMapBuild.saveStringToMap(accountMap, CoinEnums.CTVAL.name(), CoinEnums.CTVAL.getCode()); |
| | | WsMapBuild.saveStringToMap(accountMap, CoinEnums.TICKSZ.name(), CoinEnums.TICKSZ.getCode()); |
| | | WsMapBuild.saveStringToMap(accountMap, CoinEnums.MINSZ.name(), CoinEnums.MINSZ.getCode()); |
| | | WsMapBuild.saveStringToMap(accountMap, CoinEnums.OUT.name(), OrderParamEnums.OUT_NO.getValue()); |
| | | WsMapBuild.saveStringToMap(accountMap, CoinEnums.LEVERAGE.name(), CoinEnums.LEVERAGE.getCode()); |
| | | WsMapBuild.saveStringToMap(accountMap, CoinEnums.BUY_CNT.name(), CoinEnums.BUY_CNT.getCode()); |
| | | WsMapBuild.saveStringToMap(accountMap, CoinEnums.BUY_CNT_TIME.name(), CoinEnums.BUY_CNT_TIME.getCode()); |
| | | WsMapBuild.saveStringToMap(accountMap, CoinEnums.ZHI_SUN.name(), CoinEnums.ZHI_SUN.getCode()); |
| | | WsMapBuild.saveStringToMap(accountMap, CoinEnums.KANG_CANG.name(), CoinEnums.KANG_CANG.getCode()); |
| | | WsMapBuild.saveStringToMap(accountMap, CoinEnums.PING_CANG_SHOUYI.name(), CoinEnums.PING_CANG_SHOUYI.getCode()); |
| | | WsMapBuild.saveStringToMap(accountMap, CoinEnums.TOTAL_ORDER_USDTPECENT.name(), CoinEnums.TOTAL_ORDER_USDTPECENT.getCode()); |
| | | } |
| | | } |
| | |
| | | */ |
| | | @Slf4j |
| | | public class LoginWs { |
| | | public static void websocketLogin(WebSocketClient webSocketClient) { |
| | | public static void websocketLogin(WebSocketClient webSocketClient, ExchangeInfoEnum account) { |
| | | |
| | | // log.info("开始执行LoginWs......"); |
| | | try { |
| | |
| | | JSONArray argsArray = new JSONArray(); |
| | | JSONObject loginArgs = new JSONObject(); |
| | | // 获取登录凭证信息(需要从配置或Redis中获取) |
| | | String apiKey = ExchangeInfoEnum.OKX_UAT.getApiKey(); |
| | | String passphrase = ExchangeInfoEnum.OKX_UAT.getPassphrase(); |
| | | String apiKey = account.getApiKey(); |
| | | String passphrase = account.getPassphrase(); |
| | | String timestamp = String.valueOf(System.currentTimeMillis() /1000); |
| | | String sign = SignUtils.signWebsocket(timestamp, ExchangeInfoEnum.OKX_UAT.getSecretKey()); |
| | | String sign = SignUtils.signWebsocket(timestamp, account.getSecretKey()); |
| | | |
| | | loginArgs.put("apiKey", apiKey); |
| | | loginArgs.put("passphrase", passphrase); |
| | |
| | | @Slf4j |
| | | public class OrderInfoWs { |
| | | |
| | | public static final Map<String,String> ORDERINFOWSMAP = new ConcurrentHashMap<>(); |
| | | // 使用双层Map,第一层key为账号名称,第二层key为数据key |
| | | public static final Map<String, Map<String, String>> ORDERINFOWSMAP = new ConcurrentHashMap<>(); |
| | | |
| | | // 获取指定账号的Map,如果不存在则创建 |
| | | public static Map<String, String> getAccountMap(String accountName) { |
| | | return ORDERINFOWSMAP.computeIfAbsent(accountName, k -> new ConcurrentHashMap<>()); |
| | | } |
| | | |
| | | public static final String ORDERINFOWS_CHANNEL = "orders"; |
| | | |
| | |
| | | } |
| | | } |
| | | |
| | | public static void initEvent(JSONObject response) { |
| | | public static void initEvent(JSONObject response, String accountName) { |
| | | // log.info("订阅成功: {}", response.getJSONObject("arg")); |
| | | } |
| | | |
| | |
| | | private static final String ACCFILLSZ_KEY = "accFillSz"; |
| | | private static final String AVGPX_KEY = "avgPx"; |
| | | private static final String STATE_KEY = "state"; |
| | | public static void handleEvent(JSONObject response, RedisUtils redisUtils) { |
| | | public static void handleEvent(JSONObject response, RedisUtils redisUtils, String accountName) { |
| | | |
| | | // log.info("开始执行OrderInfoWs......"); |
| | | try { |
| | |
| | | String state = detail.getString(STATE_KEY); |
| | | |
| | | log.info( |
| | | "订单详情-币种: {}, 系统编号: {}, 自定义编号: {}, 订单方向: {}, 交易模式: {}," + |
| | | "{}: 订单详情-币种: {}, 系统编号: {}, 自定义编号: {}, 订单方向: {}, 交易模式: {}," + |
| | | " 累计成交数量: {}, 成交均价: {}, 订单状态: {}", |
| | | instId, ordId, clOrdId, side, tdMode, |
| | | accountName, instId, ordId, clOrdId, side, tdMode, |
| | | accFillSz, avgPx,state |
| | | ); |
| | | |
| | | String clOrdIdStr = TradeOrderWs.TRADEORDERWSMAP.get("clOrdId"); |
| | | String stateStr = TradeOrderWs.TRADEORDERWSMAP.get("state"); |
| | | String clOrdIdStr = TradeOrderWs.getAccountMap(accountName).get("clOrdId"); |
| | | String stateStr = TradeOrderWs.getAccountMap(accountName).get("state"); |
| | | if ( |
| | | StrUtil.isNotBlank(clOrdIdStr) |
| | | && clOrdId.equals(clOrdIdStr) |
| | | && StrUtil.isNotBlank(stateStr) |
| | | && state.equals(stateStr) |
| | | ){ |
| | | Map<String, String> accountMap = getAccountMap(accountName); |
| | | //记录成交均价 |
| | | if (ORDERINFOWSMAP.get("orderPrice") == null){ |
| | | WsMapBuild.saveStringToMap(ORDERINFOWSMAP, "orderPrice",avgPx); |
| | | if (accountMap.get("orderPrice") == null){ |
| | | WsMapBuild.saveStringToMap(accountMap, "orderPrice",avgPx); |
| | | } |
| | | WsMapBuild.saveStringToMap(TradeOrderWs.TRADEORDERWSMAP, "state", CoinEnums.ORDER_LIVE.getCode()); |
| | | WsMapBuild.saveStringToMap(TradeOrderWs.getAccountMap(accountName), "state", CoinEnums.ORDER_LIVE.getCode()); |
| | | |
| | | WsMapBuild.saveBigDecimalToMap(PositionsWs.POSITIONSWSMAP, CoinEnums.READY_STATE.name(), WsMapBuild.parseBigDecimalSafe(CoinEnums.READY_STATE_NO.getCode())); |
| | | WsMapBuild.saveStringToMap(AccountWs.ACCOUNTWSMAP, CoinEnums.READY_STATE.name(), CoinEnums.READY_STATE_NO.getCode()); |
| | | // 使用账号特定的Map |
| | | Map<String, BigDecimal> positionsMap = PositionsWs.getAccountMap(accountName); |
| | | WsMapBuild.saveBigDecimalToMap(positionsMap, CoinEnums.READY_STATE.name(), WsMapBuild.parseBigDecimalSafe(CoinEnums.READY_STATE_NO.getCode())); |
| | | |
| | | log.info("订单详情已完成: {}, 自定义编号: {}", CoinEnums.HE_YUE.getCode(), clOrdId); |
| | | Map<String, String> accountWsMap = AccountWs.getAccountMap(accountName); |
| | | WsMapBuild.saveStringToMap(accountWsMap, CoinEnums.READY_STATE.name(), CoinEnums.READY_STATE_NO.getCode()); |
| | | |
| | | log.info("{}: 订单详情已完成: {}, 自定义编号: {}", accountName, CoinEnums.HE_YUE.getCode(), clOrdId); |
| | | } |
| | | } |
| | | } catch (Exception e) { |
| | |
| | | @Slf4j |
| | | public class PositionsWs { |
| | | |
| | | public static final Map<String,BigDecimal> POSITIONSWSMAP = new ConcurrentHashMap<>(); |
| | | // 使用双层Map,第一层key为账号名称,第二层key为数据key |
| | | public static final Map<String, Map<String, BigDecimal>> POSITIONSWSMAP = new ConcurrentHashMap<>(); |
| | | |
| | | // 获取指定账号的Map,如果不存在则创建 |
| | | public static Map<String, BigDecimal> getAccountMap(String accountName) { |
| | | return POSITIONSWSMAP.computeIfAbsent(accountName, k -> new ConcurrentHashMap<>()); |
| | | } |
| | | |
| | | public static final String POSITIONSWS_CHANNEL = "positions"; |
| | | |
| | |
| | | } |
| | | } |
| | | |
| | | public static void initEvent(JSONObject response) { |
| | | public static void initEvent(JSONObject response, String accountName) { |
| | | // log.info("订阅成功,数据初始化: {}", response.getJSONObject("arg")); |
| | | JSONObject arg = response.getJSONObject("arg"); |
| | | initParam(arg); |
| | | initParam(arg, accountName); |
| | | } |
| | | |
| | | public static void handleEvent(JSONObject response) { |
| | | public static void handleEvent(JSONObject response, String accountName) { |
| | | |
| | | |
| | | // log.info("开始执行PositionsWs......"); |
| | |
| | | if (dataArray == null || dataArray.isEmpty()) { |
| | | // log.info("账户持仓频道数据为空,已当前价买入,并且初始化网格"); |
| | | JSONObject posData = new JSONObject(); |
| | | initParam(posData); |
| | | initParam(posData, accountName); |
| | | return; |
| | | } |
| | | |
| | |
| | | String fee = posData.getString("fee"); |
| | | String fundingFee = posData.getString("fundingFee"); |
| | | log.info( |
| | | "账户持仓频道-产品类型: {}, 保证金模式: {}, 持仓方向: {}, 持仓数量: {}, 开仓平均价: {}, " |
| | | "{}: 账户持仓频道-产品类型: {}, 保证金模式: {}, 持仓方向: {}, 持仓数量: {}, 开仓平均价: {}, " |
| | | + "未实现收益: {}, 未实现收益率: {}, 杠杆倍数: {}, 预估强平价: {}, 初始保证金: {}, " |
| | | + "维持保证金率: {}, 维持保证金: {}, 以美金价值为单位的持仓数量: {}, 占用保证金的币种: {}, " |
| | | + "最新成交价: {}, 最新指数价格: {}, 盈亏平衡价: {}, 已实现收益: {}, 累计已结算收益: {}" |
| | | + "最新标记价格: {},累计手续费: {},累计持仓费: {},", |
| | | instId, mgnMode, posSide, pos, avgPx, |
| | | accountName, instId, mgnMode, posSide, pos, avgPx, |
| | | upl, uplRatio, lever, liqPx, imr, |
| | | mgnRatio, mmr, notionalUsd, ccy, |
| | | last, idxPx, bePx, realizedPnl, settledPnl, |
| | | markPx,fee,fundingFee |
| | | ); |
| | | |
| | | initParam(posData); |
| | | initParam(posData, accountName); |
| | | } |
| | | } |
| | | } catch (Exception e) { |
| | |
| | | } |
| | | } |
| | | |
| | | private static void initParam(JSONObject posData) { |
| | | WsMapBuild.saveBigDecimalToMap(POSITIONSWSMAP, "avgPx", WsMapBuild.parseBigDecimalSafe(posData.getString("avgPx"))); |
| | | WsMapBuild.saveBigDecimalToMap(POSITIONSWSMAP, "pos", WsMapBuild.parseBigDecimalSafe(posData.getString("pos"))); |
| | | WsMapBuild.saveBigDecimalToMap(POSITIONSWSMAP, "upl", WsMapBuild.parseBigDecimalSafe(posData.getString("upl"))); |
| | | WsMapBuild.saveBigDecimalToMap(POSITIONSWSMAP, "imr", WsMapBuild.parseBigDecimalSafe(posData.getString("imr"))); |
| | | WsMapBuild.saveBigDecimalToMap(POSITIONSWSMAP, "mgnRatio", WsMapBuild.parseBigDecimalSafe(posData.getString("mgnRatio"))); |
| | | WsMapBuild.saveBigDecimalToMap(POSITIONSWSMAP, "markPx", WsMapBuild.parseBigDecimalSafe(posData.getString("markPx"))); |
| | | WsMapBuild.saveBigDecimalToMap(POSITIONSWSMAP, "bePx", WsMapBuild.parseBigDecimalSafe(posData.getString("bePx"))); |
| | | WsMapBuild.saveBigDecimalToMap(POSITIONSWSMAP, "realizedPnl", WsMapBuild.parseBigDecimalSafe(posData.getString("realizedPnl"))); |
| | | WsMapBuild.saveBigDecimalToMap(POSITIONSWSMAP, "fee", WsMapBuild.parseBigDecimalSafe(posData.getString("fee"))); |
| | | WsMapBuild.saveBigDecimalToMap(POSITIONSWSMAP, "fundingFee", WsMapBuild.parseBigDecimalSafe(posData.getString("fundingFee"))); |
| | | private static void initParam(JSONObject posData, String accountName) { |
| | | Map<String, BigDecimal> accountMap = getAccountMap(accountName); |
| | | WsMapBuild.saveBigDecimalToMap(accountMap, "avgPx", WsMapBuild.parseBigDecimalSafe(posData.getString("avgPx"))); |
| | | WsMapBuild.saveBigDecimalToMap(accountMap, "pos", WsMapBuild.parseBigDecimalSafe(posData.getString("pos"))); |
| | | WsMapBuild.saveBigDecimalToMap(accountMap, "upl", WsMapBuild.parseBigDecimalSafe(posData.getString("upl"))); |
| | | WsMapBuild.saveBigDecimalToMap(accountMap, "imr", WsMapBuild.parseBigDecimalSafe(posData.getString("imr"))); |
| | | WsMapBuild.saveBigDecimalToMap(accountMap, "mgnRatio", WsMapBuild.parseBigDecimalSafe(posData.getString("mgnRatio"))); |
| | | WsMapBuild.saveBigDecimalToMap(accountMap, "markPx", WsMapBuild.parseBigDecimalSafe(posData.getString("markPx"))); |
| | | WsMapBuild.saveBigDecimalToMap(accountMap, "bePx", WsMapBuild.parseBigDecimalSafe(posData.getString("bePx"))); |
| | | WsMapBuild.saveBigDecimalToMap(accountMap, "realizedPnl", WsMapBuild.parseBigDecimalSafe(posData.getString("realizedPnl"))); |
| | | WsMapBuild.saveBigDecimalToMap(accountMap, "fee", WsMapBuild.parseBigDecimalSafe(posData.getString("fee"))); |
| | | WsMapBuild.saveBigDecimalToMap(accountMap, "fundingFee", WsMapBuild.parseBigDecimalSafe(posData.getString("fundingFee"))); |
| | | |
| | | WsMapBuild.saveBigDecimalToMap(POSITIONSWSMAP, CoinEnums.READY_STATE.name(), WsMapBuild.parseBigDecimalSafe(CoinEnums.READY_STATE_YES.getCode())); |
| | | WsMapBuild.saveBigDecimalToMap(accountMap, CoinEnums.READY_STATE.name(), WsMapBuild.parseBigDecimalSafe(CoinEnums.READY_STATE_YES.getCode())); |
| | | } |
| | | } |
| | |
| | | @Slf4j |
| | | public class TradeOrderWs { |
| | | |
| | | public static final Map<String,String> TRADEORDERWSMAP = new ConcurrentHashMap<>(); |
| | | // 使用双层Map,第一层key为账号名称,第二层key为数据key |
| | | public static final Map<String, Map<String,String>> TRADEORDERWSMAP = new ConcurrentHashMap<>(); |
| | | |
| | | // 获取指定账号的Map,如果不存在则创建 |
| | | public static Map<String, String> getAccountMap(String accountName) { |
| | | return TRADEORDERWSMAP.computeIfAbsent(accountName, k -> new ConcurrentHashMap<>()); |
| | | } |
| | | |
| | | public static final String ORDERWS_CHANNEL = "order"; |
| | | |
| | | public static void orderEvent(WebSocketClient webSocketClient, String side) { |
| | | public static void orderEvent(WebSocketClient webSocketClient, String side, String accountName) { |
| | | |
| | | // log.info("开始执行TradeOrderWs......"); |
| | | String accountReadyState = AccountWs.ACCOUNTWSMAP.get(CoinEnums.READY_STATE.name()); |
| | | String accountReadyState = AccountWs.getAccountMap(accountName).get(CoinEnums.READY_STATE.name()); |
| | | if (!CoinEnums.READY_STATE_YES.getCode().equals(accountReadyState)) { |
| | | log.info("账户通道未就绪,取消发送"); |
| | | return; |
| | | } |
| | | BigDecimal positionsReadyState = PositionsWs.POSITIONSWSMAP.get(CoinEnums.READY_STATE.name()) == null |
| | | ? BigDecimal.ZERO : PositionsWs.POSITIONSWSMAP.get(CoinEnums.READY_STATE.name()); |
| | | BigDecimal positionsReadyState = PositionsWs.getAccountMap(accountName).get(CoinEnums.READY_STATE.name()) == null |
| | | ? BigDecimal.ZERO : PositionsWs.getAccountMap(accountName).get(CoinEnums.READY_STATE.name()); |
| | | if (WsMapBuild.parseBigDecimalSafe(CoinEnums.READY_STATE_YES.getCode()).compareTo(positionsReadyState) != 0) { |
| | | log.info("仓位通道未就绪,取消发送"); |
| | | return; |
| | |
| | | }else if (OrderParamEnums.OUT.getValue().equals(side)){ |
| | | log.info("当前状态为止损"); |
| | | side = OrderParamEnums.SELL.getValue(); |
| | | buyCnt = String.valueOf(PositionsWs.POSITIONSWSMAP.get("pos")); |
| | | buyCnt = String.valueOf(PositionsWs.getAccountMap(accountName).get("pos")); |
| | | }else if (OrderParamEnums.INIT.getValue().equals(side)){ |
| | | log.info("当前状态为初始化"); |
| | | side = OrderParamEnums.BUY.getValue(); |
| | | buyCnt = InstrumentsWs.INSTRUMENTSWSMAP.get(CoinEnums.BUY_CNT.name()); |
| | | buyCnt = InstrumentsWs.getAccountMap(accountName).get(CoinEnums.BUY_CNT.name()); |
| | | }else if (OrderParamEnums.BUY.getValue().equals(side)){ |
| | | log.info("当前状态为加仓"); |
| | | String buyCntTime = TRADEORDERWSMAP.get("buyCntTime"); |
| | | String buyCntStr = InstrumentsWs.INSTRUMENTSWSMAP.get(CoinEnums.BUY_CNT.name()); |
| | | String buyCntTime = getAccountMap(accountName).get("buyCntTime"); |
| | | String buyCntStr = InstrumentsWs.getAccountMap(accountName).get(CoinEnums.BUY_CNT.name()); |
| | | buyCnt = String.valueOf(new BigDecimal(buyCntTime).multiply(new BigDecimal(buyCntStr))); |
| | | }else if (OrderParamEnums.SELL.getValue().equals(side)){ |
| | | log.info("当前状态为减仓"); |
| | | buyCnt = String.valueOf(PositionsWs.POSITIONSWSMAP.get("pos")); |
| | | buyCnt = String.valueOf(PositionsWs.getAccountMap(accountName).get("pos")); |
| | | }else{ |
| | | log.warn("交易状态异常,取消发送"); |
| | | return; |
| | |
| | | webSocketClient.send(jsonObject.toJSONString()); |
| | | log.info("发送下单频道:{},数量:{}", side, buyCnt); |
| | | |
| | | WsMapBuild.saveStringToMap(TRADEORDERWSMAP, "buyCntTime",String.valueOf(BigDecimal.ONE)); |
| | | WsMapBuild.saveStringToMap(TRADEORDERWSMAP, "clOrdId", clOrdId); |
| | | WsMapBuild.saveStringToMap(TRADEORDERWSMAP, "state", CoinEnums.ORDER_FILLED.getCode()); |
| | | WsMapBuild.saveStringToMap(getAccountMap(accountName), "buyCntTime",String.valueOf(BigDecimal.ONE)); |
| | | WsMapBuild.saveStringToMap(getAccountMap(accountName), "clOrdId", clOrdId); |
| | | WsMapBuild.saveStringToMap(getAccountMap(accountName), "state", CoinEnums.ORDER_FILLED.getCode()); |
| | | |
| | | WsMapBuild.saveBigDecimalToMap(PositionsWs.POSITIONSWSMAP, CoinEnums.READY_STATE.name(), WsMapBuild.parseBigDecimalSafe(CoinEnums.READY_STATE_NO.getCode())); |
| | | WsMapBuild.saveStringToMap(AccountWs.ACCOUNTWSMAP, CoinEnums.READY_STATE.name(), CoinEnums.READY_STATE_NO.getCode()); |
| | | WsMapBuild.saveBigDecimalToMap(PositionsWs.getAccountMap(accountName), CoinEnums.READY_STATE.name(), WsMapBuild.parseBigDecimalSafe(CoinEnums.READY_STATE_NO.getCode())); |
| | | WsMapBuild.saveStringToMap(AccountWs.getAccountMap(accountName), CoinEnums.READY_STATE.name(), CoinEnums.READY_STATE_NO.getCode()); |
| | | |
| | | } catch (Exception e) { |
| | | log.error("下单构建失败", e); |
| | |
| | | public enum ExchangeInfoEnum { |
| | | |
| | | /** |
| | | * 模拟盘账户信息 |
| | | * 模拟盘账户1信息 |
| | | * 存储了模拟盘交易所需的API密钥、秘钥和通过码 |
| | | */ |
| | | OKX_UAT("ffb4e79f-fcf5-4afb-82c5-2fbb64123f61", |
| | | OKX_UAT1("ffb4e79f-fcf5-4afb-82c5-2fbb64123f61", |
| | | "AA06C5ED1D7C7F5AFE6484052E231C55", |
| | | "Aa12345678@", |
| | | false), |
| | | |
| | | /** |
| | | * 模拟盘账户2信息 |
| | | * 存储了模拟盘交易所需的API密钥、秘钥和通过码 |
| | | */ |
| | | OKX_UAT2("7a023eb2-06c0-4255-9969-b86ea1cef0d7", |
| | | "D0106A4D63BD22BEAB9CBA8F41219661", |
| | | "Aa12345678@", |
| | | false); |
| | | |
| | | /** |
| | | * 模拟盘账户信息 |
| | | * 模拟盘账户3信息 |
| | | * 存储了模拟盘交易所需的API密钥、秘钥和通过码 |
| | | */ |
| | | // OKX_UAT("7a023eb2-06c0-4255-9969-b86ea1cef0d7", |
| | | // "D0106A4D63BD22BEAB9CBA8F41219661", |
| | | // "Aa12345678@", |
| | | // false); |
| | | |
| | | // /** |
| | | // * 模拟盘账户信息 |
| | | // * 存储了模拟盘交易所需的API密钥、秘钥和通过码 |
| | | // */ |
| | | // OKX_UAT("0769b50c-2c36-4310-8bd9-cad6bc6c9d8f", |
| | | // OKX_UAT3("0769b50c-2c36-4310-8bd9-cad6bc6c9d8f", |
| | | // "7AF4A574BC44907CE76BBFF91F53852D", |
| | | // "Aa123456@", |
| | | // false); |
| | |
| | | quantApiMessage.setExchange(okx); |
| | | quantApiMessage.setMemberId(1L); |
| | | |
| | | |
| | | quantApiMessage.setAccountType(ExchangeInfoEnum.OKX_UAT.isAccountType()? "true":"false"); |
| | | // 根据传入的账号名称获取对应的账号信息 |
| | | ExchangeInfoEnum account = ExchangeInfoEnum.valueOf(okx); |
| | | quantApiMessage.setAccountType(account.isAccountType()? "true":"false"); |
| | | quantApiMessage.setState(1); |
| | | quantApiMessage.setIsTrade(1); |
| | | quantApiMessage.setASecretkey(ExchangeInfoEnum.OKX_UAT.getApiKey()); |
| | | quantApiMessage.setBSecretkey(ExchangeInfoEnum.OKX_UAT.getSecretKey()); |
| | | quantApiMessage.setPassPhrass(ExchangeInfoEnum.OKX_UAT.getPassphrase()); |
| | | quantApiMessage.setASecretkey(account.getApiKey()); |
| | | quantApiMessage.setBSecretkey(account.getSecretKey()); |
| | | quantApiMessage.setPassPhrass(account.getPassphrase()); |
| | | return quantApiMessage; |
| | | } |
| | | |