From c5faaa1111091280365100c95e7e06930b98ee4b Mon Sep 17 00:00:00 2001
From: Administrator <15274802129@163.com>
Date: Tue, 23 Dec 2025 16:21:44 +0800
Subject: [PATCH] feat(indicator): 添加15分钟交易策略和指标详细说明
---
src/main/java/com/xcong/excoin/modules/okxNewPrice/indicator/FifteenMinuteStrategyExample.java | 81 +
src/main/java/com/xcong/excoin/modules/okxNewPrice/OkxWebSocketClientManager.java | 17
src/main/java/com/xcong/excoin/modules/okxNewPrice/okxWs/enums/CoinEnums.java | 4
src/main/java/com/xcong/excoin/modules/okxNewPrice/celue/CaoZuoServiceImpl.java | 76 +
src/main/java/com/xcong/excoin/modules/okxNewPrice/celue/CaoZuoService.java | 2
src/main/java/com/xcong/excoin/modules/okxNewPrice/indicator/FifteenMinuteTradingStrategy.java | 237 +++++
src/main/java/com/xcong/excoin/modules/okxNewPrice/indicator/MA.java | 133 ++
src/main/java/com/xcong/excoin/modules/okxNewPrice/indicator/BOLL.java | 26
src/main/java/com/xcong/excoin/modules/okxNewPrice/indicator/TradingStrategy.java | 738 ++++++++++++++++++
src/main/java/com/xcong/excoin/modules/okxNewPrice/indicator/RSI.java | 26
src/main/java/com/xcong/excoin/modules/okxNewPrice/okxWs/param/Kline.java | 22
src/main/java/com/xcong/excoin/modules/okxNewPrice/OkxKlineWebSocketClient.java | 652 +++++++++++++++
src/main/java/com/xcong/excoin/modules/okxNewPrice/indicator/KDJ.java | 25
src/main/java/com/xcong/excoin/modules/okxNewPrice/indicator/MACD.java | 95 ++
src/main/java/com/xcong/excoin/modules/okxNewPrice/indicator/FifteenMinuteTradingExample.java | 214 +++++
src/main/java/com/xcong/excoin/modules/okxNewPrice/indicator/AdvancedMA.java | 25
src/main/java/com/xcong/excoin/modules/okxNewPrice/indicator/IndicatorBase.java | 67 +
src/main/java/com/xcong/excoin/modules/okxNewPrice/okxWs/param/StrategyParam.java | 11
18 files changed, 2,406 insertions(+), 45 deletions(-)
diff --git a/src/main/java/com/xcong/excoin/modules/okxNewPrice/OkxKlineWebSocketClient.java b/src/main/java/com/xcong/excoin/modules/okxNewPrice/OkxKlineWebSocketClient.java
new file mode 100644
index 0000000..424fc76
--- /dev/null
+++ b/src/main/java/com/xcong/excoin/modules/okxNewPrice/OkxKlineWebSocketClient.java
@@ -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();
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/xcong/excoin/modules/okxNewPrice/OkxWebSocketClientManager.java b/src/main/java/com/xcong/excoin/modules/okxNewPrice/OkxWebSocketClientManager.java
index be64554..4f23fb8 100644
--- a/src/main/java/com/xcong/excoin/modules/okxNewPrice/OkxWebSocketClientManager.java
+++ b/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;
}
}
\ No newline at end of file
diff --git a/src/main/java/com/xcong/excoin/modules/okxNewPrice/celue/CaoZuoService.java b/src/main/java/com/xcong/excoin/modules/okxNewPrice/celue/CaoZuoService.java
index e53aa38..645c35d 100644
--- a/src/main/java/com/xcong/excoin/modules/okxNewPrice/celue/CaoZuoService.java
+++ b/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);
/**
diff --git a/src/main/java/com/xcong/excoin/modules/okxNewPrice/celue/CaoZuoServiceImpl.java b/src/main/java/com/xcong/excoin/modules/okxNewPrice/celue/CaoZuoServiceImpl.java
index a912b74..304ad4c 100644
--- a/src/main/java/com/xcong/excoin/modules/okxNewPrice/celue/CaoZuoServiceImpl.java
+++ b/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);
+ }
+
/**
* 执行主要的操作逻辑,包括读取合约状态、获取市场价格信息,
* 并根据当前持仓均价和标记价格决定是否执行买卖操作。
diff --git a/src/main/java/com/xcong/excoin/modules/okxNewPrice/indicator/AdvancedMA.java b/src/main/java/com/xcong/excoin/modules/okxNewPrice/indicator/AdvancedMA.java
index a7e3aad..41e0278 100644
--- a/src/main/java/com/xcong/excoin/modules/okxNewPrice/indicator/AdvancedMA.java
+++ b/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
diff --git a/src/main/java/com/xcong/excoin/modules/okxNewPrice/indicator/BOLL.java b/src/main/java/com/xcong/excoin/modules/okxNewPrice/indicator/BOLL.java
index 8f00e0c..7ab8b78 100644
--- a/src/main/java/com/xcong/excoin/modules/okxNewPrice/indicator/BOLL.java
+++ b/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
diff --git a/src/main/java/com/xcong/excoin/modules/okxNewPrice/indicator/FifteenMinuteStrategyExample.java b/src/main/java/com/xcong/excoin/modules/okxNewPrice/indicator/FifteenMinuteStrategyExample.java
new file mode 100644
index 0000000..ed1b226
--- /dev/null
+++ b/src/main/java/com/xcong/excoin/modules/okxNewPrice/indicator/FifteenMinuteStrategyExample.java
@@ -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;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/xcong/excoin/modules/okxNewPrice/indicator/FifteenMinuteTradingExample.java b/src/main/java/com/xcong/excoin/modules/okxNewPrice/indicator/FifteenMinuteTradingExample.java
new file mode 100644
index 0000000..d98eccf
--- /dev/null
+++ b/src/main/java/com/xcong/excoin/modules/okxNewPrice/indicator/FifteenMinuteTradingExample.java
@@ -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;
+ }
+}
diff --git a/src/main/java/com/xcong/excoin/modules/okxNewPrice/indicator/FifteenMinuteTradingStrategy.java b/src/main/java/com/xcong/excoin/modules/okxNewPrice/indicator/FifteenMinuteTradingStrategy.java
new file mode 100644
index 0000000..6510022
--- /dev/null
+++ b/src/main/java/com/xcong/excoin/modules/okxNewPrice/indicator/FifteenMinuteTradingStrategy.java
@@ -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());
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/com/xcong/excoin/modules/okxNewPrice/indicator/IndicatorBase.java b/src/main/java/com/xcong/excoin/modules/okxNewPrice/indicator/IndicatorBase.java
index 6872bfd..055e3fc 100644
--- a/src/main/java/com/xcong/excoin/modules/okxNewPrice/indicator/IndicatorBase.java
+++ b/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));
+ }
}
diff --git a/src/main/java/com/xcong/excoin/modules/okxNewPrice/indicator/KDJ.java b/src/main/java/com/xcong/excoin/modules/okxNewPrice/indicator/KDJ.java
index 6651871..142cdd5 100644
--- a/src/main/java/com/xcong/excoin/modules/okxNewPrice/indicator/KDJ.java
+++ b/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
diff --git a/src/main/java/com/xcong/excoin/modules/okxNewPrice/indicator/MA.java b/src/main/java/com/xcong/excoin/modules/okxNewPrice/indicator/MA.java
index 25cba55..e288c00 100644
--- a/src/main/java/com/xcong/excoin/modules/okxNewPrice/indicator/MA.java
+++ b/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);
}
/**
diff --git a/src/main/java/com/xcong/excoin/modules/okxNewPrice/indicator/MACD.java b/src/main/java/com/xcong/excoin/modules/okxNewPrice/indicator/MACD.java
index 92c91b9..c2f869f 100644
--- a/src/main/java/com/xcong/excoin/modules/okxNewPrice/indicator/MACD.java
+++ b/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);
}
/**
diff --git a/src/main/java/com/xcong/excoin/modules/okxNewPrice/indicator/RSI.java b/src/main/java/com/xcong/excoin/modules/okxNewPrice/indicator/RSI.java
index b289d1c..7bbb32b 100644
--- a/src/main/java/com/xcong/excoin/modules/okxNewPrice/indicator/RSI.java
+++ b/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
diff --git a/src/main/java/com/xcong/excoin/modules/okxNewPrice/indicator/TradingStrategy.java b/src/main/java/com/xcong/excoin/modules/okxNewPrice/indicator/TradingStrategy.java
new file mode 100644
index 0000000..0605d0f
--- /dev/null
+++ b/src/main/java/com/xcong/excoin/modules/okxNewPrice/indicator/TradingStrategy.java
@@ -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;
+ }
+ }
+}
diff --git a/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxWs/enums/CoinEnums.java b/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxWs/enums/CoinEnums.java
index b70898d..49fcc6a 100644
--- a/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxWs/enums/CoinEnums.java
+++ b/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"),
diff --git a/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxWs/param/Kline.java b/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxWs/param/Kline.java
new file mode 100644
index 0000000..5adafea
--- /dev/null
+++ b/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxWs/param/Kline.java
@@ -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;
+
+}
diff --git a/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxWs/param/StrategyParam.java b/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxWs/param/StrategyParam.java
new file mode 100644
index 0000000..5dcd018
--- /dev/null
+++ b/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxWs/param/StrategyParam.java
@@ -0,0 +1,11 @@
+package com.xcong.excoin.modules.okxNewPrice.okxWs.param;
+
+import lombok.Data;
+
+/**
+ * @author Administrator
+ */
+@Data
+public class StrategyParam {
+ private String accountName;
+}
--
Gitblit v1.9.1