Administrator
2025-12-23 c5faaa1111091280365100c95e7e06930b98ee4b
feat(indicator): 添加15分钟交易策略和指标详细说明

- 为AdvancedMA、BOLL、KDJ、MA等指标添加详细作用说明和参数类型说明
- 添加推荐时间粒度及优缺点分析
- 实现15分钟交易策略FifteenMinuteTradingStrategy
- 添加FifteenMinuteStrategyExample和FifteenMinuteTradingExample示例
- 在CaoZuoService中添加caoZuoStrategy方法
- 实现操作策略逻辑,包括止损抗压判断和仓位管理
- 修改默认开仓张数从0.1调整为0.5
- 添加Kline参数类用于K线数据处理
- 扩展IndicatorBase添加ATR和波动率计算功能
11 files modified
7 files added
2451 ■■■■■ changed files
src/main/java/com/xcong/excoin/modules/okxNewPrice/OkxKlineWebSocketClient.java 652 ●●●●● patch | view | raw | blame | history
src/main/java/com/xcong/excoin/modules/okxNewPrice/OkxWebSocketClientManager.java 17 ●●●● patch | view | raw | blame | history
src/main/java/com/xcong/excoin/modules/okxNewPrice/celue/CaoZuoService.java 2 ●●●●● patch | view | raw | blame | history
src/main/java/com/xcong/excoin/modules/okxNewPrice/celue/CaoZuoServiceImpl.java 76 ●●●●● patch | view | raw | blame | history
src/main/java/com/xcong/excoin/modules/okxNewPrice/indicator/AdvancedMA.java 25 ●●●●● patch | view | raw | blame | history
src/main/java/com/xcong/excoin/modules/okxNewPrice/indicator/BOLL.java 26 ●●●●● patch | view | raw | blame | history
src/main/java/com/xcong/excoin/modules/okxNewPrice/indicator/FifteenMinuteStrategyExample.java 81 ●●●●● patch | view | raw | blame | history
src/main/java/com/xcong/excoin/modules/okxNewPrice/indicator/FifteenMinuteTradingExample.java 214 ●●●●● patch | view | raw | blame | history
src/main/java/com/xcong/excoin/modules/okxNewPrice/indicator/FifteenMinuteTradingStrategy.java 237 ●●●●● patch | view | raw | blame | history
src/main/java/com/xcong/excoin/modules/okxNewPrice/indicator/IndicatorBase.java 67 ●●●●● patch | view | raw | blame | history
src/main/java/com/xcong/excoin/modules/okxNewPrice/indicator/KDJ.java 25 ●●●●● patch | view | raw | blame | history
src/main/java/com/xcong/excoin/modules/okxNewPrice/indicator/MA.java 133 ●●●● patch | view | raw | blame | history
src/main/java/com/xcong/excoin/modules/okxNewPrice/indicator/MACD.java 95 ●●●●● patch | view | raw | blame | history
src/main/java/com/xcong/excoin/modules/okxNewPrice/indicator/RSI.java 26 ●●●●● patch | view | raw | blame | history
src/main/java/com/xcong/excoin/modules/okxNewPrice/indicator/TradingStrategy.java 738 ●●●●● patch | view | raw | blame | history
src/main/java/com/xcong/excoin/modules/okxNewPrice/okxWs/enums/CoinEnums.java 4 ●●●● patch | view | raw | blame | history
src/main/java/com/xcong/excoin/modules/okxNewPrice/okxWs/param/Kline.java 22 ●●●●● patch | view | raw | blame | history
src/main/java/com/xcong/excoin/modules/okxNewPrice/okxWs/param/StrategyParam.java 11 ●●●●● patch | view | raw | blame | history
src/main/java/com/xcong/excoin/modules/okxNewPrice/OkxKlineWebSocketClient.java
New file
@@ -0,0 +1,652 @@
package com.xcong.excoin.modules.okxNewPrice;
import cn.hutool.core.collection.CollUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.xcong.excoin.modules.okxNewPrice.celue.CaoZuoService;
import com.xcong.excoin.modules.okxNewPrice.indicator.TradingStrategy;
import com.xcong.excoin.modules.okxNewPrice.okxWs.*;
import com.xcong.excoin.modules.okxNewPrice.okxWs.enums.CoinEnums;
import com.xcong.excoin.modules.okxNewPrice.okxWs.enums.OrderParamEnums;
import com.xcong.excoin.modules.okxNewPrice.okxWs.param.Kline;
import com.xcong.excoin.modules.okxNewPrice.okxWs.param.TradeRequestParam;
import com.xcong.excoin.modules.okxNewPrice.okxWs.wanggeList.WangGeListEnum;
import com.xcong.excoin.modules.okxNewPrice.okxWs.wanggeList.WangGeListService;
import com.xcong.excoin.modules.okxNewPrice.okxpi.config.ExchangeInfoEnum;
import com.xcong.excoin.modules.okxNewPrice.okxpi.config.ExchangeLoginService;
import com.xcong.excoin.modules.okxNewPrice.utils.SSLConfig;
import com.xcong.excoin.modules.okxNewPrice.utils.WsParamBuild;
import com.xcong.excoin.utils.RedisUtils;
import lombok.extern.slf4j.Slf4j;
import org.java_websocket.client.WebSocketClient;
import org.java_websocket.handshake.ServerHandshake;
import java.math.BigDecimal;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Collectors;
/**
 * OKX 新价格 WebSocket 客户端类,用于连接 OKX 的 WebSocket 接口,
 * 实时获取并处理标记价格(mark price)数据,并将价格信息存储到 Redis 中。
 * 同时支持心跳检测、自动重连以及异常恢复机制。
 * @author Administrator
 */
