Compare commits

...

13 Commits

Author SHA1 Message Date
ddrwode
d1deb97843 加入一个回测, 2026-03-06 16:40:04 +08:00
ddrwode
b2515bb7ce 加入一个回测, 2026-03-06 10:36:18 +08:00
ddrwode
c473c738a3 加入一个回测, 2026-03-05 18:29:05 +08:00
ddrwode
79f4e03d6a 加入一个回测, 2026-03-05 17:46:34 +08:00
ddrwode
6b1a707e3f 加入一个回测, 2026-03-05 15:51:31 +08:00
ddrwode
3a6678089c 加入一个回测, 2026-03-05 15:44:55 +08:00
ddrwode
5530a008b3 加入一个回测, 2026-03-05 12:56:09 +08:00
ddrwode
01b6a0fdcb 加入一个回测, 2026-03-05 12:51:12 +08:00
ddrwode
b74449989b 加入一个回测, 2026-03-04 18:02:27 +08:00
ddrwode
89bc5a7a00 加入一个回测, 2026-03-04 18:02:16 +08:00
ddrwode
4b5a66d588 加入一个回测, 2026-03-04 18:02:02 +08:00
ddrwode
de22d1f3ae 加入一个回测, 2026-03-04 16:48:24 +08:00
ddrwode
2a98d431a9 第一版策略 2026-03-03 17:20:42 +08:00
38 changed files with 834755 additions and 1173 deletions

