84 files deleted
2 files added
13 files modified
7938 ■■■■■ changed files
pom.xml 277 ●●●● patch | view | raw | blame | history
src/main/java/com/xcong/excoin/ExcoinApplication.java 6 ●●●●● patch | view | raw | blame | history
src/main/java/com/xcong/excoin/common/annotations/SubmitRepeat.java 13 ●●●●● patch | view | raw | blame | history
src/main/java/com/xcong/excoin/common/annotations/UserAuth.java 15 ●●●●● patch | view | raw | blame | history
src/main/java/com/xcong/excoin/common/aop/SubmitRepeatAspect.java 74 ●●●●● patch | view | raw | blame | history
src/main/java/com/xcong/excoin/common/contants/AppContants.java 79 ●●●●● patch | view | raw | blame | history
src/main/java/com/xcong/excoin/common/enumerates/CoinTypeEnum.java 10 ●●●●● patch | view | raw | blame | history
src/main/java/com/xcong/excoin/common/enumerates/MemberWalletCoinEnum.java 75 ●●●●● patch | view | raw | blame | history
src/main/java/com/xcong/excoin/common/enumerates/OrderClosingTypeEnum.java 55 ●●●●● patch | view | raw | blame | history
src/main/java/com/xcong/excoin/common/enumerates/RabbitPriceTypeEnum.java 31 ●●●●● patch | view | raw | blame | history
src/main/java/com/xcong/excoin/common/enumerates/SymbolEnum.java 39 ●●●●● patch | view | raw | blame | history
src/main/java/com/xcong/excoin/common/exception/GlobalException.java 17 ●●●●● patch | view | raw | blame | history
src/main/java/com/xcong/excoin/common/response/Result.java 80 ●●●●● patch | view | raw | blame | history
src/main/java/com/xcong/excoin/common/response/ResultCode.java 34 ●●●●● patch | view | raw | blame | history
src/main/java/com/xcong/excoin/configurations/GlobalExceptionHandler.java 77 ●●●●● patch | view | raw | blame | history
src/main/java/com/xcong/excoin/configurations/WebMvcConfig.java 36 ●●●●● patch | view | raw | blame | history
src/main/java/com/xcong/excoin/configurations/i18n/CustomLocaleResolver.java 33 ●●●●● patch | view | raw | blame | history
src/main/java/com/xcong/excoin/configurations/i18n/LocaleResolverConfig.java 19 ●●●●● patch | view | raw | blame | history
src/main/java/com/xcong/excoin/configurations/properties/SecurityProperties.java 19 ●●●●● patch | view | raw | blame | history
src/main/java/com/xcong/excoin/configurations/security/CustomAccessDeniedHandler.java 26 ●●●●● patch | view | raw | blame | history
src/main/java/com/xcong/excoin/configurations/security/CustomAuthenticationEntryPoint.java 38 ●●●●● patch | view | raw | blame | history
src/main/java/com/xcong/excoin/configurations/security/WebSecurityConfig.java 79 ●●●●● patch | view | raw | blame | history
src/main/java/com/xcong/excoin/modules/okxNewPrice/GridQueueBuilder.java 152 ●●●●● patch | view | raw | blame | history
src/main/java/com/xcong/excoin/modules/okxNewPrice/OkxGridTradeService.java 481 ●●●● patch | view | raw | blame | history
src/main/java/com/xcong/excoin/modules/okxNewPrice/OkxGridWsClient.java 15 ●●●●● patch | view | raw | blame | history
src/main/java/com/xcong/excoin/modules/okxNewPrice/OkxTradeExecutor.java 8 ●●●●● patch | view | raw | blame | history
src/main/java/com/xcong/excoin/modules/okxNewPrice/OkxWebSocketClientManager.java 11 ●●●● patch | view | raw | blame | history
src/main/java/com/xcong/excoin/modules/okxNewPrice/StopLossManager.java 311 ●●●●● patch | view | raw | blame | history
src/main/java/com/xcong/excoin/modules/okxNewPrice/gridWs/OkxAlgoOrdersChannelHandler.java 89 ●●●●● patch | view | raw | blame | history
src/main/java/com/xcong/excoin/modules/okxNewPrice/gridWs/OkxGridChannelHandler.java 6 ●●●●● patch | view | raw | blame | history
src/main/java/com/xcong/excoin/modules/okxNewPrice/gridWs/OkxKlineChannelHandler.java 8 ●●●●● patch | view | raw | blame | history
src/main/java/com/xcong/excoin/modules/okxNewPrice/gridWs/OkxOrdersChannelHandler.java 8 ●●●●● patch | view | raw | blame | history
src/main/java/com/xcong/excoin/modules/okxNewPrice/gridWs/OkxPositionsChannelHandler.java 8 ●●●●● patch | view | raw | blame | history
src/main/java/com/xcong/excoin/modules/okxNewPrice/okxpi/config/utils/RequestBuilder.java 3 ●●●●● patch | view | raw | blame | history
src/main/java/com/xcong/excoin/utils/MessageSourceUtils.java 31 ●●●●● patch | view | raw | blame | history
src/main/java/com/xcong/excoin/utils/RedisUtils.java 583 ●●●●● patch | view | raw | blame | history
src/main/java/com/xcong/excoin/utils/SSLClient.java 49 ●●●●● patch | view | raw | blame | history
src/main/java/com/xcong/excoin/utils/ShareCodeUtil.java 118 ●●●●● patch | view | raw | blame | history
src/main/java/com/xcong/excoin/utils/SmsUtils.java 96 ●●●●● patch | view | raw | blame | history
src/main/java/com/xcong/excoin/utils/SpringContextHolder.java 66 ●●●●● patch | view | raw | blame | history
src/main/java/com/xcong/excoin/utils/TypeJudgeUtils.java 50 ●●●●● patch | view | raw | blame | history
src/main/java/com/xcong/excoin/utils/api/ApiClient.java 607 ●●●●● patch | view | raw | blame | history
src/main/java/com/xcong/excoin/utils/api/ApiException.java 33 ●●●●● patch | view | raw | blame | history
src/main/java/com/xcong/excoin/utils/api/request/CreateOrderRequest.java 52 ●●●●● patch | view | raw | blame | history
src/main/java/com/xcong/excoin/utils/api/request/DepthRequest.java 26 ●●●●● patch | view | raw | blame | history
src/main/java/com/xcong/excoin/utils/api/request/IntrustOrdersDetailRequest.java 62 ●●●●● patch | view | raw | blame | history
src/main/java/com/xcong/excoin/utils/api/response/Account.java 8 ●●●●● patch | view | raw | blame | history
src/main/java/com/xcong/excoin/utils/api/response/Accounts.java 48 ●●●●● patch | view | raw | blame | history
src/main/java/com/xcong/excoin/utils/api/response/AccountsResponse.java 56 ●●●●● patch | view | raw | blame | history
src/main/java/com/xcong/excoin/utils/api/response/ApiResponse.java 19 ●●●●● patch | view | raw | blame | history
src/main/java/com/xcong/excoin/utils/api/response/Balance.java 57 ●●●●● patch | view | raw | blame | history
src/main/java/com/xcong/excoin/utils/api/response/BalanceBean.java 37 ●●●●● patch | view | raw | blame | history
src/main/java/com/xcong/excoin/utils/api/response/BalanceResponse.java 47 ●●●●● patch | view | raw | blame | history
src/main/java/com/xcong/excoin/utils/api/response/Batchcancel.java 22 ●●●●● patch | view | raw | blame | history
src/main/java/com/xcong/excoin/utils/api/response/BatchcancelBean.java 34 ●●●●● patch | view | raw | blame | history
src/main/java/com/xcong/excoin/utils/api/response/BatchcancelResponse.java 47 ●●●●● patch | view | raw | blame | history
src/main/java/com/xcong/excoin/utils/api/response/Currencys.java 4 ●●●● patch | view | raw | blame | history
src/main/java/com/xcong/excoin/utils/api/response/CurrencysResponse.java 55 ●●●●● patch | view | raw | blame | history
src/main/java/com/xcong/excoin/utils/api/response/Depth.java 57 ●●●●● patch | view | raw | blame | history
src/main/java/com/xcong/excoin/utils/api/response/DepthResponse.java 78 ●●●●● patch | view | raw | blame | history
src/main/java/com/xcong/excoin/utils/api/response/DetailResponse.java 67 ●●●●● patch | view | raw | blame | history
src/main/java/com/xcong/excoin/utils/api/response/Details.java 98 ●●●●● patch | view | raw | blame | history
src/main/java/com/xcong/excoin/utils/api/response/HistoryTrade.java 57 ●●●●● patch | view | raw | blame | history
src/main/java/com/xcong/excoin/utils/api/response/HistoryTradeResponse.java 67 ●●●●● patch | view | raw | blame | history
src/main/java/com/xcong/excoin/utils/api/response/HistoryTradess.java 37 ●●●●● patch | view | raw | blame | history
src/main/java/com/xcong/excoin/utils/api/response/IntrustDetail.java 194 ●●●●● patch | view | raw | blame | history
src/main/java/com/xcong/excoin/utils/api/response/IntrustDetailResponse.java 47 ●●●●● patch | view | raw | blame | history
src/main/java/com/xcong/excoin/utils/api/response/Kline.java 68 ●●●●● patch | view | raw | blame | history
src/main/java/com/xcong/excoin/utils/api/response/KlineResponse.java 55 ●●●●● patch | view | raw | blame | history
src/main/java/com/xcong/excoin/utils/api/response/KlineReturn.java 66 ●●●●● patch | view | raw | blame | history
src/main/java/com/xcong/excoin/utils/api/response/MatchresultsOrdersDetail.java 112 ●●●●● patch | view | raw | blame | history
src/main/java/com/xcong/excoin/utils/api/response/MatchresultsOrdersDetailResponse.java 47 ●●●●● patch | view | raw | blame | history
src/main/java/com/xcong/excoin/utils/api/response/Merged.java 121 ●●●●● patch | view | raw | blame | history
src/main/java/com/xcong/excoin/utils/api/response/MergedResponse.java 77 ●●●●● patch | view | raw | blame | history
src/main/java/com/xcong/excoin/utils/api/response/OrdersDetail.java 178 ●●●●● patch | view | raw | blame | history
src/main/java/com/xcong/excoin/utils/api/response/OrdersDetailResponse.java 50 ●●●●● patch | view | raw | blame | history
src/main/java/com/xcong/excoin/utils/api/response/Place.java 4 ●●●● patch | view | raw | blame | history
src/main/java/com/xcong/excoin/utils/api/response/SubmitcancelResponse.java 47 ●●●●● patch | view | raw | blame | history
src/main/java/com/xcong/excoin/utils/api/response/Symbol.java 9 ●●●●● patch | view | raw | blame | history
src/main/java/com/xcong/excoin/utils/api/response/Symbols.java 98 ●●●●● patch | view | raw | blame | history
src/main/java/com/xcong/excoin/utils/api/response/SymbolsResponse.java 67 ●●●●● patch | view | raw | blame | history
src/main/java/com/xcong/excoin/utils/api/response/TimestampResponse.java 45 ●●●●● patch | view | raw | blame | history
src/main/java/com/xcong/excoin/utils/api/response/Trade.java 38 ●●●●● patch | view | raw | blame | history
src/main/java/com/xcong/excoin/utils/api/response/TradeBean.java 60 ●●●●● patch | view | raw | blame | history
src/main/java/com/xcong/excoin/utils/api/response/TradeResponse.java 70 ●●●●● patch | view | raw | blame | history
src/main/java/com/xcong/excoin/utils/mail/RequestEncoder.java 71 ●●●●● patch | view | raw | blame | history
src/main/java/com/xcong/excoin/utils/mail/Sms106Send.java 78 ●●●●● patch | view | raw | blame | history
src/main/java/com/xcong/excoin/utils/mail/SmsSend.java 164 ●●●●● patch | view | raw | blame | history
src/main/java/com/xcong/excoin/utils/mail/SubMailSend.java 170 ●●●●● patch | view | raw | blame | history
src/main/resources/application-app.yml 116 ●●●●● patch | view | raw | blame | history
src/main/resources/application-dayline.yml 114 ●●●●● patch | view | raw | blame | history
src/main/resources/application-loop.yml 114 ●●●●● patch | view | raw | blame | history
src/main/resources/application-newprice.yml 114 ●●●●● patch | view | raw | blame | history
src/main/resources/application-okx.yml 6 ●●●●● patch | view | raw | blame | history
src/main/resources/application-test.yml 75 ●●●●● patch | view | raw | blame | history
src/main/resources/application.yml 78 ●●●● patch | view | raw | blame | history
src/main/resources/i18n/messages.properties patch | view | raw | blame | history
src/main/resources/i18n/messages_en_US.properties 243 ●●●●● patch | view | raw | blame | history
src/main/resources/i18n/messages_zh_CN.properties 242 ●●●●● patch | view | raw | blame | history
pom.xml
@@ -6,65 +6,78 @@
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.2.6.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
        <relativePath/>
    </parent>
    <groupId>com.xcong</groupId>
    <artifactId>excoin</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>excoin</name>
    <description>Demo project for Spring Boot</description>
    <description>OKX Grid Trading Strategy</description>
    <properties>
        <java.version>1.8</java.version>
        <mysql-driver.version>8.0.17</mysql-driver.version>
        <alibaba-druid.version>1.1.18</alibaba-druid.version>
        <mybatis.version>2.0.1</mybatis.version>
        <mybatis-plus.version>3.3.1.tmp</mybatis-plus.version>
        <validation-api.version>2.0.1.Final</validation-api.version>
        <hibernate-validator.version>6.1.0.Final</hibernate-validator.version>
        <swagger.version>2.9.2</swagger.version>
        <io-swagger.version>1.5.23</io-swagger.version>
        <mapstruct.version>1.3.1.Final</mapstruct.version>
        <hutool.version>5.3.1</hutool.version>
        <fastjson.version>1.2.61</fastjson.version>
        <netty.version>4.1.33.Final</netty.version>
        <dom4j.version>1.6.1</dom4j.version>
        <m2e.apt.activation>jdt_apt</m2e.apt.activation>
        <okhttp.version>3.6.0</okhttp.version>
        <aliyun-oss.version>3.8.0</aliyun-oss.version>
    </properties>
    <dependencies>
        <!-- Spring Boot Web (嵌入式Tomcat) -->
        <dependency>
            <groupId>ripple</groupId>
            <artifactId>ripple</artifactId>
            <version>0.0.1</version>
            <scope>system</scope>
            <systemPath>${basedir}/lib/ripple-core-0.0.1-SNAPSHOT.jar</systemPath>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!-- Lombok -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <!-- OKX REST API 用 OkHttp3 -->
        <dependency>
            <groupId>com.squareup.okhttp3</groupId>
            <artifactId>okhttp</artifactId>
            <version>${okhttp.version}</version>
        </dependency>
        <!-- OKX WebSocket 连接 -->
        <dependency>
            <groupId>org.java-websocket</groupId>
            <artifactId>Java-WebSocket</artifactId>
            <version>1.5.3</version>
        </dependency>
        <!-- JSON 解析 (FastJSON) -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>${fastjson.version}</version>
        </dependency>
        <!-- JSON 解析 (org.json) -->
        <dependency>
            <groupId>org.json</groupId>
            <artifactId>json</artifactId>
            <version>20230618</version>
        </dependency>
        <!-- Hutool 工具库 -->
        <dependency>
            <groupId>com.squareup.okhttp3</groupId>
            <artifactId>okhttp</artifactId>
            <!--            <version>3.6.0</version>-->
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>${hutool.version}</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/com.squareup.okhttp3/logging-interceptor -->
        <!-- Apache Commons Codec (钉钉Base64签名) -->
        <dependency>
            <groupId>com.squareup.okhttp3</groupId>
            <artifactId>logging-interceptor</artifactId>
            <version>3.6.0</version>
            <groupId>commons-codec</groupId>
            <artifactId>commons-codec</artifactId>
            <version>1.15</version>
        </dependency>
        <!-- 钉钉机器人 SDK (本地Jar) -->
        <dependency>
            <groupId>taobao</groupId>
            <artifactId>taobao-sdk</artifactId>
