refactor(okxNewPrice): 账户配置
| | |
| | | if (state == StrategyState.WAITING_KLINE) { |
| | | state = StrategyState.OPENING; |
| | | log.info("[{}] 首根K线到达,开基底仓位...", config.getContract()); |
| | | executor.openLong(config.getQuantity(), () -> { |
| | | log.info("[{}] 基底多单已提交", config.getContract()); |
| | | }, null); |
| | | executor.openShort(config.getQuantity(), () -> { |
| | | log.info("[{}] 基底空单已提交", config.getContract()); |
| | | }, null); |
| | | executor.openLong(config.getQuantity(), null, null); |
| | | executor.openShort(config.getQuantity(), null, null); |
| | | return; |
| | | } |
| | | |
| | |
| | | } |
| | | |
| | | cumulativePnl = longRealizedPnl.add(shortRealizedPnl); |
| | | log.info("[{}] 持仓更新, 多已实现盈亏:{}, 空已实现盈亏:{}, 累计:{}", |
| | | config.getContract(), longRealizedPnl, shortRealizedPnl, cumulativePnl); |
| | | |
| | | if (state == StrategyState.WAITING_KLINE) { |
| | | return; |
| | |
| | | return; |
| | | } |
| | | |
| | | log.info("[{}] 订单成交, 方向:{}, 数量:{}, 成交价:{}, 盈亏:{}", |
| | | config.getContract(), posSide, fillSz, avgPx, pnl); |
| | | |
| | | if (OkxEnums.POSSIDE_LONG.equals(posSide)) { |
| | | if (!baseLongOpened) { |
| | | longBaseEntryPrice = avgPx; |
| | |
| | | log.info("[{}] 基底多成交价: {}", config.getContract(), longBaseEntryPrice); |
| | | tryGenerateQueues(); |
| | | } else if (fillSz.compareTo(BigDecimal.ZERO) > 0) { |
| | | if (longPriceQueue.isEmpty()) { |
| | | log.warn("[{}] 多仓队列为空,无法设止盈", config.getContract()); |
| | | } else { |
| | | if (!longPriceQueue.isEmpty()) { |
| | | BigDecimal tpPrice = longPriceQueue.get(0); |
| | | executor.placeTakeProfit(tpPrice, OkxEnums.ORDER_TYPE_CLOSE_LONG, config.getQuantity()); |
| | | log.info("[{}] 多单止盈已设, 成交价:{}, tp:{}, size:{}", |
| | |
| | | log.info("[{}] 基底空成交价: {}", config.getContract(), shortBaseEntryPrice); |
| | | tryGenerateQueues(); |
| | | } else if (fillSz.compareTo(BigDecimal.ZERO) > 0) { |
| | | if (shortPriceQueue.isEmpty()) { |
| | | log.warn("[{}] 空仓队列为空,无法设止盈", config.getContract()); |
| | | } else { |
| | | if (!shortPriceQueue.isEmpty()) { |
| | | BigDecimal tpPrice = shortPriceQueue.get(0); |
| | | executor.placeTakeProfit(tpPrice, OkxEnums.ORDER_TYPE_CLOSE_SHORT, config.getQuantity()); |
| | | log.info("[{}] 空单止盈已设, 成交价:{}, tp:{}, size:{}", |
| | |
| | | } |
| | | } |
| | | } |
| | | log.info("[{}] 原空队列:{}", config.getContract(), shortPriceQueue); |
| | | if (matched.isEmpty()) { |
| | | log.info("[{}] 空仓队列未触发, 当前价:{}", config.getContract(), currentPrice); |
| | | return; |
| | | } |
| | | log.info("[{}] 空仓队列触发, 匹配{}个元素, 当前价:{}", config.getContract(), matched.size(), currentPrice); |
| | |
| | | } |
| | | } |
| | | } |
| | | log.info("[{}] 原多队列:{}", config.getContract(), longPriceQueue); |
| | | if (matched.isEmpty()) { |
| | | log.info("[{}] 多仓队列未触发, 当前价:{}", config.getContract(), currentPrice); |
| | | return; |
| | | } |
| | | log.info("[{}] 多仓队列触发, 匹配{}个元素, 当前价:{}", config.getContract(), matched.size(), currentPrice); |
| | |
| | | ? tail.subtract(fixedStep).setScale(1, RoundingMode.HALF_UP) |
| | | : tail.add(fixedStep).setScale(1, RoundingMode.HALF_UP); |
| | | queue.add(tail); |
| | | log.info("[{}] {}队列增加:{}", config.getContract(), label, tail); |
| | | } |
| | | queue.sort(comparator); |
| | | log.info("[{}] 现{}队列:{}", config.getContract(), label, queue); |
| | | } |
| | | } |
| | | |
| | |
| | | : first.subtract(offset).setScale(1, RoundingMode.HALF_UP); |
| | | if (filterEntryPrice.compareTo(BigDecimal.ZERO) > 0 |
| | | && currentPrice.subtract(filterEntryPrice).abs().compareTo(filterEntryPrice.multiply(config.getGridRate())) < 0) { |
| | | log.info("[{}] {}队列跳过(price≈entry):{}", config.getContract(), label, elem); |
| | | continue; |
| | | } |
| | | targetQueue.add(elem); |
| | | log.info("[{}] {}队列增加:{}", config.getContract(), label, elem); |
| | | } |
| | | targetQueue.sort(comparator); |
| | | while (targetQueue.size() > config.getGridQueueSize()) { |
| | | targetQueue.remove(targetQueue.size() - 1); |
| | | } |
| | | log.info("[{}] 现{}队列:{}", config.getContract(), label, targetQueue); |
| | | } |
| | | } |
| | | |
| | |
| | | shortPnl = shortPositionSize.multiply(multiplier).multiply(shortEntryPrice.subtract(price)); |
| | | } |
| | | unrealizedPnl = longPnl.add(shortPnl); |
| | | log.info("[{}] 未实现盈亏: {}", config.getContract(), unrealizedPnl); |
| | | } |
| | | |
| | | public BigDecimal getLastKlinePrice() { return lastKlinePrice; } |
| | |
| | | |
| | | JSONObject login = OkxWsUtil.buildJsonObject(null, "login", argsArray); |
| | | webSocketClient.send(login.toJSONString()); |
| | | log.info("[WS] 发送登录请求"); |
| | | } catch (Exception e) { |
| | | log.error("[WS] 登录请求构建失败", e); |
| | | } |
| | |
| | | return; |
| | | } |
| | | if ("subscribe".equals(event)) { |
| | | log.info("[WS] 订阅成功: {}", response.getJSONObject("arg")); |
| | | return; |
| | | } |
| | | if ("unsubscribe".equals(event)) { |
| | | log.info("[WS] 取消订阅成功: {}", response.getJSONObject("arg")); |
| | | return; |
| | | } |
| | | if ("error".equals(event)) { |
| | |
| | | } |
| | | String op = response.getString("op"); |
| | | if ("order".equals(op) || "batch-orders".equals(op)) { |
| | | log.info("[WS] 收到下单推送结果: {}", JSON.toJSONString(response.get("data"))); |
| | | JSONArray dataArr = response.getJSONArray("data"); |
| | | if (dataArr != null && !dataArr.isEmpty()) { |
| | | JSONObject first = dataArr.getJSONObject(0); |
| | | String sCode = first.getString("sCode"); |
| | | if (sCode != null && !"0".equals(sCode)) { |
| | | log.error("[WS] 下单失败, sCode:{}, sMsg:{}", sCode, first.getString("sMsg")); |
| | | } |
| | | } |
| | | return; |
| | | } |
| | | for (OkxChannelHandler handler : channelHandlers) { |
| | |
| | | |
| | | int code = conn.getResponseCode(); |
| | | String response = readResponse(conn); |
| | | log.info("[REST] GET {} → HTTP {} body:{}", path, code, response); |
| | | if (code < 200 || code >= 300) { |
| | | log.error("[REST] GET {} → HTTP {} body:{}", path, code, response); |
| | | } |
| | | |
| | | return JSON.parseObject(response); |
| | | } catch (Exception e) { |
| | |
| | | |
| | | int code = conn.getResponseCode(); |
| | | String response = readResponse(conn); |
| | | log.info("[REST] POST {} → HTTP {} body:{}", path, code, response); |
| | | if (code < 200 || code >= 300) { |
| | | log.error("[REST] POST {} → HTTP {} body:{}", path, code, response); |
| | | } |
| | | |
| | | return JSON.parseObject(response); |
| | | } catch (Exception e) { |
| | |
| | | |
| | | String connId = OkxWsUtil.getOrderNum("order"); |
| | | JSONObject msg = OkxWsUtil.buildJsonObject(connId, "order", argsArray); |
| | | String msgStr = msg.toJSONString(); |
| | | log.info("[TradeExec] 发送下单: {}", msgStr); |
| | | wsClient.send(msgStr); |
| | | log.info("[TradeExec] 下单已发送: side={}, sz={}, instId={}", param.getSide(), param.getSz(), param.getInstId()); |
| | | wsClient.send(msg.toJSONString()); |
| | | } |
| | | |
| | | private void sendBatchOrders(List<TradeRequestParam> params) { |
| | |
| | | args.add(arg); |
| | | msg.put("args", args); |
| | | ws.send(msg.toJSONString()); |
| | | log.info("[{}] 订阅成功", CHANNEL_NAME); |
| | | } |
| | | |
| | | @Override |
| | |
| | | args.add(arg); |
| | | msg.put("args", args); |
| | | ws.send(msg.toJSONString()); |
| | | log.info("[{}] 取消订阅成功", CHANNEL_NAME); |
| | | } |
| | | |
| | | @Override |
| | |
| | | } |
| | | for (int i = 0; i < dataArray.size(); i++) { |
| | | JSONObject acct = dataArray.getJSONObject(i); |
| | | log.info("[{}] 账户更新, 总权益:{}, 未实现盈亏:{}, 保证金:{}", |
| | | CHANNEL_NAME, |
| | | acct.get("totalEq"), acct.get("upl"), acct.get("imr")); |
| | | |
| | | JSONArray details = acct.getJSONArray("details"); |
| | | if (details != null) { |
| | | for (int j = 0; j < details.size(); j++) { |
| | | JSONObject detail = details.getJSONObject(j); |
| | | if ("USDT".equals(detail.getString("ccy"))) { |
| | | log.info("[{}] USDT可用余额:{}, 权益:{}", |
| | | CHANNEL_NAME, |
| | | detail.get("availBal"), detail.get("eq")); |
| | | } |
| | | } |
| | | } |
| | | } |
| | | } catch (Exception e) { |
| | | log.error("[{}] 处理数据失败", CHANNEL_NAME, e); |
| | | } |
| | |
| | | args.add(arg); |
| | | msg.put("args", args); |
| | | ws.send(msg.toJSONString()); |
| | | log.info("[{}] 订阅成功, 合约:{}, 周期:1m", OkxEnums.CHANNEL_CANDLE, instId); |
| | | } |
| | | |
| | | @Override |
| | |
| | | args.add(arg); |
| | | msg.put("args", args); |
| | | ws.send(msg.toJSONString()); |
| | | log.info("[{}] 取消订阅成功", OkxEnums.CHANNEL_CANDLE); |
| | | } |
| | | |
| | | @Override |
| | |
| | | try { |
| | | JSONArray dataArray = response.getJSONArray("data"); |
| | | if (dataArray == null || dataArray.isEmpty()) { |
| | | log.warn("[{}] 数据为空", OkxEnums.CHANNEL_CANDLE); |
| | | return true; |
| | | } |
| | | JSONArray data = dataArray.getJSONArray(0); |
| | | BigDecimal closePx = new BigDecimal(data.getString(4)); |
| | | String time = OkxWsUtil.timestampToDateTime(Long.parseLong(data.getString(0))); |
| | | String confirm = data.getString(8); |
| | | |
| | | log.info("[{}] 收盘:{}, 时间:{}, 完结:{}", OkxEnums.CHANNEL_CANDLE, closePx, time, "1".equals(confirm)); |
| | | |
| | | if (gridTradeService != null) { |
| | | gridTradeService.onKline(closePx); |
| | |
| | | args.add(arg); |
| | | msg.put("args", args); |
| | | ws.send(msg.toJSONString()); |
| | | log.info("[{}] 订阅成功, 合约:{}", CHANNEL_NAME, instId); |
| | | } |
| | | |
| | | @Override |
| | |
| | | args.add(arg); |
| | | msg.put("args", args); |
| | | ws.send(msg.toJSONString()); |
| | | log.info("[{}] 取消订阅成功", CHANNEL_NAME); |
| | | } |
| | | |
| | | @Override |
| | |
| | | } |
| | | String state = detail.getString("state"); |
| | | String accFillSz = detail.getString("accFillSz"); |
| | | String fillPx = detail.getString("fillPx"); |
| | | String pnl = detail.getString("pnl"); |
| | | String posSide = detail.getString("posSide"); |
| | | String avgPx = detail.getString("avgPx"); |
| | | String clOrdId = detail.getString("clOrdId"); |
| | | |
| | | log.info("[{}] 订单, 方向:{}, 状态:{}, 成交量:{}, 末笔成交价:{}, 均价:{}, 盈亏:{}, 编号:{}", |
| | | CHANNEL_NAME, posSide, state, accFillSz, fillPx, avgPx, pnl, clOrdId); |
| | | |
| | | if ("filled".equals(state) && accFillSz != null && new BigDecimal(accFillSz).compareTo(BigDecimal.ZERO) > 0) { |
| | | if (gridTradeService != null) { |
| | |
| | | args.add(arg); |
| | | msg.put("args", args); |
| | | ws.send(msg.toJSONString()); |
| | | log.info("[{}] 订阅成功, 合约:{}", OkxEnums.CHANNEL_POSITIONS, instId); |
| | | } |
| | | |
| | | @Override |
| | |
| | | args.add(arg); |
| | | msg.put("args", args); |
| | | ws.send(msg.toJSONString()); |
| | | log.info("[{}] 取消订阅成功", OkxEnums.CHANNEL_POSITIONS); |
| | | } |
| | | |
| | | @Override |
| | |
| | | ? new BigDecimal(pos.getString("avgPx")) : BigDecimal.ZERO; |
| | | BigDecimal realizedPnl = pos.containsKey("realizedPnl") && pos.getString("realizedPnl") != null |
| | | ? new BigDecimal(pos.getString("realizedPnl")) : BigDecimal.ZERO; |
| | | |
| | | log.info("[{}] 持仓更新, 方向:{}, 数量:{}, 均价:{}, 未实现盈亏:{}, 已实现盈亏:{}, 保证金:{}", |
| | | OkxEnums.CHANNEL_POSITIONS, posSide, size, avgPx, |
| | | pos.get("upl"), realizedPnl, pos.get("imr")); |
| | | |
| | | if (gridTradeService != null) { |
| | | gridTradeService.onPositionUpdate(posSide, size, avgPx, realizedPnl); |