@Slf4j
public class OkxKlineWebSocketClient {
    private final RedisUtils redisUtils;
    private final CaoZuoService caoZuoService;
    private final OkxWebSocketClientManager clientManager;
    private final WangGeListService wangGeListService;
    private WebSocketClient webSocketClient;
    private ScheduledExecutorService heartbeatExecutor;
    private volatile ScheduledFuture<?> pongTimeoutFuture;
    private final AtomicReference<Long> lastMessageTime = new AtomicReference<>(System.currentTimeMillis());
    // 连接状态标志
    private final AtomicBoolean isConnected = new AtomicBoolean(false);
    private final AtomicBoolean isConnecting = new AtomicBoolean(false);
    private final AtomicBoolean isInitialized = new AtomicBoolean(false);
    private static final String CHANNEL = "candle15m";
    // 心跳超时时间(秒),小于30秒
    private static final int HEARTBEAT_TIMEOUT = 10;
    // 共享线程池用于重连等异步任务
    private final ExecutorService sharedExecutor = Executors.newCachedThreadPool(r -> {
        Thread t = new Thread(r, "okx-ws-kline-worker");
        t.setDaemon(true);
        return t;
    });
    public OkxKlineWebSocketClient(RedisUtils redisUtils,
                                   CaoZuoService caoZuoService, OkxWebSocketClientManager clientManager,
                                   WangGeListService wangGeListService) {
        this.redisUtils = redisUtils;
        this.caoZuoService = caoZuoService;
        this.clientManager = clientManager;
        this.wangGeListService = wangGeListService;
    }
    /**
     * 初始化方法,创建并初始化WebSocket客户端实例
     */
    public void init() {
        if (!isInitialized.compareAndSet(false, true)) {
            log.warn("OkxKlineWebSocketClient 已经初始化过,跳过重复初始化");
            return;
        }
        connect();
        startHeartbeat();
    }
    /**
     * 销毁方法,关闭WebSocket连接和相关资源
     */
    public void destroy() {
        log.info("开始销毁OkxKlineWebSocketClient");
        // 设置关闭标志,避免重连
        if (sharedExecutor != null && !sharedExecutor.isShutdown()) {
            sharedExecutor.shutdown();
        }
        if (webSocketClient != null && webSocketClient.isOpen()) {
            try {
                webSocketClient.closeBlocking();
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                log.warn("关闭WebSocket连接时被中断");
            }
        }
        shutdownExecutorGracefully(heartbeatExecutor);
        if (pongTimeoutFuture != null) {
            pongTimeoutFuture.cancel(true);
        }
        shutdownExecutorGracefully(sharedExecutor);
        log.info("OkxKlineWebSocketClient销毁完成");
    }
    private static final String WS_URL_MONIPAN = "wss://wspap.okx.com:8443/ws/v5/business";
    private static final String WS_URL_SHIPAN = "wss://ws.okx.com:8443/ws/v5/business";
    private static final boolean isAccountType = true;
    /**
     * 建立与 OKX WebSocket 服务器的连接。
     * 设置回调函数以监听连接打开、接收消息、关闭和错误事件。
     */
    private void connect() {
        // 避免重复连接
        if (isConnecting.get()) {
            log.info("连接已在进行中,跳过重复连接请求");
            return;
        }
        if (!isConnecting.compareAndSet(false, true)) {
            log.info("连接已在进行中,跳过重复连接请求");
            return;
        }
        try {
            SSLConfig.configureSSL();
            System.setProperty("https.protocols", "TLSv1.2,TLSv1.3");
            String WS_URL = WS_URL_MONIPAN;
            if (isAccountType){
                WS_URL = WS_URL_SHIPAN;
            }
            URI uri = new URI(WS_URL);
            // 关闭之前的连接(如果存在)
            if (webSocketClient != null) {
                try {
                    webSocketClient.closeBlocking();
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                    log.warn("关闭之前连接时被中断");
                }
            }
            webSocketClient = new WebSocketClient(uri) {
                @Override
                public void onOpen(ServerHandshake handshake) {
                    log.info("OKX kline WebSocket连接成功");
                    isConnected.set(true);
                    isConnecting.set(false);
                    // 检查应用是否正在关闭
                    if (sharedExecutor != null && !sharedExecutor.isShutdown()) {
                        resetHeartbeatTimer();
                        subscribeChannels();
                    } else {
                        log.warn("应用正在关闭,忽略WebSocket连接成功回调");
                    }
                }
                @Override
                public void onMessage(String message) {
                    lastMessageTime.set(System.currentTimeMillis());
                    handleWebSocketMessage(message);
                    resetHeartbeatTimer();
                }
                @Override
                public void onClose(int code, String reason, boolean remote) {
                    log.warn("OKX kline WebSocket连接关闭: code={}, reason={}", code, reason);
                    isConnected.set(false);
                    isConnecting.set(false);
                    cancelPongTimeout();
                    if (sharedExecutor != null && !sharedExecutor.isShutdown() && !sharedExecutor.isTerminated()) {
                        sharedExecutor.execute(() -> {
                            try {
                                reconnectWithBackoff();
                            } catch (InterruptedException e) {
                                Thread.currentThread().interrupt();
                                log.error("重连线程被中断", e);
                            } catch (Exception e) {
                                log.error("重连失败", e);
                            }
                        });
                    } else {
                        log.warn("共享线程池已关闭,无法执行重连任务");
                    }
                }
                @Override
                public void onError(Exception ex) {
                    log.error("OKX New Price WebSocket发生错误", ex);
                    isConnected.set(false);
                }
            };
            webSocketClient.connect();
        } catch (URISyntaxException e) {
            log.error("WebSocket URI格式错误", e);
            isConnecting.set(false);
        }
    }
    /**
     * 订阅指定交易对的价格通道。
     * 构造订阅请求并发送给服务端。
     */
    private void subscribeChannels() {
        JSONObject subscribeMsg = new JSONObject();
        subscribeMsg.put("op", "subscribe");
        JSONArray argsArray = new JSONArray();
        JSONObject arg = new JSONObject();
        arg.put("channel", CHANNEL);
        arg.put("instId", CoinEnums.HE_YUE.getCode());
        argsArray.add(arg);
        subscribeMsg.put("args", argsArray);
        webSocketClient.send(subscribeMsg.toJSONString());
        log.info("已发送 K线频道订阅请求,订阅通道数: {}", argsArray.size());
    }
    /**
     * 处理从 WebSocket 收到的消息。
     * 包括订阅确认、错误响应、心跳响应以及实际的数据推送。
     *
     * @param message 来自 WebSocket 的原始字符串消息
     */
    private void handleWebSocketMessage(String message) {
        try {
            JSONObject response = JSON.parseObject(message);
            String event = response.getString("event");
            if ("subscribe".equals(event)) {
                log.info(" K线频道订阅成功: {}", response.getJSONObject("arg"));
            } else if ("error".equals(event)) {
                log.error(" K线频道订阅错误: code={}, msg={}",
                         response.getString("code"), response.getString("msg"));
            } else if ("pong".equals(event)) {
                log.debug("收到pong响应");
                cancelPongTimeout();
            } else {
                processPushData(response);
            }
        } catch (Exception e) {
            log.error("处理WebSocket消息失败: {}", message, e);
        }
    }
    /**
     * 解析并处理价格推送数据。
     * 将最新的标记价格存入 Redis 并触发后续业务逻辑比较处理。
     * 当价格变化时,调用CaoZuoService的caoZuo方法,触发所有账号的量化操作
     *
     * @param response 包含价格数据的 JSON 对象
     */
    private void processPushData(JSONObject response) {
        try {
            /**
             * {
             *   "arg": {
             *     "channel": "candle1D",
             *     "instId": "BTC-USDT"
             *   },
             *   "data": [
             *     [
             *       "1629993600000",
             *       "42500",
             *       "48199.9",
             *       "41006.1",
             *       "41006.1",
             *       "3587.41204591",
             *       "166741046.22583129",
             *       "166741046.22583129",
             *       "0"
             *     ]
             *   ]
             * }
             */
            JSONObject arg = response.getJSONObject("arg");
            if (arg == null) {
                log.warn("{}: 无效的推送数据,缺少 'arg' 字段", response);
                return;
            }
            String channel = arg.getString("channel");
            if (channel == null) {
                log.warn("{}: 无效的推送数据,缺少 'channel' 字段", response);
                return;
            }
            String instId = arg.getString("instId");
            if (instId == null) {
                log.warn("{}: 无效的推送数据,缺少 'instId' 字段", response);
                return;
            }
            if (CHANNEL.equals(channel) && CoinEnums.HE_YUE.getCode().equals(instId)) {
                JSONArray dataArray = response.getJSONArray("data");
                if (dataArray == null || dataArray.isEmpty()) {
                    log.warn("K线频道数据为空");
                    return;
                }
                JSONArray data = dataArray.getJSONArray(0);
                BigDecimal openPx = new BigDecimal(data.getString(1));
                BigDecimal highPx = new BigDecimal(data.getString(2));
                BigDecimal lowPx = new BigDecimal(data.getString(3));
                BigDecimal closePx = new BigDecimal(data.getString(4));
                BigDecimal vol = new BigDecimal(data.getString(5));
                /**
                 * K线状态
                 * 0:K线未完结
                 * 1:K线已完结
                 */
                String confirm = data.getString(8);
                if ("1".equals(confirm)){
                    //调用策略
                    // 创建交易策略
                    TradingStrategy tradingStrategy = new TradingStrategy();
                    // 生成100个15分钟价格数据点
                    List<Kline> kline15MinuteData = getKlineDataByInstIdAndBar(instId, "15m");
                    //stream流获取kline15MinuteData中的o数据的集合
                    List<BigDecimal> prices = kline15MinuteData.stream()
                            .map(Kline::getO)
                            .collect(Collectors.toList());
                    // 生成对应的高、低、收盘价数据
                    List<BigDecimal> high = kline15MinuteData.stream()
                            .map(Kline::getH)
                            .collect(Collectors.toList());
                    List<BigDecimal> low = kline15MinuteData.stream()
                            .map(Kline::getL)
                            .collect(Collectors.toList());
                    List<BigDecimal> close = kline15MinuteData.stream()
                            .map(Kline::getC)
                            .collect(Collectors.toList());
                    // 生成成交量数据
                    List<BigDecimal> volume = kline15MinuteData.stream()
                            .map(Kline::getVol)
                            .collect(Collectors.toList());
                    // 获取最新价格
                    BigDecimal currentPrice = closePx;
                    // 生成多周期价格数据(5分钟、1小时、4小时)
                    List<Kline> kline5MinuteData = getKlineDataByInstIdAndBar(instId, "5m");
                    List<BigDecimal> fiveMinPrices = kline5MinuteData.stream()
                            .map(Kline::getC)
                            .collect(Collectors.toList());
                    List<Kline> kline60MinuteData = getKlineDataByInstIdAndBar(instId, "1H");
                    List<BigDecimal> oneHourPrices = kline60MinuteData.stream()
                            .map(Kline::getC)
                            .collect(Collectors.toList());
                    List<Kline> kline240MinuteData = getKlineDataByInstIdAndBar(instId, "4H");
                    List<BigDecimal> fourHourPrices = kline240MinuteData.stream()
                            .map(Kline::getC)
                            .collect(Collectors.toList());
                    // 其他参数
                    BigDecimal fundingRate = new BigDecimal("0.001"); // 正常资金费率
                    boolean hasLargeTransfer = false; // 无大额转账
                    boolean hasUpcomingEvent = false; // 无即将到来的重大事件
                    // 确定市场方向
                    TradingStrategy.Direction direction = tradingStrategy.getDirection(prices, high, low, close, currentPrice);
                    System.out.println("市场方向(15分钟): " + direction);
                    if (direction == TradingStrategy.Direction.RANGING){
                        return;
                    }
                    /**
                     * 获取当前网格信息
                     *      根据当前网格的持仓方向获取反方向是否存在持仓
                     *      如果持有,直接止损
                     */
                    Collection<OkxQuantWebSocketClient> allClients = clientManager.getAllClients();
                    //如果为空,则直接返回
                    if (allClients.isEmpty()) {
                        return;
                    }
                    // 获取所有OkxQuantWebSocketClient实例
                    for (OkxQuantWebSocketClient client : clientManager.getAllClients()) {
                        String accountName = client.getAccountName();
                        if (accountName != null) {
                            TradingStrategy.SignalType signal = TradingStrategy.SignalType.NONE;
                            TradeRequestParam tradeRequestParam = new TradeRequestParam();
                            // 检查当前持仓状态
                            boolean hasLongPosition = false;  // 示例:无当前做多持仓
                            boolean hasShortPosition = false; // 示例:无当前做空持仓
                            //先判断账户是否有持多仓
                            String positionLongAccountName = PositionsWs.initAccountName(accountName, CoinEnums.POSSIDE_LONG.getCode());
                            BigDecimal imrLong = PositionsWs.getAccountMap(positionLongAccountName).get("imr");
                            if (imrLong != null && imrLong.compareTo(BigDecimal.ZERO) > 0){
                                log.info("账户{}有持多仓", accountName);
                                hasLongPosition = true;
                            }
                            //先判断账户是否有持空仓
                            String positionShortAccountName = PositionsWs.initAccountName(accountName, CoinEnums.POSSIDE_LONG.getCode());
                            BigDecimal imrShort = PositionsWs.getAccountMap(positionShortAccountName).get("imr");
                            if (imrShort != null && imrShort.compareTo(BigDecimal.ZERO) > 0){
                                log.info("账户{}有持空仓", accountName);
                                hasShortPosition = true;
                            }
                            signal = tradingStrategy.generateSignal(prices, high, low, close, volume, currentPrice,
                                    hasLongPosition, hasShortPosition,
                                    fiveMinPrices, oneHourPrices, fourHourPrices,
                                    fundingRate, hasLargeTransfer, hasUpcomingEvent);
                            log.info("账户{}交易信号: " + signal, accountName);
                            if (TradingStrategy.SignalType.NONE == signal) {
                                continue;
                            }else if (TradingStrategy.SignalType.BUY == signal){
                                tradeRequestParam = caoZuoService.caoZuoStrategy(accountName, String.valueOf(currentPrice), CoinEnums.POSSIDE_LONG.getCode());
                                tradeRequestParam.setSide(CoinEnums.SIDE_BUY.getCode());
                                String clOrdId = WsParamBuild.getOrderNum(CoinEnums.SIDE_BUY.getCode());
                                tradeRequestParam.setClOrdId(clOrdId);
                                String sz = InstrumentsWs.getAccountMap(accountName).get(CoinEnums.BUY_CNT_INIT.name());
                                tradeRequestParam.setSz(sz);
                                TradeOrderWs.orderEvent(client.getWebSocketClient(), tradeRequestParam);
                            }else if (TradingStrategy.SignalType.SELL == signal){
                                tradeRequestParam = caoZuoService.caoZuoStrategy(accountName, String.valueOf(currentPrice), CoinEnums.POSSIDE_SHORT.getCode());
                                tradeRequestParam.setSide(CoinEnums.SIDE_SELL.getCode());
                                String clOrdId = WsParamBuild.getOrderNum(CoinEnums.SIDE_SELL.getCode());
                                tradeRequestParam.setClOrdId(clOrdId);
                                String sz = InstrumentsWs.getAccountMap(accountName).get(CoinEnums.BUY_CNT_INIT.name());
                                tradeRequestParam.setSz(sz);
                                TradeOrderWs.orderEvent(client.getWebSocketClient(), tradeRequestParam);
                            }else if (TradingStrategy.SignalType.CLOSE_BUY == signal){
                                tradeRequestParam = caoZuoService.caoZuoStrategy(accountName, String.valueOf(currentPrice), CoinEnums.POSSIDE_LONG.getCode());
                                tradeRequestParam.setSide(CoinEnums.SIDE_SELL.getCode());
                                String clOrdId = WsParamBuild.getOrderNum(CoinEnums.SIDE_SELL.getCode());
                                tradeRequestParam.setClOrdId(clOrdId);
                                BigDecimal pos = PositionsWs.getAccountMap(PositionsWs.initAccountName(accountName, CoinEnums.POSSIDE_LONG.getCode())).get("pos");
                                if (BigDecimal.ZERO.compareTo( pos) >= 0) {
                                    tradeRequestParam.setTradeType(OrderParamEnums.TRADE_NO.getValue());
                                }
                                tradeRequestParam.setSz(String.valueOf( pos));
                                TradeOrderWs.orderEvent(client.getWebSocketClient(), tradeRequestParam);
                            }else if (TradingStrategy.SignalType.CLOSE_SELL == signal){
                                tradeRequestParam = caoZuoService.caoZuoStrategy(accountName, String.valueOf(currentPrice), CoinEnums.POSSIDE_SHORT.getCode());
                                tradeRequestParam.setSide(CoinEnums.SIDE_BUY.getCode());
                                String clOrdId = WsParamBuild.getOrderNum(CoinEnums.SIDE_BUY.getCode());
                                tradeRequestParam.setClOrdId(clOrdId);
                                BigDecimal pos = PositionsWs.getAccountMap(PositionsWs.initAccountName(accountName, CoinEnums.POSSIDE_SHORT.getCode())).get("pos");
                                if (BigDecimal.ZERO.compareTo( pos) >= 0) {
                                    tradeRequestParam.setTradeType(OrderParamEnums.TRADE_NO.getValue());
                                }
                                tradeRequestParam.setSz(String.valueOf( pos));
                                TradeOrderWs.orderEvent(client.getWebSocketClient(), tradeRequestParam);
                            }
                        }
                    }
                }
            }
        } catch (Exception e) {
            log.error("处理 K线频道推送数据失败", e);
        }
    }
    /**
     * 触发所有账号的量化操作
     * @param markPx 当前标记价格
     */
    private void triggerQuantOperations(String markPx) {
        try {
            // 1. 判断当前价格属于哪个网格
            WangGeListEnum gridByPriceNew = WangGeListEnum.getGridByPrice(new BigDecimal(markPx));
            if (gridByPriceNew == null) {
                log.error("当前 K线频道{}不在任何网格范围内,无法触发量化操作", markPx);
                return;
            }
        } catch (Exception e) {
            log.error("触发量化操作失败", e);
        }
    }
    private List<Kline> getKlineDataByInstIdAndBar(String instId, String bar) {
        LinkedHashMap<String, Object> requestParam = new LinkedHashMap<>();
        requestParam.put("instId",instId);
        requestParam.put("bar",bar);
        requestParam.put("limit","100");
        String result = ExchangeLoginService.getInstance(ExchangeInfoEnum.OKX_UAT.name()).lineHistory(requestParam);
        log.info("加载OKX-KLINE,{}", result);
        JSONObject json = JSON.parseObject(result);
        String data = json.getString("data");
        List<String[]> klinesList = JSON.parseArray(data, String[].class);
        if(CollUtil.isEmpty(klinesList)){
            return  null;
        }
        ArrayList<Kline> objects = new ArrayList<>();
        for(String[] s : klinesList) {
            Kline kline = new Kline();
            kline.setTs(s[0]);
            kline.setO(new BigDecimal(s[1]));
            kline.setH(new BigDecimal(s[2]));
            kline.setL(new BigDecimal(s[3]));
            kline.setC(new BigDecimal(s[4]));
            kline.setVol(new BigDecimal(s[5]));
            kline.setConfirm(s[8]);
            objects.add(kline);
        }
        return objects;
    }
    /**
     * 构建 Redis Key
     */
    private String buildRedisKey(String instId) {
        return "PRICE_" + instId.replace("-", "");
    }
    /**
     * 启动心跳检测任务。
     * 使用 ScheduledExecutorService 定期检查是否需要发送 ping 请求来维持连接。
     */
    private void startHeartbeat() {
        if (heartbeatExecutor != null && !heartbeatExecutor.isTerminated()) {
            heartbeatExecutor.shutdownNow();
        }
        heartbeatExecutor = Executors.newSingleThreadScheduledExecutor(r -> {
            Thread t = new Thread(r, "okx-kline-heartbeat");
            t.setDaemon(true);
            return t;
        });
        heartbeatExecutor.scheduleWithFixedDelay(this::checkHeartbeatTimeout, 25, 25, TimeUnit.SECONDS);
    }
    /**
     * 重置心跳计时器。
     * 当收到新消息或发送 ping 后取消当前超时任务并重新安排下一次超时检查。
     */
    private synchronized void resetHeartbeatTimer() {
        cancelPongTimeout();
        if (heartbeatExecutor != null && !heartbeatExecutor.isShutdown()) {
            pongTimeoutFuture = heartbeatExecutor.schedule(this::checkHeartbeatTimeout,
                    HEARTBEAT_TIMEOUT, TimeUnit.SECONDS);
        }
    }
    /**
     * 检查心跳超时情况。
     * 若长时间未收到任何消息则主动发送 ping 请求保持连接活跃。
     */
    private void checkHeartbeatTimeout() {
        // 只有在连接状态下才检查心跳
        if (!isConnected.get()) {
            return;
        }
        long currentTime = System.currentTimeMillis();
        long lastTime = lastMessageTime.get();
        if (currentTime - lastTime >= HEARTBEAT_TIMEOUT * 1000L) {
            sendPing();
        }
    }
    /**
     * 发送 ping 请求至 WebSocket 服务端。
     * 用于维持长连接有效性。
     */
    private void sendPing() {
        try {
            if (webSocketClient != null && webSocketClient.isOpen()) {
                JSONObject ping = new JSONObject();
                ping.put("op", "ping");
                webSocketClient.send(ping.toJSONString());
                log.debug("发送ping请求");
            }
        } catch (Exception e) {
            log.warn("发送ping失败", e);
        }
    }
    /**
     * 取消当前的心跳超时任务。
     * 在收到 pong 或其他有效消息时调用此方法避免不必要的断开重连。
     */
    private synchronized void cancelPongTimeout() {
        if (pongTimeoutFuture != null && !pongTimeoutFuture.isDone()) {
            pongTimeoutFuture.cancel(true);
        }
    }
    /**
     * 执行 WebSocket 重连操作。
     * 在连接意外中断后尝试重新建立连接。
     */
    private void reconnectWithBackoff() throws InterruptedException {
        int attempt = 0;
        int maxAttempts = 3;
        long delayMs = 5000;
        while (attempt < maxAttempts) {
            try {
                Thread.sleep(delayMs);
                connect();
                return;
            } catch (Exception e) {
                log.warn("第{}次重连失败", attempt + 1, e);
                delayMs *= 2;
                attempt++;
            }
        }
        log.error("超过最大重试次数({})仍未连接成功", maxAttempts);
    }
    /**
     * 优雅关闭线程池
     */
    private void shutdownExecutorGracefully(ExecutorService executor) {
        if (executor == null || executor.isTerminated()) {
            return;
        }
        try {
            executor.shutdown();
            if (!executor.awaitTermination(5, TimeUnit.SECONDS)) {
                executor.shutdownNow();
            }
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            executor.shutdownNow();
        }
    }
}
src/main/java/com/xcong/excoin/modules/okxNewPrice/OkxWebSocketClientManager.java
@@ -3,8 +3,6 @@
import com.xcong.excoin.modules.okxNewPrice.celue.CaoZuoService;
import com.xcong.excoin.modules.okxNewPrice.okxWs.enums.ExchangeInfoEnum;
import com.xcong.excoin.modules.okxNewPrice.okxWs.wanggeList.WangGeListService;
import com.xcong.excoin.modules.okxNewPrice.wangge.WangGeService;
import com.xcong.excoin.rabbit.pricequeue.WebsocketPriceService;
import com.xcong.excoin.utils.RedisUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
@@ -34,7 +32,8 @@
    private final Map<String, OkxQuantWebSocketClient> quantClientMap = new ConcurrentHashMap<>();
    
    // 存储OkxNewPriceWebSocketClient实例
    private OkxNewPriceWebSocketClient newPriceClient;
    private OkxKlineWebSocketClient klinePriceClient;
    /**
     * 初始化方法,在Spring Bean构造完成后执行
@@ -46,8 +45,8 @@
        
        // 初始化价格WebSocket客户端
        try {
            newPriceClient = new OkxNewPriceWebSocketClient(redisUtils, caoZuoService, this, wangGeListService);
            newPriceClient.init();
            klinePriceClient = new OkxKlineWebSocketClient(redisUtils, caoZuoService, this, wangGeListService);
            klinePriceClient.init();
            log.info("已初始化OkxNewPriceWebSocketClient");
        } catch (Exception e) {
            log.error("初始化OkxNewPriceWebSocketClient失败", e);
@@ -80,9 +79,9 @@
        log.info("开始销毁OkxWebSocketClientManager");
        
        // 关闭价格WebSocket客户端
        if (newPriceClient != null) {
        if (klinePriceClient != null) {
            try {
                newPriceClient.destroy();
                klinePriceClient.destroy();
                log.info("已销毁OkxNewPriceWebSocketClient");
            } catch (Exception e) {
                log.error("销毁OkxNewPriceWebSocketClient失败", e);
@@ -127,7 +126,7 @@
     * 获取OkxNewPriceWebSocketClient实例
     * @return 价格WebSocket客户端实例
     */
    public OkxNewPriceWebSocketClient getNewPriceClient() {
        return newPriceClient;
    public OkxKlineWebSocketClient getKlineWebSocketClient() {
        return klinePriceClient;
    }
}
src/main/java/com/xcong/excoin/modules/okxNewPrice/celue/CaoZuoService.java
@@ -7,6 +7,8 @@
 */