@@ -73,54 +86,7 @@
            <systemPath>${basedir}/lib/taobao-sdk-java.jar</systemPath>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-amqp</artifactId>
        </dependency>
<!--        <dependency>-->
<!--            <groupId>org.springframework.security</groupId>-->
<!--            <artifactId>spring-security-test</artifactId>-->
<!--            <scope>test</scope>-->
<!--        </dependency>-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <!-- 单元测试 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
@@ -132,156 +98,6 @@
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>${mybatis.version}</version>
        </dependency>
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>${mybatis-plus.version}</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>${alibaba-druid.version}</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>${mysql-driver.version}</version>
        </dependency>
        <!-- 参数校验 start -->
        <dependency>
            <groupId>javax.validation</groupId>
            <artifactId>validation-api</artifactId>
            <version>${validation-api.version}</version>
        </dependency>
        <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-validator</artifactId>
            <version>${hibernate-validator.version}</version>
        </dependency>
        <!-- 参数校验 end -->
        <!-- swagger2 start -->
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger2</artifactId>
            <version>${swagger.version}</version>
        </dependency>
        <dependency>
            <groupId>io.swagger</groupId>
            <artifactId>swagger-annotations</artifactId>
            <version>${io-swagger.version}</version>
        </dependency>
        <dependency>
            <groupId>io.swagger</groupId>
            <artifactId>swagger-models</artifactId>
            <version>${io-swagger.version}</version>
        </dependency>
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger-ui</artifactId>
            <version>${swagger.version}</version>
        </dependency>
        <!-- swagger2 end -->
        <!-- bean映射转化 -->
        <dependency>
            <groupId>org.mapstruct</groupId>
            <artifactId>mapstruct</artifactId>
            <version>${mapstruct.version}</version>
        </dependency>
        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>${hutool.version}</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>${fastjson.version}</version>
        </dependency>
        <dependency>
            <groupId>com.google.code.gson</groupId>
            <artifactId>gson</artifactId>
        </dependency>
        <dependency>
            <groupId>org.apache.httpcomponents</groupId>
            <artifactId>httpclient</artifactId>
        </dependency>
        <dependency>
            <groupId>dom4j</groupId>
            <artifactId>dom4j</artifactId>
            <version>${dom4j.version}</version>
        </dependency>
        <dependency>
            <groupId>com.squareup.okhttp3</groupId>
            <artifactId>okhttp</artifactId>
            <version>${okhttp.version}</version>
        </dependency>
        <dependency>
            <groupId>org.java-websocket</groupId>
            <artifactId>Java-WebSocket</artifactId>
            <version>1.5.3</version>
        </dependency>
        <!-- submail邮件 start -->
        <dependency>
            <groupId>commons-logging</groupId>
            <artifactId>commons-logging</artifactId>
            <version>1.1.1</version>
        </dependency>
        <dependency>
            <groupId>commons-collections</groupId>
            <artifactId>commons-collections</artifactId>
            <version>3.2.1</version>
        </dependency>
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
            <version>3.3.2</version>
        </dependency>
        <dependency>
            <groupId>net.sf.ezmorph</groupId>
            <artifactId>ezmorph</artifactId>
            <version>1.0.3</version>
        </dependency>
        <dependency>
            <groupId>net.sf.json-lib</groupId>
            <artifactId>json-lib</artifactId>
            <version>2.2.3</version>
            <classifier>jdk15</classifier>
        </dependency>
        <dependency>
            <groupId>org.apache.httpcomponents</groupId>
            <artifactId>httpmime</artifactId>
            <version>4.3.5</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-pool2</artifactId>
        </dependency>
        <!-- submail邮件 end -->
    </dependencies>
    <build>
@@ -302,11 +118,6 @@
                    <source>${java.version}</source>
                    <target>${java.version}</target>
                    <annotationProcessorPaths>
                        <path>
                            <groupId>org.mapstruct</groupId>
                            <artifactId>mapstruct-processor</artifactId>
                            <version>${mapstruct.version}</version>
                        </path>
                        <path>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
src/main/java/com/xcong/excoin/ExcoinApplication.java
@@ -1,18 +1,16 @@
package com.xcong.excoin;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableScheduling;
import springfox.documentation.swagger2.annotations.EnableSwagger2;
/**
 * OKX 网格交易策略启动入口
 *
 * @author helius
 */