2710
1.html Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,145 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<title>ETHUSDT 2026-03-02 K线 + 布林带</title>
<style>
html,
body {
margin: 0;
padding: 0;
width: 100%;
height: 100%;
background: #111;
color: #eee;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
"Helvetica Neue", Arial, "Noto Sans", sans-serif;
}
#chart {
width: 100%;
height: 100%;
}
</style>
<script src="https://cdn.jsdelivr.net/npm/echarts@5/dist/echarts.min.js"></script>
</head>
<body>
<div id="chart"></div>
<script>
async function main() {
// 从同目录加载由 Python 导出的 JSON 数据
const resp = await fetch("bb_chart_2026_03_02_data.json");
const raw = await resp.json();
const categoryData = [];
const klineData = [];
const upper = [];
const mid = [];
const lower = [];
for (const k of raw) {
const d = new Date(k.timestamp);
const label = `${d.getHours().toString().padStart(2, "0")}:${d
.getMinutes()
.toString()
.padStart(2, "0")}`;
categoryData.push(label);
klineData.push([k.open, k.close, k.low, k.high]);
upper.push(k.bb_upper);
mid.push(k.bb_mid);
lower.push(k.bb_lower);
}
const chartDom = document.getElementById("chart");
const chart = echarts.init(chartDom, null, { renderer: "canvas" });
const option = {
backgroundColor: "#111",
tooltip: {
trigger: "axis",
axisPointer: { type: "cross" },
},
axisPointer: {
link: [{ xAxisIndex: "all" }],
},
grid: {
left: "3%",
right: "3%",
top: "6%",
bottom: "5%",
containLabel: true,
},
xAxis: {
type: "category",
data: categoryData,
scale: true,
boundaryGap: true,
axisLine: { lineStyle: { color: "#888" } },
axisLabel: { color: "#ccc" },
},
yAxis: {
scale: true,
axisLine: { lineStyle: { color: "#888" } },
splitLine: { lineStyle: { color: "#333" } },
axisLabel: { color: "#ccc" },
},
dataZoom: [
{
type: "inside",
start: 0,
end: 100,
},
{
type: "slider",
start: 0,
end: 100,
height: 20,
},
],
series: [
{
name: "K线",
type: "candlestick",
data: klineData,
itemStyle: {
color: "#26a69a",
color0: "#ef5350",
borderColor: "#26a69a",
borderColor0: "#ef5350",
},
},
{
name: "BB上轨",
type: "line",
data: upper,
symbol: "none",
lineStyle: { color: "#ff9800", width: 1 },
},
{
name: "BB中轨",
type: "line",
data: mid,
symbol: "none",
lineStyle: { color: "#fff", width: 1, type: "dashed" },
},
{
name: "BB下轨",
type: "line",
data: lower,
symbol: "none",
lineStyle: { color: "#ff9800", width: 1 },
},
],
};
chart.setOption(option);
window.addEventListener("resize", () => chart.resize());
}
main().catch((e) => {
console.error(e);
alert("加载数据失败,请确认 bb_chart_2026_03_02_data.json 已生成并与本 HTML 同目录。");
});
</script>
</body>
</html>

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,43 @@
178.5,305.0,5.2791790570783465e+19,2.6395895285391733e+19,33680,56.24109263657957,21.175874581988747,4.8783963655773225e+19
178.5,305.5,5.2791790570783465e+19,2.6395895285391733e+19,33680,56.24109263657957,21.175874581988747,4.8783963655773225e+19
178.5,306.0,5.2791790570783465e+19,2.6395895285391733e+19,33680,56.24109263657957,21.175874581988747,4.8783963655773225e+19
178.5,306.5,5.2791790570783465e+19,2.6395895285391733e+19,33680,56.24109263657957,21.175874581988747,4.8783963655773225e+19
178.5,307.0,5.2791790570783465e+19,2.6395895285391733e+19,33680,56.24109263657957,21.175874581988747,4.8783963655773225e+19
178.5,307.5,5.2791790570783465e+19,2.6395895285391733e+19,33680,56.24109263657957,21.175874581988747,4.8783963655773225e+19
178.5,308.0,5.2791790570783465e+19,2.6395895285391733e+19,33680,56.24109263657957,21.175874581988747,4.8783963655773225e+19
178.5,308.5,5.2791790570783465e+19,2.6395895285391733e+19,33680,56.24109263657957,21.175874581988747,4.8783963655773225e+19
178.5,309.0,5.2791790570783465e+19,2.6395895285391733e+19,33680,56.24109263657957,21.175874581988747,4.8783963655773225e+19
178.5,309.5,5.2791790570783465e+19,2.6395895285391733e+19,33680,56.24109263657957,21.175874581988747,4.8783963655773225e+19
178.5,310.0,5.2791790570783465e+19,2.6395895285391733e+19,33680,56.24109263657957,21.175874581988747,4.8783963655773225e+19
178.5,310.5,5.2791790570783465e+19,2.6395895285391733e+19,33680,56.24109263657957,21.175874581988747,4.8783963655773225e+19
178.5,311.0,5.2791790570783465e+19,2.6395895285391733e+19,33680,56.24109263657957,21.175874581988747,4.8783963655773225e+19
178.5,311.5,5.2791790570783465e+19,2.6395895285391733e+19,33680,56.24109263657957,21.175874581988747,4.8783963655773225e+19
178.5,312.0,5.2791790570783465e+19,2.6395895285391733e+19,33680,56.24109263657957,21.175874581988747,4.8783963655773225e+19
178.5,312.5,5.2791790570783465e+19,2.6395895285391733e+19,33680,56.24109263657957,21.175874581988747,4.8783963655773225e+19
178.5,313.0,5.2791790570783465e+19,2.6395895285391733e+19,33680,56.24109263657957,21.175874581988747,4.8783963655773225e+19
178.5,313.5,5.2791790570783465e+19,2.6395895285391733e+19,33680,56.24109263657957,21.175874581988747,4.8783963655773225e+19
178.5,314.0,5.2791790570783465e+19,2.6395895285391733e+19,33680,56.24109263657957,21.175874581988747,4.8783963655773225e+19
178.5,314.5,5.2791790570783465e+19,2.6395895285391733e+19,33680,56.24109263657957,21.175874581988747,4.8783963655773225e+19
178.5,315.0,5.2791790570783465e+19,2.6395895285391733e+19,33680,56.24109263657957,21.175874581988747,4.8783963655773225e+19
178.5,315.5,5.2791790570783465e+19,2.6395895285391733e+19,33680,56.24109263657957,21.175874581988747,4.8783963655773225e+19
178.5,316.0,5.2791790570783465e+19,2.6395895285391733e+19,33680,56.24109263657957,21.175874581988747,4.8783963655773225e+19
178.5,316.5,5.2791790570783465e+19,2.6395895285391733e+19,33680,56.24109263657957,21.175874581988747,4.8783963655773225e+19
178.5,317.0,5.2791790570783465e+19,2.6395895285391733e+19,33680,56.24109263657957,21.175874581988747,4.8783963655773225e+19
178.5,317.5,5.2791790570783465e+19,2.6395895285391733e+19,33680,56.24109263657957,21.175874581988747,4.8783963655773225e+19
178.5,318.0,5.2791790570783465e+19,2.6395895285391733e+19,33680,56.24109263657957,21.175874581988747,4.8783963655773225e+19
178.5,318.5,5.2791790570783465e+19,2.6395895285391733e+19,33680,56.24109263657957,21.175874581988747,4.8783963655773225e+19
178.5,319.0,5.2791790570783465e+19,2.6395895285391733e+19,33680,56.24109263657957,21.175874581988747,4.8783963655773225e+19
178.5,319.5,5.2791790570783465e+19,2.6395895285391733e+19,33680,56.24109263657957,21.175874581988747,4.8783963655773225e+19
178.5,320.0,5.2791790570783465e+19,2.6395895285391733e+19,33680,56.24109263657957,21.175874581988747,4.8783963655773225e+19
178.5,320.5,5.2791790570783465e+19,2.6395895285391733e+19,33680,56.24109263657957,21.175874581988747,4.8783963655773225e+19
178.5,321.0,5.2791790570783465e+19,2.6395895285391733e+19,33680,56.24109263657957,21.175874581988747,4.8783963655773225e+19
178.5,321.5,5.2791790570783465e+19,2.6395895285391733e+19,33680,56.24109263657957,21.175874581988747,4.8783963655773225e+19
178.5,322.0,5.2791790570783465e+19,2.6395895285391733e+19,33680,56.24109263657957,21.175874581988747,4.8783963655773225e+19
178.5,322.5,5.2791790570783465e+19,2.6395895285391733e+19,33680,56.24109263657957,21.175874581988747,4.8783963655773225e+19
178.5,323.0,5.2791790570783465e+19,2.6395895285391733e+19,33680,56.24109263657957,21.175874581988747,4.8783963655773225e+19
178.5,323.5,5.2791790570783465e+19,2.6395895285391733e+19,33680,56.24109263657957,21.175874581988747,4.8783963655773225e+19
178.5,324.0,5.2791790570783465e+19,2.6395895285391733e+19,33680,56.24109263657957,21.175874581988747,4.8783963655773225e+19
178.5,324.5,5.2791790570783465e+19,2.6395895285391733e+19,33680,56.24109263657957,21.175874581988747,4.8783963655773225e+19
178.5,325.0,5.2791790570783465e+19,2.6395895285391733e+19,33680,56.24109263657957,21.175874581988747,4.8783963655773225e+19
178.5,325.5,5.2791790570783465e+19,2.6395895285391733e+19,33680,56.24109263657957,21.175874581988747,4.8783963655773225e+19
178.5,326.0,5.2791790570783465e+19,2.6395895285391733e+19,33680,56.24109263657957,21.175874581988747,4.8783963655773225e+19
1 178.5 305.0 5.2791790570783465e+19 2.6395895285391733e+19 33680 56.24109263657957 21.175874581988747 4.8783963655773225e+19
2 178.5 305.5 5.2791790570783465e+19 2.6395895285391733e+19 33680 56.24109263657957 21.175874581988747 4.8783963655773225e+19
3 178.5 306.0 5.2791790570783465e+19 2.6395895285391733e+19 33680 56.24109263657957 21.175874581988747 4.8783963655773225e+19
4 178.5 306.5 5.2791790570783465e+19 2.6395895285391733e+19 33680 56.24109263657957 21.175874581988747 4.8783963655773225e+19
5 178.5 307.0 5.2791790570783465e+19 2.6395895285391733e+19 33680 56.24109263657957 21.175874581988747 4.8783963655773225e+19
6 178.5 307.5 5.2791790570783465e+19 2.6395895285391733e+19 33680 56.24109263657957 21.175874581988747 4.8783963655773225e+19
7 178.5 308.0 5.2791790570783465e+19 2.6395895285391733e+19 33680 56.24109263657957 21.175874581988747 4.8783963655773225e+19
8 178.5 308.5 5.2791790570783465e+19 2.6395895285391733e+19 33680 56.24109263657957 21.175874581988747 4.8783963655773225e+19
9 178.5 309.0 5.2791790570783465e+19 2.6395895285391733e+19 33680 56.24109263657957 21.175874581988747 4.8783963655773225e+19
10 178.5 309.5 5.2791790570783465e+19 2.6395895285391733e+19 33680 56.24109263657957 21.175874581988747 4.8783963655773225e+19
11 178.5 310.0 5.2791790570783465e+19 2.6395895285391733e+19 33680 56.24109263657957 21.175874581988747 4.8783963655773225e+19
12 178.5 310.5 5.2791790570783465e+19 2.6395895285391733e+19 33680 56.24109263657957 21.175874581988747 4.8783963655773225e+19
13 178.5 311.0 5.2791790570783465e+19 2.6395895285391733e+19 33680 56.24109263657957 21.175874581988747 4.8783963655773225e+19
14 178.5 311.5 5.2791790570783465e+19 2.6395895285391733e+19 33680 56.24109263657957 21.175874581988747 4.8783963655773225e+19
15 178.5 312.0 5.2791790570783465e+19 2.6395895285391733e+19 33680 56.24109263657957 21.175874581988747 4.8783963655773225e+19
16 178.5 312.5 5.2791790570783465e+19 2.6395895285391733e+19 33680 56.24109263657957 21.175874581988747 4.8783963655773225e+19
17 178.5 313.0 5.2791790570783465e+19 2.6395895285391733e+19 33680 56.24109263657957 21.175874581988747 4.8783963655773225e+19
18 178.5 313.5 5.2791790570783465e+19 2.6395895285391733e+19 33680 56.24109263657957 21.175874581988747 4.8783963655773225e+19
19 178.5 314.0 5.2791790570783465e+19 2.6395895285391733e+19 33680 56.24109263657957 21.175874581988747 4.8783963655773225e+19
20 178.5 314.5 5.2791790570783465e+19 2.6395895285391733e+19 33680 56.24109263657957 21.175874581988747 4.8783963655773225e+19
21 178.5 315.0 5.2791790570783465e+19 2.6395895285391733e+19 33680 56.24109263657957 21.175874581988747 4.8783963655773225e+19
22 178.5 315.5 5.2791790570783465e+19 2.6395895285391733e+19 33680 56.24109263657957 21.175874581988747 4.8783963655773225e+19
23 178.5 316.0 5.2791790570783465e+19 2.6395895285391733e+19 33680 56.24109263657957 21.175874581988747 4.8783963655773225e+19
24 178.5 316.5 5.2791790570783465e+19 2.6395895285391733e+19 33680 56.24109263657957 21.175874581988747 4.8783963655773225e+19
25 178.5 317.0 5.2791790570783465e+19 2.6395895285391733e+19 33680 56.24109263657957 21.175874581988747 4.8783963655773225e+19
26 178.5 317.5 5.2791790570783465e+19 2.6395895285391733e+19 33680 56.24109263657957 21.175874581988747 4.8783963655773225e+19
27 178.5 318.0 5.2791790570783465e+19 2.6395895285391733e+19 33680 56.24109263657957 21.175874581988747 4.8783963655773225e+19
28 178.5 318.5 5.2791790570783465e+19 2.6395895285391733e+19 33680 56.24109263657957 21.175874581988747 4.8783963655773225e+19
29 178.5 319.0 5.2791790570783465e+19 2.6395895285391733e+19 33680 56.24109263657957 21.175874581988747 4.8783963655773225e+19
30 178.5 319.5 5.2791790570783465e+19 2.6395895285391733e+19 33680 56.24109263657957 21.175874581988747 4.8783963655773225e+19
31 178.5 320.0 5.2791790570783465e+19 2.6395895285391733e+19 33680 56.24109263657957 21.175874581988747 4.8783963655773225e+19
32 178.5 320.5 5.2791790570783465e+19 2.6395895285391733e+19 33680 56.24109263657957 21.175874581988747 4.8783963655773225e+19
33 178.5 321.0 5.2791790570783465e+19 2.6395895285391733e+19 33680 56.24109263657957 21.175874581988747 4.8783963655773225e+19
34 178.5 321.5 5.2791790570783465e+19 2.6395895285391733e+19 33680 56.24109263657957 21.175874581988747 4.8783963655773225e+19
35 178.5 322.0 5.2791790570783465e+19 2.6395895285391733e+19 33680 56.24109263657957 21.175874581988747 4.8783963655773225e+19
36 178.5 322.5 5.2791790570783465e+19 2.6395895285391733e+19 33680 56.24109263657957 21.175874581988747 4.8783963655773225e+19
37 178.5 323.0 5.2791790570783465e+19 2.6395895285391733e+19 33680 56.24109263657957 21.175874581988747 4.8783963655773225e+19
38 178.5 323.5 5.2791790570783465e+19 2.6395895285391733e+19 33680 56.24109263657957 21.175874581988747 4.8783963655773225e+19
39 178.5 324.0 5.2791790570783465e+19 2.6395895285391733e+19 33680 56.24109263657957 21.175874581988747 4.8783963655773225e+19
40 178.5 324.5 5.2791790570783465e+19 2.6395895285391733e+19 33680 56.24109263657957 21.175874581988747 4.8783963655773225e+19
41 178.5 325.0 5.2791790570783465e+19 2.6395895285391733e+19 33680 56.24109263657957 21.175874581988747 4.8783963655773225e+19
42 178.5 325.5 5.2791790570783465e+19 2.6395895285391733e+19 33680 56.24109263657957 21.175874581988747 4.8783963655773225e+19
43 178.5 326.0 5.2791790570783465e+19 2.6395895285391733e+19 33680 56.24109263657957 21.175874581988747 4.8783963655773225e+19

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,157 @@
timestamp,action,price,size,margin,fee,capital,reason,pnl
2026-03-01 01:35,开short,1970.895742,0.02536917551471376,1.0,0.0024999999999999996,98.9975,触上轨开空,
2026-03-01 01:50,平仓100%,2022.33,0.02536917551471376,,0.0025652422359335528,98.690090039093,止损,-1.3048447186710703
2026-03-01 03:50,开long,2019.4038,0.02443545219611179,0.9869009003909299,0.0024672522509773245,97.70072188645109,触下轨开多,
2026-03-01 03:55,加long,2018.383596,0.04840542802670058,1.9540144377290218,0.004885036094322554,95.74182241262774,触下轨加多,
2026-03-01 04:00,平仓50%,2028.348,0.03642044011140619,,0.0036936663429545246,97.55902982143648,触中轨平50%-1m(04:02)回踩中轨,0.35044340609171903
2026-03-01 04:05,平仓100%,2018.7258371425564,0.03642044011140619,,0.003676144172649939,99.0258113463238,回开仓价全平,0.0
2026-03-01 04:15,开long,1999.6626196004338,0.02476062971215385,0.9902581134632381,0.0024756452836580947,98.03307758757691,触下轨开多,
2026-03-01 04:35,平仓50%,2014.6299999999999,0.012380314856076925,,0.0012470876859249124,98.71226043853991,触中轨平50%-1m(04:39)回踩中轨,0.18530088191730254
2026-03-01 05:10,平仓100%,2017.8250206638015,0.012380314856076925,,0.0012490654540143892,99.43099667352436,延迟反转-同K回调确认-平多,0.22485624370683877
2026-03-01 05:10,开short,2017.4214556596687,0.02464308991920873,0.9943099667352436,0.0024857749168381085,98.43420093187228,延迟反转-同K回调确认-开空,
2026-03-01 05:15,平仓50%,2014.157,0.012321544959604365,,0.0012408763015600921,98.9703381761176,触中轨平50%-1m(05:16)反抽中轨,0.04022313717924384
2026-03-01 05:20,平仓100%,2017.4214556596687,0.012321544959604365,,0.0012428874584190543,99.4662502720268,回开仓价全平,0.0
2026-03-01 05:20,开short,2020.9165042134857,0.0246091934190863,0.994662502720268,0.0024866562568006696,98.46910111304973,触上轨开空,
2026-03-01 05:35,平仓50%,2016.4740000000002,0.01230459670954315,,0.0012405949672639653,99.01985499216998,触中轨平50%-1m(05:38)反抽中轨,0.05466322272738604
2026-03-01 06:10,平仓100%,1993.0582785593572,0.01230459670954315,,0.0012261889168144599,99.8587442863308,延迟反转-同K反弹确认-平空,0.34278423171750066
2026-03-01 06:10,开long,1993.456890215069,0.02504662748828175,0.998587442863308,0.00249646860715827,98.85766037486034,延迟反转-同K反弹确认-开多,
2026-03-01 07:00,平仓50%,1996.611,0.012523313744140875,,0.0012502092989001429,99.39520379341326,触中轨平50%-1m(07:01)回踩中轨,0.03949990642015626
2026-03-01 07:05,平仓100%,1993.456890215069,0.012523313744140875,,0.001248234303579135,99.89324928054134,回开仓价全平,0.0
2026-03-01 07:10,开short,2002.49942,0.024942141876011462,0.9989324928054134,0.002497331232013533,98.89181945650391,触上轨开空,
2026-03-01 07:25,平仓50%,1997.67,0.012471070938005731,,0.0012456542140362953,99.450268088102,触中轨平50%-1m(07:26)反抽中轨,0.06022803940942398
2026-03-01 07:35,平仓100%,2002.49942,0.012471070938005731,,0.0012486656160067666,99.94848566888871,回开仓价全平,0.0
2026-03-01 08:50,开long,1983.9752370436452,0.02518894485241248,0.9994848566888871,0.0024987121417222175,98.9465021000581,触下轨开多,
2026-03-01 09:00,加long,1984.6668539999998,0.049855471662982744,1.978930042001162,0.004947325105002905,96.96262473295194,触下轨加多,
2026-03-01 09:15,平仓50%,1992.7069999999999,0.037522208257697615,,0.003738538352528591,98.75848822726503,触中轨平50%-1m(09:18)回踩中轨,0.31039458332059994
2026-03-01 09:30,平仓100%,1984.4347101286562,0.037522208257697615,,0.003723018623362561,100.24397265798669,回开仓价全平,0.0
2026-03-01 09:35,开long,1977.5454880734026,0.025345554188907188,1.0024397265798668,0.0025060993164496663,99.23902683209037,触下轨开多,
2026-03-01 10:15,平仓50%,1979.625,0.012672777094453594,,0.0012543673177803846,99.76534551917355,触中轨平50%-1m(10:17)回踩中轨,0.02635319111102702
2026-03-01 10:15,平仓100%,1977.5454880734026,0.012672777094453594,,0.0012530496582248331,100.26531233280527,回开仓价全平,0.0
2026-03-01 10:25,开short,1983.593202,0.02527365798383223,1.0026531233280527,0.0025066328083201313,99.26015257666889,触上轨开空,
2026-03-01 10:35,加short,1987.0210368714647,0.049954253495449914,1.9852030515333778,0.004963007628833443,97.26998651750668,触上轨加空,
2026-03-01 10:40,平仓50%,1980.9359999999997,0.037613955739641075,,0.0037255419513530803,98.94575440746019,触中轨平50%-1m(10:41)反抽中轨,0.18556534447413794
2026-03-01 11:00,平仓100%,1985.8694174198158,0.037613955739641075,,0.0037348202185767876,100.43594767467232,回开仓价全平,0.0
2026-03-01 13:20,开long,1971.0811861410878,0.02547737464617128,1.0043594767467232,0.0025108986918668075,99.42907729923373,触下轨开多,
2026-03-01 13:25,平仓50%,1977.221,0.01273868732308564,,0.0012593600043819353,100.00821084657333,触中轨平50%-1m(13:27)回踩中轨,0.07821316897063098
2026-03-01 13:50,平仓100%,2012.7953226137352,0.01273868732308564,,0.0012820185130072825,101.04049190791126,延迟反转-同K回调确认-平多,0.5313833414775777
2026-03-01 13:50,开short,2012.3927635492125,0.025104565504824317,1.0104049190791127,0.002526012297697781,100.02756097653445,延迟反转-同K回调确认-开空,
2026-03-01 14:25,平仓50%,2004.753,0.012552282752412158,,0.0012582113252373262,100.62740169698006,触中轨平50%-1m(14:28)反抽中轨,0.09589647223128844
2026-03-01 14:40,平仓100%,1994.9544227315625,0.012552282752412158,,0.0012520615996150872,101.35024307959607,延迟反转-同K反弹确认-平空,0.21889098467607288
2026-03-01 14:40,开long,1995.3534136161088,0.025396564435150013,1.0135024307959608,0.0025337560769899017,100.33420689272312,延迟反转-同K反弹确认-开多,
2026-03-01 15:00,平仓50%,2004.636,0.012698282217575007,,0.0012727716835755343,100.95755823804919,触中轨平50%-1m(15:03)回踩中轨,0.11787290161166897
2026-03-01 15:20,平仓100%,2012.645228772288,0.012698282217575007,,0.0012778568559403158,101.68260794549853,延迟反转-同K回调确认-平多,0.21957634890730374
2026-03-01 15:20,开short,2012.2426997265336,0.02526599002180933,1.0168260794549853,0.002542065198637463,100.6632398008449,延迟反转-同K回调确认-开空,
2026-03-01 15:50,平仓50%,2008.9260000000002,0.012632995010904665,,0.0012689376067638331,101.2122837540636,触中轨平50%-1m(15:52)反抽中轨,0.04189985109796585
2026-03-01 15:50,平仓100%,2012.2426997265336,0.012632995010904665,,0.0012710325993187314,101.71942576119179,回开仓价全平,0.0
2026-03-01 15:55,开long,1992.6786162417027,0.025523289338307855,1.017194257611918,0.0025429856440297945,100.69968851793584,触下轨开多,
2026-03-01 16:00,加long,1991.1565538891698,0.050573466120103445,2.013993770358717,0.005034984425896792,98.68065976315123,触下轨加多,
2026-03-01 16:25,平仓50%,2002.4759999999999,0.03804837772920565,,0.00380954816208344,100.60370677137739,触中轨平50%-1m(16:26)回踩中轨,0.41126254240293536
2026-03-01 16:45,平仓100%,1967.62,0.03804837772920565,,0.00374323744937698,101.20060583618708,止损,-0.9149517117262566
2026-03-01 16:50,开long,1965.2229659999998,0.02574786871185666,1.0120060583618709,0.0025300151459046764,100.18606976267931,触下轨开多,
2026-03-01 17:35,平仓50%,1967.5260000000003,0.01287393435592833,,0.001266490028379112,100.72045541036735,触中轨平50%-1m(17:36)回踩中轨,0.029649108535477225
2026-03-01 17:45,平仓100%,1965.2229659999998,0.01287393435592833,,0.0012650075729523382,101.22519343197533,回开仓价全平,0.0
2026-03-01 18:00,开short,1982.393442,0.025531055361505614,1.0122519343197534,0.002530629835799383,100.21041086781977,触上轨开空,
2026-03-01 18:05,加short,1991.233970955805,0.05032578407635248,2.0042082173563953,0.005010520543390987,98.20119212991999,触上轨加空,
2026-03-01 18:30,平仓50%,1977.5280000000002,0.037928419718929046,,0.0037502255994967156,100.11266378212795,触中轨平50%-1m(18:33)反抽中轨,0.4069918019693819
2026-03-01 18:55,平仓100%,1972.9829470185493,0.037928419718929046,,0.0037416062656404534,102.196530730795,延迟反转-同K反弹确认-平空,0.5793784790946218
2026-03-01 18:55,开long,1973.377543607953,0.025893811111265538,1.02196530730795,0.0025549132682698744,101.17201051021878,延迟反转-同K反弹确认-开多,
2026-03-01 19:05,加long,1968.973716,0.051383118874614164,2.0234402102043756,0.0050586005255109385,99.1435116994889,触下轨加多,
2026-03-01 19:40,平仓100%,1946.27,0.0772769299858797,,0.007520088526180904,100.31289177647686,止损,-1.8685053519981858
2026-03-01 20:10,开long,1933.526628,0.025940395731771856,1.0031289177647686,0.002507822294411921,99.30725503641769,触下轨开多,
2026-03-01 20:35,平仓100%,1911.05,0.025940395731771856,,0.00247866966316013,99.72485265948347,止损,-0.5830526250358269
2026-03-01 22:10,开short,1943.1708021370632,0.025660341476366342,0.9972485265948348,0.002493121316487086,98.72511101157215,触上轨开空,
2026-03-01 22:25,平仓50%,1933.2939999999999,0.012830170738183171,,0.0012402246053552545,99.34921610802999,触中轨平50%-1m(22:28)反抽中轨,0.12672105776577522
2026-03-01 22:25,平仓100%,1943.1708021370632,0.012830170738183171,,0.001246560658243543,99.84659381066916,回开仓价全平,0.0
2026-03-02 00:10,开short,1947.900342,0.025629286996313172,0.9984659381066916,0.0024961648452667285,98.8456317077172,触上轨开空,
2026-03-02 00:15,平仓50%,1940.6370000000002,0.012814643498156586,,0.001243428565716605,99.43669838654002,触中轨平50%-1m(00:17)反抽中轨,0.0930771383351843
2026-03-02 00:20,平仓100%,1947.900342,0.012814643498156586,,0.0012480824226333643,99.93468327317073,回开仓价全平,0.0
2026-03-02 00:40,开short,1961.187684,0.02547810290888272,0.9993468327317073,0.002498367081829268,98.9328380733572,触上轨开空,
2026-03-02 00:45,加short,1959.7479720000001,0.050482429111735394,1.9786567614671442,0.004946641903667859,96.9492346699864,触上轨加空,
2026-03-02 01:10,平仓100%,1988.21,0.07596053202061812,,0.007551274468435656,97.79437733094622,止损,-2.1253096587705884
2026-03-02 02:05,开long,1949.6071946200707,0.02508053355588993,0.9779437733094622,0.002444859433273655,96.81398869820349,触下轨开多,
2026-03-02 02:10,加long,1940.8287934851146,0.049882807295102104,1.9362797739640698,0.004840699434910173,94.8728682248045,触下轨加多,
2026-03-02 02:30,平仓50%,1953.676,0.037481670425496015,,0.0036613519975100665,96.69776991480681,触中轨平50%-1m(02:34)回踩中轨,0.371451268363048
2026-03-02 03:10,平仓100%,1966.7001436494575,0.037481670425496015,,0.0036857603305022308,99.0108138563194,延迟反转-同K回调确认-平多,0.859617928206332
2026-03-02 03:10,开short,1966.3068036207276,0.025176847700979926,0.990108138563194,0.0024752703464079847,98.0182304474098,延迟反转-同K回调确认-开空,
2026-03-02 03:55,平仓50%,1971.86,0.012588423850489963,,0.0012411304726913566,98.44213739647142,触中轨平50%-1m(03:57)反抽中轨,-0.0699059897472862
2026-03-02 03:55,平仓100%,1966.3068036207276,0.012588423850489963,,0.0012376351732039923,98.93595383057982,回开仓价全平,0.0
2026-03-02 04:15,开long,1969.413804,0.025118122364541886,0.9893595383057983,0.002473398845764495,97.94412089342826,触下轨开多,
2026-03-02 04:20,平仓50%,1971.578,0.012559061182270943,,0.001238058436380969,98.46474287411921,触中轨平50%-1m(04:22)回踩中轨,0.02718026997442538
2026-03-02 04:20,平仓100%,1969.413804,0.012559061182270943,,0.0012366994228822474,98.95818594384923,回开仓价全平,0.0
2026-03-02 05:05,开long,1964.19276,0.025190548493786637,0.9895818594384923,0.0024739546485962305,97.96613012976213,触下轨开多,
2026-03-02 05:15,平仓50%,1970.2719999999997,0.012595274246893318,,0.0012408058090487492,98.53624994868501,触中轨平50%-1m(05:17)回踩中轨,0.07656969501268121
2026-03-02 05:20,平仓100%,1964.19276,0.012595274246893318,,0.0012369773242981153,99.02980390107996,回开仓价全平,0.0
2026-03-02 05:40,开short,1979.524016,0.025013539391451352,0.9902980390107996,0.0024757450975269983,98.03703011697164,触上轨开空,
2026-03-02 07:20,平仓50%,1937.3039999999996,0.012506769695725676,,0.0012114707479304063,99.05900368239097,触中轨平50%-1m(07:24)反抽中轨,0.5280360166618587
2026-03-02 08:10,平仓100%,1928.936186384675,0.012506769695725676,,0.0012062380320432252,100.1856367982698,延迟反转-同K反弹确认-平空,0.6326903344054814
2026-03-02 08:10,开long,1929.3219736219519,0.025963949555342865,1.001856367982698,0.0025046409199567447,99.18127578936715,延迟反转-同K反弹确认-开多,
2026-03-02 08:15,加long,1930.6160459999999,0.051372864114984755,1.983625515787343,0.004959063789468357,97.19269120979034,触下轨加多,
2026-03-02 08:50,平仓100%,1948.7595882760102,0.07733681367032762,,0.007535542858338308,101.60740052896617,延迟反转-同K回调确认-平多,1.4367629782641276
2026-03-02 08:50,开short,1948.369836358355,0.026074977818091708,1.0160740052896617,0.0025401850132241535,100.58878633866328,延迟反转-同K回调确认-开空,
2026-03-02 09:40,平仓50%,1955.359,0.013037488909045854,,0.0012746485637851492,101.00442754928288,触中轨平50%-1m(09:43)反抽中轨,-0.09112114346145109
2026-03-02 09:40,平仓100%,1948.369836358355,0.013037488909045854,,0.0012700925066120767,101.5111944594211,回开仓价全平,0.0
2026-03-02 11:05,开long,1939.03773,0.02617566251777399,1.0151119445942112,0.0025377798614855274,100.49354473496541,触下轨开多,
2026-03-02 12:00,平仓50%,1943.6330000000003,0.013087831258886995,,0.0012718970366602154,101.05997092857488,触中轨平50%-1m(12:02)回踩中轨,0.06014211834902898
2026-03-02 12:20,平仓100%,1939.03773,0.013087831258886995,,0.0012688899307427637,101.56625801094124,回开仓价全平,0.0
2026-03-02 12:20,开long,1936.8878482318667,0.026218931081543613,1.0156625801094123,0.0025391564502735306,100.54805627438155,触下轨开多,
2026-03-02 12:35,加long,1930.6260479999999,0.05208054474274946,2.010961125487631,0.005027402813719077,98.5320677460802,触下轨加多,
2026-03-02 12:50,平仓50%,1939.3809999999999,0.039149737912146536,,0.003796312893089832,100.30224850785619,触中轨平50%-1m(12:51)回踩中轨,0.26066522187056274
2026-03-02 13:05,平仓100%,1932.7228399260932,0.039149737912146536,,0.0037832796319963034,101.81177708102271,回开仓价全平,0.0
2026-03-02 13:05,开long,1930.4860199999998,0.026369467591643766,1.0181177708102271,0.0025452944270255673,100.79111401578545,触下轨开多,
2026-03-02 13:15,平仓50%,1935.7879999999998,0.013184733795821883,,0.001276142473257322,101.36880195360808,触中轨平50%-1m(13:16)回踩中轨,0.06990519489077116
2026-03-02 13:25,平仓100%,1930.4860199999998,0.013184733795821883,,0.0012726472135127836,101.87658819179968,回开仓价全平,0.0
2026-03-02 14:25,开long,1922.6183440503523,0.02649423077311791,1.0187658819179968,0.002546914704794992,100.85527539517689,触下轨开多,
2026-03-02 14:30,平仓100%,1950.3106548105818,0.02649423077311791,,0.0025835990283911126,102.60514414998892,延迟反转-同K回调确认-平多,0.733686471922416
2026-03-02 14:30,开short,1949.9205926796196,0.02631008271187774,1.0260514414998891,0.0025651286037497224,101.57652757988528,延迟反转-同K回调确认-开空,
2026-03-02 14:45,平仓100%,1977.81,0.02631008271187774,,0.002601817234418945,101.8662045907663,止损,-0.7337726133844545
2026-03-02 17:40,开long,2025.7860592669192,0.025142389573859815,1.018662045907663,0.002546655114769157,100.84499588974387,触下轨开多,
2026-03-02 18:15,平仓50%,2037.0170000000003,0.012571194786929907,,0.00128038687456438,101.49423286941916,触中轨平50%-1m(18:15)回踩中轨,0.1411863435960275
2026-03-02 18:40,加long,2027.0190691607734,0.05007068478711442,2.0298846573883833,0.005074711643470957,99.45927350038731,触下轨加多,
2026-03-02 18:45,平仓50%,2033.3019999999997,0.03132093978702216,,0.0031842464755415857,100.93023459635924,触中轨平50%-1m(18:47)回踩中轨,0.20453750227635803
2026-03-02 18:50,平仓100%,2026.7716243577877,0.03132093978702216,,0.003174019600427768,102.19666841692992,回开仓价全平,0.0
2026-03-02 18:50,开long,2028.895698,0.02518529378264026,1.0219666841692994,0.002554916710423248,101.1721468160502,触下轨开多,
2026-03-02 18:55,加long,2027.7254639999999,0.049894400702781824,2.023442936321004,0.005058607340802509,99.1436452723884,触下轨加多,
2026-03-02 19:05,平仓50%,2031.3199999999997,0.03753984724271104,,0.003812772125053188,100.78273929931468,触中轨平50%-1m(19:05)回踩中轨,0.1202019888061939
2026-03-02 19:05,平仓100%,2028.1180160380236,0.03753984724271104,,0.003806762025612878,102.30163734753422,回开仓价全平,0.0
2026-03-02 19:05,开long,2026.35519,0.025242770332760428,1.0230163734753424,0.0025575409336883554,101.27606343312519,触下轨开多,
2026-03-02 19:10,加long,2023.6443782356102,0.05004637401825834,2.0255212686625037,0.005063803171656258,99.24547836129103,触下轨加多,
2026-03-02 19:15,平仓50%,2030.7600000000002,0.037644572175509386,,0.0038223545695568717,100.99957516548169,触中轨平50%-1m(19:18)回踩中轨,0.23365033769128954
2026-03-02 19:35,平仓100%,2038.953388071054,0.037644572175509386,,0.003837776398987009,103.06209313644565,延迟反转-同K回调确认-平多,0.5420869262940294
2026-03-02 19:35,开short,2038.5455973934397,0.025278338946213584,1.0306209313644565,0.002576552328411141,102.02889565275278,延迟反转-同K回调确认-开空,
2026-03-02 19:45,平仓50%,2031.934,0.012639169473106792,,0.0012840979092083886,102.62648712046943,触中轨平50%-1m(19:45)反抽中轨,0.08356509994363615
2026-03-02 19:50,平仓100%,2038.5455973934397,0.012639169473106792,,0.0012882761642055705,103.14050930998745,回开仓价全平,0.0
2026-03-02 19:50,开short,2043.2612660000002,0.025239187720692457,1.0314050930998746,0.0025785127327496863,102.10652570415482,触上轨开空,
2026-03-02 19:55,加short,2042.021514,0.05000266892592338,2.0421305140830963,0.00510532628520774,100.05928986378652,触上轨加空,
2026-03-02 20:05,平仓50%,2037.4699999999998,0.03762092832330792,,0.003832575641544508,101.77910244042056,触中轨平50%-1m(20:09)反抽中轨,0.18687734868409472
2026-03-02 20:05,平仓100%,2042.4373773884072,0.03762092832330792,,0.003841919508978713,103.31202832450306,回开仓价全平,0.0
2026-03-02 20:50,开short,2045.370844,0.02525508482424204,1.0331202832450306,0.002582800708112576,102.27632524054992,触上轨开空,
2026-03-02 20:55,平仓50%,2038.2310000000002,0.01262754241212102,,0.0012868924199099917,102.88175717267845,触中轨平50%-1m(20:59)反抽中轨,0.09015868292592541
2026-03-02 21:45,平仓100%,2045.370844,0.01262754241212102,,0.001291400354056288,103.3970259139469,回开仓价全平,0.0
2026-03-02 22:05,开short,2047.470424,0.025249943711506063,1.033970259139469,0.002584925647848672,102.36047072915959,触上轨开空,
2026-03-02 22:15,平仓50%,2043.7830000000001,0.012624971855753031,,0.0012901351427133249,102.92271934780683,触中轨平50%-1m(22:15)反抽中轨,0.04655362422022782
2026-03-02 22:25,平仓100%,2047.470424,0.012624971855753031,,0.001292462823924336,103.43841201455264,回开仓价全平,0.0
2026-03-02 22:30,开short,2051.5995980000002,0.025209210441303816,1.0343841201455264,0.0025859603003638154,102.40144193410674,触上轨开空,
2026-03-02 22:50,平仓50%,2049.2699999999995,0.012604605220651908,,0.001291511967026266,102.94670614532531,触中轨平50%-1m(22:54)反抽中轨,0.029363663112829255
2026-03-02 22:50,平仓100%,2051.5995980000002,0.012604605220651908,,0.0012929801501819077,103.46260522524788,回开仓价全平,0.0
2026-03-03 00:30,开long,2020.584036,0.02560215348183813,1.0346260522524788,0.0025865651306311967,102.42539260786478,触下轨开多,
2026-03-03 00:45,平仓50%,2029.438,0.012801076740919065,,0.001298949578946865,103.05474695703741,触中轨平50%-1m(00:47)回踩中轨,0.11334027262533643
2026-03-03 01:30,加long,2025.1515544797203,0.05088742456290541,2.061094939140748,0.005152737347851869,100.98849928054881,触下轨加多,
2026-03-03 02:15,平仓100%,2002.4,0.06368850130382447,,0.0063764927505389046,102.16998750049402,止损,-1.390543252571244
2026-03-03 02:45,开short,2019.206078,0.025299544363914604,1.0216998750049402,0.00255424968751235,101.14573337580157,触上轨开空,
2026-03-03 03:10,平仓50%,2012.1740000000002,0.012649772181957302,,0.0012726771345228875,101.74426482083527,触中轨平50%-1m(03:13)反抽中轨,0.0889541846657505
2026-03-03 04:35,平仓100%,2003.7637516585983,0.012649772181957302,,0.0012673577482472664,102.44918931076766,延迟反转-同K反弹确认-平空,0.1953419101781693
2026-03-03 04:35,开long,2004.16450440893,0.025559076883507146,1.0244918931076767,0.0025612297327691916,101.42213618792722,延迟反转-同K反弹确认-开多,
2026-03-03 04:40,加long,2002.570434,0.05064597702331163,2.0284427237585443,0.0050711068093963595,99.38862235735928,触下轨加多,
2026-03-03 05:05,平仓50%,2003.5459999999998,0.03810252695340939,,0.0038170082733697775,100.92807270325906,触中轨平50%-1m(05:09)回踩中轨,0.016800045740044088
2026-03-03 05:05,平仓100%,2003.1050831610567,0.03810252695340939,,0.003816168271082775,102.45072384342109,回开仓价全平,0.0
2026-03-03 05:15,开short,2013.5872020000002,0.02543985275176105,1.0245072384342109,0.0025612680960855265,101.42365533689079,触上轨开空,
2026-03-03 05:20,平仓50%,2003.975,0.012719926375880526,,0.0012745207229552585,102.05690093713504,触中轨平50%-1m(05:24)反抽中轨,0.12226650175009475
2026-03-03 06:35,加short,1993.821156,0.0511865874379031,2.041138018742701,0.005102845046856751,100.01066007334548,触上轨加空,
2026-03-03 06:40,平仓50%,1992.6649999999997,0.03195325690689181,,0.0031836068337185775,101.44682656011526,触中轨平50%-1m(06:41)反抽中轨,0.1626542746235937
2026-03-03 06:45,平仓100%,1988.3703092642115,0.03195325690689181,,0.003176745365897763,103.02022926476914,延迟反转-同K反弹确认-平空,0.2998836310398825
2026-03-03 06:45,开long,1988.7679833260643,0.025900514823372103,1.0302022926476915,0.002575505731619228,101.98745146638983,延迟反转-同K反弹确认-开多,
2026-03-03 06:50,平仓50%,1992.611,0.012950257411686051,,0.0012902412685678573,102.55103042660998,触中轨平50%-1m(06:51)回踩中轨,0.04976805516487063
2026-03-03 06:55,平仓100%,1999.2593560774471,0.012950257411686051,,0.0012945461646962318,103.20070300450149,延迟反转-同K回调确认-平多,0.1358659777323571
2026-03-03 06:55,开short,1998.8595042062318,0.025814896641643555,1.032007030045015,0.002580017575112537,102.16611595688137,延迟反转-同K回调确认-开空,
2026-03-03 07:05,平仓50%,1993.9159999999997,0.012907448320821777,,0.0012868183863029834,102.74464067858328,触中轨平50%-1m(07:08)反抽中轨,0.0638080250657058
2026-03-03 07:05,平仓100%,1998.8595042062318,0.012907448320821777,,0.0012900087875562685,103.25935418481824,回开仓价全平,0.0
1 timestamp action price size margin fee capital reason pnl
2 2026-03-01 01:35 开short 1970.895742 0.02536917551471376 1.0 0.0024999999999999996 98.9975 触上轨开空
3 2026-03-01 01:50 平仓100% 2022.33 0.02536917551471376 0.0025652422359335528 98.690090039093 止损 -1.3048447186710703
4 2026-03-01 03:50 开long 2019.4038 0.02443545219611179 0.9869009003909299 0.0024672522509773245 97.70072188645109 触下轨开多
5 2026-03-01 03:55 加long 2018.383596 0.04840542802670058 1.9540144377290218 0.004885036094322554 95.74182241262774 触下轨加多
6 2026-03-01 04:00 平仓50% 2028.348 0.03642044011140619 0.0036936663429545246 97.55902982143648 触中轨平50%-1m(04:02)回踩中轨 0.35044340609171903
7 2026-03-01 04:05 平仓100% 2018.7258371425564 0.03642044011140619 0.003676144172649939 99.0258113463238 回开仓价全平 0.0
8 2026-03-01 04:15 开long 1999.6626196004338 0.02476062971215385 0.9902581134632381 0.0024756452836580947 98.03307758757691 触下轨开多
9 2026-03-01 04:35 平仓50% 2014.6299999999999 0.012380314856076925 0.0012470876859249124 98.71226043853991 触中轨平50%-1m(04:39)回踩中轨 0.18530088191730254
10 2026-03-01 05:10 平仓100% 2017.8250206638015 0.012380314856076925 0.0012490654540143892 99.43099667352436 延迟反转-同K回调确认-平多 0.22485624370683877
11 2026-03-01 05:10 开short 2017.4214556596687 0.02464308991920873 0.9943099667352436 0.0024857749168381085 98.43420093187228 延迟反转-同K回调确认-开空
12 2026-03-01 05:15 平仓50% 2014.157 0.012321544959604365 0.0012408763015600921 98.9703381761176 触中轨平50%-1m(05:16)反抽中轨 0.04022313717924384
13 2026-03-01 05:20 平仓100% 2017.4214556596687 0.012321544959604365 0.0012428874584190543 99.4662502720268 回开仓价全平 0.0
14 2026-03-01 05:20 开short 2020.9165042134857 0.0246091934190863 0.994662502720268 0.0024866562568006696 98.46910111304973 触上轨开空
15 2026-03-01 05:35 平仓50% 2016.4740000000002 0.01230459670954315 0.0012405949672639653 99.01985499216998 触中轨平50%-1m(05:38)反抽中轨 0.05466322272738604
16 2026-03-01 06:10 平仓100% 1993.0582785593572 0.01230459670954315 0.0012261889168144599 99.8587442863308 延迟反转-同K反弹确认-平空 0.34278423171750066
17 2026-03-01 06:10 开long 1993.456890215069 0.02504662748828175 0.998587442863308 0.00249646860715827 98.85766037486034 延迟反转-同K反弹确认-开多
18 2026-03-01 07:00 平仓50% 1996.611 0.012523313744140875 0.0012502092989001429 99.39520379341326 触中轨平50%-1m(07:01)回踩中轨 0.03949990642015626
19 2026-03-01 07:05 平仓100% 1993.456890215069 0.012523313744140875 0.001248234303579135 99.89324928054134 回开仓价全平 0.0
20 2026-03-01 07:10 开short 2002.49942 0.024942141876011462 0.9989324928054134 0.002497331232013533 98.89181945650391 触上轨开空
21 2026-03-01 07:25 平仓50% 1997.67 0.012471070938005731 0.0012456542140362953 99.450268088102 触中轨平50%-1m(07:26)反抽中轨 0.06022803940942398
22 2026-03-01 07:35 平仓100% 2002.49942 0.012471070938005731 0.0012486656160067666 99.94848566888871 回开仓价全平 0.0
23 2026-03-01 08:50 开long 1983.9752370436452 0.02518894485241248 0.9994848566888871 0.0024987121417222175 98.9465021000581 触下轨开多
24 2026-03-01 09:00 加long 1984.6668539999998 0.049855471662982744 1.978930042001162 0.004947325105002905 96.96262473295194 触下轨加多
25 2026-03-01 09:15 平仓50% 1992.7069999999999 0.037522208257697615 0.003738538352528591 98.75848822726503 触中轨平50%-1m(09:18)回踩中轨 0.31039458332059994
26 2026-03-01 09:30 平仓100% 1984.4347101286562 0.037522208257697615 0.003723018623362561 100.24397265798669 回开仓价全平 0.0
27 2026-03-01 09:35 开long 1977.5454880734026 0.025345554188907188 1.0024397265798668 0.0025060993164496663 99.23902683209037 触下轨开多
28 2026-03-01 10:15 平仓50% 1979.625 0.012672777094453594 0.0012543673177803846 99.76534551917355 触中轨平50%-1m(10:17)回踩中轨 0.02635319111102702
29 2026-03-01 10:15 平仓100% 1977.5454880734026 0.012672777094453594 0.0012530496582248331 100.26531233280527 回开仓价全平 0.0
30 2026-03-01 10:25 开short 1983.593202 0.02527365798383223 1.0026531233280527 0.0025066328083201313 99.26015257666889 触上轨开空
31 2026-03-01 10:35 加short 1987.0210368714647 0.049954253495449914 1.9852030515333778 0.004963007628833443 97.26998651750668 触上轨加空
32 2026-03-01 10:40 平仓50% 1980.9359999999997 0.037613955739641075 0.0037255419513530803 98.94575440746019 触中轨平50%-1m(10:41)反抽中轨 0.18556534447413794
33 2026-03-01 11:00 平仓100% 1985.8694174198158 0.037613955739641075 0.0037348202185767876 100.43594767467232 回开仓价全平 0.0
34 2026-03-01 13:20 开long 1971.0811861410878 0.02547737464617128 1.0043594767467232 0.0025108986918668075 99.42907729923373 触下轨开多
35 2026-03-01 13:25 平仓50% 1977.221 0.01273868732308564 0.0012593600043819353 100.00821084657333 触中轨平50%-1m(13:27)回踩中轨 0.07821316897063098
36 2026-03-01 13:50 平仓100% 2012.7953226137352 0.01273868732308564 0.0012820185130072825 101.04049190791126 延迟反转-同K回调确认-平多 0.5313833414775777
37 2026-03-01 13:50 开short 2012.3927635492125 0.025104565504824317 1.0104049190791127 0.002526012297697781 100.02756097653445 延迟反转-同K回调确认-开空
38 2026-03-01 14:25 平仓50% 2004.753 0.012552282752412158 0.0012582113252373262 100.62740169698006 触中轨平50%-1m(14:28)反抽中轨 0.09589647223128844
39 2026-03-01 14:40 平仓100% 1994.9544227315625 0.012552282752412158 0.0012520615996150872 101.35024307959607 延迟反转-同K反弹确认-平空 0.21889098467607288
40 2026-03-01 14:40 开long 1995.3534136161088 0.025396564435150013 1.0135024307959608 0.0025337560769899017 100.33420689272312 延迟反转-同K反弹确认-开多
41 2026-03-01 15:00 平仓50% 2004.636 0.012698282217575007 0.0012727716835755343 100.95755823804919 触中轨平50%-1m(15:03)回踩中轨 0.11787290161166897
42 2026-03-01 15:20 平仓100% 2012.645228772288 0.012698282217575007 0.0012778568559403158 101.68260794549853 延迟反转-同K回调确认-平多 0.21957634890730374
43 2026-03-01 15:20 开short 2012.2426997265336 0.02526599002180933 1.0168260794549853 0.002542065198637463 100.6632398008449 延迟反转-同K回调确认-开空
44 2026-03-01 15:50 平仓50% 2008.9260000000002 0.012632995010904665 0.0012689376067638331 101.2122837540636 触中轨平50%-1m(15:52)反抽中轨 0.04189985109796585
45 2026-03-01 15:50 平仓100% 2012.2426997265336 0.012632995010904665 0.0012710325993187314 101.71942576119179 回开仓价全平 0.0
46 2026-03-01 15:55 开long 1992.6786162417027 0.025523289338307855 1.017194257611918 0.0025429856440297945 100.69968851793584 触下轨开多
47 2026-03-01 16:00 加long 1991.1565538891698 0.050573466120103445 2.013993770358717 0.005034984425896792 98.68065976315123 触下轨加多
48 2026-03-01 16:25 平仓50% 2002.4759999999999 0.03804837772920565 0.00380954816208344 100.60370677137739 触中轨平50%-1m(16:26)回踩中轨 0.41126254240293536
49 2026-03-01 16:45 平仓100% 1967.62 0.03804837772920565 0.00374323744937698 101.20060583618708 止损 -0.9149517117262566
50 2026-03-01 16:50 开long 1965.2229659999998 0.02574786871185666 1.0120060583618709 0.0025300151459046764 100.18606976267931 触下轨开多
51 2026-03-01 17:35 平仓50% 1967.5260000000003 0.01287393435592833 0.001266490028379112 100.72045541036735 触中轨平50%-1m(17:36)回踩中轨 0.029649108535477225
52 2026-03-01 17:45 平仓100% 1965.2229659999998 0.01287393435592833 0.0012650075729523382 101.22519343197533 回开仓价全平 0.0
53 2026-03-01 18:00 开short 1982.393442 0.025531055361505614 1.0122519343197534 0.002530629835799383 100.21041086781977 触上轨开空
54 2026-03-01 18:05 加short 1991.233970955805 0.05032578407635248 2.0042082173563953 0.005010520543390987 98.20119212991999 触上轨加空
55 2026-03-01 18:30 平仓50% 1977.5280000000002 0.037928419718929046 0.0037502255994967156 100.11266378212795 触中轨平50%-1m(18:33)反抽中轨 0.4069918019693819
56 2026-03-01 18:55 平仓100% 1972.9829470185493 0.037928419718929046 0.0037416062656404534 102.196530730795 延迟反转-同K反弹确认-平空 0.5793784790946218
57 2026-03-01 18:55 开long 1973.377543607953 0.025893811111265538 1.02196530730795 0.0025549132682698744 101.17201051021878 延迟反转-同K反弹确认-开多
58 2026-03-01 19:05 加long 1968.973716 0.051383118874614164 2.0234402102043756 0.0050586005255109385 99.1435116994889 触下轨加多
59 2026-03-01 19:40 平仓100% 1946.27 0.0772769299858797 0.007520088526180904 100.31289177647686 止损 -1.8685053519981858
60 2026-03-01 20:10 开long 1933.526628 0.025940395731771856 1.0031289177647686 0.002507822294411921 99.30725503641769 触下轨开多
61 2026-03-01 20:35 平仓100% 1911.05 0.025940395731771856 0.00247866966316013 99.72485265948347 止损 -0.5830526250358269
62 2026-03-01 22:10 开short 1943.1708021370632 0.025660341476366342 0.9972485265948348 0.002493121316487086 98.72511101157215 触上轨开空
63 2026-03-01 22:25 平仓50% 1933.2939999999999 0.012830170738183171 0.0012402246053552545 99.34921610802999 触中轨平50%-1m(22:28)反抽中轨 0.12672105776577522
64 2026-03-01 22:25 平仓100% 1943.1708021370632 0.012830170738183171 0.001246560658243543 99.84659381066916 回开仓价全平 0.0
65 2026-03-02 00:10 开short 1947.900342 0.025629286996313172 0.9984659381066916 0.0024961648452667285 98.8456317077172 触上轨开空
66 2026-03-02 00:15 平仓50% 1940.6370000000002 0.012814643498156586 0.001243428565716605 99.43669838654002 触中轨平50%-1m(00:17)反抽中轨 0.0930771383351843
67 2026-03-02 00:20 平仓100% 1947.900342 0.012814643498156586 0.0012480824226333643 99.93468327317073 回开仓价全平 0.0
68 2026-03-02 00:40 开short 1961.187684 0.02547810290888272 0.9993468327317073 0.002498367081829268 98.9328380733572 触上轨开空
69 2026-03-02 00:45 加short 1959.7479720000001 0.050482429111735394 1.9786567614671442 0.004946641903667859 96.9492346699864 触上轨加空
70 2026-03-02 01:10 平仓100% 1988.21 0.07596053202061812 0.007551274468435656 97.79437733094622 止损 -2.1253096587705884
71 2026-03-02 02:05 开long 1949.6071946200707 0.02508053355588993 0.9779437733094622 0.002444859433273655 96.81398869820349 触下轨开多
72 2026-03-02 02:10 加long 1940.8287934851146 0.049882807295102104 1.9362797739640698 0.004840699434910173 94.8728682248045 触下轨加多
73 2026-03-02 02:30 平仓50% 1953.676 0.037481670425496015 0.0036613519975100665 96.69776991480681 触中轨平50%-1m(02:34)回踩中轨 0.371451268363048
74 2026-03-02 03:10 平仓100% 1966.7001436494575 0.037481670425496015 0.0036857603305022308 99.0108138563194 延迟反转-同K回调确认-平多 0.859617928206332
75 2026-03-02 03:10 开short 1966.3068036207276 0.025176847700979926 0.990108138563194 0.0024752703464079847 98.0182304474098 延迟反转-同K回调确认-开空
76 2026-03-02 03:55 平仓50% 1971.86 0.012588423850489963 0.0012411304726913566 98.44213739647142 触中轨平50%-1m(03:57)反抽中轨 -0.0699059897472862
77 2026-03-02 03:55 平仓100% 1966.3068036207276 0.012588423850489963 0.0012376351732039923 98.93595383057982 回开仓价全平 0.0
78 2026-03-02 04:15 开long 1969.413804 0.025118122364541886 0.9893595383057983 0.002473398845764495 97.94412089342826 触下轨开多
79 2026-03-02 04:20 平仓50% 1971.578 0.012559061182270943 0.001238058436380969 98.46474287411921 触中轨平50%-1m(04:22)回踩中轨 0.02718026997442538
80 2026-03-02 04:20 平仓100% 1969.413804 0.012559061182270943 0.0012366994228822474 98.95818594384923 回开仓价全平 0.0
81 2026-03-02 05:05 开long 1964.19276 0.025190548493786637 0.9895818594384923 0.0024739546485962305 97.96613012976213 触下轨开多
82 2026-03-02 05:15 平仓50% 1970.2719999999997 0.012595274246893318 0.0012408058090487492 98.53624994868501 触中轨平50%-1m(05:17)回踩中轨 0.07656969501268121
83 2026-03-02 05:20 平仓100% 1964.19276 0.012595274246893318 0.0012369773242981153 99.02980390107996 回开仓价全平 0.0
84 2026-03-02 05:40 开short 1979.524016 0.025013539391451352 0.9902980390107996 0.0024757450975269983 98.03703011697164 触上轨开空
85 2026-03-02 07:20 平仓50% 1937.3039999999996 0.012506769695725676 0.0012114707479304063 99.05900368239097 触中轨平50%-1m(07:24)反抽中轨 0.5280360166618587
86 2026-03-02 08:10 平仓100% 1928.936186384675 0.012506769695725676 0.0012062380320432252 100.1856367982698 延迟反转-同K反弹确认-平空 0.6326903344054814
87 2026-03-02 08:10 开long 1929.3219736219519 0.025963949555342865 1.001856367982698 0.0025046409199567447 99.18127578936715 延迟反转-同K反弹确认-开多
88 2026-03-02 08:15 加long 1930.6160459999999 0.051372864114984755 1.983625515787343 0.004959063789468357 97.19269120979034 触下轨加多
89 2026-03-02 08:50 平仓100% 1948.7595882760102 0.07733681367032762 0.007535542858338308 101.60740052896617 延迟反转-同K回调确认-平多 1.4367629782641276
90 2026-03-02 08:50 开short 1948.369836358355 0.026074977818091708 1.0160740052896617 0.0025401850132241535 100.58878633866328 延迟反转-同K回调确认-开空
91 2026-03-02 09:40 平仓50% 1955.359 0.013037488909045854 0.0012746485637851492 101.00442754928288 触中轨平50%-1m(09:43)反抽中轨 -0.09112114346145109
92 2026-03-02 09:40 平仓100% 1948.369836358355 0.013037488909045854 0.0012700925066120767 101.5111944594211 回开仓价全平 0.0
93 2026-03-02 11:05 开long 1939.03773 0.02617566251777399 1.0151119445942112 0.0025377798614855274 100.49354473496541 触下轨开多
94 2026-03-02 12:00 平仓50% 1943.6330000000003 0.013087831258886995 0.0012718970366602154 101.05997092857488 触中轨平50%-1m(12:02)回踩中轨 0.06014211834902898
95 2026-03-02 12:20 平仓100% 1939.03773 0.013087831258886995 0.0012688899307427637 101.56625801094124 回开仓价全平 0.0
96 2026-03-02 12:20 开long 1936.8878482318667 0.026218931081543613 1.0156625801094123 0.0025391564502735306 100.54805627438155 触下轨开多
97 2026-03-02 12:35 加long 1930.6260479999999 0.05208054474274946 2.010961125487631 0.005027402813719077 98.5320677460802 触下轨加多
98 2026-03-02 12:50 平仓50% 1939.3809999999999 0.039149737912146536 0.003796312893089832 100.30224850785619 触中轨平50%-1m(12:51)回踩中轨 0.26066522187056274
99 2026-03-02 13:05 平仓100% 1932.7228399260932 0.039149737912146536 0.0037832796319963034 101.81177708102271 回开仓价全平 0.0
100 2026-03-02 13:05 开long 1930.4860199999998 0.026369467591643766 1.0181177708102271 0.0025452944270255673 100.79111401578545 触下轨开多
101 2026-03-02 13:15 平仓50% 1935.7879999999998 0.013184733795821883 0.001276142473257322 101.36880195360808 触中轨平50%-1m(13:16)回踩中轨 0.06990519489077116
102 2026-03-02 13:25 平仓100% 1930.4860199999998 0.013184733795821883 0.0012726472135127836 101.87658819179968 回开仓价全平 0.0
103 2026-03-02 14:25 开long 1922.6183440503523 0.02649423077311791 1.0187658819179968 0.002546914704794992 100.85527539517689 触下轨开多
104 2026-03-02 14:30 平仓100% 1950.3106548105818 0.02649423077311791 0.0025835990283911126 102.60514414998892 延迟反转-同K回调确认-平多 0.733686471922416
105 2026-03-02 14:30 开short 1949.9205926796196 0.02631008271187774 1.0260514414998891 0.0025651286037497224 101.57652757988528 延迟反转-同K回调确认-开空
106 2026-03-02 14:45 平仓100% 1977.81 0.02631008271187774 0.002601817234418945 101.8662045907663 止损 -0.7337726133844545
107 2026-03-02 17:40 开long 2025.7860592669192 0.025142389573859815 1.018662045907663 0.002546655114769157 100.84499588974387 触下轨开多
108 2026-03-02 18:15 平仓50% 2037.0170000000003 0.012571194786929907 0.00128038687456438 101.49423286941916 触中轨平50%-1m(18:15)回踩中轨 0.1411863435960275
109 2026-03-02 18:40 加long 2027.0190691607734 0.05007068478711442 2.0298846573883833 0.005074711643470957 99.45927350038731 触下轨加多
110 2026-03-02 18:45 平仓50% 2033.3019999999997 0.03132093978702216 0.0031842464755415857 100.93023459635924 触中轨平50%-1m(18:47)回踩中轨 0.20453750227635803
111 2026-03-02 18:50 平仓100% 2026.7716243577877 0.03132093978702216 0.003174019600427768 102.19666841692992 回开仓价全平 0.0
112 2026-03-02 18:50 开long 2028.895698 0.02518529378264026 1.0219666841692994 0.002554916710423248 101.1721468160502 触下轨开多
113 2026-03-02 18:55 加long 2027.7254639999999 0.049894400702781824 2.023442936321004 0.005058607340802509 99.1436452723884 触下轨加多
114 2026-03-02 19:05 平仓50% 2031.3199999999997 0.03753984724271104 0.003812772125053188 100.78273929931468 触中轨平50%-1m(19:05)回踩中轨 0.1202019888061939
115 2026-03-02 19:05 平仓100% 2028.1180160380236 0.03753984724271104 0.003806762025612878 102.30163734753422 回开仓价全平 0.0
116 2026-03-02 19:05 开long 2026.35519 0.025242770332760428 1.0230163734753424 0.0025575409336883554 101.27606343312519 触下轨开多
117 2026-03-02 19:10 加long 2023.6443782356102 0.05004637401825834 2.0255212686625037 0.005063803171656258 99.24547836129103 触下轨加多
118 2026-03-02 19:15 平仓50% 2030.7600000000002 0.037644572175509386 0.0038223545695568717 100.99957516548169 触中轨平50%-1m(19:18)回踩中轨 0.23365033769128954
119 2026-03-02 19:35 平仓100% 2038.953388071054 0.037644572175509386 0.003837776398987009 103.06209313644565 延迟反转-同K回调确认-平多 0.5420869262940294
120 2026-03-02 19:35 开short 2038.5455973934397 0.025278338946213584 1.0306209313644565 0.002576552328411141 102.02889565275278 延迟反转-同K回调确认-开空
121 2026-03-02 19:45 平仓50% 2031.934 0.012639169473106792 0.0012840979092083886 102.62648712046943 触中轨平50%-1m(19:45)反抽中轨 0.08356509994363615
122 2026-03-02 19:50 平仓100% 2038.5455973934397 0.012639169473106792 0.0012882761642055705 103.14050930998745 回开仓价全平 0.0
123 2026-03-02 19:50 开short 2043.2612660000002 0.025239187720692457 1.0314050930998746 0.0025785127327496863 102.10652570415482 触上轨开空
124 2026-03-02 19:55 加short 2042.021514 0.05000266892592338 2.0421305140830963 0.00510532628520774 100.05928986378652 触上轨加空
125 2026-03-02 20:05 平仓50% 2037.4699999999998 0.03762092832330792 0.003832575641544508 101.77910244042056 触中轨平50%-1m(20:09)反抽中轨 0.18687734868409472
126 2026-03-02 20:05 平仓100% 2042.4373773884072 0.03762092832330792 0.003841919508978713 103.31202832450306 回开仓价全平 0.0
127 2026-03-02 20:50 开short 2045.370844 0.02525508482424204 1.0331202832450306 0.002582800708112576 102.27632524054992 触上轨开空
128 2026-03-02 20:55 平仓50% 2038.2310000000002 0.01262754241212102 0.0012868924199099917 102.88175717267845 触中轨平50%-1m(20:59)反抽中轨 0.09015868292592541
129 2026-03-02 21:45 平仓100% 2045.370844 0.01262754241212102 0.001291400354056288 103.3970259139469 回开仓价全平 0.0
130 2026-03-02 22:05 开short 2047.470424 0.025249943711506063 1.033970259139469 0.002584925647848672 102.36047072915959 触上轨开空
131 2026-03-02 22:15 平仓50% 2043.7830000000001 0.012624971855753031 0.0012901351427133249 102.92271934780683 触中轨平50%-1m(22:15)反抽中轨 0.04655362422022782
132 2026-03-02 22:25 平仓100% 2047.470424 0.012624971855753031 0.001292462823924336 103.43841201455264 回开仓价全平 0.0
133 2026-03-02 22:30 开short 2051.5995980000002 0.025209210441303816 1.0343841201455264 0.0025859603003638154 102.40144193410674 触上轨开空
134 2026-03-02 22:50 平仓50% 2049.2699999999995 0.012604605220651908 0.001291511967026266 102.94670614532531 触中轨平50%-1m(22:54)反抽中轨 0.029363663112829255
135 2026-03-02 22:50 平仓100% 2051.5995980000002 0.012604605220651908 0.0012929801501819077 103.46260522524788 回开仓价全平 0.0
136 2026-03-03 00:30 开long 2020.584036 0.02560215348183813 1.0346260522524788 0.0025865651306311967 102.42539260786478 触下轨开多
137 2026-03-03 00:45 平仓50% 2029.438 0.012801076740919065 0.001298949578946865 103.05474695703741 触中轨平50%-1m(00:47)回踩中轨 0.11334027262533643
138 2026-03-03 01:30 加long 2025.1515544797203 0.05088742456290541 2.061094939140748 0.005152737347851869 100.98849928054881 触下轨加多
139 2026-03-03 02:15 平仓100% 2002.4 0.06368850130382447 0.0063764927505389046 102.16998750049402 止损 -1.390543252571244
140 2026-03-03 02:45 开short 2019.206078 0.025299544363914604 1.0216998750049402 0.00255424968751235 101.14573337580157 触上轨开空
141 2026-03-03 03:10 平仓50% 2012.1740000000002 0.012649772181957302 0.0012726771345228875 101.74426482083527 触中轨平50%-1m(03:13)反抽中轨 0.0889541846657505
142 2026-03-03 04:35 平仓100% 2003.7637516585983 0.012649772181957302 0.0012673577482472664 102.44918931076766 延迟反转-同K反弹确认-平空 0.1953419101781693
143 2026-03-03 04:35 开long 2004.16450440893 0.025559076883507146 1.0244918931076767 0.0025612297327691916 101.42213618792722 延迟反转-同K反弹确认-开多
144 2026-03-03 04:40 加long 2002.570434 0.05064597702331163 2.0284427237585443 0.0050711068093963595 99.38862235735928 触下轨加多
145 2026-03-03 05:05 平仓50% 2003.5459999999998 0.03810252695340939 0.0038170082733697775 100.92807270325906 触中轨平50%-1m(05:09)回踩中轨 0.016800045740044088
146 2026-03-03 05:05 平仓100% 2003.1050831610567 0.03810252695340939 0.003816168271082775 102.45072384342109 回开仓价全平 0.0
147 2026-03-03 05:15 开short 2013.5872020000002 0.02543985275176105 1.0245072384342109 0.0025612680960855265 101.42365533689079 触上轨开空
148 2026-03-03 05:20 平仓50% 2003.975 0.012719926375880526 0.0012745207229552585 102.05690093713504 触中轨平50%-1m(05:24)反抽中轨 0.12226650175009475
149 2026-03-03 06:35 加short 1993.821156 0.0511865874379031 2.041138018742701 0.005102845046856751 100.01066007334548 触上轨加空
150 2026-03-03 06:40 平仓50% 1992.6649999999997 0.03195325690689181 0.0031836068337185775 101.44682656011526 触中轨平50%-1m(06:41)反抽中轨 0.1626542746235937
151 2026-03-03 06:45 平仓100% 1988.3703092642115 0.03195325690689181 0.003176745365897763 103.02022926476914 延迟反转-同K反弹确认-平空 0.2998836310398825
152 2026-03-03 06:45 开long 1988.7679833260643 0.025900514823372103 1.0302022926476915 0.002575505731619228 101.98745146638983 延迟反转-同K反弹确认-开多
153 2026-03-03 06:50 平仓50% 1992.611 0.012950257411686051 0.0012902412685678573 102.55103042660998 触中轨平50%-1m(06:51)回踩中轨 0.04976805516487063
154 2026-03-03 06:55 平仓100% 1999.2593560774471 0.012950257411686051 0.0012945461646962318 103.20070300450149 延迟反转-同K回调确认-平多 0.1358659777323571
155 2026-03-03 06:55 开short 1998.8595042062318 0.025814896641643555 1.032007030045015 0.002580017575112537 102.16611595688137 延迟反转-同K回调确认-开空
156 2026-03-03 07:05 平仓50% 1993.9159999999997 0.012907448320821777 0.0012868183863029834 102.74464067858328 触中轨平50%-1m(07:08)反抽中轨 0.0638080250657058
157 2026-03-03 07:05 平仓100% 1998.8595042062318 0.012907448320821777 0.0012900087875562685 103.25935418481824 回开仓价全平 0.0