public interface CaoZuoService {
    TradeRequestParam caoZuoStrategy(String accountName, String markPx, String posSide);
    TradeRequestParam caoZuoHandler(String accountName, String markPx, String posSide);
    /**
src/main/java/com/xcong/excoin/modules/okxNewPrice/celue/CaoZuoServiceImpl.java
@@ -40,6 +40,82 @@
    private final RedisUtils redisUtils;
    private final TechnicalIndicatorStrategy technicalIndicatorStrategy;
    @Override
    public TradeRequestParam caoZuoStrategy(String accountName, String markPx, String posSide) {
        TradeRequestParam tradeRequestParam = new TradeRequestParam();
        tradeRequestParam.setAccountName(accountName);
        tradeRequestParam.setInstId(CoinEnums.HE_YUE.getCode());
        tradeRequestParam.setTdMode(CoinEnums.CROSS.getCode());
        tradeRequestParam.setPosSide(posSide);
        tradeRequestParam.setOrdType(CoinEnums.ORDTYPE_MARKET.getCode());
        log.info("操作账户:{},当前价格: {},仓位方向: {}", accountName,markPx,posSide);
        /**
         * 准备工作
         * 1、准备好下单的基本信息
         */
        // 系统设置的开关,等于冷静中,则代表不开仓
        String outStr = InstrumentsWs.getAccountMap(accountName).get(CoinEnums.OUT.name());
        if (OrderParamEnums.OUT_YES.getValue().equals(outStr)){
            log.error("冷静中,不允许下单......");
            tradeRequestParam.setTradeType(OrderParamEnums.TRADE_NO.getValue());
            return tradeRequestParam;
        }
        BigDecimal cashBal = WsMapBuild.parseBigDecimalSafe(AccountWs.getAccountMap(accountName).get("cashBal"));
        /**
         * 判断止损抗压
         */
        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());
                tradeRequestParam.setTradeType(OrderParamEnums.TRADE_YES.getValue());
                return caoZuoZhiSunEvent(accountName, markPx, posSide);
            }
            // 判断抗压
            if (realKuiSunAmount.compareTo(kangYaAmount) > 0 && realKuiSunAmount.compareTo(zhiSunAmount) <= 0){
                log.error("账户紧张扛仓......");
                tradeRequestParam.setTradeType(OrderParamEnums.TRADE_NO.getValue());
                return tradeRequestParam;
            }
        }
        String positionAccountName = PositionsWs.initAccountName(accountName, posSide);
        // 判断是否保证金超标
        if (PositionsWs.getAccountMap(positionAccountName).get("imr") == null){
            log.error("没有获取到持仓信息,等待初始化......");
            tradeRequestParam.setTradeType(OrderParamEnums.TRADE_NO.getValue());
            return tradeRequestParam;
        }
        BigDecimal ordFrozImr = PositionsWs.getAccountMap(positionAccountName).get("imr");
        BigDecimal totalOrderUsdt = WsMapBuild.parseBigDecimalSafe(AccountWs.getAccountMap(accountName).get(CoinEnums.TOTAL_ORDER_USDT.name()))
                .divide(new BigDecimal("2"), RoundingMode.DOWN);
        if (ordFrozImr.compareTo(totalOrderUsdt) >= 0){
            log.error("已满仓......");
            tradeRequestParam.setTradeType(OrderParamEnums.TRADE_NO.getValue());
            return tradeRequestParam;
        }
        if (PositionsWs.getAccountMap(positionAccountName).get("pos") == null){
            log.error("没有获取到持仓信息,等待初始化......");
            tradeRequestParam.setTradeType(OrderParamEnums.TRADE_NO.getValue());
            return tradeRequestParam;
        }
        tradeRequestParam.setTradeType(OrderParamEnums.TRADE_YES.getValue());
        return chooseEvent(tradeRequestParam);
    }
    /**
     * 执行主要的操作逻辑,包括读取合约状态、获取市场价格信息,
     * 并根据当前持仓均价和标记价格决定是否执行买卖操作。
src/main/java/com/xcong/excoin/modules/okxNewPrice/indicator/AdvancedMA.java
@@ -11,6 +11,31 @@
/**
 * Advanced MA (Moving Average) 指标实现
 * 支持扩展周期的指数移动平均线(EMA),用于三重EMA交叉系统
 *
 * 作用:
 * 1. 基于三重EMA交叉系统识别趋势方向和强度
 * 2. 当9EMA > 21EMA > 55EMA时形成多头排列,提示上涨趋势
 * 3. 当9EMA < 21EMA < 55EMA时形成空头排列,提示下跌趋势
 * 4. 计算三线粘合度,自动过滤震荡行情
 *
 * 价格参数类型:
 * - 参数名称:prices
 * - 参数类型:List<BigDecimal>
 * - 参数说明:需要至少1个价格数据点用于计算,根据不同周期需求更多数据点
 *
 * 推荐时间粒度及优缺点:
 * 1. 5分钟(5m):
 *    - 优点:适合短线三重EMA交叉策略
 *    - 缺点:需要频繁监控,容易受短期波动影响
 * 2. 15分钟(15m):
 *    - 优点:平衡了信号可靠性和反应速度
 *    - 缺点:仍有一定噪音
 * 3. 1小时(1h):
 *    - 优点:信号较为可靠,适合中期趋势跟踪
 *    - 缺点:反应较慢
 * 4. 4小时(4h)及以上:
 *    - 优点:趋势信号明确,适合长期持仓
 *    - 缺点:反应滞后,入场点较晚
 */
@Slf4j
@Getter
src/main/java/com/xcong/excoin/modules/okxNewPrice/indicator/BOLL.java
@@ -14,6 +14,32 @@
 * 1. 中轨(MB)= N日移动平均线
 * 2. 上轨(UP)= 中轨 + K倍标准差
 * 3. 下轨(DN)= 中轨 - K倍标准差
 *
 * 作用:
 * 1. 测量价格波动范围和市场宽度
 * 2. 价格突破上轨,提示超买或趋势加速
 * 3. 价格跌破下轨,提示超卖或趋势加速
 * 4. 轨道收窄,提示即将发生剧烈波动
 * 5. 价格回归轨道内,提示趋势可能反转
 *
 * 价格参数类型:
 * - 参数名称:prices
 * - 参数类型:List<BigDecimal>
 * - 参数说明:需要至少20个(默认周期)价格数据点用于计算
 *
 * 推荐时间粒度及优缺点:
 * 1. 1分钟(1m):
 *    - 优点:反应迅速,适合超短线突破策略
 *    - 缺点:布林带宽度窄,假突破多
 * 2. 5分钟(5m):
 *    - 优点:布林带宽度适中,突破信号相对可靠
 *    - 缺点:仍有一定假突破
 * 3. 15分钟(15m):
 *    - 优点:适合日内交易,突破信号较为可靠
 *    - 缺点:反应速度较慢
 * 4. 1小时(1h)及以上:
 *    - 优点:布林带宽度稳定,突破信号可靠
 *    - 缺点:反应滞后,不适合短线交易
 */
@Slf4j
@Getter
src/main/java/com/xcong/excoin/modules/okxNewPrice/indicator/FifteenMinuteStrategyExample.java
New file
@@ -0,0 +1,81 @@
package com.xcong.excoin.modules.okxNewPrice.indicator;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;
/**
 * 15分钟交易策略使用示例
 * 展示如何使用FifteenMinuteTradingStrategy处理100个15分钟价格数据点
 */
public class FifteenMinuteStrategyExample {
    public static void main(String[] args) {
        // 1. 创建策略实例
        FifteenMinuteTradingStrategy strategy = new FifteenMinuteTradingStrategy();
        // 2. 准备100个15分钟价格数据(这里使用模拟数据,用户可以替换为真实数据)
        List<BigDecimal> prices = generateSampleFifteenMinuteData();
        System.out.println("已加载 " + prices.size() + " 个15分钟价格数据点");
        // 3. 获取当前价格
        BigDecimal currentPrice = prices.get(prices.size() - 1);
        System.out.println("当前价格: " + currentPrice);
        // 4. 示例1:获取多空方向
        System.out.println("\n=== 多空方向分析 ===");
        FifteenMinuteTradingStrategy.Direction direction = strategy.getDirection(prices);
        System.out.println("当前市场方向: " + direction);
        // 5. 示例2:获取开仓平仓信号(假设当前没有持仓)
        System.out.println("\n=== 开仓平仓信号分析(无持仓)===");
        FifteenMinuteTradingStrategy.PositionSignal signal1 =
            strategy.getPositionSignal(prices, false, false);
        System.out.println("无持仓时的信号: " + signal1);
        // 6. 示例3:获取开仓平仓信号(假设当前持有多仓)
        System.out.println("\n=== 开仓平仓信号分析(持有多仓)===");
        FifteenMinuteTradingStrategy.PositionSignal signal2 =
            strategy.getPositionSignal(prices, true, false);
        System.out.println("持有多仓时的信号: " + signal2);
        // 7. 示例4:获取开仓平仓信号(假设当前持有空仓)
        System.out.println("\n=== 开仓平仓信号分析(持有空仓)===");
        FifteenMinuteTradingStrategy.PositionSignal signal3 =
            strategy.getPositionSignal(prices, false, true);
        System.out.println("持有空仓时的信号: " + signal3);
        // 8. 示例5:获取完整交易结果
        System.out.println("\n=== 完整交易结果分析 ===");
        FifteenMinuteTradingStrategy.TradingResult result =
            strategy.getTradingResult(prices, false, false);
        System.out.println("市场方向: " + result.getDirection());
        System.out.println("交易信号: " + result.getSignal());
        System.out.println("\n指标状态详情:");
        System.out.println(result.getIndicatorStatus());
    }
    /**
     * 生成模拟的15分钟价格数据(100个数据点)
     * 用户可以替换为真实的价格数据
     * @return 15分钟价格数据列表
     */
    private static List<BigDecimal> generateSampleFifteenMinuteData() {
        List<BigDecimal> prices = new ArrayList<>();
        // 模拟ETH价格数据(从2400开始,有一定波动)
        BigDecimal basePrice = new BigDecimal(2400);
        for (int i = 0; i < 100; i++) {
            // 添加一些随机波动,但保持整体上升趋势
            double random = (Math.random() - 0.48) * 10; // -5 到 5 的随机波动,略微偏向上行
            BigDecimal price = basePrice.add(new BigDecimal(random));
            prices.add(price.setScale(2, BigDecimal.ROUND_HALF_UP));
            // 整体缓慢上升
            basePrice = basePrice.add(new BigDecimal(0.2));
        }
        return prices;
    }
}
src/main/java/com/xcong/excoin/modules/okxNewPrice/indicator/FifteenMinuteTradingExample.java
New file
@@ -0,0 +1,214 @@
package com.xcong.excoin.modules.okxNewPrice.indicator;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
/**
 * 15分钟交易策略示例
 * 演示如何使用交易策略与15分钟时间框架数据
 * 展示如何获取方向信号和交易信号
 */