@EnableScheduling
@EnableSwagger2
@SpringBootApplication
@MapperScan("com.xcong.excoin.modules.*.dao")
public class ExcoinApplication {
    public static void main(String[] args) {
src/main/java/com/xcong/excoin/common/annotations/SubmitRepeat.java
File was deleted
src/main/java/com/xcong/excoin/common/annotations/UserAuth.java
File was deleted
src/main/java/com/xcong/excoin/common/aop/SubmitRepeatAspect.java
File was deleted
src/main/java/com/xcong/excoin/common/contants/AppContants.java
File was deleted
src/main/java/com/xcong/excoin/common/enumerates/CoinTypeEnum.java
File was deleted
src/main/java/com/xcong/excoin/common/enumerates/MemberWalletCoinEnum.java
File was deleted
src/main/java/com/xcong/excoin/common/enumerates/OrderClosingTypeEnum.java
File was deleted
src/main/java/com/xcong/excoin/common/enumerates/RabbitPriceTypeEnum.java
File was deleted
src/main/java/com/xcong/excoin/common/enumerates/SymbolEnum.java
File was deleted
src/main/java/com/xcong/excoin/common/exception/GlobalException.java
File was deleted
src/main/java/com/xcong/excoin/common/response/Result.java
File was deleted
src/main/java/com/xcong/excoin/common/response/ResultCode.java
File was deleted
src/main/java/com/xcong/excoin/configurations/GlobalExceptionHandler.java
File was deleted
src/main/java/com/xcong/excoin/configurations/WebMvcConfig.java
File was deleted
src/main/java/com/xcong/excoin/configurations/i18n/CustomLocaleResolver.java
File was deleted
src/main/java/com/xcong/excoin/configurations/i18n/LocaleResolverConfig.java
File was deleted
src/main/java/com/xcong/excoin/configurations/properties/SecurityProperties.java
File was deleted
src/main/java/com/xcong/excoin/configurations/security/CustomAccessDeniedHandler.java
File was deleted
src/main/java/com/xcong/excoin/configurations/security/CustomAuthenticationEntryPoint.java
File was deleted
src/main/java/com/xcong/excoin/configurations/security/WebSecurityConfig.java
File was deleted
src/main/java/com/xcong/excoin/modules/okxNewPrice/GridQueueBuilder.java
New file
@@ -0,0 +1,152 @@
package com.xcong.excoin.modules.okxNewPrice;
import lombok.extern.slf4j.Slf4j;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.ArrayList;
import java.util.List;
/**
 * 网格价格队列构建器 — 从基底入场价生成双向价格队列和 OkxGridElement 列表。
 *
 * <h3>职责</h3>
 * <ul>
 *   <li>根据基底空头入场价,向下递减排空仓队列</li>
 *   <li>向上递增排多仓队列</li>
 *   <li>整合两队列为 OkxGridElement 列表并注入 {@link OkxConfig}</li>
 * </ul>
 *
 * <h3>线程安全</h3>
 * 本类为纯函数式(除 config.setStep / config.setGridElements 副作用外),
 * 每次调用返回新构建的对象,不持有可变状态。
 *
 * @author Administrator
 */
@Slf4j
public final class GridQueueBuilder {
    private GridQueueBuilder() { /* utility class */ }
    /**
     * 从基底入场价向下递减生成空仓价格队列,降序排列(大→小)。
     *
     * @param config   全局配置(step 会被计算并写入)
     * @param basePrice 空仓基底入场价
     * @return 空仓价格队列,按降序排列
     */
    public static List<BigDecimal> buildShortQueue(OkxConfig config, BigDecimal basePrice) {
        int prec = config.getPriceScale();
        BigDecimal step = basePrice.multiply(config.getGridRate()).setScale(prec, RoundingMode.HALF_UP);
        config.setStep(step); // 其它方法依赖此 step
        List<BigDecimal> queue = new ArrayList<>();
        BigDecimal elem = basePrice.subtract(step).setScale(prec, RoundingMode.HALF_UP);
        for (int i = 0; i < config.getGridQueueSize(); i++) {
            queue.add(elem);
            elem = elem.subtract(step).setScale(prec, RoundingMode.HALF_UP);
            if (elem.compareTo(BigDecimal.ZERO) <= 0) break;
        }
        queue.sort((a, b) -> b.compareTo(a)); // 降序
        log.info("[OKX] 空队列:{}", queue);
        return queue;
    }
    /**
     * 从基底入场价向上递增生成多仓价格队列,升序排列(小→大)。
     *
     * @param config   全局配置(使用已设置的 step)
     * @param basePrice 空仓基底入场价(对齐 Gate 版本,多仓队列也以此为基准)
     * @return 多仓价格队列,按升序排列
     */
    public static List<BigDecimal> buildLongQueue(OkxConfig config, BigDecimal basePrice) {
        int prec = config.getPriceScale();
        BigDecimal step = config.getStep();
        List<BigDecimal> queue = new ArrayList<>();
        BigDecimal elem = basePrice.add(step).setScale(prec, RoundingMode.HALF_UP);
        for (int i = 0; i < config.getGridQueueSize(); i++) {
            queue.add(elem);
            elem = elem.add(step).setScale(prec, RoundingMode.HALF_UP);
        }
        queue.sort(BigDecimal::compareTo); // 升序
        log.info("[OKX] 多队列:{}", queue);
        return queue;
    }
    /**
     * 将空/多仓队列整合为 OkxGridElement 列表。
     * <ul>
     *   <li>空仓网格: id = -1, -2, -3 …</li>
     *   <li>基底网格: id = 0(短仓入场价位置)</li>
     *   <li>多仓网格: id = +1, +2, +3 …</li>
     * </ul>
     * 每个网格元素包含两个方向的 {@link OkxTraderParam}(入场价 / 止盈价 / 数量)。
     *
     * @param config     全局配置
     * @param shortQueue 空仓价格队列
     * @param longQueue  多仓价格队列
     * @param basePrice  空仓基底入场价
     * @return 构建好的网格元素列表(已注入 config)
     */
    public static List<OkxGridElement> buildGridElements(OkxConfig config,
                                                          List<BigDecimal> shortQueue,
                                                          List<BigDecimal> longQueue,
                                                          BigDecimal basePrice) {
        List<OkxGridElement> elements = new ArrayList<>();
        int shortSize = shortQueue.size();
        int longSize = longQueue.size();
        int prec = config.getPriceScale();
        BigDecimal step = config.getStep();
        String qty = config.getQuantity();
        // ---- 空仓网格: id = -1, -2, … ----
        for (int i = 0; i < shortSize; i++) {
            int id = -(i + 1);
            Integer upId = (i == 0) ? 0 : id + 1;
            Integer downId = (i == shortSize - 1) ? null : id - 1;
            BigDecimal price = shortQueue.get(i);
            OkxTraderParam longParam = OkxTraderParam.builder()
                    .direction(OkxTraderParam.Direction.LONG)
                    .entryPrice(price).takeProfitPrice(price.add(step).setScale(prec, RoundingMode.HALF_UP)).quantity(qty).build();
            OkxTraderParam shortParam = OkxTraderParam.builder()
                    .direction(OkxTraderParam.Direction.SHORT)
                    .entryPrice(price).takeProfitPrice(price.subtract(step).setScale(prec, RoundingMode.HALF_UP)).quantity(qty).build();
            elements.add(OkxGridElement.builder().id(id).gridPrice(price).upId(upId).downId(downId)
                    .longTraderParam(longParam).shortTraderParam(shortParam).build());
        }
        // ---- 基底网格: id = 0 ----
        {
            BigDecimal price = basePrice;
            OkxTraderParam longParam = OkxTraderParam.builder()
                    .direction(OkxTraderParam.Direction.LONG)
                    .entryPrice(price).takeProfitPrice(price.add(step).setScale(prec, RoundingMode.HALF_UP)).quantity(qty).build();
            OkxTraderParam shortParam = OkxTraderParam.builder()
                    .direction(OkxTraderParam.Direction.SHORT)
                    .entryPrice(price).takeProfitPrice(price.subtract(step).setScale(prec, RoundingMode.HALF_UP)).quantity(qty).build();
            elements.add(OkxGridElement.builder().id(0).gridPrice(price)
                    .upId(shortSize > 0 ? 1 : null).downId(longSize > 0 ? -1 : null)
                    .longTraderParam(longParam).shortTraderParam(shortParam).build());
        }
        // ---- 多仓网格: id = 1, 2, … ----
        for (int i = 0; i < longSize; i++) {
            int id = i + 1;
            Integer downId = (i == 0) ? 0 : id - 1;
            Integer upId = (i == longSize - 1) ? null : id + 1;
            BigDecimal price = longQueue.get(i);
            OkxTraderParam longParam = OkxTraderParam.builder()
                    .direction(OkxTraderParam.Direction.LONG)
                    .entryPrice(price).takeProfitPrice(price.add(step).setScale(prec, RoundingMode.HALF_UP)).quantity(qty).build();
            OkxTraderParam shortParam = OkxTraderParam.builder()
                    .direction(OkxTraderParam.Direction.SHORT)
                    .entryPrice(price).takeProfitPrice(price.subtract(step).setScale(prec, RoundingMode.HALF_UP)).quantity(qty).build();
            elements.add(OkxGridElement.builder().id(id).gridPrice(price).upId(upId).downId(downId)
                    .longTraderParam(longParam).shortTraderParam(shortParam).build());
        }
        config.setGridElements(elements);
        log.info("[OKX] 网格元素列表已构建, 共{}个元素", elements.size());
        return elements;
    }
}
src/main/java/com/xcong/excoin/modules/okxNewPrice/OkxGridTradeService.java
@@ -57,6 +57,7 @@
    private final OkxConfig config;
    private final OkxTradeExecutor executor;
    private final OKXAccount okxAccount;
    private final StopLossManager stopLossManager;
    private volatile StrategyState state = StrategyState.WAITING_KLINE;
@@ -79,6 +80,16 @@
    /** 基底空头是否已开 */
    private volatile boolean baseShortOpened = false;
    // ---- WS 订阅就绪标志 ----
    /** candle1m (Business WS) 订阅已确认 */
    private volatile boolean candle1mSubscribed = false;
    /** positions (Private WS) 订阅已确认 */
    private volatile boolean positionsSubscribed = false;
    /** orders (Private WS) 订阅已确认 */
    private volatile boolean ordersSubscribed = false;
    /** 等待所有订阅就绪期间缓存的最新 K 线价格 */
    private volatile BigDecimal pendingKlinePrice = null;
    private volatile boolean shortActive = false;
    private volatile boolean longActive = false;
@@ -95,6 +106,7 @@
        this.config = config;
        this.okxAccount = okxAccount;
        this.executor = new OkxTradeExecutor(okxAccount, config.getInstId(), config.getTdMode());
        this.stopLossManager = new StopLossManager(config, executor);
    }
    // ---- 初始化 ----
@@ -195,10 +207,15 @@
        baseShortOpened = false;
        longActive = false;
        shortActive = false;
        candle1mSubscribed = false;
        positionsSubscribed = false;
        ordersSubscribed = false;
        pendingKlinePrice = null;
        shortPriceQueue.clear();
        longPriceQueue.clear();
        currentLongOrderIds.clear();
        currentShortOrderIds.clear();
        stopLossManager.resetAllEntryQuantities();
        // 每次重启重新获取当前本金,确保盈亏对比基准正确
        refreshInitialPrincipal();
@@ -253,25 +270,23 @@
        updateUnrealizedPnl();
        if (state == StrategyState.STOPPED) {
            executor.cancelAllAlgoOrders();
            closeExistingPositions();
            // stopGrid() 已做清理,仅打印日志不重复操作
            BigDecimal totalPnl = cumulativePnl.add(unrealizedPnl);
            log.info("[OKX] 已实现:{}, 未实现:{}, 合计:{}", cumulativePnl, unrealizedPnl, totalPnl);
            startGrid();
            return;
        }
        if (state == StrategyState.WAITING_KLINE) {
            // 等待所有 WS 订阅就绪后再开仓
            if (!allSubscriptionsReady()) {
                pendingKlinePrice = closePrice;
                log.info("[OKX] 等待所有 WS 订阅就绪(candle1m={}, positions={}, orders={}), 当前价: {}",
                        candle1mSubscribed, positionsSubscribed, ordersSubscribed, closePrice);
                return;
            }
            state = StrategyState.OPENING;
            log.info("[OKX] 首根K线到达,开基底仓位 多空各{}张...", config.getBaseQuantity());
            executor.openLong(config.getBaseQuantity(), (orderId) -> {
                OkxTraderParam baseLongTp = OkxTraderParam.builder().entryOrderId(orderId).build();
                config.setBaseLongTraderParam(baseLongTp);
            }, null);
            executor.openShort(config.getBaseQuantity(), (orderId) -> {
                OkxTraderParam baseShortTp = OkxTraderParam.builder().entryOrderId(orderId).build();
                config.setBaseShortTraderParam(baseShortTp);
            }, null);
            log.info("[OKX] 首根K线到达,所有订阅就绪,开基底仓位 多空各{}张...", config.getBaseQuantity());
            openBasePositions();
            return;
        }
@@ -279,6 +294,47 @@
            return;
        }
        checkProfitAndReset();
    }
    /**
     * WS 订阅成功确认回调,由各 {@link OkxGridChannelHandler#onSubscribed()} 触发。
     * 当所有订阅就绪且已有缓存的 K 线价格时,自动触发开仓。
     */
    public void onSubscriptionConfirmed(String channel) {
        if ("candle1m".equals(channel)) candle1mSubscribed = true;
        else if ("positions".equals(channel)) positionsSubscribed = true;
        else if ("orders".equals(channel)) ordersSubscribed = true;
        log.info("[OKX] 订阅就绪: {}, 全部就绪: {}", channel, allSubscriptionsReady());
        // 所有订阅就绪 + 有缓存 K 线价格 + 仍处于等待状态 → 触发开仓
        if (allSubscriptionsReady() && pendingKlinePrice != null && state == StrategyState.WAITING_KLINE) {
            BigDecimal price = pendingKlinePrice;
            pendingKlinePrice = null;
            log.info("[OKX] 所有 WS 订阅就绪,触发开仓, 价格: {}", price);
            state = StrategyState.OPENING;
            log.info("[OKX] 首根K线到达,所有订阅就绪,开基底仓位 多空各{}张...", config.getBaseQuantity());
            openBasePositions();
        }
    }
    /** @return true 表示 candle1m + positions + orders 三个频道均已订阅成功 */
    private boolean allSubscriptionsReady() {
        return candle1mSubscribed && positionsSubscribed && ordersSubscribed;
    }
    /** 市价双开基底仓位(多 + 空),对齐 Gate 版本逻辑 */
    private void openBasePositions() {
        executor.openLong(config.getBaseQuantity(), (orderId) -> {
            OkxTraderParam baseLongTp = OkxTraderParam.builder().entryOrderId(orderId).build();
            config.setBaseLongTraderParam(baseLongTp);
            tryGenerateQueues();
        }, null);
        executor.openShort(config.getBaseQuantity(), (orderId) -> {
            OkxTraderParam baseShortTp = OkxTraderParam.builder().entryOrderId(orderId).build();
            config.setBaseShortTraderParam(baseShortTp);
            tryGenerateQueues();
        }, null);
    }
    // ---- 仓位推送回调 ----
