实战前的红线
老板说"进来给你发币去实战",这很正常:交易所希望你用用户的眼睛把业务走一遍。但交易所员工有几条线,跨过去就不是技术问题了。先把线认清,再动手。
内部信息 和 老鼠仓 insider information · front-running
员工交易政策 employee trading policy
公司发的币怎么用
API Key 的安全
合规:你自己要弄清楚的事 compliance
测试网:真操作、假钱
测试网是交易所自己搭的一套"练习场":界面、下单接口、撮合都是真的,钱是假的。D4 的模拟盘是你在本地假装成交,测试网是交易所真的在帮你撮合。
测试网 和 模拟交易 testnet · demo trading
| 常见例子 | 现货 | 合约 | 备注 |
|---|---|---|---|
| Binance | 现货测试网 testnet.binance.vision | 合约测试网 testnet.binancefuture.com | 主要给接口练习用 |
| OKX | 网页和 App 里有模拟交易模式,接口也有对应的模拟环境 | 在正式账户里切换 | |
| Bybit | 有独立的测试网站点 | 单独注册 | |
网址、注册方式、赠送的假币数量和规则经常变,以各家官网文档为准。访问任何交易所网站,都要遵守你所在地和公司的规定。入职以后,以公司自己的测试环境为准:你们自己的交易所一定有测试或预发环境,问带你的同事要地址和测试账号。
测试网的几个"不像真的"的地方
- 选一家同时有现货和合约测试环境、并且有下单接口的
- 拿到假币后,先截图记下初始余额(后面对账用)
- 在测试网上建一个 API Key,按第 1 节的三条规矩设置权限(第 9 节会用)
看懂合约交易界面
各家交易所的永续合约界面布局大同小异,都是七块:顶部行情栏、K 线、订单簿、最近成交、下单面板、仓位栏、下面几个记录标签页。认得这七块,换哪家都不慌。
+---------------------------------------------------------------------+ | [1] BTCUSDT Perp | Last | Mark | Index | Funding + countdown | 24h | +-----------------------------+--------------+------------------------+ | [2] Chart (candles) | [3] Order | [5] Order form | | | book | Isolated/Cross | 5x | | +--------------+ Limit/Market/Stop | | | [4] Recent | Price Qty TP/SL | | | trades | Reduce-only Post-only | | | | [Open Long][Open Short]| +-----------------------------+--------------+------------------------+ | [6] Positions: size entry mark liq margin ratio uPnL ROE | +---------------------------------------------------------------------+ | [7] Open orders | Order history | Trade history | Transaction log | +---------------------------------------------------------------------+
1 顶部行情栏:交易对(Perp = 永续)、最新价、标记价格、指数价格、资金费率和结算倒计时、24 小时成交量 · 2 K 线 · 3 订单簿 · 4 最近成交 · 5 下单面板:逐仓 / 全仓、杠杆、限价 / 市价 / 条件单、价格、数量、止盈止损、只减仓、只做挂单、开多 / 开空 · 6 仓位栏:数量、开仓均价、标记价格、强平价、保证金、保证金率、未实现盈亏、收益率 · 7 当前委托 / 历史委托 / 成交记录 / 资金流水。
几个一看就懂、但容易看错的字段
怎么玩:依次点 1 到 7 号区域,下面会解释这一块看什么、新手最容易看错什么、对应前面哪天。默认选中的是 6 号仓位栏:你会看到强平价 48,241.2 和 ROE +5% 是怎么来的,这两个数和第 5 节练习 1 的算例一致。
现货手动练习
先从现货开始:没有杠杆、没有强平,最适合练"下单以后钱去哪儿了"。每个练习都先把预期算出来,再去界面上核对。
冻结 和 可用余额 frozen / locked · available
available 和 frozen 两列,下单时在一个事务里 available -= x; frozen += x。练习 1:挂一张不会成交的买单,再撤掉
练习 2:小额市价买,看滑点和手续费
练习 3:挂单和吃单的手续费对比,外加一个坑
练习 4:部分成交后撤单
下单面板
资产
委托 / 成交
怎么玩:它会自己一步步播放,像一段录屏。看被框起来的那一块,三个数字"可用 / 冻结 / BTC"每一步怎么变。想停下来看就点"暂停",再用上一步 / 下一步。最后一步把账对平:花掉的 239.96 U 正好换来了 0.004 BTC。
合约手动练习
合约练习的目标只有一个:界面上的每个数,你都能自己算出来,或者说得出为什么算不出来。下面的例子统一用:BTCUSDT 永续,逐仓 5 倍,限价开多 0.01 BTC @ 60,000。
练习 1:开一张最小的逐仓多单,核对强平价
强平价(D2 公式,维持保证金率按 0.5% 举例)= 60,000 × (1 − 1/5) ÷ (1 − 0.5%) = 48,000 ÷ 0.995 ≈ 48,241.21。
练习 2:挂止盈止损
练习 3:等一次资金费结算
练习 4:手动平仓,看已实现盈亏
净结果 = 6 − 0.12 − 0.303 − 0.0606(练习 3 的资金费)= +5.5164 U。和总页 D10 录屏最后一步是同一笔账。
如果改用限价单挂在 60,600 等成交,平仓就是挂单,手续费变成 606 × 0.02% = 0.1212 U,净结果 +5.6982 U。差的这 0.18 U 就是挂单和吃单的区别。
练习 5:切到双向持仓,同时拿一多一空 hedge mode
练习 6:同一张单,逐仓和全仓的强平价差多少
余额 200 U:P = (600 − 200) ÷ (0.01 × 0.995) ≈ 40,201.01,比逐仓的 48,241.21 远得多。
余额 1,000 U:算出来 P 是负数,也就是价格跌到 0 也不会强平,界面上的强平价会显示成"--"。
怎么玩:默认值就是本节练习 1、3、4、6 的算例,打开页面你就能看到 120 U、48,241.21、40,201.01、+6 U、+5%、−0.0606、净 +5.5164(平仓按吃单 0.05%)。去测试网操作完以后,把界面上的真实数字填进来,看看哪一个对不上,再按练习 1 的"对不上怎么办"找原因。资金费按标记价计算,平仓后净结果 = 价差盈亏 − 两次手续费 − 多头付出去的资金费(费率为负时变成加)。
故意犯错练习
真正干过的人,都知道交易所会因为哪些原因拒单。今天故意把每种错都犯一遍,看交易所怎么回。以后线上用户说"下不了单",你第一反应就知道查哪几项。
下面这张表里的原因代码,是我们自己起的名字,不是哪家交易所的真实错误码(各家的错误码和提示文字都不一样)。它们是简化版规则,真实规则各家不同,细节以文档为准。回到总页 D10 的编程题 checkOrder,就是把这张表写成代码:下单前自己先挡一遍,别等交易所来拒。
| 原因代码 | 什么意思 | 在测试网上怎么制造 | 对应 |
|---|---|---|---|
| PRICE_TICK | 价格不是 tickSize 的整数倍 | tickSize 是 0.1 时,价格填 60,000.05 | D2 精度 |
| QTY_LOT | 数量不是 lotSize 的整数倍 | lotSize 是 0.001 时,数量填 0.0015 | D2 精度 |
| MIN_NOTIONAL | 价格 × 数量小于最小下单金额 | 下一张只值几 U 的单 | D2 最小下单金额 |
| PRICE_BAND | 价格离标记价格太远,触发价格保护 | 买价填标记价的 1.2 倍 | D8 价格保护 |
| POST_ONLY_TAKER | 只做挂单的订单一下单就会吃掉对手单 | 勾选只做挂单,买价填卖一或更高 | D2 post-only |
| REDUCE_ONLY | 只减仓的订单没有仓位可减、方向和仓位同向,或者数量超过仓位 | 持有 0.01 多,挂只减仓卖 0.02 | D2 reduce-only |
| MARGIN | 保证金不够:价格 × 数量 ÷ 杠杆 > 可用余额 | 下一张远超余额的开仓单 | D2 初始保证金 |
价格保护 和 拒单
怎么玩:点上面的七个按钮,每个都会填好一张"犯了某种错"的单子,下面列出它会被哪条规则拦下、为什么。也可以自己改价格和数量。一张单可能同时犯好几种错,比如"精度不对"那个会同时报 PRICE_TICK 和 QTY_LOT。检查顺序和总页 checkOrder 编程题一样。
行话对照:别让人一听就是新手
行话不是为了装,是为了说得准。"价格不对"可能指五种不同的问题,"最新价插针、标记价没动"只指一种。
| 场景 | 像新手 | 像干过 |
|---|---|---|
| 买入开仓 | "我买了个合约" | "开了个 BTC 永续多单,逐仓 5 倍,0.01 个" |
| 把空单买回来 | "把卖出去的买回来了" | "平空了" / "空单止盈了" |
| 仓位被强平 | "钱突然没了" | "爆仓了,强平价 48,241,离开仓价 −19.6%" |
| 价格瞬间跳一下又回来 | "价格抽风" | "刚才插了根针,最新价打到 58,000,标记价没怎么动,所以没触发强平" |
| 挂单的人少 | "卖的人不多" | "深度很薄,卖一到卖五加起来才 0.3 个,大市价单滑点会很大" |
| 成交价比看到的差 | "成交价不对" | "吃了三档,均价比卖一差 25 U,滑点 4 个基点左右" |
| 多空之间定期转账 | "要交一笔钱" | "现在费率万一,多头付空头,下次结算还有 3 小时" |
| 两边差价赚钱 | "两个地方价格不一样" | "A 所比 B 所高 30 U,扣掉两边吃单手续费还剩多少?值不值得搬砖" |
| 收益百分比 | "赚了百分之五" | "未实现盈亏 +6 U,ROE +5%,按保证金算的" |
| 下了一张不想吃单的单 | "我下了个单" | "挂了个 post-only 的限价单,会吃单就直接拒" |
| 止盈单防反手 | "设了个卖出" | "止盈单带了 reduce-only,不会反手开空" |
| 费率大小 | "百分之零点零一" | "1 个 bp"(1 个基点,万分之一) |
这几个词再说一遍
公开接口实操:不用账号也能练
公开接口不需要账号,谁都能调。你可以用它拿到真实的盘口、K 线、标记价格和资金费率,把 D2、D3、D9 学的公式用真数据算一遍。
公开接口 和 curl
下面用 Binance 的公开接口举例(路径、参数和返回字段以官方文档为准;把域名换成测试网的,就能拿到测试网的数据):
# 1. 盘口:买卖各 5 档(D3) curl 'https://api.binance.com/api/v3/depth?symbol=BTCUSDT&limit=5' # 2. K 线:最近 3 根 1 小时 K 线(D3),每根是一个数组: # [开盘时间, 开, 高, 低, 收, 成交量, 收盘时间, ...] curl 'https://api.binance.com/api/v3/klines?symbol=BTCUSDT&interval=1h&limit=3' # 3. 交易规则:tickSize、lotSize、最小下单金额都在 filters 里(D2) curl 'https://api.binance.com/api/v3/exchangeInfo?symbol=BTCUSDT' # 4. 永续合约:标记价格、指数价格、当前资金费率、下次结算时间(D2) curl 'https://fapi.binance.com/fapi/v1/premiumIndex?symbol=BTCUSDT' # 5. 永续合约:最近 3 次资金费率历史(D9 资金费率套利要用) curl 'https://fapi.binance.com/fapi/v1/fundingRate?symbol=BTCUSDT&limit=3'
1 depth 盘口 · 2 klines K 线 · 3 exchangeInfo 交易规则 · 4 premiumIndex 标记价格和资金费率 · 5 fundingRate 资金费率历史。网络连不上时,先确认你所在的网络环境是否允许访问,不要为了练习去绕公司的网络规定。
exchangeInfo:下单前必看的"交易规则"
怎么玩:默认是一条示例返回,表里逐个解释字段,下面算出"持 0.01 BTC 多单,下次结算要付多少资金费、还剩多久"。把 lastFundingRate 改成负数(比如 "-0.00010000"),看付钱的方向反过来。字段名是 Binance 的叫法,各家不同。
Java 版本(JDK 11 起自带 HttpClient;解析 JSON 用 D6 已经加过依赖的 Jackson,它是 Java 里最常用的 JSON 库):
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.math.BigDecimal;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
public class PublicRest {
public static void main(String[] args) throws Exception {
HttpClient http = HttpClient.newHttpClient();
HttpRequest req = HttpRequest.newBuilder(
URI.create("https://fapi.binance.com/fapi/v1/premiumIndex?symbol=BTCUSDT")).build();
String body = http.send(req, HttpResponse.BodyHandlers.ofString()).body();
JsonNode n = new ObjectMapper().readTree(body);
BigDecimal mark = new BigDecimal(n.path("markPrice").asText()); // 价格是字符串,直接进 BigDecimal
BigDecimal rate = new BigDecimal(n.path("lastFundingRate").asText());
BigDecimal qty = new BigDecimal("0.01");
// 资金费 = 仓位价值 × 费率;正数表示多头付钱
System.out.println("标记价 " + mark + ",0.01 BTC 多单下次资金费 " + mark.multiply(qty).multiply(rate));
}
}
签名接口:用 Java 真的下一张单
下单、查单、撤单要证明"这个请求真是你发的,而且中途没被改过"。办法是签名:用只有你和交易所知道的密钥,给请求内容算一个指纹,一起发过去。
API Key 和 密钥 API key · secret
签名 和 HMAC-SHA256 signature
symbol=BTCUSDT&side=BUY&…。你签的字符串和你发出去的必须一个字符都不差,顺序也不能变。NhqPtmdS…Tj0j,参数 symbol=LTCBTC&side=BUY&type=LIMIT&timeInForce=GTC&quantity=1&price=0.1&recvWindow=5000×tamp=1499827319559,签名结果是 c8db5682…bd6b71(64 个十六进制字符)。把 quantity=1 改成 2,签名变成 aca62923…be3ea3,完全不一样。Mac.getInstance("HmacSHA256")。和你做过的"回调验签""接口防篡改"是同一个东西。自己在终端验证一下(输出应该是 c8db…6b71):
echo -n "symbol=LTCBTC&side=BUY&type=LIMIT&timeInForce=GTC&quantity=1&price=0.1&recvWindow=5000×tamp=1499827319559" \ | openssl dgst -sha256 -hmac "NhqPtmdSJYdKjVHjA7PZj4Mge3R5YNiP1e3UZjInClVN65XAbvqqM6A7H5fATj0j"
echo -n:不要在末尾加换行(多一个换行,签名就不对了,这是最常见的签名错误之一)。这对示例密钥是文档公开的,不是任何人的真实密钥。
timestamp 和 recvWindow
怎么玩:自动播放七步,被框起来的是当前这一步,灰的是还没走到的。重点看第 3 步和第 6 步:你这边和交易所那边用同一个密钥、同一串参数各算一次,结果一样才放行。第 7 步演示改了一个字符,签名就完全不同。
限频 rate limit
下面的 TestnetClient 是 D5 Exchange 接口的真实实现,连的是现货测试网。上层的 placeSafely、Reconciler 一行都不用改,这就是 D5 先抽接口的好处。Order、Side、RemoteOrder 用 D4 / D5 定义好的,名字和参数顺序都一样。
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.io.IOException;
import java.math.BigDecimal;
import java.net.URI;
import java.net.URLEncoder;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.net.http.HttpTimeoutException;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.util.*;
import java.util.concurrent.TimeoutException;
import java.util.stream.Collectors;
/** 现货测试网上的 Exchange 实现。以 Binance 现货测试网为例,路径、参数、错误码以官方文档为准 */
public class TestnetClient implements Exchange {
private static final String BASE = "https://testnet.binance.vision";
private final String apiKey = System.getenv("TESTNET_API_KEY"); // 密钥只从环境变量读,绝不写进代码
private final String secret = System.getenv("TESTNET_API_SECRET");
private final HttpClient http = HttpClient.newBuilder().connectTimeout(Duration.ofSeconds(3)).build();
private final ObjectMapper json = new ObjectMapper();
private final String symbol;
public TestnetClient(String symbol) {
if (apiKey == null || secret == null)
throw new IllegalStateException("先设置环境变量 TESTNET_API_KEY 和 TESTNET_API_SECRET");
this.symbol = symbol;
}
@Override
public Order place(Order o) throws TimeoutException {
Map<String, String> p = new LinkedHashMap<>(); // LinkedHashMap:保持参数顺序
p.put("symbol", symbol);
p.put("side", o.side().name()); // BUY / SELL
p.put("type", "LIMIT");
p.put("timeInForce", "GTC"); // 一直挂着,直到成交或撤单
p.put("quantity", o.qty().toPlainString()); // toPlainString:避免 1E-3 这种科学计数法
p.put("price", o.price().toPlainString());
p.put("newClientOrderId", o.clientOid()); // 我们自己的编号,D5 的 ClientOid.of(...)
send("POST", "/api/v3/order", p); // 被拒会抛异常;超时抛 TimeoutException
return o;
}
@Override
public Optional<RemoteOrder> query(String clientOid) {
Map<String, String> p = new LinkedHashMap<>();
p.put("symbol", symbol);
p.put("origClientOrderId", clientOid); // 按我们的编号查,不需要交易所的 orderId
try {
return Optional.of(toRemote(send("GET", "/api/v3/order", p)));
} catch (NotFound e) {
return Optional.empty(); // 交易所确实没有这张单
} catch (TimeoutException e) {
// 查询超时 ≠ 查不到。绝不能当成"没有"去重下,交给上层稍后重试
throw new IllegalStateException("查单超时,稍后重试: " + clientOid, e);
}
}
@Override
public void cancel(String clientOid) {
Map<String, String> p = new LinkedHashMap<>();
p.put("symbol", symbol);
p.put("origClientOrderId", clientOid);
try {
send("DELETE", "/api/v3/order", p);
} catch (NotFound e) {
// 已经成交或已经撤掉了:撤单是幂等的,不算错
} catch (TimeoutException e) {
throw new IllegalStateException("撤单超时,稍后按编号查一下再决定: " + clientOid, e);
}
}
@Override
public List<RemoteOrder> openOrders() {
Map<String, String> p = new LinkedHashMap<>();
p.put("symbol", symbol);
try {
List<RemoteOrder> out = new ArrayList<>();
for (JsonNode n : send("GET", "/api/v3/openOrders", p)) out.add(toRemote(n));
return out;
} catch (TimeoutException e) {
throw new IllegalStateException("查挂单超时,稍后重试", e);
}
}
/** 统一发请求:加 recvWindow 和 timestamp → 签名 → 带上 API Key 发出去 */
private JsonNode send(String method, String path, Map<String, String> params) throws TimeoutException {
params.put("recvWindow", "5000");
params.put("timestamp", String.valueOf(System.currentTimeMillis()));
String query = params.entrySet().stream()
.map(e -> e.getKey() + "=" + URLEncoder.encode(e.getValue(), StandardCharsets.UTF_8))
.collect(Collectors.joining("&"));
String url = BASE + path + "?" + query + "&signature=" + hmacSha256(secret, query); // 签的和发的是同一串
HttpRequest req = HttpRequest.newBuilder(URI.create(url))
.timeout(Duration.ofSeconds(3))
.header("X-MBX-APIKEY", apiKey)
.method(method, HttpRequest.BodyPublishers.noBody())
.build();
try {
HttpResponse<String> resp = http.send(req, HttpResponse.BodyHandlers.ofString());
JsonNode body = json.readTree(resp.body());
if (resp.statusCode() == 200) return body;
int code = body.path("code").asInt();
if (code == -2013 || code == -2011) throw new NotFound(); // Binance:订单不存在 / 撤单时找不到这张单
throw new IllegalStateException("交易所拒绝: " + resp.body());
} catch (HttpTimeoutException e) {
throw new TimeoutException("请求超时,不知道成没成: " + path); // 交给 D5 的 placeSafely 按编号去查
} catch (IOException e) {
throw new IllegalStateException(e);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new IllegalStateException(e);
}
}
static String hmacSha256(String secret, String data) {
try {
Mac mac = Mac.getInstance("HmacSHA256");
mac.init(new SecretKeySpec(secret.getBytes(StandardCharsets.UTF_8), "HmacSHA256"));
return HexFormat.of().formatHex(mac.doFinal(data.getBytes(StandardCharsets.UTF_8))); // HexFormat:JDK 17
} catch (Exception e) {
throw new IllegalStateException(e);
}
}
/** 交易所的返回 → D5 的 RemoteOrder;状态翻译成 D5 用的四种 */
private RemoteOrder toRemote(JsonNode n) {
String oid = n.path("clientOrderId").asText();
Order o = new Order(oid, Side.valueOf(n.path("side").asText()),
new BigDecimal(n.path("price").asText()), new BigDecimal(n.path("origQty").asText()), levelOf(oid));
String status = switch (n.path("status").asText()) {
case "NEW" -> "OPEN";
case "PARTIALLY_FILLED" -> "PARTIALLY_FILLED";
case "FILLED" -> "FILLED";
default -> "CANCELED"; // CANCELED / EXPIRED / REJECTED 都算"不在挂了"
};
return new RemoteOrder(o, status);
}
/** G1-3-B-7 → 3;不是我们起的编号(比如网页上手动下的单)返回 -1 */
private static int levelOf(String oid) {
String[] a = oid.split("-");
try { return a.length == 4 ? Integer.parseInt(a[1]) : -1; }
catch (NumberFormatException e) { return -1; }
}
private static class NotFound extends RuntimeException {}
}
这段代码要注意的四件事
place 超时只抛 TimeoutException,不重试、不猜。D5 的 placeSafely 接住它,用同一个 clientOid 去 query,查到了就接管,查不到才用同一个编号重下。Optional.empty(),上层会以为"没下成功"去重下,而第一张单其实已经挂上了。跑一下:挂一张不会成交的单 → 查 → 撤 → 再查。价格取测试网当前价的九折左右,数量用规则允许的值。
import java.math.BigDecimal;
public class TestnetDemo {
public static void main(String[] args) throws Exception {
Exchange ex = new TestnetClient("BTCUSDT");
String oid = ClientOid.of("T1", 0, Side.BUY, 1); // "T1-0-B-1",D5 的编号规则
Order o = new Order(oid, Side.BUY, new BigDecimal("54000.00"), new BigDecimal("0.00100"), 0);
ex.place(o);
System.out.println(ex.query(oid)); // 期望:status=OPEN
ex.cancel(oid);
System.out.println(ex.query(oid)); // 期望:status=CANCELED
ex.cancel(oid); // 再撤一次:不报错(幂等)
}
}
// 运行前:export TESTNET_API_KEY=... export TESTNET_API_SECRET=...(只在终端里设,不写进任何文件)
跑完以后去测试网网页的历史委托里,找到编号 T1-0-B-1 的那张单,状态应该是已撤销。网页上能看到你程序下的单,说明整条链路通了。
私有推送,以及把 D4 机器人接到测试网
D3、D4 用的是公开推送,所有人收到的都一样。你自己的订单什么时候成交,要靠私有推送来告诉你。这一节是进阶,可以选做。
私有推送 和 用户数据流 user data stream
BotLoop。轮询:先用最笨但最稳的办法 polling
接入步骤(每一步都能单独跑通再做下一步):
- 下单换实现:D4 的
BotLoop里直接用的是PaperMatcher。把"下单"改成调Exchange.place,传进去的是new TestnetClient("BTCUSDT");PaperMatcher.onTrade不再需要,成交改由下一步产生。 - 成交从交易所来:第一版用轮询,发现 FILLED 就包成 D4 的
Fill放进BotLoop的队列(代码见下)。 - 去重:同一张单可能被连续两次轮询都看到 FILLED,事件循环里按 clientOid 去重,处理过的直接跳过(D6、D7 的幂等)。
- 启动先对账:每次启动先跑 D5 的
Reconciler,本地和交易所对齐了再开始挂新单。 - 一键停:写一个"撤掉本网格所有挂单"的方法(用
openOrders+ClientOid.belongsTo过滤),跑实盘前必须有。
// 第一版成交来源:每 2 秒轮询一次本地认为还在挂的单
ScheduledExecutorService poller = Executors.newSingleThreadScheduledExecutor();
poller.scheduleAtFixedRate(() -> {
for (Order o : List.copyOf(local.openOrders())) { // local 是 D5 的 LocalBook
exchange.query(o.clientOid()).ifPresent(r -> {
if (r.status().equals("FILLED"))
loop.submit(new Fill(o.clientOid(), o.side(), o.level(), o.price(), o.qty(), "FILLED"));
}); // 只放进队列,不在这个线程里改状态(D4 第 8 节)
}
}, 0, 2, TimeUnit.SECONDS);
这段代码里查询超时会抛异常,而 scheduleAtFixedRate 遇到没捕获的异常会悄悄停止后续执行。真实代码要在循环体外包一层 try-catch,打日志后继续。
实操清单,以及入职第一周怎么开口
按下面的顺序做,每一步都记下一个数字。数字填到总页 D10 的实操记录表里(保存在你这台浏览器上),入职以后拿到公司的币再走一遍,对比两边。
| # | 做什么 | 记下哪个数 | 对应本页 |
|---|---|---|---|
| 1 | 测试网领假币,截图初始余额 | 初始 USDT 余额 | 第 2 节 |
| 2 | 现货:挂不会成交的买单 → 撤单 | 冻结金额 | 第 4 节练习 1 |
| 3 | 现货:小额市价买 | 成交均价、滑点 | 第 4 节练习 2 |
| 4 | 合约:逐仓 5 倍开最小多单 | 开仓价、界面强平价、自己算的强平价、保证金 | 第 5 节练习 1 |
| 5 | 合约:设止盈止损 | 止盈、止损各自的预期盈亏 | 第 5 节练习 2 |
| 6 | 合约:持仓跨过一次资金费结算 | 资金费扣款 | 第 5 节练习 3 |
| 7 | 合约:手动平仓 | 已实现盈亏、开仓手续费、平仓手续费 | 第 5 节练习 4 |
| 8 | 故意犯错:至少制造 3 种拒单 | 被拒原因代码(用第 6 节的名字记) | 第 6 节 |
| 9 | Java:TestnetDemo 下单 → 查 → 撤 | 网页上看到的那张单的编号 | 第 9 节 |
| 10 | 带着计划下一单:先写交易计划、用以损定仓算数量,再用计划委托 + 止盈止损执行 | 计划风险(U)、实际盈亏(U)、R 倍数 | D12 第 3、9、10 节 |
记录表在这里:七日冲刺 D10 → 实操记录表。每一行分"自己先算"和"界面实际"两栏,差不到 0.5% 会自动显示对上了。记录项是:现货限价买单冻结金额、合约开仓保证金、强平价、某一时刻的未实现盈亏、一次资金费扣款、已实现盈亏、开仓手续费、平仓手续费,最后一行写被拒过的原因代码。
入职第一周怎么开口
这些问题的共同点:先说你自己做过什么、看到了什么数,再问一个具体的点。比"能给我讲讲系统架构吗"好回答得多,也显得你是做过功课的。
词典和自测
今天用到的词,包括前几天学过的,这里都能查到。