public class FifteenMinuteTradingExample {
    public static void main(String[] args) {
        // 创建交易策略
        TradingStrategy tradingStrategy = new TradingStrategy();
        // 生成100个15分钟价格数据点
        List<BigDecimal> prices = generateSampleFifteenMinuteData(100);
        // 生成对应的高、低、收盘价数据
        List<BigDecimal> high = generateHighPrices(prices);
        List<BigDecimal> low = generateLowPrices(prices);
        List<BigDecimal> close = new ArrayList<>(prices); // 使用价格作为收盘价
        // 生成成交量数据
        List<BigDecimal> volume = generateVolumeData(prices.size());
        // 获取最新价格
        BigDecimal currentPrice = prices.get(prices.size() - 1);
        // 生成多周期价格数据(5分钟、1小时、4小时)
        List<BigDecimal> fiveMinPrices = generateSampleFifteenMinuteData(100);
        List<BigDecimal> oneHourPrices = generateSampleFifteenMinuteData(100);
        List<BigDecimal> fourHourPrices = generateSampleFifteenMinuteData(100);
        // 其他参数
        BigDecimal fundingRate = new BigDecimal("0.001"); // 正常资金费率
        boolean hasLargeTransfer = false; // 无大额转账
        boolean hasUpcomingEvent = false; // 无即将到来的重大事件
        // 确定市场方向
        TradingStrategy.Direction direction = tradingStrategy.getDirection(prices, high, low, close, currentPrice);
        System.out.println("市场方向(15分钟): " + direction);
        // 检查当前持仓状态
        boolean hasLongPosition = false;  // 示例:无当前做多持仓
        boolean hasShortPosition = false; // 示例:无当前做空持仓
        // 生成交易信号(开仓/平仓)
        TradingStrategy.SignalType signal = tradingStrategy.generateSignal(prices, high, low, close, volume, currentPrice,
                                                                          hasLongPosition, hasShortPosition,
                                                                          fiveMinPrices, oneHourPrices, fourHourPrices,
                                                                          fundingRate, hasLargeTransfer, hasUpcomingEvent);
        System.out.println("交易信号(15分钟): " + signal);
        // 显示指标状态用于分析
        System.out.println("\n指标状态:");
        System.out.println(tradingStrategy.getIndicatorStatus());
        // 计算动态杠杆
        BigDecimal dynamicLeverage = tradingStrategy.calculateDynamicLeverage(high, low, close);
        System.out.println("\n动态杠杆倍数: " + dynamicLeverage);
        // 基于信号模拟持仓变化
        if (signal == TradingStrategy.SignalType.BUY) {
            System.out.println("\n=== 执行开多操作 ===");
            hasLongPosition = true;
            // 演示三段式止盈策略
            BigDecimal entryPrice = currentPrice;
            BigDecimal positionSize = new BigDecimal(100);
            TradingStrategy.ProfitTakingResult profitTakingResult =
                tradingStrategy.calculateThreeStepProfitTaking(entryPrice, currentPrice, direction, positionSize);
            System.out.println("三段式止盈信号: " + profitTakingResult.getSignal());
            System.out.println("应平仓仓位大小: " + profitTakingResult.getClosePositionSize());
        } else if (signal == TradingStrategy.SignalType.SELL) {
            System.out.println("\n=== 执行开空操作 ===");
            hasShortPosition = true;
            // 演示三段式止盈策略
            BigDecimal entryPrice = currentPrice;
            BigDecimal positionSize = new BigDecimal(100);
            TradingStrategy.ProfitTakingResult profitTakingResult =
                tradingStrategy.calculateThreeStepProfitTaking(entryPrice, currentPrice, direction, positionSize);
            System.out.println("三段式止盈信号: " + profitTakingResult.getSignal());
            System.out.println("应平仓仓位大小: " + profitTakingResult.getClosePositionSize());
        } else if (signal == TradingStrategy.SignalType.CLOSE_BUY) {
            System.out.println("\n=== 执行平多操作 ===");
            hasLongPosition = false;
        } else if (signal == TradingStrategy.SignalType.CLOSE_SELL) {
            System.out.println("\n=== 执行平空操作 ===");
            hasShortPosition = false;
        } else {
            System.out.println("\n无需交易操作。");
        }
        // 现有做多持仓的模拟示例
        System.out.println("\n=== 现有做多持仓的模拟 ===");
        hasLongPosition = true;
        hasShortPosition = false;
        signal = tradingStrategy.generateSignal(prices, high, low, close, volume, currentPrice,
                                               hasLongPosition, hasShortPosition,
                                               fiveMinPrices, oneHourPrices, fourHourPrices,
                                               fundingRate, hasLargeTransfer, hasUpcomingEvent);
        System.out.println("有做多持仓时的交易信号: " + signal);
        // 现有做空持仓的模拟示例
        System.out.println("\n=== 现有做空持仓的模拟 ===");
        hasLongPosition = false;
        hasShortPosition = true;
        signal = tradingStrategy.generateSignal(prices, high, low, close, volume, currentPrice,
                                               hasLongPosition, hasShortPosition,
                                               fiveMinPrices, oneHourPrices, fourHourPrices,
                                               fundingRate, hasLargeTransfer, hasUpcomingEvent);
        System.out.println("有做空持仓时的交易信号: " + signal);
        // 模拟盈利场景演示三段式止盈
        System.out.println("\n=== 三段式止盈盈利场景演示 ===");
        BigDecimal entryPrice = new BigDecimal(2500.0);
        BigDecimal currentPriceProfit = new BigDecimal(2700.0); // 模拟盈利价格
        BigDecimal positionSize = new BigDecimal(100);
        TradingStrategy.ProfitTakingResult profitTakingResult =
            tradingStrategy.calculateThreeStepProfitTaking(entryPrice, currentPriceProfit, TradingStrategy.Direction.LONG, positionSize);
        System.out.println("入场价格: " + entryPrice);
        System.out.println("当前价格: " + currentPriceProfit);
        System.out.println("三段式止盈信号: " + profitTakingResult.getSignal());
        System.out.println("应平仓仓位大小: " + profitTakingResult.getClosePositionSize());
    }
    /**
     * 生成具有真实波动的15分钟价格数据
     * @param size 要生成的数据点数量
     * @return 价格数据列表
     */
    private static List<BigDecimal> generateSampleFifteenMinuteData(int size) {
        List<BigDecimal> prices = new ArrayList<>();
        Random random = new Random();
        // 以基础价格开始(ETH示例价格)
        BigDecimal basePrice = new BigDecimal(2500.0);
        prices.add(basePrice);
        // 生成具有真实波动的后续价格
        for (int i = 1; i < size; i++) {
            // 创建价格趋势(轻微上升偏向)
            BigDecimal trend = new BigDecimal(0.1).multiply(new BigDecimal(i));
            // 添加随机波动(每个周期±2%)
            BigDecimal volatility = new BigDecimal(random.nextDouble() * 0.04 - 0.02);
            // 计算新价格
            BigDecimal newPrice = basePrice.add(trend).multiply(BigDecimal.ONE.add(volatility));
            // 四舍五入到2位小数
            newPrice = newPrice.setScale(2, BigDecimal.ROUND_HALF_UP);
            prices.add(newPrice);
        }
        return prices;
    }
    /**
     * 生成最高价数据
     * @param prices 价格数据
     * @return 最高价数据列表
     */
    private static List<BigDecimal> generateHighPrices(List<BigDecimal> prices) {
        List<BigDecimal> high = new ArrayList<>();
        Random random = new Random();
        for (BigDecimal price : prices) {
            // 最高价比当前价格高0-2%
            BigDecimal highPrice = price.multiply(BigDecimal.ONE.add(new BigDecimal(random.nextDouble() * 0.02)));
            high.add(highPrice.setScale(2, BigDecimal.ROUND_HALF_UP));
        }
        return high;
    }
    /**
     * 生成最低价数据
     * @param prices 价格数据
     * @return 最低价数据列表
     */
    private static List<BigDecimal> generateLowPrices(List<BigDecimal> prices) {
        List<BigDecimal> low = new ArrayList<>();
        Random random = new Random();
        for (BigDecimal price : prices) {
            // 最低价比当前价格低0-2%
            BigDecimal lowPrice = price.multiply(BigDecimal.ONE.subtract(new BigDecimal(random.nextDouble() * 0.02)));
            low.add(lowPrice.setScale(2, BigDecimal.ROUND_HALF_UP));
        }
        return low;
    }
    /**
     * 生成成交量数据
     * @param size 数据点数量
     * @return 成交量数据列表
     */
    private static List<BigDecimal> generateVolumeData(int size) {
        List<BigDecimal> volume = new ArrayList<>();
        Random random = new Random();
        for (int i = 0; i < size; i++) {
            // 生成1000-10000之间的随机成交量
            BigDecimal vol = new BigDecimal(random.nextInt(9001) + 1000);
            volume.add(vol);
        }
        return volume;
    }
}
src/main/java/com/xcong/excoin/modules/okxNewPrice/indicator/FifteenMinuteTradingStrategy.java
New file
@@ -0,0 +1,237 @@
package com.xcong.excoin.modules.okxNewPrice.indicator;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import java.math.BigDecimal;
import java.util.List;
/**
 * 15分钟时间粒度的交易策略实现
 * 专门针对100个15分钟数据点设计的策略,包含明确的多空方向选择和开仓平仓方法
 */
@Slf4j
public class FifteenMinuteTradingStrategy {
    @Getter
    @Setter
    public static class TradingResult {
        private Direction direction;      // 多空方向
        private PositionSignal signal;    // 开仓平仓信号
        private String indicatorStatus;   // 指标状态
        public TradingResult(Direction direction, PositionSignal signal, String indicatorStatus) {
            this.direction = direction;
            this.signal = signal;
            this.indicatorStatus = indicatorStatus;
        }
    }
    public enum Direction {
        LONG,    // 多头方向
        SHORT,   // 空头方向
        RANGING  // 震荡行情
    }
    public enum PositionSignal {
        OPEN_LONG,    // 开多仓
        OPEN_SHORT,   // 开空仓
        CLOSE_LONG,   // 平多仓
        CLOSE_SHORT,  // 平空仓
        HOLD,         // 持有
        STAY_OUT      // 观望
    }
    private final MA ma;
    private final AdvancedMA advancedMA;
    private final BOLL boll;
    private final KDJ kdj;
    private final MACD macd;
    private final RSI rsi;
    public FifteenMinuteTradingStrategy() {
        // 15分钟数据优化的参数配置
        this.ma = new MA();
        this.advancedMA = new AdvancedMA();
        this.boll = new BOLL(20, 2.0);  // BOLL使用默认20周期
        this.kdj = new KDJ(9);          // KDJ使用默认9周期
        this.macd = new MACD();         // MACD使用默认12/26/9周期
        this.rsi = new RSI(14);         // RSI使用默认14周期
    }
    /**
     * 计算所有指标
     * @param prices 15分钟价格数据(至少100个数据点)
     */
    private void calculateIndicators(List<BigDecimal> prices) {
        ma.calculate(prices);
        advancedMA.calculateTripleEMA(prices);
        boll.calculate(prices);
        kdj.calculate(prices);
        macd.calculate(prices);
        rsi.calculate(prices);
    }
    /**
     * 判断市场是否处于震荡行情
     * @return 是否为震荡行情
     */
    private boolean isRangeMarket() {
        // AdvancedMA三线粘合 + RSI(40-60) + BOLL带宽收窄
        boolean isMaConverged = advancedMA.calculatePercent().compareTo(new BigDecimal(2)) < 0;
        boolean isRsiNeutral = rsi.getRsi().compareTo(new BigDecimal(40)) > 0 &&
                              rsi.getRsi().compareTo(new BigDecimal(60)) < 0;
        boolean isBollNarrow = boll.calculateBandWidth().compareTo(new BigDecimal(0.05)) < 0;
        return isMaConverged && isRsiNeutral && isBollNarrow;
    }
    /**
     * 获取多空方向选择
     * @param prices 15分钟价格数据(100个数据点)
     * @return 多空方向
     */
    public Direction getDirection(List<BigDecimal> prices) {
        if (prices == null || prices.size() < 100) {
            throw new IllegalArgumentException("需要至少100个15分钟价格数据点");
        }
        calculateIndicators(prices);
        // 震荡过滤
        if (isRangeMarket()) {
            return Direction.RANGING;
        }
        BigDecimal currentPrice = prices.get(prices.size() - 1);
        // 多头信号判断:MA多头排列 + MACD金叉 + RSI(30-70) + BOLL价格在上轨与中轨之间
        boolean isLongSignal =
            ma.getEma5().compareTo(ma.getEma10()) > 0 &&  // MA5 > MA10
            ma.getEma10().compareTo(ma.getEma20()) > 0 && // MA10 > MA20
            macd.getDif().compareTo(macd.getDea()) > 0 && // MACD金叉
            rsi.getRsi().compareTo(new BigDecimal(30)) > 0 && rsi.getRsi().compareTo(new BigDecimal(70)) < 0 && // RSI(30-70)
            currentPrice.compareTo(boll.getMid()) > 0 && currentPrice.compareTo(boll.getUpper()) < 0; // BOLL价格在上轨与中轨之间
        // 空头信号判断:MA空头排列 + MACD死叉 + RSI(30-70) + BOLL价格在下轨与中轨之间
        boolean isShortSignal =
            ma.getEma5().compareTo(ma.getEma10()) < 0 &&  // MA5 < MA10
            ma.getEma10().compareTo(ma.getEma20()) < 0 && // MA10 < MA20
            macd.getDif().compareTo(macd.getDea()) < 0 && // MACD死叉
            rsi.getRsi().compareTo(new BigDecimal(30)) > 0 && rsi.getRsi().compareTo(new BigDecimal(70)) < 0 && // RSI(30-70)
            currentPrice.compareTo(boll.getMid()) < 0 && currentPrice.compareTo(boll.getLower()) > 0; // BOLL价格在下轨与中轨之间
        if (isLongSignal) {
            return Direction.LONG;
        } else if (isShortSignal) {
            return Direction.SHORT;
        } else {
            return Direction.RANGING;
        }
    }
    /**
     * 获取开仓平仓策略信号
     * @param prices 15分钟价格数据(100个数据点)
     * @param hasLongPosition 当前是否持有多仓
     * @param hasShortPosition 当前是否持有空仓
     * @return 开仓平仓信号
     */
    public PositionSignal getPositionSignal(List<BigDecimal> prices, boolean hasLongPosition, boolean hasShortPosition) {
        if (prices == null || prices.size() < 100) {
            throw new IllegalArgumentException("需要至少100个15分钟价格数据点");
        }
        calculateIndicators(prices);
        // 震荡过滤
        if (isRangeMarket()) {
            return PositionSignal.STAY_OUT;
        }
        BigDecimal currentPrice = prices.get(prices.size() - 1);
        // 开多信号:MA金叉 + MACD金叉 + KDJ金叉 + RSI中性 + 价格在BOLL中轨上方
        boolean shouldOpenLong =
            ma.getEma5().compareTo(ma.getEma20()) > 0 &&  // MA金叉(5日EMA上穿20日EMA)
            macd.getDif().compareTo(macd.getDea()) > 0 && macd.getMacdBar().compareTo(BigDecimal.ZERO) > 0 && // MACD金叉且柱状图为正
            kdj.isGoldenCross() && // KDJ金叉
            rsi.getRsi().compareTo(new BigDecimal(30)) > 0 && rsi.getRsi().compareTo(new BigDecimal(70)) < 0 && // RSI中性
            currentPrice.compareTo(boll.getMid()) > 0; // 价格在BOLL中轨上方
        // 开空信号:MA死叉 + MACD死叉 + KDJ死叉 + RSI中性 + 价格在BOLL中轨下方
        boolean shouldOpenShort =
            ma.getEma5().compareTo(ma.getEma20()) < 0 &&  // MA死叉(5日EMA下穿20日EMA)
            macd.getDif().compareTo(macd.getDea()) < 0 && macd.getMacdBar().compareTo(BigDecimal.ZERO) < 0 && // MACD死叉且柱状图为负
            kdj.isDeathCross() && // KDJ死叉
            rsi.getRsi().compareTo(new BigDecimal(30)) > 0 && rsi.getRsi().compareTo(new BigDecimal(70)) < 0 && // RSI中性
            currentPrice.compareTo(boll.getMid()) < 0; // 价格在BOLL中轨下方
        // 平多信号:MA死叉 + MACD死叉 + RSI超买 + 价格跌破BOLL中轨
        boolean shouldCloseLong =
            (ma.getEma5().compareTo(ma.getEma20()) < 0 && // MA死叉
            macd.getDif().compareTo(macd.getDea()) < 0 && // MACD死叉
            (rsi.isOverbought() || rsi.isExtremelyOverbought())) || // RSI超买
            currentPrice.compareTo(boll.getMid()) < 0; // 价格跌破BOLL中轨
        // 平空信号:MA金叉 + MACD金叉 + RSI超卖 + 价格突破BOLL中轨
        boolean shouldCloseShort =
            (ma.getEma5().compareTo(ma.getEma20()) > 0 && // MA金叉
            macd.getDif().compareTo(macd.getDea()) > 0 && // MACD金叉
            (rsi.isOversold() || rsi.isExtremelyOversold())) || // RSI超卖
            currentPrice.compareTo(boll.getMid()) > 0; // 价格突破BOLL中轨
        // 确定开仓信号
        if (shouldOpenLong && !hasLongPosition && !hasShortPosition) {
            return PositionSignal.OPEN_LONG;
        } else if (shouldOpenShort && !hasLongPosition && !hasShortPosition) {
            return PositionSignal.OPEN_SHORT;
        }
        // 确定平仓信号
        if (shouldCloseLong && hasLongPosition) {
            return PositionSignal.CLOSE_LONG;
        } else if (shouldCloseShort && hasShortPosition) {
            return PositionSignal.CLOSE_SHORT;
        }
        // 无信号
        return hasLongPosition || hasShortPosition ? PositionSignal.HOLD : PositionSignal.STAY_OUT;
    }
    /**
     * 综合获取交易结果
     * @param prices 15分钟价格数据(100个数据点)
     * @param hasLongPosition 当前是否持有多仓
     * @param hasShortPosition 当前是否持有空仓
     * @return 包含多空方向和开仓平仓信号的完整交易结果
     */
    public TradingResult getTradingResult(List<BigDecimal> prices, boolean hasLongPosition, boolean hasShortPosition) {
        Direction direction = getDirection(prices);
        PositionSignal signal = getPositionSignal(prices, hasLongPosition, hasShortPosition);
        String indicatorStatus = getIndicatorStatus();
        return new TradingResult(direction, signal, indicatorStatus);
    }
    /**
     * 获取当前指标状态
     * @return 指标状态字符串
     */
    private String getIndicatorStatus() {
        return String.format("MA5: %s, MA20: %s, " +
                        "MACD-DIF: %s, MACD-DEA: %s, MACD-BAR: %s, " +
                        "KDJ-K: %s, KDJ-D: %s, KDJ-J: %s, " +
                        "RSI: %s, " +
                        "BOLL-UP: %s, BOLL-MID: %s, BOLL-DN: %s, " +
                        "AdvancedMA-Bullish: %s, Bearish: %s, Percent: %s",
                ma.getEma5(), ma.getEma20(),
                macd.getDif(), macd.getDea(), macd.getMacdBar(),
                kdj.getK(), kdj.getD(), kdj.getJ(),
                rsi.getRsi(),
                boll.getUpper(), boll.getMid(), boll.getLower(),
                advancedMA.isBullish(), advancedMA.isBearish(), advancedMA.calculatePercent());
    }
}
src/main/java/com/xcong/excoin/modules/okxNewPrice/indicator/IndicatorBase.java
@@ -7,6 +7,22 @@
/**
 * 技术指标基础类,提供通用计算方法
 *
 * 指标组合策略:
 * 1. 趋势判断(MA/AdvancedMA/MACD):判断价格的整体走势方向
 * 2. 动量确认(RSI/KDJ):确认当前趋势的强度和可持续性
 * 3. 波动参考(BOLL):确定价格的合理波动范围和突破时机
 *
 * 多空方向选择逻辑:
 * - 多头信号:MA多头排列 + MACD金叉 + RSI(30-70) + BOLL价格在上轨与中轨之间
 * - 空头信号:MA空头排列 + MACD死叉 + RSI(30-70) + BOLL价格在下轨与中轨之间
 * - 震荡信号:AdvancedMA三线粘合 + RSI(40-60) + BOLL带宽收窄
 *
 * 开仓和平仓策略:
 * - 开多:MA金叉 + MACD金叉 + KDJ金叉 + RSI(30-70) + 价格突破BOLL中轨
 * - 开空:MA死叉 + MACD死叉 + KDJ死叉 + RSI(30-70) + 价格跌破BOLL中轨
 * - 平多:MA死叉 + MACD死叉 + RSI超买(>70) + 价格跌破BOLL中轨
 * - 平空:MA金叉 + MACD金叉 + RSI超卖(<30) + 价格突破BOLL中轨
 */