@@ -300,12 +356,12 @@
                    baseLongOpened = true;
                    log.info("[OKX] 基底多成交价: {}", longBaseEntryPrice);
                    tryGenerateQueues();
                } else {
                    longPositionSize = posSize;
                }
            } else {
                longPositionSize = posSize;
                tryGenerateQueues(); // 后续 WS 推送触发重试,兜底此前 NPE 失败的情况
            }
            } else {
                if (longActive && state == StrategyState.ACTIVE) {
                    log.info("[OKX] 多仓持仓归零,重置策略");
                    handlePositionZeroAndReset("多仓");
                }
                longActive = false;
@@ -321,12 +377,12 @@
                    baseShortOpened = true;
                    log.info("[OKX] 基底空成交价: {}", shortBaseEntryPrice);
                    tryGenerateQueues();
                } else {
                    shortPositionSize = posSize;
                }
            } else {
                shortPositionSize = posSize;
                tryGenerateQueues(); // 后续 WS 推送触发重试,兜底此前 NPE 失败的情况
            }
            } else {
                if (shortActive && state == StrategyState.ACTIVE) {
                    log.info("[OKX] 空仓持仓归零,重置策略");
                    handlePositionZeroAndReset("空仓");
                }
                shortActive = false;
@@ -360,32 +416,34 @@
            return;
        }
        // 匹配止损单
        // 匹配止损单 → 委托 StopLossManager
        OkxGridElement byLongStopLoss = OkxGridElement.findByLongStopLossOrderId(algoId);
        if (byLongStopLoss != null) {
            handleLongStopLossTriggered(byLongStopLoss);
            stopLossManager.handleLongStopLossTriggered(byLongStopLoss, longEntryPrice);
            return;
        }
        OkxGridElement byShortStopLoss = OkxGridElement.findByShortStopLossOrderId(algoId);
        if (byShortStopLoss != null) {
            handleShortStopLossTriggered(byShortStopLoss);
            stopLossManager.handleShortStopLossTriggered(byShortStopLoss, shortEntryPrice);
            return;
        }
        // 匹配挂单 —— 条件单成交后:清空挂单状态 + 追挂止损 + 挂止盈单
        // 匹配挂单 —— 条件单成交后:清空挂单状态 + 追挂止损
        OkxGridElement shortGridElement = OkxGridElement.findByShortOrderId(algoId);
        if (shortGridElement != null && shortGridElement.isHasShortOrder()) {
            int filledQty = Integer.parseInt(shortGridElement.getShortTraderParam().getQuantity());
            shortEntryTraderIdParam(shortGridElement, null, false);
            extendShortStopLoss(filledQty);
            stopLossManager.clearShortEntryState(shortGridElement);
            stopLossManager.resetShortEntryQty();
            stopLossManager.extendShortStopLoss(filledQty);
            log.info("[OKX] 空单成交 gridId:{}, qty:{}, 追挂止损", shortGridElement.getId(), filledQty);
            return;
        }
        OkxGridElement longGridElement = OkxGridElement.findByLongOrderId(algoId);
        if (longGridElement != null && longGridElement.isHasLongOrder()) {
            int filledQty = Integer.parseInt(longGridElement.getLongTraderParam().getQuantity());
            longEntryTraderIdParam(longGridElement, null, false);
            extendLongStopLoss(filledQty);
            stopLossManager.clearLongEntryState(longGridElement);
            stopLossManager.resetLongEntryQty();
            stopLossManager.extendLongStopLoss(filledQty);
            log.info("[OKX] 多单成交 gridId:{}, qty:{}, 追挂止损", longGridElement.getId(), filledQty);
            return;
        }