View File

@@ -0,0 +1,580 @@
"""
布林带延迟反转策略回测 - 2025年多周期测试
测试1分钟和15分钟周期的收益对比
策略规则:
1. BB(10, 2.5)
2. 空仓触上轨开空,触下轨开多
3. 同向加仓最多1次保证金1%
4. 延迟反转:触轨不立刻平仓,记录价格,回调到该价再平仓+反向开仓
5. 中轨平半+回开仓价全平
回测参数:
- 本金: 100U
- 杠杆: 100x
- 逐仓模式
- 开仓保证金: 1%
- 手续费: 0.05% (万五)
- 返佣: 90%次日早上8点到账
"""
import pandas as pd
from pathlib import Path
from peewee import *
from loguru import logger
import numpy as np
# 数据库配置
DB_PATH = Path(__file__).parent / 'models' / 'database.db'
db = SqliteDatabase(str(DB_PATH))
class BitMartETH1M(Model):
"""1分钟K线模型"""
id = BigIntegerField(primary_key=True)
open = FloatField(null=True)
high = FloatField(null=True)
low = FloatField(null=True)
close = FloatField(null=True)
class Meta:
database = db
table_name = 'bitmart_eth_1m'
class BitMartETH3M(Model):
"""3分钟K线模型"""
id = BigIntegerField(primary_key=True)
open = FloatField(null=True)
high = FloatField(null=True)
low = FloatField(null=True)
close = FloatField(null=True)
class Meta:
database = db
table_name = 'bitmart_eth_3m'
class BitMartETH5M(Model):
"""5分钟K线模型"""
id = BigIntegerField(primary_key=True)
open = FloatField(null=True)
high = FloatField(null=True)
low = FloatField(null=True)
close = FloatField(null=True)
class Meta:
database = db
table_name = 'bitmart_eth_5m'
class BitMartETH15M(Model):
"""15分钟K线模型"""
id = BigIntegerField(primary_key=True)
open = FloatField(null=True)
high = FloatField(null=True)
low = FloatField(null=True)
close = FloatField(null=True)
class Meta:
database = db
table_name = 'bitmart_eth_15m'
class BBDelayReversalBacktest:
"""布林带延迟反转策略回测"""
def __init__(self, timeframe='1m'):
# 策略参数
self.timeframe = timeframe
self.bb_period = 10
self.bb_std = 2.5
self.initial_capital = 100 # 初始本金100U
self.leverage = 100 # 100倍杠杆
self.fee_rate = 0.0005 # 万五手续费
self.rebate_rate = 0.9 # 90%返佣
self.margin_ratio = 0.01 # 开仓保证金1%
self.rebate_credit_hour = 8 # 次日早上8点返佣到账
# 账户状态
self.capital = self.initial_capital
self.position = 0 # 持仓量(正=多,负=空)
self.position_count = 0 # 持仓次数0=空仓1=首次2=加仓)
self.entry_price = 0 # 开仓均价
self.total_margin = 0 # 总保证金
# 延迟反转状态
self.delay_reverse_price = None
self.delay_reverse_type = None
self.delay_reverse_kline_index = None
# 中轨平仓记录
self.mid_closed_half = False
# 交易记录
self.trades = []
self.pending_rebates = []
self.total_rebate_credited = 0.0
def calculate_bollinger_bands(self, df):
"""计算布林带整体右移1根避免使用当前K收盘价"""
df['sma'] = df['close'].rolling(window=self.bb_period).mean()
df['std'] = df['close'].rolling(window=self.bb_period).std()
df['upper'] = (df['sma'] + self.bb_std * df['std']).shift(1)
df['lower'] = (df['sma'] - self.bb_std * df['std']).shift(1)
df['middle'] = df['sma'].shift(1)
return df
def schedule_rebate(self, fee, timestamp):
"""登记返佣到账时间次日08:00上海时间"""
rebate = fee * self.rebate_rate
if rebate <= 0:
return
trade_utc = pd.Timestamp(timestamp, tz='UTC')
trade_local = trade_utc.tz_convert('Asia/Shanghai')
credit_local = (trade_local + pd.Timedelta(days=1)).normalize() + pd.Timedelta(hours=self.rebate_credit_hour)
credit_utc = credit_local.tz_convert('UTC').tz_localize(None)
self.pending_rebates.append({
'credit_time': credit_utc,
'amount': rebate,
'trade_time': trade_utc.tz_localize(None),
})
def apply_pending_rebates(self, current_time):
"""处理当前时刻前应到账的返佣"""
if not self.pending_rebates:
return
remaining = []
for item in self.pending_rebates:
if item['credit_time'] <= current_time:
amount = item['amount']
self.capital += amount
self.total_rebate_credited += amount
else:
remaining.append(item)
self.pending_rebates = remaining
def clear_delay_reversal(self):
"""清理延迟反转状态"""
self.delay_reverse_price = None
self.delay_reverse_type = None
self.delay_reverse_kline_index = None
def mark_delay_reversal(self, reverse_type, trigger_price, kline_index):
"""记录延迟反转触发信息"""
self.delay_reverse_type = reverse_type
self.delay_reverse_price = trigger_price
self.delay_reverse_kline_index = kline_index
def check_delay_reversal_signal(self, i, row, prev_row):
"""检查延迟反转是否成立"""
if self.position == 0 or self.delay_reverse_price is None or self.delay_reverse_kline_index is None:
return None
offset = i - self.delay_reverse_kline_index
if offset <= 0:
return None
high = row['high']
low = row['low']
if self.delay_reverse_type == 'long_to_short':
# 多转空:回调确认
if offset == 1 and low <= self.delay_reverse_price:
return 'short', self.delay_reverse_price, "次K回调确认"
if offset >= 2 and prev_row is not None:
prev_upper = prev_row['upper']
prev_touch_upper = pd.notna(prev_upper) and prev_row['high'] >= prev_upper
if prev_touch_upper:
if low <= prev_upper:
return 'short', prev_upper, "上一根触上轨后回调确认"
else:
prev_body_low = min(prev_row['open'], prev_row['close'])
if low <= prev_body_low:
return 'short', prev_body_low, "跌破上一根实体确认"
elif self.delay_reverse_type == 'short_to_long':
# 空转多:反弹确认
if offset == 1 and high >= self.delay_reverse_price:
return 'long', self.delay_reverse_price, "次K反弹确认"
if offset >= 2 and prev_row is not None:
prev_lower = prev_row['lower']
prev_touch_lower = pd.notna(prev_lower) and prev_row['low'] <= prev_lower
if prev_touch_lower:
if high >= prev_lower:
return 'long', prev_lower, "上一根触下轨后反弹确认"
else:
prev_body_high = max(prev_row['open'], prev_row['close'])
if high >= prev_body_high:
return 'long', prev_body_high, "突破上一根实体确认"
return None
def open_position(self, price, direction, timestamp, reason):
"""开仓或加仓"""
if self.position_count not in (0, 1):
return False
if self.position_count == 1:
current_direction = 'long' if self.position > 0 else 'short'
if direction != current_direction:
return False
margin = self.capital * self.margin_ratio
if margin <= 0:
return False
position_size = margin * self.leverage / price
fee = position_size * price * self.fee_rate
required = margin + fee
if self.capital < required:
return False
self.capital -= required
self.schedule_rebate(fee, timestamp)
if self.position_count == 0:
self.position = position_size if direction == 'long' else -position_size
self.entry_price = price
self.total_margin = margin
self.position_count = 1
else:
old_size = abs(self.position)
new_size = old_size + position_size
old_value = old_size * self.entry_price
new_value = position_size * price
self.entry_price = (old_value + new_value) / new_size
self.position = new_size if direction == 'long' else -new_size
self.total_margin += margin
self.position_count = 2
self.mid_closed_half = False
self.trades.append({
'timestamp': timestamp,
'action': f'{direction}' if self.position_count == 1 else f'{direction}',
'price': price,
'size': position_size,
'margin': margin,
'fee': fee,
'capital': self.capital,
'reason': reason
})
return True
def close_position(self, price, ratio, timestamp, reason):
"""平仓"""
if self.position == 0:
return False
ratio = min(max(ratio, 0.0), 1.0)
if ratio == 0:
return False
close_size = abs(self.position) * ratio
if self.position > 0:
pnl = close_size * (price - self.entry_price)
else:
pnl = close_size * (self.entry_price - price)
fee = close_size * price * self.fee_rate
released_margin = self.total_margin * ratio
self.capital += released_margin + pnl - fee
self.schedule_rebate(fee, timestamp)
if ratio >= 0.999:
self.position = 0
self.position_count = 0
self.total_margin = 0
self.entry_price = 0
self.mid_closed_half = False
self.clear_delay_reversal()
else:
self.position *= (1 - ratio)
self.total_margin *= (1 - ratio)
self.trades.append({
'timestamp': timestamp,
'action': f'平仓{int(ratio*100)}%',
'price': price,
'size': close_size,
'pnl': pnl,
'fee': fee,
'capital': self.capital,
'reason': reason
})
return True
def run_backtest(self, start_date, end_date):
"""运行回测"""
# 重置状态
self.capital = self.initial_capital
self.position = 0
self.position_count = 0
self.entry_price = 0
self.total_margin = 0
self.mid_closed_half = False
self.trades = []
self.pending_rebates = []
self.total_rebate_credited = 0.0
self.clear_delay_reversal()
logger.info(f"{'='*80}")
logger.info(f"开始回测: {start_date} ~ {end_date} | 周期: {self.timeframe}")
logger.info(f"初始资金: {self.initial_capital}U | 杠杆: {self.leverage}x | BB({self.bb_period}, {self.bb_std})")
logger.info(f"{'='*80}")
# 从数据库加载数据
start_dt = pd.Timestamp(start_date)
end_dt = pd.Timestamp(end_date)
if isinstance(end_date, str) and len(end_date) <= 10:
end_dt = end_dt + pd.Timedelta(days=1) - pd.Timedelta(milliseconds=1)
start_ts = int(start_dt.timestamp() * 1000)
end_ts = int(end_dt.timestamp() * 1000)
# 根据周期选择数据表
model_mapping = {
'1m': BitMartETH1M,
'3m': BitMartETH3M,
'5m': BitMartETH5M,
'15m': BitMartETH15M,
}
Model = model_mapping.get(self.timeframe)
if Model is None:
logger.error(f"不支持的周期: {self.timeframe}")
return None
query = Model.select().where(
(Model.id >= start_ts) & (Model.id <= end_ts)
).order_by(Model.id)
data = []
for row in query:
data.append({
'timestamp': row.id,
'open': row.open,
'high': row.high,
'low': row.low,
'close': row.close
})
if not data:
logger.error("没有找到数据!")
return None
df = pd.DataFrame(data)
df['datetime'] = pd.to_datetime(df['timestamp'], unit='ms')
logger.info(f"加载数据: {len(df)} 根K线")
logger.info(f"时间范围: {df['datetime'].min()} ~ {df['datetime'].max()}")
# 计算布林带
df = self.calculate_bollinger_bands(df)
if len(df) <= self.bb_period + 1:
logger.error("数据不足,无法执行回测")
return None
# 逐根K线回测
for i in range(self.bb_period, len(df)):
row = df.iloc[i]
prev_row = df.iloc[i-1] if i > 0 else None
signal_dt = row['datetime']
signal_ts = signal_dt.strftime('%Y-%m-%d %H:%M')
high = row['high']
low = row['low']
close = row['close']
upper = row['upper']
lower = row['lower']
middle = row['middle']
# 处理返佣到账
self.apply_pending_rebates(signal_dt)
if pd.isna(upper) or pd.isna(lower) or pd.isna(middle):
continue
# 检查延迟反转确认
if self.delay_reverse_price is not None:
reversal_signal = self.check_delay_reversal_signal(i, row, prev_row)
if reversal_signal is not None and self.position != 0:
new_direction, reversal_price, reason = reversal_signal
self.close_position(reversal_price, 1.0, signal_ts, f"{reason}-平仓")
self.open_position(reversal_price, new_direction, signal_ts, f"{reason}-开仓")
self.mid_closed_half = False
self.clear_delay_reversal()
continue
# 中轨平仓逻辑
if self.position != 0:
if self.position > 0:
# 回到开仓价全平+反手
if self.mid_closed_half and low <= self.entry_price:
self.close_position(close, 1.0, signal_ts, "回开仓价全平")
self.open_position(close, 'short', signal_ts, "回开仓价反手开空")
self.mid_closed_half = False
continue
# 触中轨平半
if not self.mid_closed_half and low <= middle <= high:
self.close_position(close, 0.5, signal_ts, "触中轨平50%")
self.mid_closed_half = True
continue
else: # 空仓
# 回到开仓价全平+反手
if self.mid_closed_half and high >= self.entry_price:
self.close_position(close, 1.0, signal_ts, "回开仓价全平")
self.open_position(close, 'long', signal_ts, "回开仓价反手开多")
self.mid_closed_half = False
continue
# 触中轨平半
if not self.mid_closed_half and low <= middle <= high:
self.close_position(close, 0.5, signal_ts, "触中轨平50%")
self.mid_closed_half = True
continue
# 开仓与加仓逻辑
if self.position == 0:
self.clear_delay_reversal()
# 触上轨开空
if high >= upper:
self.open_position(upper, 'short', signal_ts, "触上轨开空")
# 触下轨开多
elif low <= lower:
self.open_position(lower, 'long', signal_ts, "触下轨开多")
continue
# 延迟反转触发
if self.position > 0 and high >= upper:
self.mark_delay_reversal('long_to_short', upper, i)
continue
elif self.position < 0 and low <= lower:
self.mark_delay_reversal('short_to_long', lower, i)
continue
# 加仓
if self.delay_reverse_price is None and self.position_count == 1:
if self.position > 0 and low <= lower:
self.open_position(lower, 'long', signal_ts, "触下轨加多")
elif self.position < 0 and high >= upper:
self.open_position(upper, 'short', signal_ts, "触上轨加空")
# 回测末尾处理返佣
self.apply_pending_rebates(df.iloc[-1]['datetime'])
# 最后平仓
if self.position != 0:
final_price = df.iloc[-1]['close']
final_time = df.iloc[-1]['datetime'].strftime('%Y-%m-%d %H:%M')
self.close_position(final_price, 1.0, final_time, "回测结束平仓")
# 生成报告
return self.generate_report(df)
def generate_report(self, df):
"""生成回测报告"""
logger.info(f"\n{'='*80}")
logger.info(f"回测报告 - {self.timeframe}")
logger.info(f"{'='*80}")
# 基本统计
total_trades = len([t for t in self.trades if '' in t['action']])
win_trades = len([t for t in self.trades if '' in t['action'] and t.get('pnl', 0) > 0])
loss_trades = len([t for t in self.trades if '' in t['action'] and t.get('pnl', 0) < 0])
total_pnl = sum([t.get('pnl', 0) for t in self.trades])
total_fee = sum([t.get('fee', 0) for t in self.trades])
pending_rebate = sum([x['amount'] for x in self.pending_rebates])
realized_net_fee = total_fee - self.total_rebate_credited
final_capital = self.capital
roi = (final_capital - self.initial_capital) / self.initial_capital * 100
logger.info(f"初始资金: {self.initial_capital:.2f}U")
logger.info(f"最终资金: {final_capital:.2f}U")
logger.info(f"总盈亏: {total_pnl:.2f}U")
logger.info(f"总手续费: {total_fee:.2f}U")
logger.info(f"返佣已到账: {self.total_rebate_credited:.2f}U")
logger.info(f"返佣待到账: {pending_rebate:.2f}U")
logger.info(f"已实现净手续费: {realized_net_fee:.2f}U")
logger.info(f"净收益: {final_capital - self.initial_capital:.2f}U")
logger.info(f"收益率: {roi:.2f}%")
logger.info(f"总交易次数: {total_trades}")
logger.info(f"盈利次数: {win_trades}")
logger.info(f"亏损次数: {loss_trades}")
if win_trades + loss_trades > 0:
logger.info(f"胜率: {win_trades/(win_trades+loss_trades)*100:.2f}%")
# 保存交易记录
trades_df = pd.DataFrame(self.trades)
output_dir = Path(__file__).parent / 'backtest_outputs' / 'trades'
output_dir.mkdir(parents=True, exist_ok=True)
output_file = output_dir / f'bb_backtest_2025_{self.timeframe}_trades.csv'
trades_df.to_csv(output_file, index=False, encoding='utf-8-sig')
logger.info(f"\n交易记录已保存到: {output_file}")
return {
'timeframe': self.timeframe,
'initial_capital': self.initial_capital,
'final_capital': final_capital,
'total_pnl': total_pnl,
'total_fee': total_fee,
'total_rebate_credited': self.total_rebate_credited,
'pending_rebate': pending_rebate,
'realized_net_fee': realized_net_fee,
'roi': roi,
'total_trades': total_trades,
'win_trades': win_trades,
'loss_trades': loss_trades,
'win_rate': win_trades/(win_trades+loss_trades)*100 if (win_trades+loss_trades) > 0 else 0,
'trades_file': str(output_file)
}
if __name__ == '__main__':
# 连接数据库
db.connect(reuse_if_open=True)
try:
results = []
for tf in ['1m', '3m', '5m', '15m']:
logger.info("\n" + "="*80)
logger.info(f"测试 {tf} 周期")
logger.info("="*80)
backtest = BBDelayReversalBacktest(timeframe=tf)
result = backtest.run_backtest('2025-01-01', '2025-12-31')
if result:
results.append(result)
# 对比总结
if len(results) > 0:
logger.info("\n" + "="*80)
logger.info("回测对比总结")
logger.info("="*80)
for result in results:
logger.info(f"\n{result['timeframe']}周期】")
logger.info(f" 收益率: {result['roi']:.2f}%")
logger.info(f" 最终资金: {result['final_capital']:.2f}U")
logger.info(f" 总交易次数: {result['total_trades']}")
logger.info(f" 胜率: {result['win_rate']:.2f}%")
logger.info(f" 净手续费: {result['realized_net_fee']:.2f}U")
finally:
db.close()