public abstract class IndicatorBase {
@@ -91,4 +107,55 @@
        int startIndex = Math.max(0, prices.size() - period);
        return prices.subList(startIndex, prices.size());
    }
    /**
     * 计算ATR(Average True Range)
     * @param high 最高价列表
     * @param low 最低价列表
     * @param close 收盘价列表
     * @param period 周期
     * @return ATR值
     */
    protected BigDecimal calculateATR(List<BigDecimal> high, List<BigDecimal> low, List<BigDecimal> close, int period) {
        if (high == null || low == null || close == null ||
            high.size() < period || low.size() < period || close.size() < period) {
            return BigDecimal.ZERO;
        }
        List<BigDecimal> trList = new ArrayList<>();
        for (int i = 1; i < high.size(); i++) {
            BigDecimal trueRange = calculateTrueRange(high.get(i), low.get(i), close.get(i - 1));
            trList.add(trueRange);
        }
        // 使用简单移动平均计算ATR
        return calculateMA(trList, Math.min(period, trList.size()));
    }
    /**
     * 计算真实波幅(True Range)
     * @param high 当前最高价
     * @param low 当前最低价
     * @param prevClose 前收盘价
     * @return 真实波幅
     */
    protected BigDecimal calculateTrueRange(BigDecimal high, BigDecimal low, BigDecimal prevClose) {
        BigDecimal h1 = high.subtract(low);
        BigDecimal h2 = high.subtract(prevClose).abs();
        BigDecimal h3 = low.subtract(prevClose).abs();
        return h1.max(h2).max(h3);
    }
    /**
     * 计算标准化波动率(基于ATR)
     * @param close 收盘价列表
     * @param atr ATR值
     * @return 标准化波动率(百分比)
     */
    protected BigDecimal normalizeVolatility(List<BigDecimal> close, BigDecimal atr) {
        if (close == null || close.size() == 0 || atr.compareTo(BigDecimal.ZERO) == 0) {
            return BigDecimal.ZERO;
        }
        return atr.divide(close.get(close.size() - 1), 4, RoundingMode.HALF_UP).multiply(new BigDecimal(100));
    }
}
src/main/java/com/xcong/excoin/modules/okxNewPrice/indicator/KDJ.java
@@ -15,6 +15,31 @@
 * 2. K = 2/3 * 前一日K值 + 1/3 * 当日RSV
 * 3. D = 2/3 * 前一日D值 + 1/3 * 当日K值
 * 4. J = 3*K - 2*D
 *
 * 作用:
 * 1. 衡量价格的超买超卖状态(K值>80超买,K值<20超卖)
 * 2. K线上穿D线形成金叉,提示买入信号
 * 3. K线下穿D线形成死叉,提示卖出信号
 * 4. J值反映市场的极端状态,J值>100或J值<0为极端行情
 *
 * 价格参数类型:
 * - 参数名称:prices
 * - 参数类型:List<BigDecimal>
 * - 参数说明:需要至少9个(默认周期)价格数据点用于计算
 *
 * 推荐时间粒度及优缺点:
 * 1. 1分钟(1m):
 *    - 优点:反应迅速,适合超短线交易
 *    - 缺点:K值波动剧烈,信号频繁且可靠性低
 * 2. 5分钟(5m):
 *    - 优点:K值波动相对稳定,适合短线交易
 *    - 缺点:仍有一定虚假信号
 * 3. 15分钟(15m):
 *    - 优点:信号较为可靠,适合日内交易
 *    - 缺点:反应速度较慢
 * 4. 1小时(1h)及以上:
 *    - 优点:超买超卖信号明确,适合中期交易
 *    - 缺点:反应滞后,不适合短线交易
 */
@Slf4j
@Getter
src/main/java/com/xcong/excoin/modules/okxNewPrice/indicator/MA.java
@@ -10,18 +10,50 @@
/**
 * MA (Moving Average) 指标实现
 * 支持不同周期的简单移动平均线(SMA)和指数移动平均线(EMA)
 *
 * 作用:
 * 1. 平滑价格波动,识别趋势方向
 * 2. 短周期MA上穿长周期MA形成金叉,提示买入信号
 * 3. 短周期MA下穿长周期MA形成死叉,提示卖出信号
 * 4. 价格上穿/下穿MA线,也可作为买卖参考
 *
 * 价格参数类型:
 * - 参数名称:prices
 * - 参数类型:List<BigDecimal>
 * - 参数说明:需要至少1个价格数据点用于计算,根据不同周期需求更多数据点
 *
 * 推荐时间粒度及优缺点:
 * 1. 1分钟(1m):
 *    - 优点:反应迅速,适合超短线交易
 *    - 缺点:噪音多,容易产生虚假信号
 * 2. 5分钟(5m):
 *    - 优点:平衡了反应速度和噪音过滤
 *    - 缺点:仍有一定噪音
 * 3. 15分钟(15m):
 *    - 优点:适合日内交易,信号相对可靠
 *    - 缺点:反应速度较慢
 * 4. 1小时(1h)及以上:
 *    - 优点:趋势信号明确,虚假信号少
 *    - 缺点:反应滞后,不适合短线交易
 */