@@ -394,346 +452,48 @@
    // ---- 网格队列处理 ----
    private void tryGenerateQueues() {
        // 防止重复执行:一旦已进入 ACTIVE 状态不再重复初始化
        if (state == StrategyState.ACTIVE) {
            return;
        }
        if (baseLongOpened && baseShortOpened) {
            generateShortQueue();
            generateLongQueue();
            updateGridElements();
            // 防御异步竞态:openLong/openShort 的回调可能还未设置 trader param
            OkxTraderParam baseLongTp = config.getBaseLongTraderParam();
            OkxTraderParam baseShortTp = config.getBaseShortTraderParam();
            if (baseLongTp == null || baseShortTp == null) {
                log.warn("[OKX] tryGenerateQueues 等待异步回调: longTp={}, shortTp={}",
                        baseLongTp != null, baseShortTp != null);
                return;
            }
            // 委托 GridQueueBuilder 构建价格队列 + GridElements
            List<BigDecimal> tmpShort = GridQueueBuilder.buildShortQueue(config, shortBaseEntryPrice);
            List<BigDecimal> tmpLong = GridQueueBuilder.buildLongQueue(config, shortBaseEntryPrice);
            synchronized (shortPriceQueue) {
                shortPriceQueue.clear();
                shortPriceQueue.addAll(tmpShort);
            }
            synchronized (longPriceQueue) {
                longPriceQueue.clear();
                longPriceQueue.addAll(tmpLong);
            }
            GridQueueBuilder.buildGridElements(config, shortPriceQueue, longPriceQueue, shortBaseEntryPrice);
            // 标记基座挂单
            OkxGridElement baseGridElement = OkxGridElement.findById(0);
            OkxTraderParam baseLongTp = config.getBaseLongTraderParam();
            baseGridElement.setLongOrderId(baseLongTp.getEntryOrderId());
            baseGridElement.setHasLongOrder(true);
            OkxTraderParam baseShortTp = config.getBaseShortTraderParam();
            baseGridElement.setShortOrderId(baseShortTp.getEntryOrderId());
            baseGridElement.setHasShortOrder(true);
            // 挂多仓止损 (id=-2 到 -11),每格 quantity 张
            for (int id = -2; id >= -11; id--) {
                OkxGridElement elem = OkxGridElement.findById(id);
                if (elem == null) continue;
                BigDecimal triggerPrice = elem.getGridPrice();
                int finalId = id;
                executor.placeTakeProfit(triggerPrice.toString(), "sell", "long", config.getQuantity(),
                        profitId -> {
                            elem.setLongStopLossOrderId(profitId);
                            OkxGridElement.refreshIndices();
                            log.info("[OKX] 多仓止损已挂, gridId:{}, 触发价:{}, qty:{}, stopLossId:{}",
                                    finalId, triggerPrice, config.getQuantity(), profitId);
                        });
            }
            // 挂空仓止损 (id=2 到 11),每格 quantity 张
            for (int id = 2; id <= 11; id++) {
                OkxGridElement elem = OkxGridElement.findById(id);
                if (elem == null) continue;
                BigDecimal triggerPrice = elem.getGridPrice();
                int finalId = id;
                executor.placeTakeProfit(triggerPrice.toString(), "buy", "short", config.getQuantity(),
                        profitId -> {
                            elem.setShortStopLossOrderId(profitId);
                            OkxGridElement.refreshIndices();
                            log.info("[OKX] 空仓止损已挂, gridId:{}, 触发价:{}, qty:{}, stopLossId:{}",
                                    finalId, triggerPrice, config.getQuantity(), profitId);
                        });
            }
            log.info("[OKX] 止损单已全部挂完, 空仓止损: 2~11, 多仓止损: -2~-11");
            // 委托 StopLossManager 挂止损单
            stopLossManager.setupBaseStopLosses();
            state = StrategyState.ACTIVE;
        }
    }
    private void generateShortQueue() {
        shortPriceQueue.clear();
        int prec = config.getPriceScale();
        BigDecimal step = shortBaseEntryPrice.multiply(config.getGridRate()).setScale(prec, RoundingMode.HALF_UP);
        config.setStep(step);
        BigDecimal elem = shortBaseEntryPrice.subtract(step).setScale(prec, RoundingMode.HALF_UP);
        for (int i = 0; i < config.getGridQueueSize(); i++) {
            shortPriceQueue.add(elem);
            elem = elem.subtract(step).setScale(prec, RoundingMode.HALF_UP);
            if (elem.compareTo(BigDecimal.ZERO) <= 0) break;
        }
        shortPriceQueue.sort((a, b) -> b.compareTo(a));
        log.info("[OKX] 空队列:{}", shortPriceQueue);
    }
    private void generateLongQueue() {
        longPriceQueue.clear();
        int prec = config.getPriceScale();
        BigDecimal step = config.getStep();
        BigDecimal elem = shortBaseEntryPrice.add(step).setScale(prec, RoundingMode.HALF_UP);
        for (int i = 0; i < config.getGridQueueSize(); i++) {
            longPriceQueue.add(elem);
            elem = elem.add(step).setScale(prec, RoundingMode.HALF_UP);
        }
        longPriceQueue.sort(BigDecimal::compareTo);
        log.info("[OKX] 多队列:{}", longPriceQueue);
    }
    private void updateGridElements() {
        List<OkxGridElement> elements = new ArrayList<>();
        int shortSize = shortPriceQueue.size();
        int longSize = longPriceQueue.size();
        int prec = config.getPriceScale();
        BigDecimal step = config.getStep();
        String qty = config.getQuantity();
        // 空仓队列: id=-1, -2, ...
        for (int i = 0; i < shortSize; i++) {
            int id = -(i + 1);
            Integer upId = (i == 0) ? 0 : id + 1;
            Integer downId = (i == shortSize - 1) ? null : id - 1;
            BigDecimal price = shortPriceQueue.get(i);
            OkxTraderParam longParam = OkxTraderParam.builder()
                    .direction(OkxTraderParam.Direction.LONG)
                    .entryPrice(price).takeProfitPrice(price.add(step).setScale(prec, RoundingMode.HALF_UP)).quantity(qty).build();
            OkxTraderParam shortParam = OkxTraderParam.builder()
                    .direction(OkxTraderParam.Direction.SHORT)
                    .entryPrice(price).takeProfitPrice(price.subtract(step).setScale(prec, RoundingMode.HALF_UP)).quantity(qty).build();
            elements.add(OkxGridElement.builder().id(id).gridPrice(price).upId(upId).downId(downId)
                    .longTraderParam(longParam).shortTraderParam(shortParam).build());
        }
        // 位置 0: 基底价格
        {
            BigDecimal price = shortBaseEntryPrice;
            OkxTraderParam longParam = OkxTraderParam.builder()
                    .direction(OkxTraderParam.Direction.LONG)
                    .entryPrice(price).takeProfitPrice(price.add(step).setScale(prec, RoundingMode.HALF_UP)).quantity(qty).build();
            OkxTraderParam shortParam = OkxTraderParam.builder()
                    .direction(OkxTraderParam.Direction.SHORT)
                    .entryPrice(price).takeProfitPrice(price.subtract(step).setScale(prec, RoundingMode.HALF_UP)).quantity(qty).build();
            elements.add(OkxGridElement.builder().id(0).gridPrice(price)
                    .upId(shortSize > 0 ? 1 : null).downId(longSize > 0 ? -1 : null)
                    .longTraderParam(longParam).shortTraderParam(shortParam).build());
        }
        // 多仓队列: id=1, 2, ...
        for (int i = 0; i < longSize; i++) {
            int id = i + 1;
            Integer downId = (i == 0) ? 0 : id - 1;
            Integer upId = (i == longSize - 1) ? null : id + 1;
            BigDecimal price = longPriceQueue.get(i);
            OkxTraderParam longParam = OkxTraderParam.builder()
                    .direction(OkxTraderParam.Direction.LONG)
                    .entryPrice(price).takeProfitPrice(price.add(step).setScale(prec, RoundingMode.HALF_UP)).quantity(qty).build();
            OkxTraderParam shortParam = OkxTraderParam.builder()
                    .direction(OkxTraderParam.Direction.SHORT)
                    .entryPrice(price).takeProfitPrice(price.subtract(step).setScale(prec, RoundingMode.HALF_UP)).quantity(qty).build();
            elements.add(OkxGridElement.builder().id(id).gridPrice(price).upId(upId).downId(downId)
                    .longTraderParam(longParam).shortTraderParam(shortParam).build());
        }
        config.setGridElements(elements);
        log.info("[OKX] 网格元素列表已构建, 共{}个元素", elements.size());
    }
    // ---- 止损触发处理 ----
    /**
     * 多仓止损触发处理(Gate 模式逐步缩进)。
     * 止损触发后向基底方向缩进 1 格挂条件多单,数量 = |触发价 - 当前持仓均价| / 网格步长,取整。
     * 若 N>2,先取消上一步的旧挂单。
     */
    private void handleLongStopLossTriggered(OkxGridElement gridElement) {
        int gridId = gridElement.getId();
        int N = Math.abs(gridId);
        gridElement.setLongStopLossOrderId(null);
        log.info("[OKX] 多仓止损触发 gridId:{}, 逐步缩进", gridId);
        int newEntryGridId = -(N - 1);
        OkxGridElement newEntryGrid = OkxGridElement.findById(newEntryGridId);
        if (newEntryGrid == null) {
            OkxGridElement.refreshIndices();
            log.warn("[OKX] 多仓止损触发 gridId:{} 找不到入单网格({})", gridId, newEntryGridId);
            return;
        }
        if (N > 2) {
            int cancelGridId = -(N - 2);
            OkxGridElement cancelGrid = OkxGridElement.findById(cancelGridId);
            if (cancelGrid != null && cancelGrid.isHasLongOrder()) {
                executor.cancelAlgoOrder(cancelGrid.getLongOrderId(), oid -> {
                    longEntryTraderIdParam(cancelGrid, null, false);
                    log.info("[OKX] 多仓止损触发, 取消gridId:{}的多单", cancelGridId);
                });
            }
        }
        BigDecimal triggerPrice = newEntryGrid.getGridPrice();
        BigDecimal priceDiff = longEntryPrice.subtract(triggerPrice).abs();
        int count = priceDiff.divide(config.getStep(), 0, RoundingMode.DOWN).intValue();
        count = Math.max(1, count);
        int entryQty = count * Integer.parseInt(config.getQuantity());
        String size = String.valueOf(entryQty);
        log.info("[OKX] 多仓止损触发 gridId:{}, 在gridId:{}挂{}张多单(价差:{},步长:{},count:{},qty:{})",
                gridId, newEntryGridId, entryQty, priceDiff, config.getStep(), count, config.getQuantity());
        newEntryGrid.getLongTraderParam().setQuantity(size);
        placeEntryOrderWithPreFlag(newEntryGrid, true, triggerPrice, size);
    }
    /**
     * 空仓止损触发处理(Gate 模式逐步缩进)。
     * 止损触发后向基底方向缩进 1 格挂条件空单,数量 = |触发价 - 当前持仓均价| / 网格步长,取整。
     * 若 N>2,先取消上一步的旧挂单。
     */
    private void handleShortStopLossTriggered(OkxGridElement gridElement) {
        int gridId = gridElement.getId();
        int N = gridId;
        gridElement.setShortStopLossOrderId(null);
        log.info("[OKX] 空仓止损触发 gridId:{}, 逐步缩进", gridId);
        int newEntryGridId = N - 1;
        OkxGridElement newEntryGrid = OkxGridElement.findById(newEntryGridId);
        if (newEntryGrid == null) {
            OkxGridElement.refreshIndices();
            log.warn("[OKX] 空仓止损触发 gridId:{} 找不到入单网格({})", gridId, newEntryGridId);
            return;
        }
        if (N > 2) {
            int cancelGridId = N - 2;
            OkxGridElement cancelGrid = OkxGridElement.findById(cancelGridId);
            if (cancelGrid != null && cancelGrid.isHasShortOrder()) {
                executor.cancelAlgoOrder(cancelGrid.getShortOrderId(), oid -> {
                    shortEntryTraderIdParam(cancelGrid, null, false);
                    log.info("[OKX] 空仓止损触发, 取消gridId:{}的空单", cancelGridId);
                });
            }
        }
        BigDecimal triggerPrice = newEntryGrid.getGridPrice();
        BigDecimal priceDiff = shortEntryPrice.subtract(triggerPrice).abs();
        int count = priceDiff.divide(config.getStep(), 0, RoundingMode.DOWN).intValue();
        count = Math.max(1, count);
        int entryQty = count * Integer.parseInt(config.getQuantity());
        String size = String.valueOf(entryQty);
        log.info("[OKX] 空仓止损触发 gridId:{}, 在gridId:{}挂{}张空单(价差:{},步长:{},count:{},qty:{})",
                gridId, newEntryGridId, entryQty, priceDiff, config.getStep(), count, config.getQuantity());
        newEntryGrid.getShortTraderParam().setQuantity(size);
        placeEntryOrderWithPreFlag(newEntryGrid, false, triggerPrice, size);
    }
    private void extendLongStopLoss(int filledQty) {
        // filledQty 为本次新增止损张数 = count * quantity, 需要按 quantity 为粒度拆分为 count 个止损单
        int qty = Integer.parseInt(config.getQuantity());
        int stopLossCount = filledQty / qty;
        int furthestSlId = 0;
        for (OkxGridElement e : config.getGridElements()) {
            if (e.getLongStopLossOrderId() != null && e.getId() < furthestSlId) {
                furthestSlId = e.getId();
            }
        }
        if (furthestSlId == 0) furthestSlId = -11;
        log.info("[OKX] 多仓追挂止损, 当前最远止损gridId:{}, 追加{}单, 每单{}张", furthestSlId, stopLossCount, qty);
        for (int i = 0; i < stopLossCount; i++) {
            int newSlId = furthestSlId - i - 1;
            OkxGridElement elem = OkxGridElement.findById(newSlId);
            if (elem == null) continue;
            BigDecimal triggerPrice = elem.getGridPrice();
            int finalSlId = newSlId;
            executor.placeTakeProfit(triggerPrice.toString(), "sell", "long", config.getQuantity(),
                    profitId -> {
                        elem.setLongStopLossOrderId(profitId);
                        OkxGridElement.refreshIndices();
                        log.info("[OKX] 多仓止损追加, gridId:{}, 触发价:{}, stopLossId:{}", finalSlId, triggerPrice, profitId);
                    });
        }
    }
    private void extendShortStopLoss(int filledQty) {
        int qty = Integer.parseInt(config.getQuantity());
        int stopLossCount = filledQty / qty;
        int furthestSlId = 0;
        for (OkxGridElement e : config.getGridElements()) {
            if (e.getShortStopLossOrderId() != null && e.getId() > furthestSlId) {
                furthestSlId = e.getId();
            }
        }
        if (furthestSlId == 0) furthestSlId = 11;
        log.info("[OKX] 空仓追挂止损, 当前最远止损gridId:{}, 追加{}单, 每单{}张", furthestSlId, stopLossCount, qty);
        for (int i = 0; i < stopLossCount; i++) {
            int newSlId = furthestSlId + i + 1;
            OkxGridElement elem = OkxGridElement.findById(newSlId);
            if (elem == null) continue;
            BigDecimal triggerPrice = elem.getGridPrice();
            int finalSlId = newSlId;
            executor.placeTakeProfit(triggerPrice.toString(), "buy", "short", config.getQuantity(),
                    profitId -> {
                        elem.setShortStopLossOrderId(profitId);
                        OkxGridElement.refreshIndices();
                        log.info("[OKX] 空仓止损追加, gridId:{}, 触发价:{}, stopLossId:{}", finalSlId, triggerPrice, profitId);
                    });
        }
    }
    // ---- 辅助方法 ----
    private void longTakeProfitTraderIdParam(OkxGridElement baseElement, String profitId, boolean flag) {
        OkxTraderParam tp = baseElement.getLongTraderParam();
        tp.setTakeProfitOrderId(profitId);
        tp.setTakeProfitPlaced(flag);
        baseElement.setLongTakeProfitOrderId(profitId);
        OkxGridElement.refreshIndices();
    }
    private void shortTakeProfitTraderIdParam(OkxGridElement baseElement, String profitId, boolean flag) {
        OkxTraderParam tp = baseElement.getShortTraderParam();
        tp.setTakeProfitOrderId(profitId);
        tp.setTakeProfitPlaced(flag);
        baseElement.setShortTakeProfitOrderId(profitId);
        OkxGridElement.refreshIndices();
    }
    private void longEntryTraderIdParam(OkxGridElement baseElement, String entryId, boolean flag) {
        OkxTraderParam tp = baseElement.getLongTraderParam();
        tp.setEntryOrderId(entryId);
        tp.setEntryOrderPlaced(flag);
        baseElement.setHasLongOrder(flag);
        baseElement.setLongOrderId(entryId);
        OkxGridElement.refreshIndices();
    }
    private void shortEntryTraderIdParam(OkxGridElement baseElement, String entryId, boolean flag) {
        OkxTraderParam tp = baseElement.getShortTraderParam();
        tp.setEntryOrderId(entryId);
        tp.setEntryOrderPlaced(flag);
        baseElement.setHasShortOrder(flag);
        baseElement.setShortOrderId(entryId);
        OkxGridElement.refreshIndices();
    }
    private void placeEntryOrderWithPreFlag(OkxGridElement gridElement, boolean isLong,
                                             BigDecimal triggerPrice, String size) {
        if (isLong) {
            gridElement.setHasLongOrder(true);
        } else {
            gridElement.setHasShortOrder(true);
        }
        String side = isLong ? "buy" : "sell";
        String posSide = isLong ? "long" : "short";
        executor.placeConditionalEntryOrder(triggerPrice.toString(), side, posSide, size,
                orderId -> {
                    if (isLong) {
                        longEntryTraderIdParam(gridElement, orderId, true);
                    } else {
                        shortEntryTraderIdParam(gridElement, orderId, true);
                    }
                },
                () -> {
                    if (isLong) {
                        gridElement.setHasLongOrder(false);
                        gridElement.setLongOrderId(null);
                    } else {
                        gridElement.setHasShortOrder(false);
                        gridElement.setShortOrderId(null);
                    }
                    OkxGridElement.refreshIndices();
                    log.warn("[OKX] 条件单创建失败,回滚标志位 gridId:{}, isLong:{}", gridElement.getId(), isLong);
                }
        );
    }
    // ---- 盈亏计算 ----
    private void updateUnrealizedPnl() {
        if (lastKlinePrice == null || lastKlinePrice.compareTo(BigDecimal.ZERO) == 0) return;
@@ -794,7 +554,11 @@
                state = StrategyState.STOPPED;
                closeExistingPositions();
                executor.cancelAllAlgoOrders();
                startGrid();
                // 提交到 executor 末尾:单线程FIFO保证前面所有平仓/取消任务完成后才重置
                executor.submitTask(() -> {
                    try { Thread.sleep(3000); } catch (InterruptedException e) { Thread.currentThread().interrupt(); }
                    startGrid();
                });
            }
        } catch (Exception e) {
            log.warn("[OKX] 盈亏检查失败", e);
@@ -802,10 +566,15 @@
    }
    private void handlePositionZeroAndReset(String direction) {
        log.info("[OKX] {}持仓归零,重置策略", direction);
        state = StrategyState.STOPPED;
        executor.cancelAllAlgoOrders();
        closeExistingPositions();
        startGrid();
        // 提交到 executor 末尾:FIFO保证平仓完成后再重置
        executor.submitTask(() -> {
            try { Thread.sleep(3000); } catch (InterruptedException e) { Thread.currentThread().interrupt(); }
            startGrid();
        });
    }
    // ---- getters ----
