├── LICENSE
├── README.md
├── engine
├── .gitignore
├── README.md
├── build.gradle
├── exchange
│ ├── build.gradle
│ └── src
│ │ ├── main
│ │ └── java
│ │ │ └── cqt
│ │ │ └── goai
│ │ │ └── exchange
│ │ │ ├── Action.java
│ │ │ ├── BaseExchange.java
│ │ │ ├── ExchangeError.java
│ │ │ ├── ExchangeException.java
│ │ │ ├── ExchangeInfo.java
│ │ │ ├── ExchangeManager.java
│ │ │ ├── ExchangeName.java
│ │ │ ├── ExchangeUtil.java
│ │ │ ├── http
│ │ │ ├── HistoryOrdersByTimestamp.java
│ │ │ ├── HistoryOrdersDetailsByTimestamp.java
│ │ │ ├── HttpExchange.java
│ │ │ ├── TradeByFillOrKill.java
│ │ │ ├── binance
│ │ │ │ └── BinanceExchange.java
│ │ │ ├── bitfinex
│ │ │ │ └── BitfinexExchange.java
│ │ │ ├── huobi
│ │ │ │ └── pro
│ │ │ │ │ └── HuobiProExchange.java
│ │ │ └── okexv3
│ │ │ │ └── Okexv3Exchange.java
│ │ │ ├── util
│ │ │ ├── CommonUtil.java
│ │ │ ├── CompleteList.java
│ │ │ ├── Md5Util.java
│ │ │ ├── OkhttpWebSocket.java
│ │ │ ├── RateLimit.java
│ │ │ ├── Seal.java
│ │ │ ├── bitfinex
│ │ │ │ └── BitfinexUtil.java
│ │ │ ├── huobi
│ │ │ │ └── pro
│ │ │ │ │ └── HoubiProUtil.java
│ │ │ └── okexv3
│ │ │ │ └── Okexv3Util.java
│ │ │ └── web
│ │ │ └── socket
│ │ │ ├── BaseWebSocketClient.java
│ │ │ ├── WebSocketExchange.java
│ │ │ ├── WebSocketInfo.java
│ │ │ └── okexv3
│ │ │ ├── Okexv3WebSocketClient.java
│ │ │ └── Okexv3WebSocketExchange.java
│ │ └── test
│ │ └── java
│ │ └── test
│ │ └── RateLimitTest.java
├── model
│ ├── build.gradle
│ └── src
│ │ ├── main
│ │ └── java
│ │ │ └── cqt
│ │ │ └── goai
│ │ │ └── model
│ │ │ ├── BaseModelList.java
│ │ │ ├── To.java
│ │ │ ├── Util.java
│ │ │ ├── enums
│ │ │ ├── Period.java
│ │ │ ├── Side.java
│ │ │ ├── State.java
│ │ │ └── Type.java
│ │ │ ├── market
│ │ │ ├── Depth.java
│ │ │ ├── Kline.java
│ │ │ ├── Klines.java
│ │ │ ├── Row.java
│ │ │ ├── Rows.java
│ │ │ ├── Ticker.java
│ │ │ ├── Trade.java
│ │ │ └── Trades.java
│ │ │ ├── other
│ │ │ ├── RunInfo.java
│ │ │ └── RunState.java
│ │ │ └── trade
│ │ │ ├── Account.java
│ │ │ ├── Balance.java
│ │ │ ├── Balances.java
│ │ │ ├── Order.java
│ │ │ ├── OrderDetail.java
│ │ │ ├── OrderDetails.java
│ │ │ ├── Orders.java
│ │ │ ├── Precision.java
│ │ │ └── Precisions.java
│ │ └── test
│ │ └── java
│ │ └── test
│ │ ├── market
│ │ ├── DepthTest.java
│ │ ├── KlineTest.java
│ │ ├── KlinesTest.java
│ │ ├── RowTest.java
│ │ ├── RowsTest.java
│ │ ├── TickerTest.java
│ │ ├── TradeTest.java
│ │ └── TradesTest.java
│ │ └── trade
│ │ └── PrecisionTest.java
├── run
│ ├── build.gradle
│ ├── jar.bat
│ └── src
│ │ └── main
│ │ ├── java
│ │ └── cqt
│ │ │ └── goai
│ │ │ └── run
│ │ │ ├── Application.java
│ │ │ ├── annotation
│ │ │ ├── Scheduled.java
│ │ │ ├── ScheduledScope.java
│ │ │ └── Schedules.java
│ │ │ ├── exchange
│ │ │ ├── BaseExchange.java
│ │ │ ├── Exchange.java
│ │ │ ├── FourFunction.java
│ │ │ ├── LocalExchange.java
│ │ │ ├── ProxyExchange.java
│ │ │ ├── TrConsumer.java
│ │ │ ├── factory
│ │ │ │ ├── BaseExchangeFactory.java
│ │ │ │ └── LocalExchangeFactory.java
│ │ │ └── model
│ │ │ │ ├── LogAction.java
│ │ │ │ ├── ModelManager.java
│ │ │ │ ├── ModelObserver.java
│ │ │ │ └── TradesObserver.java
│ │ │ ├── main
│ │ │ ├── Const.java
│ │ │ ├── Injectable.java
│ │ │ ├── MethodInfo.java
│ │ │ ├── MethodScope.java
│ │ │ ├── Minister.java
│ │ │ ├── Notice.java
│ │ │ ├── RunTask.java
│ │ │ ├── ScheduledInfo.java
│ │ │ ├── ScheduledJob.java
│ │ │ ├── ScheduledMethod.java
│ │ │ ├── TaskManager.java
│ │ │ ├── UserLogger.java
│ │ │ └── Util.java
│ │ │ └── notice
│ │ │ ├── BaseNotice.java
│ │ │ ├── EmailNotice.java
│ │ │ ├── Notice.java
│ │ │ ├── NoticeType.java
│ │ │ └── TelegramNotice.java
│ │ └── resources
│ │ └── logback.xml
├── settings.gradle
└── strategy
│ ├── build.gradle
│ ├── package.bat
│ ├── package.sh
│ └── src
│ └── main
│ ├── java
│ ├── Demo.java
│ └── Main.java
│ └── resources
│ ├── .gitignore
│ ├── config.yml
│ └── logback.xml
├── qrcode_for_gh_9666b42fb2ca_430.jpg
└── strategy
├── .gitignore
├── application
└── web.jar
├── build.gradle
├── libs
└── goai-engine.jar
├── package.bat
├── package.sh
├── run_goai.bat
├── run_goai.sh
└── src
└── main
├── java
├── Demo.java
├── Main.java
└── tutorial
│ ├── Tutorial01.java
│ ├── Tutorial02.java
│ ├── Tutorial03.java
│ └── Tutorial04.java
└── resources
└── config.yml
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | 新一代的量化系统,为交易而构建
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 | ## 简介
14 | GOAi 是一款基于JAVA的开源量化交易系统,起源于公司自研量化系统。目标从初期的交易所API接口封装,一步步成长为一套全功能量化交易平台。目前仅支持数字资产量化交易,未来会陆续支持A股、港股、美股、期货、期权等多种交易标的。
15 | ## 动机
16 | #### 商业闭源平台问题:
17 | * 不要把交易所密钥存储在第三方平台,重要的事情说三遍。
18 | * 策略的保密性,策略是自己辛辛苦苦研究成果。平台无法确保存储的绝对安全。
19 | * 如果代跑策略,用第三方平台并不方便。
20 | * 商业平台的闭源性质,你并不清楚框架做了什么。
21 | * 熊市本来就不赚钱,还要支出一笔额外费用。
22 | #### 其它开源平台:
23 | * 部分项目产品化做的不够好,偏技术,易用性较差。
24 | * 部分项目个人业余时间进行维护,文档、bug修复、新增功能不够及时。
25 | ## 我们的优势
26 | * 交易引擎100%开源
27 | * 公司持续化维护运营,配备专门的开发、设计、测试人员。
28 | * 产品优化会根据使用者的反馈第一时间做出调整。
29 | * 定期举行线上线下的交流会。
30 | ## 功能
31 | #### 现有功能:
32 | * 支持主流交易所现货http接口(okexv3、火币、币安、bitfinex)。
33 | * 支持交易所WebSocket连接方式(okexv3 ws接口)。
34 | * 支持电报发消息通知。
35 | * 接口的模板化封装。
36 | #### 准备实现功能:
37 | * 更多主流现货交易的支持。
38 | * okex合约的支持。
39 | * bitmex交易所支持。
40 | * 回测系统的实现。
41 | * 图表及收益曲线的支持。
42 | * 多语言开发的支持 如 python、javascript 等。
43 | ## 界面
44 |
45 | **登录页**
46 |
47 | 
48 |
49 | **首页**
50 |
51 | 
52 |
53 | **实例管理**
54 |
55 | > 在线配置参数实例
56 |
57 | 
58 |
59 | > jar包本地获取配置参数实例
60 |
61 | 
62 |
63 | **策略管理**
64 |
65 | 
66 |
67 | **交易所管理**
68 |
69 | 
70 |
71 | **策略模板**
72 |
73 | 
74 |
75 |
76 | ## 环境要求
77 |
78 | **GOAi运行环境基本要求**
79 | > 只需启动运行GOAi服务(策略实例、GOAi后台管理系统Web服务)情况
80 | + JDK(1.8及以上)
81 | + Linux(Centos7) 或 Windows
82 |
83 | **GOAi策略开发环境基本要求**
84 | > 策略开发调试基本环境,一般是在本地开发调试策略时所需要的环境要求
85 | + JDK(1.8及以上)
86 | + Gradle(4.8及以上)
87 | + Linux(Centos7) 或 Windows
88 |
89 |
90 | ## 一键启动方式
91 |
92 | **Windows**
93 |
94 | + 进入strategy目录,点击运行 `run_goai.bat` 脚本文件即可
95 |
96 | **MAC**
97 |
98 | + 进入strategy目录,运行 ./run_goai.sh 命令
99 |
100 | **Linux**
101 |
102 | + 进入strategy目录,点击运行 `run_goai.sh` 脚本文件即可
103 |
104 | 若是Centos7系统(更多系统支持敬请期待),则可下载[一键安装启动脚本](https://github.com/goaiquant/GOAi/releases/tag/v0.0.1),快速安装部署并启动:
105 | + 使用root身份登录Linux服务器
106 | + 切换到root目录:`cd /root`
107 | + 下载一键安装启动脚本:`wget https://github.com/goaiquant/GOAi/releases/download/v0.0.1/goai.sh`
108 | + 运行脚本进行安装或管理:`bash goai.sh`
109 | + 按照窗口提示输入对应命令序号进行操作即可
110 |
111 | ## 文档
112 | * [API 接口文档](https://github.com/goaiquant/GOAi/wiki/GOAi-API-接口文档)
113 | * [telegram 通知配置说明](https://github.com/goaiquant/GOAi/wiki/电报通知配置方法)
114 |
115 | ## 使用IDEA 打开工程写策略
116 | * [IDE打开工程并写策略方法](http://note.youdao.com/noteshare?id=cce22cf28f89ffcba66c758c38756b13)
117 |
118 | ## 社群
119 | * 公众号:了解最新产品动态,听策略大师、开发小哥、设计妹子吐槽吹水。
120 | > 
121 | * GOAi官方QQ交流群:689620375 (群附件有GOAi使用的视频教程)
122 |
123 | ## 授权
124 | 引擎使用 AGPLV3 开源,须严格遵守 AGPLV3 的相关条款。
125 |
--------------------------------------------------------------------------------
/engine/.gitignore:
--------------------------------------------------------------------------------
1 | .idea
2 | .gradle
3 | .run_info
4 | .goai
5 | *.iml
6 | PID
7 | node
8 | build
9 | *.log
10 | logs
11 | *.csv
12 | *.xlsx
13 | .mime_cache
14 | .realtime
15 | .profit
16 |
--------------------------------------------------------------------------------
/engine/README.md:
--------------------------------------------------------------------------------
1 | # GOAi量化交易系统
2 |
3 | ## 本地环境搭建
4 | + JDK1.8
5 | + Gradle
6 |
7 | ## 模块介绍
8 | + **exchange**: 封装各交易所接口 http 和 websocket
9 | + **model**: model bean
10 | + **run**: 结合model和exchange可以运行策略
11 | + **strategy**: 策略模块 可在引擎中的该目录下写策略
12 |
13 |
14 |
--------------------------------------------------------------------------------
/engine/build.gradle:
--------------------------------------------------------------------------------
1 | buildscript {
2 | repositories {
3 | maven { url 'http://maven.aliyun.com/nexus/content/groups/public/' }
4 | maven { url 'http://maven.aliyun.com/nexus/content/repositories/jcenter/' }
5 | mavenCentral()
6 | }
7 | dependencies {
8 | classpath("org.springframework.boot:spring-boot-gradle-plugin:2.1.1.RELEASE")
9 | }
10 | }
11 |
12 | allprojects {
13 | repositories {
14 | maven { url 'http://maven.aliyun.com/nexus/content/groups/public/' }
15 | maven { url 'http://maven.aliyun.com/nexus/content/repositories/jcenter/' }
16 | maven { url 'https://maven.yinian.tech/public/' }
17 | mavenCentral()
18 | }
19 | apply plugin: 'java'
20 | apply plugin: 'idea'
21 |
22 | idea {
23 | module {
24 | downloadSources = true
25 | inheritOutputDirs = false
26 | outputDir = file("build/classes/main/")
27 | testOutputDir = file("build/classes/test/")
28 | }
29 | }
30 |
31 | [compileJava,compileTestJava,javadoc]*.options*.encoding = 'UTF-8'
32 |
33 | sourceCompatibility = 1.8
34 | targetCompatibility = 1.8
35 |
36 | dependencies {
37 | annotationProcessor 'org.projectlombok:lombok:1.18.4'
38 | implementation 'org.projectlombok:lombok:1.18.4'
39 | }
40 | }
41 |
42 |
--------------------------------------------------------------------------------
/engine/exchange/build.gradle:
--------------------------------------------------------------------------------
1 | jar {
2 | enabled = true
3 | baseName = 'exchange'
4 | version = '1.0.0'
5 | }
6 |
7 | dependencies {
8 | testCompile 'junit:junit:4.12'
9 |
10 | // compile fileTree(dir: 'libs', includes: ['*.jar'])
11 |
12 | compile project(':model')
13 |
14 | compile 'dive:common:0.0.1'
15 | compile 'dive:cache-mime:0.0.2'
16 | compile 'dive:http-okhttp:0.0.1'
17 | compile 'com.squareup.okhttp3:okhttp:3.11.0'
18 |
19 | compile 'org.java-websocket:Java-WebSocket:1.3.9'
20 | compile 'org.apache.commons:commons-compress:1.18'
21 | }
22 |
--------------------------------------------------------------------------------
/engine/exchange/src/main/java/cqt/goai/exchange/Action.java:
--------------------------------------------------------------------------------
1 | package cqt.goai.exchange;
2 |
3 | /**
4 | * 请求行为动作
5 | * @author GOAi
6 |
7 | */
8 | public enum Action {
9 | // 市场接口
10 | TICKER,
11 | KLINES,
12 | DEPTH,
13 | TRADES,
14 |
15 | // 账户接口
16 | BALANCES,
17 | ACCOUNT,
18 |
19 | // 交易接口
20 | PRECISIONS,
21 | PRECISION,
22 | BUY_LIMIT,
23 | SELL_LIMIT,
24 | BUY_MARKET,
25 | SELL_MARKET,
26 | MULTI_BUY,
27 | MULTI_SELL,
28 | CANCEL_ORDER,
29 | CANCEL_ORDERS,
30 |
31 | // 订单接口
32 | ORDERS,
33 | HISTORY_ORDERS,
34 | ORDER,
35 | ORDER_DETAILS,
36 |
37 | // 推送接口
38 | ON_TICKER,
39 | ON_KLINES,
40 | ON_DEPTH,
41 | ON_TRADES,
42 | ON_ACCOUNT,
43 | ON_ORDERS
44 | }
45 |
--------------------------------------------------------------------------------
/engine/exchange/src/main/java/cqt/goai/exchange/BaseExchange.java:
--------------------------------------------------------------------------------
1 | package cqt.goai.exchange;
2 |
3 | import org.slf4j.Logger;
4 |
5 | /**
6 | * 交易所基本信息
7 | * @author GOAi
8 | */
9 | public class BaseExchange {
10 |
11 | /**
12 | * 日志
13 | */
14 | protected final Logger log;
15 |
16 | /**
17 | * 交易所名称
18 | */
19 | protected final ExchangeName name;
20 |
21 | public BaseExchange(ExchangeName name, Logger log) {
22 | this.name = name;
23 | this.log = log;
24 | }
25 |
26 | }
27 |
--------------------------------------------------------------------------------
/engine/exchange/src/main/java/cqt/goai/exchange/ExchangeError.java:
--------------------------------------------------------------------------------
1 | package cqt.goai.exchange;
2 |
3 | /**
4 | * 交易所相关错误码
5 | *
6 | * @author GOAi
7 | */
8 | public enum ExchangeError {
9 | // 交易所不支持
10 | EXCHANGE(1001),
11 | // 币对不支持
12 | SYMBOL(1002),
13 | // K线周期不支持
14 | PERIOD(1003);
15 | // public static final Integer ERROR_TOKEN = 1001; // 授权错误
16 |
17 | private int code;
18 |
19 | ExchangeError(int code) {
20 | this.code = code;
21 | }
22 |
23 | public int getCode() {
24 | return code;
25 | }
26 |
27 | }
28 |
--------------------------------------------------------------------------------
/engine/exchange/src/main/java/cqt/goai/exchange/ExchangeException.java:
--------------------------------------------------------------------------------
1 | package cqt.goai.exchange;
2 |
3 |
4 | import dive.common.model.Message;
5 |
6 | /**
7 | * 交易所信息处理异常
8 | * @author GOAi
9 | */
10 | public class ExchangeException extends RuntimeException {
11 |
12 | private static final long serialVersionUID = -8633608065854161508L;
13 |
14 | private Integer code = Message.CODE_FAILED;
15 |
16 | public ExchangeException(String message) {
17 | super(message);
18 | }
19 |
20 | public ExchangeException(ExchangeError error, String message) {
21 | super(message);
22 | this.code = error.getCode();
23 | }
24 |
25 | public ExchangeException(String message, Throwable throwable) {
26 | super(message, throwable);
27 | }
28 |
29 | public ExchangeException(ExchangeError error, String message, Throwable throwable) {
30 | super(message, throwable);
31 | this.code = error.getCode();
32 | }
33 |
34 |
35 | public Integer getCode() {
36 | return code;
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/engine/exchange/src/main/java/cqt/goai/exchange/ExchangeManager.java:
--------------------------------------------------------------------------------
1 | package cqt.goai.exchange;
2 |
3 | import cqt.goai.exchange.http.HttpExchange;
4 | import cqt.goai.exchange.http.binance.BinanceExchange;
5 | import cqt.goai.exchange.http.bitfinex.BitfinexExchange;
6 | import cqt.goai.exchange.http.huobi.pro.HuobiProExchange;
7 | import cqt.goai.exchange.http.okexv3.Okexv3Exchange;
8 | import cqt.goai.exchange.web.socket.WebSocketExchange;
9 | import cqt.goai.exchange.web.socket.okexv3.Okexv3WebSocketExchange;
10 | import org.slf4j.Logger;
11 |
12 | /**
13 | * 统一获取Exchange对象
14 | * @author GOAi
15 | */
16 | public class ExchangeManager {
17 |
18 | /**
19 | * 获取HttpExchange
20 | * Simple Factory Pattern 简单工厂模式
21 | * 获取实例对象即可,没有太多的拓展性
22 | * @param name 交易所名
23 | * @param log 日志
24 | */
25 | public static HttpExchange getHttpExchange(ExchangeName name, Logger log) {
26 | if (null != name) {
27 | switch (name) {
28 | case OKEXV3: return new Okexv3Exchange(log);
29 | case BITFINEX: return new BitfinexExchange(log);
30 | case HUOBIPRO: return new HuobiProExchange(log);
31 | case BINANCE: return new BinanceExchange(log);
32 | default:
33 | }
34 | }
35 | throw new ExchangeException(ExchangeError.EXCHANGE, "exchange name is not supported");
36 | }
37 |
38 | /**
39 | * 获取WSExchange
40 | * 简单工厂模式
41 | * @param name 交易所名
42 | * @param log 日志
43 | */
44 | public static WebSocketExchange getWebSocketExchange(ExchangeName name, Logger log) {
45 | if (null != name) {
46 | switch (name) {
47 | case OKEXV3: return new Okexv3WebSocketExchange(log);
48 | default:
49 | }
50 | }
51 | throw new ExchangeException(ExchangeError.EXCHANGE, "exchange name is not supported");
52 | }
53 |
54 | }
55 |
--------------------------------------------------------------------------------
/engine/exchange/src/main/java/cqt/goai/exchange/ExchangeName.java:
--------------------------------------------------------------------------------
1 | package cqt.goai.exchange;
2 |
3 | import cqt.goai.exchange.util.CompleteList;
4 |
5 | import java.util.ArrayList;
6 | import java.util.Arrays;
7 | import java.util.List;
8 |
9 | import static cqt.goai.exchange.Action.*;
10 | import static cqt.goai.exchange.Action.ORDER;
11 | import static cqt.goai.exchange.util.CompleteList.*;
12 |
13 | /**
14 | * 交易所名称
15 | *
16 | * @author GOAi
17 | */
18 | public enum ExchangeName {
19 |
20 | /**
21 | * OKEX V3 版本, ALL
22 | * https://www.okex.com/docs/zh/
23 | * CompleteList.ALL
24 | */
25 | OKEXV3("okexv3", CompleteList.ALL),
26 |
27 | /**
28 | * bitfinex
29 | * https://docs.bitfinex.com/v1/docs/rest-general
30 | * https://docs.bitfinex.com/v2/docs/rest-auth
31 | * CompleteList.MARKET
32 | * CompleteList.ACCOUNT
33 | * CompleteList.TRADE
34 | * CompleteList.ORDER
35 | */
36 | BITFINEX("bitfinex", CompleteList.exclude(PUSH)),
37 |
38 |
39 | /**
40 | * huobipro
41 | * https://github.com/huobiapi/API_Docs
42 | */
43 | HUOBIPRO("huobipro", CompleteList.exclude(PUSH)),
44 |
45 |
46 |
47 | /**
48 | * binance
49 | * https://github.com/binance-exchange/binance-official-api-docs/blob/master/rest-api.md
50 | */
51 | BINANCE("binance", CompleteList.exclude(PUSH));
52 |
53 |
54 | /**
55 | * 交易所名称,唯一识别
56 | */
57 | private String name;
58 |
59 | /**
60 | * 已完成的功能
61 | */
62 | private List functions;
63 |
64 | ExchangeName(String name, Action... actions) {
65 | this.name = name;
66 | this.functions = null == actions ? new ArrayList<>() : Arrays.asList(actions);
67 | }
68 |
69 | public String getName() {
70 | return this.name;
71 | }
72 |
73 | public List getFunctions() {
74 | return this.functions;
75 | }
76 |
77 | /**
78 | * 根据名称获取
79 | */
80 | public static ExchangeName getByName(String name) {
81 | for (ExchangeName en : ExchangeName.values()) {
82 | if (en.name.equals(name)) {
83 | return en;
84 | }
85 | }
86 | return null;
87 | }
88 |
89 | @Override
90 | public String toString() {
91 | return this.name;
92 | }
93 |
94 | }
95 |
--------------------------------------------------------------------------------
/engine/exchange/src/main/java/cqt/goai/exchange/ExchangeUtil.java:
--------------------------------------------------------------------------------
1 | package cqt.goai.exchange;
2 |
3 | import dive.http.okhttp.OkHttp;
4 |
5 | /**
6 | * 工具类
7 | * @author GOAi
8 | */
9 | public class ExchangeUtil {
10 |
11 | /**
12 | * okhttp util
13 | */
14 | public static final OkHttp OKHTTP = new OkHttp();
15 |
16 | }
17 |
--------------------------------------------------------------------------------
/engine/exchange/src/main/java/cqt/goai/exchange/http/HistoryOrdersByTimestamp.java:
--------------------------------------------------------------------------------
1 | package cqt.goai.exchange.http;
2 |
3 | import cqt.goai.exchange.ExchangeInfo;
4 | import cqt.goai.exchange.ExchangeName;
5 | import cqt.goai.model.trade.Orders;
6 | import dive.http.common.MimeHttp;
7 | import dive.http.common.MimeRequest;
8 | import org.slf4j.Logger;
9 |
10 | import java.util.List;
11 |
12 | /**
13 | * 根据时间戳获取历史订单
14 | *
15 | * @author GOAi
16 | */
17 | public interface HistoryOrdersByTimestamp {
18 |
19 | /**
20 | * 获取请求工具
21 | * @return 请求工具
22 | */
23 | MimeHttp getHttp();
24 |
25 | /**
26 | * 获取日志输出
27 | * @return 日志
28 | */
29 | Logger getLog();
30 |
31 | /**
32 | * 获取交易所名称
33 | * @return 交易所名称
34 | */
35 | ExchangeName getExchangeName();
36 |
37 | /**
38 | * 获取历史订单
39 | * @param access access
40 | * @param secret secret
41 | * @param symbol 币对
42 | * @param start 开始时间
43 | * @param end 结束时间
44 | * @return Orders
45 | */
46 | default Orders getHistoryOrders(String symbol, String access, String secret, Long start, Long end) {
47 | List mimeRequests = this.historyOrdersRequests(symbol, access, secret, start, end, 0);
48 | List results = HttpExchange.results(mimeRequests, this.getHttp(), this.getLog());
49 | return this.parseHistoryOrders(results, symbol, access, secret);
50 | }
51 |
52 | /**
53 | * 获取历史订单请求
54 | * @param symbol 币对
55 | * @param access access
56 | * @param secret secret
57 | * @param start 开始时间戳
58 | * @param end 截止时间戳
59 | * @param delay 延时
60 | * @return 请求
61 | */
62 | List historyOrdersRequests(String symbol, String access, String secret,
63 | Long start, Long end, long delay);
64 |
65 | /**
66 | * 解析历史订单
67 | * @param results 请结果
68 | * @param symbol 币对
69 | * @param access access
70 | * @param secret secret
71 | * @return 订单列表
72 | */
73 | default Orders parseHistoryOrders(List results, String symbol, String access, String secret) {
74 | return HttpExchange.analyze(results, ExchangeInfo.historyOrders(symbol, access, secret), this::transformHistoryOrders, this.getExchangeName(), this.getLog());
75 | }
76 |
77 | /**
78 | * 解析历史订单明细
79 | * @param results 请结果
80 | * @param info 请求信息
81 | * @return 结果
82 | */
83 | Orders transformHistoryOrders(List results, ExchangeInfo info);
84 |
85 | }
86 |
--------------------------------------------------------------------------------
/engine/exchange/src/main/java/cqt/goai/exchange/http/HistoryOrdersDetailsByTimestamp.java:
--------------------------------------------------------------------------------
1 | package cqt.goai.exchange.http;
2 |
3 | import cqt.goai.exchange.ExchangeInfo;
4 | import cqt.goai.exchange.ExchangeName;
5 | import cqt.goai.model.trade.OrderDetails;
6 | import dive.http.common.MimeHttp;
7 | import dive.http.common.MimeRequest;
8 | import org.slf4j.Logger;
9 |
10 | import java.util.List;
11 |
12 | /**
13 | * 获取历史订单明细
14 | *
15 | * @author GOAi
16 | */
17 | public interface HistoryOrdersDetailsByTimestamp {
18 |
19 | /**
20 | * 获取请求工具
21 | * @return 请求工具
22 | */
23 | MimeHttp getHttp();
24 |
25 | /**
26 | * 获取日志输出
27 | * @return 日志
28 | */
29 | Logger getLog();
30 |
31 | /**
32 | * 获取交易所名称
33 | * @return 交易所名称
34 | */
35 | ExchangeName getExchangeName();
36 |
37 | /**
38 | * 获取历史订单明细
39 | * @param symbol 币对
40 | * @param access access
41 | * @param secret secret
42 | * @param start 开始时间
43 | * @param end 结束时间
44 | * @return 订单明细
45 | */
46 | default OrderDetails getHistoryOrdersDetails(String symbol, String access, String secret, Long start, Long end) {
47 | List mimeRequests = this.historyOrdersDetailsRequests(symbol, access, secret, start, end, 0);
48 | List results = HttpExchange.results(mimeRequests, this.getHttp(), this.getLog());
49 | return this.parseHistoryOrdersDetails(results, symbol, access, secret);
50 |
51 | }
52 |
53 | /**
54 | * 获取历史订单明细请求
55 | * @param symbol 币对
56 | * @param access access
57 | * @param secret secret
58 | * @param start 开始时间戳
59 | * @param end 截止时间戳
60 | * @param delay 延时
61 | * @return 请求
62 | */
63 | List historyOrdersDetailsRequests(String symbol, String access, String secret,
64 | Long start, Long end, long delay);
65 |
66 | /**
67 | * 解析历史订单明细
68 | * @param results 请求结果
69 | * @param symbol 币对
70 | * @param access access
71 | * @param secret secret
72 | * @return 订单明细
73 | */
74 | default OrderDetails parseHistoryOrdersDetails(List results, String symbol, String access, String secret) {
75 | return HttpExchange.analyze(results, ExchangeInfo.historyOrders(symbol, access, secret), this::transformHistoryOrdersDetails, this.getExchangeName(), this.getLog());
76 | }
77 |
78 | /**
79 | * 解析历史订单明细
80 | * @param results 请求结果
81 | * @param info 请求信息
82 | * @return 结果
83 | */
84 | OrderDetails transformHistoryOrdersDetails(List results, ExchangeInfo info);
85 |
86 | }
87 |
--------------------------------------------------------------------------------
/engine/exchange/src/main/java/cqt/goai/exchange/http/TradeByFillOrKill.java:
--------------------------------------------------------------------------------
1 | package cqt.goai.exchange.http;
2 |
3 | import cqt.goai.exchange.ExchangeInfo;
4 | import cqt.goai.exchange.ExchangeName;
5 | import dive.http.common.MimeHttp;
6 | import dive.http.common.MimeRequest;
7 | import org.slf4j.Logger;
8 |
9 | import java.util.List;
10 |
11 | /**
12 | * fok交易
13 | *
14 | * @author GOAi
15 | */
16 | public interface TradeByFillOrKill {
17 |
18 | /**
19 | * 获取请求工具
20 | * @return 请求工具
21 | */
22 | MimeHttp getHttp();
23 |
24 | /**
25 | * 获取日志输出
26 | * @return 日志
27 | */
28 | Logger getLog();
29 |
30 | /**
31 | * 获取交易所名称
32 | * @return 交易所名称
33 | */
34 | ExchangeName getExchangeName();
35 |
36 | /**
37 | * 下fok买单
38 | * @param access access
39 | * @param secret secret
40 | * @param symbol 币对
41 | * @param price 价格
42 | * @param amount 数量
43 | * @return 订单id
44 | */
45 | default String buyFillOrKill(String symbol, String access, String secret, String price, String amount) {
46 | List mimeRequests = this.buyFillOrKillRequests(symbol, access, secret, price, amount, 0);
47 | List results = HttpExchange.results(mimeRequests, this.getHttp(), this.getLog());
48 | return this.parseBuyFillOrKill(results, symbol, access, secret, price, amount);
49 | }
50 |
51 | /**
52 | * 获取fok买单请求
53 | * @param symbol 币对
54 | * @param access access
55 | * @param secret secret
56 | * @param price 价格
57 | * @param amount 数量
58 | * @param delay 延时
59 | * @return 请求
60 | */
61 | List buyFillOrKillRequests(String symbol, String access, String secret, String price, String amount, long delay);
62 |
63 | /**
64 | * 解析fok订单
65 | * @param results 请结果
66 | * @param symbol 币对
67 | * @param access access
68 | * @param secret secret
69 | * @param price 价格
70 | * @param amount 数量
71 | * @return 订单id
72 | */
73 | default String parseBuyFillOrKill(List results, String symbol, String access, String secret, String price, String amount) {
74 | return HttpExchange.analyze(results, ExchangeInfo.buyLimit(symbol, access, secret, price, amount), this::transformBuyFillOrKill, this.getExchangeName(), this.getLog());
75 | }
76 |
77 | /**
78 | * 解析订单id
79 | * @param results 请结果
80 | * @param info 请求信息
81 | * @return 订单id
82 | */
83 | String transformBuyFillOrKill(List results, ExchangeInfo info);
84 |
85 |
86 | /**
87 | * 下fok卖单
88 | * @param access access
89 | * @param secret secret
90 | * @param symbol 币对
91 | * @param price 价格
92 | * @param amount 数量
93 | * @return 订单id
94 | */
95 | default String sellFillOrKill(String symbol, String access, String secret, String price, String amount) {
96 | List mimeRequests = this.sellFillOrKillRequests(symbol, access, secret, price, amount, 0);
97 | List results = HttpExchange.results(mimeRequests, this.getHttp(), this.getLog());
98 | return this.parseSellFillOrKill(results, symbol, access, secret, price, amount);
99 | }
100 |
101 | /**
102 | * 获取fok卖单请求
103 | * @param symbol 币对
104 | * @param access access
105 | * @param secret secret
106 | * @param price 价格
107 | * @param amount 数量
108 | * @param delay 延时
109 | * @return 请求
110 | */
111 | List sellFillOrKillRequests(String symbol, String access, String secret, String price, String amount, long delay);
112 |
113 | /**
114 | * 解析订单id
115 | * @param results 请结果
116 | * @param symbol 币对
117 | * @param access access
118 | * @param secret secret
119 | * @param price 价格
120 | * @param amount 数量
121 | * @return 订单id
122 | */
123 | default String parseSellFillOrKill(List results, String symbol, String access, String secret, String price, String amount) {
124 | return HttpExchange.analyze(results, ExchangeInfo.buyLimit(symbol, access, secret, price, amount), this::transformSellFillOrKill, this.getExchangeName(), this.getLog());
125 | }
126 |
127 | /**
128 | * 解析订单id
129 | * @param results 请结果
130 | * @param info 请求信息
131 | * @return 订单id
132 | */
133 | String transformSellFillOrKill(List results, ExchangeInfo info);
134 | }
135 |
--------------------------------------------------------------------------------
/engine/exchange/src/main/java/cqt/goai/exchange/util/CompleteList.java:
--------------------------------------------------------------------------------
1 | package cqt.goai.exchange.util;
2 |
3 | import cqt.goai.exchange.Action;
4 |
5 | import java.util.*;
6 | import java.util.stream.Stream;
7 |
8 | import static cqt.goai.exchange.Action.*;
9 |
10 | /**
11 | * 便捷标识交易所接口的完成情况
12 | * @author GOAi
13 | */
14 | public class CompleteList {
15 |
16 | public static Action[] ALL;
17 | public static Action[] MARKET;
18 | public static Action[] ACCOUNT;
19 | public static Action[] TRADE;
20 | public static Action[] ORDER;
21 | public static Action[] PUSH;
22 |
23 | static {
24 | MARKET = new Action[]{TICKER, KLINES, DEPTH, TRADES};
25 | ACCOUNT = new Action[]{BALANCES, Action.ACCOUNT};
26 | TRADE = new Action[]{PRECISIONS, PRECISION, BUY_LIMIT, SELL_LIMIT,
27 | BUY_MARKET, SELL_MARKET, MULTI_BUY, MULTI_SELL, CANCEL_ORDER, CANCEL_ORDERS};
28 | ORDER = new Action[]{ORDERS, HISTORY_ORDERS, Action.ORDER, ORDER_DETAILS};
29 | PUSH = new Action[]{ON_TICKER, ON_KLINES, ON_DEPTH, ON_TRADES, ON_ACCOUNT, ON_ORDERS};
30 | Set list = new HashSet<>();
31 | list.addAll(Arrays.asList(MARKET));
32 | list.addAll(Arrays.asList(ACCOUNT));
33 | list.addAll(Arrays.asList(TRADE));
34 | list.addAll(Arrays.asList(ORDER));
35 | list.addAll(Arrays.asList(PUSH));
36 | ALL = list.toArray(new Action[]{});
37 | }
38 |
39 |
40 | public static Action[] include(Object... actions) {
41 | Set set = new HashSet<>();
42 | for (Object o : actions) {
43 | if (o instanceof Action) {
44 | set.add((Action) o);
45 | } else if ("Action[]".equals(o.getClass().getSimpleName())
46 | && o.getClass().getComponentType() == Action.class) {
47 | set.addAll(Arrays.asList((Action[]) o));
48 | }
49 | }
50 | return set.toArray(new Action[]{});
51 | }
52 |
53 | public static Action[] exclude(Action[] exclude) {
54 | Set all = new HashSet<>(Arrays.asList(ALL));
55 | Stream.of(exclude).forEach(all::remove);
56 | return all.toArray(new Action[]{});
57 | }
58 |
59 | public static Action[] exclude(Object... actions) {
60 | Action[] exclude = CompleteList.include(actions);
61 | Set all = new HashSet<>(Arrays.asList(ALL));
62 | Stream.of(exclude).forEach(all::remove);
63 | return all.toArray(new Action[]{});
64 | }
65 |
66 | }
67 |
--------------------------------------------------------------------------------
/engine/exchange/src/main/java/cqt/goai/exchange/util/Md5Util.java:
--------------------------------------------------------------------------------
1 | package cqt.goai.exchange.util;
2 |
3 | import java.security.MessageDigest;
4 | import java.security.NoSuchAlgorithmException;
5 |
6 | /**
7 | * @author GOAi
8 | * @description
9 | */
10 | public class Md5Util {
11 |
12 | private static final char[] HEX_DIGITS = { '0', '1', '2', '3', '4', '5',
13 | '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };
14 |
15 | public static String md5(String str) {
16 | try {
17 | if (str == null || str.trim().length() == 0) {
18 | return "";
19 | }
20 | byte[] bytes = str.getBytes();
21 | MessageDigest messageDigest = MessageDigest.getInstance("MD5");
22 | messageDigest.update(bytes);
23 | bytes = messageDigest.digest();
24 | StringBuilder sb = new StringBuilder();
25 | for (byte b : bytes) {
26 | sb.append(HEX_DIGITS[(b & 0xf0) >> 4]).append(HEX_DIGITS[b & 0xf]);
27 | }
28 | return sb.toString();
29 | } catch (NoSuchAlgorithmException e) {
30 | e.printStackTrace();
31 | }
32 | return "";
33 | }
34 |
35 | // public static String md5(String str) {
36 | // return md5(str).toLowerCase();
37 | // }
38 | }
39 |
--------------------------------------------------------------------------------
/engine/exchange/src/main/java/cqt/goai/exchange/util/OkhttpWebSocket.java:
--------------------------------------------------------------------------------
1 | package cqt.goai.exchange.util;
2 |
3 | import okhttp3.*;
4 | import okio.ByteString;
5 | import org.slf4j.Logger;
6 |
7 | import java.util.function.Consumer;
8 | import java.util.function.Function;
9 |
10 | /**
11 | * 推送回调
12 | * @author GOAi
13 | */
14 | public class OkhttpWebSocket extends WebSocketListener {
15 |
16 | private static final OkHttpClient OKHTTP_CLIENT = new OkHttpClient();
17 |
18 | /**
19 | * 连接url
20 | */
21 | private final String url;
22 |
23 | /**
24 | * 收到消息转码方式
25 | */
26 | private final Function decode;
27 |
28 | /**
29 | * 连接开始
30 | */
31 | private final Runnable open;
32 |
33 | /**
34 | * 接收消息,转码后,交给谁
35 | */
36 | private final Consumer receive;
37 |
38 | /**
39 | * 断开后回调
40 | */
41 | private final Runnable closed;
42 |
43 | /**
44 | * 日志
45 | */
46 | private Logger log;
47 |
48 | /**
49 | * 连接对象
50 | */
51 | private WebSocket webSocket;
52 |
53 | public OkhttpWebSocket(String url, Function decode, Runnable open,
54 | Consumer receive, Runnable closed, Logger log) {
55 | this.url = url;
56 | this.decode = decode;
57 | this.open = open;
58 | this.receive = receive;
59 | this.closed = closed;
60 | this.log = log;
61 | this.connect();
62 | }
63 |
64 | /**
65 | * 连接
66 | */
67 | public void connect() {
68 | if (null != this.webSocket) {
69 | this.webSocket.cancel();
70 | this.webSocket = null;
71 | }
72 | OKHTTP_CLIENT.newWebSocket(new Request.Builder()
73 | .url(this.url)
74 | .build(), this);
75 | }
76 |
77 | /**
78 | * 打开连接回调
79 | */
80 | @Override
81 | public void onOpen(WebSocket webSocket, Response response) {
82 | this.webSocket = webSocket;
83 | if (null != this.open) {
84 | this.open.run();
85 | }
86 | }
87 |
88 | /**
89 | * 发送消息
90 | * @param message 消息
91 | */
92 | public void send(String message) {
93 | if (null != this.webSocket) {
94 | this.webSocket.send(message);
95 | }
96 | }
97 |
98 | /**
99 | * 收到消息
100 | */
101 | @Override
102 | public void onMessage(WebSocket webSocket, ByteString bytes) {
103 | if (null != this.receive) {
104 | String message = this.decode.apply(bytes);
105 | this.receive.accept(message);
106 | }
107 | }
108 |
109 | /**
110 | * 连接正在关闭
111 | */
112 | @Override
113 | public void onClosing(WebSocket webSocket, int code, String reason) {
114 | super.onClosing(webSocket, code, reason);
115 | this.log.info("onClosing --> {} {} {}", webSocket, code, reason);
116 | if (null != this.closed) {
117 | this.closed.run();
118 | }
119 | }
120 |
121 | /**
122 | * 连接关闭回调
123 | */
124 | @Override
125 | public void onClosed(WebSocket webSocket, int code, String reason) {
126 | super.onClosed(webSocket, code, reason);
127 | this.log.info("onClosed --> {} {} {}", webSocket, code, reason);
128 | if (null != this.closed) {
129 | this.closed.run();
130 | }
131 | }
132 |
133 | @Override
134 | public void onFailure(WebSocket webSocket, Throwable t, Response response) {
135 | super.onFailure(webSocket, t, response);
136 | t.printStackTrace();
137 | this.log.info("onFailure --> {} {} {}", webSocket, t, response);
138 | if (null != this.closed) {
139 | this.closed.run();
140 | }
141 | }
142 |
143 | /**
144 | * 主动断开
145 | * @param code code
146 | * @param reason reason
147 | */
148 | public void close(int code, String reason) {
149 | if (null != this.webSocket) {
150 | this.webSocket.close(code, reason);
151 | }
152 | }
153 |
154 | }
155 |
--------------------------------------------------------------------------------
/engine/exchange/src/main/java/cqt/goai/exchange/util/RateLimit.java:
--------------------------------------------------------------------------------
1 | package cqt.goai.exchange.util;
2 |
3 | /**
4 | * 频率限制
5 | * 2种限制
6 | * 1. 主动推送限制频率,limit毫秒内只有一次
7 | * 2. 超时限制,推送则更新,30s未推送需要重连
8 | *
9 | * @author GOAi
10 | */
11 | public class RateLimit {
12 |
13 | /**
14 | * 无限制
15 | */
16 | public static final RateLimit NO = new RateLimit(0);
17 |
18 | /**
19 | * 上次true时间
20 | * 这里判断时间应当非常快没有阻塞的,就不考虑并发问题了,反正限制的频率都是主动推送,不必那么精确的
21 | * AtomicLong 和 Lock 都可以实现并发安全,但是得不偿失,因为即使多推送一次,无伤大雅
22 | */
23 | private long last;
24 |
25 | /**
26 | * 频率限制,若为5000,则在下个5000毫秒内,不会返回true
27 | */
28 | private final long limit;
29 |
30 | private RateLimit(long limit) {
31 | this.limit = limit;
32 | this.last = 0;
33 | }
34 |
35 | /**
36 | * 获取实例
37 | * @param milliseconds 毫秒
38 | * @return 实例
39 | */
40 | public static RateLimit limit(long milliseconds) {
41 | return new RateLimit(milliseconds);
42 | }
43 |
44 | public static RateLimit second() { return new RateLimit(1000); }
45 | public static RateLimit second5() { return new RateLimit(1000 * 5); }
46 | public static RateLimit second10() { return new RateLimit(1000 * 10); }
47 | public static RateLimit second15() { return new RateLimit(1000 * 15); }
48 | public static RateLimit second20() { return new RateLimit(1000 * 20); }
49 | public static RateLimit second30() { return new RateLimit(1000 * 30); }
50 | public static RateLimit second3() { return new RateLimit(1000 * 3); }
51 | public static RateLimit second13() { return new RateLimit(1000 * 13); }
52 | public static RateLimit second23() { return new RateLimit(1000 * 23); }
53 |
54 | /**
55 | * limit内不会再次返回true
56 | * @param update 是否更新
57 | * @return 是否到了下一次
58 | */
59 | public boolean timeout(boolean update) {
60 | if (limit <= 0) {
61 | return true;
62 | }
63 | long now = System.currentTimeMillis();
64 | boolean result = last + limit < now;
65 | if (update && result) {
66 | this.last = now;
67 | }
68 | return result;
69 | }
70 |
71 | /**
72 | * limit内不会再次返回true
73 | * @return 是否到了下一次
74 | */
75 | public boolean timeout() {
76 | return this.timeout(true);
77 | }
78 |
79 | /**
80 | * 更新时间, 收到消息更新一下
81 | */
82 | public void update() {
83 | this.last = System.currentTimeMillis();
84 | }
85 |
86 | /**
87 | * 是否有限制
88 | * @return 是否有限制
89 | */
90 | public boolean isLimit() {
91 | return 0 < this.limit;
92 | }
93 |
94 | @Override
95 | public String toString() {
96 | return "RateLimit{" +
97 | "limit=" + limit +
98 | '}';
99 | }
100 | }
101 |
--------------------------------------------------------------------------------
/engine/exchange/src/main/java/cqt/goai/exchange/util/Seal.java:
--------------------------------------------------------------------------------
1 | package cqt.goai.exchange.util;
2 |
3 |
4 | import static dive.common.util.Util.useful;
5 |
6 | /**
7 | * 隐藏字符串
8 | * @author GOAi
9 | */
10 | public class Seal {
11 | /**
12 | * 隐藏字符串
13 | */
14 | public static String seal(String secret) {
15 | if (!useful(secret)) {
16 | return "";
17 | }
18 | int length = secret.length();
19 | switch (length) {
20 | case 1: return "*";
21 | case 2: return "**";
22 | default:
23 | }
24 | int margin = 5;
25 | int div = length / 3;
26 | if (margin <= div) {
27 | return secret.substring(0, margin)
28 | + getStar(length - margin * 2)
29 | + secret.substring(length - margin);
30 | } else {
31 | return secret.substring(0, div)
32 | + getStar(length - div * 2)
33 | + secret.substring(length - div);
34 | }
35 | }
36 |
37 | /**
38 | * 获取指定长度的*
39 | */
40 | private static String getStar(int length) {
41 | StringBuilder sb = new StringBuilder();
42 | while (sb.length() < length) {
43 | sb.append("*");
44 | }
45 | return sb.toString();
46 | }
47 |
48 | }
49 |
--------------------------------------------------------------------------------
/engine/exchange/src/main/java/cqt/goai/exchange/util/okexv3/Okexv3Util.java:
--------------------------------------------------------------------------------
1 | package cqt.goai.exchange.util.okexv3;
2 |
3 | import com.alibaba.fastjson.JSONArray;
4 | import com.alibaba.fastjson.JSONObject;
5 | import cqt.goai.exchange.util.CommonUtil;
6 | import cqt.goai.model.enums.Period;
7 | import cqt.goai.model.enums.Side;
8 | import cqt.goai.model.enums.State;
9 | import cqt.goai.model.enums.Type;
10 | import cqt.goai.model.market.*;
11 | import cqt.goai.model.trade.Balance;
12 | import cqt.goai.model.trade.Order;
13 | import cqt.goai.model.trade.Orders;
14 | import okio.ByteString;
15 | import org.apache.commons.compress.compressors.deflate64.Deflate64CompressorInputStream;
16 |
17 | import java.io.ByteArrayInputStream;
18 | import java.io.ByteArrayOutputStream;
19 | import java.io.IOException;
20 | import java.math.BigDecimal;
21 | import java.math.RoundingMode;
22 | import java.util.ArrayList;
23 | import java.util.LinkedList;
24 | import java.util.List;
25 | import java.util.stream.Collectors;
26 |
27 | import static dive.common.math.BigDecimalUtil.div;
28 | import static dive.common.math.BigDecimalUtil.greater;
29 | import static java.math.BigDecimal.ZERO;
30 |
31 | /**
32 | * @author GOAi
33 | */
34 | public class Okexv3Util {
35 |
36 | /**
37 | * okexv3 的周期换算
38 | * @param period 周期
39 | * @return 周期长度 秒
40 | */
41 | public static Integer getPeriod(Period period) {
42 | switch (period) {
43 | case MIN1:
44 | case MIN3:
45 | case MIN5:
46 | case MIN15:
47 | case MIN30:
48 | case HOUR1:
49 | case HOUR2:
50 | // case HOUR3 : return 7200;
51 | case HOUR4:
52 | case HOUR6:
53 | case HOUR12:
54 | case DAY1:
55 | // case DAY3 : return 604800;
56 | case WEEK1:
57 | return period.getValue();
58 | // case WEEK2 : return 604800;
59 | // case MONTH1 : return 604800;
60 | default:
61 | return null;
62 | }
63 | }
64 |
65 |
66 | /**
67 | * http 和 ws 解析方式相同
68 | * @param result 原始文件
69 | * @param r json
70 | * @return Ticker
71 | */
72 | public static Ticker parseTicker(String result, JSONObject r) {
73 | /* {
74 | * "instrument_id":"ETH-USDT",
75 | * "last":"131.6299",
76 | * "best_bid":"131.6299",
77 | * "best_ask":"131.6857",
78 | * "open_24h":"149.8823",
79 | * "high_24h":"150.5",
80 | * "low_24h":"130.9743",
81 | * "base_volume_24h":"879069.0066515",
82 | * "quote_volume_24h":"124634731.57238156",
83 | * "timestamp":"2019-01-10T09:26:55.932Z"
84 | * }
85 | */
86 | Long time = r.getDate("timestamp").getTime();
87 | BigDecimal open = r.getBigDecimal("open_24h");
88 | BigDecimal high = r.getBigDecimal("high_24h");
89 | BigDecimal low = r.getBigDecimal("low_24h");
90 | BigDecimal last = r.getBigDecimal("last");
91 | BigDecimal volume = r.getBigDecimal("base_volume_24h");
92 |
93 | return new Ticker(result, time, open, high, low, last, volume);
94 | }
95 |
96 | /**
97 | * ws 解析方式
98 | * @param result 原始文件
99 | * @param r json
100 | * @return Kline
101 | */
102 | public static Kline parseKline(String result, JSONArray r) {
103 | /* {"candle":
104 | * ["2019-01-15T09:51:00.000Z",
105 | * "3594.0579",
106 | * "3595.5917",
107 | * "3593.9239",
108 | * "3595.5917",
109 | * "19.24375778"],
110 | * "instrument_id":"BTC-USDT"}
111 | */
112 | Long time = r.getDate(0).getTime() / 1000;
113 |
114 | return CommonUtil.parseKlineByIndex(result, r, time, 1);
115 | }
116 |
117 | /**
118 | * 统一解析Depth
119 | */
120 | public static Depth parseDepth(JSONObject r) {
121 | Long time = r.getDate("timestamp").getTime();
122 | return CommonUtil.parseDepthByIndex(time, r.getJSONArray("asks"), r.getJSONArray("bids"));
123 | }
124 |
125 | /**
126 | * 统一解析Trades
127 | */
128 | public static Trades parseTrades(JSONArray r) {
129 | List trades = new ArrayList<>(r.size());
130 | for (int i = 0, l = r.size(); i < l; i++) {
131 | /*
132 | * [
133 | * {
134 | * "time":"2019-01-11T14:02:02.536Z",
135 | * "timestamp":"2019-01-11T14:02:02.536Z",
136 | * "trade_id":"861471459",
137 | * "price":"3578.8482",
138 | * "size":"0.0394402",
139 | * "side":"sell"
140 | * },
141 | */
142 | JSONObject t = r.getJSONObject(i);
143 | Long time = t.getDate("timestamp").getTime();
144 | String id = t.getString("trade_id");
145 | Side side = Side.valueOf(t.getString("side").toUpperCase());
146 | BigDecimal price = t.getBigDecimal("price");
147 | BigDecimal amount = t.getBigDecimal("size");
148 | Trade trade = new Trade(r.getString(i), time, id, side, price, amount);
149 | trades.add(trade);
150 | }
151 | return new Trades(trades.stream()
152 | .sorted((t1, t2) -> t2.getTime().compareTo(t1.getTime()))
153 | .collect(Collectors.toList()));
154 | }
155 |
156 | /**
157 | * 解析单个币种余额
158 | *
159 | * @param result 原始数据
160 | * @param r json
161 | * @return Balance
162 | */
163 | public static Balance parseBalance(String result, JSONObject r) {
164 | /*
165 | * {
166 | * "frozen":"0",
167 | * "hold":"0",
168 | * "id":"6278097",
169 | * "currency":"USDT",
170 | * "balance":"127.613453580677953",
171 | * "available":"127.613453580677953",
172 | * "holds":"0"
173 | * }
174 | */
175 | return new Balance(result, r.getString("currency"),
176 | r.getBigDecimal("available"), r.getBigDecimal("hold"));
177 | }
178 |
179 | /**
180 | * 解析Order
181 | * @param result 原始数据
182 | * @param t json
183 | * @return Order
184 | */
185 | public static Order parseOrder(String result, JSONObject t) {
186 | /*
187 | * {
188 | * "order_id": "125678",
189 | * "notional": "12.4",
190 | * "price": "0.10000000",
191 | * "size": "0.01000000",
192 | * "instrument_id": "BTC-USDT",
193 | * "side": "buy",
194 | * "type": "limit",
195 | * "timestamp": "2016-12-08T20:02:28.538Z",
196 | * "filled_size": "0.00000000",
197 | * "filled_notional": "0.0000000000000000",
198 | * "status": "open"
199 | * }
200 | */
201 | Long time = t.getDate("timestamp").getTime();
202 | String id = t.getString("order_id");
203 | Side side = Side.valueOf(t.getString("side").toUpperCase());
204 | Type type = Type.valueOf(t.getString("type").toUpperCase());
205 | State state = null;
206 | switch (t.getString("status")) {
207 | case "open":
208 | state = State.SUBMIT;
209 | break;
210 | case "part_filled":
211 | state = State.PARTIAL;
212 | break;
213 | case "canceling":
214 | state = State.CANCEL;
215 | break;
216 | case "filled":
217 | state = State.FILLED;
218 | break;
219 | case "cancelled":
220 | state = State.CANCEL;
221 | break;
222 | case "failure":
223 | state = State.CANCEL;
224 | break;
225 | case "ordering":
226 | state = State.SUBMIT;
227 | break;
228 | default:
229 | }
230 | BigDecimal price = t.getBigDecimal("price");
231 | BigDecimal amount = t.getBigDecimal("size");
232 | BigDecimal deal = t.getBigDecimal("filled_size");
233 | BigDecimal average = ZERO;
234 | if (greater(deal, ZERO)) {
235 | average = div(t.getBigDecimal("filled_notional"), deal, 8, RoundingMode.HALF_UP);
236 | if (State.CANCEL.equals(state)) {
237 | state = State.UNDONE;
238 | }
239 | }
240 | return new Order(result, time, id, side, type, state, price, amount, deal, average);
241 | }
242 |
243 | /**
244 | * 统一解析orders
245 | * @param r json
246 | * @return orders
247 | */
248 | public static Orders parseOrders(JSONArray r) {
249 | List orders = new LinkedList<>();
250 | for (int i = 0; i< r.size(); i++) {
251 | orders.add(Okexv3Util.parseOrder(r.getString(i), r.getJSONObject(i)));
252 | }
253 | return new Orders(orders);
254 | }
255 | /**
256 | * WebSocket解压缩函数
257 | * @param bytes 压缩字符串
258 | * @return 解压缩后结果
259 | */
260 | public static String uncompress(ByteString bytes) {
261 | try (final ByteArrayOutputStream out = new ByteArrayOutputStream();
262 | final ByteArrayInputStream in = new ByteArrayInputStream(bytes.toByteArray());
263 | final Deflate64CompressorInputStream zin = new Deflate64CompressorInputStream(in)) {
264 | final byte[] buffer = new byte[1024];
265 | int offset;
266 | while (-1 != (offset = zin.read(buffer))) {
267 | out.write(buffer, 0, offset);
268 | }
269 | return out.toString();
270 | } catch (final IOException e) {
271 | throw new RuntimeException(e);
272 | }
273 | }
274 | }
275 |
--------------------------------------------------------------------------------
/engine/exchange/src/main/java/cqt/goai/exchange/web/socket/BaseWebSocketClient.java:
--------------------------------------------------------------------------------
1 | package cqt.goai.exchange.web.socket;
2 |
3 | import cqt.goai.exchange.util.RateLimit;
4 | import cqt.goai.model.enums.Period;
5 | import cqt.goai.model.market.Klines;
6 | import cqt.goai.model.market.Depth;
7 | import cqt.goai.model.market.Ticker;
8 | import cqt.goai.model.market.Trades;
9 | import cqt.goai.model.trade.Account;
10 | import cqt.goai.model.trade.Orders;
11 | import org.slf4j.Logger;
12 |
13 | import java.util.concurrent.ConcurrentHashMap;
14 | import java.util.function.Consumer;
15 |
16 | import static dive.common.util.Util.exist;
17 |
18 |
19 | /**
20 | * WebSocket连接
21 | * @author GOAi
22 | */
23 | public abstract class BaseWebSocketClient {
24 |
25 | /**
26 | * 日志
27 | */
28 | protected final Logger log;
29 |
30 | /**
31 | * 币对
32 | */
33 | protected final String symbol;
34 |
35 | /**
36 | * access
37 | */
38 | protected final String access;
39 |
40 | /**
41 | * secret
42 | */
43 | protected final String secret;
44 |
45 | /**
46 | * 连接ping的频率 本链接最近更新频率, 高于该频率不发送ping
47 | */
48 | protected final RateLimit limit;
49 |
50 | /**
51 | * 消费函数
52 | */
53 | private Consumer onTicker;
54 |
55 | private ConcurrentHashMap> onKlines = new ConcurrentHashMap<>();
56 |
57 | private Consumer onDepth;
58 |
59 | private Consumer onTrades;
60 |
61 | private Consumer onAccount;
62 |
63 | private Consumer onOrders;
64 |
65 | public BaseWebSocketClient(String symbol, String access, String secret, RateLimit limit, Logger log) {
66 | this.symbol = symbol;
67 | this.access = access;
68 | this.secret = secret;
69 | this.limit = limit;
70 | this.log = log;
71 | }
72 |
73 | /**
74 | * 第一次连接需要做的
75 | */
76 | public abstract void open();
77 |
78 | /**
79 | * 连接断开
80 | */
81 | public abstract void closed();
82 |
83 | /**
84 | * 发送message
85 | * @param message message
86 | */
87 | protected abstract void send(String message);
88 |
89 | /**
90 | * ping
91 | */
92 | public void ping() {
93 | if (this.limit.timeout(true)) {
94 | this.send("ping");
95 | }
96 | }
97 |
98 | /**
99 | * 处理收到的新信息
100 | * @param message 收到信息
101 | */
102 | protected abstract void transform(String message);
103 |
104 | /**
105 | * 主动断开
106 | * @param code code
107 | * @param reason reason
108 | */
109 | public abstract void close(int code, String reason);
110 |
111 | /**
112 | * 命令日志
113 | * @param command 命令
114 | * @param url url
115 | */
116 | protected void commandLog(String command, String url) {
117 | this.log.info("websocket send: {} to {}", command, url);
118 | }
119 | // ===================== ticker =====================
120 |
121 | /**
122 | * 注册推送Ticker
123 | * @param onTicker 推送函数
124 | */
125 | public void onTicker(Consumer onTicker) {
126 | this.onTicker = onTicker;
127 | this.askTicker();
128 | }
129 |
130 | /**
131 | * 发送订阅信息
132 | */
133 | protected abstract void askTicker();
134 |
135 | /**
136 | * 接收到Ticker后
137 | * @param ticker ticker
138 | */
139 | protected void onTicker(Ticker ticker) {
140 | if (exist(this.onTicker)) {
141 | this.onTicker.accept(ticker);
142 | }
143 | }
144 |
145 | /**
146 | * 取消推送Ticker
147 | */
148 | public abstract void noTicker();
149 |
150 | // ===================== klines =====================
151 |
152 | /**
153 | * 注册推送Klines
154 | * @param onKlines 推送函数
155 | * @param period 周期
156 | */
157 | public void onKlines(Consumer onKlines, Period period) {
158 | this.onKlines.put(period, onKlines);
159 | this.askKlines(period);
160 | }
161 |
162 | /**
163 | * 发送订阅信息
164 | * @param period 周期
165 | */
166 | protected abstract void askKlines(Period period);
167 |
168 | /**
169 | * 接收到Ticker后
170 | * @param klines klines
171 | * @param period 周期
172 | */
173 | protected void onKlines(Klines klines, Period period) {
174 | if (this.onKlines.containsKey(period)) {
175 | this.onKlines.get(period).accept(klines);
176 | }
177 | }
178 |
179 | /**
180 | * 取消推送Klines
181 | * @param period 周期
182 | */
183 | public abstract void noKlines(Period period);
184 |
185 | // ===================== depth =====================
186 |
187 | /**
188 | * 注册推送Depth
189 | * @param onDepth 推送函数
190 | */
191 | public void onDepth(Consumer onDepth) {
192 | this.onDepth = onDepth;
193 | this.askDepth();
194 | }
195 |
196 | /**
197 | * 发送订阅信息
198 | */
199 | protected abstract void askDepth();
200 |
201 | /**
202 | * 接收到Depth后
203 | * @param depth depth
204 | */
205 | protected void onDepth(Depth depth) {
206 | if (exist(this.onDepth)) {
207 | this.onDepth.accept(depth);
208 | }
209 | }
210 |
211 | /**
212 | * 取消推送Ticker
213 | */
214 | public abstract void noDepth();
215 |
216 |
217 | // ===================== trades =====================
218 |
219 | /**
220 | * 注册推送Trades
221 | * @param onTrades 推送函数
222 | */
223 | public void onTrades(Consumer onTrades) {
224 | this.onTrades = onTrades;
225 | this.askTrades();
226 | }
227 |
228 | /**
229 | * 发送订阅信息
230 | */
231 | protected abstract void askTrades();
232 |
233 | /**
234 | * 接收到Trades后
235 | * @param trades Trades
236 | */
237 | protected void onTrades(Trades trades) {
238 | if (exist(this.onTrades)) {
239 | this.onTrades.accept(trades);
240 | }
241 | }
242 |
243 | /**
244 | * 取消推送Trades
245 | */
246 | public abstract void noTrades();
247 |
248 |
249 | // ===================== account =====================
250 |
251 | /**
252 | * 注册推送Account
253 | * @param onAccount 推送函数
254 | */
255 | public void onAccount(Consumer onAccount) {
256 | this.onAccount = onAccount;
257 | this.askAccount();
258 | }
259 |
260 | /**
261 | * 发送订阅信息
262 | */
263 | protected abstract void askAccount();
264 |
265 | /**
266 | * 接收到Account后
267 | * @param account account
268 | */
269 | protected void onAccount(Account account) {
270 | if (exist(this.onAccount)) {
271 | this.onAccount.accept(account);
272 | }
273 | }
274 |
275 | /**
276 | * 取消推送Account
277 | */
278 | public abstract void noAccount();
279 |
280 | // ===================== orders =====================
281 |
282 | /**
283 | * 注册推送Orders
284 | * @param onOrders 推送函数
285 | */
286 | public void onOrders(Consumer onOrders) {
287 | this.onOrders = onOrders;
288 | this.askOrders();
289 | }
290 |
291 | /**
292 | * 发送订阅信息
293 | */
294 | protected abstract void askOrders();
295 |
296 | /**
297 | * 接收到Orders后
298 | * @param orders orders
299 | */
300 | protected void onOrders(Orders orders) {
301 | if (exist(this.onOrders)) {
302 | this.onOrders.accept(orders);
303 | }
304 | }
305 |
306 | /**
307 | * 取消推送Orders
308 | */
309 | public abstract void noOrders();
310 |
311 | }
312 |
--------------------------------------------------------------------------------
/engine/exchange/src/main/java/cqt/goai/exchange/web/socket/WebSocketExchange.java:
--------------------------------------------------------------------------------
1 | package cqt.goai.exchange.web.socket;
2 |
3 | import cqt.goai.exchange.BaseExchange;
4 | import cqt.goai.exchange.ExchangeException;
5 | import cqt.goai.exchange.ExchangeInfo;
6 | import cqt.goai.exchange.ExchangeName;
7 | import cqt.goai.model.enums.Period;
8 | import cqt.goai.model.market.Klines;
9 | import cqt.goai.model.market.Depth;
10 | import cqt.goai.model.market.Ticker;
11 | import cqt.goai.model.market.Trades;
12 | import cqt.goai.model.trade.Account;
13 | import cqt.goai.model.trade.Orders;
14 | import dive.common.math.RandomUtil;
15 | import org.slf4j.Logger;
16 |
17 | import java.util.concurrent.*;
18 | import java.util.function.Consumer;
19 |
20 | /**
21 | * WebSocket请求,每个交易所的WebSocket请求方式都要继承该类
22 | * @author GOAi
23 | */
24 | public class WebSocketExchange extends BaseExchange {
25 |
26 | /**
27 | * 每个连接加入ping的id,取消时用
28 | */
29 | private String id;
30 |
31 | /**
32 | * 需要ping的连接
33 | */
34 | private static final ConcurrentHashMap EXCHANGES = new ConcurrentHashMap<>();
35 |
36 | static {
37 | ScheduledExecutorService timer = new ScheduledThreadPoolExecutor(1, new ThreadPoolExecutor.DiscardPolicy());
38 | timer.scheduleAtFixedRate(() -> EXCHANGES.values().stream().parallel().forEach(WebSocketExchange::ping), 7, 7, TimeUnit.SECONDS);
39 | }
40 |
41 | public WebSocketExchange(ExchangeName name, Logger log) {
42 | super(name, log);
43 | this.ping(this);
44 | }
45 |
46 | /**
47 | * 定时某些交易所需要定时向服务器发送ping
48 | */
49 | protected void ping() {}
50 |
51 | /**
52 | * 加入ping
53 | * @param e ping对象
54 | */
55 | private void ping(WebSocketExchange e) {
56 | if (null != this.id && EXCHANGES.containsKey(this.id)) {
57 | return;
58 | }
59 | this.id = RandomUtil.token();
60 | EXCHANGES.put(this.id, e);
61 | }
62 |
63 | /**
64 | * 取消ping
65 | */
66 | protected void noPing() {
67 | EXCHANGES.remove(this.id);
68 | }
69 |
70 | // =============== ticker ===============
71 |
72 | /**
73 | * 获取Ticker
74 | * @param info 请求信息
75 | * @param onTicker 消费函数
76 | * @return 订阅id
77 | */
78 | public boolean onTicker(ExchangeInfo info, Consumer onTicker) {
79 | throw new ExchangeException(super.name + " " + info.getSymbol() + " onTicker is not supported.");
80 | }
81 |
82 | public void noTicker(String pushId) {
83 | throw new ExchangeException(super.name + " noTicker is not supported. pushId: " + pushId);
84 | }
85 |
86 | // =============== klines ===============
87 |
88 | /**
89 | * 获取Klines
90 | * @param info 请求信息
91 | * @param onKlines 消费函数
92 | * @return 订阅id
93 | */
94 | public boolean onKlines(ExchangeInfo info, Consumer onKlines) {
95 | throw new ExchangeException(super.name + " " + info.getSymbol() + " onKlines " + info.getPeriod() + " is not supported.");
96 | }
97 |
98 | public void noKlines(Period period, String pushId) {
99 | throw new ExchangeException(super.name + " noKlines " + period + " is not supported. pushId: " + pushId);
100 | }
101 |
102 | // =============== depth ===============
103 |
104 | /**
105 | * 获取Depth
106 | * @param info 请求信息
107 | * @param onDepth 消费函数
108 | * @return 订阅id
109 | */
110 | public boolean onDepth(ExchangeInfo info, Consumer onDepth) {
111 | throw new ExchangeException(super.name + " " + info.getSymbol() + " onDepth is not supported.");
112 | }
113 |
114 | public void noDepth(String pushId) {
115 | throw new ExchangeException(super.name + " noDepth is not supported. pushId: " + pushId);
116 | }
117 |
118 | // =============== trades ===============
119 |
120 | /**
121 | * 获取Trades
122 | * @param info 请求信息
123 | * @param onTrades 消费函数
124 | * @return 订阅id
125 | */
126 | public boolean onTrades(ExchangeInfo info, Consumer onTrades) {
127 | throw new ExchangeException(super.name + " " + info.getSymbol() + " onTrades is not supported.");
128 | }
129 |
130 | public void noTrades(String pushId) {
131 | throw new ExchangeException(super.name + " noTrades is not supported. pushId: " + pushId);
132 | }
133 |
134 | // =============== account ===============
135 |
136 | /**
137 | * 获取Account
138 | * @param info 请求信息
139 | * @param onAccount 消费函数
140 | * @return 订阅id
141 | */
142 | public boolean onAccount(ExchangeInfo info, Consumer onAccount) {
143 | throw new ExchangeException(super.name + " " + info.getSymbol() + " onAccount is not supported.");
144 | }
145 |
146 | public void noAccount(String pushId) {
147 | throw new ExchangeException(super.name + " noAccount is not supported. pushId: " + pushId);
148 | }
149 |
150 | // =============== orders ===============
151 |
152 | /**
153 | * 获取Orders
154 | * @param info 请求信息
155 | * @param onOrders 消费函数
156 | * @return 订阅id
157 | */
158 | public boolean onOrders(ExchangeInfo info, Consumer onOrders) {
159 | throw new ExchangeException(super.name + " " + info.getSymbol() + " onOrders is not supported.");
160 | }
161 |
162 | public void noOrders(String pushId) {
163 | throw new ExchangeException(super.name + " noOrders is not supported. pushId: " + pushId);
164 | }
165 |
166 | }
167 |
--------------------------------------------------------------------------------
/engine/exchange/src/main/java/cqt/goai/exchange/web/socket/WebSocketInfo.java:
--------------------------------------------------------------------------------
1 | package cqt.goai.exchange.web.socket;
2 |
3 | import cqt.goai.exchange.Action;
4 | import cqt.goai.model.enums.Period;
5 | import lombok.Data;
6 |
7 | import java.util.Objects;
8 | import java.util.function.Consumer;
9 |
10 | /**
11 | * WebSocket推送封装的信息
12 | * @author GOAi
13 | */
14 | @Data
15 | public class WebSocketInfo {
16 |
17 | /**
18 | * 推送id
19 | */
20 | private String id;
21 |
22 | /**
23 | * 推送行为类型
24 | */
25 | private Action action;
26 |
27 | /**
28 | * 推送币对
29 | */
30 | private String symbol;
31 |
32 | /**
33 | * 推送用户
34 | */
35 | private String access;
36 |
37 | /**
38 | * 消费函数
39 | */
40 | private Consumer consumer;
41 |
42 | /**
43 | * 周期
44 | */
45 | private Period period;
46 |
47 | public WebSocketInfo(String id, Action action, String symbol, String access, Consumer consumer) {
48 | this.id = id;
49 | this.action = action;
50 | this.symbol = symbol;
51 | this.access = access;
52 | this.consumer = consumer;
53 | }
54 |
55 | public WebSocketInfo(String id, Action action, String symbol, String access, Consumer consumer, Period period) {
56 | this.id = id;
57 | this.action = action;
58 | this.symbol = symbol;
59 | this.access = access;
60 | this.consumer = consumer;
61 | this.period = period;
62 | }
63 |
64 | @Override
65 | public boolean equals(Object o) {
66 | if (this == o) {
67 | return true;
68 | }
69 | if (o == null || getClass() != o.getClass()) {
70 | return false;
71 | }
72 | WebSocketInfo> onInfo = (WebSocketInfo>) o;
73 | return Objects.equals(id, onInfo.id);
74 | }
75 |
76 | @Override
77 | public int hashCode() {
78 | return Objects.hash(id);
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/engine/exchange/src/test/java/test/RateLimitTest.java:
--------------------------------------------------------------------------------
1 | package test;
2 |
3 | import cqt.goai.exchange.util.RateLimit;
4 | import org.junit.Assert;
5 | import org.junit.Test;
6 |
7 | /**
8 | * @author GOAi
9 | */
10 | public class RateLimitTest {
11 |
12 | @Test
13 | public void test() throws InterruptedException {
14 | RateLimit NO = RateLimit.NO;
15 | for (int i = 0; i < 10000; i++) {
16 | // 次次需要更新
17 | Assert.assertTrue(NO.timeout(false));
18 | }
19 |
20 | RateLimit limit = RateLimit.limit(300);
21 | Assert.assertFalse(limit.timeout()); // 300毫秒之内,不需要推送
22 | Thread.sleep(200);
23 | Assert.assertFalse(limit.timeout()); // 300毫秒之内,不需要推送
24 | Thread.sleep(200);
25 | Assert.assertTrue(limit.timeout()); // 300毫秒之外,需要推送
26 | Assert.assertFalse(limit.timeout()); // 300毫秒之内,不需要推送
27 |
28 | }
29 |
30 | }
31 |
--------------------------------------------------------------------------------
/engine/model/build.gradle:
--------------------------------------------------------------------------------
1 | jar {
2 | enabled = true
3 | baseName = 'goai-model'
4 | version = '1.0.0'
5 | }
6 |
7 | dependencies {
8 | testCompile 'junit:junit:4.12'
9 | testCompile 'org.slf4j:slf4j-log4j12:1.7.25'
10 |
11 | compile 'org.slf4j:slf4j-api:1.7.25'
12 | compile 'com.alibaba:fastjson:1.2.49'
13 | }
--------------------------------------------------------------------------------
/engine/model/src/main/java/cqt/goai/model/BaseModelList.java:
--------------------------------------------------------------------------------
1 | package cqt.goai.model;
2 |
3 | import lombok.EqualsAndHashCode;
4 | import lombok.Getter;
5 |
6 | import java.io.Serializable;
7 | import java.util.Iterator;
8 | import java.util.List;
9 | import java.util.function.Consumer;
10 | import java.util.stream.Stream;
11 |
12 | /**
13 | * 处理list情况
14 | *
15 | * 封装基本list,额外添加 to 和 of 方法
16 | * Iterator Pattern 迭代器模式
17 | *
18 | * @author GOAi
19 | * @param list的对象
20 | */
21 | @Getter
22 | @EqualsAndHashCode
23 | public abstract class BaseModelList implements To, Iterable, Serializable {
24 | private static final long serialVersionUID = 1L;
25 | /**
26 | * 底层数据
27 | */
28 | private List list;
29 |
30 | /**
31 | * 构造器传入list对象,可选择ArrayList或LinkedList,看情况选择
32 | * @param list list
33 | */
34 | public BaseModelList(List list) {
35 | this.list = list;
36 | }
37 |
38 | @Override
39 | public String to() {
40 | return Util.to(this.list);
41 | }
42 |
43 |
44 | public int size() {
45 | return this.list.size();
46 | }
47 |
48 | public boolean isEmpty() {
49 | return this.list.isEmpty();
50 | }
51 |
52 | public boolean contains(E e) {
53 | return this.list.contains(e);
54 | }
55 |
56 | @Override
57 | public Iterator iterator() {
58 | return this.list.iterator();
59 | }
60 |
61 | public Object[] toArray() {
62 | return this.list.toArray();
63 | }
64 |
65 | public E[] toArray(E[] a) {
66 | return this.list.toArray(a);
67 | }
68 |
69 | public E get(int index) {
70 | return this.list.get(index);
71 | }
72 |
73 | public E remove(int index) {
74 | return this.list.remove(index);
75 | }
76 |
77 | public int indexOf(E e) {
78 | return this.list.indexOf(e);
79 | }
80 |
81 | public int lastIndexOf(E e) {
82 | return this.list.lastIndexOf(e);
83 | }
84 |
85 | public Stream stream() {
86 | return this.list.stream();
87 | }
88 |
89 | public Stream parallelStream() {
90 | return this.list.parallelStream();
91 | }
92 |
93 | public E first() {
94 | return this.get(0);
95 | }
96 |
97 | public E last() {
98 | return this.get(this.size() - 1);
99 | }
100 |
101 | public E getFirst() {
102 | return this.first();
103 | }
104 |
105 | public E getLast() {
106 | return this.last();
107 | }
108 |
109 | public void add(E e) {
110 | this.list.add(e);
111 | }
112 |
113 | public void set(List eList) {
114 | this.list = eList;
115 | }
116 |
117 | @Override
118 | public String toString() {
119 | return list.toString();
120 | }
121 | }
122 |
--------------------------------------------------------------------------------
/engine/model/src/main/java/cqt/goai/model/To.java:
--------------------------------------------------------------------------------
1 | package cqt.goai.model;
2 |
3 | /**
4 | * 对象转换
5 | * @author GOAi
6 | */
7 | public interface To {
8 |
9 | /**
10 | * 转字符串
11 | * @return 字符串
12 | */
13 | String to();
14 |
15 | }
16 |
--------------------------------------------------------------------------------
/engine/model/src/main/java/cqt/goai/model/Util.java:
--------------------------------------------------------------------------------
1 | package cqt.goai.model;
2 |
3 | import com.alibaba.fastjson.JSON;
4 | import com.alibaba.fastjson.JSONArray;
5 | import cqt.goai.model.trade.Balance;
6 | import org.slf4j.Logger;
7 |
8 | import java.math.BigDecimal;
9 | import java.util.ArrayList;
10 | import java.util.Base64;
11 | import java.util.List;
12 | import java.util.function.Function;
13 | import java.util.stream.Collectors;
14 |
15 | /**
16 | * 工具类
17 | * @author GOAi
18 | */
19 | public class Util {
20 |
21 | /**
22 | * 去除末尾的0, 保存时应该检查,有些交易所的数据末尾0乱七八糟的,不如都不要
23 | * @param number 待结尾数字
24 | * @return 结尾后数字
25 | */
26 | public static BigDecimal strip(BigDecimal number) {
27 | return null != number ?
28 | new BigDecimal(number.stripTrailingZeros().toPlainString()) : null;
29 | }
30 |
31 | /**
32 | * 处理Long数据
33 | */
34 | public static String to(Long time) {
35 | if (null == time) {
36 | return "null";
37 | }
38 | return String.valueOf(time);
39 | }
40 |
41 | /**
42 | * 处理BigDecimal数据
43 | */
44 | public static String to(BigDecimal number) {
45 | if (null == number) {
46 | return "null";
47 | }
48 | return number.toPlainString();
49 | }
50 |
51 | /**
52 | * 处理String数据
53 | */
54 | public static String to(String content) {
55 | if (null == content) {
56 | return "null";
57 | }
58 | return '\"' + content + '\"';
59 | }
60 |
61 | /**
62 | * 处理ModelList数据
63 | */
64 | public static String to(BaseModelList list) {
65 | if (null == list) {
66 | return "null";
67 | }
68 | return list.to();
69 | }
70 |
71 | /**
72 | * 处理枚举数据
73 | */
74 | public static String to(Enum e) {
75 | if (null == e) {
76 | return "null";
77 | }
78 | return '\"' + e.name() + '\"';
79 | }
80 |
81 | /**
82 | * 处理整型数据
83 | */
84 | public static String to(Integer number) {
85 | if (null == number) {
86 | return "null";
87 | }
88 | return String.valueOf(number);
89 | }
90 |
91 | /**
92 | * 处理Balance数据
93 | */
94 | public static String to(Balance balance) {
95 | if (null == balance) {
96 | return "null";
97 | }
98 | return balance.to();
99 | }
100 |
101 | /**
102 | * list 转换字符串数组
103 | * @param list list
104 | * @param 元素类型
105 | * @return 字符串数组
106 | */
107 | public static String to(List list) {
108 | if (null == list) {
109 | return null;
110 | }
111 | if (0 == list.size()) {
112 | return "[]";
113 | }
114 | return '[' +
115 | list.stream()
116 | .map(To::to)
117 | .collect(Collectors.joining(",")) +
118 | ']';
119 | }
120 |
121 | /**
122 | * data数据传输时变成Base64编码
123 | * @param data 原始数据
124 | * @return Base64编码
125 | */
126 | public static String encode(String data) {
127 | return null == data || "null".equals(data) ?
128 | "null" : ('\"' + Base64.getEncoder().encodeToString(data.getBytes()) + '\"');
129 | }
130 |
131 | /**
132 | * data数据存入时解码
133 | * @param data Base64编码
134 | * @return 原始数据
135 | */
136 | public static String decode(String data) {
137 | return null == data || "null".equals(data) ?
138 | null : new String(Base64.getDecoder().decode(data.getBytes()));
139 | }
140 |
141 | /**
142 | * 单个值还原
143 | * @param data 文本数据
144 | * @param of 转化方法
145 | * @param 结果对象
146 | * @return 转化结果
147 | */
148 | public static E of(String data, Function of) {
149 | return null == data || "null".equals(data) ? null : of.apply(data);
150 | }
151 |
152 | /**
153 | * 可能组合成ModelList的元素,在上级就已经转变成JSONArray了
154 | * 本身的of(String data)不能使用,则重载个of(JSONArray r)方法
155 | * @param data 数组数据
156 | * @param of 解析JSONArray对象
157 | * @param log 错误输出
158 | * @return 解析结果
159 | */
160 | public static T of(String data, Function of, Logger log) {
161 | try {
162 | return of.apply(JSON.parseArray(data));
163 | } catch (Exception e) {
164 | e.printStackTrace();
165 | log.error(e.getMessage());
166 | log.error("of error -> {}", data);
167 | }
168 | return null;
169 | }
170 |
171 | /**
172 | * 解析list数据并添加到ModelList对象中
173 | * @param data 原始数据
174 | * @param fresh 构造新的实例
175 | * @param of JSONArray解析of
176 | * @param log 错误输出
177 | * @param 元素对象
178 | * @return 解析成功与否
179 | */
180 | public static T of(String data, Function, T> fresh,
181 | Function of, Logger log) {
182 | try {
183 | JSONArray r = JSON.parseArray(data);
184 | return Util.of(r, fresh, of, log);
185 | } catch (Exception e) {
186 | e.printStackTrace();
187 | log.error(e.getMessage());
188 | log.error("of error -> {}", data);
189 | }
190 | return null;
191 | }
192 |
193 | /**
194 | * 解析JSONArray数据并添加到ModelList对象中
195 | * @param r JSONArray数据
196 | * @param fresh 构造新的实例
197 | * @param of JSONArray解析of
198 | * @param log 错误输出
199 | * @param 元素对象
200 | * @return 解析成功与否
201 | */
202 | public static T of(JSONArray r, Function, T> fresh,
203 | Function of, Logger log) {
204 | if (null == r) {
205 | return null;
206 | }
207 | try {
208 | List list = new ArrayList<>(r.size());
209 | for (int i = 0; i < r.size(); i++) {
210 | list.add(of.apply(r.getJSONArray(i)));
211 | }
212 | return fresh.apply(list);
213 | } catch (Exception e) {
214 | e.printStackTrace();
215 | log.error(e.getMessage());
216 | log.error("of error -> {}", r);
217 | }
218 | return null;
219 | }
220 |
221 | }
222 |
--------------------------------------------------------------------------------
/engine/model/src/main/java/cqt/goai/model/enums/Period.java:
--------------------------------------------------------------------------------
1 | package cqt.goai.model.enums;
2 |
3 | /**
4 | * 周期,K线需要使用
5 | * @author GOAi
6 | */
7 | public enum Period {
8 | // 1分钟
9 | MIN1 (60),
10 | // 3分钟
11 | MIN3 (60 * 3),
12 | // 5分钟
13 | MIN5 (60 * 5),
14 | // 15分钟
15 | MIN15 (60 * 15),
16 | // 30分钟
17 | MIN30 (60 * 30),
18 | // 1小时
19 | HOUR1 (60 * 60),
20 | // 2小时
21 | HOUR2 (60 * 60 * 2),
22 | // 3小时
23 | HOUR3 (60 * 60 * 3),
24 | // 4小时
25 | HOUR4 (60 * 60 * 4),
26 | // 6小时
27 | HOUR6 (60 * 60 * 6),
28 | // 12小时
29 | HOUR12 (60 * 60 * 12),
30 | // 1天
31 | DAY1 (60 * 60 * 24),
32 | // 3天
33 | DAY3 (60 * 60 * 24 * 3),
34 | // 1周
35 | WEEK1 (60 * 60 * 24 * 7),
36 | // 2周
37 | WEEK2 (60 * 60 * 24 * 14),
38 | // 1月
39 | MONTH1 (60 * 60 * 24 * 30);
40 |
41 | private int value;
42 |
43 | Period(int value) {
44 | this.value = value;
45 | }
46 |
47 | public int getValue() {
48 | return value;
49 | }
50 |
51 | /**
52 | * 根据值解析周期
53 | * @param value 秒
54 | * @return 周期
55 | */
56 | public static Period parse(int value) {
57 | switch (value) {
58 | case 60:
59 | return MIN1;
60 | case 60 * 3:
61 | return MIN3;
62 | case 60 * 5:
63 | return MIN5;
64 | case 60 * 15:
65 | return MIN15;
66 | case 60 * 30:
67 | return MIN30;
68 | case 60 * 60:
69 | return HOUR1;
70 | case 60 * 60 * 2:
71 | return HOUR2;
72 | case 60 * 60 * 3:
73 | return HOUR3;
74 | case 60 * 60 * 4:
75 | return HOUR4;
76 | case 60 * 60 * 6:
77 | return HOUR6;
78 | case 60 * 60 * 12:
79 | return HOUR12;
80 | case 60 * 60 * 24:
81 | return DAY1;
82 | case 60 * 60 * 24 * 3:
83 | return DAY3;
84 | case 60 * 60 * 24 * 7:
85 | return WEEK1;
86 | case 60 * 60 * 24 * 14:
87 | return WEEK2;
88 | case 60 * 60 * 24 * 30:
89 | return MONTH1;
90 | default:
91 | return null;
92 | }
93 | }
94 | }
95 |
--------------------------------------------------------------------------------
/engine/model/src/main/java/cqt/goai/model/enums/Side.java:
--------------------------------------------------------------------------------
1 | package cqt.goai.model.enums;
2 |
3 | /**
4 | * 订单方向
5 | * @author GOAi
6 | */
7 | public enum Side {
8 | // 买
9 | BUY,
10 | // 卖
11 | SELL,
12 | }
13 |
--------------------------------------------------------------------------------
/engine/model/src/main/java/cqt/goai/model/enums/State.java:
--------------------------------------------------------------------------------
1 | package cqt.goai.model.enums;
2 |
3 | /**
4 | * 订单状态
5 | * @author GOAi
6 | */
7 | public enum State {
8 | // 已提交
9 | SUBMIT,
10 | // 已成交
11 | FILLED,
12 | // 已取消
13 | CANCEL,
14 | // 部分成交
15 | PARTIAL,
16 | // 部分成交后取消
17 | UNDONE,
18 |
19 | }
--------------------------------------------------------------------------------
/engine/model/src/main/java/cqt/goai/model/enums/Type.java:
--------------------------------------------------------------------------------
1 | package cqt.goai.model.enums;
2 |
3 | /**
4 | * 订单类型
5 | * @author GOAi
6 | */
7 | public enum Type {
8 | // 限价单
9 | LIMIT,
10 | // 市价单
11 | MARKET,
12 | // FOK
13 | FILL_OR_KILL,
14 | // IOC
15 | IMMEDIATE_OR_CANCEL,
16 | }
17 |
--------------------------------------------------------------------------------
/engine/model/src/main/java/cqt/goai/model/market/Depth.java:
--------------------------------------------------------------------------------
1 | package cqt.goai.model.market;
2 |
3 | import com.alibaba.fastjson.JSON;
4 | import com.alibaba.fastjson.JSONObject;
5 | import cqt.goai.model.To;
6 | import cqt.goai.model.Util;
7 | import lombok.EqualsAndHashCode;
8 | import lombok.Getter;
9 | import lombok.ToString;
10 | import org.slf4j.Logger;
11 |
12 | import java.io.Serializable;
13 |
14 | /**
15 | * 盘口深度
16 | * @author GOAi
17 | */
18 | @Getter
19 | @ToString
20 | @EqualsAndHashCode
21 | public class Depth implements To, Serializable {
22 |
23 | private static final long serialVersionUID = -8133481259875889084L;
24 |
25 | /**
26 | * 时间戳
27 | */
28 | private final Long time;
29 |
30 | /**
31 | * 卖盘数据,由低到高
32 | */
33 | private final Rows asks;
34 |
35 | /**
36 | * 买盘数据,由高到低
37 | */
38 | private final Rows bids;
39 |
40 | public Depth(Long time, Rows asks, Rows bids) {
41 | this.time = time;
42 | this.asks = asks;
43 | this.bids = bids;
44 | }
45 |
46 | @Override
47 | public String to() {
48 | return '{' +
49 | "\"time\":" + Util.to(this.time) +
50 | ",\"asks\":" + Util.to(this.asks) +
51 | ",\"bids\":" + Util.to(this.bids) +
52 | '}';
53 | }
54 |
55 | public static Depth of(String data, Logger log) {
56 | try {
57 | JSONObject r = JSON.parseObject(data);
58 | return new Depth(
59 | r.getLong("time"),
60 | Rows.of(r.getJSONArray("asks"), log),
61 | Rows.of(r.getJSONArray("bids"), log));
62 | } catch (Exception e) {
63 | e.printStackTrace();
64 | log.error(e.getMessage());
65 | log.error("of error -> {}", data);
66 | }
67 | return null;
68 | }
69 |
70 | }
71 |
--------------------------------------------------------------------------------
/engine/model/src/main/java/cqt/goai/model/market/Kline.java:
--------------------------------------------------------------------------------
1 | package cqt.goai.model.market;
2 |
3 | import com.alibaba.fastjson.JSONArray;
4 | import cqt.goai.model.To;
5 | import cqt.goai.model.Util;
6 | import lombok.EqualsAndHashCode;
7 | import lombok.Getter;
8 | import lombok.ToString;
9 | import org.slf4j.Logger;
10 |
11 | import java.io.Serializable;
12 | import java.math.BigDecimal;
13 | import java.util.Objects;
14 |
15 | /**
16 | * K线信息
17 | * @author GOAi
18 | */
19 | @Getter
20 | @ToString
21 | @EqualsAndHashCode
22 | public class Kline implements To, Serializable {
23 |
24 | private static final long serialVersionUID = 3739670180018865992L;
25 |
26 | /**
27 | * 交易所返回的原始数据, 防止有些用户需要一些特殊的数据
28 | */
29 | private final String data;
30 |
31 | /**
32 | * 时间, 精确到秒
33 | */
34 | private final Long time;
35 |
36 | /**
37 | * 开盘价
38 | */
39 | private final BigDecimal open;
40 |
41 | /**
42 | * 最高价
43 | */
44 | private final BigDecimal high;
45 |
46 | /**
47 | * 最低价
48 | */
49 | private final BigDecimal low;
50 |
51 | /**
52 | * 收盘价
53 | */
54 | private final BigDecimal close;
55 |
56 | /**
57 | * 交易量
58 | */
59 | private final BigDecimal volume;
60 |
61 | public Kline(String data, Long time, BigDecimal open, BigDecimal high,
62 | BigDecimal low, BigDecimal close, BigDecimal volume) {
63 | this.data = data;
64 | this.time = time;
65 | this.open = Util.strip(open);
66 | this.high = Util.strip(high);
67 | this.low = Util.strip(low);
68 | this.close = Util.strip(close);
69 | this.volume = Util.strip(volume);
70 | }
71 |
72 | @Override
73 | public String to() {
74 | return '[' +
75 | Util.encode(this.data) +
76 | ',' + Util.to(this.time) +
77 | ',' + Util.to(this.open) +
78 | ',' + Util.to(this.high) +
79 | ',' + Util.to(this.low) +
80 | ',' + Util.to(this.close) +
81 | ',' + Util.to(this.volume) +
82 | ']';
83 | }
84 |
85 | public double open() {
86 | Objects.requireNonNull(this.open);
87 | return this.open.doubleValue();
88 | }
89 |
90 | public double high() {
91 | Objects.requireNonNull(this.high);
92 | return this.high.doubleValue();
93 | }
94 |
95 | public double low() {
96 | Objects.requireNonNull(this.low);
97 | return this.low.doubleValue();
98 | }
99 |
100 | public double close() {
101 | Objects.requireNonNull(this.close);
102 | return this.close.doubleValue();
103 | }
104 |
105 | public double volume() {
106 | Objects.requireNonNull(this.volume);
107 | return this.volume.doubleValue();
108 | }
109 |
110 | public static Kline of(String data, Logger log) {
111 | return Util.of(data, Kline::of, log);
112 | }
113 |
114 | static Kline of(JSONArray r) {
115 | return new Kline(
116 | Util.decode(r.getString(0)),
117 | r.getLong(1),
118 | r.getBigDecimal(2),
119 | r.getBigDecimal(3),
120 | r.getBigDecimal(4),
121 | r.getBigDecimal(5),
122 | r.getBigDecimal(6));
123 | }
124 |
125 | }
126 |
--------------------------------------------------------------------------------
/engine/model/src/main/java/cqt/goai/model/market/Klines.java:
--------------------------------------------------------------------------------
1 | package cqt.goai.model.market;
2 |
3 | import cqt.goai.model.BaseModelList;
4 | import cqt.goai.model.Util;
5 | import org.slf4j.Logger;
6 |
7 | import java.io.Serializable;
8 | import java.util.List;
9 |
10 | /**
11 | * 多根K线,按时间顺序由近到远 get(0)是最近的K线
12 | * @author GOAi
13 | */
14 | public class Klines extends BaseModelList implements Serializable {
15 |
16 | private static final long serialVersionUID = 3954815101761999805L;
17 |
18 | public Klines(List list) {
19 | super(list);
20 | }
21 |
22 | public static Klines of(String data, Logger log) {
23 | return Util.of(data, Klines::new, Kline::of, log);
24 | }
25 |
26 | @Override
27 | public String toString() {
28 | return "Klines" + super.toString();
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/engine/model/src/main/java/cqt/goai/model/market/Row.java:
--------------------------------------------------------------------------------
1 | package cqt.goai.model.market;
2 |
3 | import com.alibaba.fastjson.JSONArray;
4 | import cqt.goai.model.To;
5 | import cqt.goai.model.Util;
6 | import lombok.EqualsAndHashCode;
7 | import lombok.Getter;
8 | import lombok.ToString;
9 | import org.slf4j.Logger;
10 |
11 | import java.io.Serializable;
12 | import java.math.BigDecimal;
13 | import java.util.Objects;
14 |
15 | /**
16 | * 档位 价格 和 数量
17 | * @author GOAi
18 | */
19 | @Getter
20 | @ToString
21 | @EqualsAndHashCode
22 | public class Row implements To, Serializable {
23 |
24 | private static final long serialVersionUID = -5070260547441227713L;
25 |
26 | /**
27 | * 交易所返回的原始数据, 防止有些用户需要一些特殊的数据
28 | */
29 | private final String data;
30 |
31 | /**
32 | * 价格
33 | */
34 | private final BigDecimal price;
35 |
36 | /**
37 | * 数量
38 | */
39 | private final BigDecimal amount;
40 |
41 | public Row(String data, BigDecimal price, BigDecimal amount) {
42 | this.data = data;
43 | this.price = Util.strip(price);
44 | this.amount = Util.strip(amount);
45 | }
46 |
47 | @Override
48 | public String to() {
49 | return '[' +
50 | Util.encode(this.data) +
51 | ',' + Util.to(this.price) +
52 | ',' + Util.to(this.amount) +
53 | ']';
54 | }
55 |
56 | public double price() {
57 | Objects.requireNonNull(this.price);
58 | return this.price.doubleValue();
59 | }
60 |
61 | public double amount() {
62 | Objects.requireNonNull(this.amount);
63 | return this.amount.doubleValue();
64 | }
65 |
66 | public static Row of(String data, Logger log) {
67 | return Util.of(data, Row::of, log);
68 | }
69 |
70 | static Row of(JSONArray r) {
71 | return new Row(
72 | Util.decode(r.getString(0)),
73 | r.getBigDecimal(1),
74 | r.getBigDecimal(2));
75 | }
76 |
77 | public static Row row(BigDecimal price, BigDecimal amount) {
78 | return new Row(null, price, amount);
79 | }
80 |
81 | }
82 |
--------------------------------------------------------------------------------
/engine/model/src/main/java/cqt/goai/model/market/Rows.java:
--------------------------------------------------------------------------------
1 | package cqt.goai.model.market;
2 |
3 | import com.alibaba.fastjson.JSONArray;
4 | import cqt.goai.model.BaseModelList;
5 | import cqt.goai.model.Util;
6 | import org.slf4j.Logger;
7 |
8 | import java.io.Serializable;
9 | import java.util.List;
10 |
11 | /**
12 | * 多个档位
13 | * @author GOAi
14 | */
15 | public class Rows extends BaseModelList implements Serializable {
16 |
17 | private static final long serialVersionUID = -7432880663696816431L;
18 |
19 | public Rows(List list) {
20 | super(list);
21 | }
22 |
23 | public static Rows of(String data, Logger log) {
24 | return Util.of(data, Rows::new, Row::of, log);
25 | }
26 |
27 | static Rows of(JSONArray r, Logger log) {
28 | return Util.of(r, Rows::new, Row::of, log);
29 | }
30 |
31 | @Override
32 | public String toString() {
33 | return "Rows" + super.toString();
34 | }
35 |
36 | }
37 |
--------------------------------------------------------------------------------
/engine/model/src/main/java/cqt/goai/model/market/Ticker.java:
--------------------------------------------------------------------------------
1 | package cqt.goai.model.market;
2 |
3 | import com.alibaba.fastjson.JSON;
4 | import com.alibaba.fastjson.JSONObject;
5 | import cqt.goai.model.To;
6 | import cqt.goai.model.Util;
7 | import lombok.EqualsAndHashCode;
8 | import lombok.Getter;
9 | import lombok.ToString;
10 | import org.slf4j.Logger;
11 |
12 | import java.io.Serializable;
13 | import java.math.BigDecimal;
14 | import java.util.Objects;
15 |
16 | /**
17 | * Ticker信息
18 | *
19 | * Prototype Pattern 原型模式
20 | * 本来想用原型模式,每个推送都clone一份
21 | * 最后决定还是用不可变类,这样并发更好
22 | *
23 | * @author GOAi
24 | */
25 | @Getter
26 | @ToString
27 | @EqualsAndHashCode
28 | public class Ticker implements To, Serializable {
29 |
30 | private static final long serialVersionUID = 8856908960657104056L;
31 |
32 | /**
33 | * 交易所返回的原始数据, 防止有些用户需要一些特殊的数据
34 | */
35 | private final String data;
36 |
37 | /**
38 | * 时间戳
39 | */
40 | private final Long time;
41 |
42 | /**
43 | * 开盘价
44 | */
45 | private final BigDecimal open;
46 |
47 | /**
48 | * 最高价
49 | */
50 | private final BigDecimal high;
51 |
52 | /**
53 | * 最低价
54 | */
55 | private final BigDecimal low;
56 |
57 | /**
58 | * 收盘价
59 | */
60 | private final BigDecimal last;
61 |
62 | /**
63 | * 交易量
64 | */
65 | private final BigDecimal volume;
66 |
67 | public Ticker(String data, Long time, BigDecimal open, BigDecimal high,
68 | BigDecimal low, BigDecimal last, BigDecimal volume) {
69 | this.data = data;
70 | this.time = time;
71 | this.open = Util.strip(open);
72 | this.high = Util.strip(high);
73 | this.low = Util.strip(low);
74 | this.last = Util.strip(last);
75 | this.volume = Util.strip(volume);
76 | }
77 |
78 | @Override
79 | public String to() {
80 | return '{' +
81 | "\"data\":" + Util.encode(this.data) +
82 | ",\"time\":" + Util.to(this.time) +
83 | ",\"open\":" + Util.to(this.open) +
84 | ",\"high\":" + Util.to(this.high) +
85 | ",\"low\":" + Util.to(this.low) +
86 | ",\"last\":" + Util.to(this.last) +
87 | ",\"volume\":" + Util.to(this.volume) +
88 | '}';
89 | }
90 |
91 | public double open() {
92 | Objects.requireNonNull(this.open);
93 | return this.open.doubleValue();
94 | }
95 |
96 | public double high() {
97 | Objects.requireNonNull(this.high);
98 | return this.high.doubleValue();
99 | }
100 |
101 | public double low() {
102 | Objects.requireNonNull(this.low);
103 | return this.low.doubleValue();
104 | }
105 |
106 | public double last() {
107 | Objects.requireNonNull(this.last);
108 | return this.last.doubleValue();
109 | }
110 |
111 | public double volume() {
112 | Objects.requireNonNull(this.volume);
113 | return this.volume.doubleValue();
114 | }
115 |
116 | public static Ticker of(String data, Logger log) {
117 | try {
118 | JSONObject r = JSON.parseObject(data);
119 | return new Ticker(
120 | Util.decode(r.getString("data")),
121 | r.getLong("time"),
122 | r.getBigDecimal("open"),
123 | r.getBigDecimal("high"),
124 | r.getBigDecimal("low"),
125 | r.getBigDecimal("last"),
126 | r.getBigDecimal("volume"));
127 | } catch (Exception e) {
128 | e.printStackTrace();
129 | log.error(e.getMessage());
130 | log.error("of error -> {}", data);
131 | }
132 | return null;
133 | }
134 |
135 | }
136 |
--------------------------------------------------------------------------------
/engine/model/src/main/java/cqt/goai/model/market/Trade.java:
--------------------------------------------------------------------------------
1 | package cqt.goai.model.market;
2 |
3 | import com.alibaba.fastjson.JSONArray;
4 | import cqt.goai.model.To;
5 | import cqt.goai.model.Util;
6 | import cqt.goai.model.enums.Side;
7 | import lombok.EqualsAndHashCode;
8 | import lombok.Getter;
9 | import lombok.ToString;
10 | import org.slf4j.Logger;
11 |
12 | import java.io.Serializable;
13 | import java.math.BigDecimal;
14 | import java.util.Objects;
15 |
16 | /**
17 | * 成交信息
18 | * @author GOAi
19 | */
20 | @Getter
21 | @ToString
22 | @EqualsAndHashCode
23 | public class Trade implements To, Serializable {
24 |
25 | private static final long serialVersionUID = 514750454318716315L;
26 |
27 | /**
28 | * 交易所返回的原始数据, 防止有些用户需要一些特殊的数据
29 | */
30 | private final String data;
31 |
32 | /**
33 | * 时间戳
34 | */
35 | private final Long time;
36 |
37 | /**
38 | * 交易id
39 | */
40 | private final String id;
41 |
42 | /**
43 | * 方向
44 | */
45 | private final Side side;
46 |
47 | /**
48 | * 价格
49 | */
50 | private final BigDecimal price;
51 |
52 | /**
53 | * 数量
54 | */
55 | private final BigDecimal amount;
56 |
57 | public Trade(String data, Long time, String id, Side side, BigDecimal price, BigDecimal amount) {
58 | this.data = data;
59 | this.time = time;
60 | this.id = id;
61 | this.side = side;
62 | this.price = Util.strip(price);
63 | this.amount = Util.strip(amount);
64 | }
65 |
66 | @Override
67 | public String to() {
68 | return '[' +
69 | Util.encode(this.data) +
70 | ',' + Util.to(this.time) +
71 | ',' + Util.to(this.id) +
72 | ',' + Util.to(this.side) +
73 | ',' + Util.to(this.price) +
74 | ',' + Util.to(this.amount) +
75 | ']';
76 | }
77 |
78 | public double price() {
79 | Objects.requireNonNull(this.price);
80 | return this.price.doubleValue();
81 | }
82 |
83 | public double amount() {
84 | Objects.requireNonNull(this.amount);
85 | return this.amount.doubleValue();
86 | }
87 |
88 | public static Trade of(String data, Logger log) {
89 | return Util.of(data, Trade::of, log);
90 | }
91 |
92 | static Trade of(JSONArray r) {
93 | return new Trade(
94 | Util.decode(r.getString(0)),
95 | r.getLong(1),
96 | r.getString(2),
97 | Util.of(r.getString(3), Side::valueOf),
98 | r.getBigDecimal(4),
99 | r.getBigDecimal(5));
100 | }
101 |
102 | }
103 |
--------------------------------------------------------------------------------
/engine/model/src/main/java/cqt/goai/model/market/Trades.java:
--------------------------------------------------------------------------------
1 | package cqt.goai.model.market;
2 |
3 | import cqt.goai.model.BaseModelList;
4 | import cqt.goai.model.Util;
5 | import org.slf4j.Logger;
6 |
7 | import java.io.Serializable;
8 | import java.util.List;
9 |
10 | /**
11 | * 多个成交信息
12 | * @author GOAi
13 | */
14 | public class Trades extends BaseModelList implements Serializable {
15 |
16 | private static final long serialVersionUID = -5998165404968198947L;
17 |
18 | public Trades(List list) {
19 | super(list);
20 | }
21 |
22 | public static Trades of(String data, Logger log) {
23 | return Util.of(data, Trades::new, Trade::of, log);
24 | }
25 |
26 | @Override
27 | public String toString() {
28 | return "Trades" + super.toString();
29 | }
30 |
31 | }
32 |
--------------------------------------------------------------------------------
/engine/model/src/main/java/cqt/goai/model/other/RunInfo.java:
--------------------------------------------------------------------------------
1 | package cqt.goai.model.other;
2 |
3 | import lombok.Data;
4 |
5 | /**
6 | * 标记程序运行信息
7 | * run 落盘
8 | * node 读取
9 | * @author GOAi
10 | */
11 | @Data
12 | public class RunInfo {
13 |
14 | /**
15 | * 启动时间
16 | */
17 | private Long startTime;
18 |
19 | /**
20 | * 结束时间
21 | */
22 | private Long endTime;
23 |
24 | /**
25 | * 程序当前状态
26 | */
27 | private RunState runState;
28 |
29 | /**
30 | * 程序pid
31 | */
32 | private Integer pid;
33 |
34 | public RunInfo(Long startTime, RunState runState) {
35 | this.startTime = startTime;
36 | this.runState = runState;
37 | }
38 |
39 | public RunInfo setRunState(RunState runState) {
40 | this.runState = runState;
41 | return this;
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/engine/model/src/main/java/cqt/goai/model/other/RunState.java:
--------------------------------------------------------------------------------
1 | package cqt.goai.model.other;
2 |
3 | /**
4 | * 运行状态
5 | * STOPPED -> STARTING -> STARTED -> STOPPING -> STOPPED
6 | * @author GOAi
7 | */
8 | public enum RunState {
9 | /**
10 | * 程序停止运行
11 | */
12 | STOPPED,
13 | /**
14 | * 启动中
15 | */
16 | STARTING,
17 | /**
18 | * 启动完毕
19 | */
20 | STARTED,
21 | /**
22 | * 停止中
23 | */
24 | STOPPING,
25 |
26 | /**
27 | * 意外停止
28 | */
29 | ERROR
30 | }
31 |
--------------------------------------------------------------------------------
/engine/model/src/main/java/cqt/goai/model/trade/Account.java:
--------------------------------------------------------------------------------
1 | package cqt.goai.model.trade;
2 |
3 | import com.alibaba.fastjson.JSON;
4 | import com.alibaba.fastjson.JSONObject;
5 | import cqt.goai.model.To;
6 | import cqt.goai.model.Util;
7 | import lombok.EqualsAndHashCode;
8 | import lombok.Getter;
9 | import lombok.ToString;
10 | import org.slf4j.Logger;
11 |
12 | import java.io.Serializable;
13 | import java.math.BigDecimal;
14 | import java.util.Objects;
15 |
16 | /**
17 | * 某个币对账户信息
18 | * @author GOAi
19 | */
20 | @Getter
21 | @ToString
22 | @EqualsAndHashCode
23 | public class Account implements To, Serializable {
24 |
25 | private static final long serialVersionUID = -81334812598889084L;
26 |
27 | /**
28 | * 时间戳
29 | */
30 | private final Long time;
31 |
32 | /**
33 | * 目标货币 BTC_USD 中的 BTC, 用USD买卖BTC
34 | */
35 | private final Balance base;
36 |
37 | /**
38 | * 计价货币 BTC_USD 中的 USD
39 | */
40 | private final Balance quote;
41 |
42 | public Account(Long time, Balance base, Balance quote) {
43 | this.time = time;
44 | this.base = base;
45 | this.quote = quote;
46 | }
47 |
48 | public BigDecimal getBaseFree() {
49 | Objects.requireNonNull(base);
50 | return this.base.getFree();
51 | }
52 |
53 | public BigDecimal getBaseUsed() {
54 | Objects.requireNonNull(base);
55 | return this.base.getUsed();
56 | }
57 |
58 | public BigDecimal getQuoteFree() {
59 | Objects.requireNonNull(quote);
60 | return this.quote.getFree();
61 | }
62 |
63 | public BigDecimal getQuoteUsed() {
64 | Objects.requireNonNull(quote);
65 | return this.quote.getUsed();
66 | }
67 |
68 | public double baseFree() {
69 | Objects.requireNonNull(base);
70 | return this.base.free();
71 | }
72 |
73 | public double baseUsed() {
74 | Objects.requireNonNull(base);
75 | return this.base.used();
76 | }
77 |
78 | public double quoteFree() {
79 | Objects.requireNonNull(quote);
80 | return this.quote.free();
81 | }
82 |
83 | public double quoteUsed() {
84 | Objects.requireNonNull(quote);
85 | return this.quote.used();
86 | }
87 |
88 | @Override
89 | public String to() {
90 | return '{' +
91 | "\"time\":" + Util.to(this.time) +
92 | ",\"base\":" + Util.to(this.base) +
93 | ",\"quote\":" + Util.to(this.quote) +
94 | '}';
95 | }
96 |
97 | public static Account of(String data, Logger log) {
98 | try {
99 | JSONObject r = JSON.parseObject(data);
100 | return new Account(
101 | r.getLong("time"),
102 | Balance.of(r.getJSONArray("base")),
103 | Balance.of(r.getJSONArray("quote")));
104 | } catch (Exception e) {
105 | e.printStackTrace();
106 | log.error(e.getMessage());
107 | log.error("of error -> {}", data);
108 | }
109 | return null;
110 | }
111 |
112 | }
113 |
--------------------------------------------------------------------------------
/engine/model/src/main/java/cqt/goai/model/trade/Balance.java:
--------------------------------------------------------------------------------
1 | package cqt.goai.model.trade;
2 |
3 | import com.alibaba.fastjson.JSONArray;
4 | import cqt.goai.model.To;
5 | import cqt.goai.model.Util;
6 | import lombok.EqualsAndHashCode;
7 | import lombok.Getter;
8 | import lombok.ToString;
9 | import org.slf4j.Logger;
10 |
11 | import java.io.Serializable;
12 | import java.math.BigDecimal;
13 | import java.util.Objects;
14 |
15 | /**
16 | * 某个币的余额
17 | * @author GOAi
18 | */
19 | @Getter
20 | @ToString
21 | @EqualsAndHashCode
22 | public class Balance implements To, Serializable {
23 |
24 | private static final long serialVersionUID = -8006911245239428396L;
25 |
26 | /**
27 | * 交易所返回的原始数据, 防止有些用户需要一些特殊的数据
28 | */
29 | private final String data;
30 |
31 | /**
32 | * 币的名称 BTC USD 等
33 | */
34 | private final String currency;
35 |
36 | /**
37 | * 可用数量
38 | */
39 | private final BigDecimal free;
40 |
41 | /**
42 | * 冻结数量
43 | */
44 | private final BigDecimal used;
45 |
46 | public Balance(String data, String currency, BigDecimal free, BigDecimal used) {
47 | this.data = data;
48 | this.currency = currency;
49 | this.free = Util.strip(free);
50 | this.used = Util.strip(used);
51 | }
52 |
53 | @Override
54 | public String to() {
55 | return '[' +
56 | Util.encode(data) +
57 | "," + Util.to(currency) +
58 | "," + Util.to(free) +
59 | "," + Util.to(used) +
60 | ']';
61 | }
62 |
63 | public double free() {
64 | Objects.requireNonNull(this.free);
65 | return this.free.doubleValue();
66 | }
67 |
68 | public double used() {
69 | Objects.requireNonNull(this.used);
70 | return this.used.doubleValue();
71 | }
72 |
73 | public static Balance of(String data, Logger log) {
74 | return Util.of(data, Balance::of, log);
75 | }
76 |
77 | static Balance of(JSONArray r) {
78 | return new Balance(
79 | Util.decode(r.getString(0)),
80 | r.getString(1),
81 | r.getBigDecimal(2),
82 | r.getBigDecimal(3));
83 | }
84 | }
85 |
--------------------------------------------------------------------------------
/engine/model/src/main/java/cqt/goai/model/trade/Balances.java:
--------------------------------------------------------------------------------
1 | package cqt.goai.model.trade;
2 |
3 | import cqt.goai.model.BaseModelList;
4 | import cqt.goai.model.Util;
5 | import org.slf4j.Logger;
6 |
7 | import java.io.Serializable;
8 | import java.util.List;
9 |
10 | /**
11 | * 账户下所有币余额信息
12 | * @author GOAi
13 | */
14 | public class Balances extends BaseModelList implements Serializable {
15 |
16 | private static final long serialVersionUID = -58830328674451561L;
17 |
18 | public Balances(List list) {
19 | super(list);
20 | }
21 |
22 | public static Balances of(String data, Logger log) {
23 | return Util.of(data, Balances::new, Balance::of, log);
24 | }
25 |
26 | @Override
27 | public String toString() {
28 | return "Balances" + super.toString();
29 | }
30 |
31 | }
32 |
--------------------------------------------------------------------------------
/engine/model/src/main/java/cqt/goai/model/trade/Order.java:
--------------------------------------------------------------------------------
1 | package cqt.goai.model.trade;
2 |
3 | import com.alibaba.fastjson.JSONArray;
4 | import cqt.goai.model.To;
5 | import cqt.goai.model.Util;
6 | import cqt.goai.model.enums.Side;
7 | import cqt.goai.model.enums.State;
8 | import cqt.goai.model.enums.Type;
9 | import lombok.EqualsAndHashCode;
10 | import lombok.Getter;
11 | import lombok.ToString;
12 | import org.slf4j.Logger;
13 |
14 | import java.io.Serializable;
15 | import java.math.BigDecimal;
16 | import java.time.Instant;
17 | import java.time.LocalDateTime;
18 | import java.time.ZoneId;
19 | import java.time.format.DateTimeFormatter;
20 | import java.util.Objects;
21 |
22 | import static java.math.BigDecimal.ZERO;
23 |
24 | /**
25 | * 订单
26 | * @author GOAi
27 | */
28 | @Getter
29 | @ToString
30 | @EqualsAndHashCode
31 | public class Order implements To, Serializable {
32 |
33 | private static final long serialVersionUID = -298726083025069628L;
34 |
35 | /**
36 | * 交易所返回的原始数据, 防止有些用户需要一些特殊的数据
37 | */
38 | private final String data;
39 |
40 | /**
41 | * 时间戳
42 | */
43 | private final Long time;
44 |
45 | /**
46 | * 订单id
47 | */
48 | private final String id;
49 |
50 | /**
51 | * 买卖方向
52 | */
53 | private final Side side;
54 |
55 | /**
56 | * 订单类型
57 | */
58 | private final Type type;
59 |
60 | /**
61 | * 订单状态
62 | */
63 | private final State state;
64 |
65 | /**
66 | * 下单价格
67 | */
68 | private final BigDecimal price;
69 |
70 | /**
71 | * 下单数量
72 | */
73 | private final BigDecimal amount;
74 |
75 | /**
76 | * 成交数量
77 | */
78 | private final BigDecimal deal;
79 |
80 | /**
81 | * 成交均价
82 | */
83 | private BigDecimal average;
84 |
85 | public Order(String data, Long time, String id, Side side, Type type, State state,
86 | BigDecimal price, BigDecimal amount, BigDecimal deal, BigDecimal average) {
87 | this.data = data;
88 | this.time = time;
89 | this.id = id;
90 | this.side = side;
91 | this.type = type;
92 | this.state = state;
93 | this.price = Util.strip(price);
94 | this.amount = Util.strip(amount);
95 | this.deal = Util.strip(deal);
96 | this.average = Util.strip(average);
97 | }
98 |
99 | @Override
100 | public String to() {
101 | return '[' +
102 | Util.encode(data) +
103 | ',' + Util.to(time) +
104 | ',' + Util.to(id) +
105 | ',' + Util.to(side) +
106 | ',' + Util.to(type) +
107 | ',' + Util.to(state) +
108 | ',' + Util.to(price) +
109 | ',' + Util.to(amount) +
110 | ',' + Util.to(deal) +
111 | ',' + Util.to(average) +
112 | ']';
113 | }
114 |
115 | public double price() {
116 | Objects.requireNonNull(this.price);
117 | return this.price.doubleValue();
118 | }
119 |
120 | public double amount() {
121 | Objects.requireNonNull(this.amount);
122 | return this.amount.doubleValue();
123 | }
124 |
125 | public double deal() {
126 | Objects.requireNonNull(this.deal);
127 | return this.deal.doubleValue();
128 | }
129 |
130 | public double average() {
131 | Objects.requireNonNull(this.average);
132 | return this.average.doubleValue();
133 | }
134 |
135 | public static Order of(String data, Logger log) {
136 | return Util.of(data, Order::of, log);
137 | }
138 |
139 | static Order of(JSONArray r) {
140 | return new Order(
141 | Util.decode(r.getString(0)),
142 | r.getLong(1),
143 | r.getString(2),
144 | Util.of(r.getString(3), Side::valueOf),
145 | Util.of(r.getString(4), Type::valueOf),
146 | Util.of(r.getString(5), State::valueOf),
147 | r.getBigDecimal(6),
148 | r.getBigDecimal(7),
149 | r.getBigDecimal(8),
150 | r.getBigDecimal(9));
151 | }
152 |
153 | /**
154 | * 是否活跃订单
155 | * @return 是否活跃订单
156 | */
157 | public boolean alive() {
158 | return this.state == State.SUBMIT || this.state == State.PARTIAL;
159 | }
160 |
161 | /**
162 | * 是否有成交
163 | * @return 是否有成交
164 | */
165 | public boolean isDeal() {
166 | return this.state == State.FILLED || this.state == State.UNDONE;
167 | }
168 |
169 | /**
170 | * 成交总价
171 | * @return 成交总价
172 | */
173 | public BigDecimal dealVolume() {
174 | return null == this.average ? ZERO : this.deal.multiply(this.average);
175 | }
176 |
177 | private static final DateTimeFormatter DTF = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS");
178 |
179 | public String simple() {
180 | return "Order{" +
181 | "time='" + LocalDateTime
182 | .ofInstant(Instant.ofEpochMilli(time), ZoneId.systemDefault())
183 | .format(DTF) + '\'' +
184 | ", id='" + id + '\'' +
185 | ", side=" + side +
186 | ", type=" + type +
187 | ", state=" + state +
188 | ", price=" + price +
189 | ", amount=" + amount +
190 | ", deal=" + deal +
191 | ", average=" + average +
192 | '}';
193 | }
194 |
195 | public boolean isSell() {
196 | return this.side == Side.SELL;
197 | }
198 |
199 | public boolean isBuy() {
200 | return this.side == Side.BUY;
201 | }
202 |
203 | }
204 |
--------------------------------------------------------------------------------
/engine/model/src/main/java/cqt/goai/model/trade/OrderDetail.java:
--------------------------------------------------------------------------------
1 | package cqt.goai.model.trade;
2 |
3 | import com.alibaba.fastjson.JSONArray;
4 | import cqt.goai.model.To;
5 | import cqt.goai.model.Util;
6 | import cqt.goai.model.enums.Side;
7 | import lombok.EqualsAndHashCode;
8 | import lombok.Getter;
9 | import lombok.ToString;
10 | import org.slf4j.Logger;
11 |
12 | import java.io.Serializable;
13 | import java.math.BigDecimal;
14 | import java.util.Objects;
15 |
16 | /**
17 | * 某笔订单的一条成交明细记录
18 | * @author GOAi
19 | */
20 | @Getter
21 | @ToString
22 | @EqualsAndHashCode
23 | public class OrderDetail implements To, Serializable {
24 |
25 | private static final long serialVersionUID = 8135946558399552447L;
26 |
27 | /**
28 | * 交易所返回的原始数据, 防止有些用户需要一些特殊的数据
29 | */
30 | private final String data;
31 |
32 | /**
33 | * 时间戳
34 | */
35 | private final Long time;
36 |
37 | /**
38 | * 订单id
39 | */
40 | private final String orderId;
41 |
42 | /**
43 | * 成交明细id
44 | */
45 | private final String detailId;
46 |
47 | /**
48 | * 成交价格
49 | */
50 | private final BigDecimal price;
51 |
52 | /**
53 | * 成交数量
54 | */
55 | private final BigDecimal amount;
56 |
57 | /**
58 | * 手续费
59 | */
60 | private final BigDecimal fee;
61 |
62 | /**
63 | * 手续费货币
64 | */
65 | private final String feeCurrency;
66 |
67 | /**
68 | * 买卖方向
69 | */
70 | private final Side side;
71 |
72 | public OrderDetail(String data, Long time, String orderId, String detailId,
73 | BigDecimal price, BigDecimal amount, BigDecimal fee, String feeCurrency) {
74 | this.data = data;
75 | this.time = time;
76 | this.orderId = orderId;
77 | this.detailId = detailId;
78 | this.price = Util.strip(price);
79 | this.amount = Util.strip(amount);
80 | this.fee = Util.strip(fee);
81 | this.feeCurrency = feeCurrency;
82 | this.side = null;
83 | }
84 |
85 | public OrderDetail(String data, Long time, String orderId, String detailId,
86 | BigDecimal price, BigDecimal amount, BigDecimal fee, String feeCurrency, Side side) {
87 | this.data = data;
88 | this.time = time;
89 | this.orderId = orderId;
90 | this.detailId = detailId;
91 | this.price = Util.strip(price);
92 | this.amount = Util.strip(amount);
93 | this.fee = Util.strip(fee);
94 | this.feeCurrency = feeCurrency;
95 | this.side = side;
96 | }
97 |
98 | @Override
99 | public String to() {
100 | return '[' +
101 | Util.encode(this.data) +
102 | "," + Util.to(this.time) +
103 | "," + Util.to(this.orderId) +
104 | "," + Util.to(this.detailId) +
105 | "," + Util.to(this.price) +
106 | "," + Util.to(this.amount) +
107 | "," + Util.to(this.fee) +
108 | "," + Util.to(this.feeCurrency) +
109 | "," + Util.to(this.side) +
110 | ']';
111 | }
112 |
113 |
114 | public double price() {
115 | Objects.requireNonNull(this.price);
116 | return this.price.doubleValue();
117 | }
118 |
119 | public double amount() {
120 | Objects.requireNonNull(this.amount);
121 | return this.amount.doubleValue();
122 | }
123 |
124 | public double fee() {
125 | Objects.requireNonNull(this.fee);
126 | return this.fee.doubleValue();
127 | }
128 |
129 | public static OrderDetail of(String data, Logger log) {
130 | return Util.of(data, OrderDetail::of, log);
131 | }
132 |
133 | static OrderDetail of(JSONArray r) {
134 | return new OrderDetail
135 | (Util.decode(r.getString(0)),
136 | r.getLong(1),
137 | r.getString(2),
138 | r.getString(3),
139 | r.getBigDecimal(4),
140 | r.getBigDecimal(5),
141 | r.getBigDecimal(6),
142 | r.getString(7),
143 | Util.of(r.getString(8), Side::valueOf));
144 | }
145 | }
146 |
--------------------------------------------------------------------------------
/engine/model/src/main/java/cqt/goai/model/trade/OrderDetails.java:
--------------------------------------------------------------------------------
1 | package cqt.goai.model.trade;
2 |
3 | import cqt.goai.model.BaseModelList;
4 | import cqt.goai.model.Util;
5 | import org.slf4j.Logger;
6 |
7 | import java.io.Serializable;
8 | import java.util.List;
9 |
10 | /**
11 | * 多个订单成交明细记录
12 | * @author GOAi
13 | */
14 | public class OrderDetails extends BaseModelList implements Serializable {
15 |
16 | private static final long serialVersionUID = -5883032867443051561L;
17 |
18 | public OrderDetails(List list) {
19 | super(list);
20 | }
21 |
22 | public static OrderDetails of(String data, Logger log) {
23 | return Util.of(data, OrderDetails::new, OrderDetail::of, log);
24 | }
25 |
26 | @Override
27 | public String toString() {
28 | return "OrderDetails" + super.toString();
29 | }
30 |
31 | }
32 |
--------------------------------------------------------------------------------
/engine/model/src/main/java/cqt/goai/model/trade/Orders.java:
--------------------------------------------------------------------------------
1 | package cqt.goai.model.trade;
2 |
3 | import cqt.goai.model.BaseModelList;
4 | import cqt.goai.model.Util;
5 | import org.slf4j.Logger;
6 |
7 | import java.io.Serializable;
8 | import java.util.List;
9 |
10 | /**
11 | * 多笔订单
12 | * @author GOAi
13 | */
14 | public class Orders extends BaseModelList implements Serializable {
15 |
16 | private static final long serialVersionUID = 3062332528451320335L;
17 |
18 | public Orders(List list) {
19 | super(list);
20 | }
21 |
22 | public static Orders of(String data, Logger log) {
23 | return Util.of(data, Orders::new, Order::of, log);
24 | }
25 |
26 | @Override
27 | public String toString() {
28 | return "Orders" + super.toString();
29 | }
30 |
31 | }
32 |
--------------------------------------------------------------------------------
/engine/model/src/main/java/cqt/goai/model/trade/Precision.java:
--------------------------------------------------------------------------------
1 | package cqt.goai.model.trade;
2 |
3 | import com.alibaba.fastjson.JSONArray;
4 | import cqt.goai.model.To;
5 | import cqt.goai.model.Util;
6 | import lombok.EqualsAndHashCode;
7 | import lombok.Getter;
8 | import lombok.ToString;
9 | import org.slf4j.Logger;
10 |
11 | import java.io.Serializable;
12 | import java.math.BigDecimal;
13 | import java.util.Objects;
14 |
15 | /**
16 | * 某交易币对的精度
17 | * @author GOAi
18 | */
19 | @Getter
20 | @ToString
21 | @EqualsAndHashCode
22 | public class Precision implements To, Serializable {
23 |
24 | private static final long serialVersionUID = -5822155062030969112L;
25 |
26 | /**
27 | * 交易所返回的原始数据, 防止有些用户需要一些特殊的数据
28 | */
29 | private final String data;
30 |
31 | /**
32 | * 币对
33 | */
34 | private final String symbol;
35 |
36 | /**
37 | * 交易时币精度,amount
38 | */
39 | private final Integer base;
40 |
41 | /**
42 | * 计价货币精度,price BTC/USD 1btc->6000usd 所以数数数的是usd,也就是写price的位置的精度
43 | */
44 | private final Integer quote;
45 |
46 | /**
47 | * 最小币步长
48 | */
49 | private final BigDecimal baseStep;
50 |
51 | /**
52 | * 最小价格步长
53 | */
54 | private final BigDecimal quoteStep;
55 |
56 | /**
57 | * 最小买卖数量
58 | */
59 | private final BigDecimal minBase;
60 |
61 | /**
62 | * 最小买价格
63 | */
64 | private final BigDecimal minQuote;
65 |
66 | /**
67 | * 最大买卖数量
68 | */
69 | private final BigDecimal maxBase;
70 |
71 | /**
72 | * 最大买价格
73 | */
74 | private final BigDecimal maxQuote;
75 |
76 | public Precision(String data, String symbol,
77 | Integer base, Integer quote,
78 | BigDecimal baseStep, BigDecimal quoteStep,
79 | BigDecimal minBase, BigDecimal minQuote,
80 | BigDecimal maxBase, BigDecimal maxQuote) {
81 | this.data = data;
82 | this.symbol = symbol;
83 | this.base = base;
84 | this.quote = quote;
85 | this.baseStep = Util.strip(baseStep);
86 | this.quoteStep = Util.strip(quoteStep);
87 | this.minBase = Util.strip(minBase);
88 | this.minQuote = Util.strip(minQuote);
89 | this.maxBase = Util.strip(maxBase);
90 | this.maxQuote = Util.strip(maxQuote);
91 | }
92 |
93 | @Override
94 | public String to() {
95 | return '[' +
96 | Util.encode(this.data) +
97 | "," + Util.to(this.symbol) +
98 | "," + Util.to(this.base) +
99 | "," + Util.to(this.quote) +
100 | "," + Util.to(this.baseStep) +
101 | "," + Util.to(this.quoteStep) +
102 | "," + Util.to(this.minBase) +
103 | "," + Util.to(this.minQuote) +
104 | "," + Util.to(this.maxBase) +
105 | "," + Util.to(this.maxQuote) +
106 | ']';
107 | }
108 |
109 | public double baseStep() {
110 | Objects.requireNonNull(this.baseStep);
111 | return this.baseStep.doubleValue();
112 | }
113 |
114 | public double quoteStep() {
115 | Objects.requireNonNull(this.quoteStep);
116 | return this.quoteStep.doubleValue();
117 | }
118 |
119 | public double minBase() {
120 | Objects.requireNonNull(this.minBase);
121 | return this.minBase.doubleValue();
122 | }
123 |
124 | public double minQuote() {
125 | Objects.requireNonNull(this.minQuote);
126 | return this.minQuote.doubleValue();
127 | }
128 |
129 | public double maxBase() {
130 | Objects.requireNonNull(this.maxBase);
131 | return this.maxBase.doubleValue();
132 | }
133 |
134 | public double maxQuote() {
135 | Objects.requireNonNull(this.maxQuote);
136 | return this.maxQuote.doubleValue();
137 | }
138 |
139 | public static Precision of(String data, Logger log) {
140 | return Util.of(data, Precision::of, log);
141 | }
142 |
143 | static Precision of(JSONArray r) {
144 | return new Precision(
145 | Util.decode(r.getString(0)),
146 | r.getString(1),
147 | r.getInteger(2),
148 | r.getInteger(3),
149 | r.getBigDecimal(4),
150 | r.getBigDecimal(5),
151 | r.getBigDecimal(6),
152 | r.getBigDecimal(7),
153 | r.getBigDecimal(8),
154 | r.getBigDecimal(9));
155 | }
156 |
157 | }
158 |
--------------------------------------------------------------------------------
/engine/model/src/main/java/cqt/goai/model/trade/Precisions.java:
--------------------------------------------------------------------------------
1 | package cqt.goai.model.trade;
2 |
3 | import cqt.goai.model.BaseModelList;
4 | import cqt.goai.model.Util;
5 | import org.slf4j.Logger;
6 |
7 | import java.io.Serializable;
8 | import java.util.List;
9 |
10 | /**
11 | * @author GOAi
12 | */
13 | public class Precisions extends BaseModelList implements Serializable {
14 |
15 | private static final long serialVersionUID = 6672027458716855352L;
16 |
17 | public Precisions(List list) {
18 | super(list);
19 | }
20 |
21 | public static Precisions of(String data, Logger log) {
22 | return Util.of(data, Precisions::new, Precision::of, log);
23 | }
24 |
25 | @Override
26 | public String toString() {
27 | return "Precisions" + super.toString();
28 | }
29 |
30 | }
31 |
--------------------------------------------------------------------------------
/engine/model/src/test/java/test/market/DepthTest.java:
--------------------------------------------------------------------------------
1 | package test.market;
2 |
3 | import cqt.goai.model.market.Depth;
4 | import cqt.goai.model.market.Row;
5 | import cqt.goai.model.market.Rows;
6 | import org.junit.Assert;
7 | import org.junit.Test;
8 |
9 | import java.math.BigDecimal;
10 | import java.util.Arrays;
11 |
12 | public class DepthTest {
13 |
14 | @Test
15 | public void test() {
16 | Depth depth = new Depth(
17 | 1546426483495L,
18 | null,
19 | new Rows( Arrays.asList(new Row("231",new BigDecimal("1"), new BigDecimal("2") ),
20 | new Row(null, new BigDecimal("0.500"), new BigDecimal("1"))))
21 | );
22 | String data = depth.to();
23 | Assert.assertEquals("{\"time\":1546426483495,\"asks\":null,\"bids\":[[\"MjMx\",1,2],[null,0.5,1]]}", data);
24 | Depth t = Depth.of(data, null);
25 | Assert.assertEquals(depth, t);
26 | }
27 |
28 | }
29 |
--------------------------------------------------------------------------------
/engine/model/src/test/java/test/market/KlineTest.java:
--------------------------------------------------------------------------------
1 | package test.market;
2 |
3 | import cqt.goai.model.market.Kline;
4 | import org.junit.Assert;
5 | import org.junit.Test;
6 |
7 | import java.math.BigDecimal;
8 |
9 | public class KlineTest {
10 |
11 | @Test
12 | public void test() {
13 | Kline kline = new Kline(
14 | "[123,123]",
15 | 1546419093195L,
16 | new BigDecimal(1),
17 | new BigDecimal(2),
18 | null,
19 | new BigDecimal(4),
20 | new BigDecimal(5)
21 | );
22 | String data = kline.to();
23 | Assert.assertEquals("[\"WzEyMywxMjNd\",1546419093195,1,2,null,4,5]", data);
24 | Kline c = Kline.of(data, null);
25 | Assert.assertEquals(kline, c);
26 | }
27 |
28 | }
29 |
--------------------------------------------------------------------------------
/engine/model/src/test/java/test/market/KlinesTest.java:
--------------------------------------------------------------------------------
1 | package test.market;
2 |
3 | import cqt.goai.model.market.Kline;
4 | import cqt.goai.model.market.Klines;
5 | import org.junit.Assert;
6 | import org.junit.Test;
7 |
8 | import java.math.BigDecimal;
9 | import java.util.Arrays;
10 |
11 | public class KlinesTest {
12 |
13 | @Test
14 | public void test() {
15 | Klines klines = new Klines(Arrays.asList(new Kline(
16 | "{123,\"123\"}",
17 | 1546422552967L,
18 | null,
19 | new BigDecimal("1"),
20 | new BigDecimal("2"),
21 | new BigDecimal("3"),
22 | new BigDecimal("0")
23 | ),new Kline(
24 | null,
25 | 1546422552968L,
26 | null,
27 | new BigDecimal("2"),
28 | new BigDecimal("3"),
29 | new BigDecimal("4"),
30 | new BigDecimal("5")
31 | )));
32 |
33 | String data = klines.to();
34 | Assert.assertEquals("[[\"ezEyMywiMTIzIn0=\",1546422552967,null,1,2,3,0],[null,1546422552968,null,2,3,4,5]]", data);
35 |
36 | Klines c = Klines.of(data, null);
37 | Assert.assertEquals(klines, c);
38 | }
39 |
40 | }
41 |
--------------------------------------------------------------------------------
/engine/model/src/test/java/test/market/RowTest.java:
--------------------------------------------------------------------------------
1 | package test.market;
2 |
3 | import cqt.goai.model.market.Row;
4 | import org.junit.Assert;
5 | import org.junit.Test;
6 |
7 | import java.math.BigDecimal;
8 |
9 | public class RowTest {
10 |
11 | @Test
12 | public void test() {
13 | Row row = new Row("12321,qwr42w3r238987c",new BigDecimal("1.12"),new BigDecimal("1.12"));
14 | String data = row.to();
15 | Assert.assertEquals("[\"MTIzMjEscXdyNDJ3M3IyMzg5ODdj\",1.12,1.12]", data);
16 | Row r = Row.of(data, null);
17 | Assert.assertEquals(row, r);
18 | }
19 |
20 | }
21 |
--------------------------------------------------------------------------------
/engine/model/src/test/java/test/market/RowsTest.java:
--------------------------------------------------------------------------------
1 | package test.market;
2 |
3 | import cqt.goai.model.market.Row;
4 | import cqt.goai.model.market.Rows;
5 | import org.junit.Assert;
6 | import org.junit.Test;
7 |
8 | import java.math.BigDecimal;
9 | import java.util.Arrays;
10 |
11 | public class RowsTest {
12 |
13 | @Test
14 | public void test() {
15 | Rows rows = new Rows(Arrays.asList(new Row(
16 | "{123,\"123\"}",
17 | new BigDecimal("3"),
18 | new BigDecimal("0")
19 | ),new Row(
20 | null,
21 | new BigDecimal("3"),
22 | new BigDecimal("0")
23 | )));
24 |
25 | String data = rows.to();
26 | Assert.assertEquals("[[\"ezEyMywiMTIzIn0=\",3,0],[null,3,0]]", data);
27 |
28 | Rows rs = Rows.of(data, null);
29 | Assert.assertEquals(rows, rs);
30 | }
31 |
32 | }
33 |
--------------------------------------------------------------------------------
/engine/model/src/test/java/test/market/TickerTest.java:
--------------------------------------------------------------------------------
1 | package test.market;
2 |
3 | import cqt.goai.model.market.Ticker;
4 | import org.junit.Assert;
5 | import org.junit.Test;
6 |
7 | import java.math.BigDecimal;
8 |
9 | public class TickerTest {
10 |
11 | @Test
12 | public void test() {
13 | Ticker ticker = new Ticker(
14 | "{123,\"123\"}",
15 | 1546419093195L,
16 | new BigDecimal(1),
17 | new BigDecimal(2),
18 | null,
19 | new BigDecimal(4),
20 | new BigDecimal(5)
21 | );
22 | String data = ticker.to();
23 | Assert.assertEquals("{\"data\":\"ezEyMywiMTIzIn0=\",\"time\":1546419093195,\"open\":1,\"high\":2,\"low\":null,\"last\":4,\"volume\":5}", data);
24 | Ticker t = Ticker.of(data, null);
25 | Assert.assertEquals(ticker, t);
26 | }
27 |
28 | }
29 |
--------------------------------------------------------------------------------
/engine/model/src/test/java/test/market/TradeTest.java:
--------------------------------------------------------------------------------
1 | package test.market;
2 |
3 | import cqt.goai.model.enums.Side;
4 | import cqt.goai.model.market.Trade;
5 | import org.junit.Assert;
6 | import org.junit.Test;
7 |
8 | import java.math.BigDecimal;
9 |
10 | public class TradeTest {
11 |
12 | @Test
13 | public void test() {
14 | Trade trade = new Trade(
15 | "[123,123]",
16 | 1546419093195L,
17 | "123",
18 | Side.BUY,
19 | new BigDecimal(1),
20 | new BigDecimal(2)
21 | );
22 | String data = trade.to();
23 | Assert.assertEquals("[\"WzEyMywxMjNd\",1546419093195,\"123\",\"BUY\",1,2]", data);
24 | Trade t = Trade.of(data, null);
25 | Assert.assertEquals(trade, t);
26 | }
27 |
28 | }
29 |
--------------------------------------------------------------------------------
/engine/model/src/test/java/test/market/TradesTest.java:
--------------------------------------------------------------------------------
1 | package test.market;
2 |
3 | import cqt.goai.model.enums.Side;
4 | import cqt.goai.model.market.Trade;
5 | import cqt.goai.model.market.Trades;
6 | import org.junit.Assert;
7 | import org.junit.Test;
8 |
9 | import java.math.BigDecimal;
10 | import java.util.Arrays;
11 |
12 | public class TradesTest {
13 |
14 | @Test
15 | public void test() {
16 | Trades trades = new Trades(Arrays.asList(new Trade(
17 | "asd{89as\"",
18 | 123L,
19 | "123",
20 | Side.BUY,
21 | new BigDecimal("1"),
22 | new BigDecimal("2")
23 | ),new Trade(
24 | null,
25 | 123L,
26 | "123",
27 | Side.SELL,
28 | new BigDecimal("1"),
29 | new BigDecimal("2")
30 | )));
31 |
32 | String data = trades.to();
33 | Assert.assertEquals("[[\"YXNkezg5YXMi\",123,\"123\",\"BUY\",1,2],[null,123,\"123\",\"SELL\",1,2]]", data);
34 | Trades ts = Trades.of(data, null);
35 | Assert.assertEquals(trades, ts);
36 | }
37 |
38 | }
39 |
--------------------------------------------------------------------------------
/engine/model/src/test/java/test/trade/PrecisionTest.java:
--------------------------------------------------------------------------------
1 | package test.trade;
2 |
3 | import cqt.goai.model.trade.Precision;
4 | import org.junit.Assert;
5 | import org.junit.Test;
6 |
7 | import java.math.BigDecimal;
8 |
9 | public class PrecisionTest {
10 |
11 | @Test
12 | public void test() {
13 | Precision precision = new Precision(
14 | "asda\"",
15 | "symbol",
16 | 2,
17 | 3,
18 | new BigDecimal("3"),
19 | new BigDecimal("5"),
20 | new BigDecimal("0.01"),
21 | new BigDecimal("0.001"),
22 | new BigDecimal("50").stripTrailingZeros(),
23 | new BigDecimal("0.01")
24 | );
25 | String data = precision.to();
26 | Assert.assertEquals("[\"YXNkYSI=\",\"symbol\",2,3,3,5,0.01,0.001,50,0.01]", data);
27 | Assert.assertEquals(precision, Precision.of(data, null));
28 | }
29 |
30 | }
31 |
--------------------------------------------------------------------------------
/engine/run/build.gradle:
--------------------------------------------------------------------------------
1 | sourceSets {
2 | main {
3 | java {
4 | srcDirs = ['src/main/java']
5 | }
6 | resources {
7 | srcDirs = ['src/main/resources']
8 | }
9 | }
10 | }
11 |
12 | //processResources {
13 | // include "**/logback.xml"
14 | // include "**/config**.yml"
15 | //}
16 |
17 | jar {
18 | from { configurations.runtime.collect{zipTree(it)} }
19 | baseName = 'goai-engine'
20 | manifest {
21 | attributes("Main-Class": "cqt.goai.run.Application")
22 | }
23 | }
24 |
25 | dependencies {
26 | testCompile 'junit:junit:4.12'
27 |
28 | // compile fileTree(dir: 'libs', includes: ['*.jar'])
29 |
30 | compile project(":exchange")
31 |
32 | compile 'ch.qos.logback:logback-classic:1.2.3'
33 | compile 'org.apache.logging.log4j:log4j-to-slf4j:2.11.1'
34 | compile 'org.yaml:snakeyaml:1.19'
35 | compile 'org.quartz-scheduler:quartz:2.3.0'
36 |
37 | compile 'javax.mail:mail:1.4.7'
38 | }
39 |
40 | task copyJar(type:Copy){
41 | from ('build\\libs')
42 | into ('.')
43 | }
--------------------------------------------------------------------------------
/engine/run/jar.bat:
--------------------------------------------------------------------------------
1 | gradle clean build -x test copyJar
--------------------------------------------------------------------------------
/engine/run/src/main/java/cqt/goai/run/Application.java:
--------------------------------------------------------------------------------
1 | package cqt.goai.run;
2 |
3 | import cqt.goai.run.main.Minister;
4 |
5 | /**
6 | * 启动类
7 | *
8 | * @author GOAi
9 | */
10 | public class Application {
11 |
12 | /**
13 | * 主启动函数
14 | * @param args 启动参数
15 | */
16 | public static void main(String[] args) {
17 | Minister.run(args);
18 | }
19 |
20 | }
21 |
--------------------------------------------------------------------------------
/engine/run/src/main/java/cqt/goai/run/annotation/Scheduled.java:
--------------------------------------------------------------------------------
1 | package cqt.goai.run.annotation;
2 |
3 | import java.lang.annotation.*;
4 |
5 | /**
6 | * 定时任务
7 | * @author GOAi
8 | */
9 | @Repeatable(Schedules.class)
10 | @Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
11 | @Retention(RetentionPolicy.RUNTIME)
12 | public @interface Scheduled {
13 | /**
14 | * cron方式 优先
15 | */
16 | String cron() default "";
17 |
18 | /**
19 | * 固定频率,毫秒
20 | */
21 | long fixedRate() default 0;
22 |
23 | /**
24 | * 延时,毫秒
25 | */
26 | long delay() default 0;
27 |
28 | }
29 |
--------------------------------------------------------------------------------
/engine/run/src/main/java/cqt/goai/run/annotation/ScheduledScope.java:
--------------------------------------------------------------------------------
1 | package cqt.goai.run.annotation;
2 |
3 | import cqt.goai.run.main.MethodScope;
4 |
5 | import java.lang.annotation.ElementType;
6 | import java.lang.annotation.Retention;
7 | import java.lang.annotation.RetentionPolicy;
8 | import java.lang.annotation.Target;
9 |
10 | /**
11 | * 方法范围
12 | *
13 | * @author GOAi
14 | */
15 | @Target(ElementType.METHOD)
16 | @Retention(RetentionPolicy.RUNTIME)
17 | public @interface ScheduledScope {
18 | /**
19 | * 默认实例范围
20 | * @return 方法运行范围
21 | */
22 | MethodScope value() default MethodScope.INSTANCE;
23 | }
24 |
--------------------------------------------------------------------------------
/engine/run/src/main/java/cqt/goai/run/annotation/Schedules.java:
--------------------------------------------------------------------------------
1 | package cqt.goai.run.annotation;
2 |
3 | import java.lang.annotation.ElementType;
4 | import java.lang.annotation.Retention;
5 | import java.lang.annotation.RetentionPolicy;
6 | import java.lang.annotation.Target;
7 |
8 | /**
9 | * 单个方法允许设置多个定时任务
10 | * @author GOAi
11 | */
12 | @Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
13 | @Retention(RetentionPolicy.RUNTIME)
14 | public @interface Schedules {
15 | Scheduled[] value();
16 | }
17 |
--------------------------------------------------------------------------------
/engine/run/src/main/java/cqt/goai/run/exchange/Exchange.java:
--------------------------------------------------------------------------------
1 | package cqt.goai.run.exchange;
2 |
3 | import cqt.goai.exchange.ExchangeName;
4 | import cqt.goai.exchange.http.HttpExchange;
5 | import cqt.goai.exchange.util.RateLimit;
6 | import cqt.goai.model.enums.Period;
7 | import cqt.goai.model.market.*;
8 | import cqt.goai.model.trade.*;
9 | import org.slf4j.Logger;
10 |
11 | import java.math.BigDecimal;
12 | import java.util.List;
13 | import java.util.function.Consumer;
14 |
15 | /**
16 | * 交易所接口
17 | * @author GOAi
18 | */
19 | public interface Exchange {
20 |
21 | /**
22 | * 所属交易所信息
23 | * @return 交易所信息
24 | */
25 | ExchangeName getExchangeName();
26 |
27 | /**
28 | * 交易所名
29 | * @return 交易所名
30 | */
31 | String getName();
32 |
33 | /**
34 | * 币对
35 | * @return 币对
36 | */
37 | String getSymbol();
38 |
39 | /**
40 | * access
41 | * @return access
42 | */
43 | String getAccess();
44 |
45 | /**
46 | * secret
47 | * @return secret
48 | */
49 | String getSecret();
50 |
51 | /**
52 | * 日志
53 | * @return 日志
54 | */
55 | Logger getLog();
56 |
57 | /**
58 | * 获取http请求对象 方便调用特殊api
59 | * @return HttpExchange
60 | */
61 | HttpExchange getHttpExchange();
62 |
63 | /**
64 | * 取消所有订阅
65 | */
66 | void destroy();
67 |
68 | /**
69 | * 检查价格
70 | * @param count 价格
71 | * @param ceil 取整方向
72 | * @return 调整后
73 | */
74 | BigDecimal checkCount(BigDecimal count, boolean ceil);
75 |
76 | /**
77 | * 检查数量
78 | * @param base 数量
79 | * @return 调整后
80 | */
81 | BigDecimal checkBase(BigDecimal base);
82 |
83 | /**
84 | * 检查多个精度
85 | * @param rows 多个
86 | * @param ceil 取整方向
87 | * @return 调整后
88 | */
89 | List checkRows(List rows, boolean ceil);
90 |
91 | // =================== ticker =======================
92 |
93 | /**
94 | * 获取Ticker
95 | * @return Ticker
96 | */
97 | Ticker getTicker();
98 |
99 | /**
100 | * 获取Ticker
101 | * @param latest http方式, false -> 允许不是最新的 true -> 必须最新的
102 | * @return Ticker
103 | */
104 | Ticker getTicker(boolean latest);
105 |
106 | // =================== klines =======================
107 |
108 | /**
109 | * 获取Klines
110 | * @return Klines
111 | */
112 | Klines getKlines();
113 |
114 | /**
115 | * 获取Klines 默认可以取不是最新的
116 | * @param period K线周期
117 | * @return Klines
118 | */
119 | Klines getKlines(Period period);
120 |
121 | /**
122 | * 获取Klines 默认周期为MIN1
123 | * @param latest http方式, 允许不是最新的
124 | * @return Klines
125 | */
126 | Klines getKlines(boolean latest);
127 |
128 | /**
129 | * 获取Klines
130 | * @param period K线周期
131 | * @param latest http方式, 允许不是最新的
132 | * @return Klines
133 | */
134 | Klines getKlines(Period period, boolean latest);
135 |
136 | // =================== Depth =======================
137 |
138 | /**
139 | * 获取Depth
140 | * @return Depth
141 | */
142 | Depth getDepth();
143 |
144 | /**
145 | * 获取Depth
146 | * @param latest http方式, 允许不是最新的
147 | * @return Depth
148 | */
149 | Depth getDepth(boolean latest);
150 |
151 | // =================== trades =======================
152 |
153 | /**
154 | * 获取Trades
155 | * @return Trades
156 | */
157 | Trades getTrades();
158 |
159 | /**
160 | * 获取Trades
161 | * @param latest http方式, 允许不是最新的
162 | * @return Trades
163 | */
164 | Trades getTrades(boolean latest);
165 |
166 | // =================== account =======================
167 |
168 | /**
169 | * 获取账户所有余额
170 | * @return Balances
171 | */
172 | Balances getBalances();
173 |
174 | /**
175 | * 获取Account
176 | * @return Account
177 | */
178 | Account getAccount();
179 |
180 | /**
181 | * 获取Account
182 | * @param latest http方式, 允许不是最新的
183 | * @return Account
184 | */
185 | Account getAccount(boolean latest);
186 |
187 | // =================== trade =======================
188 |
189 | /**
190 | * 获取所有币对精度信息
191 | * @return Precisions
192 | */
193 | Precisions getPrecisions();
194 |
195 | /**
196 | * 获取币对精度信息
197 | * @return Precision
198 | */
199 | Precision getPrecision();
200 |
201 | /**
202 | * 限价买
203 | * 自动结算精度,价格向下截断,数量向下截断
204 | * @param price 订单价格
205 | * @param amount 订单数量
206 | * @return 订单id
207 | */
208 | String buyLimit(BigDecimal price, BigDecimal amount);
209 |
210 | /**
211 | * 限价买
212 | * 自动结算精度,价格向下截断,数量向下截断
213 | * @param price 订单价格
214 | * @param amount 订单数量
215 | * @return 订单id
216 | */
217 | default String buyLimit(double price, double amount) {
218 | return this.buyLimit(new BigDecimal(price), new BigDecimal(amount));
219 | }
220 |
221 | /**
222 | * 限价卖
223 | * 自动结算精度,价格向上截断,数量向上截断
224 | * @param price 订单价格
225 | * @param amount 订单数量
226 | * @return 订单id
227 | */
228 | String sellLimit(BigDecimal price, BigDecimal amount);
229 |
230 | /**
231 | * 限价卖
232 | * 自动结算精度,价格向上截断,数量向上截断
233 | * @param price 订单价格
234 | * @param amount 订单数量
235 | * @return 订单id
236 | */
237 | default String sellLimit(double price, double amount) {
238 | return this.sellLimit(new BigDecimal(price), new BigDecimal(amount));
239 | }
240 |
241 | /**
242 | * 市价买
243 | * 自动结算精度,价格向下截断
244 | * @param count 订单总价
245 | * @return 订单id
246 | */
247 | String buyMarket(BigDecimal count);
248 |
249 | /**
250 | * 市价卖
251 | * 自动结算精度,数量向上截断
252 | * @param base 订单数量
253 | * @return 订单id
254 | */
255 | String sellMarket(BigDecimal base);
256 |
257 | /**
258 | * 多买
259 | * @param bids 多对买单
260 | * @return 订单id
261 | */
262 | List multiBuy(List bids);
263 |
264 | /**
265 | * 多卖
266 | * @param asks 多对卖单
267 | * @return 订单id
268 | */
269 | List multiSell(List asks);
270 |
271 | /**
272 | * 取消单个订单
273 | * @param id 订单id
274 | * @return 取消成功与否
275 | */
276 | boolean cancelOrder(String id);
277 |
278 | /**
279 | * 取消多个订单
280 | * @param ids 多个订单id
281 | * @return 成功取消的订单id
282 | */
283 | List cancelOrders(List ids);
284 |
285 | // =================== order =======================
286 |
287 | /**
288 | * 获取活跃订单
289 | * @return Orders
290 | */
291 | Orders getOrders();
292 |
293 | /**
294 | * 获取历史订单
295 | * @return Orders
296 | */
297 | Orders getHistoryOrders();
298 |
299 |
300 | /**
301 | * 获取历史订单
302 | * @param id 订单id
303 | * @return Order
304 | */
305 | Order getOrder(String id);
306 |
307 | /**
308 | * 获取订单成交明细
309 | * @param id 订单id
310 | * @return OrderDetails
311 | */
312 | OrderDetails getOrderDetails(String id);
313 |
314 | /**
315 | * 获取所有订单成交明细
316 | * @return getOrderDetailAll
317 | */
318 | OrderDetails getOrderDetailAll();
319 |
320 | // =================== push =======================
321 |
322 | /**
323 | * 设置onTicker, 默认无频率限制
324 | * @param onTicker 消费函数
325 | */
326 | void setOnTicker(Consumer onTicker);
327 |
328 | /**
329 | * 设置onTicker
330 | * @param onTicker 消费函数
331 | * @param limit 推送频率限制
332 | */
333 | void setOnTicker(Consumer onTicker, RateLimit limit);
334 |
335 | /**
336 | * 设置onTicker, 默认1MIN, 频率无频率限制
337 | * @param onKlines 消费函数
338 | */
339 | void setOnKlines(Consumer onKlines);
340 |
341 | /**
342 | * 设置onTicker
343 | * @param onKlines 消费函数
344 | * @param period 推送K线周期
345 | * @param limit 频率限制
346 | */
347 | void setOnKlines(Consumer onKlines, Period period, RateLimit limit);
348 |
349 | /**
350 | * 设置onDepth, 默认无频率限制
351 | * @param onDepth 消费函数
352 | */
353 | void setOnDepth(Consumer onDepth);
354 |
355 | /**
356 | * 设置onDepth
357 | * @param onDepth 消费函数
358 | * @param limit 推送频率限制
359 | */
360 | void setOnDepth(Consumer onDepth, RateLimit limit);
361 |
362 | /**
363 | * 设置onTrades, 默认无频率限制
364 | * @param onTrades 消费函数
365 | */
366 | void setOnTrades(Consumer onTrades);
367 |
368 | /**
369 | * 设置onTrades
370 | * @param onTrades 消费函数
371 | * @param limit 推送频率限制
372 | */
373 | void setOnTrades(Consumer onTrades, RateLimit limit);
374 |
375 | /**
376 | * 设置onAccount, 默认无频率限制
377 | * @param onAccount 消费函数
378 | */
379 | void setOnAccount(Consumer onAccount);
380 |
381 | /**
382 | * 设置onOrders, 默认无频率限制
383 | * @param onOrders 消费函数
384 | */
385 | void setOnOrders(Consumer onOrders);
386 |
387 |
388 | /**
389 | * 获取Exchange json
390 | * @param name 交易所名
391 | * @param symbol 币对
392 | * @param access access
393 | * @param secret secret
394 | * @return 交易所json
395 | */
396 | static String getExchangeJson(String name, String symbol, String access, String secret) {
397 | return String.format("{\"name\":\"%s\",\"symbol\":\"%s\",\"access\":\"%s\",\"secret\":\"%s\"}",
398 | name, symbol, access, secret);
399 | }
400 |
401 | }
402 |
--------------------------------------------------------------------------------
/engine/run/src/main/java/cqt/goai/run/exchange/FourFunction.java:
--------------------------------------------------------------------------------
1 | package cqt.goai.run.exchange;
2 |
3 | /**
4 | * @author GOAi
5 | */
6 | @FunctionalInterface
7 | public interface FourFunction {
8 | /**
9 | * 接受6个参数
10 | * @param t t
11 | * @param u u
12 | * @param k k
13 | * @param l l
14 | */
15 | R apply(T t, U u, K k, L l);
16 | }
17 |
--------------------------------------------------------------------------------
/engine/run/src/main/java/cqt/goai/run/exchange/TrConsumer.java:
--------------------------------------------------------------------------------
1 | package cqt.goai.run.exchange;
2 |
3 | /**
4 | * @author GOAi
5 | */
6 | @FunctionalInterface
7 | public interface TrConsumer {
8 | /**
9 | * 接受3个参数
10 | * @param t t
11 | * @param u u
12 | * @param k k
13 | */
14 | void accept(T t, U u, K k);
15 | }
16 |
--------------------------------------------------------------------------------
/engine/run/src/main/java/cqt/goai/run/exchange/factory/BaseExchangeFactory.java:
--------------------------------------------------------------------------------
1 | package cqt.goai.run.exchange.factory;
2 |
3 | import com.alibaba.fastjson.JSONObject;
4 | import cqt.goai.run.exchange.Exchange;
5 | import org.slf4j.Logger;
6 |
7 | import java.util.function.Supplier;
8 |
9 | /**
10 | * @author GOAi
11 | */
12 | public abstract class BaseExchangeFactory {
13 |
14 | /**
15 | * Exchange工厂
16 | * Factory Method Pattern 工厂方法模式
17 | * 有多种Exchange类型,不同的工厂产生的Exchange底层实现不一样
18 | * @param proxy 是否包装代理
19 | * @param log 日志
20 | * @param config 配置信息
21 | * @param ready 是否准备好接收推送
22 | * @return Exchange
23 | */
24 | public abstract Exchange getExchange(boolean proxy, Logger log, JSONObject config, Supplier ready);
25 |
26 | }
27 |
--------------------------------------------------------------------------------
/engine/run/src/main/java/cqt/goai/run/exchange/factory/LocalExchangeFactory.java:
--------------------------------------------------------------------------------
1 | package cqt.goai.run.exchange.factory;
2 |
3 | import com.alibaba.fastjson.JSONObject;
4 | import cqt.goai.exchange.ExchangeName;
5 | import cqt.goai.run.exchange.Exchange;
6 | import cqt.goai.run.exchange.LocalExchange;
7 | import cqt.goai.run.exchange.ProxyExchange;
8 | import org.slf4j.Logger;
9 |
10 | import java.util.function.Supplier;
11 |
12 | /**
13 | * 本地实现Exchange
14 | * @author GOAi
15 | */
16 | public class LocalExchangeFactory extends BaseExchangeFactory {
17 |
18 | @Override
19 | public Exchange getExchange(boolean proxy, Logger log, JSONObject config, Supplier ready) {
20 | Exchange exchange = new LocalExchange(log,
21 | ExchangeName.getByName(config.getString("name")),
22 | config.getString("symbol"),
23 | config.getString("access"),
24 | config.getString("secret"),
25 | ready);
26 | if (proxy) {
27 | exchange = new ProxyExchange(exchange);
28 | }
29 | return exchange;
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/engine/run/src/main/java/cqt/goai/run/exchange/model/LogAction.java:
--------------------------------------------------------------------------------
1 | package cqt.goai.run.exchange.model;
2 |
3 | import com.alibaba.fastjson.JSON;
4 | import com.alibaba.fastjson.JSONArray;
5 | import com.alibaba.fastjson.JSONObject;
6 | import cqt.goai.exchange.Action;
7 | import cqt.goai.model.market.Row;
8 | import lombok.Data;
9 |
10 | import java.math.BigDecimal;
11 | import java.util.ArrayList;
12 | import java.util.List;
13 |
14 | /**
15 | * @author GOAi
16 | */
17 | @Data
18 | public class LogAction {
19 |
20 | private String name;
21 | private String symbol;
22 | private String access;
23 | private Action action;
24 |
25 | private BigDecimal price;
26 | private BigDecimal amount;
27 |
28 | private BigDecimal quote;
29 | private BigDecimal base;
30 |
31 | private List rows;
32 |
33 | private String id;
34 | private List ids;
35 |
36 | private LogAction(String name, String symbol, String access, Action action) {
37 | this.name = name;
38 | this.symbol = symbol;
39 | this.access = access;
40 | this.action = action;
41 | }
42 |
43 | private LogAction(String name, String symbol, String access, Action action,
44 | BigDecimal price, BigDecimal amount) {
45 | this(name, symbol, access, action);
46 | this.price = price;
47 | this.amount = amount;
48 | }
49 |
50 | public static LogAction buyLimit(String name, String symbol, String access,
51 | BigDecimal price, BigDecimal amount) {
52 | return new LogAction(name, symbol, access, Action.BUY_LIMIT, price, amount);
53 | }
54 |
55 | public static LogAction sellLimit(String name, String symbol, String access,
56 | BigDecimal price, BigDecimal amount) {
57 | return new LogAction(name, symbol, access, Action.SELL_LIMIT, price, amount);
58 | }
59 |
60 | public static LogAction buyMarket(String name, String symbol, String access, BigDecimal quote) {
61 | LogAction la = new LogAction(name, symbol, access, Action.BUY_MARKET);
62 | la.quote = quote;
63 | return la;
64 | }
65 |
66 | public static LogAction sellMarket(String name, String symbol, String access, BigDecimal base) {
67 | LogAction la = new LogAction(name, symbol, access, Action.BUY_MARKET);
68 | la.base = base;
69 | return la;
70 | }
71 |
72 | public static LogAction multiBuy(String name, String symbol, String access, List rows) {
73 | LogAction la = new LogAction(name, symbol, access, Action.MULTI_BUY);
74 | la.rows = rows;
75 | return la;
76 | }
77 |
78 | public static LogAction multiSell(String name, String symbol, String access, List rows) {
79 | LogAction la = new LogAction(name, symbol, access, Action.MULTI_SELL);
80 | la.rows = rows;
81 | return la;
82 | }
83 |
84 | private static final String NAME = "name";
85 | private static final String SYMBOL = "symbol";
86 | private static final String ACCESS = "access";
87 | private static final String ACTION = "action";
88 | private static final String PRICE = "price";
89 | private static final String AMOUNT = "amount";
90 | private static final String QUOTE = "quote";
91 | private static final String BASE = "base";
92 | private static final String ROWS = "rows";
93 | private static final String ID = "id";
94 | private static final String IDS = "ids";
95 |
96 | public static LogAction of(String json) {
97 | JSONObject r = JSON.parseObject(json);
98 | String name = null;
99 | String symbol = null;
100 | String access = null;
101 | Action action = null;
102 | if (r.containsKey(NAME)) {
103 | name = r.getString(NAME);
104 | }
105 | if (r.containsKey(SYMBOL)) {
106 | symbol = r.getString(SYMBOL);
107 | }
108 | if (r.containsKey(ACCESS)) {
109 | access = r.getString(ACCESS);
110 | }
111 | if (r.containsKey(ACTION)) {
112 | action = Action.valueOf(r.getString(ACTION));
113 | }
114 | LogAction la = new LogAction(name, symbol, access, action);
115 | if (r.containsKey(PRICE)) {
116 | la.price = r.getBigDecimal(PRICE);
117 | }
118 | if (r.containsKey(AMOUNT)) {
119 | la.amount = r.getBigDecimal(AMOUNT);
120 | }
121 | if (r.containsKey(QUOTE)) {
122 | la.quote = r.getBigDecimal(QUOTE);
123 | }
124 | if (r.containsKey(BASE)) {
125 | la.base = r.getBigDecimal(BASE);
126 | }
127 | if (r.containsKey(ROWS)) {
128 | JSONArray a = r.getJSONArray(ROWS);
129 | la.rows = new ArrayList<>(a.size());
130 | for (int i = 0; i < a.size(); i++) {
131 | la.rows.add(Row.row(a.getJSONObject(i).getBigDecimal("price"),
132 | a.getJSONObject(i).getBigDecimal("amount")));
133 | }
134 | }
135 | if (r.containsKey(ID)) {
136 | la.id = r.getString(ID);
137 | }
138 | if (r.containsKey(IDS)) {
139 | JSONArray array = r.getJSONArray(IDS);
140 | la.ids = new ArrayList<>(array.size());
141 | for (int i = 0; i < array.size(); i++) {
142 | la.ids.add(array.getString(i));
143 | }
144 | }
145 |
146 | return la;
147 | }
148 |
149 | }
150 |
--------------------------------------------------------------------------------
/engine/run/src/main/java/cqt/goai/run/exchange/model/ModelManager.java:
--------------------------------------------------------------------------------
1 | package cqt.goai.run.exchange.model;
2 |
3 | import cqt.goai.exchange.util.RateLimit;
4 |
5 | import java.util.concurrent.ConcurrentHashMap;
6 |
7 | /**
8 | * 管理某一类型对象
9 | * @author GOAi
10 | */
11 | public class ModelManager {
12 | /**
13 | * 缓存对象
14 | */
15 | private T model;
16 |
17 | /**
18 | * model是否超时
19 | */
20 | private RateLimit limit;
21 |
22 | /**
23 | * 管理所有需要推送的函数
24 | */
25 | private ConcurrentHashMap> observers = new ConcurrentHashMap<>();
26 |
27 | public ModelManager(RateLimit limit) {
28 | this.limit = limit;
29 | }
30 |
31 | /**
32 | * 推送model
33 | * @param ready 是否准备好接收
34 | * @param model model
35 | * @param id 推送id
36 | */
37 | public void on(boolean ready, T model, String id) {
38 | this.update(model);
39 | if (ready && this.on() && this.observers.containsKey(id)) {
40 | this.observers.get(id).on(model);
41 | }
42 | }
43 |
44 | /**
45 | * 是否正在推送
46 | */
47 | public boolean on() {
48 | return !observers.isEmpty();
49 | }
50 |
51 | /**
52 | * 注册推送
53 | * @param observer 注册者
54 | */
55 | public void observe(ModelObserver observer) {
56 | this.observers.put(observer.getId(), observer);
57 | }
58 |
59 | /**
60 | * http方式不用推送
61 | * @param model model
62 | */
63 | public void update(T model) {
64 | this.model = model;
65 | this.limit.update();
66 | }
67 |
68 | public T getModel() {
69 | return model;
70 | }
71 |
72 | public RateLimit getLimit() {
73 | return limit;
74 | }
75 |
76 | public ConcurrentHashMap> getObservers() {
77 | return this.observers;
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/engine/run/src/main/java/cqt/goai/run/exchange/model/ModelObserver.java:
--------------------------------------------------------------------------------
1 | package cqt.goai.run.exchange.model;
2 |
3 | import cqt.goai.exchange.util.RateLimit;
4 |
5 | import java.util.function.Consumer;
6 |
7 | /**
8 | * 某个具体订阅
9 | * @author GOAi
10 | */
11 | public class ModelObserver {
12 | /**
13 | * 消费函数
14 | */
15 | final Consumer consumer;
16 | /**
17 | * 频率限制
18 | */
19 | final RateLimit limit;
20 |
21 | /**
22 | * 推送id
23 | */
24 | private final String id;
25 |
26 | public ModelObserver(Consumer consumer, RateLimit limit, String id) {
27 | this.consumer = consumer;
28 | this.limit = limit;
29 | this.id = id;
30 | }
31 |
32 | /**
33 | * 接收model
34 | * @param model 新的对象
35 | */
36 | void on(T model) {
37 | if (this.limit.timeout()) {
38 | this.consumer.accept(model);
39 | }
40 | }
41 |
42 | public String getId() {
43 | return id;
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/engine/run/src/main/java/cqt/goai/run/exchange/model/TradesObserver.java:
--------------------------------------------------------------------------------
1 | package cqt.goai.run.exchange.model;
2 |
3 | import cqt.goai.exchange.util.RateLimit;
4 | import cqt.goai.model.market.Trade;
5 | import cqt.goai.model.market.Trades;
6 |
7 | import java.util.ArrayList;
8 | import java.util.List;
9 | import java.util.concurrent.ConcurrentLinkedDeque;
10 | import java.util.concurrent.ConcurrentLinkedQueue;
11 | import java.util.function.Consumer;
12 | import java.util.stream.Collectors;
13 |
14 | /**
15 | * @author GOAi
16 | */
17 | public class TradesObserver extends ModelObserver {
18 | /**
19 | * 最多缓存个数
20 | */
21 | private int max;
22 | /**
23 | * 对于有频率限制的推送,不能就此丢弃Trades
24 | * 缓存起来,等到能推送的时候一起推
25 | */
26 | private ConcurrentLinkedDeque trades;
27 |
28 | public TradesObserver(Consumer consumer, RateLimit limit, String id) {
29 | this(consumer, limit, id, 200);
30 | }
31 |
32 | public TradesObserver(Consumer consumer, RateLimit limit, String id, int max) {
33 | super(consumer, limit, id);
34 | if (limit.isLimit()) {
35 | trades = new ConcurrentLinkedDeque<>();
36 | this.max = max;
37 | }
38 | }
39 |
40 | @Override
41 | void on(Trades model) {
42 | if (super.limit.timeout()) {
43 | if (!this.trades.isEmpty()) {
44 | this.addFirst(model);
45 | model = new Trades(new ArrayList<>(this.trades));
46 | }
47 | this.trades.clear();
48 | super.consumer.accept(model);
49 | } else {
50 | this.addFirst(model);
51 | }
52 | }
53 |
54 | /**
55 | * 从前面加入
56 | */
57 | private void addFirst(Trades trades) {
58 | List ts = trades.getList();
59 | for (int i = ts.size() - 1; 0 <= i; i--) {
60 | this.trades.addFirst(ts.get(i));
61 | }
62 | // 最多保存200个
63 | while (this.max < this.trades.size()) {
64 | this.trades.removeLast();
65 | }
66 | }
67 |
68 | }
69 |
--------------------------------------------------------------------------------
/engine/run/src/main/java/cqt/goai/run/main/Const.java:
--------------------------------------------------------------------------------
1 | package cqt.goai.run.main;
2 |
3 | /**
4 | * 常量
5 | * @author GOAi
6 | */
7 | class Const {
8 |
9 | /**
10 | * 获取配置的url
11 | */
12 | static final String START_URL = "--url=";
13 |
14 | /**
15 | * 配置文件名称,可以相对路径或绝对路径
16 | */
17 | static final String START_NAME = "--name=";
18 |
19 |
20 | /**
21 | * 策略名称
22 | */
23 | static final String CONFIG_STRATEGY_NAME = "strategy_name";
24 | /**
25 | * 启动类名,java的run需要
26 | */
27 | static final String CONFIG_CLASS_NAME = "class_name";
28 | /**
29 | * 每个实例的配置
30 | */
31 | static final String CONFIG_CONFIGS = "configs";
32 | /**
33 | * 运行模式 如果是debug,不使用ProxyExchange
34 | */
35 | static final String CONFIG_RUN_MODE = "run_mode";
36 | // static final String CONFIG_RUN_MODE_DEBUG = "DEBUG";
37 |
38 | /**
39 | * 默认的定时方法
40 | */
41 | static final String DEFAULT_SCHEDULED_METHOD = "loop";
42 |
43 | // 魔法变量
44 | static final String LEFT_SQUARE_BRACKETS = "[";
45 |
46 | }
47 |
--------------------------------------------------------------------------------
/engine/run/src/main/java/cqt/goai/run/main/Injectable.java:
--------------------------------------------------------------------------------
1 | package cqt.goai.run.main;
2 |
3 | import cqt.goai.run.exchange.Exchange;
4 |
5 | import java.util.function.Function;
6 |
7 | /**
8 | * 自定义类型实现该接口,可实现自动注入
9 | * @author GOAi
10 | */
11 | public interface Injectable {
12 |
13 | /**
14 | * 注入
15 | *
16 | * @param config 配置内容
17 | * @param getExchange 获取exchange 参数为json字符串
18 | * {"name":"xxx","symbol":"BTC_USD","access":"xxx","secret":"xxx"}
19 | */
20 | void inject(String config, Function getExchange);
21 |
22 |
23 |
24 | }
25 |
--------------------------------------------------------------------------------
/engine/run/src/main/java/cqt/goai/run/main/MethodInfo.java:
--------------------------------------------------------------------------------
1 | package cqt.goai.run.main;
2 |
3 | import lombok.Getter;
4 |
5 | import java.lang.reflect.Method;
6 | import java.util.List;
7 | import java.util.concurrent.locks.ReentrantLock;
8 |
9 | /**
10 | * 方法运行信息
11 | *
12 | * @author GOAi
13 | */
14 | @Getter
15 | class MethodInfo {
16 |
17 | /**
18 | * 方法
19 | */
20 | private final Method method;
21 |
22 | /**
23 | * 方法运行范围
24 | */
25 | private final MethodScope methodScope;
26 |
27 | /**
28 | * 方法运行锁,重点是全局一个锁,不检查无锁,实例内只用一个,定时范围新生成
29 | */
30 | private final ReentrantLock globalLock;
31 |
32 | /**
33 | * 实例锁
34 | */
35 | private final ReentrantLock instanceLock;
36 |
37 | /**
38 | * 定时任务信息
39 | */
40 | private final List schedules;
41 |
42 | MethodInfo(Method method, MethodScope methodScope, List schedules) {
43 | this.method = method;
44 | this.methodScope = methodScope;
45 | this.schedules = schedules;
46 | if (methodScope == MethodScope.GLOBAL) {
47 | this.globalLock = new ReentrantLock();
48 | this.instanceLock = null;
49 | } else {
50 | this.globalLock = null;
51 | this.instanceLock = null;
52 | }
53 | }
54 |
55 | private MethodInfo(Method method, MethodScope methodScope, ReentrantLock globalLock, ReentrantLock instanceLock, List schedules) {
56 | this.method = method;
57 | this.methodScope = methodScope;
58 | this.globalLock = globalLock;
59 | this.instanceLock = instanceLock;
60 | this.schedules = schedules;
61 | }
62 |
63 | MethodInfo copy() {
64 | switch (methodScope) {
65 | case INSTANCE: return new MethodInfo(method, methodScope, null, new ReentrantLock(), schedules);
66 | case SCHEDULED: return new MethodInfo(method, methodScope, null, null, schedules);
67 | default: return this;
68 | }
69 | }
70 |
71 | ReentrantLock getLock() {
72 | switch (methodScope) {
73 | case GLOBAL: return this.globalLock;
74 | case INSTANCE: return this.instanceLock;
75 | case SCHEDULED: return new ReentrantLock();
76 | default: return null;
77 | }
78 | }
79 |
80 | }
81 |
--------------------------------------------------------------------------------
/engine/run/src/main/java/cqt/goai/run/main/MethodScope.java:
--------------------------------------------------------------------------------
1 | package cqt.goai.run.main;
2 |
3 | /**
4 | * 定时任务方法运行范围
5 | *
6 | * @author GOAi
7 | */
8 | public enum MethodScope {
9 |
10 | /**
11 | * 全局唯一
12 | */
13 | GLOBAL,
14 |
15 | /**
16 | * 实例唯一
17 | */
18 | INSTANCE,
19 |
20 | /**
21 | * 循环任务唯一
22 | */
23 | SCHEDULED,
24 |
25 | /**
26 | * 不限制
27 | */
28 | NONE
29 |
30 | }
31 |
--------------------------------------------------------------------------------
/engine/run/src/main/java/cqt/goai/run/main/Notice.java:
--------------------------------------------------------------------------------
1 | package cqt.goai.run.main;
2 |
3 | import cqt.goai.exchange.ExchangeUtil;
4 | import dive.http.common.MimeRequest;
5 | import org.slf4j.Logger;
6 |
7 | import static dive.common.util.Util.useful;
8 |
9 |
10 | /**
11 | * 通知对象
12 | *
13 | * @author GOAi
14 | */
15 | public class Notice {
16 |
17 | private final Logger log;
18 |
19 | private final String telegramGroup;
20 |
21 | private final String token;
22 |
23 | public Notice(Logger log, String telegramGroup, String token) {
24 | this.log = log;
25 | this.telegramGroup = telegramGroup;
26 | this.token = token;
27 | }
28 |
29 | /**
30 | * 高级通知,发电报
31 | * @param message 消息
32 | */
33 | public void noticeHigh(String message) {
34 | this.log.info("NOTICE_HIGH {}", message);
35 |
36 | // FIX ME 发电报通知
37 | if (useful(this.telegramGroup)) {
38 | new MimeRequest.Builder()
39 | .url("https://api.telegram.org/bot" + token + "/sendMessage")
40 | .post()
41 | .body("chat_id", this.telegramGroup)
42 | .body("text", message)
43 | .body("parse_mode", "HTML")
44 | .execute(ExchangeUtil.OKHTTP);
45 | }
46 | }
47 |
48 | /**
49 | * 低级通知,打日志
50 | * @param message 消息
51 | */
52 | public void noticeLow(String message) {
53 | this.log.info("NOTICE_LOW {}", message);
54 | }
55 |
56 | }
57 |
--------------------------------------------------------------------------------
/engine/run/src/main/java/cqt/goai/run/main/RunTask.java:
--------------------------------------------------------------------------------
1 | package cqt.goai.run.main;
2 |
3 | import com.alibaba.fastjson.JSON;
4 | import com.alibaba.fastjson.JSONObject;
5 | import cqt.goai.model.market.Depth;
6 | import cqt.goai.model.market.Klines;
7 | import cqt.goai.model.market.Ticker;
8 | import cqt.goai.model.market.Trades;
9 | import cqt.goai.model.trade.Account;
10 | import cqt.goai.model.trade.Orders;
11 | import cqt.goai.run.exchange.Exchange;
12 | import cqt.goai.run.notice.BaseNotice;
13 | import cqt.goai.run.notice.Notice;
14 | import dive.cache.mime.PersistCache;
15 | import dive.common.util.DateUtil;
16 | import dive.common.util.TryUtil;
17 | import org.slf4j.Logger;
18 |
19 | import java.io.*;
20 | import java.util.Date;
21 | import java.util.HashMap;
22 | import java.util.List;
23 | import java.util.Map;
24 | import java.util.function.Function;
25 | import java.util.function.Supplier;
26 |
27 | /**
28 | * 抽象任务类
29 | *
30 | * @author GOAi
31 | */
32 | public class RunTask {
33 | /**
34 | * 是否准备好推送,init方法结束后设置为true
35 | */
36 | private Boolean ready = false;
37 |
38 | /**
39 | * 准备好了才推送
40 | */
41 | final Supplier isReady = () -> this.ready;
42 |
43 | /**
44 | * 原始日志工具
45 | */
46 | private Logger originLog;
47 |
48 | /**
49 | * 用户日志
50 | */
51 | protected Logger log;
52 |
53 | /**
54 | * 通知对象
55 | */
56 | protected Notice notice;
57 |
58 | /**
59 | * 配置名称
60 | */
61 | protected String strategyName;
62 |
63 | /**
64 | * 本实例id
65 | */
66 | protected Integer id;
67 |
68 | /**
69 | * 本实例配置
70 | */
71 | protected JSONObject config;
72 |
73 | /**
74 | * 默认e
75 | */
76 | protected Exchange e;
77 |
78 | /**
79 | * 获取exchange 参数为json字符串
80 | * {"name":"xxx","symbol":"BTC_USD","access":"xxx","secret":"xxx"}
81 | */
82 | protected Function getExchange;
83 |
84 | public RunTask() {}
85 |
86 | /**
87 | * 初始化设置
88 | * @param strategyName 配置名称
89 | * @param id 实例id
90 | * @param config 实例配置
91 | */
92 | void init(String strategyName, Integer id, JSONObject config, Logger log, List notices) {
93 | this.originLog = log;
94 | this.log = new UserLogger(log);
95 | this.notice = new Notice(log, notices);
96 | this.strategyName = strategyName;
97 | this.id = id;
98 | this.config = config;
99 | }
100 |
101 | /**
102 | * 初始化方法
103 | */
104 | protected void init() {}
105 |
106 | /**
107 | * 默认循环
108 | */
109 | protected void loop() {}
110 |
111 | /**
112 | * 退出方法
113 | */
114 | protected void destroy() {}
115 |
116 |
117 | /**
118 | * 重写该方法,可自动对e进行主动推送Ticker
119 | * @param ticker 主动推送的ticker
120 | */
121 | protected void onTicker(Ticker ticker) { }
122 |
123 | /**
124 | * 重写该方法,可自动对e进行主动推送Klines
125 | * K线是1分钟K线,100个
126 | * @param klines K线
127 | */
128 | protected void onKlines(Klines klines) { }
129 |
130 | /**
131 | * 重写该方法,可自动对e进行主动推送Depth
132 | * @param depth 盘口深度
133 | */
134 | protected void onDepth(Depth depth) { }
135 |
136 | /**
137 | * 重写该方法,可自动对e进行主动推送Trades
138 | * @param trades 交易信息
139 | */
140 | protected void onTrades(Trades trades) { }
141 |
142 | /**
143 | * 重写该方法,可自动对e进行主动推送币对余额
144 | * @param account 币对余额
145 | */
146 | protected void onAccount(Account account) { }
147 |
148 | /**
149 | * 重写该方法,可自动对e进行主动推送Orders
150 | * @param orders 订单信息
151 | */
152 | protected void onOrders(Orders orders) { }
153 |
154 |
155 | void setGetExchange(Function getExchange) {
156 | this.getExchange = getExchange;
157 | }
158 |
159 | /**
160 | * 睡眠一段时间
161 | * @param millis 毫秒
162 | */
163 | protected void sleep(long millis) {
164 | try {
165 | Thread.sleep(millis);
166 | } catch (InterruptedException e) {
167 | e.printStackTrace();
168 | }
169 | }
170 |
171 | /**
172 | * 重试获取
173 | * @param supplier 获取函数
174 | * @param times 次数
175 | * @param millis 失败暂停时间
176 | * @param 结果对象
177 | * @return 结果
178 | */
179 | protected T retry(Supplier supplier, int times, long millis) {
180 | return TryUtil.retry(supplier, times, () -> this.sleep(millis));
181 | }
182 |
183 | /**
184 | * 重试获取
185 | * @param supplier 获取函数
186 | * @param times 次数
187 | * @param 结果对象
188 | * @return 结果
189 | */
190 | protected T retry(Supplier supplier, int times) {
191 | return retry(supplier, times, 3000);
192 | }
193 |
194 | /**
195 | * 重试获取
196 | * @param supplier 获取函数
197 | * @param 结果对象
198 | * @return 结果
199 | */
200 | protected T retry(Supplier supplier) {
201 | return retry(supplier, 5, 1000);
202 | }
203 |
204 |
205 | // ============================
206 |
207 | /**
208 | * 全局持久化
209 | */
210 | private static final PersistCache PERSIST_CACHE = new PersistCache<>(".global");
211 |
212 | /**
213 | * 全局保存
214 | * @param key 键
215 | * @param value 值
216 | */
217 | protected void global(String key, Serializable value) {
218 | PERSIST_CACHE.set(key, value);
219 | }
220 |
221 | /**
222 | * 读取值
223 | * @param key 键
224 | * @param 值类型
225 | * @return 读取的值
226 | */
227 | @SuppressWarnings("unchecked")
228 | protected T global(String key) {
229 | return (T) PERSIST_CACHE.get(key);
230 | }
231 |
232 | /**
233 | * 持久化缓存
234 | */
235 | private PersistCache persistCache;
236 |
237 | /**
238 | * 初始化缓存
239 | */
240 | private void initPersistCache() {
241 | if (null == this.persistCache) {
242 | synchronized (this) {
243 | if (null == this.persistCache) {
244 | this.persistCache = new PersistCache<>(".global_" + id);
245 | }
246 | }
247 | }
248 | }
249 |
250 | /**
251 | * 实例内保存
252 | * @param key 键
253 | * @param value 值
254 | */
255 | protected void store(String key, Serializable value) {
256 | this.initPersistCache();
257 | this.persistCache.set(key, value);
258 | }
259 |
260 | /**
261 | * 读取值
262 | * @param key 键
263 | * @param 值类型
264 | * @return 读取的值
265 | */
266 | @SuppressWarnings("unchecked")
267 | protected T store(String key) {
268 | this.initPersistCache();
269 | return (T) this.persistCache.get(key);
270 | }
271 |
272 | /**
273 | * 实时信息保存路径
274 | */
275 | private static final String PATH_REALTIME = ".realtime";
276 | static {
277 | File file = new File(PATH_REALTIME);
278 | if (!file.exists()) {
279 | boolean result = file.mkdir();
280 | if (!result) {
281 | throw new RuntimeException("can not create dir: " + PATH_REALTIME);
282 | }
283 | }
284 | }
285 |
286 | /**
287 | * 存储实时信息
288 | * @param key 键
289 | * @param value 值
290 | */
291 | protected void show(String key, Object value) {
292 | String message = JSON.toJSONString(value);
293 | File file = new File(PATH_REALTIME + "/" + key);
294 | if (!file.exists()) {
295 | try {
296 | boolean result = file.createNewFile();
297 | if (!result) {
298 | throw new RuntimeException("can not create file: " + file.getPath());
299 | }
300 | } catch (IOException e1) {
301 | e1.printStackTrace();
302 | }
303 | }
304 | FileOutputStream os = null;
305 | try {
306 | os = new FileOutputStream(file, false);
307 | os.write(message.getBytes());
308 | } catch (IOException e1) {
309 | e1.printStackTrace();
310 | } finally {
311 | if (null != os) {
312 | try {
313 | os.close();
314 | } catch (IOException e1) {
315 | e1.printStackTrace();
316 | }
317 | }
318 | }
319 | }
320 |
321 | /**
322 | * 默认值保存的实时信息
323 | * @param value 值
324 | */
325 | protected void show(String value) {
326 | Map map = new HashMap<>(2);
327 | map.put("type", "string");
328 | map.put("value", value);
329 | this.show(".default", map);
330 | }
331 |
332 | /**
333 | * 默认值保存的实时表格信息
334 | * @param comment 描述信息
335 | * @param headers 表头
336 | * @param lists 每一行
337 | */
338 | protected void show(String comment, List headers, List