754
bb_backtest_march_2026.py Normal file
View File

@@ -0,0 +1,754 @@
"""
布林带延迟反转策略回测 - 2026年3月
策略规则:
1. 5分钟K线BB(10, 2.5)
2. 空仓触上轨开空,触下轨开多
3. 同向加仓最多1次保证金递增(1%->2%)
4. 延迟反转:触轨不立刻平仓,记录价格,回调到该价再平仓+反向开仓
5. 中轨平半+回开仓价全平
6. 止损亏损达保证金50%
"""
import pandas as pd
from pathlib import Path
from peewee import *
from loguru import logger
# 数据库配置
DB_PATH = Path(__file__).parent / 'models' / 'database.db'
db = SqliteDatabase(str(DB_PATH))
class BitMartETH5M(Model):
"""5分钟K线模型"""
id = BigIntegerField(primary_key=True)
open = FloatField(null=True)
high = FloatField(null=True)
low = FloatField(null=True)
close = FloatField(null=True)
class Meta:
database = db
table_name = 'bitmart_eth_5m'
class BitMartETH1M(Model):
"""1分钟K线模型"""
id = BigIntegerField(primary_key=True)
open = FloatField(null=True)
high = FloatField(null=True)
low = FloatField(null=True)
close = FloatField(null=True)
class Meta:
database = db
table_name = 'bitmart_eth_1m'
class BollingerBandBacktest:
"""布林带延迟反转策略回测"""
def __init__(self):
# 策略参数
self.bb_period = 10
self.bb_std = 2.5
self.initial_capital = 100 # 初始本金100U
self.leverage = 50 # 50倍杠杆
self.fee_rate = 0.0005 # 万五手续费
self.rebate_rate = 0.9 # 90%返佣
self.margin_ratio_1 = 0.01 # 首次开仓保证金比例1%
self.margin_ratio_2 = 0.01 # 加仓保证金比例1%
self.stop_loss_ratio = 0.5 # 止损比例50%
self.entry_slippage = 0.0002 # 开仓滑点2bps
self.rebate_credit_hour = 8 # 次日早上8点返佣到账上海时间
# 账户状态
self.capital = self.initial_capital
self.position = 0 # 持仓量(正=多,负=空)
self.position_count = 0 # 持仓次数0=空仓1=首次2=加仓)
self.entry_price = 0 # 开仓均价
self.total_margin = 0 # 总保证金
# 延迟反转状态
self.delay_reverse_price = None # 记录触碰轨道时的轨道价格
self.delay_reverse_type = None # 'long_to_short' 或 'short_to_long'
self.delay_reverse_kline_index = None # 触发延迟反转的K线索引
# 中轨平仓记录
self.mid_closed_half = False # 是否已平50%
# 1分钟K线缓存key=5分钟起始时间戳, value=该5分钟内的1m列表
self.one_minute_by_5m = {}
# 交易记录
self.trades = []
self.daily_pnl = []
self.pending_rebates = [] # 待到账返佣队列
self.total_rebate_credited = 0.0
self.current_run_label = "default"
def calculate_bollinger_bands(self, df):
"""计算布林带整体右移1根避免使用当前K收盘价"""
df['sma'] = df['close'].rolling(window=self.bb_period).mean()
df['std'] = df['close'].rolling(window=self.bb_period).std()
df['upper'] = (df['sma'] + self.bb_std * df['std']).shift(1)
df['lower'] = (df['sma'] - self.bb_std * df['std']).shift(1)
df['middle'] = df['sma'].shift(1)
return df
def get_rebate_amount(self, fee):
"""计算返佣金额"""
return fee * self.rebate_rate
def schedule_rebate(self, fee, timestamp):
"""登记返佣到账时间次日08:00上海时间"""
rebate = self.get_rebate_amount(fee)
if rebate <= 0:
return
trade_utc = pd.Timestamp(timestamp, tz='UTC')
trade_local = trade_utc.tz_convert('Asia/Shanghai')
credit_local = (trade_local + pd.Timedelta(days=1)).normalize() + pd.Timedelta(hours=self.rebate_credit_hour)
credit_utc = credit_local.tz_convert('UTC').tz_localize(None)
self.pending_rebates.append({
'credit_time': credit_utc,
'amount': rebate,
'trade_time': trade_utc.tz_localize(None),
})
def apply_pending_rebates(self, current_time):
"""处理当前时刻前应到账的返佣"""
if not self.pending_rebates:
return
remaining = []
for item in self.pending_rebates:
if item['credit_time'] <= current_time:
amount = item['amount']
self.capital += amount
self.total_rebate_credited += amount
self.trades.append({
'timestamp': current_time.strftime('%Y-%m-%d %H:%M'),
'action': '返佣到账',
'price': None,
'size': None,
'rebate': amount,
'capital': self.capital,
'reason': f"次日08:00返佣到账({item['trade_time'].strftime('%Y-%m-%d %H:%M')}手续费)"
})
logger.info(
f"[{current_time.strftime('%Y-%m-%d %H:%M')}] 返佣到账: {amount:.4f}U | 可用资金: {self.capital:.4f}U"
)
else:
remaining.append(item)
self.pending_rebates = remaining
def apply_entry_slippage(self, price, direction):
"""按方向施加不利滑点"""
if direction == 'long':
return price * (1 + self.entry_slippage)
return price * (1 - self.entry_slippage)
def load_one_minute_cache(self, start_ts, end_ts):
"""加载并缓存1分钟K线用于5分钟内走势确认"""
query = BitMartETH1M.select().where(
(BitMartETH1M.id >= start_ts) & (BitMartETH1M.id <= end_ts)
).order_by(BitMartETH1M.id)
bucket = {}
for row in query:
five_min_start = int(row.id - (row.id % 300000))
bucket.setdefault(five_min_start, []).append({
'timestamp': int(row.id),
'open': float(row.open) if row.open is not None else None,
'high': float(row.high) if row.high is not None else None,
'low': float(row.low) if row.low is not None else None,
'close': float(row.close) if row.close is not None else None,
})
self.one_minute_by_5m = bucket
total_rows = sum(len(v) for v in bucket.values())
logger.info(f"加载1分钟数据: {total_rows} 根 | 5分钟桶: {len(bucket)}")
def should_close_half_by_1m_trend(self, five_min_ts, middle_price, side):
"""
1分钟顺序确认中轨平半
- 多仓:先到中轨上方,再回踩中轨
- 空仓:先到中轨下方,再反抽中轨
"""
minute_rows = self.one_minute_by_5m.get(five_min_ts, [])
if not minute_rows:
return False, None
seen_above_middle = False
seen_below_middle = False
for minute in minute_rows:
m_open = minute['open']
m_high = minute['high']
m_low = minute['low']
m_close = minute['close']
if None in (m_open, m_high, m_low, m_close):
continue
minute_time = pd.to_datetime(minute['timestamp'], unit='ms').strftime('%H:%M')
if side == 'long':
# 当前1m开在中轨上方且回踩到中轨视为有效回踩
if m_open >= middle_price and m_low <= middle_price:
return True, f"1m({minute_time})回踩中轨"
# 已经到过中轨上方后,再次触及中轨
if seen_above_middle and m_low <= middle_price:
return True, f"1m({minute_time})回踩中轨"
if m_high >= middle_price:
seen_above_middle = True
else: # short
# 当前1m开在中轨下方且反抽到中轨视为有效反抽
if m_open <= middle_price and m_high >= middle_price:
return True, f"1m({minute_time})反抽中轨"
# 已经到过中轨下方后,再次触及中轨
if seen_below_middle and m_high >= middle_price:
return True, f"1m({minute_time})反抽中轨"
if m_low <= middle_price:
seen_below_middle = True
return False, None
def get_touch_entry_price(self, five_min_ts, direction, touch_price):
"""
触轨开仓的保守成交价:
- 在该5分钟内找到首个触轨1m
- 用该1m收盘价作为基准
- 为避免“过于理想”,不允许优于触轨价,再叠加不利滑点
"""
minute_rows = self.one_minute_by_5m.get(five_min_ts, [])
if not minute_rows:
return self.apply_entry_slippage(touch_price, direction)
trigger_close = None
for minute in minute_rows:
m_high = minute['high']
m_low = minute['low']
m_close = minute['close']
if None in (m_high, m_low, m_close):
continue
if direction == 'short' and m_high >= touch_price:
trigger_close = m_close
break
if direction == 'long' and m_low <= touch_price:
trigger_close = m_close
break
if trigger_close is None:
base_price = touch_price
elif direction == 'long':
base_price = max(touch_price, trigger_close)
else:
base_price = min(touch_price, trigger_close)
return self.apply_entry_slippage(base_price, direction)
def clear_delay_reversal(self):
"""清理延迟反转状态"""
self.delay_reverse_price = None
self.delay_reverse_type = None
self.delay_reverse_kline_index = None
def mark_delay_reversal(self, reverse_type, trigger_price, kline_index, timestamp):
"""记录延迟反转触发信息"""
self.delay_reverse_type = reverse_type
self.delay_reverse_price = trigger_price
self.delay_reverse_kline_index = kline_index
if reverse_type == 'long_to_short':
logger.info(f"[{timestamp}] 多仓触上轨 @ {trigger_price:.2f},进入延迟反转")
else:
logger.info(f"[{timestamp}] 空仓触下轨 @ {trigger_price:.2f},进入延迟反转")
def reverse_position(self, price, new_direction, timestamp, reason):
"""全平后反向开仓"""
if self.position == 0:
self.clear_delay_reversal()
return False
close_side = "" if self.position > 0 else ""
open_side = "" if new_direction == "long" else ""
self.close_position(price, 1.0, timestamp, f"{reason}-平{close_side}")
open_price = self.apply_entry_slippage(price, new_direction)
self.open_position(open_price, new_direction, timestamp, f"{reason}-开{open_side}")
self.mid_closed_half = False
self.clear_delay_reversal()
return True
def check_delay_reversal_signal(self, i, row, prev_row):
"""检查延迟反转是否在当前收盘K成立仅返回信号不直接执行"""
if self.position == 0 or self.delay_reverse_price is None or self.delay_reverse_kline_index is None:
return None
offset = i - self.delay_reverse_kline_index
# 禁止同K确认最早次K确认
if offset <= 0:
return None
high = row['high']
low = row['low']
if self.delay_reverse_type == 'long_to_short':
# 情况1触上轨后次K回调到记录上轨价
if offset == 1 and low <= self.delay_reverse_price:
return 'short', "延迟反转-次K回调确认", self.delay_reverse_price
# 情况2持续等待动态追踪上一根K线条件
if offset >= 2 and prev_row is not None:
prev_upper = prev_row['upper']
prev_touch_upper = pd.notna(prev_upper) and prev_row['high'] >= prev_upper
if prev_touch_upper:
if low <= prev_upper:
return 'short', "延迟反转-上一根触上轨后回调确认", prev_upper
else:
prev_body_low = min(prev_row['open'], prev_row['close'])
if low <= prev_body_low:
return 'short', "延迟反转-跌破上一根实体确认", prev_body_low
elif self.delay_reverse_type == 'short_to_long':
# 情况1触下轨后次K反弹到记录下轨价
if offset == 1 and high >= self.delay_reverse_price:
return 'long', "延迟反转-次K反弹确认", self.delay_reverse_price
# 情况2持续等待动态追踪上一根K线条件
if offset >= 2 and prev_row is not None:
prev_lower = prev_row['lower']
prev_touch_lower = pd.notna(prev_lower) and prev_row['low'] <= prev_lower
if prev_touch_lower:
if high >= prev_lower:
return 'long', "延迟反转-上一根触下轨后反弹确认", prev_lower
else:
prev_body_high = max(prev_row['open'], prev_row['close'])
if high >= prev_body_high:
return 'long', "延迟反转-突破上一根实体确认", prev_body_high
return None
def open_position(self, price, direction, timestamp, reason):
"""开仓或加仓"""
if self.position_count not in (0, 1):
return False
if self.position_count == 1:
current_direction = 'long' if self.position > 0 else 'short'
if direction != current_direction:
logger.warning("加仓方向不一致,跳过")
return False
margin_ratio = self.margin_ratio_1 if self.position_count == 0 else self.margin_ratio_2
margin = self.capital * margin_ratio
if margin <= 0:
logger.warning(f"[{timestamp}] 资金不足,无法开仓 | 可用资金: {self.capital:.4f}U")
return False
position_size = margin * self.leverage / price
fee = position_size * price * self.fee_rate
required = margin + fee
if self.capital < required:
logger.warning(
f"[{timestamp}] 可用资金不足,无法开仓 | 需要: {required:.4f}U | 可用: {self.capital:.4f}U"
)
return False
# 冻结保证金并扣除手续费
self.capital -= required
self.schedule_rebate(fee, timestamp)
if self.position_count == 0:
self.position = position_size if direction == 'long' else -position_size
self.entry_price = price
self.total_margin = margin
self.position_count = 1
action = f'{direction}'
else:
old_size = abs(self.position)
new_size = old_size + position_size
old_value = old_size * self.entry_price
new_value = position_size * price
self.entry_price = (old_value + new_value) / new_size
self.position = new_size if direction == 'long' else -new_size
self.total_margin += margin
self.position_count = 2
action = f'{direction}'
self.mid_closed_half = False
self.trades.append({
'timestamp': timestamp,
'action': action,
'price': price,
'size': position_size,
'margin': margin,
'fee': fee,
'rebate': self.get_rebate_amount(fee),
'capital': self.capital,
'reason': reason
})
logger.info(
f"[{timestamp}] {action} @ {price:.2f} | 仓位: {position_size:.4f} | "
f"保证金: {margin:.4f}U | 手续费: {fee:.4f}U | 返佣待到账: {self.get_rebate_amount(fee):.4f}U | "
f"可用资金: {self.capital:.4f}U | {reason}"
)
return True
def close_position(self, price, ratio, timestamp, reason):
"""平仓"""
if self.position == 0:
return False
ratio = min(max(ratio, 0.0), 1.0)
if ratio == 0:
return False
close_size = abs(self.position) * ratio
if self.position > 0:
pnl = close_size * (price - self.entry_price)
else:
pnl = close_size * (self.entry_price - price)
fee = close_size * price * self.fee_rate
released_margin = self.total_margin * ratio
self.capital += released_margin + pnl - fee
self.schedule_rebate(fee, timestamp)
if ratio >= 0.999:
self.position = 0
self.position_count = 0
self.total_margin = 0
self.entry_price = 0
self.mid_closed_half = False
self.clear_delay_reversal()
else:
self.position *= (1 - ratio)
self.total_margin *= (1 - ratio)
if abs(self.position) < 1e-12:
self.position = 0
self.position_count = 0
self.total_margin = 0
self.entry_price = 0
self.mid_closed_half = False
self.clear_delay_reversal()
self.trades.append({
'timestamp': timestamp,
'action': f'平仓{int(ratio*100)}%',
'price': price,
'size': close_size,
'pnl': pnl,
'fee': fee,
'rebate': self.get_rebate_amount(fee),
'capital': self.capital,
'reason': reason
})
logger.info(f"[{timestamp}] 平仓{int(ratio*100)}% @ {price:.2f} | "
f"盈亏: {pnl:.4f}U | 手续费: {fee:.4f}U | 返佣待到账: {self.get_rebate_amount(fee):.4f}U | "
f"可用资金: {self.capital:.4f}U | {reason}")
return True
def check_stop_loss(self, high, low):
"""检查当前收盘K是否触发止损信号"""
if self.position == 0:
return False
stop_price = low if self.position > 0 else high
if self.position > 0:
unrealized_pnl = abs(self.position) * (stop_price - self.entry_price)
else:
unrealized_pnl = abs(self.position) * (self.entry_price - stop_price)
return unrealized_pnl <= -self.total_margin * self.stop_loss_ratio
def run_backtest(self, start_date, end_date):
"""运行回测开仓当K触发平仓/止损仍按下一根K开盘执行"""
# 重置状态,支持同一实例重复回测
self.capital = self.initial_capital
self.position = 0
self.position_count = 0
self.entry_price = 0
self.total_margin = 0
self.mid_closed_half = False
self.trades = []
self.daily_pnl = []
self.pending_rebates = []
self.total_rebate_credited = 0.0
self.clear_delay_reversal()
logger.info(f"{'='*80}")
logger.info(f"开始回测: {start_date} ~ {end_date}")
logger.info(f"初始资金: {self.initial_capital}U | 杠杆: {self.leverage}x | BB({self.bb_period}, {self.bb_std})")
logger.info(f"{'='*80}")
# 从数据库加载数据
start_dt = pd.Timestamp(start_date)
end_dt = pd.Timestamp(end_date)
if isinstance(end_date, str) and len(end_date) <= 10:
end_dt = end_dt + pd.Timedelta(days=1) - pd.Timedelta(milliseconds=1)
self.current_run_label = f"{start_dt.strftime('%Y%m%d')}_{end_dt.strftime('%Y%m%d')}"
start_ts = int(start_dt.timestamp() * 1000)
end_ts = int(end_dt.timestamp() * 1000)
query = BitMartETH5M.select().where(
(BitMartETH5M.id >= start_ts) & (BitMartETH5M.id <= end_ts)
).order_by(BitMartETH5M.id)
data = []
for row in query:
data.append({
'timestamp': row.id,
'open': row.open,
'high': row.high,
'low': row.low,
'close': row.close
})
if not data:
logger.error("没有找到数据!")
return None
df = pd.DataFrame(data)
df['datetime'] = pd.to_datetime(df['timestamp'], unit='ms')
logger.info(f"加载数据: {len(df)} 根K线")
logger.info(f"时间范围: {df['datetime'].min()} ~ {df['datetime'].max()}")
# 计算布林带
df = self.calculate_bollinger_bands(df)
if len(df) <= self.bb_period + 1:
logger.error("数据不足,无法执行回测")
return None
# 逐根K线回测开仓当K触发平仓/止损下一K开盘执行
for i in range(self.bb_period, len(df) - 1):
row = df.iloc[i]
prev_row = df.iloc[i-1] if i > 0 else None
next_row = df.iloc[i + 1]
signal_dt = row['datetime']
signal_ts = signal_dt.strftime('%Y-%m-%d %H:%M')
five_min_ts = int(row['timestamp'])
execute_ts = next_row['datetime'].strftime('%Y-%m-%d %H:%M')
next_open = float(next_row['open']) if pd.notna(next_row['open']) else None
high = row['high']
low = row['low']
upper = row['upper']
lower = row['lower']
middle = row['middle']
# 先处理当前收盘时刻返佣到账
self.apply_pending_rebates(signal_dt)
if pd.isna(upper) or pd.isna(lower) or pd.isna(middle):
continue
if next_open is None:
continue
# 检查止损收盘确认下一K开盘平仓
if self.check_stop_loss(high, low):
logger.warning(f"[{signal_ts}] 触发止损信号下一K开盘执行")
self.close_position(next_open, 1.0, execute_ts, f"止损-收盘确认({signal_ts})")
continue
# 已处于延迟反转状态时,先检查确认逻辑
if self.delay_reverse_price is not None:
reversal_signal = self.check_delay_reversal_signal(i, row, prev_row)
if reversal_signal is not None and self.position != 0:
new_direction, reason, reversal_price = reversal_signal
if reversal_price is None or pd.isna(reversal_price):
reversal_price = float(row['close'])
close_side = "" if self.position > 0 else ""
open_side = "" if new_direction == 'long' else ""
self.close_position(
reversal_price,
1.0,
signal_ts,
f"{reason}-当K确认({signal_ts})-平{close_side}"
)
open_price = self.apply_entry_slippage(reversal_price, new_direction)
self.open_position(
open_price,
new_direction,
signal_ts,
f"{reason}-当K确认({signal_ts})-开{open_side}"
)
self.mid_closed_half = False
self.clear_delay_reversal()
continue
# === 中轨平仓逻辑 ===
if self.position != 0:
had_mid_closed_half = self.mid_closed_half
if self.position > 0: # 多仓
# 回到开仓价全平+反手(仅在此前已平半的前提下触发)
if had_mid_closed_half and low <= self.entry_price:
self.close_position(next_open, 1.0, execute_ts, f"回开仓价全平-收盘确认({signal_ts})")
entry_price = self.apply_entry_slippage(next_open, 'short')
self.open_position(entry_price, 'short', execute_ts, f"回开仓价反手开空-收盘确认({signal_ts})")
self.mid_closed_half = False
continue
# 触中轨平半(收盘确认)
if not had_mid_closed_half and low <= middle <= high:
self.close_position(next_open, 0.5, execute_ts, f"触中轨平50%-收盘确认({signal_ts})")
self.mid_closed_half = True
continue
else: # 空仓
# 回到开仓价全平+反手(仅在此前已平半的前提下触发)
if had_mid_closed_half and high >= self.entry_price:
self.close_position(next_open, 1.0, execute_ts, f"回开仓价全平-收盘确认({signal_ts})")
entry_price = self.apply_entry_slippage(next_open, 'long')
self.open_position(entry_price, 'long', execute_ts, f"回开仓价反手开多-收盘确认({signal_ts})")
self.mid_closed_half = False
continue
# 触中轨平半(收盘确认)
if not had_mid_closed_half and low <= middle <= high:
self.close_position(next_open, 0.5, execute_ts, f"触中轨平50%-收盘确认({signal_ts})")
self.mid_closed_half = True
continue
# === 开仓与加仓逻辑 ===
if self.position == 0: # 空仓
self.clear_delay_reversal()
# 触上轨开空
if high >= upper:
entry_price = self.get_touch_entry_price(five_min_ts, 'short', upper)
self.open_position(entry_price, 'short', signal_ts, f"触上轨开空-当K触发({signal_ts})")
# 触下轨开多
elif low <= lower:
entry_price = self.get_touch_entry_price(five_min_ts, 'long', lower)
self.open_position(entry_price, 'long', signal_ts, f"触下轨开多-当K触发({signal_ts})")
continue
# 有持仓:先检查是否触发延迟反转(核心)
if self.position > 0 and high >= upper:
self.mark_delay_reversal('long_to_short', upper, i, signal_ts)
continue
elif self.position < 0 and low <= lower:
self.mark_delay_reversal('short_to_long', lower, i, signal_ts)
continue
# 进入延迟反转等待后,不再执行加仓
if self.delay_reverse_price is not None:
continue
# 同向加仓最多1次
if self.position_count == 1:
if self.position > 0 and low <= lower:
entry_price = self.get_touch_entry_price(five_min_ts, 'long', lower)
self.open_position(entry_price, 'long', signal_ts, f"触下轨加多-当K触发({signal_ts})")
elif self.position < 0 and high >= upper:
entry_price = self.get_touch_entry_price(five_min_ts, 'short', upper)
self.open_position(entry_price, 'short', signal_ts, f"触上轨加空-当K触发({signal_ts})")
# 回测末尾再处理一次返佣到账
self.apply_pending_rebates(df.iloc[-1]['datetime'])
# 最后平仓
if self.position != 0:
final_price = df.iloc[-1]['close']
final_time = df.iloc[-1]['datetime'].strftime('%Y-%m-%d %H:%M')
self.close_position(final_price, 1.0, final_time, "回测结束平仓")
# 生成报告
return self.generate_report(df)
def generate_report(self, df):
"""生成回测报告"""
logger.info(f"\n{'='*80}")
logger.info("回测报告")
logger.info(f"{'='*80}")
# 基本统计
total_trades = len([t for t in self.trades if '' in t['action']])
win_trades = len([t for t in self.trades if '' in t['action'] and t.get('pnl', 0) > 0])
loss_trades = len([t for t in self.trades if '' in t['action'] and t.get('pnl', 0) < 0])
total_pnl = sum([t.get('pnl', 0) for t in self.trades])
total_fee = sum([t.get('fee', 0) for t in self.trades])
total_rebate_expected = sum([t.get('rebate', 0) for t in self.trades if t.get('fee', 0) > 0])
pending_rebate = sum([x['amount'] for x in self.pending_rebates])
realized_net_fee = total_fee - self.total_rebate_credited
final_capital = self.capital
roi = (final_capital - self.initial_capital) / self.initial_capital * 100
logger.info(f"初始资金: {self.initial_capital:.2f}U")
logger.info(f"最终资金: {final_capital:.2f}U")
logger.info(f"总盈亏: {total_pnl:.2f}U")
logger.info(f"总手续费(开平全额): {total_fee:.2f}U")
logger.info(f"返佣应返总额: {total_rebate_expected:.2f}U")
logger.info(f"返佣已到账: {self.total_rebate_credited:.2f}U")
logger.info(f"返佣待到账: {pending_rebate:.2f}U")
logger.info(f"已实现净手续费: {realized_net_fee:.2f}U")
logger.info(f"净收益: {final_capital - self.initial_capital:.2f}U")
logger.info(f"收益率: {roi:.2f}%")
logger.info(f"总交易次数: {total_trades}")
logger.info(f"盈利次数: {win_trades}")
logger.info(f"亏损次数: {loss_trades}")
if win_trades + loss_trades > 0:
logger.info(f"胜率: {win_trades/(win_trades+loss_trades)*100:.2f}%")
# 保存交易记录
trades_df = pd.DataFrame(self.trades)
output_dir = Path(__file__).parent / 'backtest_outputs' / 'trades'
output_dir.mkdir(parents=True, exist_ok=True)
output_file = output_dir / f'bb_backtest_{self.current_run_label}_trades.csv'
trades_df.to_csv(output_file, index=False, encoding='utf-8-sig')
logger.info(f"\n交易记录已保存到: {output_file}")
return {
'initial_capital': self.initial_capital,
'final_capital': final_capital,
'total_pnl': total_pnl,
'total_fee': total_fee,
'total_rebate_expected': total_rebate_expected,
'total_rebate_credited': self.total_rebate_credited,
'pending_rebate': pending_rebate,
'realized_net_fee': realized_net_fee,
'roi': roi,
'total_trades': total_trades,
'win_trades': win_trades,
'loss_trades': loss_trades,
'trades_file': str(output_file),
'trades': self.trades
}
if __name__ == '__main__':
# 连接数据库
db.connect(reuse_if_open=True)
try:
# 创建回测实例
backtest = BollingerBandBacktest()
# 运行回测2026年全年
result = backtest.run_backtest('2026-01-01', '2026-12-31')
if result:
logger.success(f"\n回测完成!最终收益率: {result['roi']:.2f}%")
finally:
db.close()