src/main/java/com/xcong/excoin/modules/okxNewPrice/OkxGridWsClient.java
@@ -229,6 +229,21 @@
            if ("subscribe".equals(event) || "unsubscribe".equals(event)) {
                log.info("[{}] {}事件: {}", logPrefix, event, response.getString("arg"));
                // 订阅成功确认:解析频道名并通知对应 handler
                if ("subscribe".equals(event)) {
                    JSONObject argObj = response.getJSONObject("arg");
                    if (argObj != null) {
                        String channel = argObj.getString("channel");
                        if (channel != null) {
                            for (OkxGridChannelHandler handler : channelHandlers) {
                                if (channel.equals(handler.getChannelName())) {
                                    handler.onSubscribed();
                                    break;
                                }
                            }
                        }
                    }
                }
                return;
            }
src/main/java/com/xcong/excoin/modules/okxNewPrice/OkxTradeExecutor.java
@@ -79,6 +79,14 @@
    }
    /**
     * 提交一个通用任务到交易线程池末尾。
     * 利用单线程池的 FIFO 特性确保任务按提交顺序执行。
     */
    public void submitTask(Runnable task) {
        executor.execute(task);
    }
    /**
     * 异步 IOC 市价开多。
     *
     * @param quantity  开仓张数(正数)
src/main/java/com/xcong/excoin/modules/okxNewPrice/OkxWebSocketClientManager.java
@@ -1,6 +1,5 @@
package com.xcong.excoin.modules.okxNewPrice;
import com.xcong.excoin.modules.okxNewPrice.gridWs.OkxAlgoOrdersChannelHandler;
import com.xcong.excoin.modules.okxNewPrice.gridWs.OkxKlineChannelHandler;
import com.xcong.excoin.modules.okxNewPrice.gridWs.OkxOrdersChannelHandler;
import com.xcong.excoin.modules.okxNewPrice.gridWs.OkxPositionsChannelHandler;
@@ -86,14 +85,14 @@
                    .apiKey(primaryAccount.getApiKey())
                    .secretKey(primaryAccount.getSecretKey())
                    .passphrase(primaryAccount.getPassphrase())
                    .instId("BTC-USDT-SWAP")
                    .instId("ETH-USDT-SWAP")
                    .leverage("100")
                    .tdMode("cross")
                    .gridRate(new BigDecimal("0.001"))
                    .expectedProfit(new BigDecimal("20"))
                    .gridRate(new BigDecimal("0.002"))
                    .expectedProfit(new BigDecimal("180"))
                    .maxLoss(new BigDecimal("30"))
                    .quantity("1")
                    .baseQuantity("10")
                    .quantity("5")
                    .baseQuantity("50")
                    .priceScale(2)
                    .ctVal(new BigDecimal("0.01"))
                    .isSimulate(!primaryAccount.isAccountType())
src/main/java/com/xcong/excoin/modules/okxNewPrice/StopLossManager.java
New file
@@ -0,0 +1,311 @@
package com.xcong.excoin.modules.okxNewPrice;
import lombok.extern.slf4j.Slf4j;
import java.math.BigDecimal;
/**
 * 止损管理器 — 负责基底止损单挂载、止损触发后的逐步缩进、以及止损追挂。
 *
 * <h3>止损策略(对齐 Gate 版本)</h3>
 * <ol>
 *   <li>基底开仓后挂多仓止损(id=-2~-11)和空仓止损(id=2~11),每格 1 倍 quantity</li>
 *   <li>条件单成交后以成交量为粒度追挂止损(extend 系列)</li>
 *   <li>止损触发后向基底方向缩进 1 格,数量由价格差 / 步长决定,N&gt;2 时先取消旧挂单</li>
 * </ol>
 *
 * <h3>线程安全</h3>
 * 本类通过 executor 的异步回调串行化下单操作;对 OkxGridElement 的读写依赖调用方保证有序。
 *
 * @author Administrator
 */