@Slf4j
@Getter
@Setter
public class MA extends IndicatorBase {
    // 常用周期
    public static final int MA5 = 5;
    public static final int MA10 = 10;
    public static final int MA20 = 20;
    public static final int MA30 = 30;
    public static final int MA60 = 60;
    // 默认周期
    public static final int DEFAULT_MA5 = 5;
    public static final int DEFAULT_MA10 = 10;
    public static final int DEFAULT_MA20 = 20;
    public static final int DEFAULT_MA30 = 30;
    public static final int DEFAULT_MA60 = 60;
    // 动态周期参数
    private int ma5Period;
    private int ma10Period;
    private int ma20Period;
    private int ma30Period;
    private int ma60Period;
    private BigDecimal ma5 = BigDecimal.ZERO;
    private BigDecimal ma10 = BigDecimal.ZERO;
@@ -41,52 +73,101 @@
    private BigDecimal prevEma30 = null;
    private BigDecimal prevEma60 = null;
    // 构造函数使用默认周期
    public MA() {
        this.ma5Period = DEFAULT_MA5;
        this.ma10Period = DEFAULT_MA10;
        this.ma20Period = DEFAULT_MA20;
        this.ma30Period = DEFAULT_MA30;
        this.ma60Period = DEFAULT_MA60;
    }
    /**
     * 计算所有周期的MA指标
     * 计算所有周期的MA指标(使用当前周期设置)
     * @param prices 价格列表
     */
    public void calculate(List<BigDecimal> prices) {
        calculate(prices, null);
    }
    /**
     * 计算所有周期的MA指标,并支持动态周期调整
     * @param prices 价格列表
     * @param volatility 标准化波动率(ATR百分比),用于动态调整周期
     */
    public void calculate(List<BigDecimal> prices, BigDecimal volatility) {
        if (prices == null || prices.size() < 1) {
            return;
        }
        // 如果提供了波动率,则动态调整周期
        if (volatility != null) {
            adjustPeriodsByVolatility(volatility);
        }
        // 计算SMA
        if (prices.size() >= MA5) {
            ma5 = calculateMA(prices, MA5);
        if (prices.size() >= ma5Period) {
            ma5 = calculateMA(prices, ma5Period);
        }
        if (prices.size() >= MA10) {
            ma10 = calculateMA(prices, MA10);
        if (prices.size() >= ma10Period) {
            ma10 = calculateMA(prices, ma10Period);
        }
        if (prices.size() >= MA20) {
            ma20 = calculateMA(prices, MA20);
        if (prices.size() >= ma20Period) {
            ma20 = calculateMA(prices, ma20Period);
        }
        if (prices.size() >= MA30) {
            ma30 = calculateMA(prices, MA30);
        if (prices.size() >= ma30Period) {
            ma30 = calculateMA(prices, ma30Period);
        }
        if (prices.size() >= MA60) {
            ma60 = calculateMA(prices, MA60);
        if (prices.size() >= ma60Period) {
            ma60 = calculateMA(prices, ma60Period);
        }
        // 计算EMA
        prevEma5 = calculateEMA(prices, MA5, prevEma5);
        prevEma5 = calculateEMA(prices, ma5Period, prevEma5);
        ema5 = prevEma5;
        
        prevEma10 = calculateEMA(prices, MA10, prevEma10);
        prevEma10 = calculateEMA(prices, ma10Period, prevEma10);
        ema10 = prevEma10;
        
        prevEma20 = calculateEMA(prices, MA20, prevEma20);
        prevEma20 = calculateEMA(prices, ma20Period, prevEma20);
        ema20 = prevEma20;
        
        prevEma30 = calculateEMA(prices, MA30, prevEma30);
        prevEma30 = calculateEMA(prices, ma30Period, prevEma30);
        ema30 = prevEma30;
        
        prevEma60 = calculateEMA(prices, MA60, prevEma60);
        prevEma60 = calculateEMA(prices, ma60Period, prevEma60);
        ema60 = prevEma60;
        log.debug("MA计算结果 - MA5: {}, MA10: {}, MA20: {}, MA30: {}, MA60: {}",
                ma5, ma10, ma20, ma30, ma60);
        log.debug("EMA计算结果 - EMA5: {}, EMA10: {}, EMA20: {}, EMA30: {}, EMA60: {}",
                ema5, ema10, ema20, ema30, ema60);
        log.debug("MA计算结果 - MA5({}): {}, MA10({}): {}, MA20({}): {}, MA30({}): {}, MA60({}): {}",
                ma5Period, ma5, ma10Period, ma10, ma20Period, ma20, ma30Period, ma30, ma60Period, ma60);
        log.debug("EMA计算结果 - EMA5({}): {}, EMA10({}): {}, EMA20({}): {}, EMA30({}): {}, EMA60({}): {}",
                ma5Period, ema5, ma10Period, ema10, ma20Period, ema20, ma30Period, ema30, ma60Period, ema60);
    }
    /**
     * 根据波动率调整MA周期
     * @param volatility 标准化波动率(ATR百分比)
     */
    private void adjustPeriodsByVolatility(BigDecimal volatility) {
        // 根据波动率缩放均线周期
        // 3%、5%、8%作为ATR阈值
        BigDecimal lowVolatility = new BigDecimal(3);
        BigDecimal midVolatility = new BigDecimal(5);
        BigDecimal highVolatility = new BigDecimal(8);
        // 快速MA周期 (ma5)
        ma5Period = volatility.compareTo(lowVolatility) < 0 ? 10 : 6;
        // 中期MA周期 (ma10, ma20)
        ma10Period = volatility.compareTo(midVolatility) < 0 ? 10 : 8;
        ma20Period = volatility.compareTo(midVolatility) < 0 ? 21 : 13;
        // 长期MA周期 (ma30, ma60)
        ma30Period = volatility.compareTo(highVolatility) < 0 ? 30 : 24;
        ma60Period = volatility.compareTo(highVolatility) < 0 ? 50 : 34;
        log.debug("根据波动率{}调整MA周期: ma5={}, ma10={}, ma20={}, ma30={}, ma60={}",
                volatility, ma5Period, ma10Period, ma20Period, ma30Period, ma60Period);
    }
    /**
src/main/java/com/xcong/excoin/modules/okxNewPrice/indicator/MACD.java
@@ -15,15 +15,47 @@
 * 1. DIF = EMA(12) - EMA(26)
 * 2. DEA = EMA(DIF, 9)
 * 3. MACD柱状图 = (DIF - DEA) * 2
 *
 * 作用:
 * 1. 识别趋势方向和动能变化
 * 2. DIF上穿DEA形成金叉,提示买入信号
 * 3. DIF下穿DEA形成死叉,提示卖出信号
 * 4. MACD柱状图由负转正,提示多头力量增强
 * 5. MACD柱状图由正转负,提示空头力量增强
 *
 * 价格参数类型:
 * - 参数名称:prices
 * - 参数类型:List<BigDecimal>
 * - 参数说明:需要至少2个价格数据点用于计算,数据越多计算越准确
 *
 * 推荐时间粒度及优缺点:
 * 1. 1分钟(1m):
 *    - 优点:反应迅速,适合超短线交易
 *    - 缺点:MACD柱状图波动剧烈,信号频繁
 * 2. 5分钟(5m):
 *    - 优点:平衡了反应速度和信号可靠性
 *    - 缺点:仍有一定噪音
 * 3. 15分钟(15m):
 *    - 优点:适合日内交易,信号较为可靠
 *    - 缺点:反应速度较慢
 * 4. 1小时(1h)及以上:
 *    - 优点:趋势信号明确,MACD柱状图变化稳定
 *    - 缺点:反应滞后,不适合短线交易
 */
@Slf4j
@Getter
@Setter
public class MACD extends IndicatorBase {
    private static final int FAST_PERIOD = 12;
    private static final int SLOW_PERIOD = 26;
    private static final int SIGNAL_PERIOD = 9;
    // 默认周期参数
    public static final int DEFAULT_FAST_PERIOD = 12;
    public static final int DEFAULT_SLOW_PERIOD = 26;
    public static final int DEFAULT_SIGNAL_PERIOD = 9;
    // 动态周期参数
    private int fastPeriod;
    private int slowPeriod;
    private int signalPeriod;
    private BigDecimal dif = BigDecimal.ZERO;
    private BigDecimal dea = BigDecimal.ZERO;
@@ -32,20 +64,41 @@
    private BigDecimal prevSlowEMA = null;
    private BigDecimal prevDea = null;
    // 构造函数使用默认周期
    public MACD() {
        this.fastPeriod = DEFAULT_FAST_PERIOD;
        this.slowPeriod = DEFAULT_SLOW_PERIOD;
        this.signalPeriod = DEFAULT_SIGNAL_PERIOD;
    }
    /**
     * 计算MACD指标
     * 计算MACD指标(使用当前周期设置)
     * @param prices 价格列表
     */
    public void calculate(List<BigDecimal> prices) {
        calculate(prices, null);
    }
    /**
     * 计算MACD指标,并支持动态周期调整
     * @param prices 价格列表
     * @param volatility 标准化波动率(百分比),用于动态调整周期
     */
    public void calculate(List<BigDecimal> prices, BigDecimal volatility) {
        if (prices == null || prices.size() < 2) {
            return;
        }
        // 如果提供了波动率,则动态调整周期
        if (volatility != null) {
            adjustPeriodsByVolatility(volatility);
        }
        // 计算快速EMA
        prevFastEMA = calculateEMA(prices, FAST_PERIOD, prevFastEMA);
        prevFastEMA = calculateEMA(prices, fastPeriod, prevFastEMA);
        
        // 计算慢速EMA
        prevSlowEMA = calculateEMA(prices, SLOW_PERIOD, prevSlowEMA);
        prevSlowEMA = calculateEMA(prices, slowPeriod, prevSlowEMA);
        
        // 计算DIF
        dif = prevFastEMA.subtract(prevSlowEMA).setScale(8, RoundingMode.HALF_UP);
@@ -53,13 +106,39 @@
        // 计算DEA
        List<BigDecimal> difList = new ArrayList<>();
        difList.add(dif);
        prevDea = calculateEMA(difList, SIGNAL_PERIOD, prevDea);
        prevDea = calculateEMA(difList, signalPeriod, prevDea);
        dea = prevDea.setScale(8, RoundingMode.HALF_UP);
        
        // 计算MACD柱状图
        macdBar = dif.subtract(dea).multiply(new BigDecimal(2)).setScale(8, RoundingMode.HALF_UP);
        
        log.debug("MACD计算结果 - DIF: {}, DEA: {}, MACD柱状图: {}", dif, dea, macdBar);
        log.debug("MACD计算结果 - DIF: {}, DEA: {}, MACD柱状图: {}, 参数: fast={}, slow={}, signal={}",
                dif, dea, macdBar, fastPeriod, slowPeriod, signalPeriod);
    }
    /**
     * 根据波动率调整MACD周期参数
     * @param volatility 标准化波动率(百分比)
     */
    private void adjustPeriodsByVolatility(BigDecimal volatility) {
        // 波动率阈值
        BigDecimal volatilityThreshold = new BigDecimal(15);
        // 根据波动率调整MACD参数
        if (volatility.compareTo(volatilityThreshold) < 0) {
            // 低波动率环境,使用默认参数
            fastPeriod = DEFAULT_FAST_PERIOD;
            slowPeriod = DEFAULT_SLOW_PERIOD;
            signalPeriod = DEFAULT_SIGNAL_PERIOD;
        } else {
            // 高波动率环境,使用更灵敏的参数
            fastPeriod = 8;
            slowPeriod = 17;
            signalPeriod = 5;
        }
        log.debug("根据波动率{}调整MACD周期: fast={}, slow={}, signal={}",
                volatility, fastPeriod, slowPeriod, signalPeriod);
    }
    /**
src/main/java/com/xcong/excoin/modules/okxNewPrice/indicator/RSI.java
@@ -14,6 +14,32 @@
 * 1. 计算N天内的上涨幅度和下跌幅度
 * 2. 计算平均上涨幅度和平均下跌幅度
 * 3. RSI = 100 - (100 / (1 + (平均上涨幅度 / 平均下跌幅度)))
 *
 * 作用:
 * 1. 衡量市场的相对强弱程度(0-100)
 * 2. 超买信号:RSI>70表示市场超买,可能回调
 * 3. 超卖信号:RSI<30表示市场超卖,可能反弹
 * 4. 极端超买:RSI>80表示市场极度超买
 * 5. 极端超卖:RSI<20表示市场极度超卖
 *
 * 价格参数类型:
 * - 参数名称:prices
 * - 参数类型:List<BigDecimal>
 * - 参数说明:需要至少15个(默认周期+1)价格数据点用于计算
 *
 * 推荐时间粒度及优缺点:
 * 1. 1分钟(1m):
 *    - 优点:反应迅速,适合超短线交易
 *    - 缺点:RSI波动剧烈,频繁进入超买超卖区域
 * 2. 5分钟(5m):
 *    - 优点:RSI波动相对稳定,适合短线交易
 *    - 缺点:仍有一定虚假超买超卖信号
 * 3. 15分钟(15m):
 *    - 优点:超买超卖信号较为可靠,适合日内交易
 *    - 缺点:反应速度较慢
 * 4. 1小时(1h)及以上:
 *    - 优点:超买超卖信号明确,适合中期交易
 *    - 缺点:反应滞后,不适合短线交易
 */
@Slf4j
@Getter
src/main/java/com/xcong/excoin/modules/okxNewPrice/indicator/TradingStrategy.java
New file
@@ -0,0 +1,738 @@
package com.xcong.excoin.modules.okxNewPrice.indicator;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;
/**
 * 交易策略实现
 * 展示如何为ETH合约交易(开仓/平仓)组合所有指标
 */
@Slf4j
public class TradingStrategy extends IndicatorBase {
    @Getter
    @Setter
    @AllArgsConstructor
    @NoArgsConstructor
    public static class StrategyConfig {
        private int maShortPeriod = 5;      // 短期移动平均周期
        private int maLongPeriod = 20;      // 长期移动平均周期
        private int rsiPeriod = 14;         // RSI指标周期
        private int kdjPeriod = 9;          // KDJ指标周期
        private int bollPeriod = 20;        // 布林带周期
        private double bollK = 2.0;         // 布林带标准差倍数
        private int atrPeriod = 14;         // ATR计算周期
        private boolean enableDynamicParams = true; // 是否启用动态参数优化
        private boolean enableMultiTimeframeConfirm = true; // 是否启用多周期确认
        private int volumeMaPeriod = 20;    // 成交量移动平均周期
        private boolean enableVolumeConfirm = true; // 是否启用成交量验证
        // 风险控制参数
        private BigDecimal baseLeverage = new BigDecimal(3); // 基础杠杆倍数
        private int volatilityThresholdPeriod = 30; // 波动率阈值计算周期(用于动态杠杆)
        private boolean enableDynamicLeverage = true; // 是否启用动态杠杆
        private boolean enableThreeStepProfitTaking = true; // 是否启用三段式止盈
        private boolean enableBlackSwanFilter = true; // 是否启用黑天鹅事件过滤
    }
    public enum Direction {
        LONG,      // 做多方向信号
        SHORT,     // 做空方向信号
        RANGING    // 震荡市场
    }
    public enum SignalType {
        NONE,      // 无信号
        BUY,       // 开多信号
        SELL,      // 开空信号
        CLOSE_BUY, // 平多信号
        CLOSE_SELL // 平空信号
    }
    private final StrategyConfig config;
    private final MA ma;
    private final AdvancedMA advancedMA;
    private final BOLL boll;
    private final KDJ kdj;
    private final MACD macd;
    private final RSI rsi;
    public TradingStrategy() {
        this(new StrategyConfig());
    }
    public TradingStrategy(StrategyConfig config) {
        this.config = config;
        this.ma = new MA();
        this.advancedMA = new AdvancedMA();
        this.boll = new BOLL(config.getBollPeriod(), config.getBollK());
        this.kdj = new KDJ(config.getKdjPeriod());
        this.macd = new MACD();
        this.rsi = new RSI(config.getRsiPeriod());
    }
    /**
     * 计算所有指标并生成交易信号
     * @param prices 价格数据
     * @param high 最高价列表
     * @param low 最低价列表
     * @param close 收盘价列表
     * @param volume 成交量列表
     * @param currentPrice 当前价格
     * @param hasLongPosition 是否当前持有做多仓位
     * @param hasShortPosition 是否当前持有做空仓位
     * @param fiveMinPrices 5分钟价格数据(多周期确认)
     * @param oneHourPrices 1小时价格数据(多周期确认)
     * @param fourHourPrices 4小时价格数据(多周期确认)
     * @param fundingRate 当前资金费率(用于黑天鹅过滤)
     * @param hasLargeTransfer 是否有大额转账(用于黑天鹅过滤)
     * @param hasUpcomingEvent 是否有即将到来的重大事件(用于黑天鹅过滤)
     * @return 交易信号
     */
    public SignalType generateSignal(List<BigDecimal> prices, List<BigDecimal> high,
                                    List<BigDecimal> low, List<BigDecimal> close,
                                    List<BigDecimal> volume, BigDecimal currentPrice,
                                    boolean hasLongPosition, boolean hasShortPosition,
                                    List<BigDecimal> fiveMinPrices,
                                    List<BigDecimal> oneHourPrices,
                                    List<BigDecimal> fourHourPrices,
                                    BigDecimal fundingRate,
                                    boolean hasLargeTransfer,
                                    boolean hasUpcomingEvent) {
        // 计算所有指标
        calculateIndicators(prices, high, low, close);
        // 检查是否为震荡市场,如果是,则无信号
        if (isRangeMarket()) {
            log.debug("当前市场为震荡行情,不产生信号");
            return SignalType.NONE;
        }
        // 黑天鹅事件过滤
        if (blackSwanFilter(fundingRate, hasLargeTransfer, hasUpcomingEvent)) {
            log.debug("黑天鹅事件过滤触发,不产生信号");
            return SignalType.NONE;
        }
        // 开多信号
        if (shouldOpenLong(currentPrice, prices, volume, fiveMinPrices, oneHourPrices, fourHourPrices) && !hasLongPosition && !hasShortPosition) {
            log.debug("生成买入信号");
            return SignalType.BUY;
        }
        // 开空信号
        if (shouldOpenShort(currentPrice, volume, fiveMinPrices, oneHourPrices, fourHourPrices) && !hasLongPosition && !hasShortPosition) {
            log.debug("生成卖出信号");
            return SignalType.SELL;
        }
        // 平多信号
        if (shouldCloseLong(currentPrice, volume, fiveMinPrices, oneHourPrices, fourHourPrices) && hasLongPosition) {
            log.debug("生成平多信号");
            return SignalType.CLOSE_BUY;
        }
        // 平空信号
        if (shouldCloseShort(currentPrice, volume, fiveMinPrices, oneHourPrices, fourHourPrices) && hasShortPosition) {
            log.debug("生成平空信号");
            return SignalType.CLOSE_SELL;
        }
        log.debug("未生成信号");
        return SignalType.NONE;
    }
    /**
     * 多周期确认辅助方法(看涨)
     * @param fiveMinPrices 5分钟价格数据
     * @param oneHourPrices 1小时价格数据
     * @param fourHourPrices 4小时价格数据
     * @return 是否有足够的多周期确认
     */
    private boolean multiTimeframeConfirm(List<BigDecimal> fiveMinPrices,
                                         List<BigDecimal> oneHourPrices,
                                         List<BigDecimal> fourHourPrices) {
        if (!config.isEnableMultiTimeframeConfirm()) {
            return true; // 如果未启用多周期确认,则默认返回true
        }
        int confirmCount = 0;
        // 检查5分钟周期
        if (hasBullishTrend(fiveMinPrices)) {
            confirmCount++;
        }
        // 检查1小时周期
        if (hasBullishTrend(oneHourPrices)) {
            confirmCount++;
        }
        // 检查4小时周期
        if (hasBullishTrend(fourHourPrices)) {
            confirmCount++;
        }
        // 至少需要2个周期确认
        return confirmCount >= 2;
    }
    /**
     * 多周期确认辅助方法(看跌)
     * @param fiveMinPrices 5分钟价格数据
     * @param oneHourPrices 1小时价格数据
     * @param fourHourPrices 4小时价格数据
     * @return 是否有足够的多周期确认
     */
    private boolean multiTimeframeBearishConfirm(List<BigDecimal> fiveMinPrices,
                                         List<BigDecimal> oneHourPrices,
                                         List<BigDecimal> fourHourPrices) {
        if (!config.isEnableMultiTimeframeConfirm()) {
            return true; // 如果未启用多周期确认,则默认返回true
        }
        int confirmCount = 0;
        // 检查5分钟周期
        if (hasBearishTrend(fiveMinPrices)) {
            confirmCount++;
        }
        // 检查1小时周期
        if (hasBearishTrend(oneHourPrices)) {
            confirmCount++;
        }
        // 检查4小时周期
        if (hasBearishTrend(fourHourPrices)) {
            confirmCount++;
        }
        // 至少需要2个周期确认
        return confirmCount >= 2;
    }
    /**
     * 检查指定周期是否有看涨趋势
     * @param prices 价格数据
     * @return 是否有看涨趋势
     */
    private boolean hasBullishTrend(List<BigDecimal> prices) {
        if (prices == null || prices.size() < 20) {
            return false; // 数据不足
        }
        // 创建临时MA指标用于判断趋势
        MA tempMA = new MA();
        tempMA.calculate(prices);
        // 简单的趋势判断:短期MA > 长期MA
        return tempMA.getEma5().compareTo(tempMA.getEma20()) > 0;
    }
    /**
     * 检查指定周期是否有看跌趋势
     * @param prices 价格数据
     * @return 是否有看跌趋势
     */
    private boolean hasBearishTrend(List<BigDecimal> prices) {
        if (prices == null || prices.size() < 20) {
            return false; // 数据不足
        }
        // 创建临时MA指标用于判断趋势
        MA tempMA = new MA();
        tempMA.calculate(prices);
        // 简单的趋势判断:短期MA < 长期MA
        return tempMA.getEma5().compareTo(tempMA.getEma20()) < 0;
    }
    /**
     * 成交量验证辅助方法
     * @param volume 成交量列表
     * @return 是否通过成交量验证
     */
    private boolean volumeConfirm(List<BigDecimal> volume) {
        if (!config.isEnableVolumeConfirm() || volume == null || volume.size() < config.getVolumeMaPeriod()) {
            return true; // 如果未启用成交量验证或数据不足,则默认返回true
        }
        // 计算成交量移动平均
        BigDecimal volumeMA = calculateMA(volume, config.getVolumeMaPeriod());
        BigDecimal currentVolume = volume.get(volume.size() - 1);
        // 成交量需要大于均线
        return currentVolume.compareTo(volumeMA) > 0;
    }
    /**
     * 量价背离检测
     * 检测价格上涨/下跌但成交量萎缩的情况,或价格和成交量趋势不一致
     * @param prices 价格列表
     * @param volume 成交量列表
     * @return 是否存在量价背离
     */
    private boolean hasPriceVolumeDivergence(List<BigDecimal> prices, List<BigDecimal> volume) {
        if (!config.isEnableVolumeConfirm() || prices == null || volume == null || prices.size() < 3 || volume.size() < 3) {
            return false; // 如果未启用成交量验证或数据不足,则默认返回false
        }
        // 获取最近3个周期的价格和成交量
        BigDecimal currentPrice = prices.get(prices.size() - 1);
        BigDecimal prevPrice1 = prices.get(prices.size() - 2);
        BigDecimal prevPrice2 = prices.get(prices.size() - 3);
        BigDecimal currentVolume = volume.get(volume.size() - 1);
        BigDecimal prevVolume1 = volume.get(volume.size() - 2);
        BigDecimal prevVolume2 = volume.get(volume.size() - 3);
        // 计算价格趋势
        boolean priceTrendUp = currentPrice.compareTo(prevPrice1) > 0 && prevPrice1.compareTo(prevPrice2) > 0;
        boolean priceTrendDown = currentPrice.compareTo(prevPrice1) < 0 && prevPrice1.compareTo(prevPrice2) < 0;
        // 计算成交量趋势
        boolean volumeTrendUp = currentVolume.compareTo(prevVolume1) > 0 && prevVolume1.compareTo(prevVolume2) > 0;
        boolean volumeTrendDown = currentVolume.compareTo(prevVolume1) < 0 && prevVolume1.compareTo(prevVolume2) < 0;
        // 检测量价背离
        // 价格上涨但成交量萎缩
        boolean bullishDivergence = priceTrendUp && volumeTrendDown;
        // 价格下跌但成交量萎缩(通常是强势信号,不视为背离)
        // 价格下跌但成交量放大(可能是恐慌性抛售,视为背离)
        boolean bearishDivergence = priceTrendDown && volumeTrendUp;
        return bullishDivergence || bearishDivergence;
    }
    /**
     * 计算所有指标
     * @param prices 价格数据
     * @param high 最高价列表
     * @param low 最低价列表
     * @param close 收盘价列表
     */
    private void calculateIndicators(List<BigDecimal> prices, List<BigDecimal> high,
                                    List<BigDecimal> low, List<BigDecimal> close) {
        // 计算ATR和波动率
        BigDecimal atr = calculateATR(high, low, close, config.getAtrPeriod());
        BigDecimal volatility = normalizeVolatility(close, atr);
        // 使用动态参数计算指标
        if (config.isEnableDynamicParams()) {
            log.debug("使用动态参数计算指标,波动率: {}", volatility);
            ma.calculate(prices, volatility);
            macd.calculate(prices, volatility);
        } else {
            ma.calculate(prices);
            macd.calculate(prices);
        }
        // 其他指标计算
        advancedMA.calculateTripleEMA(prices);
        boll.calculate(prices);
        kdj.calculate(prices);
        rsi.calculate(prices);
    }
    /**
     * 检查是否为震荡市场
     * @return 是否为震荡市场
     */
    private boolean isRangeMarket() {
        // 高级MA线收敛 + RSI(40-60) + 布林带收窄
        boolean isMaConverged = advancedMA.calculatePercent().compareTo(new BigDecimal(2)) < 0;
        boolean isRsiNeutral = rsi.getRsi().compareTo(new BigDecimal(40)) > 0 &&
                              rsi.getRsi().compareTo(new BigDecimal(60)) < 0;
        boolean isBollNarrow = boll.calculateBandWidth().compareTo(new BigDecimal(0.05)) < 0;
        return isMaConverged && isRsiNeutral && isBollNarrow;
    }
    /**
     * 根据15分钟时间框架指标确定市场方向(做多/做空/震荡)
     * @param prices 价格数据列表
     * @param high 最高价列表
     * @param low 最低价列表
     * @param close 收盘价列表
     * @param currentPrice 当前价格
     * @return 市场方向
     */
    public Direction getDirection(List<BigDecimal> prices, List<BigDecimal> high,
                                List<BigDecimal> low, List<BigDecimal> close,
                                BigDecimal currentPrice) {
        // 计算所有指标
        calculateIndicators(prices, high, low, close);
        // 检查是否为震荡市场
        if (isRangeMarket()) {
            return Direction.RANGING;
        }
        // 检查做多方向条件:MA多头排列 + MACD金叉 + RSI中性(30-70) + BOLL价格在上轨和中轨之间
        boolean isMaBullish = ma.getEma5().compareTo(ma.getEma10()) > 0 &&
                              ma.getEma10().compareTo(ma.getEma20()) > 0;
        boolean isMacdGoldenCross = macd.getDif().compareTo(macd.getDea()) > 0;
        boolean isRsiNeutral = rsi.getRsi().compareTo(new BigDecimal(30)) > 0 &&
                              rsi.getRsi().compareTo(new BigDecimal(70)) < 0;
        boolean isPriceInUpperMid = currentPrice.compareTo(boll.getMid()) > 0 &&
                                   currentPrice.compareTo(boll.getUpper()) < 0;
        if (isMaBullish && isMacdGoldenCross && isRsiNeutral && isPriceInUpperMid) {
            return Direction.LONG;
        }
        // 检查做空方向条件:MA空头排列 + MACD死叉 + RSI中性(30-70) + BOLL价格在下轨和中轨之间
        boolean isMaBearish = ma.getEma5().compareTo(ma.getEma10()) < 0 &&
                              ma.getEma10().compareTo(ma.getEma20()) < 0;
        boolean isMacdDeathCross = macd.getDif().compareTo(macd.getDea()) < 0;
        boolean isPriceInLowerMid = currentPrice.compareTo(boll.getLower()) > 0 &&
                                   currentPrice.compareTo(boll.getMid()) < 0;
        if (isMaBearish && isMacdDeathCross && isRsiNeutral && isPriceInLowerMid) {
            return Direction.SHORT;
        }
        // 如果没有明确方向,默认为震荡
        return Direction.RANGING;
    }
    /**
     * 根据用户要求检查是否应该开多仓位
     * 条件:MA金叉 + MACD金叉 + KDJ金叉 + RSI中性 + 价格高于BOLL中轨 + 多周期确认 + 成交量验证
     * @param currentPrice 当前价格
     * @param prices 价格数据
     * @param volume 成交量列表
     * @param fiveMinPrices 5分钟价格数据
     * @param oneHourPrices 1小时价格数据
     * @param fourHourPrices 4小时价格数据
     * @return 是否应该开多
     */
    private boolean shouldOpenLong(BigDecimal currentPrice, List<BigDecimal> prices, List<BigDecimal> volume,
                                  List<BigDecimal> fiveMinPrices,
                                  List<BigDecimal> oneHourPrices,
                                  List<BigDecimal> fourHourPrices) {
        // MA金叉 (5EMA > 20EMA)
        boolean isMaGoldenCross = ma.getEma5().compareTo(ma.getEma20()) > 0;
        // MACD金叉 (DIF > DEA)
        boolean isMacdGoldenCross = macd.getDif().compareTo(macd.getDea()) > 0;
        // KDJ金叉 (K线上穿D线)
        boolean isKdjGoldenCross = kdj.isGoldenCross();
        // RSI中性 (30-70)
        boolean isRsiNeutral = rsi.getRsi().compareTo(new BigDecimal(30)) > 0 &&
                              rsi.getRsi().compareTo(new BigDecimal(70)) < 0;
        // 价格高于BOLL中轨
        boolean isPriceAboveBollMid = currentPrice.compareTo(boll.getMid()) > 0;
        // 成交量验证
        boolean isVolumeConfirmed = volumeConfirm(volume);
        // 量价背离检测
        boolean isPriceVolumeDivergence = hasPriceVolumeDivergence(prices, volume);
        // 多周期确认
        boolean isMultiTimeframeConfirmed = multiTimeframeConfirm(fiveMinPrices, oneHourPrices, fourHourPrices);
        return isMaGoldenCross && isMacdGoldenCross && isKdjGoldenCross &&
               isRsiNeutral && isPriceAboveBollMid && isVolumeConfirmed &&
               !isPriceVolumeDivergence && isMultiTimeframeConfirmed;
    }
    /**
     * 根据用户要求检查是否应该开空仓位
     * 条件:MA死叉 + MACD死叉 + KDJ死叉 + RSI中性 + 价格低于BOLL中轨 + 多周期确认 + 成交量验证
     * @param currentPrice 当前价格
     * @param volume 成交量列表
     * @param fiveMinPrices 5分钟价格数据
     * @param oneHourPrices 1小时价格数据
     * @param fourHourPrices 4小时价格数据
     * @return 是否应该开空
     */
    private boolean shouldOpenShort(BigDecimal currentPrice, List<BigDecimal> volume,
                                   List<BigDecimal> fiveMinPrices,
                                   List<BigDecimal> oneHourPrices,
                                   List<BigDecimal> fourHourPrices) {
        // MA死叉 (5EMA < 20EMA)
        boolean isMaDeathCross = ma.getEma5().compareTo(ma.getEma20()) < 0;
        // MACD死叉 (DIF < DEA)
        boolean isMacdDeathCross = macd.getDif().compareTo(macd.getDea()) < 0;
        // KDJ死叉 (K线下穿D线)
        boolean isKdjDeathCross = kdj.isDeathCross();
        // RSI中性 (30-70)
        boolean isRsiNeutral = rsi.getRsi().compareTo(new BigDecimal(30)) > 0 &&
                              rsi.getRsi().compareTo(new BigDecimal(70)) < 0;
        // 价格低于BOLL中轨
        boolean isPriceBelowBollMid = currentPrice.compareTo(boll.getMid()) < 0;
        // 成交量验证
        boolean isVolumeConfirmed = volumeConfirm(volume);
        // 多周期确认(看跌)
        boolean isMultiTimeframeConfirmed = multiTimeframeBearishConfirm(fiveMinPrices, oneHourPrices, fourHourPrices);
        return isMaDeathCross && isMacdDeathCross && isKdjDeathCross &&
               isRsiNeutral && isPriceBelowBollMid && isVolumeConfirmed && isMultiTimeframeConfirmed;
    }
    /**
     * 根据用户要求检查是否应该平多仓位
     * 条件:MA死叉 + MACD死叉 + RSI超买 + 价格低于BOLL中轨 + 多周期确认
     * @param currentPrice 当前价格
     * @param volume 成交量列表
     * @param fiveMinPrices 5分钟价格数据
     * @param oneHourPrices 1小时价格数据
     * @param fourHourPrices 4小时价格数据
     * @return 是否应该平多
     */
    private boolean shouldCloseLong(BigDecimal currentPrice, List<BigDecimal> volume,
                                   List<BigDecimal> fiveMinPrices,
                                   List<BigDecimal> oneHourPrices,
                                   List<BigDecimal> fourHourPrices) {
        // MA死叉 (5EMA < 20EMA)
        boolean isMaDeathCross = ma.getEma5().compareTo(ma.getEma20()) < 0;
        // MACD死叉 (DIF < DEA)
        boolean isMacdDeathCross = macd.getDif().compareTo(macd.getDea()) < 0;
        // RSI超买 (>70)
        boolean isRsiOverbought = rsi.isOverbought();
        // 价格低于BOLL中轨
        boolean isPriceBelowBollMid = currentPrice.compareTo(boll.getMid()) < 0;
        // 多周期确认(看跌)
        boolean isMultiTimeframeConfirmed = multiTimeframeBearishConfirm(fiveMinPrices, oneHourPrices, fourHourPrices);
        return isMaDeathCross && isMacdDeathCross && isRsiOverbought && isPriceBelowBollMid && isMultiTimeframeConfirmed;
    }
    /**
     * 根据用户要求检查是否应该平空仓位
     * 条件:MA金叉 + MACD金叉 + RSI超卖 + 价格高于BOLL中轨 + 多周期确认
     * @param currentPrice 当前价格
     * @param volume 成交量列表
     * @param fiveMinPrices 5分钟价格数据
     * @param oneHourPrices 1小时价格数据
     * @param fourHourPrices 4小时价格数据
     * @return 是否应该平空
     */
    private boolean shouldCloseShort(BigDecimal currentPrice, List<BigDecimal> volume,
                                    List<BigDecimal> fiveMinPrices,
                                    List<BigDecimal> oneHourPrices,
                                    List<BigDecimal> fourHourPrices) {
        // MA金叉 (5EMA > 20EMA)
        boolean isMaGoldenCross = ma.getEma5().compareTo(ma.getEma20()) > 0;
        // MACD金叉 (DIF > DEA)
        boolean isMacdGoldenCross = macd.getDif().compareTo(macd.getDea()) > 0;
        // RSI超卖 (<30)
        boolean isRsiOversold = rsi.isOversold();
        // 价格高于BOLL中轨
        boolean isPriceAboveBollMid = currentPrice.compareTo(boll.getMid()) > 0;
        // 多周期确认(看涨)
        boolean isMultiTimeframeConfirmed = multiTimeframeConfirm(fiveMinPrices, oneHourPrices, fourHourPrices);
        return isMaGoldenCross && isMacdGoldenCross && isRsiOversold && isPriceAboveBollMid && isMultiTimeframeConfirmed;
    }
    /**
     * 获取所有指标的当前状态
     * @return 指标状态字符串
     */
    public String getIndicatorStatus() {
        return String.format("MA5: %s, MA20: %s, ", ma.getEma5(), ma.getEma20()) +
               String.format("MACD-DIF: %s, MACD-DEA: %s, MACD-BAR: %s, ", macd.getDif(), macd.getDea(), macd.getMacdBar()) +
               String.format("KDJ-K: %s, KDJ-D: %s, KDJ-J: %s, ", kdj.getK(), kdj.getD(), kdj.getJ()) +
               String.format("RSI: %s, ", rsi.getRsi()) +
               String.format("BOLL-MID: %s, BOLL-UP: %s, BOLL-DN: %s, ", boll.getMid(), boll.getUpper(), boll.getLower()) +
               String.format("AdvancedMA-Bullish: %s, Bearish: %s, Percent: %s",
               advancedMA.isBullish(), advancedMA.isBearish(), advancedMA.calculatePercent());
    }
    /**
     * 计算动态杠杆倍数
     * 杠杆倍数 = 基础杠杆 * (波动率阈值/当前波动率)
     * @param high 最高价列表
     * @param low 最低价列表
     * @param close 收盘价列表
     * @return 动态杠杆倍数
     */
    public BigDecimal calculateDynamicLeverage(List<BigDecimal> high, List<BigDecimal> low, List<BigDecimal> close) {
        if (!config.isEnableDynamicLeverage()) {
            return config.getBaseLeverage();
        }
        // 计算当前ATR和波动率
        BigDecimal currentAtr = calculateATR(high, low, close, config.getAtrPeriod());
        BigDecimal currentVolatility = normalizeVolatility(close, currentAtr);
        // 计算30日ATR移动中位数作为波动率阈值
        BigDecimal volatilityThreshold = calculateVolatilityThreshold(high, low, close);
        // 动态计算杠杆倍数
        BigDecimal leverage = config.getBaseLeverage().multiply(volatilityThreshold).divide(currentVolatility, 2, BigDecimal.ROUND_HALF_UP);
        // 限制杠杆范围在1x-10x之间
        leverage = leverage.min(new BigDecimal(10)).max(BigDecimal.ONE);
        log.debug("动态杠杆计算 - 基础杠杆: {}, 波动率阈值: {}, 当前波动率: {}, 计算杠杆: {}",
                config.getBaseLeverage(), volatilityThreshold, currentVolatility, leverage);
        return leverage;
    }
    /**
     * 计算波动率阈值(30日ATR移动中位数)
     * @param high 最高价列表
     * @param low 最低价列表
     * @param close 收盘价列表
     * @return 波动率阈值
     */
    private BigDecimal calculateVolatilityThreshold(List<BigDecimal> high, List<BigDecimal> low, List<BigDecimal> close) {
        if (high == null || low == null || close == null || close.size() < config.getVolatilityThresholdPeriod()) {
            return new BigDecimal(5); // 默认阈值
        }
        List<BigDecimal> volatilityList = new ArrayList<>();
        for (int i = close.size() - config.getVolatilityThresholdPeriod(); i < close.size(); i++) {
            List<BigDecimal> recentHigh = high.subList(Math.max(0, i - config.getAtrPeriod()), i + 1);
            List<BigDecimal> recentLow = low.subList(Math.max(0, i - config.getAtrPeriod()), i + 1);
            List<BigDecimal> recentClose = close.subList(Math.max(0, i - config.getAtrPeriod()), i + 1);
            BigDecimal atr = calculateATR(recentHigh, recentLow, recentClose, config.getAtrPeriod());
            BigDecimal volatility = normalizeVolatility(recentClose, atr);
            volatilityList.add(volatility);
        }
        // 计算中位数
        volatilityList.sort(BigDecimal::compareTo);
        int midIndex = volatilityList.size() / 2;
        return volatilityList.get(midIndex);
    }
    /**
     * 三段式止盈策略
     * 第一目标:BOLL上轨(30%仓位)
     * 第二目标:斐波那契161.8%(50%仓位)
     * 第三目标:趋势线破位(20%仓位)
     * @param entryPrice 入场价格
     * @param currentPrice 当前价格
     * @param direction 交易方向
     * @param positionSize 当前仓位大小
     * @return 止盈信号和应该平仓的仓位比例
     */
    public ProfitTakingResult calculateThreeStepProfitTaking(BigDecimal entryPrice, BigDecimal currentPrice,
                                                            Direction direction, BigDecimal positionSize) {
        if (!config.isEnableThreeStepProfitTaking()) {
            return new ProfitTakingResult(SignalType.NONE, BigDecimal.ZERO);
        }
        // 计算三个止盈目标
        BigDecimal firstTarget = calculateFirstProfitTarget(entryPrice, currentPrice, direction);
        BigDecimal secondTarget = calculateSecondProfitTarget(entryPrice, direction);
        BigDecimal thirdTarget = calculateThirdProfitTarget(currentPrice, direction);
        // 判断当前价格是否达到目标
        if (direction == Direction.LONG) {
            if (currentPrice.compareTo(thirdTarget) >= 0) {
                // 达到第三目标,平全部仓位
                return new ProfitTakingResult(SignalType.CLOSE_BUY, positionSize);
            } else if (currentPrice.compareTo(secondTarget) >= 0) {
                // 达到第二目标,平50%仓位
                return new ProfitTakingResult(SignalType.CLOSE_BUY, positionSize.multiply(new BigDecimal("0.5")));
            } else if (currentPrice.compareTo(firstTarget) >= 0) {
                // 达到第一目标,平30%仓位
                return new ProfitTakingResult(SignalType.CLOSE_BUY, positionSize.multiply(new BigDecimal("0.3")));
            }
        } else if (direction == Direction.SHORT) {
            if (currentPrice.compareTo(thirdTarget) <= 0) {
                // 达到第三目标,平全部仓位
                return new ProfitTakingResult(SignalType.CLOSE_SELL, positionSize);
            } else if (currentPrice.compareTo(secondTarget) <= 0) {
                // 达到第二目标,平50%仓位
                return new ProfitTakingResult(SignalType.CLOSE_SELL, positionSize.multiply(new BigDecimal("0.5")));
            } else if (currentPrice.compareTo(firstTarget) <= 0) {
                // 达到第一目标,平30%仓位
                return new ProfitTakingResult(SignalType.CLOSE_SELL, positionSize.multiply(new BigDecimal("0.3")));
            }
        }
        return new ProfitTakingResult(SignalType.NONE, BigDecimal.ZERO);
    }
    /**
     * 计算第一止盈目标:BOLL上轨
     */
    private BigDecimal calculateFirstProfitTarget(BigDecimal entryPrice, BigDecimal currentPrice, Direction direction) {
        return direction == Direction.LONG ? boll.getUpper() : boll.getLower();
    }
    /**
     * 计算第二止盈目标:斐波那契161.8%
     */
    private BigDecimal calculateSecondProfitTarget(BigDecimal entryPrice, Direction direction) {
        BigDecimal fibonacciRatio = new BigDecimal("1.618");
        if (direction == Direction.LONG) {
            return entryPrice.multiply(BigDecimal.ONE.add(fibonacciRatio.divide(new BigDecimal(100))));
        } else {
            return entryPrice.multiply(BigDecimal.ONE.subtract(fibonacciRatio.divide(new BigDecimal(100))));
        }
    }
    /**
     * 计算第三止盈目标:简单趋势线破位(这里简化为MA5下穿MA20)
     */
    private BigDecimal calculateThirdProfitTarget(BigDecimal currentPrice, Direction direction) {
        // 这里使用简化的趋势线破位判断
        // 实际应用中可以使用更复杂的趋势线计算
        return direction == Direction.LONG ? ma.getEma20() : ma.getEma20();
    }
    /**
     * 黑天鹅事件过滤
     * 规避重大事件前后30分钟、链上大额转账、异常资金费率
     * @param fundingRate 当前资金费率
     * @param hasLargeTransfer 是否有大额转账
     * @param hasUpcomingEvent 是否有即将到来的重大事件
     * @return 是否应该规避交易
     */
    public boolean blackSwanFilter(BigDecimal fundingRate, boolean hasLargeTransfer, boolean hasUpcomingEvent) {
        if (!config.isEnableBlackSwanFilter()) {
            return false;
        }
        // 资金费率绝对值大于0.2%
        boolean isAbnormalFundingRate = fundingRate != null &&
                fundingRate.abs().compareTo(new BigDecimal("0.002")) > 0;
        // 大额转账监控(这里简化为外部传入)
        // 重大事件监控(这里简化为外部传入)
        boolean shouldAvoid = isAbnormalFundingRate || hasLargeTransfer || hasUpcomingEvent;
        if (shouldAvoid) {
            log.debug("黑天鹅事件过滤触发 - 资金费率异常: {}, 大额转账: {}, 即将发生重大事件: {}",
                    isAbnormalFundingRate, hasLargeTransfer, hasUpcomingEvent);
        }
        return shouldAvoid;
    }
    /**
     * 止盈结果类
     */
    public static class ProfitTakingResult {
        private SignalType signal;
        private BigDecimal closePositionSize;
        public ProfitTakingResult(SignalType signal, BigDecimal closePositionSize) {
            this.signal = signal;
            this.closePositionSize = closePositionSize;
        }
        public SignalType getSignal() {
            return signal;
        }
        public BigDecimal getClosePositionSize() {
            return closePositionSize;
        }
    }
}
src/main/java/com/xcong/excoin/modules/okxNewPrice/okxWs/enums/CoinEnums.java
@@ -50,8 +50,8 @@
    KANG_CANG("抗压比例KANG_CANG","0.9"),
    ZHI_SUN("止损比例ZHI_SUN","0.8"),
    //每次下单的张数
    BUY_CNT("每次开仓的张数buyCnt","0.1"),
    BUY_CNT_INIT("每次初始化开仓张数的基础值buyCntInit","0.1"),
    BUY_CNT("每次开仓的张数buyCnt","0.5"),
    BUY_CNT_INIT("每次初始化开仓张数的基础值buyCntInit","0.5"),
    BUY_CNT_TIME("每次开仓张数的倍数基础值buyCntTime","20"),
    OUT("是否允许下单out","操作中"),
    CTVAL("合约面值ctVal","0.1"),
src/main/java/com/xcong/excoin/modules/okxNewPrice/okxWs/param/Kline.java
New file
@@ -0,0 +1,22 @@
package com.xcong.excoin.modules.okxNewPrice.okxWs.param;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;
import java.io.Serializable;
import java.math.BigDecimal;
@Data
public class Kline{
    private String ts;
    private BigDecimal o;
    private BigDecimal h;
    private BigDecimal l;
    private BigDecimal c;
    private BigDecimal vol;
    private String confirm;
}
src/main/java/com/xcong/excoin/modules/okxNewPrice/okxWs/param/StrategyParam.java
New file
@@ -0,0 +1,11 @@
package com.xcong.excoin.modules.okxNewPrice.okxWs.param;
import lombok.Data;
/**
 * @author Administrator
 */
@Data
public class StrategyParam {
    private String accountName;
}