626
bb_delay_reversal_trade.py Normal file
View File

@@ -0,0 +1,626 @@
"""
布林带均值回归策略 — 实盘交易 (D方案: 递增加仓)
BB(10, 2.5) | 5分钟K线 | ETH | 50x杠杆 逐仓 | 递增加仓+1%/次 max=3
逻辑:
- 价格触及上布林带 → 平多(如有) + 开空; 已持空则加仓
- 价格触及下布林带 → 平空(如有) + 开多; 已持多则加仓
- 始终持仓(多空翻转 + 同向加仓)
- 加仓比例: 开仓1%, 第1次加仓2%, 第2次3%, 第3次4%, 最多加仓3次
使用浏览器自动化进行开平仓有手续费返佣API仅用于查询数据
"""
import time
import numpy as np
from datetime import datetime, timezone
from pathlib import Path
from loguru import logger
from bitmart.api_contract import APIContract
from bit_tools import openBrowser
from DrissionPage import ChromiumPage, ChromiumOptions
# ---------------------------------------------------------------------------
# 配置
# ---------------------------------------------------------------------------
class BBDelayReversalConfig:
# API 凭证
API_KEY = "a0fb7b98464fd9bcce67e7c519d58ec10d0c38a8"
SECRET_KEY = "4eaeba78e77aeaab1c2027f846a276d164f264a44c2c1bb1c5f3be50c8de1ca5"
MEMO = "合约交易"
# 合约
CONTRACT_SYMBOL = "ETHUSDT"
TRADE_URL = "https://derivatives.bitmart.com/zh-CN/futures/ETHUSDT"
# 浏览器
BIT_ID = "f2320f57e24c45529a009e1541e25961"
# 布林带参数
BB_PERIOD = 10 # 10根5分钟K线 = 50分钟回看
BB_STD = 2.5 # 标准差倍数
# 仓位管理
LEVERAGE = 50 # 杠杆倍数
OPEN_TYPE = "isolated" # 逐仓模式
MARGIN_PCT = 0.01 # 首次开仓用权益的1%作为保证金
# 递增加仓 (D方案)
PYRAMID_STEP = 0.01 # 每次加仓增加1%权益比例 (1%→2%→3%→4%)
PYRAMID_MAX = 3 # 最多加仓3次 (首次开仓不算)
# 风控
MAX_DAILY_LOSS = 50.0 # 日最大亏损(U),达到后停止交易
COOLDOWN_SECONDS = 30 # 两次交易之间最小间隔(秒)
# 运行
POLL_INTERVAL = 5 # 主循环轮询间隔(秒)
KLINE_STEP = 5 # K线周期(分钟)
KLINE_HOURS = 2 # 获取最近多少小时K线(需覆盖BB_PERIOD)
# ---------------------------------------------------------------------------
# 布林带计算
# ---------------------------------------------------------------------------
def calc_bollinger(closes: list, period: int, n_std: float):
"""计算布林带,返回 (mid, upper, lower) 或 None数据不足时"""
if len(closes) < period:
return None
arr = np.array(closes[-period:], dtype=float)
mid = arr.mean()
std = arr.std(ddof=0)
upper = mid + n_std * std
lower = mid - n_std * std
return mid, upper, lower
# ---------------------------------------------------------------------------
# 交易主类
# ---------------------------------------------------------------------------
class BBDelayReversalTrader:
def __init__(self, cfg: BBDelayReversalConfig = None, bit_id: str = None):
self.cfg = cfg or BBDelayReversalConfig()
if bit_id:
self.cfg.BIT_ID = bit_id
self.api = APIContract(
self.cfg.API_KEY, self.cfg.SECRET_KEY, self.cfg.MEMO,
timeout=(5, 15)
)
# 浏览器
self.page: ChromiumPage | None = None
self.page_start = True # 需要(重新)打开浏览器
self.last_page_open_time = 0.0 # 上次打开浏览器的时间
self.PAGE_REFRESH_INTERVAL = 1800 # 每30分钟关闭重开浏览器
# 持仓状态: -1=空, 0=无, 1=多
self.position = 0
self.open_avg_price = None
self.current_amount = None
# 加仓状态
self.pyramid_count = 0 # 当前已加仓次数 (0=仅首次开仓)
# 风控
self.daily_pnl = 0.0
self.daily_stopped = False
self.current_date = None
self.last_trade_time = 0.0
# 日志
self.log_dir = Path(__file__).resolve().parent
logger.add(
self.log_dir / "bb_delay_reversal_trade_{time:YYYY-MM-DD}.log",
rotation="1 day", retention="30 days",
format="{time:YYYY-MM-DD HH:mm:ss} | {level} | {message}"
)
# ------------------------------------------------------------------
# API 封装
# ------------------------------------------------------------------
def get_klines(self) -> list | None:
"""获取最近N小时的5分钟K线返回 [{id, open, high, low, close}, ...]"""
try:
end_time = int(time.time())
start_time = end_time - 3600 * self.cfg.KLINE_HOURS
resp = self.api.get_kline(
contract_symbol=self.cfg.CONTRACT_SYMBOL,
step=self.cfg.KLINE_STEP,
start_time=start_time,
end_time=end_time
)[0]
if resp.get("code") != 1000:
logger.error(f"获取K线失败: {resp}")
return None
data = resp["data"]
klines = []
for k in data:
klines.append({
"id": int(k["timestamp"]),
"open": float(k["open_price"]),
"high": float(k["high_price"]),
"low": float(k["low_price"]),
"close": float(k["close_price"]),
})
klines.sort(key=lambda x: x["id"])
return klines
except Exception as e:
logger.error(f"获取K线异常: {e}")
return None
def get_current_price(self) -> float | None:
"""获取当前最新价格最近1分钟K线收盘价"""
try:
end_time = int(time.time())
resp = self.api.get_kline(
contract_symbol=self.cfg.CONTRACT_SYMBOL,
step=1,
start_time=end_time - 300,
end_time=end_time
)[0]
if resp.get("code") == 1000 and resp["data"]:
return float(resp["data"][-1]["close_price"])
return None
except Exception as e:
logger.error(f"获取价格异常: {e}")
return None
def get_balance(self) -> float | None:
"""获取合约账户可用余额"""
try:
resp = self.api.get_assets_detail()[0]
if resp.get("code") == 1000:
data = resp["data"]
if isinstance(data, dict):
return float(data.get("available_balance", 0))
elif isinstance(data, list):
for asset in data:
if asset.get("currency") == "USDT":
return float(asset.get("available_balance", 0))
return None
except Exception as e:
logger.error(f"查询余额异常: {e}")
return None
def get_position_status(self) -> bool:
"""查询当前持仓,更新 self.position / open_avg_price / current_amount"""
try:
resp = self.api.get_position(contract_symbol=self.cfg.CONTRACT_SYMBOL)[0]
if resp.get("code") != 1000:
logger.error(f"查询持仓失败: {resp}")
return False
positions = resp["data"]
if not positions:
self.position = 0
self.open_avg_price = None
self.current_amount = None
return True
pos = positions[0]
self.position = 1 if pos["position_type"] == 1 else -1
self.open_avg_price = float(pos["open_avg_price"])
self.current_amount = float(pos["current_amount"])
unrealized = float(pos.get("unrealized_value", 0))
logger.debug(f"持仓: dir={self.position} price={self.open_avg_price} "
f"amt={self.current_amount} upnl={unrealized:.2f}")
return True
except Exception as e:
logger.error(f"查询持仓异常: {e}")
return False
def set_leverage(self) -> bool:
"""设置杠杆和逐仓模式"""
try:
resp = self.api.post_submit_leverage(
contract_symbol=self.cfg.CONTRACT_SYMBOL,
leverage=str(self.cfg.LEVERAGE),
open_type=self.cfg.OPEN_TYPE
)[0]
if resp.get("code") == 1000:
logger.success(f"杠杆设置成功: {self.cfg.LEVERAGE}x {self.cfg.OPEN_TYPE}")
return True
else:
logger.error(f"杠杆设置失败: {resp}")
return False
except Exception as e:
logger.error(f"设置杠杆异常: {e}")
return False
# ------------------------------------------------------------------
# 浏览器自动化
# ------------------------------------------------------------------
def open_browser(self) -> bool:
"""打开浏览器并进入交易页面"""
try:
bit_port = openBrowser(id=self.cfg.BIT_ID)
co = ChromiumOptions()
co.set_local_port(port=bit_port)
self.page = ChromiumPage(addr_or_opts=co)
self.last_page_open_time = time.time()
return True
except Exception as e:
logger.error(f"打开浏览器失败: {e}")
return False
def click_safe(self, xpath, sleep=0.5) -> bool:
"""安全点击元素"""
try:
ele = self.page.ele(xpath)
if not ele:
return False
ele.click(by_js=True)
time.sleep(sleep)
return True
except Exception as e:
logger.warning(f"点击失败 [{xpath}]: {e}")
return False
def browser_close_position(self) -> bool:
"""浏览器点击市价全平"""
logger.info("浏览器操作: 市价平仓")
return self.click_safe('x://span[normalize-space(text()) ="市价"]')
# ------------------------------------------------------------------
# 仓位操作
# ------------------------------------------------------------------
def calc_order_usdt(self, is_add: bool = False) -> float:
"""
计算开仓/加仓金额(U)
首次开仓: 余额 × MARGIN_PCT (1%)
加仓: 余额 × (MARGIN_PCT + PYRAMID_STEP × (pyramid_count+1))
例: 开仓1%, 第1次加仓2%, 第2次加仓3%, 第3次加仓4%
"""
balance = self.get_balance()
if balance is None or balance <= 0:
logger.warning(f"余额不足或查询失败: {balance}")
return 0
if is_add:
pct = self.cfg.MARGIN_PCT + self.cfg.PYRAMID_STEP * (self.pyramid_count + 1)
else:
pct = self.cfg.MARGIN_PCT
order_usdt = round(balance * pct, 2)
logger.info(f"仓位计算: 余额={balance:.2f} × {pct:.0%} = {order_usdt} U"
f" ({'加仓#' + str(self.pyramid_count+1) if is_add else '首次开仓'})")
return order_usdt
def verify_position(self, expected: int) -> bool:
"""验证持仓方向"""
if self.get_position_status():
if self.position == expected:
return True
logger.warning(f"持仓方向不符: 期望{expected}, 实际{self.position}")
return False
# ------------------------------------------------------------------
# 风控
# ------------------------------------------------------------------
def check_daily_reset(self):
"""每日重置(UTC+8 00:00 = UTC 16:00)"""
now = datetime.now(timezone.utc)
# 用UTC日期做简单日切
today = now.date()
if self.current_date != today:
if self.current_date is not None:
logger.info(f"日切: {self.current_date}{today}, 日PnL={self.daily_pnl:.2f}")
self.current_date = today
self.daily_pnl = 0.0
self.daily_stopped = False
def can_trade(self) -> bool:
"""检查是否可交易"""
if self.daily_stopped:
return False
now = time.time()
if now - self.last_trade_time < self.cfg.COOLDOWN_SECONDS:
remain = self.cfg.COOLDOWN_SECONDS - (now - self.last_trade_time)
logger.debug(f"交易冷却中,剩余 {remain:.0f}s")
return False
return True
# ------------------------------------------------------------------
# 日志
# ------------------------------------------------------------------
def write_trade_log(self, action: str, price: float, bb_upper: float,
bb_mid: float, bb_lower: float, reason: str):
"""写入交易日志文件"""
try:
date_str = datetime.now().strftime("%Y%m%d")
log_file = self.log_dir / f"bb_delay_reversal_trade_log_{date_str}.txt"
time_str = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
block = (
f"\n{'='*60}\n"
f"时间: {time_str}\n"
f"操作: {action}\n"
f"价格: {price:.2f}\n"
f"BB上轨: {bb_upper:.2f} | 中轨: {bb_mid:.2f} | 下轨: {bb_lower:.2f}\n"
f"原因: {reason}\n"
f"{'='*60}\n"
)
with open(log_file, "a", encoding="utf-8") as f:
f.write(block)
except Exception as e:
logger.warning(f"写入日志失败: {e}")
def login(self):
self.page.ele('x://input[@placeholder="邮箱"]').input("ddrwode@gmail.com")
self.page.ele('x://input[@placeholder="密码"]').input("040828cjj")
self.page.ele('x://*[@id="__layout"]/div/div[2]/div/div[2]/div/div/div[2]/div[1]/div[2]/div/div[1]/div[2]/form/div[3]/div/button').click()
# ------------------------------------------------------------------
# 主循环(浏览器流程与四分之一代码一致)
# ------------------------------------------------------------------
def run(self):
"""策略主循环"""
logger.info("=" * 60)
logger.info(f" BB策略启动(D方案): BB({self.cfg.BB_PERIOD},{self.cfg.BB_STD})")
logger.info(f" 合约: {self.cfg.CONTRACT_SYMBOL} | {self.cfg.LEVERAGE}x {self.cfg.OPEN_TYPE}")
logger.info(f" 首次开仓: 权益×{self.cfg.MARGIN_PCT:.0%} | 递增加仓: +{self.cfg.PYRAMID_STEP:.0%}/次 | 最多{self.cfg.PYRAMID_MAX}")
logger.info("=" * 60)
# 设置杠杆
if not self.set_leverage():
logger.error("杠杆设置失败,退出")
return
# 初始持仓同步
if not self.get_position_status():
logger.error("初始持仓查询失败,退出")
return
logger.info(f"初始持仓状态: {self.position}")
last_kline_id = None # 避免同一根K线重复触发
page_start = True # 需要打开浏览器
while True:
# ===== 浏览器管理 =====
# page_start时: 打开浏览器 → 导航 → 点市价 → 输入张数
if page_start:
for i in range(5):
if self.open_browser():
logger.info("浏览器打开成功")
break
else:
logger.error("打开浏览器失败!")
return
# self.login()
self.page.get(self.cfg.TRADE_URL)
time.sleep(2)
# 点击市价模式
self.click_safe('x://button[normalize-space(text()) ="市价"]')
time.sleep(0.5)
# 计算并预输入开仓金额(U)
current_price = self.get_current_price()
if current_price:
order_usdt = self.calc_order_usdt()
if order_usdt > 0:
self.page.ele('x://*[@id="size_0"]').input(vals=order_usdt, clear=True)
logger.info(f"预输入开仓金额: {order_usdt} U")
page_start = False
try:
# 每30分钟关闭浏览器重新打开
if time.time() - self.last_page_open_time >= self.PAGE_REFRESH_INTERVAL:
logger.info("浏览器已打开超过30分钟关闭刷新")
try:
self.page.close()
except Exception:
pass
self.page = None
page_start = True
time.sleep(3)
continue
self.check_daily_reset()
if self.daily_stopped:
logger.info(f"日亏损已达限制({self.daily_pnl:.2f}),等待日切")
time.sleep(60)
continue
# 1. 获取K线
klines = self.get_klines()
if not klines or len(klines) < self.cfg.BB_PERIOD:
logger.warning(f"K线数据不足({len(klines) if klines else 0}根),等待...")
time.sleep(self.cfg.POLL_INTERVAL)
continue
closed_klines = klines[:-1]
current_kline = klines[-1]
if len(closed_klines) < self.cfg.BB_PERIOD:
time.sleep(self.cfg.POLL_INTERVAL)
continue
# 2. 计算布林带
closes = [k["close"] for k in closed_klines]
bb = calc_bollinger(closes, self.cfg.BB_PERIOD, self.cfg.BB_STD)
if bb is None:
time.sleep(self.cfg.POLL_INTERVAL)
continue
bb_mid, bb_upper, bb_lower = bb
# 3. 获取当前价格
current_price = self.get_current_price()
if current_price is None:
time.sleep(self.cfg.POLL_INTERVAL)
continue
cur_high = current_kline["high"]
cur_low = current_kline["low"]
# 容错: K线high/low + 当前实时价格,任一触及即算触碰
touched_upper = cur_high >= bb_upper or current_price >= bb_upper
touched_lower = cur_low <= bb_lower or current_price <= bb_lower
logger.info(
f"价格={current_price:.2f} | "
f"BB: {bb_lower:.2f} / {bb_mid:.2f} / {bb_upper:.2f} | "
f"H={cur_high:.2f} L={cur_low:.2f} | "
f"触上={touched_upper} 触下={touched_lower} | "
f"仓位={self.position}"
)
# 4. 同步持仓状态
if not self.get_position_status():
time.sleep(self.cfg.POLL_INTERVAL)
continue
# 5. 信号判断
kline_id = current_kline["id"]
if kline_id == last_kline_id:
time.sleep(self.cfg.POLL_INTERVAL)
continue
if touched_upper and touched_lower:
logger.warning("同时触及上下轨,跳过")
time.sleep(self.cfg.POLL_INTERVAL)
continue
action = None
reason = ""
success = False
# ===== 触及上轨 → 开空 / 翻转为空 / 加仓空 =====
if touched_upper:
if not self.can_trade():
time.sleep(self.cfg.POLL_INTERVAL)
continue
reason = (f"价格最高{cur_high:.2f}触及上轨{bb_upper:.2f}"
f"BB({self.cfg.BB_PERIOD},{self.cfg.BB_STD})")
if self.position == 1:
action = "翻转: 平多→开空"
# 在当前页面点市价平仓
self.browser_close_position()
time.sleep(1)
# 等待确认平仓
for _ in range(10):
if self.get_position_status() and self.position == 0:
break
time.sleep(1)
if self.position != 0:
logger.warning(f"平仓后仍有持仓({self.position}),放弃开空")
time.sleep(self.cfg.POLL_INTERVAL)
continue
# 翻转时重置加仓计数
self.pyramid_count = 0
# 平仓后在同一页面直接点卖出/做空
logger.info("平仓完成,直接开空")
self.click_safe('x://span[normalize-space(text()) ="卖出/做空"]')
time.sleep(3)
if self.verify_position(-1):
success = True
elif self.position == 0:
action = "开空"
self.pyramid_count = 0
self.click_safe('x://span[normalize-space(text()) ="卖出/做空"]')
time.sleep(3)
if self.verify_position(-1):
success = True
elif self.position == -1 and self.pyramid_count < self.cfg.PYRAMID_MAX:
# 已持空仓 + 再次触上轨 → 加仓做空
action = f"加仓空#{self.pyramid_count+1}"
reason += f" (加仓#{self.pyramid_count+1}/{self.cfg.PYRAMID_MAX})"
# 重新计算加仓金额并输入
add_usdt = self.calc_order_usdt(is_add=True)
if add_usdt > 0:
self.page.ele('x://*[@id="size_0"]').input(vals=add_usdt, clear=True)
time.sleep(0.5)
self.click_safe('x://span[normalize-space(text()) ="卖出/做空"]')
time.sleep(3)
if self.verify_position(-1):
self.pyramid_count += 1
success = True
else:
logger.info(f"已持空仓,加仓已达上限({self.pyramid_count}/{self.cfg.PYRAMID_MAX})")
# ===== 触及下轨 → 开多 / 翻转为多 / 加仓多 =====
elif touched_lower:
if not self.can_trade():
time.sleep(self.cfg.POLL_INTERVAL)
continue
reason = (f"价格最低{cur_low:.2f}触及下轨{bb_lower:.2f}"
f"BB({self.cfg.BB_PERIOD},{self.cfg.BB_STD})")
if self.position == -1:
action = "翻转: 平空→开多"
self.browser_close_position()
time.sleep(1)
for _ in range(10):
if self.get_position_status() and self.position == 0:
break
time.sleep(1)
if self.position != 0:
logger.warning(f"平仓后仍有持仓({self.position}),放弃开多")
time.sleep(self.cfg.POLL_INTERVAL)
continue
# 翻转时重置加仓计数
self.pyramid_count = 0
logger.info("平仓完成,直接开多")
self.click_safe('x://span[normalize-space(text()) ="买入/做多"]')
time.sleep(3)
if self.verify_position(1):
success = True
elif self.position == 0:
action = "开多"
self.pyramid_count = 0
self.click_safe('x://span[normalize-space(text()) ="买入/做多"]')
time.sleep(3)
if self.verify_position(1):
success = True
elif self.position == 1 and self.pyramid_count < self.cfg.PYRAMID_MAX:
# 已持多仓 + 再次触下轨 → 加仓做多
action = f"加仓多#{self.pyramid_count+1}"
reason += f" (加仓#{self.pyramid_count+1}/{self.cfg.PYRAMID_MAX})"
# 重新计算加仓金额并输入
add_usdt = self.calc_order_usdt(is_add=True)
if add_usdt > 0:
self.page.ele('x://*[@id="size_0"]').input(vals=add_usdt, clear=True)
time.sleep(0.5)
self.click_safe('x://span[normalize-space(text()) ="买入/做多"]')
time.sleep(3)
if self.verify_position(1):
self.pyramid_count += 1
success = True
else:
logger.info(f"已持多仓,加仓已达上限({self.pyramid_count}/{self.cfg.PYRAMID_MAX})")
# ===== 交易成功后处理 =====
if success and action:
last_kline_id = kline_id
self.last_trade_time = time.time()
self.write_trade_log(action, current_price,
bb_upper, bb_mid, bb_lower, reason)
logger.success(f"{action} 执行成功")
# 交易完成后关闭浏览器,下轮重新打开
page_start = True
try:
self.page.close()
except Exception:
pass
self.page = None
time.sleep(5)
time.sleep(self.cfg.POLL_INTERVAL)
except KeyboardInterrupt:
logger.info("用户中断,程序退出")
break
except Exception as e:
logger.error(f"主循环异常: {e}")
page_start = True
time.sleep(10)
# ---------------------------------------------------------------------------
# 入口
# ---------------------------------------------------------------------------
if __name__ == "__main__":
trader = BBDelayReversalTrader()
trader.run()

