Skip to content

Commit 087ada9

Browse files
完善 README
撤单风控更加完善 删除测试代码 完善类库成员可见性
1 parent 8928328 commit 087ada9

File tree

8 files changed

+135
-66
lines changed

8 files changed

+135
-66
lines changed

README.md

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,129 @@
55

66
[KTrader-Broker-API](https://github.com/ktrader-tech/ktrader-broker-api) 的 CTP 实现。可以作为类库使用,也可以作为插件使用。
77

8+
对底层 CTP 的调用使用了 CTP 的 Java 封装 [JCTP](https://github.com/RationalityFrontline/jctp) ,支持 64 位的 Windows 及 Linux 操作系统。
9+
默认使用的 JCTP 版本为 `6.6.1_P1-1.0.0`,如果需要更换为其它版本,请参考 [Download](#download) 部分。
10+
11+
## 功能特性
12+
* 利用 [Kotlin 协程](https://github.com/Kotlin/kotlinx.coroutines) 将 CTP 的异步接口封装为 [KTrader-Broker-API](https://github.com/ktrader-tech/ktrader-broker-api) 的统一同步调用方式,降低心智负担,提升开发效率
13+
* 内置自成交风控,存在自成交风险的下单请求会本地拒单
14+
* 内置撤单数量风控,单合约日内撤单数达到 499 次后会本地拒绝该合约的撤单请求
15+
* 内置 CTP 流控处理,调用层无需关注任何 CTP 流控信息
16+
* 内置维护本地持仓、订单、成交、Tick 缓存,让查询请求快速返回,不受流控阻塞
17+
* 支持期货及期权的交易(目前尚不支持期权行权及自对冲,仅支持期权交易)
18+
* 自动查询账户真实的手续费率(包括中金所申报手续费)与保证金率,并计算持仓、订单、成交相关的手续费、保证金、冻结资金(期权也支持)
19+
* 封装提供了一些 CTP 原生不支持的功能,如查询当前已订阅行情,Tick 中带有合约交易状态及 Tick 内成交量成交额等
20+
* 网络断开重连时会自动订阅原先已订阅的行情,不用手动重新订阅
21+
* 众所周知,CTP 使用繁琐(如登录流程)且存在很多的坑(如批量订阅行情每34个订阅会丢失一个订阅),但本框架封装后暴露给终端用户的接口是简洁且统一的
22+
* 支持 7x24 小时不间断运行
23+
24+
## 快速入门
25+
这里以类库的使用方式为例,首先参考 [Download](#download) 部分添加类库依赖,然后就可以使用本框架了:
26+
```kotlin
27+
import kotlinx.coroutines.runBlocking
28+
import org.rationalityfrontline.kevent.KEVENT
29+
import org.rationalityfrontline.ktrader.broker.api.*
30+
import org.rationalityfrontline.ktrader.broker.ctp.CtpBrokerApi
31+
32+
fun main() {
33+
println("------------ 启动 ------------")
34+
// 创建 CTP 配置参数
35+
val config = mutableMapOf(
36+
"mdFronts" to listOf( // 行情前置地址
37+
"tcp://0.0.0.0:0",
38+
),
39+
"tdFronts" to listOf( // 交易前置地址
40+
"tcp://0.0.0.0:0",
41+
),
42+
"investorId" to "123456", // 资金账号
43+
"password" to "123456", // 资金账号密码
44+
"brokerId" to "1234", // BROKER ID
45+
"appId" to "rf_ktrader_1.0.0", // APPID
46+
"authCode" to "ASDFGHJKL", // 授权码
47+
"cachePath" to "./build/flow/", // 本地缓存文件存储目录
48+
"disableAutoSubscribe" to false, // 是否禁用自动订阅
49+
"disableFeeCalculation" to false, // 是否禁用费用计算
50+
)
51+
// 创建 CtpBrokerApi 实例
52+
val api = CtpBrokerApi(config, KEVENT)
53+
// 订阅所有事件
54+
KEVENT.subscribeMultiple<BrokerEvent>(BrokerEventType.values().asList()) { event -> runBlocking {
55+
// 处理事件推送
56+
val brokerEvent = event.data
57+
when (brokerEvent.type) {
58+
// Tick 推送
59+
BrokerEventType.MD_TICK -> {
60+
val tick = brokerEvent.data as Tick
61+
// 当某合约触及涨停价时,以跌停价挂1手多单开仓限价委托单
62+
if (tick.lastPrice == tick.todayHighLimitPrice) {
63+
api.insertOrder(tick.code, tick.todayLowLimitPrice, 1, Direction.LONG, OrderOffset.OPEN, OrderType.LIMIT)
64+
}
65+
}
66+
// 其它事件(网络连接、订单回报、成交回报等)
67+
else -> {
68+
println(brokerEvent)
69+
}
70+
}
71+
}}
72+
// 测试 api
73+
runBlocking {
74+
api.connect()
75+
println("CTP 已连接")
76+
println("当前交易日:${api.getTradingDay()}")
77+
println("查询账户资金:")
78+
println(api.queryAssets())
79+
println("查询账户持仓:")
80+
println(api.queryPositions().joinToString("\n"))
81+
println("查询当日全部订单:")
82+
println(api.queryOrders(onlyUnfinished = false).joinToString("\n"))
83+
println("查询当日全部成交记录:")
84+
println(api.queryTrades().joinToString("\n"))
85+
// 订阅行情
86+
api.subscribeMarketData("SHFE.ru2109")
87+
// Thread.currentThread().join() // 如果需要 7x24 小时不间断运行,取消注释此行。(如需主动退出运行请使用 System.exit(0) 或 exitProcess(0))
88+
api.close()
89+
println("CTP 已关闭")
90+
}
91+
// 清空 KEVENT
92+
KEVENT.clear()
93+
println("------------ 退出 ------------")
94+
}
95+
```
96+
97+
## 示例项目
98+
本框架在 examples 目录下提供了一些示例项目帮助使用者快速入门及创建新项目:
99+
100+
[library-basic](https://github.com/ktrader-tech/ktrader-broker-ctp/tree/master/examples/library-basic) :展示以类库方式使用本框架的最简单基础的示例项目
101+
102+
## 使用说明
103+
相较于 [KTrader-Broker-API](https://github.com/ktrader-tech/ktrader-broker-api) 标准接口,本框架需要说明的参数及额外扩展如下:
104+
```text
105+
configKeys:
106+
{
107+
mdFronts: List<String> 行情前置
108+
tdFronts: List<String> 交易前置
109+
investorId: String 投资者资金账号
110+
password: String 投资者资金账号的密码
111+
brokerId: String 经纪商ID
112+
appId: String 交易终端软件的标识码
113+
authCode: String 交易终端软件的授权码
114+
userProductInfo: String 交易终端软件的产品信息
115+
cachePath: String 存贮订阅信息文件等临时文件的目录
116+
disableAutoSubscribe: Boolean 是否禁止自动订阅持仓合约的行情(用于计算合约今仓保证金以及查询持仓时返回最新价及盈亏)
117+
disableFeeCalculation: Boolean 是否禁止计算保证金及手续费(首次计算某个合约的费用时,可能会查询该合约的最新 Tick、保证金率、手续费率,造成额外开销,后续再次计算时则会使用上次查询的结果)
118+
}
119+
methodExtras:
120+
{
121+
subscribeMarketData/unsubscribeMarketData/subscribeAllMarketData/unsubscribeAllMarketData: [isForce: Boolean = false]【是否强制向交易所发送未更改的订阅请求(默认只发送未/已被订阅的标的的订阅请求)】
122+
insertOrder: [minVolume: Int]【最小成交量。仅当下单类型为 OrderType.FAK 时生效】
123+
querySecurity: [queryFee: Boolean = false]【是否查询保证金率及手续费率,如果之前没查过,可能会耗时。当 useCache 为 false 时无效】
124+
}
125+
customMethods:
126+
null
127+
customEvents:
128+
null
129+
```
130+
8131
## Download
9132

10133
**Gradle:**
@@ -16,6 +139,8 @@ repositories {
16139

17140
dependencies {
18141
implementation("org.rationalityfrontline.ktrader:ktrader-broker-ctp:1.1.1")
142+
// 如果需要使用其它版本的 JCTP,取消注释下面一行,并填入自己需要的版本号
143+
// implementation("org.rationalityfrontline:jctp") { version { strictly("6.6.1_P1_CP-1.0.0") } }
19144
}
20145
```
21146

examples/library-basic/app/build.gradle.kts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ repositories {
99

1010
dependencies {
1111
implementation("org.rationalityfrontline.ktrader:ktrader-broker-ctp:1.1.1")
12+
// 如果需要使用其它版本的 JCTP,取消注释下面一行,并填入自己需要的版本号
13+
// implementation("org.rationalityfrontline:jctp") { version { strictly("6.6.1_P1_CP-1.0.0") } }
1214
}
1315

1416
application {

lib/build.gradle.kts

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -47,14 +47,7 @@ dependencies {
4747
implementation(depJctp) {
4848
exclude(group = "org.slf4j", module = "slf4j-api")
4949
}
50-
testImplementation(depCoroutines)
5150
}
52-
testImplementation(depPf4j)
53-
testImplementation(depKtraderBrokerApi)
54-
testImplementation(platform("org.junit:junit-bom:5.7.0"))
55-
testImplementation("org.junit.jupiter:junit-jupiter:5.7.0")
56-
testImplementation("org.slf4j:slf4j-api:1.7.30")
57-
testImplementation("org.slf4j:slf4j-simple:1.7.30")
5851
}
5952

6053
sourceSets.main {

lib/src/main/kotlin/org/rationalityfrontline/ktrader/broker/ctp/CtpBrokerInfo.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ object CtpBrokerInfo {
3636
authCode = config["authCode"] as String? ?: "",
3737
userProductInfo = config["userProductInfo"] as String? ?: "",
3838
cachePath = config["cachePath"] as String? ?: "",
39-
flowSubscribeType = config["flowSubscribeType"] as String? ?: "",
39+
// flowSubscribeType = config["flowSubscribeType"] as String? ?: "",
4040
disableAutoSubscribe = config["disableAutoSubscribe"] == true,
4141
disableFeeCalculation = config["disableFeeCalculation"] == true,
4242
)
@@ -53,7 +53,7 @@ data class CtpConfig(
5353
val authCode: String,
5454
val userProductInfo: String,
5555
val cachePath: String,
56-
val flowSubscribeType: String,
56+
// val flowSubscribeType: String,
5757
val disableAutoSubscribe: Boolean,
5858
val disableFeeCalculation: Boolean,
5959
)

lib/src/main/kotlin/org/rationalityfrontline/ktrader/broker/ctp/CtpMdApi.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import kotlin.coroutines.resumeWithException
1818
import kotlin.coroutines.suspendCoroutine
1919
import kotlin.math.min
2020

21-
class CtpMdApi(val config: CtpConfig, val kEvent: KEvent, val sourceId: String) {
21+
internal class CtpMdApi(val config: CtpConfig, val kEvent: KEvent, val sourceId: String) {
2222
private val mdApi: CThostFtdcMdApi
2323
private val mdSpi: CtpMdSpi
2424
/**

lib/src/main/kotlin/org/rationalityfrontline/ktrader/broker/ctp/CtpTdApi.kt

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ import kotlin.coroutines.resumeWithException
1919
import kotlin.coroutines.suspendCoroutine
2020
import kotlin.math.max
2121

22-
class CtpTdApi(val config: CtpConfig, val kEvent: KEvent, val sourceId: String) {
22+
internal class CtpTdApi(val config: CtpConfig, val kEvent: KEvent, val sourceId: String) {
2323
private val tdApi: CThostFtdcTraderApi
2424
private val tdSpi: CtpTdSpi
2525
/**
@@ -1300,6 +1300,9 @@ class CtpTdApi(val config: CtpConfig, val kEvent: KEvent, val sourceId: String)
13001300
Direction.SHORT -> unfinishedShortOrders.insert(it)
13011301
}
13021302
}
1303+
if (it.status == OrderStatus.CANCELED) {
1304+
cancelStatistics[it.code] = cancelStatistics.getOrDefault(it.code, 0) + 1
1305+
}
13031306
}
13041307
}) { e ->
13051308
resumeRequestsWithException("connect", "查询当日订单失败:$e")

lib/src/main/kotlin/org/rationalityfrontline/ktrader/broker/ctp/utils.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ internal suspend inline fun <T> runWithResultCheck(action: () -> Int, onSuccess:
7878
* 发送用 [runWithResultCheck] 包装过后的 CTP 请求,如果遇到 CTP 柜台处流控,则不断自动间隔 10ms 重新请求
7979
* @param action 用 [runWithResultCheck] 包装过后的 CTP 请求,返回的是请求结果
8080
*/
81-
suspend fun <T> runWithRetry(action: suspend () -> T, onError: (Exception) -> T = { e -> throw e }): T {
81+
internal suspend fun <T> runWithRetry(action: suspend () -> T, onError: (Exception) -> T = { e -> throw e }): T {
8282
return try {
8383
action()
8484
} catch (e: Exception) {

lib/src/test/kotlin/org/rationalityfrontline/ktrader/broker/ctp/CtpTest.kt

Lines changed: 0 additions & 54 deletions
This file was deleted.

0 commit comments

Comments
 (0)