14 files modified
1 files added
| | |
| | | public class ExcoinApplication { |
| | | |
| | | public static void main(String[] args) { |
| | | System.setProperty("spring.devtools.restart.enabled", "false"); |
| | | SpringApplication.run(ExcoinApplication.class, args); |
| | | } |
| | | |
| | |
| | | * @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"; |
| | |
| | | return t; |
| | | }); |
| | | |
| | | // 在 OkxQuantWebSocketClient 中添加初始化标记 |
| | | private final AtomicBoolean isInitialized = new AtomicBoolean(false); |
| | | |
| | | /** |
| | | * 初始化方法,在 Spring Bean 构造完成后执行。 |
| | | * 负责建立 WebSocket 连接并启动心跳检测任务。 |
| | | */ |
| | | @PostConstruct |
| | | public void init() { |
| | | // 防止重复初始化 |
| | | if (!isInitialized.compareAndSet(false, true)) { |
| | | log.warn("OkxQuantWebSocketClient 已经初始化过,跳过重复初始化"); |
| | | return; |
| | | } |
| | | |
| | | connect(); |
| | | startHeartbeat(); |
| | | |
| | | // 移除了每小时重连的定时任务 |
| | | } |
| | | |
| | | /** |
| | | * 销毁方法,在 Spring Bean 销毁前执行。 |
| | | * 关闭 WebSocket 连接、停止心跳定时器及相关的线程资源。 |
| | | */ |
| | | // @PreDestroy |
| | | // public void destroy() { |
| | | // if (webSocketClient != null && webSocketClient.isOpen()) { |
| | | // subscribeAccountChannel(UNSUBSCRIBE); |
| | | // subscribePositionChannel(UNSUBSCRIBE); |
| | | // subscribeOrderInfoChannel(UNSUBSCRIBE); |
| | | // webSocketClient.close(); |
| | | // } |
| | | // shutdownExecutorGracefully(heartbeatExecutor); |
| | | // if (pongTimeoutFuture != null) { |
| | | // pongTimeoutFuture.cancel(true); |
| | | // } |
| | | // shutdownExecutorGracefully(sharedExecutor); |
| | | // |
| | | // // 移除了 reconnectScheduler 的关闭操作 |
| | | // } |
| | | @PreDestroy |
| | | public void destroy() { |
| | | if (webSocketClient != null && webSocketClient.isOpen()) { |
| | | subscribeAccountChannel(UNSUBSCRIBE); |
| | | subscribePositionChannel(UNSUBSCRIBE); |
| | | subscribeOrderInfoChannel(UNSUBSCRIBE); |
| | | webSocketClient.close(); |
| | | log.info("开始销毁OkxQuantWebSocketClient"); |
| | | |
| | | // 设置关闭标志,避免重连 |
| | | if (sharedExecutor != null && !sharedExecutor.isShutdown()) { |
| | | sharedExecutor.shutdown(); |
| | | } |
| | | |
| | | if (webSocketClient != null && webSocketClient.isOpen()) { |
| | | try { |
| | | subscribeAccountChannel(UNSUBSCRIBE); |
| | | subscribePositionChannel(UNSUBSCRIBE); |
| | | subscribeOrderInfoChannel(UNSUBSCRIBE); |
| | | webSocketClient.closeBlocking(); |
| | | } catch (InterruptedException e) { |
| | | Thread.currentThread().interrupt(); |
| | | log.warn("关闭WebSocket连接时被中断"); |
| | | } |
| | | } |
| | | |
| | | shutdownExecutorGracefully(heartbeatExecutor); |
| | | if (pongTimeoutFuture != null) { |
| | | pongTimeoutFuture.cancel(true); |
| | | } |
| | | shutdownExecutorGracefully(sharedExecutor); |
| | | |
| | | // 移除了 reconnectScheduler 的关闭操作 |
| | | log.info("OkxQuantWebSocketClient销毁完成"); |
| | | } |
| | | |
| | | private void shutdownExecutorGracefully(ExecutorService executor) { |
| | |
| | | } |
| | | |
| | | 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); |
| | | 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); |
| | | } |
| | | String side = caoZuoService.caoZuo(); |
| | | TradeOrderWs.orderEvent(webSocketClient, side); |
| | | } |
| | | |
| | | /** |
| | |
| | | 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); |
| | | |
| | | String caoZuoLong(String accountName); |
| | | |
| | | String caoZuoShort(String accountName); |
| | | } |
| | |
| | | * @return 返回操作类型字符串(如买入BUY、卖出SELL等),如果无有效操作则返回null |
| | | */ |
| | | @Override |
| | | public String caoZuo() { |
| | | log.info("开始执行操作CaoZuoServiceImpl......"); |
| | | String accountReadyState = AccountWs.ACCOUNTWSMAP.get(CoinEnums.READY_STATE.name()); |
| | | public String caoZuo(String accountName) { |
| | | String posSide = InstrumentsWs.getAccountMap(accountName).get(CoinEnums.POSSIDE.name()); |
| | | if (CoinEnums.POSSIDE_LONG.getCode().equals(posSide)){ |
| | | return caoZuoLong(accountName); |
| | | }else if (CoinEnums.POSSIDE_SHORT.getCode().equals(posSide)){ |
| | | return caoZuoShort(accountName); |
| | | }else{ |
| | | log.error("账户未设置持仓方向......"); |
| | | return null; |
| | | } |
| | | } |
| | | |
| | | @Override |
| | | public String caoZuoLong(String accountName) { |
| | | log.info("开始看涨执行操作CaoZuoServiceImpl......"); |
| | | 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 = buyCntTimeLongEvent(accountName, avgPx, markPx); |
| | | if (buyCntTimeFlag){ |
| | | log.info("加仓参数准备成功......"); |
| | | return OrderParamEnums.BUY.getValue(); |
| | |
| | | return OrderParamEnums.HOLDING.getValue(); |
| | | } |
| | | AscBigDecimal pingCang = queuePingCang.peek(); |
| | | if (pingCang != null && markPx.compareTo(pingCang.getValue()) >= 0 && avgPx.compareTo(pingCang.getValue()) < 0) { |
| | | log.info("开始减仓...平仓队列价格小于当前价格{}<={}", pingCang.getValue(), markPx); |
| | | if (pingCang != null && avgPx.compareTo(pingCang.getValue()) < 0) { |
| | | log.info("开始减仓...平仓队列价格大于当前价格{}<={}", pingCang.getValue(), avgPx); |
| | | // 手续费 |
| | | BigDecimal feeValue = PositionsWs.POSITIONSWSMAP.get("fee").multiply(new BigDecimal("2")); |
| | | // 资金费用 |
| | | BigDecimal fundingFeeValue = PositionsWs.POSITIONSWSMAP.get("fundingFee"); |
| | | 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"); |
| | | realizedPnlValue = realizedPnlValue.add(feeValue).add(fundingFeeValue); |
| | | 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); |
| | | WsMapBuild.saveStringToMap(OrderInfoWs.ORDERINFOWSMAP, "orderPrice", String.valueOf(markPx)); |
| | | log.info("当前未实现盈亏:{}大于预计收益>{},赚钱咯", uplValue, imrValue.add(realizedPnlValueZheng)); |
| | | WsMapBuild.saveStringToMap(OrderInfoWs.getAccountMap(accountName), "orderPrice", String.valueOf(markPx)); |
| | | return OrderParamEnums.SELL.getValue(); |
| | | }else{ |
| | | log.info("当前未实现盈亏:{}没有大于预计收益>{},钱在路上了", uplValue, imrValue); |
| | | log.info("当前未实现盈亏:{}没有大于预计收益>{},钱在路上了", uplValue, imrValue.add(realizedPnlValueZheng)); |
| | | return OrderParamEnums.HOLDING.getValue(); |
| | | } |
| | | }else { |
| | | if (uplValue.compareTo(imrValue) >= 0) { |
| | | WsMapBuild.saveStringToMap(OrderInfoWs.ORDERINFOWSMAP, "orderPrice", String.valueOf(markPx)); |
| | | log.info("当前未实现盈亏:{}大于预计收益>{},赚钱咯", uplValue, imrValue); |
| | | if (uplValue.compareTo(imrValue.add(feeValue)) >= 0) { |
| | | WsMapBuild.saveStringToMap(OrderInfoWs.getAccountMap(accountName), "orderPrice", String.valueOf(markPx)); |
| | | log.info("当前未实现盈亏:{}大于预计收益>{},赚钱咯", uplValue, imrValue.add(feeValue)); |
| | | return OrderParamEnums.SELL.getValue(); |
| | | }else{ |
| | | log.info("当前未实现盈亏:{}没有大于预计收益>{},钱在路上了", uplValue, imrValue); |
| | | log.info("当前未实现盈亏:{}没有大于预计收益>{},钱在路上了", uplValue, imrValue.add(feeValue)); |
| | | return OrderParamEnums.HOLDING.getValue(); |
| | | } |
| | | } |
| | |
| | | } |
| | | } |
| | | |
| | | private boolean buyCntTimeEvent(BigDecimal avgPx, BigDecimal markPx){ |
| | | @Override |
| | | public String caoZuoShort(String accountName) { |
| | | |
| | | log.info("开始看空执行操作CaoZuoServiceImpl......"); |
| | | 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.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.getAccountMap(accountName).get(CoinEnums.OUT.name()); |
| | | if (OrderParamEnums.OUT_YES.getValue().equals(outStr)){ |
| | | log.error("冷静中,不允许下单......"); |
| | | return null; |
| | | } |
| | | BigDecimal cashBal = WsMapBuild.parseBigDecimalSafe(AccountWs.getAccountMap(accountName).get("cashBal")); |
| | | |
| | | // 判断账户余额是否充足 |
| | | if (cashBal.compareTo(BigDecimal.ZERO) <= 0){ |
| | | log.error("账户没有钱,请充值......"); |
| | | return null; |
| | | } |
| | | /** |
| | | * 判断止损抗压 |
| | | */ |
| | | // 实际亏损金额 |
| | | BigDecimal realKuiSunAmount = WsMapBuild.parseBigDecimalSafe(AccountWs.getAccountMap(accountName).get("upl")); |
| | | log.info("未实现盈亏: {}", realKuiSunAmount); |
| | | String zhiSunPercent = InstrumentsWs.getAccountMap(accountName).get(CoinEnums.ZHI_SUN.name()); |
| | | BigDecimal zhiSunAmount = cashBal.multiply(new BigDecimal(zhiSunPercent)); |
| | | log.info("预期亏损金额: {}", zhiSunAmount); |
| | | String kangYaPercent = InstrumentsWs.getAccountMap(accountName).get(CoinEnums.KANG_CANG.name()); |
| | | BigDecimal kangYaAmount = cashBal.multiply(new BigDecimal(kangYaPercent)); |
| | | log.info("预期抗仓金额: {}", kangYaAmount); |
| | | |
| | | if (realKuiSunAmount.compareTo(BigDecimal.ZERO) < 0){ |
| | | realKuiSunAmount = realKuiSunAmount.multiply(new BigDecimal("-1")); |
| | | // 账户预期亏损金额比这个还小时,立即止损 |
| | | if (realKuiSunAmount.compareTo(zhiSunAmount) > 0){ |
| | | log.error("账户冷静止损......"); |
| | | WsMapBuild.saveStringToMap(InstrumentsWs.getAccountMap(accountName), CoinEnums.OUT.name(), OrderParamEnums.OUT_YES.getValue()); |
| | | return OrderParamEnums.OUT.getValue(); |
| | | } |
| | | // 判断抗压 |
| | | if (realKuiSunAmount.compareTo(kangYaAmount) > 0 && realKuiSunAmount.compareTo(zhiSunAmount) <= 0){ |
| | | log.error("账户紧张扛仓......"); |
| | | return OrderParamEnums.HOLDING.getValue(); |
| | | } |
| | | } |
| | | |
| | | if (PositionsWs.getAccountMap(accountName).get("pos") == null){ |
| | | log.error("没有获取到持仓信息,等待初始化......"); |
| | | return null; |
| | | } |
| | | BigDecimal pos = PositionsWs.getAccountMap(accountName).get("pos"); |
| | | if (BigDecimal.ZERO.compareTo( pos) >= 0) { |
| | | log.error("持仓数量为零,进行初始化订单"); |
| | | return OrderParamEnums.INIT.getValue(); |
| | | } |
| | | // 判断是否保证金超标 |
| | | if (PositionsWs.getAccountMap(accountName).get("imr") == null){ |
| | | log.error("没有获取到持仓信息,等待初始化......"); |
| | | return null; |
| | | } |
| | | 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.getAccountMap(accountName).get("markPx"); |
| | | BigDecimal avgPx = PositionsWs.getAccountMap(accountName).get("avgPx"); |
| | | log.info("开仓价格: {}, 当前价格:{},匹配队列中......", avgPx, markPx); |
| | | |
| | | // 初始化网格队列 |
| | | PriorityBlockingQueue<AscBigDecimal> queueAsc = WangGeQueue.getQueueAsc(); |
| | | PriorityBlockingQueue<DescBigDecimal> queueKaiCang = wangGeService.initKaiCang(avgPx, queueAsc); |
| | | PriorityBlockingQueue<AscBigDecimal> queuePingCang = wangGeService.initPingCang(avgPx, queueAsc); |
| | | |
| | | // 处理订单价格在队列中的情况 |
| | | String orderPrice = OrderInfoWs.getAccountMap(accountName).get("orderPrice"); |
| | | handleOrderPriceInQueues(orderPrice, queueKaiCang, queuePingCang); |
| | | // 判断是加仓还是减仓 |
| | | if (avgPx.compareTo(markPx) > 0) { |
| | | log.info("开始减仓..."); |
| | | if (queueKaiCang.isEmpty()) { |
| | | // 队列为空 |
| | | // log.info("开始加仓,但是超出了网格设置..."); |
| | | return OrderParamEnums.HOLDING.getValue(); |
| | | } |
| | | DescBigDecimal kaiCang = queueKaiCang.peek(); |
| | | if (kaiCang != null && avgPx.compareTo(kaiCang.getValue()) >= 0) { |
| | | log.info("开始减仓...减仓队列价格小于开仓价格{}>{}", kaiCang.getValue(), avgPx); |
| | | |
| | | // 手续费 |
| | | BigDecimal feeValue = PositionsWs.getAccountMap(accountName).get("fee").multiply(new BigDecimal("2")); |
| | | //未实现收益 |
| | | BigDecimal uplValue = PositionsWs.getAccountMap(accountName).get("upl"); |
| | | //已实现收益 |
| | | BigDecimal realizedPnlValue = PositionsWs.getAccountMap(accountName).get("realizedPnl"); |
| | | realizedPnlValue = realizedPnlValue.add(feeValue); |
| | | |
| | | //持仓保证金 |
| | | 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.getAccountMap(accountName), "orderPrice", String.valueOf(markPx)); |
| | | return OrderParamEnums.BUY.getValue(); |
| | | }else{ |
| | | log.info("当前未实现盈亏:{}没有大于预计收益>{},钱在路上了", uplValue, imrValue.add(realizedPnlValueZheng)); |
| | | return OrderParamEnums.HOLDING.getValue(); |
| | | } |
| | | }else { |
| | | if (uplValue.compareTo(imrValue.add(feeValue)) >= 0) { |
| | | WsMapBuild.saveStringToMap(OrderInfoWs.getAccountMap(accountName), "orderPrice", String.valueOf(markPx)); |
| | | log.info("当前未实现盈亏:{}大于预计收益>{},赚钱咯", uplValue, imrValue.add(feeValue)); |
| | | return OrderParamEnums.BUY.getValue(); |
| | | }else{ |
| | | log.info("当前未实现盈亏:{}没有大于预计收益>{},钱在路上了", uplValue, imrValue.add(feeValue)); |
| | | return OrderParamEnums.HOLDING.getValue(); |
| | | } |
| | | } |
| | | } else { |
| | | log.info("未触发减仓......,等待"); |
| | | return OrderParamEnums.HOLDING.getValue(); |
| | | } |
| | | } else if (avgPx.compareTo(markPx) < 0) { |
| | | log.info("开始加仓..."); |
| | | if (queuePingCang.isEmpty()) { |
| | | // 队列为空 |
| | | log.info("开始加仓,但是超出了网格设置..."); |
| | | return OrderParamEnums.HOLDING.getValue(); |
| | | } |
| | | AscBigDecimal pingCang = queuePingCang.peek(); |
| | | if (pingCang != null && markPx.compareTo(pingCang.getValue()) >= 0 && avgPx.compareTo(pingCang.getValue()) < 0) { |
| | | log.info("开始加仓...加仓队列价格小于当前价格{}<={}", pingCang.getValue(), markPx); |
| | | WsMapBuild.saveStringToMap(OrderInfoWs.getAccountMap(accountName), "orderPrice", String.valueOf(markPx)); |
| | | boolean buyCntTimeFlag = buyCntTimeShortEvent(accountName, avgPx, markPx); |
| | | if (buyCntTimeFlag){ |
| | | log.info("加仓参数准备成功......"); |
| | | return OrderParamEnums.SELL.getValue(); |
| | | }else{ |
| | | log.error("加仓参数准备失败......"); |
| | | return null; |
| | | } |
| | | } else { |
| | | log.info("未触发加仓......,等待"); |
| | | } |
| | | } else { |
| | | log.info("价格波动较小......,等待"); |
| | | } |
| | | return null; |
| | | } catch (NumberFormatException e) { |
| | | log.error("解析价格失败,请检查Redis中的值是否合法", e); |
| | | return null; |
| | | } |
| | | } |
| | | |
| | | private boolean buyCntTimeLongEvent(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).add(BigDecimal.ONE); |
| | | return WsMapBuild.saveStringToMap(TradeOrderWs.TRADEORDERWSMAP, "buyCntTime",String.valueOf(divide)); |
| | | 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.getAccountMap(accountName), "buyCntTime",String.valueOf(divide)); |
| | | } |
| | | |
| | | private boolean buyCntTimeShortEvent(String accountName, BigDecimal avgPx, BigDecimal markPx){ |
| | | //判断当前价格和开仓价格直接间隔除以间距,取整,获取的数量是否大于等于0,如果大于0,则下单基础张数*倍数 |
| | | String buyCntTime = InstrumentsWs.getAccountMap(accountName).get(CoinEnums.BUY_CNT_TIME.name()); |
| | | BigDecimal subtract = markPx.subtract(avgPx); |
| | | 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.getAccountMap(accountName), "buyCntTime",String.valueOf(divide)); |
| | | } |
| | | |
| | | /** |
| | |
| | | queueKaiCang.removeIf(item -> item.getValue().compareTo(priceDecimal) >= 0); |
| | | |
| | | // 打印开仓队列 |
| | | StringBuilder kaiCangStr = new StringBuilder(); |
| | | kaiCangStr.append("开仓队列: ["); |
| | | boolean first = true; |
| | | for (DescBigDecimal item : queueKaiCang) { |
| | | if (!first) { |
| | | kaiCangStr.append(", "); |
| | | } |
| | | kaiCangStr.append(item.getValue()); |
| | | first = false; |
| | | } |
| | | kaiCangStr.append("]"); |
| | | log.info(kaiCangStr.toString()); |
| | | // StringBuilder kaiCangStr = new StringBuilder(); |
| | | // kaiCangStr.append("开仓队列: ["); |
| | | // boolean first = true; |
| | | // for (DescBigDecimal item : queueKaiCang) { |
| | | // if (!first) { |
| | | // kaiCangStr.append(", "); |
| | | // } |
| | | // kaiCangStr.append(item.getValue()); |
| | | // first = false; |
| | | // } |
| | | // kaiCangStr.append("]"); |
| | | // log.info(kaiCangStr.toString()); |
| | | |
| | | // 删除比该价格小的数据 |
| | | queuePingCang.removeIf(item -> item.getValue().compareTo(priceDecimal) <= 0); |
| | | |
| | | // 打印平仓队列 |
| | | StringBuilder pingCangStr = new StringBuilder(); |
| | | pingCangStr.append("平仓队列: ["); |
| | | first = true; |
| | | for (AscBigDecimal item : queuePingCang) { |
| | | if (!first) { |
| | | pingCangStr.append(", "); |
| | | } |
| | | pingCangStr.append(item.getValue()); |
| | | first = false; |
| | | } |
| | | pingCangStr.append("]"); |
| | | log.info(pingCangStr.toString()); |
| | | // StringBuilder pingCangStr = new StringBuilder(); |
| | | // pingCangStr.append("平仓队列: ["); |
| | | // first = true; |
| | | // for (AscBigDecimal item : queuePingCang) { |
| | | // if (!first) { |
| | | // pingCangStr.append(", "); |
| | | // } |
| | | // pingCangStr.append(item.getValue()); |
| | | // first = false; |
| | | // } |
| | | // pingCangStr.append("]"); |
| | | // log.info(pingCangStr.toString()); |
| | | } |
| | | } |
| | |
| | | @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 |
| | | // ); |
| | | log.info( |
| | | "{}: 账户详情-币种: {}, 可用余额: {}, 现金余额: {}, 余额: {}, 全仓未实现盈亏: {}, 下单总保证金: {}", |
| | | 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_INIT.name(), CoinEnums.BUY_CNT_INIT.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()); |
| | | WsMapBuild.saveStringToMap(accountMap, CoinEnums.POSSIDE.name(), CoinEnums.POSSIDE.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())); |
| | | |
| | | Map<String, String> accountWsMap = AccountWs.getAccountMap(accountName); |
| | | WsMapBuild.saveStringToMap(accountWsMap, CoinEnums.READY_STATE.name(), CoinEnums.READY_STATE_NO.getCode()); |
| | | |
| | | log.info("订单详情已完成: {}, 自定义编号: {}", CoinEnums.HE_YUE.getCode(), clOrdId); |
| | | 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 instId = posData.getString("instId"); |
| | | if (CoinEnums.HE_YUE.getCode().equals(instId)) { |
| | | // log.info("查询到账户{}持仓数据",CoinEnums.HE_YUE.getCode()); |
| | | // String mgnMode = posData.getString("mgnMode"); |
| | | // String posSide = posData.getString("posSide"); |
| | | // String pos = posData.getString("pos"); |
| | | // String avgPx = posData.getString("avgPx"); |
| | | // String upl = posData.getString("upl"); |
| | | // String uplRatio = posData.getString("uplRatio"); |
| | | // String lever = posData.getString("lever"); |
| | | // String liqPx = posData.getString("liqPx"); |
| | | // String markPx = posData.getString("markPx"); |
| | | // String imr = posData.getString("imr"); |
| | | // String mgnRatio = posData.getString("mgnRatio"); |
| | | // String mmr = posData.getString("mmr"); |
| | | // String notionalUsd = posData.getString("notionalUsd"); |
| | | // String ccy = posData.getString("ccy"); |
| | | // String last = posData.getString("last"); |
| | | // String idxPx = posData.getString("idxPx"); |
| | | // String bePx = posData.getString("bePx"); |
| | | // String realizedPnl = posData.getString("realizedPnl"); |
| | | // String settledPnl = posData.getString("settledPnl"); |
| | | // log.info( |
| | | // "账户持仓频道-产品类型: {}, 保证金模式: {}, 持仓方向: {}, 持仓数量: {}, 开仓平均价: {}, " |
| | | // + "未实现收益: {}, 未实现收益率: {}, 杠杆倍数: {}, 预估强平价: {}, 初始保证金: {}, " |
| | | // + "维持保证金率: {}, 维持保证金: {}, 以美金价值为单位的持仓数量: {}, 占用保证金的币种: {}, " |
| | | // + "最新成交价: {}, 最新指数价格: {}, 盈亏平衡价: {}, 已实现收益: {}, 累计已结算收益: {}" |
| | | // + "最新标记价格: {}", |
| | | // instId, mgnMode, posSide, pos, avgPx, |
| | | // upl, uplRatio, lever, liqPx, imr, |
| | | // mgnRatio, mmr, notionalUsd, ccy, |
| | | // last, idxPx, bePx, realizedPnl, settledPnl, |
| | | // markPx |
| | | // ); |
| | | String mgnMode = posData.getString("mgnMode"); |
| | | String posSide = posData.getString("posSide"); |
| | | String pos = posData.getString("pos"); |
| | | String avgPx = posData.getString("avgPx"); |
| | | String upl = posData.getString("upl"); |
| | | String uplRatio = posData.getString("uplRatio"); |
| | | String lever = posData.getString("lever"); |
| | | String liqPx = posData.getString("liqPx"); |
| | | String markPx = posData.getString("markPx"); |
| | | String imr = posData.getString("imr"); |
| | | String mgnRatio = posData.getString("mgnRatio"); |
| | | String mmr = posData.getString("mmr"); |
| | | String notionalUsd = posData.getString("notionalUsd"); |
| | | String ccy = posData.getString("ccy"); |
| | | String last = posData.getString("last"); |
| | | String idxPx = posData.getString("idxPx"); |
| | | String bePx = posData.getString("bePx"); |
| | | String realizedPnl = posData.getString("realizedPnl"); |
| | | String settledPnl = posData.getString("settledPnl"); |
| | | String fee = posData.getString("fee"); |
| | | String fundingFee = posData.getString("fundingFee"); |
| | | log.info( |
| | | "{}: 账户持仓频道-产品类型: {}, 保证金模式: {}, 持仓方向: {}, 持仓数量: {}, 开仓平均价: {}, " |
| | | + "未实现收益: {}, 未实现收益率: {}, 杠杆倍数: {}, 预估强平价: {}, 初始保证金: {}, " |
| | | + "维持保证金率: {}, 维持保证金: {}, 以美金价值为单位的持仓数量: {}, 占用保证金的币种: {}, " |
| | | + "最新成交价: {}, 最新指数价格: {}, 盈亏平衡价: {}, 已实现收益: {}, 累计已结算收益: {}" |
| | | + "最新标记价格: {},累计手续费: {},累计持仓费: {},", |
| | | 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; |
| | |
| | | log.warn("下单参数 side 为空,取消发送"); |
| | | return; |
| | | } |
| | | String buyCnt = ""; |
| | | if (OrderParamEnums.HOLDING.getValue().equals(side)){ |
| | | log.info("当前状态为持仓中,取消发送"); |
| | | return; |
| | | }else if (OrderParamEnums.OUT.getValue().equals(side)){ |
| | | log.info("当前状态为止损"); |
| | | side = OrderParamEnums.SELL.getValue(); |
| | | buyCnt = String.valueOf(PositionsWs.POSITIONSWSMAP.get("pos")); |
| | | }else if (OrderParamEnums.INIT.getValue().equals(side)){ |
| | | log.info("当前状态为初始化"); |
| | | side = OrderParamEnums.BUY.getValue(); |
| | | buyCnt = InstrumentsWs.INSTRUMENTSWSMAP.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()); |
| | | 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")); |
| | | }else{ |
| | | log.warn("交易状态异常,取消发送"); |
| | | String posSide = InstrumentsWs.getAccountMap(accountName).get(CoinEnums.POSSIDE.name()); |
| | | // 校验必要参数 |
| | | if (StrUtil.isBlank(posSide)) { |
| | | log.warn("下单参数 posSide 为空,取消发送"); |
| | | return; |
| | | } |
| | | |
| | | String buyCnt = ""; |
| | | if (CoinEnums.POSSIDE_LONG.getCode().equals(posSide)){ |
| | | if (OrderParamEnums.HOLDING.getValue().equals(side)){ |
| | | log.info("当前状态为持仓中,取消发送"); |
| | | return; |
| | | }else if (OrderParamEnums.OUT.getValue().equals(side)){ |
| | | log.info("当前状态为止损"); |
| | | side = OrderParamEnums.SELL.getValue(); |
| | | buyCnt = String.valueOf(PositionsWs.getAccountMap(accountName).get("pos")); |
| | | }else if (OrderParamEnums.INIT.getValue().equals(side)){ |
| | | log.info("当前状态为初始化"); |
| | | side = OrderParamEnums.BUY.getValue(); |
| | | buyCnt = InstrumentsWs.getAccountMap(accountName).get(CoinEnums.BUY_CNT_INIT.name()); |
| | | }else if (OrderParamEnums.BUY.getValue().equals(side)){ |
| | | log.info("当前状态为加仓"); |
| | | 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.getAccountMap(accountName).get("pos")); |
| | | }else{ |
| | | log.warn("交易状态异常,取消发送"); |
| | | return; |
| | | } |
| | | }else if (CoinEnums.POSSIDE_SHORT.getCode().equals(posSide)){ |
| | | if (OrderParamEnums.HOLDING.getValue().equals(side)){ |
| | | log.info("当前状态为持仓中,取消发送"); |
| | | return; |
| | | }else if (OrderParamEnums.OUT.getValue().equals(side)){ |
| | | log.info("当前状态为止损"); |
| | | side = OrderParamEnums.BUY.getValue(); |
| | | buyCnt = String.valueOf(PositionsWs.getAccountMap(accountName).get("pos")); |
| | | }else if (OrderParamEnums.INIT.getValue().equals(side)){ |
| | | log.info("当前状态为初始化"); |
| | | side = OrderParamEnums.SELL.getValue(); |
| | | buyCnt = InstrumentsWs.getAccountMap(accountName).get(CoinEnums.BUY_CNT_INIT.name()); |
| | | }else if (OrderParamEnums.BUY.getValue().equals(side)){ |
| | | log.info("当前状态为减仓"); |
| | | buyCnt = String.valueOf(PositionsWs.getAccountMap(accountName).get("pos")); |
| | | }else if (OrderParamEnums.SELL.getValue().equals(side)){ |
| | | log.info("当前状态为加仓"); |
| | | 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{ |
| | | log.warn("交易状态异常,取消发送"); |
| | | return; |
| | | } |
| | | } |
| | | |
| | | |
| | | if (StrUtil.isBlank(buyCnt)) { |
| | | log.warn("下单数量 buyCnt 为空,取消发送"); |
| | |
| | | args.put("tdMode", CoinEnums.CROSS.getCode()); |
| | | args.put("clOrdId", clOrdId); |
| | | args.put("side", side); |
| | | args.put("posSide", CoinEnums.POSSIDE_LONG.getCode()); |
| | | |
| | | args.put("posSide", posSide); |
| | | args.put("ordType", CoinEnums.ORDTYPE_MARKET.getCode()); |
| | | args.put("sz", buyCnt); |
| | | argsArray.add(args); |
| | |
| | | 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); |
| | |
| | | |
| | | PING_CANG_SHOUYI("平仓收益比例", "0.1"), |
| | | //下单的总保障金为账户总金额cashBal * TOTAL_ORDER_USDT用来做保证金 |
| | | TOTAL_ORDER_USDTPECENT("总保证金比例total_order_usdtpecent","0.2"), |
| | | TOTAL_ORDER_USDTPECENT("总保证金比例total_order_usdtpecent","0.05"), |
| | | TOTAL_ORDER_USDT("总保证金totalOrderUsdt","0"), |
| | | KANG_CANG("抗压比例KANG_CANG","0.6"), |
| | | ZHI_SUN("止损比例ZHI_SUN","0.5"), |
| | | KANG_CANG("抗压比例KANG_CANG","0.7"), |
| | | ZHI_SUN("止损比例ZHI_SUN","0.6"), |
| | | //每次下单的张数 |
| | | BUY_CNT("每次开仓的张数buyCnt","0.5"), |
| | | BUY_CNT_TIME("每次开仓张数的倍数基础值buyCntTime","10"), |
| | | BUY_CNT("每次开仓的张数buyCnt","0.1"), |
| | | BUY_CNT_INIT("每次初始化开仓张数的基础值buyCntInit","0.2"), |
| | | BUY_CNT_TIME("每次开仓张数的倍数基础值buyCntTime","5"), |
| | | OUT("是否允许下单out","操作中"), |
| | | CTVAL("合约面值ctVal","0.1"), |
| | | TICKSZ("下单价格精度tickSz","2"), |
| | | MINSZ("最小下单数小数位minSz","2"), |
| | | LEVERAGE("合约杠杆leverage","100"), |
| | | HE_YUE("合约instId","ETH-USDT-SWAP"); |
| | | HE_YUE("合约instId","ETH-USDT-SWAP"), |
| | | POSSIDE("持仓方向posSide","short"); |
| | | |
| | | private String name; |
| | | |
| | |
| | | public enum ExchangeInfoEnum { |
| | | |
| | | /** |
| | | * 模拟盘账户信息 |
| | | * 模拟盘账户1信息 |
| | | * 存储了模拟盘交易所需的API密钥、秘钥和通过码 |
| | | */ |
| | | OKX_UAT("ffb4e79f-fcf5-4afb-82c5-2fbb64123f61", |
| | | // OKX_PRD_xiao("f512673b-2685-4fcb-9bb1-2ae8db745d62", |
| | | // "B0C1CC8F39625B41140D93DC25039E33", |
| | | // "Aa12345678@", |
| | | // true), |
| | | OKX_UAT_ceshi("ffb4e79f-fcf5-4afb-82c5-2fbb64123f61", |
| | | "AA06C5ED1D7C7F5AFE6484052E231C55", |
| | | "Aa12345678@", |
| | | false), |
| | | // |
| | | // /** |
| | | // * 模拟盘账户2信息 |
| | | // * 存储了模拟盘交易所需的API密钥、秘钥和通过码 |
| | | // */ |
| | | // OKX_PRD_wang("72e380a6-4133-451b-8b10-8b1905b30717", |
| | | // "2A5BD55BF0771F1ADF08AE0A2FB4D561", |
| | | // "Aa12345678@", |
| | | // true); |
| | | 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; |
| | | } |
| | | |