189789
bb_sweep_results.csv Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1,368 +0,0 @@
============================================================
时间: 2026-03-01 03:34:26
操作: 加仓空#1
价格: 1945.80
BB上轨: 1942.91 | 中轨: 1928.45 | 下轨: 1913.99
原因: 价格最高1945.80触及上轨1942.91BB(10,2.5) (加仓#1/3)
============================================================
============================================================
时间: 2026-03-01 03:35:28
操作: 加仓空#2
价格: 1946.06
BB上轨: 1947.60 | 中轨: 1930.12 | 下轨: 1912.64
原因: 价格最高1948.81触及上轨1947.60BB(10,2.5) (加仓#2/3)
============================================================
============================================================
时间: 2026-03-01 04:20:55
操作: 加仓空#3
价格: 1956.54
BB上轨: 1958.72 | 中轨: 1947.07 | 下轨: 1935.42
原因: 价格最高1963.78触及上轨1958.72BB(10,2.5) (加仓#3/3)
============================================================
============================================================
时间: 2026-03-01 06:37:04
操作: 开空
价格: 1981.98
BB上轨: 1971.65 | 中轨: 1961.60 | 下轨: 1951.55
原因: 价格最高1982.00触及上轨1971.65BB(10,2.5)
============================================================
============================================================
时间: 2026-03-01 08:04:15
操作: 加仓空#1
价格: 1968.36
BB上轨: 1967.65 | 中轨: 1961.56 | 下轨: 1955.46
原因: 价格最高1968.37触及上轨1967.65BB(10,2.5) (加仓#1/3)
============================================================
============================================================
时间: 2026-03-01 09:11:15
操作: 翻转: 平空→开多
价格: 1948.38
BB上轨: 1965.27 | 中轨: 1957.74 | 下轨: 1950.20
原因: 价格最低1948.07触及下轨1950.20BB(10,2.5)
============================================================
============================================================
时间: 2026-03-01 09:15:44
操作: 加仓多#1
价格: 1946.42
BB上轨: 1966.34 | 中轨: 1956.43 | 下轨: 1946.53
原因: 价格最低1946.20触及下轨1946.53BB(10,2.5) (加仓#1/3)
============================================================
============================================================
时间: 2026-03-01 09:37:21
操作: 翻转: 平多→开空
价格: 1966.51
BB上轨: 1967.60 | 中轨: 1955.23 | 下轨: 1942.85
原因: 价格最高1967.64触及上轨1967.60BB(10,2.5)
============================================================
============================================================
时间: 2026-03-01 09:47:41
操作: 加仓空#1
价格: 1974.91
BB上轨: 1973.88 | 中轨: 1956.84 | 下轨: 1939.80
原因: 价格最高1974.91触及上轨1973.88BB(10,2.5) (加仓#1/3)
============================================================
============================================================
时间: 2026-03-01 09:50:10
操作: 加仓空#2
价格: 1990.01
BB上轨: 1988.73 | 中轨: 1959.52 | 下轨: 1930.30
原因: 价格最高1991.56触及上轨1988.73BB(10,2.5) (加仓#2/3)
============================================================
============================================================
时间: 2026-03-01 09:59:41
操作: 开空
价格: 2009.09
BB上轨: 2008.95 | 中轨: 1964.55 | 下轨: 1920.15
原因: 价格最高2009.20触及上轨2008.95BB(10,2.5)
============================================================
============================================================
时间: 2026-03-01 10:02:15
操作: 加仓空#1
价格: 2023.05
BB上轨: 2025.72 | 中轨: 1970.39 | 下轨: 1915.07
原因: 价格最高2026.00触及上轨2025.72BB(10,2.5) (加仓#1/3)
============================================================
============================================================
时间: 2026-03-01 11:22:14
操作: 开多
价格: 2028.59
BB上轨: 2042.05 | 中轨: 2035.46 | 下轨: 2028.87
原因: 价格最低2028.58触及下轨2028.87BB(10,2.5)
============================================================
============================================================
时间: 2026-03-01 11:54:08
操作: 加仓多#1
价格: 2023.39
BB上轨: 2039.59 | 中轨: 2032.27 | 下轨: 2024.96
原因: 价格最低2021.86触及下轨2024.96BB(10,2.5) (加仓#1/3)
============================================================
============================================================
时间: 2026-03-01 11:55:14
操作: 加仓多#2
价格: 2020.39
BB上轨: 2042.34 | 中轨: 2030.59 | 下轨: 2018.84
原因: 价格最低2018.50触及下轨2018.84BB(10,2.5) (加仓#2/3)
============================================================
============================================================
时间: 2026-03-01 12:15:00
操作: 加仓多#3
价格: 2012.23
BB上轨: 2039.99 | 中轨: 2026.95 | 下轨: 2013.91
原因: 价格最低2008.07触及下轨2013.91BB(10,2.5) (加仓#3/3)
============================================================
============================================================
时间: 2026-03-01 13:20:34
操作: 翻转: 平多→开空
价格: 2017.26
BB上轨: 2017.21 | 中轨: 2014.22 | 下轨: 2011.24
原因: 价格最高2017.47触及上轨2017.21BB(10,2.5)
============================================================
============================================================
时间: 2026-03-01 13:25:07
操作: 加仓空#1
价格: 2021.89
BB上轨: 2021.00 | 中轨: 2015.09 | 下轨: 2009.17
原因: 价格最高2021.58触及上轨2021.00BB(10,2.5) (加仓#1/3)
============================================================
============================================================
时间: 2026-03-01 13:55:06
操作: 翻转: 平空→开多
价格: 2009.30
BB上轨: 2023.23 | 中轨: 2016.18 | 下轨: 2009.13
原因: 价格最低2008.73触及下轨2009.13BB(10,2.5)
============================================================
============================================================
时间: 2026-03-01 14:04:49
操作: 加仓多#1
价格: 2005.00
BB上轨: 2024.71 | 中轨: 2015.13 | 下轨: 2005.56
原因: 价格最低2005.00触及下轨2005.56BB(10,2.5) (加仓#1/3)
============================================================
============================================================
时间: 2026-03-01 14:05:27
操作: 加仓多#2
价格: 2001.68
BB上轨: 2026.45 | 中轨: 2014.43 | 下轨: 2002.41
原因: 价格最低2001.68触及下轨2002.41BB(10,2.5) (加仓#2/3)
============================================================
============================================================
时间: 2026-03-01 14:10:46
操作: 加仓多#3
价格: 1993.93
BB上轨: 2029.77 | 中轨: 2012.76 | 下轨: 1995.76
原因: 价格最低1993.62触及下轨1995.76BB(10,2.5) (加仓#3/3)
============================================================
============================================================
时间: 2026-03-01 15:12:06
操作: 翻转: 平多→开空
价格: 2003.74
BB上轨: 2003.48 | 中轨: 1996.50 | 下轨: 1989.52
原因: 价格最高2003.74触及上轨2003.48BB(10,2.5)
============================================================
============================================================
时间: 2026-03-01 15:22:02
操作: 加仓空#1
价格: 2004.51
BB上轨: 2004.32 | 中轨: 1996.69 | 下轨: 1989.06
原因: 价格最高2004.51触及上轨2004.32BB(10,2.5) (加仓#1/3)
============================================================
============================================================
时间: 2026-03-01 15:39:37
操作: 加仓空#2
价格: 2006.49
BB上轨: 2005.89 | 中轨: 1998.49 | 下轨: 1991.09
原因: 价格最高2007.16触及上轨2005.89BB(10,2.5) (加仓#2/3)
============================================================
============================================================
时间: 2026-03-01 15:40:18
操作: 加仓空#3
价格: 2005.95
BB上轨: 2008.50 | 中轨: 1999.85 | 下轨: 1991.20
原因: 价格最高2008.89触及上轨2008.50BB(10,2.5) (加仓#3/3)
============================================================
============================================================
时间: 2026-03-01 16:47:20
操作: 翻转: 平空→开多
价格: 1997.54
BB上轨: 2011.42 | 中轨: 2004.66 | 下轨: 1997.89
原因: 价格最低1997.54触及下轨1997.89BB(10,2.5)
============================================================
============================================================
时间: 2026-03-01 16:50:39
操作: 加仓多#1
价格: 1994.11
BB上轨: 2012.88 | 中轨: 2003.62 | 下轨: 1994.36
原因: 价格最低1994.00触及下轨1994.36BB(10,2.5) (加仓#1/3)
============================================================
============================================================
时间: 2026-03-01 16:55:10
操作: 加仓多#2
价格: 1984.53
BB上轨: 2019.59 | 中轨: 2002.06 | 下轨: 1984.53
原因: 价格最低1984.04触及下轨1984.53BB(10,2.5) (加仓#2/3)
============================================================
============================================================
时间: 2026-03-01 17:02:18
操作: 加仓多#3
价格: 1977.78
BB上轨: 2021.30 | 中轨: 2000.09 | 下轨: 1978.87
原因: 价格最低1977.34触及下轨1978.87BB(10,2.5) (加仓#3/3)
============================================================
============================================================
时间: 2026-03-01 18:26:31
操作: 翻转: 平多→开空
价格: 1981.80
BB上轨: 1982.79 | 中轨: 1979.44 | 下轨: 1976.08
原因: 价格最高1982.80触及上轨1982.79BB(10,2.5)
============================================================
============================================================
时间: 2026-03-01 18:30:23
操作: 加仓空#1
价格: 1985.00
BB上轨: 1984.64 | 中轨: 1979.89 | 下轨: 1975.13
原因: 价格最高1985.00触及上轨1984.64BB(10,2.5) (加仓#1/3)
============================================================
============================================================
时间: 2026-03-01 18:35:09
操作: 加仓空#2
价格: 1986.68
BB上轨: 1986.58 | 中轨: 1980.49 | 下轨: 1974.40
原因: 价格最高1986.68触及上轨1986.58BB(10,2.5) (加仓#2/3)
============================================================
============================================================
时间: 2026-03-01 19:16:32
操作: 加仓空#3
价格: 1987.27
BB上轨: 1987.94 | 中轨: 1983.79 | 下轨: 1979.63
原因: 价格最高1987.96触及上轨1987.94BB(10,2.5) (加仓#3/3)
============================================================
============================================================
时间: 2026-03-01 20:28:43
操作: 翻转: 平空→开多
价格: 1979.84
BB上轨: 1987.42 | 中轨: 1983.66 | 下轨: 1979.90
原因: 价格最低1979.64触及下轨1979.90BB(10,2.5)
============================================================
============================================================
时间: 2026-03-01 20:34:05
操作: 加仓多#1
价格: 1978.24
BB上轨: 1987.88 | 中轨: 1983.22 | 下轨: 1978.55
原因: 价格最低1978.24触及下轨1978.55BB(10,2.5) (加仓#1/3)
============================================================
============================================================
时间: 2026-03-01 21:20:40
操作: 加仓多#2
价格: 1973.00
BB上轨: 1982.99 | 中轨: 1978.04 | 下轨: 1973.10
原因: 价格最低1973.00触及下轨1973.10BB(10,2.5) (加仓#2/3)
============================================================
============================================================
时间: 2026-03-01 21:51:21
操作: 翻转: 平多→开空
价格: 1981.17
BB上轨: 1980.40 | 中轨: 1976.08 | 下轨: 1971.77
原因: 价格最高1981.82触及上轨1980.40BB(10,2.5)
============================================================
============================================================
时间: 2026-03-01 21:55:09
操作: 开空
价格: 2019.50
BB上轨: 2011.12 | 中轨: 1980.16 | 下轨: 1949.21
原因: 价格最高2019.50触及上轨2011.12BB(10,2.5)
============================================================
============================================================
时间: 2026-03-01 22:42:31
操作: 翻转: 平空→开多
价格: 1998.46
BB上轨: 2021.45 | 中轨: 2010.61 | 下轨: 1999.77
原因: 价格最低1997.98触及下轨1999.77BB(10,2.5)
============================================================
============================================================
时间: 2026-03-01 22:45:18
操作: 加仓多#1
价格: 1995.73
BB上轨: 2021.76 | 中轨: 2008.71 | 下轨: 1995.66
原因: 价格最低1995.55触及下轨1995.66BB(10,2.5) (加仓#1/3)
============================================================
============================================================
时间: 2026-03-01 23:17:24
操作: 翻转: 平多→开空
价格: 2011.33
BB上轨: 2011.41 | 中轨: 2003.47 | 下轨: 1995.52
原因: 价格最高2011.70触及上轨2011.41BB(10,2.5)
============================================================
============================================================
时间: 2026-03-01 23:20:33
操作: 加仓空#1
价格: 2013.00
BB上轨: 2012.52 | 中轨: 2003.72 | 下轨: 1994.92
原因: 价格最高2013.00触及上轨2012.52BB(10,2.5) (加仓#1/3)
============================================================
============================================================
时间: 2026-03-01 23:28:46
操作: 加仓空#2
价格: 2012.26
BB上轨: 2012.18 | 中轨: 2003.62 | 下轨: 1995.05
原因: 价格最高2012.30触及上轨2012.18BB(10,2.5) (加仓#2/3)
============================================================
============================================================
时间: 2026-03-01 23:34:42
操作: 加仓空#3
价格: 2012.90
BB上轨: 2013.39 | 中轨: 2003.97 | 下轨: 1994.56
原因: 价格最高2013.42触及上轨2013.39BB(10,2.5) (加仓#3/3)
============================================================
============================================================
时间: 2026-03-01 23:57:04
操作: 翻转: 平空→开多
价格: 2000.60
BB上轨: 2017.24 | 中轨: 2008.93 | 下轨: 2000.62
原因: 价格最低2000.38触及下轨2000.62BB(10,2.5)
============================================================

View File

@@ -1,160 +0,0 @@
============================================================
时间: 2026-03-02 00:00:12
操作: 加仓多#1
价格: 1993.03
BB上轨: 2022.56 | 中轨: 2007.82 | 下轨: 1993.08
原因: 价格最低1992.31触及下轨1993.08BB(10,2.5) (加仓#1/3)
============================================================
============================================================
时间: 2026-03-02 00:47:35
操作: 加仓多#2
价格: 1991.52
BB上轨: 2009.23 | 中轨: 2000.42 | 下轨: 1991.62
原因: 价格最低1991.22触及下轨1991.62BB(10,2.5) (加仓#2/3)
============================================================
============================================================
时间: 2026-03-02 00:50:07
操作: 加仓多#3
价格: 1972.92
BB上轨: 2020.78 | 中轨: 1998.46 | 下轨: 1976.14
原因: 价格最低1972.33触及下轨1976.14BB(10,2.5) (加仓#3/3)
============================================================
============================================================
时间: 2026-03-02 02:54:45
操作: 翻转: 平空→开多
价格: 1975.78
BB上轨: 1987.69 | 中轨: 1981.61 | 下轨: 1975.53
原因: 价格最低1974.35触及下轨1975.53BB(10,2.5)
============================================================
============================================================
时间: 2026-03-02 02:55:32
操作: 加仓多#1
价格: 1972.67
BB上轨: 1988.35 | 中轨: 1980.89 | 下轨: 1973.42
原因: 价格最低1972.66触及下轨1973.42BB(10,2.5) (加仓#1/3)
============================================================
============================================================
时间: 2026-03-02 03:01:38
操作: 加仓多#2
价格: 1973.37
BB上轨: 1985.83 | 中轨: 1979.58 | 下轨: 1973.32
原因: 价格最低1972.87触及下轨1973.32BB(10,2.5) (加仓#2/3)
============================================================
============================================================
时间: 2026-03-02 03:06:27
操作: 加仓多#3
价格: 1969.61
BB上轨: 1986.78 | 中轨: 1978.35 | 下轨: 1969.93
原因: 价格最低1968.00触及下轨1969.93BB(10,2.5) (加仓#3/3)
============================================================
============================================================
时间: 2026-03-02 04:10:24
操作: 开多
价格: 1938.14
BB上轨: 1968.16 | 中轨: 1954.01 | 下轨: 1939.86
原因: 价格最低1932.00触及下轨1939.86BB(10,2.5)
============================================================
============================================================
时间: 2026-03-02 04:15:20
操作: 加仓多#1
价格: 1931.04
BB上轨: 1971.27 | 中轨: 1951.22 | 下轨: 1931.16
原因: 价格最低1931.04触及下轨1931.16BB(10,2.5) (加仓#1/3)
============================================================
============================================================
时间: 2026-03-02 05:21:09
操作: 翻转: 平多→开空
价格: 1925.01
BB上轨: 1925.19 | 中轨: 1914.61 | 下轨: 1904.03
原因: 价格最高1926.00触及上轨1925.19BB(10,2.5)
============================================================
============================================================
时间: 2026-03-02 06:03:53
操作: 加仓空#1
价格: 1930.73
BB上轨: 1930.55 | 中轨: 1925.10 | 下轨: 1919.65
原因: 价格最高1930.73触及上轨1930.55BB(10,2.5) (加仓#1/3)
============================================================
============================================================
时间: 2026-03-02 06:13:08
操作: 加仓空#2
价格: 1933.10
BB上轨: 1930.98 | 中轨: 1926.37 | 下轨: 1921.75
原因: 价格最高1933.10触及上轨1930.98BB(10,2.5) (加仓#2/3)
============================================================
============================================================
时间: 2026-03-02 06:15:10
操作: 开空
价格: 1944.64
BB上轨: 1942.77 | 中轨: 1928.24 | 下轨: 1913.72
原因: 价格最高1945.94触及上轨1942.77BB(10,2.5)
============================================================
============================================================
时间: 2026-03-02 08:07:31
操作: 加仓空#1
价格: 1943.79
BB上轨: 1945.86 | 中轨: 1937.82 | 下轨: 1929.77
原因: 价格最高1945.97触及上轨1945.86BB(10,2.5) (加仓#1/3)
============================================================
============================================================
时间: 2026-03-02 08:10:09
操作: 加仓空#2
价格: 1950.40
BB上轨: 1948.66 | 中轨: 1938.79 | 下轨: 1928.92
原因: 价格最高1950.40触及上轨1948.66BB(10,2.5) (加仓#2/3)
============================================================
============================================================
时间: 2026-03-02 08:32:48
操作: 加仓空#3
价格: 1950.23
BB上轨: 1950.72 | 中轨: 1942.27 | 下轨: 1933.83
原因: 价格最高1950.89触及上轨1950.72BB(10,2.5) (加仓#3/3)
============================================================
============================================================
时间: 2026-03-02 09:10:45
操作: 开空
价格: 1980.63
BB上轨: 1979.30 | 中轨: 1956.47 | 下轨: 1933.64
原因: 价格最高1982.52触及上轨1979.30BB(10,2.5)
============================================================
============================================================
时间: 2026-03-02 10:06:29
操作: 翻转: 平空→开多
价格: 1956.56
BB上轨: 1977.03 | 中轨: 1966.90 | 下轨: 1956.77
原因: 价格最低1956.32触及下轨1956.77BB(10,2.5)
============================================================
============================================================
时间: 2026-03-02 10:10:10
操作: 加仓多#1
价格: 1948.99
BB上轨: 1978.42 | 中轨: 1964.20 | 下轨: 1949.99
原因: 价格最低1948.98触及下轨1949.99BB(10,2.5) (加仓#1/3)
============================================================
============================================================
时间: 2026-03-02 10:19:45
操作: 加仓多#2
价格: 1941.70
BB上轨: 1981.96 | 中轨: 1961.75 | 下轨: 1941.53
原因: 价格最低1941.34触及下轨1941.53BB(10,2.5) (加仓#2/3)
============================================================

268
bitmart/框架.py Normal file
View File

@@ -0,0 +1,268 @@
import time
from tqdm import tqdm
from loguru import logger
from bit_tools import openBrowser
from DrissionPage import ChromiumPage
from DrissionPage import ChromiumOptions
from bitmart.api_contract import APIContract
class BitmartFuturesTransaction:
def __init__(self, bit_id):
self.page: ChromiumPage | None = None
self.api_key = "a0fb7b98464fd9bcce67e7c519d58ec10d0c38a8"
self.secret_key = "4eaeba78e77aeaab1c2027f846a276d164f264a44c2c1bb1c5f3be50c8de1ca5"
self.memo = "合约交易"
self.contract_symbol = "ETHUSDT"
self.contractAPI = APIContract(self.api_key, self.secret_key, self.memo, timeout=(5, 15))
self.start = 0 # 持仓状态: -1 空, 0 无, 1 多
self.direction = None
self.pbar = tqdm(total=30, desc="等待K线", ncols=80)
self.last_kline_time = None
self.leverage = "100" # 高杠杆(全仓模式下可开更大仓位)
self.open_type = "cross" # 全仓模式(你的“成本开仓”需求)
self.risk_percent = 0.01 # 每次开仓使用可用余额的 1%
self.open_avg_price = None # 开仓价格
self.current_amount = None # 持仓量
self.bit_id = bit_id
def get_klines(self):
"""获取最近3根30分钟K线step=30"""
try:
end_time = int(time.time())
# 获取足够多的条目确保有最新3根
response = self.contractAPI.get_kline(
contract_symbol=self.contract_symbol,
step=30, # 30分钟
start_time=end_time - 3600 * 10, # 取最近10小时
end_time=end_time
)[0]["data"]
# 每根: [timestamp, open, high, low, close, volume]
formatted = []
for k in response:
formatted.append({
'id': int(k["timestamp"]),
'open': float(k["open_price"]),
'high': float(k["high_price"]),
'low': float(k["low_price"]),
'close': float(k["close_price"])
})
formatted.sort(key=lambda x: x['id'])
return formatted # 最近3根: kline_1 (最老), kline_2, kline_3 (最新)
except Exception as e:
logger.error(f"获取K线异常: {e}")
self.ding(error=True, msg="获取K线异常")
return None
def get_current_price(self):
"""获取当前最新价格,用于计算张数"""
try:
end_time = int(time.time())
response = self.contractAPI.get_kline(
contract_symbol=self.contract_symbol,
step=1, # 1分钟
start_time=end_time - 3600 * 3, # 取最近10小时
end_time=end_time
)[0]
if response['code'] == 1000:
return float(response['data'][-1]["close_price"])
return None
except Exception as e:
logger.error(f"获取价格异常: {e}")
return None
def get_available_balance(self):
"""获取合约账户可用USDT余额"""
try:
response = self.contractAPI.get_assets_detail()[0]
if response['code'] == 1000:
data = response['data']
if isinstance(data, dict):
return float(data.get('available_balance', 0))
elif isinstance(data, list):
for asset in data:
if asset.get('currency') == 'USDT':
return float(asset.get('available_balance', 0))
return None
except Exception as e:
logger.error(f"余额查询异常: {e}")
return None
# 获取当前持仓方向
def get_position_status(self):
"""获取当前持仓方向"""
try:
response = self.contractAPI.get_position(contract_symbol=self.contract_symbol)[0]
if response['code'] == 1000:
positions = response['data']
if not positions:
self.start = 0
return True
self.start = 1 if positions[0]['position_type'] == 1 else -1
self.open_avg_price = positions[0]['open_avg_price']
self.current_amount = positions[0]['current_amount']
self.position_cross = positions[0]["position_cross"]
return True
else:
return False
except Exception as e:
logger.error(f"持仓查询异常: {e}")
return False
# 设置杠杆和全仓
def set_leverage(self):
"""程序启动时设置全仓 + 高杠杆"""
try:
response = self.contractAPI.post_submit_leverage(
contract_symbol=self.contract_symbol,
leverage=self.leverage,
open_type=self.open_type
)[0]
if response['code'] == 1000:
logger.success(f"全仓模式 + {self.leverage}x 杠杆设置成功")
return True
else:
logger.error(f"杠杆设置失败: {response}")
return False
except Exception as e:
logger.error(f"设置杠杆异常: {e}")
return False
def openBrowser(self):
"""打开 TGE 对应浏览器实例"""
try:
bit_port = openBrowser(id=self.bit_id)
co = ChromiumOptions()
co.set_local_port(port=bit_port)
self.page = ChromiumPage(addr_or_opts=co)
return True
except:
return False
def take_over_browser(self):
"""接管浏览器"""
try:
co = ChromiumOptions()
co.set_local_port(self.tge_port)
self.page = ChromiumPage(addr_or_opts=co)
self.page.set.window.max()
return True
except:
return False
def close_extra_tabs(self):
"""关闭多余 tab"""
try:
for idx, tab in enumerate(self.page.get_tabs()):
if idx > 0:
tab.close()
return True
except:
return False
def click_safe(self, xpath, sleep=0.5):
"""安全点击"""
try:
ele = self.page.ele(xpath)
if not ele:
return False
ele.scroll.to_see(center=True)
time.sleep(sleep)
ele.click()
return True
except:
return False
def 全平仓(self):
self.click_safe('x://span[normalize-space(text()) ="市价"]')
def 平一半多仓(self):
self.click_safe('x://button[normalize-space(text()) ="平仓"]')
self.click_safe('x://*[@id="futureTradeForm"]/div[5]/div[3]/div[3]/span[3]')
self.click_safe('x://span[normalize-space(text()) ="卖出/平多"]')
def 平一半空仓(self):
self.click_safe('x://button[normalize-space(text()) ="平仓"]')
self.click_safe('x://*[@id="futureTradeForm"]/div[5]/div[3]/div[3]/span[3]')
self.click_safe('x://span[normalize-space(text()) ="买入/平空"]')
def 开单(self, marketPriceLongOrder=0, limitPriceShortOrder=0, size=None, price=None):
"""
marketPriceLongOrder 市价最多或者做空1是做多-1是做空
limitPriceShortOrder 限价最多或者做空
"""
if marketPriceLongOrder == -1:
self.click_safe('x://button[normalize-space(text()) ="市价"]')
self.page.ele('x://*[@id="size_0"]').input(size)
self.click_safe('x://span[normalize-space(text()) ="卖出/做空"]')
elif marketPriceLongOrder == 1:
self.click_safe('x://button[normalize-space(text()) ="市价"]')
self.page.ele('x://*[@id="size_0"]').input(size)
self.click_safe('x://span[normalize-space(text()) ="买入/做多"]')
if limitPriceShortOrder == -1:
self.click_safe('x://button[normalize-space(text()) ="限价"]')
self.page.ele('x://*[@id="price_0"]').input(vals=price, clear=True)
time.sleep(1)
self.page.ele('x://*[@id="size_0"]').input(1)
self.click_safe('x://span[normalize-space(text()) ="卖出/做空"]')
elif limitPriceShortOrder == 1:
self.click_safe('x://button[normalize-space(text()) ="限价"]')
self.page.ele('x://*[@id="price_0"]').input(vals=price, clear=True)
time.sleep(1)
self.page.ele('x://*[@id="size_0"]').input(1)
self.click_safe('x://span[normalize-space(text()) ="买入/做多"]')
def ding(self, text, error=False):
logger.info(text)
def action(self):
# 启动时设置全仓高杠杆
if not self.set_leverage():
logger.error("杠杆设置失败,程序继续运行但可能下单失败")
return
# 1. 打开浏览器
if not self.openBrowser():
self.ding("打开 TGE 失败!", error=True)
return
logger.info("TGE 端口获取成功")
self.get_klines()
# self.close_extra_tabs()
# self.page.get("https://derivatives.bitmart.com/zh-CN/futures/ETHUSDT")
#
self.click_safe('x://button[normalize-space(text()) ="市价"]')
# self.click_safe('x://button[normalize-space(text()) ="限价"]')
#
# self.page.ele('x://*[@id="price_0"]').input(vals=3000, clear=True)
# self.page.ele('x://*[@id="size_0"]').input(1)
# self.click_safe('x://span[normalize-space(text()) ="买入/做多"]')
# self.click_safe('x://span[normalize-space(text()) ="卖出/做空"]')
self.click_safe('x://button[normalize-space(text()) ="开仓"]')
self.click_safe('x://button[normalize-space(text()) ="平仓"]')
self.click_safe('x://span[normalize-space(text()) ="买入/平空"]')
self.click_safe('x://span[normalize-space(text()) ="卖出/平多"]')
self.click_safe('x://*[@id="futureTradeForm"]/div[5]/div[3]/div[3]/span[3]')
if __name__ == '__main__':
BitmartFuturesTransaction(bit_id="f2320f57e24c45529a009e1541e25961").action()

View File

@@ -1,113 +0,0 @@
"""
用 CLIP 做图块相似度匹配,在大图中找模板图标位置
原理:把大图切成滑动窗口小块,用 CLIP 计算每块和模板的视觉相似度,取最高分的块
"""
import cv2
import numpy as np
from PIL import Image
import torch
from transformers import CLIPProcessor, CLIPModel
from pathlib import Path
BASE = Path(__file__).parent / "images"
MODEL_NAME = "openai/clip-vit-base-patch32" # 约600MB小模型
def load_clip():
print("加载 CLIP 模型约600MB首次自动下载...")
model = CLIPModel.from_pretrained(MODEL_NAME).to("cuda")
processor = CLIPProcessor.from_pretrained(MODEL_NAME)
print("CLIP 加载完成")
return model, processor
def find_by_clip(model, processor, main_img: np.ndarray, template_img: np.ndarray,
step=10, win_sizes=None):
"""
滑动窗口 + CLIP 相似度,找模板在大图中的最佳位置
"""
if win_sizes is None:
th, tw = template_img.shape[:2]
# 尝试原始尺寸及上下浮动
win_sizes = [(int(tw * s), int(th * s)) for s in [0.8, 0.9, 1.0, 1.1, 1.2]]
# 预处理模板
tmpl_pil = Image.fromarray(cv2.cvtColor(template_img, cv2.COLOR_BGR2RGB))
tmpl_inputs = processor(images=tmpl_pil, return_tensors="pt").to("cuda")
with torch.no_grad():
tmpl_out = model.vision_model(**tmpl_inputs)
tmpl_feat = model.visual_projection(tmpl_out.pooler_output).float()
tmpl_feat = tmpl_feat / tmpl_feat.norm(dim=-1, keepdim=True)
mh, mw = main_img.shape[:2]
best_score = -1
best_box = None
for (ww, wh) in win_sizes:
if ww > mw or wh > mh:
continue
for y in range(0, mh - wh + 1, step):
for x in range(0, mw - ww + 1, step):
crop = main_img[y:y+wh, x:x+ww]
crop_pil = Image.fromarray(cv2.cvtColor(crop, cv2.COLOR_BGR2RGB))
crop_inputs = processor(images=crop_pil, return_tensors="pt").to("cuda")
with torch.no_grad():
crop_out = model.vision_model(**crop_inputs)
crop_feat = model.visual_projection(crop_out.pooler_output).float()
crop_feat = crop_feat / crop_feat.norm(dim=-1, keepdim=True)
score = (tmpl_feat * crop_feat).sum().item()
if score > best_score:
best_score = score
best_box = (x, y, ww, wh)
return best_box, best_score
def main():
main_img = cv2.imread(str(BASE / "1.jpg"))
templates = {
"2.png": cv2.imread(str(BASE / "2.png")),
"3.png": cv2.imread(str(BASE / "3.png")),
"4.png": cv2.imread(str(BASE / "4.png")),
}
# 模板是 BGRA转 BGR
for name in templates:
img = cv2.imread(str(BASE / name), cv2.IMREAD_UNCHANGED)
if img.shape[2] == 4:
# alpha 通道合成白底
alpha = img[:, :, 3:4] / 255.0
rgb = img[:, :, :3].astype(float)
white = np.ones_like(rgb) * 255
merged = (rgb * alpha + white * (1 - alpha)).astype(np.uint8)
templates[name] = merged
else:
templates[name] = img
model, processor = load_clip()
vis = main_img.copy()
colors = {"2.png": (0, 0, 255), "3.png": (0, 255, 0), "4.png": (255, 0, 0)}
print()
for name, tmpl in templates.items():
print(f"正在匹配 {name} ...")
box, score = find_by_clip(model, processor, main_img, tmpl, step=8)
if box:
x, y, w, h = box
cx, cy = x + w // 2, y + h // 2
color = colors[name]
cv2.rectangle(vis, (x, y), (x+w, y+h), color, 2)
cv2.circle(vis, (cx, cy), 5, color, -1)
cv2.putText(vis, name, (x, y-5), cv2.FONT_HERSHEY_SIMPLEX, 0.5, color, 1)
print(f" {name}: 中心点 ({cx}, {cy}) 相似度={score:.4f}")
else:
print(f" {name}: 未找到")
out = BASE / "result_clip.jpg"
cv2.imwrite(str(out), vis)
print(f"\n结果保存到: {out}")
if __name__ == "__main__":
main()

View File

@@ -1,131 +0,0 @@
"""
用 Qwen2-VL 本地模型识别验证码图片位置
首次运行会自动下载模型(约 4GB
"""
import base64
from pathlib import Path
from transformers import Qwen2VLForConditionalGeneration, AutoProcessor
from qwen_vl_utils import process_vision_info
import torch
MODEL_NAME = "Qwen/Qwen2-VL-7B-Instruct"
BASE = Path(__file__).parent / "images"
def img_to_base64(path):
with open(path, "rb") as f:
return base64.b64encode(f.read()).decode()
def load_model():
print("加载模型中首次运行会下载约15GB...")
from transformers import BitsAndBytesConfig
bnb_config = BitsAndBytesConfig(
load_in_4bit=True,
bnb_4bit_compute_dtype=torch.float16,
bnb_4bit_use_double_quant=True,
bnb_4bit_quant_type="nf4",
)
model = Qwen2VLForConditionalGeneration.from_pretrained(
MODEL_NAME,
quantization_config=bnb_config,
device_map="cuda",
)
processor = AutoProcessor.from_pretrained(MODEL_NAME)
print("模型加载完成")
return model, processor
def ask_one(model, processor, main_img_path, template_path):
"""让模型找出单个模板图在主图中的位置,返回原始回答"""
messages = [
{
"role": "user",
"content": [
{
"type": "text",
"text": (
"下面是一张背景大图300x200像素"
"以及一个需要在大图中找到的小图标轮廓。\n"
"大图:"
)
},
{"type": "image", "image": str(main_img_path)},
{
"type": "text",
"text": "\n小图标轮廓(这个图标出现在大图中某个物体上):"
},
{"type": "image", "image": str(template_path)},
{
"type": "text",
"text": (
"\n请仔细观察小图标的形状,在大图中找到形状最相似的物体,"
"给出该物体中心点的像素坐标。"
"坐标原点在左上角x向右y向下。"
"只需回答坐标,格式:(x, y)"
)
}
]
}
]
text = processor.apply_chat_template(messages, tokenize=False, add_generation_prompt=True)
image_inputs, video_inputs = process_vision_info(messages)
inputs = processor(
text=[text],
images=image_inputs,
videos=video_inputs,
padding=True,
return_tensors="pt",
).to("cuda")
with torch.no_grad():
generated_ids = model.generate(**inputs, max_new_tokens=50)
generated_ids_trimmed = [
out_ids[len(in_ids):] for in_ids, out_ids in zip(inputs.input_ids, generated_ids)
]
output = processor.batch_decode(
generated_ids_trimmed, skip_special_tokens=True, clean_up_tokenization_spaces=False
)
return output[0].strip()
def main():
main_img = BASE / "1.jpg"
templates = [BASE / "2.png", BASE / "3.png", BASE / "4.png"]
model, processor = load_model()
import re
import cv2
img = cv2.imread(str(main_img))
colors = {"2.png": (0, 0, 255), "3.png": (0, 255, 0), "4.png": (255, 0, 0)}
results = {}
for tmpl_path in templates:
name = tmpl_path.name
print(f"\n正在识别 {name} ...")
answer = ask_one(model, processor, main_img, tmpl_path)
print(f"{name} 模型回答: {answer}")
match = re.search(r"\((\d+)[,\s]+(\d+)\)", answer)
if match:
x, y = int(match.group(1)), int(match.group(2))
results[name] = (x, y)
color = colors[name]
cv2.circle(img, (x, y), 8, color, -1)
cv2.circle(img, (x, y), 12, color, 2)
cv2.putText(img, name, (x + 14, y + 5), cv2.FONT_HERSHEY_SIMPLEX, 0.5, color, 1)
else:
print(f"{name}: 未能解析坐标,原始回答: {answer}")
print("\n=== 点击坐标汇总 ===")
for name, (x, y) in results.items():
print(f"{name}: ({x}, {y})")
out = BASE / "result_vl.jpg"
cv2.imwrite(str(out), img)
print(f"\n可视化结果保存到: {out}")
if __name__ == "__main__":
main()

View File

@@ -1 +0,0 @@
{"installed":{"client_id":"823839778551-mgovppr13aoil1r69upj7uiv84ej0sih.apps.googleusercontent.com","project_id":"gen-lang-client-0535910705","auth_uri":"https://accounts.google.com/o/oauth2/auth","token_uri":"https://oauth2.googleapis.com/token","auth_provider_x509_cert_url":"https://www.googleapis.com/oauth2/v1/certs","client_secret":"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","redirect_uris":["http://localhost"]}}

View File

@@ -1,127 +0,0 @@
"""
验证码图片匹配脚本
在 1.jpg 中找到 2.png、3.png、4.png 的位置
"""
import cv2
import numpy as np
import os
def find_template_in_image(main_img_path, template_path, threshold=0.5):
"""
在主图中查找模板图片的位置(原始尺寸 + 多尺度)
支持 PNG alpha 通道作为模板轮廓
返回置信度最高的匹配结果
"""
main_img = cv2.imread(main_img_path)
template = cv2.imread(template_path, cv2.IMREAD_UNCHANGED)
if main_img is None:
print(f"无法读取主图: {main_img_path}")
return []
if template is None:
print(f"无法读取模板: {template_path}")
return []
h, w = template.shape[:2]
main_gray = cv2.cvtColor(main_img, cv2.COLOR_BGR2GRAY)
# 如果模板有 alpha 通道且 RGB 全黑,用 alpha 通道作为灰度图
if template.shape[2] == 4:
alpha = template[:, :, 3]
rgb_sum = template[:, :, :3].sum()
if rgb_sum == 0:
tmpl_gray = alpha # 用 alpha 通道
else:
tmpl_gray = cv2.cvtColor(template[:, :, :3], cv2.COLOR_BGR2GRAY)
else:
tmpl_gray = cv2.cvtColor(template, cv2.COLOR_BGR2GRAY)
best = None
# 先尝试原始尺寸
scales = [1.0] + list(np.linspace(0.6, 1.4, 17))
for scale in scales:
rw, rh = int(w * scale), int(h * scale)
if rh < 5 or rw < 5:
continue
if rh > main_gray.shape[0] or rw > main_gray.shape[1]:
continue
resized_tmpl = cv2.resize(tmpl_gray, (rw, rh))
res = cv2.matchTemplate(main_gray, resized_tmpl, cv2.TM_CCOEFF_NORMED)
_, max_val, _, max_loc = cv2.minMaxLoc(res)
if best is None or max_val > best["confidence"]:
best = {
"x": max_loc[0],
"y": max_loc[1],
"w": rw,
"h": rh,
"confidence": max_val,
"scale": scale,
"center_x": max_loc[0] + rw // 2,
"center_y": max_loc[1] + rh // 2,
}
if best is None or best["confidence"] < threshold:
return []
return [best]
def main():
base_dir = os.path.join(os.path.dirname(__file__), "images")
main_img_path = os.path.join(base_dir, "1.jpg")
templates = ["2.png", "3.png", "4.png"]
print(f"主图: {main_img_path}\n")
all_results = {}
for tmpl_name in templates:
tmpl_path = os.path.join(base_dir, tmpl_name)
matches = find_template_in_image(main_img_path, tmpl_path, threshold=0.5)
all_results[tmpl_name] = matches
if matches:
m = matches[0]
print(f"[{tmpl_name}] 找到匹配:")
print(f" 位置: ({m['x']}, {m['y']})")
print(f" 中心点: ({m['center_x']}, {m['center_y']})")
print(f" 尺寸: {m['w']}x{m['h']}")
print(f" 置信度: {m['confidence']:.4f}")
print(f" 缩放比例: {m['scale']:.2f}")
else:
print(f"[{tmpl_name}] 未找到匹配(置信度不足)")
print()
# 可视化结果,保存标注图
main_img = cv2.imread(main_img_path)
colors = [(0, 0, 255), (0, 255, 0), (255, 0, 0)] # 红、绿、蓝
for i, tmpl_name in enumerate(templates):
matches = all_results.get(tmpl_name, [])
for m in matches:
x, y, w, h = m["x"], m["y"], m["w"], m["h"]
color = colors[i % len(colors)]
cv2.rectangle(main_img, (x, y), (x + w, y + h), color, 2)
cv2.putText(main_img, tmpl_name, (x, y - 5),
cv2.FONT_HERSHEY_SIMPLEX, 0.6, color, 2)
# 画中心点
cx, cy = m["center_x"], m["center_y"]
cv2.circle(main_img, (cx, cy), 5, color, -1)
output_path = os.path.join(base_dir, "result.jpg")
cv2.imwrite(output_path, main_img)
print(f"标注结果已保存到: {output_path}")
# 打印点击坐标汇总(用于自动化点击)
print("\n=== 点击坐标汇总 ===")
for tmpl_name in templates:
matches = all_results.get(tmpl_name, [])
if matches:
m = matches[0]
print(f"{tmpl_name}: 点击 ({m['center_x']}, {m['center_y']})")
else:
print(f"{tmpl_name}: 未找到")
if __name__ == "__main__":
main()

527
generate_backtest_chart.py Normal file
View File

@@ -0,0 +1,527 @@
"""
生成回测可视化图表
显示K线、布林带、开仓/平仓位置
"""
import pandas as pd
import json
from pathlib import Path
from peewee import *
import time
from loguru import logger
# 数据库配置
DB_PATH = Path(__file__).parent / 'models' / 'database.db'
db = SqliteDatabase(str(DB_PATH))
class BitMartETH5M(Model):
"""5分钟K线模型"""
id = BigIntegerField(primary_key=True)
open = FloatField(null=True)
high = FloatField(null=True)
low = FloatField(null=True)
close = FloatField(null=True)
class Meta:
database = db
table_name = 'bitmart_eth_5m'
def calculate_bollinger_bands(df, period=10, std_dev=2.5):
"""计算布林带右移1根与回测口径一致"""
df['sma'] = df['close'].rolling(window=period).mean()
df['std'] = df['close'].rolling(window=period).std()
df['bb_upper'] = (df['sma'] + std_dev * df['std']).shift(1)
df['bb_mid'] = df['sma'].shift(1)
df['bb_lower'] = (df['sma'] - std_dev * df['std']).shift(1)
return df
def generate_chart_data(start_date, end_date, trades_file):
"""生成图表数据"""
# 1. 加载K线数据
start_ts = int(pd.Timestamp(start_date).timestamp() * 1000)
end_ts = int(pd.Timestamp(end_date).timestamp() * 1000) + 86400000 # 加一天确保包含end_date当天的数据
query = BitMartETH5M.select().where(
(BitMartETH5M.id >= start_ts) & (BitMartETH5M.id <= end_ts)
).order_by(BitMartETH5M.id)
data = []
for row in query:
data.append({
'timestamp': row.id,
'open': row.open,
'high': row.high,
'low': row.low,
'close': row.close
})
df = pd.DataFrame(data)
df['datetime'] = pd.to_datetime(df['timestamp'], unit='ms')
# 2. 计算布林带
df = calculate_bollinger_bands(df)
# 3. 加载交易记录
trades_df = pd.read_csv(trades_file)
trades_df['datetime'] = pd.to_datetime(trades_df['timestamp'])
# 4. 准备图表数据
chart_data = []
for idx, row in df.iterrows():
chart_data.append({
'timestamp': int(row['timestamp']),
'datetime': row['datetime'].strftime('%Y-%m-%d %H:%M'),
'open': float(row['open']) if pd.notna(row['open']) else None,
'high': float(row['high']) if pd.notna(row['high']) else None,
'low': float(row['low']) if pd.notna(row['low']) else None,
'close': float(row['close']) if pd.notna(row['close']) else None,
'bb_upper': float(row['bb_upper']) if pd.notna(row['bb_upper']) else None,
'bb_mid': float(row['bb_mid']) if pd.notna(row['bb_mid']) else None,
'bb_lower': float(row['bb_lower']) if pd.notna(row['bb_lower']) else None,
})
# 5. 准备交易标记数据
trades_markers = []
for idx, trade in trades_df.iterrows():
action = trade['action']
price = trade['price']
timestamp = trade['datetime']
reason = trade['reason']
# 找到对应的K线索引使用最近的K线
kline_idx = df[df['datetime'] == timestamp].index
if len(kline_idx) == 0:
# 如果找不到完全匹配的找最近的K线
time_diff = abs(df['datetime'] - timestamp)
min_diff = time_diff.min()
# 如果时间差超过10分钟跳过这个标记
if min_diff > pd.Timedelta(minutes=10):
print(f"跳过标记 {timestamp}找不到匹配的K线最小时间差: {min_diff}")
continue
kline_idx = time_diff.idxmin()
else:
kline_idx = kline_idx[0]
# 图上显示真实成交价,避免“总在最高/最低点成交”的错觉
kline = df.loc[kline_idx]
if pd.notna(price):
display_price = float(price)
else:
display_price = float(kline['close'])
marker = {
'timestamp': timestamp.strftime('%Y-%m-%d %H:%M'),
'price': display_price, # 使用K线实际价格
'action': action,
'reason': reason,
'index': int(kline_idx)
}
# 关键操作直接显示在图上,便于快速理解“为什么做这笔交易”
# 新增:开仓/加仓也展示标签
marker['show_reason_label'] = (
('延迟反转' in reason)
or ('止损' in reason)
or ('开long' in action)
or ('开short' in action)
or ('加long' in action)
or ('加short' in action)
)
if '止损' in reason:
marker['short_reason'] = '止损'
elif '延迟反转' in reason:
marker['short_reason'] = '延迟反转'
elif '触中轨平50%' in reason:
marker['short_reason'] = '中轨平半'
elif '回开仓价全平' in reason:
marker['short_reason'] = '回本全平'
elif '触上轨开空' in reason:
marker['short_reason'] = '上轨开空'
elif '触下轨开多' in reason:
marker['short_reason'] = '下轨开多'
elif '触上轨加空' in reason:
marker['short_reason'] = '上轨加空'
elif '触下轨加多' in reason:
marker['short_reason'] = '下轨加多'
else:
marker['short_reason'] = reason
# 分类标记
if '开long' in action or '加long' in action:
marker['type'] = 'open_long'
marker['color'] = '#00ff00'
marker['symbol'] = 'triangle'
elif '开short' in action or '加short' in action:
marker['type'] = 'open_short'
marker['color'] = '#ff0000'
marker['symbol'] = 'triangle'
elif '平仓' in action:
if '50%' in action:
marker['type'] = 'close_half'
marker['color'] = '#ffff00'
marker['symbol'] = 'diamond'
else:
marker['type'] = 'close_all'
marker['color'] = '#ff00ff'
marker['symbol'] = 'circle'
else:
marker['type'] = 'other'
marker['color'] = '#ffffff'
marker['symbol'] = 'circle'
trades_markers.append(marker)
return chart_data, trades_markers
def generate_html(chart_data, trades_markers, output_file):
"""生成HTML文件"""
html_content = f"""<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<title>布林带策略回测可视化 - 2026年3月</title>
<style>
html,
body {{
margin: 0;
padding: 0;
width: 100%;
height: 100%;
background: #0a0e27;
color: #e0e6ed;
font-family: 'SF Mono', Monaco, 'Cascadia Code', 'Roboto Mono', Consolas, 'Courier New', monospace;
}}
#chart {{
width: 100%;
height: 100%;
}}
.legend {{
position: fixed;
top: 20px;
right: 20px;
background: rgba(15, 23, 42, 0.95);
padding: 20px;
border-radius: 12px;
border: 1px solid #334155;
font-size: 13px;
line-height: 1.8;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.4);
backdrop-filter: blur(10px);
z-index: 1000;
}}
.legend-title {{
font-weight: 600;
margin-bottom: 12px;
color: #f1f5f9;
font-size: 14px;
letter-spacing: 0.5px;
}}
.legend-item {{
display: flex;
align-items: center;
margin: 8px 0;
}}
.legend-marker {{
width: 14px;
height: 14px;
margin-right: 10px;
border-radius: 2px;
}}
</style>
<script src="https://cdn.jsdelivr.net/npm/echarts@5/dist/echarts.min.js"></script>
</head>
<body>
<div id="chart"></div>
<div class="legend">
<div class="legend-title">📊 交易标记说明</div>
<div class="legend-item">
<div class="legend-marker" style="background: #00ff00;">▲</div>
<span>开多/加多</span>
</div>
<div class="legend-item">
<div class="legend-marker" style="background: #ff0000;">▼</div>
<span>开空/加空</span>
</div>
<div class="legend-item">
<div class="legend-marker" style="background: #ffff00;">◆</div>
<span>平仓50%</span>
</div>
<div class="legend-item">
<div class="legend-marker" style="background: #ff00ff;">●</div>
<span>平仓100%</span>
</div>
</div>
<script>
const chartData = {json.dumps(chart_data, ensure_ascii=False)};
const tradesMarkers = {json.dumps(trades_markers, ensure_ascii=False)};
function main() {{
const categoryData = [];
const klineData = [];
const upper = [];
const mid = [];
const lower = [];
for (const k of chartData) {{
const d = new Date(k.timestamp);
const label = `${{d.getMonth()+1}}/${{d.getDate()}} ${{d.getHours().toString().padStart(2, "0")}}:${{d
.getMinutes()
.toString()
.padStart(2, "0")}}`;
categoryData.push(label);
klineData.push([k.open, k.close, k.low, k.high]);
upper.push(k.bb_upper);
mid.push(k.bb_mid);
lower.push(k.bb_lower);
}}
// 准备交易标记数据
const openLongData = [];
const openShortData = [];
const closeHalfData = [];
const closeAllData = [];
for (const marker of tradesMarkers) {{
const point = {{
value: [marker.index, marker.price],
reason: marker.reason,
action: marker.action,
shortReason: marker.short_reason,
label: marker.show_reason_label
? {{
show: true,
formatter: marker.short_reason,
color: "#f8fafc",
backgroundColor: "rgba(15,23,42,0.85)",
borderColor: "#475569",
borderWidth: 1,
borderRadius: 4,
padding: [2, 4],
fontSize: 10,
}}
: {{ show: false }},
itemStyle: {{ color: marker.color }},
}};
if (marker.type === 'open_long') {{
openLongData.push(point);
}} else if (marker.type === 'open_short') {{
openShortData.push(point);
}} else if (marker.type === 'close_half') {{
closeHalfData.push(point);
}} else if (marker.type === 'close_all') {{
closeAllData.push(point);
}}
}}
const chartDom = document.getElementById("chart");
const chart = echarts.init(chartDom, null, {{ renderer: "canvas" }});
const option = {{
backgroundColor: "#0a0e27",
tooltip: {{
trigger: "axis",
axisPointer: {{ type: "cross" }},
backgroundColor: "rgba(15, 23, 42, 0.95)",
borderColor: "#334155",
textStyle: {{ color: "#e0e6ed" }},
}},
axisPointer: {{
link: [{{ xAxisIndex: "all" }}],
}},
grid: {{
left: "3%",
right: "200px",
top: "6%",
bottom: "8%",
containLabel: true,
}},
xAxis: {{
type: "category",
data: categoryData,
scale: true,
boundaryGap: true,
axisLine: {{ lineStyle: {{ color: "#475569" }} }},
axisLabel: {{
color: "#94a3b8",
rotate: 45,
fontSize: 11
}},
}},
yAxis: {{
scale: true,
axisLine: {{ lineStyle: {{ color: "#475569" }} }},
splitLine: {{ lineStyle: {{ color: "#1e293b" }} }},
axisLabel: {{ color: "#94a3b8" }},
}},
dataZoom: [
{{
type: "inside",
start: 0,
end: 100,
}},
{{
type: "slider",
start: 0,
end: 100,
height: 30,
backgroundColor: "#1e293b",
fillerColor: "rgba(100, 116, 139, 0.3)",
borderColor: "#334155",
handleStyle: {{
color: "#64748b",
borderColor: "#94a3b8"
}},
textStyle: {{ color: "#94a3b8" }},
}},
],
series: [
{{
name: "K线",
type: "candlestick",
data: klineData,
itemStyle: {{
color: "#10b981",
color0: "#ef4444",
borderColor: "#10b981",
borderColor0: "#ef4444",
}},
}},
{{
name: "BB上轨",
type: "line",
data: upper,
symbol: "none",
lineStyle: {{ color: "#f59e0b", width: 2 }},
z: 1,
}},
{{
name: "BB中轨",
type: "line",
data: mid,
symbol: "none",
lineStyle: {{ color: "#8b5cf6", width: 2, type: "dashed" }},
z: 1,
}},
{{
name: "BB下轨",
type: "line",
data: lower,
symbol: "none",
lineStyle: {{ color: "#f59e0b", width: 2 }},
z: 1,
}},
{{
name: "开多/加多",
type: "scatter",
symbol: "triangle",
symbolSize: 12,
symbolOffset: [0, 8], // 向下偏移,使三角形底部对齐价格
data: openLongData,
itemStyle: {{ color: "#00ff00" }},
z: 10,
tooltip: {{
formatter: (params) => {{
const marker = params.data;
return `开多/加多<br/>价格: ${{marker.value[1].toFixed(2)}}<br/>原因: ${{marker.reason}}`;
}}
}}
}},
{{
name: "开空/加空",
type: "scatter",
symbol: "triangle",
symbolSize: 12,
symbolRotate: 180,
symbolOffset: [0, -8], // 向上偏移,使三角形底部对齐价格
data: openShortData,
itemStyle: {{ color: "#ff0000" }},
z: 10,
tooltip: {{
formatter: (params) => {{
const marker = params.data;
return `开空/加空<br/>价格: ${{marker.value[1].toFixed(2)}}<br/>原因: ${{marker.reason}}`;
}}
}}
}},
{{
name: "平仓50%",
type: "scatter",
symbol: "diamond",
symbolSize: 10,
data: closeHalfData,
itemStyle: {{ color: "#ffff00" }},
z: 10,
tooltip: {{
formatter: (params) => {{
const marker = params.data;
return `平仓50%<br/>价格: ${{marker.value[1].toFixed(2)}}<br/>原因: ${{marker.reason}}`;
}}
}}
}},
{{
name: "平仓100%",
type: "scatter",
symbol: "circle",
symbolSize: 10,
data: closeAllData,
itemStyle: {{ color: "#ff00ff" }},
z: 10,
tooltip: {{
formatter: (params) => {{
const marker = params.data;
return `平仓100%<br/>价格: ${{marker.value[1].toFixed(2)}}<br/>原因: ${{marker.reason}}`;
}}
}}
}},
],
}};
chart.setOption(option);
window.addEventListener("resize", () => chart.resize());
}}
main();
</script>
</body>
</html>
"""
with open(output_file, 'w', encoding='utf-8') as f:
f.write(html_content)
print(f"✅ 图表已生成: {output_file}")
if __name__ == '__main__':
db.connect(reuse_if_open=True)
try:
print("正在生成回测可视化图表...")
output_dir = Path(__file__).parent / 'backtest_outputs' / 'charts'
output_dir.mkdir(parents=True, exist_ok=True)
# 生成图表数据
chart_data, trades_markers = generate_chart_data(
start_date='2026-03-01',
end_date='2026-03-03',
trades_file=str(Path(__file__).parent / 'backtest_outputs' / 'trades' / 'bb_backtest_march_2026_trades.csv')
)
print(f"📊 K线数据: {len(chart_data)}")
print(f"📍 交易标记: {len(trades_markers)}")
# 生成HTML
output_file = output_dir / 'bb_backtest_visualization.html'
generate_html(chart_data, trades_markers, str(output_file))
print(f"\n🎉 完成!请在浏览器中打开 {output_file} 查看图表")
finally:
db.close()

View File

@@ -1,208 +0,0 @@
"""
Gmail API 邮件读取工具
使用前准备:
1. 访问 https://console.cloud.google.com/ 创建项目
2. 启用 Gmail API
3. 创建 OAuth 2.0 凭据(桌面应用类型),下载 credentials.json 放到本目录
4. 首次运行会弹出浏览器授权,授权后自动生成 token.json
"""
import os
import base64
import json
from datetime import datetime
from google.auth.transport.requests import Request
from google.oauth2.credentials import Credentials
from google_auth_oauthlib.flow import InstalledAppFlow
from googleapiclient.discovery import build
from email.utils import parsedate_to_datetime
# 只读权限
SCOPES = ['https://www.googleapis.com/auth/gmail.readonly']
# 凭据文件路径(和脚本同目录)
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
CREDENTIALS_FILE = os.path.join(BASE_DIR, 'credentials.json')
TOKEN_FILE = os.path.join(BASE_DIR, 'token.json')
def get_service():
"""获取 Gmail API 服务实例"""
creds = None
# 尝试加载已有 token
if os.path.exists(TOKEN_FILE):
creds = Credentials.from_authorized_user_file(TOKEN_FILE, SCOPES)
# token 无效或过期,重新授权
if not creds or not creds.valid:
if creds and creds.expired and creds.refresh_token:
creds.refresh(Request())
else:
if not os.path.exists(CREDENTIALS_FILE):
print(f"❌ 找不到 {CREDENTIALS_FILE}")
print("请从 Google Cloud Console 下载 OAuth 凭据文件,命名为 credentials.json 放到本目录")
return None
flow = InstalledAppFlow.from_client_secrets_file(CREDENTIALS_FILE, SCOPES)
creds = flow.run_local_server(port=0)
# 保存 token 供下次使用
with open(TOKEN_FILE, 'w') as f:
f.write(creds.to_json())
return build('gmail', 'v1', credentials=creds)
def decode_body(payload):
"""递归解析邮件正文(优先纯文本)"""
# 直接有 body data
if 'body' in payload and payload['body'].get('data'):
return base64.urlsafe_b64decode(payload['body']['data']).decode('utf-8', errors='ignore')
# 多部分邮件,递归查找
if 'parts' in payload:
# 优先找 text/plain
for part in payload['parts']:
if part.get('mimeType') == 'text/plain':
data = part['body'].get('data', '')
if data:
return base64.urlsafe_b64decode(data).decode('utf-8', errors='ignore')
# 没有纯文本,找 text/html
for part in payload['parts']:
if part.get('mimeType') == 'text/html':
data = part['body'].get('data', '')
if data:
return base64.urlsafe_b64decode(data).decode('utf-8', errors='ignore')
# 递归子部分
for part in payload['parts']:
result = decode_body(part)
if result:
return result
return None
def get_header(headers, name):
"""从 headers 列表中取指定字段"""
for h in headers:
if h['name'].lower() == name.lower():
return h['value']
return ''
def list_emails(service, query='', label_ids=None, max_results=10):
"""
列出邮件
:param query: Gmail 搜索语法,如 'from:xxx@gmail.com' 'subject:报告' 'is:unread'
:param label_ids: 标签过滤,如 ['INBOX'], ['UNREAD']
:param max_results: 最多返回条数
"""
params = {'userId': 'me', 'maxResults': max_results}
if query:
params['q'] = query
if label_ids:
params['labelIds'] = label_ids
results = service.users().messages().list(**params).execute()
return results.get('messages', [])
def read_email(service, msg_id):
"""读取单封邮件详情"""
msg = service.users().messages().get(userId='me', id=msg_id, format='full').execute()
headers = msg['payload']['headers']
subject = get_header(headers, 'Subject') or '(无主题)'
sender = get_header(headers, 'From')
to = get_header(headers, 'To')
date_str = get_header(headers, 'Date')
body = decode_body(msg['payload']) or '(无正文)'
# 解析日期
try:
date = parsedate_to_datetime(date_str)
date_str = date.strftime('%Y-%m-%d %H:%M:%S')
except Exception:
pass
return {
'id': msg_id,
'subject': subject,
'from': sender,
'to': to,
'date': date_str,
'body': body,
'labels': msg.get('labelIds', []),
'snippet': msg.get('snippet', ''),
}
def get_attachments(service, msg_id, save_dir=None):
"""下载邮件附件"""
msg = service.users().messages().get(userId='me', id=msg_id, format='full').execute()
attachments = []
if save_dir is None:
save_dir = os.path.join(BASE_DIR, 'attachments')
def _find_attachments(payload):
if 'parts' in payload:
for part in payload['parts']:
filename = part.get('filename', '')
if filename and part['body'].get('attachmentId'):
att = service.users().messages().attachments().get(
userId='me', messageId=msg_id, id=part['body']['attachmentId']
).execute()
data = base64.urlsafe_b64decode(att['data'])
os.makedirs(save_dir, exist_ok=True)
filepath = os.path.join(save_dir, filename)
with open(filepath, 'wb') as f:
f.write(data)
attachments.append({'filename': filename, 'path': filepath, 'size': len(data)})
print(f" 📎 已保存附件: {filename} ({len(data)} bytes)")
# 递归
_find_attachments(part)
_find_attachments(msg['payload'])
return attachments
# ============ 使用示例 ============
if __name__ == '__main__':
service = get_service()
if not service:
exit(1)
print("=" * 60)
print("📬 Gmail 邮件读取")
print("=" * 60)
# --- 示例1: 读取收件箱最近 5 封邮件 ---
print("\n📥 收件箱最近 5 封邮件:\n")
messages = list_emails(service, label_ids=['INBOX'], max_results=5)
for m in messages:
email_data = read_email(service, m['id'])
print(f"📧 主题: {email_data['subject']}")
print(f" 发件人: {email_data['from']}")
print(f" 日期: {email_data['date']}")
print(f" 摘要: {email_data['snippet'][:80]}...")
print()
# --- 示例2: 搜索特定邮件(取消注释使用)---
# messages = list_emails(service, query='subject:报告 is:unread', max_results=5)
# --- 示例3: 读取完整正文 ---
# if messages:
# email_data = read_email(service, messages[0]['id'])
# print(f"\n完整正文:\n{email_data['body']}")
# --- 示例4: 下载附件 ---
# if messages:
# get_attachments(service, messages[0]['id'])

Binary file not shown.

Before

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 37 KiB

View File

@@ -1,65 +0,0 @@
import cv2
import numpy as np
base = 'c:/Users/27942/Desktop/codes/codex_jxs_code/images/'
main_img = cv2.imread(base + '1.jpg')
main_gray = cv2.cvtColor(main_img, cv2.COLOR_BGR2GRAY)
def match_template_alpha(main_gray, alpha, name):
h, w = alpha.shape[:2]
best = None
# 策略1: 直接用alpha通道匹配灰度图
# 策略2: 用alpha做mask只比较非透明区域
# 策略3: 对alpha二值化后匹配
_, alpha_bin = cv2.threshold(alpha, 10, 255, cv2.THRESH_BINARY)
scales = [1.0] + list(np.linspace(0.65, 1.35, 29))
for scale in scales:
rw, rh = int(w * scale), int(h * scale)
if rh < 10 or rw < 10:
continue
if rh > main_gray.shape[0] or rw > main_gray.shape[1]:
continue
resized_alpha = cv2.resize(alpha, (rw, rh))
resized_bin = cv2.resize(alpha_bin, (rw, rh))
_, resized_mask = cv2.threshold(resized_bin, 10, 255, cv2.THRESH_BINARY)
# 用mask匹配只看非透明区域
res = cv2.matchTemplate(main_gray, resized_alpha, cv2.TM_CCOEFF_NORMED, mask=resized_mask)
_, max_val, _, max_loc = cv2.minMaxLoc(res)
if best is None or max_val > best['conf']:
best = {
'x': max_loc[0], 'y': max_loc[1],
'w': rw, 'h': rh,
'conf': max_val, 'scale': scale,
'cx': max_loc[0] + rw // 2,
'cy': max_loc[1] + rh // 2,
}
return best
results = {}
for name in ['2.png', '3.png', '4.png']:
template = cv2.imread(base + name, cv2.IMREAD_UNCHANGED)
alpha = template[:, :, 3]
best = match_template_alpha(main_gray, alpha, name)
results[name] = best
x, y, cx, cy = best['x'], best['y'], best['cx'], best['cy']
conf, scale = best['conf'], best['scale']
print(name + ': pos=(' + str(x) + ',' + str(y) + ') center=(' + str(cx) + ',' + str(cy) + ') conf=' + str(round(conf, 4)) + ' scale=' + str(round(scale, 2)))
# 可视化
colors = [(0, 0, 255), (0, 255, 0), (255, 0, 0)]
vis = main_img.copy()
for i, name in enumerate(['2.png', '3.png', '4.png']):
m = results[name]
cv2.rectangle(vis, (m['x'], m['y']), (m['x'] + m['w'], m['y'] + m['h']), colors[i], 2)
cv2.putText(vis, name, (m['x'], m['y'] - 5), cv2.FONT_HERSHEY_SIMPLEX, 0.5, colors[i], 1)
cv2.circle(vis, (m['cx'], m['cy']), 5, colors[i], -1)
cv2.imwrite(base + 'result.jpg', vis)
print('result saved to images/result.jpg')

Binary file not shown.