@Slf4j
public class StopLossManager {
    private final OkxConfig config;
    private final OkxTradeExecutor executor;
    /** 多仓挂单张数计数器:止损触发时递增后再使用;挂单成交后重置为1 */
    private volatile int longEntryQty = 1;
    /** 空仓挂单张数计数器:止损触发时递增后再使用;挂单成交后重置为1 */
    private volatile int shortEntryQty = 1;
    public StopLossManager(OkxConfig config, OkxTradeExecutor executor) {
        this.config = config;
        this.executor = executor;
    }
    // ========== 基底止损挂载 ==========
    /**
     * 在基底开仓完成后挂载初始止损单。
     * 多仓止损: id=-2 到 -11,每格 quantity 张,方向 sell/long
     * 空仓止损: id=2 到 11,每格 quantity 张,方向 buy/short
     */
    public void setupBaseStopLosses() {
        // 挂多仓止损 (id=-2 到 -11)
        for (int id = -2; id >= -11; id--) {
            OkxGridElement elem = OkxGridElement.findById(id);
            if (elem == null) continue;
            BigDecimal triggerPrice = elem.getGridPrice();
            int finalId = id;
            executor.placeTakeProfit(triggerPrice.toString(), "sell", "long", config.getQuantity(),
                    profitId -> {
                        elem.setLongStopLossOrderId(profitId);
                        OkxGridElement.refreshIndices();
                        log.info("[OKX] 多仓止损已挂, gridId:{}, 触发价:{}, qty:{}, stopLossId:{}",
                                finalId, triggerPrice, config.getQuantity(), profitId);
                    });
        }
        // 挂空仓止损 (id=2 到 11)
        for (int id = 2; id <= 11; id++) {
            OkxGridElement elem = OkxGridElement.findById(id);
            if (elem == null) continue;
            BigDecimal triggerPrice = elem.getGridPrice();
            int finalId = id;
            executor.placeTakeProfit(triggerPrice.toString(), "buy", "short", config.getQuantity(),
                    profitId -> {
                        elem.setShortStopLossOrderId(profitId);
                        OkxGridElement.refreshIndices();
                        log.info("[OKX] 空仓止损已挂, gridId:{}, 触发价:{}, qty:{}, stopLossId:{}",
                                finalId, triggerPrice, config.getQuantity(), profitId);
                    });
        }
        log.info("[OKX] 止损单已全部挂完, 空仓止损: 2~11, 多仓止损: -2~-11");
    }
    // ========== 止损触发处理 ==========
    /**
     * 多仓止损触发处理。
     * 止损触发后向基底方向缩进 1 格挂条件多单,数量 = |触发价 - 当前持仓均价| / 网格步长,取整。
     * 若 N &gt; 2,先取消上一步的旧挂单。
     */
    public void handleLongStopLossTriggered(OkxGridElement gridElement, BigDecimal longEntryPrice) {
        int gridId = gridElement.getId();
        int N = Math.abs(gridId);
        gridElement.setLongStopLossOrderId(null);
        log.info("[OKX] 多仓止损触发 gridId:{}, 逐步缩进", gridId);
        int newEntryGridId = -(N - 1);
        OkxGridElement newEntryGrid = OkxGridElement.findById(newEntryGridId);
        if (newEntryGrid == null) {
            OkxGridElement.refreshIndices();
            log.warn("[OKX] 多仓止损触发 gridId:{} 找不到入单网格({})", gridId, newEntryGridId);
            return;
        }
        if (N > 2) {
            int cancelGridId = -(N - 2);
            OkxGridElement cancelGrid = OkxGridElement.findById(cancelGridId);
            if (cancelGrid != null && cancelGrid.isHasLongOrder()) {
                executor.cancelAlgoOrder(cancelGrid.getLongOrderId(), oid -> {
                    clearLongEntryState(cancelGrid);
                    log.info("[OKX] 多仓止损触发, 取消gridId:{}的多单", cancelGridId);
                });
            }
        }
        BigDecimal triggerPrice = newEntryGrid.getGridPrice();
        longEntryQty++;
        int entryQty = longEntryQty * Integer.parseInt(config.getQuantity());
        String size = String.valueOf(entryQty);
        log.info("[OKX] 多仓止损触发 gridId:{}, 在gridId:{}挂{}张多单(计数器:{}, qty:{})",
                gridId, newEntryGridId, entryQty, longEntryQty, config.getQuantity());
        newEntryGrid.getLongTraderParam().setQuantity(size);
        placeEntryOrderWithPreFlag(newEntryGrid, true, triggerPrice, size);
    }
    /**
     * 空仓止损触发处理。
     * 止损触发后向基底方向缩进 1 格挂条件空单,数量 = |触发价 - 当前持仓均价| / 网格步长,取整。
     * 若 N &gt; 2,先取消上一步的旧挂单。
     */
    public void handleShortStopLossTriggered(OkxGridElement gridElement, BigDecimal shortEntryPrice) {
        int gridId = gridElement.getId();
        int N = gridId;
        gridElement.setShortStopLossOrderId(null);
        log.info("[OKX] 空仓止损触发 gridId:{}, 逐步缩进", gridId);
        int newEntryGridId = N - 1;
        OkxGridElement newEntryGrid = OkxGridElement.findById(newEntryGridId);
        if (newEntryGrid == null) {
            OkxGridElement.refreshIndices();
            log.warn("[OKX] 空仓止损触发 gridId:{} 找不到入单网格({})", gridId, newEntryGridId);
            return;
        }
        if (N > 2) {
            int cancelGridId = N - 2;
            OkxGridElement cancelGrid = OkxGridElement.findById(cancelGridId);
            if (cancelGrid != null && cancelGrid.isHasShortOrder()) {
                executor.cancelAlgoOrder(cancelGrid.getShortOrderId(), oid -> {
                    clearShortEntryState(cancelGrid);
                    log.info("[OKX] 空仓止损触发, 取消gridId:{}的空单", cancelGridId);
                });
            }
        }
        BigDecimal triggerPrice = newEntryGrid.getGridPrice();
        shortEntryQty++;
        int entryQty = shortEntryQty * Integer.parseInt(config.getQuantity());
        String size = String.valueOf(entryQty);
        log.info("[OKX] 空仓止损触发 gridId:{}, 在gridId:{}挂{}张空单(计数器:{}, qty:{})",
                gridId, newEntryGridId, entryQty, shortEntryQty, config.getQuantity());
        newEntryGrid.getShortTraderParam().setQuantity(size);
        placeEntryOrderWithPreFlag(newEntryGrid, false, triggerPrice, size);
    }
    // ========== 止损追挂 ==========
    /**
     * 多仓追挂止损:按 quantity 粒度拆分为 count 个止损单,从当前最远止损格再往外延伸。
     */
    public void extendLongStopLoss(int filledQty) {
        int qty = Integer.parseInt(config.getQuantity());
        int stopLossCount = filledQty / qty;
        int furthestSlId = 0;
        for (OkxGridElement e : config.getGridElements()) {
            if (e.getLongStopLossOrderId() != null && e.getId() < furthestSlId) {
                furthestSlId = e.getId();
            }
        }
        if (furthestSlId == 0) furthestSlId = -11;
        log.info("[OKX] 多仓追挂止损, 当前最远止损gridId:{}, 追加{}单, 每单{}张", furthestSlId, stopLossCount, qty);
        for (int i = 0; i < stopLossCount; i++) {
            int newSlId = furthestSlId - i - 1;
            OkxGridElement elem = OkxGridElement.findById(newSlId);
            if (elem == null) continue;
            BigDecimal triggerPrice = elem.getGridPrice();
            int finalSlId = newSlId;
            executor.placeTakeProfit(triggerPrice.toString(), "sell", "long", config.getQuantity(),
                    profitId -> {
                        elem.setLongStopLossOrderId(profitId);
                        OkxGridElement.refreshIndices();
                        log.info("[OKX] 多仓止损追加, gridId:{}, 触发价:{}, stopLossId:{}", finalSlId, triggerPrice, profitId);
                    });
        }
    }
    /**
     * 空仓追挂止损:按 quantity 粒度拆分为 count 个止损单,从当前最远止损格再往外延伸。
     */
    public void extendShortStopLoss(int filledQty) {
        int qty = Integer.parseInt(config.getQuantity());
        int stopLossCount = filledQty / qty;
        int furthestSlId = 0;
        for (OkxGridElement e : config.getGridElements()) {
            if (e.getShortStopLossOrderId() != null && e.getId() > furthestSlId) {
                furthestSlId = e.getId();
            }
        }
        if (furthestSlId == 0) furthestSlId = 11;
        log.info("[OKX] 空仓追挂止损, 当前最远止损gridId:{}, 追加{}单, 每单{}张", furthestSlId, stopLossCount, qty);
        for (int i = 0; i < stopLossCount; i++) {
            int newSlId = furthestSlId + i + 1;
            OkxGridElement elem = OkxGridElement.findById(newSlId);
            if (elem == null) continue;
            BigDecimal triggerPrice = elem.getGridPrice();
            int finalSlId = newSlId;
            executor.placeTakeProfit(triggerPrice.toString(), "buy", "short", config.getQuantity(),
                    profitId -> {
                        elem.setShortStopLossOrderId(profitId);
                        OkxGridElement.refreshIndices();
                        log.info("[OKX] 空仓止损追加, gridId:{}, 触发价:{}, stopLossId:{}", finalSlId, triggerPrice, profitId);
                    });
        }
    }
    // ========== 辅助:条件单创建 & 状态回写 ==========
    /**
     * 预置标志位后发送条件入单请求,成功/失败均通过回调写入 GridElement 状态。
     */
    void placeEntryOrderWithPreFlag(OkxGridElement gridElement, boolean isLong,
                                     BigDecimal triggerPrice, String size) {
        if (isLong) {
            gridElement.setHasLongOrder(true);
        } else {
            gridElement.setHasShortOrder(true);
        }
        String side = isLong ? "buy" : "sell";
        String posSide = isLong ? "long" : "short";
        executor.placeConditionalEntryOrder(triggerPrice.toString(), side, posSide, size,
                orderId -> {
                    if (isLong) {
                        setLongEntryState(gridElement, orderId, true);
                    } else {
                        setShortEntryState(gridElement, orderId, true);
                    }
                },
                () -> {
                    if (isLong) {
                        gridElement.setHasLongOrder(false);
                        gridElement.setLongOrderId(null);
                    } else {
                        gridElement.setHasShortOrder(false);
                        gridElement.setShortOrderId(null);
                    }
                    OkxGridElement.refreshIndices();
                    log.warn("[OKX] 条件单创建失败,回滚标志位 gridId:{}, isLong:{}", gridElement.getId(), isLong);
                }
        );
    }
    // ---- GridElement 状态修改(package-private,OkxGridTradeService 也可调用) ----
    void setLongEntryState(OkxGridElement gridElement, String entryId, boolean flag) {
        OkxTraderParam tp = gridElement.getLongTraderParam();
        tp.setEntryOrderId(entryId);
        tp.setEntryOrderPlaced(flag);
        gridElement.setHasLongOrder(flag);
        gridElement.setLongOrderId(entryId);
        OkxGridElement.refreshIndices();
    }
    void setShortEntryState(OkxGridElement gridElement, String entryId, boolean flag) {
        OkxTraderParam tp = gridElement.getShortTraderParam();
        tp.setEntryOrderId(entryId);
        tp.setEntryOrderPlaced(flag);
        gridElement.setHasShortOrder(flag);
        gridElement.setShortOrderId(entryId);
        OkxGridElement.refreshIndices();
    }
    void clearLongEntryState(OkxGridElement gridElement) {
        setLongEntryState(gridElement, null, false);
    }
    void clearShortEntryState(OkxGridElement gridElement) {
        setShortEntryState(gridElement, null, false);
    }
    void setLongTakeProfitState(OkxGridElement gridElement, String profitId, boolean flag) {
        OkxTraderParam tp = gridElement.getLongTraderParam();
        tp.setTakeProfitOrderId(profitId);
        tp.setTakeProfitPlaced(flag);
        gridElement.setLongTakeProfitOrderId(profitId);
        OkxGridElement.refreshIndices();
    }
    void setShortTakeProfitState(OkxGridElement gridElement, String profitId, boolean flag) {
        OkxTraderParam tp = gridElement.getShortTraderParam();
        tp.setTakeProfitOrderId(profitId);
        tp.setTakeProfitPlaced(flag);
        gridElement.setShortTakeProfitOrderId(profitId);
        OkxGridElement.refreshIndices();
    }
    // ========== 计数器管理 ==========
    /** 重置多仓挂单张数计数器(挂单成交后调用) */
    public void resetLongEntryQty() { longEntryQty = 1; }
    /** 重置空仓挂单张数计数器(挂单成交后调用) */
    public void resetShortEntryQty() { shortEntryQty = 1; }
    /** 重置全部挂单张数计数器(startGrid时调用) */
    public void resetAllEntryQuantities() { longEntryQty = 1; shortEntryQty = 1; }
}
src/main/java/com/xcong/excoin/modules/okxNewPrice/gridWs/OkxAlgoOrdersChannelHandler.java
File was deleted
src/main/java/com/xcong/excoin/modules/okxNewPrice/gridWs/OkxGridChannelHandler.java
@@ -27,4 +27,10 @@
     * @return true 表示已处理(循环停止),false 表示频道不匹配
     */
    boolean handleMessage(JSONObject response);
    /**
     * 订阅成功确认回调,收到 OKX 服务器 subscribe 事件时触发。
     * 子类可重写以通知策略引擎订阅就绪。
     */
    default void onSubscribed() {}
}
src/main/java/com/xcong/excoin/modules/okxNewPrice/gridWs/OkxKlineChannelHandler.java
@@ -46,6 +46,14 @@
    }
    @Override
    public void onSubscribed() {
        log.info("[OKX-WS] {} 订阅确认, instId:{}", CHANNEL_NAME, instId);
        if (gridTradeService != null) {
            gridTradeService.onSubscriptionConfirmed(CHANNEL_NAME);
        }
    }
    @Override
    public void unsubscribe(WebSocketClient ws) {
        JSONObject msg = new JSONObject();
        JSONObject arg = new JSONObject();
src/main/java/com/xcong/excoin/modules/okxNewPrice/gridWs/OkxOrdersChannelHandler.java
@@ -41,6 +41,14 @@
    }
    @Override
    public void onSubscribed() {
        log.info("[OKX-WS] {} 订阅确认", CHANNEL_NAME);
        if (gridTradeService != null) {
            gridTradeService.onSubscriptionConfirmed(CHANNEL_NAME);
        }
    }
    @Override
    public void unsubscribe(WebSocketClient ws) {
        JSONObject msg = new JSONObject();
        JSONObject arg = new JSONObject();
src/main/java/com/xcong/excoin/modules/okxNewPrice/gridWs/OkxPositionsChannelHandler.java
@@ -45,6 +45,14 @@
    }
    @Override
    public void onSubscribed() {
        log.info("[OKX-WS] {} 订阅确认", CHANNEL_NAME);
        if (gridTradeService != null) {
            gridTradeService.onSubscriptionConfirmed(CHANNEL_NAME);
        }
    }
    @Override
    public void unsubscribe(WebSocketClient ws) {
        JSONObject msg = new JSONObject();
        JSONObject arg = new JSONObject();
src/main/java/com/xcong/excoin/modules/okxNewPrice/okxpi/config/utils/RequestBuilder.java
@@ -96,7 +96,6 @@
                            .get()
                            .addHeader("Content-Type", "application/x-www-form-urlencoded")
                            .addHeader("OK-ACCESS-KEY", apiKey)
                            .addHeader("OK-ACCESS-KEY", apiKey)
                            .addHeader("OK-ACCESS-SIGN", sign)
                            .addHeader("OK-ACCESS-TIMESTAMP", timeStamp)
                            .addHeader("OK-ACCESS-PASSPHRASE", passphrase)
@@ -112,7 +111,6 @@
                            .put(RequestBody.create(JSON_TYPE, ""))
                            .addHeader("Content-Type", "application/x-www-form-urlencoded")
                            .addHeader("OK-ACCESS-KEY", apiKey)
                            .addHeader("OK-ACCESS-KEY", apiKey)
                            .addHeader("OK-ACCESS-SIGN", sign)
                            .addHeader("OK-ACCESS-TIMESTAMP", timeStamp)
                            .addHeader("OK-ACCESS-PASSPHRASE", passphrase)
@@ -127,7 +125,6 @@
                            .url(fullUrl)
                            .delete()
                            .addHeader("Content-Type", "application/x-www-form-urlencoded")
                            .addHeader("OK-ACCESS-KEY", apiKey)
                            .addHeader("OK-ACCESS-KEY", apiKey)
                            .addHeader("OK-ACCESS-SIGN", sign)
                            .addHeader("OK-ACCESS-TIMESTAMP", timeStamp)
src/main/java/com/xcong/excoin/utils/MessageSourceUtils.java
File was deleted
src/main/java/com/xcong/excoin/utils/RedisUtils.java
File was deleted
src/main/java/com/xcong/excoin/utils/SSLClient.java
File was deleted
src/main/java/com/xcong/excoin/utils/ShareCodeUtil.java
File was deleted
src/main/java/com/xcong/excoin/utils/SmsUtils.java
File was deleted
src/main/java/com/xcong/excoin/utils/SpringContextHolder.java
File was deleted
src/main/java/com/xcong/excoin/utils/TypeJudgeUtils.java
File was deleted
src/main/java/com/xcong/excoin/utils/api/ApiClient.java
File was deleted
src/main/java/com/xcong/excoin/utils/api/ApiException.java
File was deleted
src/main/java/com/xcong/excoin/utils/api/request/CreateOrderRequest.java
File was deleted
src/main/java/com/xcong/excoin/utils/api/request/DepthRequest.java
File was deleted
src/main/java/com/xcong/excoin/utils/api/request/IntrustOrdersDetailRequest.java
File was deleted
src/main/java/com/xcong/excoin/utils/api/response/Account.java
File was deleted
src/main/java/com/xcong/excoin/utils/api/response/Accounts.java
File was deleted
src/main/java/com/xcong/excoin/utils/api/response/AccountsResponse.java
File was deleted
src/main/java/com/xcong/excoin/utils/api/response/ApiResponse.java
File was deleted
src/main/java/com/xcong/excoin/utils/api/response/Balance.java
File was deleted
src/main/java/com/xcong/excoin/utils/api/response/BalanceBean.java
File was deleted
src/main/java/com/xcong/excoin/utils/api/response/BalanceResponse.java
File was deleted
src/main/java/com/xcong/excoin/utils/api/response/Batchcancel.java
File was deleted
src/main/java/com/xcong/excoin/utils/api/response/BatchcancelBean.java
File was deleted
src/main/java/com/xcong/excoin/utils/api/response/BatchcancelResponse.java
File was deleted
src/main/java/com/xcong/excoin/utils/api/response/Currencys.java
File was deleted
src/main/java/com/xcong/excoin/utils/api/response/CurrencysResponse.java
File was deleted
src/main/java/com/xcong/excoin/utils/api/response/Depth.java
File was deleted
src/main/java/com/xcong/excoin/utils/api/response/DepthResponse.java
File was deleted
src/main/java/com/xcong/excoin/utils/api/response/DetailResponse.java
File was deleted
src/main/java/com/xcong/excoin/utils/api/response/Details.java
File was deleted
src/main/java/com/xcong/excoin/utils/api/response/HistoryTrade.java
File was deleted
src/main/java/com/xcong/excoin/utils/api/response/HistoryTradeResponse.java
File was deleted
src/main/java/com/xcong/excoin/utils/api/response/HistoryTradess.java
File was deleted
src/main/java/com/xcong/excoin/utils/api/response/IntrustDetail.java
File was deleted
src/main/java/com/xcong/excoin/utils/api/response/IntrustDetailResponse.java
File was deleted
src/main/java/com/xcong/excoin/utils/api/response/Kline.java
File was deleted
src/main/java/com/xcong/excoin/utils/api/response/KlineResponse.java
File was deleted
src/main/java/com/xcong/excoin/utils/api/response/KlineReturn.java
File was deleted
src/main/java/com/xcong/excoin/utils/api/response/MatchresultsOrdersDetail.java
File was deleted
src/main/java/com/xcong/excoin/utils/api/response/MatchresultsOrdersDetailResponse.java
File was deleted
src/main/java/com/xcong/excoin/utils/api/response/Merged.java
File was deleted
src/main/java/com/xcong/excoin/utils/api/response/MergedResponse.java
File was deleted
src/main/java/com/xcong/excoin/utils/api/response/OrdersDetail.java
File was deleted
src/main/java/com/xcong/excoin/utils/api/response/OrdersDetailResponse.java
File was deleted
src/main/java/com/xcong/excoin/utils/api/response/Place.java
File was deleted
src/main/java/com/xcong/excoin/utils/api/response/SubmitcancelResponse.java
File was deleted
src/main/java/com/xcong/excoin/utils/api/response/Symbol.java
File was deleted
src/main/java/com/xcong/excoin/utils/api/response/Symbols.java
File was deleted
src/main/java/com/xcong/excoin/utils/api/response/SymbolsResponse.java
File was deleted
src/main/java/com/xcong/excoin/utils/api/response/TimestampResponse.java
File was deleted
src/main/java/com/xcong/excoin/utils/api/response/Trade.java
File was deleted
src/main/java/com/xcong/excoin/utils/api/response/TradeBean.java
File was deleted
src/main/java/com/xcong/excoin/utils/api/response/TradeResponse.java
File was deleted
src/main/java/com/xcong/excoin/utils/mail/RequestEncoder.java
File was deleted
src/main/java/com/xcong/excoin/utils/mail/Sms106Send.java
File was deleted
src/main/java/com/xcong/excoin/utils/mail/SmsSend.java
File was deleted
src/main/java/com/xcong/excoin/utils/mail/SubMailSend.java
File was deleted
src/main/resources/application-app.yml
File was deleted
src/main/resources/application-dayline.yml
File was deleted
src/main/resources/application-loop.yml
File was deleted
src/main/resources/application-newprice.yml
File was deleted
src/main/resources/application-okx.yml
@@ -1,2 +1,4 @@
mybatis-plus:
  mapper-locations: classpath*:mapper/blackchain/*.xml, classpath*:mapper/ding/*.xml, classpath*:mapper/price/*.xml, classpath*:mapper/combom/*.xml, classpath*:mapper/record/*.xml, classpath*:mapper/uinfo/*.xml, classpath*:mapper/push/*.xml, classpath*:mapper/coin/*.xml, classpath*:mapper/user/*.xml, classpath*:mapper/demo/*.xml
# OKX 网格策略专用配置
logging:
  level:
    com.xcong.excoin.modules.okxNewPrice: INFO
src/main/resources/application-test.yml
File was deleted
src/main/resources/application.yml
@@ -1,75 +1,17 @@
server:
  port: 8888
  servlet:
    context-path: /
spring:
  OKEX:
    baseurl: https://www.okex.com
  profiles:
    active: test
  datasource:
    url: jdbc:mysql://120.27.238.55:3406/db_base?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=GMT%2b8
    username: ct_test
    password: 123456
    driver-class-name: com.mysql.jdbc.Driver
    type: com.alibaba.druid.pool.DruidDataSource
    druid:
      initial-size: ${spring_datasource_druid_initial_size:10}
      max-active: ${spring_datasource_druid_max_active:20}
      min-idle: ${spring_datasource_druid_min_idle:3}
      #配置获取连接等待超时的时间
      max-wait: 60000
      pool-prepared-statements: true
      max-pool-prepared-statement-per-connection-size: 20
      validation-query: SELECT 'x'
      test-on-borrow: true
      test-on-return: true
      test-while-idle: true
      #配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
      time-between-eviction-runs-millis: 60000
      #配置一个连接在池中最小生存的时间,单位是毫秒
      min-evictable-idle-time-millis: 300000
      #spring.datasource.druid.max-evguide.ftlictable-idle-time-millis=
      filters: stat,wall
      stat-view-servlet:
        # 默认true 内置监控页面首页/druid/index.html
        enabled: true
        url-pattern: /druid/*
        # 允许清空统计数据
        reset-enable: true
        login-username: root
        login-password: 123456
        # IP白名单 多个逗号分隔
        allow: ${spring_datasource_stat_view_servlet_allow:}
        # IP黑名单
        deny: ${spring_datasource_stat_view_servlet_deny:}
  ## 国际化配置
  messages:
    basename: i18n/messages
    active: okx
  autoconfigure:
    exclude:
      # 移除不需要的自动配置
      - org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration
      - org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration
      - org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration
      - org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration
app:
  debug: false
  redis_expire: 3000
  # k线更新任务控制
  kline-update-job: false
  #最新价任务控制
  newest-price-update-job: false
  #日线 该任务不能与最新价处于同一个服务器
  day-line: false
  #其他任务控制
  other-job: false
  loop-job: false
  rabbit-consumer: false
  block-job: false
aliyun:
  oss:
    end-point: https://oss-cn-hangzhou.aliyuncs.com
    bucket-name: https://excoin.oss-cn-hangzhou.aliyuncs.com
    access-key-id: LTAI4GBuydqbJ5bTsDP97Lpd
    access-key-secret: vbCjQtPxABWjqtUlQfzjlA0qAY96fh
rsa:
  public_key: MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCCf8UFZK54AiK4PRu7tNd+Z8qZ77o/QXCnk25DRmygVpOEu5mGNSAvfnWmKp2pEV2RljeXq3Rid/+LQkonaebMJeXKSF0yxL/VgyeT8JaQ5gNbOrdfdlc+mFkXJyzyJt8YkvApEdPRNSU2ENBn7mgRfD0BYPM4vZ6/rv+de38FJwIDAQAB
  private_key: MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAIJ/xQVkrngCIrg9G7u0135nypnvuj9BcKeTbkNGbKBWk4S7mYY1IC9+daYqnakRXZGWN5erdGJ3/4tCSidp5swl5cpIXTLEv9WDJ5PwlpDmA1s6t192Vz6YWRcnLPIm3xiS8CkR09E1JTYQ0GfuaBF8PQFg8zi9nr+u/517fwUnAgMBAAECgYBhPt9NvpI4wbanvnndLczr2GJkxfzvSE+vwLCJF4C5FusFHVsxZINggQcg1V75bwRgCiXRMyYefreCSdrCditS43PzTOmE4RRrpxLlm8oubJc0C98LQ2qlN9AsUqL5IHpVGgbHDyWAwjc1GBID6nwXKpxq1/VodFqhahG9D5EZsQJBALnkb+5VTxQbiyQI4Uc9NIvAyVcNY1OisbvY6tvNgdBbJkADgAb78M1HWxxYjUqsvzggNHc7cWqWBHMgpnJaqm8CQQCztze4D7uAk7OC9MJHY5eE980J8Kk+GEZKxz4LahzU6V6dcb9GFac3wEtgilj/tOAn9y0/Q8sm9vvCIbMDzgzJAkEAqRYcqhF26LdVDOX25DHMBgLKISDQZFbsjA13M4/usHL4i+mjHrc0BcUOHu59NpuDI65HitzLAUSLr5zXSdUmiQJAW77wOg4GCejdXsB3IhzMsHwU97sdm26nC+vVV9xvJZ6Rx8zW+f9543NOx9U5BCmhuaVtOvvwDU9PTVcI3atmSQJAXAIJ5gGdtXx0DXiX4VvzNFHqgaqHMGvXyjNVkU2FYQbSAd2A6app4uRO+BkZu9dSjh14m+oXMnV2HzAN2rRnjA==
  # OKX 量化策略开关
  quant: true
src/main/resources/i18n/messages.properties
src/main/resources/i18n/messages_en_US.properties
File was deleted
src/main/resources/i18n/messages_zh_CN.properties
File was deleted