This commit is contained in:
27942
2025-12-19 19:16:41 +08:00
parent 060678f203
commit b04a1da4c3
19 changed files with 746788 additions and 192 deletions

16
1111
View File

@@ -12,3 +12,19 @@ websea
3. 更多交易信号
包住形态(原策略)
锤子线和上吊线
早晨之星和黄昏之星
乌云盖顶和刺透形态
三只乌鸦和三白兵
移动平均线金叉死叉
RSI超买超卖
布林带突破
4. 风险管理
止损止盈:可配置的百分比止损止盈
时间止损持仓超过24小时自动平仓
凯利公式改进版:仓位管理
最大回撤控制
手续费考虑0.05%交易手续费

BIN
bitmart/database.db Normal file

Binary file not shown.

View File

@@ -0,0 +1,36 @@
open_datetime,close_datetime,direction,open_price,close_price,size,pnl_amount,pnl_pct,duration_minutes,open_reason,close_reason
2025-12-01 08:23:00,2025-12-01 20:49:00,short,2890.34,2803.6298,0.1,8.67102,2.9999999999999996,746.0,乌云盖顶形态,止盈触发
2025-12-02 00:47:00,2025-12-02 06:01:00,short,2743.72,2798.5944,0.1,-5.487440000000015,-2.0000000000000058,314.0,乌云盖顶形态,止损触发
2025-12-02 07:10:00,2025-12-02 22:07:00,short,2800.39,2856.3977999999997,0.1,-5.600779999999986,-1.9999999999999951,897.0,乌云盖顶形态,止损触发
2025-12-02 23:34:00,2025-12-03 12:27:00,long,2968.42,3057.4726,0.1,8.90526,2.9999999999999996,773.0,刺透形态,止盈触发
2025-12-03 14:51:00,2025-12-03 22:41:00,short,3061.9,3123.138,0.1,-6.123799999999983,-1.9999999999999944,470.0,乌云盖顶形态,止损触发
2025-12-03 23:44:00,2025-12-04 06:01:00,long,3080.1,3172.503,0.1,9.240300000000024,3.000000000000008,377.0,刺透形态,止盈触发
2025-12-04 06:22:00,2025-12-05 03:13:00,long,3162.67,3099.4166,0.1,-6.325340000000007,-2.0000000000000018,1251.0,刺透形态,止损触发
2025-12-05 03:46:00,2025-12-05 10:35:00,short,3117.16,3179.5032,0.1,-6.234320000000025,-2.000000000000008,409.0,乌云盖顶形态,止损触发
2025-12-05 10:39:00,2025-12-05 21:43:00,short,3183.49,3087.9853,0.1,9.550469999999995,2.9999999999999987,664.0,乌云盖顶形态,止盈触发
2025-12-05 22:03:00,2025-12-06 00:34:00,long,3102.98,3040.9204,0.1,-6.205960000000005,-2.0000000000000013,151.0,刺透形态,止损触发
2025-12-06 01:15:00,2025-12-07 22:25:00,long,3016.18,2955.8563999999997,0.1,-6.032360000000017,-2.0000000000000058,2710.0,刺透形态,止损触发
2025-12-08 01:45:00,2025-12-08 06:05:00,long,3113.29,3051.0242,0.1,-6.2265800000000135,-2.000000000000004,260.0,刺透形态,止损触发
2025-12-08 06:42:00,2025-12-08 16:55:00,long,3049.43,3140.9129,0.1,9.148289999999998,2.999999999999999,613.0,刺透形态,止盈触发
2025-12-08 17:53:00,2025-12-09 23:45:00,short,3154.12,3217.2024,0.1,-6.308240000000024,-2.000000000000007,1792.0,乌云盖顶形态,止损触发
2025-12-10 01:37:00,2025-12-10 05:48:00,long,3368.1,3300.738,0.1,-6.736200000000008,-2.0000000000000027,251.0,刺透形态,止损触发
2025-12-10 06:28:00,2025-12-11 01:21:00,short,3312.95,3379.209,0.1,-6.6259000000000015,-2.0000000000000004,1133.0,乌云盖顶形态,止损触发
2025-12-11 02:25:00,2025-12-11 08:34:00,long,3363.14,3295.8772,0.1,-6.726279999999998,-1.9999999999999993,369.0,刺透形态,止损触发
2025-12-11 09:36:00,2025-12-11 11:12:00,short,3266.05,3168.0685000000003,0.1,9.798149999999987,2.9999999999999956,96.0,乌云盖顶形态,止盈触发
2025-12-11 13:39:00,2025-12-12 05:33:00,short,3205.6,3269.712,0.1,-6.411200000000008,-2.0000000000000027,954.0,乌云盖顶形态,止损触发
2025-12-12 06:40:00,2025-12-12 23:31:00,short,3243.5,3146.1949999999997,0.1,9.73050000000003,3.000000000000009,1011.0,乌云盖顶形态,止盈触发
2025-12-12 23:47:00,2025-12-13 00:21:00,long,3106.89,3044.7522,0.1,-6.213779999999997,-1.9999999999999991,34.0,刺透形态,止损触发
2025-12-13 01:19:00,2025-12-15 07:32:00,long,3091.99,3030.1501999999996,0.1,-6.1839800000000205,-2.0000000000000067,3253.0,刺透形态,止损触发
2025-12-15 07:54:00,2025-12-15 17:51:00,long,3063.81,3155.7243,0.1,9.191429999999992,2.9999999999999973,597.0,刺透形态,止盈触发
2025-12-15 18:29:00,2025-12-15 22:53:00,short,3166.44,3071.4468,0.1,9.499319999999988,2.9999999999999964,264.0,乌云盖顶形态,止盈触发
2025-12-15 23:28:00,2025-12-16 02:35:00,short,2997.16,2907.2452,0.1,8.991480000000003,3.0000000000000004,187.0,乌云盖顶形态,止盈触发
2025-12-16 02:48:00,2025-12-17 23:05:00,long,2919.12,3006.6936,0.1,8.757360000000016,3.0000000000000058,2657.0,刺透形态,止盈触发
2025-12-17 23:11:00,2025-12-17 23:50:00,short,3011.15,2920.8155,0.1,9.033449999999993,2.999999999999998,39.0,乌云盖顶形态,止盈触发
2025-12-18 01:31:00,2025-12-18 02:51:00,long,2866.92,2809.5816,0.1,-5.73384000000001,-2.000000000000003,80.0,刺透形态,止损触发
2025-12-18 04:00:00,2025-12-18 21:01:00,short,2818.01,2874.3702000000003,0.1,-5.636020000000008,-2.0000000000000027,1021.0,乌云盖顶形态,止损触发
2025-12-18 21:07:00,2025-12-18 21:49:00,long,2881.22,2967.6566,0.1,8.64366,3.0000000000000004,42.0,刺透形态,止盈触发
2025-12-18 22:35:00,2025-12-19 01:10:00,short,2945.83,2857.4550999999997,0.1,8.837490000000026,3.0000000000000084,155.0,乌云盖顶形态,止盈触发
2025-12-19 01:13:00,2025-12-19 01:23:00,long,2862.15,2804.907,0.1,-5.724299999999994,-1.9999999999999976,10.0,刺透形态,止损触发
2025-12-19 01:25:00,2025-12-19 02:12:00,short,2799.24,2855.2248,0.1,-5.598480000000018,-2.0000000000000067,47.0,乌云盖顶形态,止损触发
2025-12-19 02:14:00,2025-12-19 11:29:00,short,2855.26,2912.3652,0.1,-5.710519999999996,-1.9999999999999987,555.0,乌云盖顶形态,止损触发
2025-12-19 11:38:00,2025-12-19 15:11:00,long,2897.05,2983.9615000000003,0.1,8.691150000000016,3.0000000000000053,213.0,刺透形态,止盈触发
1 open_datetime close_datetime direction open_price close_price size pnl_amount pnl_pct duration_minutes open_reason close_reason
2 2025-12-01 08:23:00 2025-12-01 20:49:00 short 2890.34 2803.6298 0.1 8.67102 2.9999999999999996 746.0 乌云盖顶形态 止盈触发
3 2025-12-02 00:47:00 2025-12-02 06:01:00 short 2743.72 2798.5944 0.1 -5.487440000000015 -2.0000000000000058 314.0 乌云盖顶形态 止损触发
4 2025-12-02 07:10:00 2025-12-02 22:07:00 short 2800.39 2856.3977999999997 0.1 -5.600779999999986 -1.9999999999999951 897.0 乌云盖顶形态 止损触发
5 2025-12-02 23:34:00 2025-12-03 12:27:00 long 2968.42 3057.4726 0.1 8.90526 2.9999999999999996 773.0 刺透形态 止盈触发
6 2025-12-03 14:51:00 2025-12-03 22:41:00 short 3061.9 3123.138 0.1 -6.123799999999983 -1.9999999999999944 470.0 乌云盖顶形态 止损触发
7 2025-12-03 23:44:00 2025-12-04 06:01:00 long 3080.1 3172.503 0.1 9.240300000000024 3.000000000000008 377.0 刺透形态 止盈触发
8 2025-12-04 06:22:00 2025-12-05 03:13:00 long 3162.67 3099.4166 0.1 -6.325340000000007 -2.0000000000000018 1251.0 刺透形态 止损触发
9 2025-12-05 03:46:00 2025-12-05 10:35:00 short 3117.16 3179.5032 0.1 -6.234320000000025 -2.000000000000008 409.0 乌云盖顶形态 止损触发
10 2025-12-05 10:39:00 2025-12-05 21:43:00 short 3183.49 3087.9853 0.1 9.550469999999995 2.9999999999999987 664.0 乌云盖顶形态 止盈触发
11 2025-12-05 22:03:00 2025-12-06 00:34:00 long 3102.98 3040.9204 0.1 -6.205960000000005 -2.0000000000000013 151.0 刺透形态 止损触发
12 2025-12-06 01:15:00 2025-12-07 22:25:00 long 3016.18 2955.8563999999997 0.1 -6.032360000000017 -2.0000000000000058 2710.0 刺透形态 止损触发
13 2025-12-08 01:45:00 2025-12-08 06:05:00 long 3113.29 3051.0242 0.1 -6.2265800000000135 -2.000000000000004 260.0 刺透形态 止损触发
14 2025-12-08 06:42:00 2025-12-08 16:55:00 long 3049.43 3140.9129 0.1 9.148289999999998 2.999999999999999 613.0 刺透形态 止盈触发
15 2025-12-08 17:53:00 2025-12-09 23:45:00 short 3154.12 3217.2024 0.1 -6.308240000000024 -2.000000000000007 1792.0 乌云盖顶形态 止损触发
16 2025-12-10 01:37:00 2025-12-10 05:48:00 long 3368.1 3300.738 0.1 -6.736200000000008 -2.0000000000000027 251.0 刺透形态 止损触发
17 2025-12-10 06:28:00 2025-12-11 01:21:00 short 3312.95 3379.209 0.1 -6.6259000000000015 -2.0000000000000004 1133.0 乌云盖顶形态 止损触发
18 2025-12-11 02:25:00 2025-12-11 08:34:00 long 3363.14 3295.8772 0.1 -6.726279999999998 -1.9999999999999993 369.0 刺透形态 止损触发
19 2025-12-11 09:36:00 2025-12-11 11:12:00 short 3266.05 3168.0685000000003 0.1 9.798149999999987 2.9999999999999956 96.0 乌云盖顶形态 止盈触发
20 2025-12-11 13:39:00 2025-12-12 05:33:00 short 3205.6 3269.712 0.1 -6.411200000000008 -2.0000000000000027 954.0 乌云盖顶形态 止损触发
21 2025-12-12 06:40:00 2025-12-12 23:31:00 short 3243.5 3146.1949999999997 0.1 9.73050000000003 3.000000000000009 1011.0 乌云盖顶形态 止盈触发
22 2025-12-12 23:47:00 2025-12-13 00:21:00 long 3106.89 3044.7522 0.1 -6.213779999999997 -1.9999999999999991 34.0 刺透形态 止损触发
23 2025-12-13 01:19:00 2025-12-15 07:32:00 long 3091.99 3030.1501999999996 0.1 -6.1839800000000205 -2.0000000000000067 3253.0 刺透形态 止损触发
24 2025-12-15 07:54:00 2025-12-15 17:51:00 long 3063.81 3155.7243 0.1 9.191429999999992 2.9999999999999973 597.0 刺透形态 止盈触发
25 2025-12-15 18:29:00 2025-12-15 22:53:00 short 3166.44 3071.4468 0.1 9.499319999999988 2.9999999999999964 264.0 乌云盖顶形态 止盈触发
26 2025-12-15 23:28:00 2025-12-16 02:35:00 short 2997.16 2907.2452 0.1 8.991480000000003 3.0000000000000004 187.0 乌云盖顶形态 止盈触发
27 2025-12-16 02:48:00 2025-12-17 23:05:00 long 2919.12 3006.6936 0.1 8.757360000000016 3.0000000000000058 2657.0 刺透形态 止盈触发
28 2025-12-17 23:11:00 2025-12-17 23:50:00 short 3011.15 2920.8155 0.1 9.033449999999993 2.999999999999998 39.0 乌云盖顶形态 止盈触发
29 2025-12-18 01:31:00 2025-12-18 02:51:00 long 2866.92 2809.5816 0.1 -5.73384000000001 -2.000000000000003 80.0 刺透形态 止损触发
30 2025-12-18 04:00:00 2025-12-18 21:01:00 short 2818.01 2874.3702000000003 0.1 -5.636020000000008 -2.0000000000000027 1021.0 乌云盖顶形态 止损触发
31 2025-12-18 21:07:00 2025-12-18 21:49:00 long 2881.22 2967.6566 0.1 8.64366 3.0000000000000004 42.0 刺透形态 止盈触发
32 2025-12-18 22:35:00 2025-12-19 01:10:00 short 2945.83 2857.4550999999997 0.1 8.837490000000026 3.0000000000000084 155.0 乌云盖顶形态 止盈触发
33 2025-12-19 01:13:00 2025-12-19 01:23:00 long 2862.15 2804.907 0.1 -5.724299999999994 -1.9999999999999976 10.0 刺透形态 止损触发
34 2025-12-19 01:25:00 2025-12-19 02:12:00 short 2799.24 2855.2248 0.1 -5.598480000000018 -2.0000000000000067 47.0 乌云盖顶形态 止损触发
35 2025-12-19 02:14:00 2025-12-19 11:29:00 short 2855.26 2912.3652 0.1 -5.710519999999996 -1.9999999999999987 555.0 乌云盖顶形态 止损触发
36 2025-12-19 11:38:00 2025-12-19 15:11:00 long 2897.05 2983.9615000000003 0.1 8.691150000000016 3.0000000000000053 213.0 刺透形态 止盈触发

16929
bitmart/ethusdt_kline.csv Normal file

File diff suppressed because it is too large Load Diff

507832
bitmart/kline_1.csv Normal file

File diff suppressed because it is too large Load Diff

33858
bitmart/kline_15.csv Normal file

File diff suppressed because it is too large Load Diff

169295
bitmart/kline_3.csv Normal file

File diff suppressed because it is too large Load Diff

16930
bitmart/kline_30.csv Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,19 @@
============================================================
ETHUSDT <20><><EFBFBD>ײ<EFBFBD><D7B2><EFBFBD><EFBFBD>ܽ<EFBFBD>
============================================================
<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>: <20><><EFBFBD>ƸǶ<C6B8> & <20><>͸<EFBFBD><CDB8>̬
<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>: 1<><31><EFBFBD><EFBFBD>K<EFBFBD><4B>
<EFBFBD><EFBFBD><EFBFBD><EFBFBD>ʱ<EFBFBD><EFBFBD>: 2025<32><35>12<31><32>
<EFBFBD><EFBFBD>ʼ<EFBFBD>ʽ<EFBFBD>: $10000.00
<EFBFBD><EFBFBD>λ<EFBFBD><EFBFBD>С: 0.1 ETH
ֹ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>: 2.0%
ֹӯ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>: 3.0%
<EFBFBD>ܽ<EFBFBD><EFBFBD>״<EFBFBD><EFBFBD><EFBFBD>: 35
ӯ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>: 15 <20><>
<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>: 20 <20><>
ʤ<EFBFBD><EFBFBD>: 42.86%
<EFBFBD><EFBFBD>ӯ<EFBFBD><EFBFBD>: $14.84
<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ʽ<EFBFBD>: $10014.84
<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>: 0.15%

View File

@@ -0,0 +1,448 @@
import time
import csv
from datetime import datetime
import pandas as pd
import numpy as np
from typing import List, Dict, Optional, Tuple
# ------------------ 配置 ------------------
START_YEAR = 2025
CONTRACT_SYMBOL = "ETHUSDT"
STEP = 3 # K 线周期,单位分钟
CSV_FILE = f"kline_{STEP}.csv"
# ------------------ 策略参数 ------------------
class TradingStrategy:
def __init__(self, initial_balance: float = 10000):
self.positions = [] # 存储持仓信息
self.trades = [] # 存储交易记录
self.initial_balance = initial_balance
self.current_balance = initial_balance
self.position_size = 0.1 # 每次开仓数量ETH
self.stop_loss_pct = 0.02 # 止损比例 2%
self.take_profit_pct = 0.03 # 止盈比例 3%
self.max_positions = 1 # 最大同时持仓数
def detect_dark_cloud_cover(self, df: pd.DataFrame, i: int) -> bool:
"""检测乌云盖顶形态(看跌)"""
if i < 1:
return False
prev_candle = df.iloc[i - 1]
curr_candle = df.iloc[i]
# 前一根是阳线
if prev_candle['close'] <= prev_candle['open']:
return False
# 当前是阴线
if curr_candle['close'] >= curr_candle['open']:
return False
# 当前开盘价高于前一根最高价
if curr_candle['open'] <= prev_candle['high']:
return False
# 当前收盘价低于前一根实体的50%以下
prev_body_mid = (prev_candle['open'] + prev_candle['close']) / 2
if curr_candle['close'] >= prev_body_mid:
return False
# 可选:添加成交量确认
# if curr_candle['volume'] < prev_candle['volume']:
# return False
return True
def detect_piercing_pattern(self, df: pd.DataFrame, i: int) -> bool:
"""检测刺透形态(看涨)"""
if i < 1:
return False
prev_candle = df.iloc[i - 1]
curr_candle = df.iloc[i]
# 前一根是阴线
if prev_candle['close'] >= prev_candle['open']:
return False
# 当前是阳线
if curr_candle['close'] <= curr_candle['open']:
return False
# 当前开盘价低于前一根最低价
if curr_candle['open'] >= prev_candle['low']:
return False
# 当前收盘价高于前一根实体的50%以上
prev_body_mid = (prev_candle['open'] + prev_candle['close']) / 2
if curr_candle['close'] <= prev_body_mid:
return False
# 可选:添加成交量确认
# if curr_candle['volume'] < prev_candle['volume']:
# return False
return True
def open_position(self, direction: str, price: float, timestamp: int, reason: str):
"""开仓"""
# 检查是否达到最大持仓限制
if len(self.positions) >= self.max_positions:
return False
position = {
'direction': direction, # 'long' 或 'short'
'open_price': price,
'open_time': timestamp,
'open_reason': reason,
'size': self.position_size,
'status': 'open',
'stop_loss': self.calculate_stop_loss(direction, price),
'take_profit': self.calculate_take_profit(direction, price)
}
self.positions.append(position)
print(f"\n📈 开仓信号 @ {datetime.fromtimestamp(timestamp)}")
print(f" 方向: {'做多' if direction == 'long' else '做空'}")
print(f" 价格: ${price:.2f}")
print(f" 数量: {self.position_size} ETH")
print(f" 止损: ${position['stop_loss']:.2f}")
print(f" 止盈: ${position['take_profit']:.2f}")
print(f" 原因: {reason}")
return True
def calculate_stop_loss(self, direction: str, price: float) -> float:
"""计算止损价"""
if direction == 'long':
return price * (1 - self.stop_loss_pct)
else:
return price * (1 + self.stop_loss_pct)
def calculate_take_profit(self, direction: str, price: float) -> float:
"""计算止盈价"""
if direction == 'long':
return price * (1 + self.take_profit_pct)
else:
return price * (1 - self.take_profit_pct)
def close_position(self, position_idx: int, price: float, timestamp: int, reason: str):
"""平仓"""
position = self.positions[position_idx]
# 计算盈亏
if position['direction'] == 'long':
pnl_pct = (price - position['open_price']) / position['open_price']
else:
pnl_pct = (position['open_price'] - price) / position['open_price']
pnl_amount = self.position_size * position['open_price'] * pnl_pct
# 更新持仓状态
position.update({
'close_price': price,
'close_time': timestamp,
'close_reason': reason,
'pnl_pct': pnl_pct * 100, # 百分比
'pnl_amount': pnl_amount,
'status': 'closed'
})
# 更新余额
self.current_balance += pnl_amount
print(f"\n📉 平仓信号 @ {datetime.fromtimestamp(timestamp)}")
print(f" 方向: {'平多单' if position['direction'] == 'long' else '平空单'}")
print(f" 开仓价: ${position['open_price']:.2f}")
print(f" 平仓价: ${price:.2f}")
print(f" 盈亏: {pnl_pct * 100:.2f}% (${pnl_amount:.2f})")
print(f" 原因: {reason}")
print(f" 当前余额: ${self.current_balance:.2f}")
# 记录交易
trade_record = position.copy()
trade_record['duration'] = timestamp - position['open_time']
trade_record['duration_minutes'] = trade_record['duration'] / 60
self.trades.append(trade_record)
# 从持仓列表中移除已平仓的仓位
self.positions.pop(position_idx)
return True
def check_stop_loss_take_profit(self, df: pd.DataFrame, i: int):
"""检查止损止盈"""
current_price = df.iloc[i]['close']
current_time = df.iloc[i]['id']
current_high = df.iloc[i]['high']
current_low = df.iloc[i]['low']
positions_to_close = []
for idx, position in enumerate(self.positions):
if position['status'] != 'open':
continue
close_reason = None
close_price = current_price
if position['direction'] == 'long':
# 多头止损检查:最低价是否触及止损
if current_low <= position['stop_loss']:
close_reason = "止损触发"
close_price = position['stop_loss'] # 使用止损价
# 多头止盈检查:最高价是否触及止盈
elif current_high >= position['take_profit']:
close_reason = "止盈触发"
close_price = position['take_profit'] # 使用止盈价
else:
# 空头止损检查:最高价是否触及止损
if current_high >= position['stop_loss']:
close_reason = "止损触发"
close_price = position['stop_loss'] # 使用止损价
# 空头止盈检查:最低价是否触及止盈
elif current_low <= position['take_profit']:
close_reason = "止盈触发"
close_price = position['take_profit'] # 使用止盈价
if close_reason:
positions_to_close.append((idx, close_price, close_reason))
# 按索引从大到小平仓,避免索引错乱
positions_to_close.sort(reverse=True)
for idx, close_price, close_reason in positions_to_close:
self.close_position(idx, close_price, current_time, close_reason)
def analyze_trades(self):
"""分析交易结果"""
if not self.trades:
return {
'total_trades': 0,
'winning_trades': 0,
'losing_trades': 0,
'win_rate': 0,
'total_pnl': 0,
'avg_pnl': 0,
'max_win': 0,
'max_loss': 0,
'profit_factor': 0
}
total_trades = len(self.trades)
winning_trades = [t for t in self.trades if t['pnl_amount'] > 0]
losing_trades = [t for t in self.trades if t['pnl_amount'] < 0]
total_pnl = sum(t['pnl_amount'] for t in self.trades)
total_win = sum(t['pnl_amount'] for t in winning_trades)
total_loss = abs(sum(t['pnl_amount'] for t in losing_trades))
win_rate = len(winning_trades) / total_trades * 100 if total_trades > 0 else 0
avg_pnl = total_pnl / total_trades if total_trades > 0 else 0
max_win = max(t['pnl_amount'] for t in winning_trades) if winning_trades else 0
max_loss = min(t['pnl_amount'] for t in losing_trades) if losing_trades else 0
profit_factor = total_win / total_loss if total_loss > 0 else float('inf')
return {
'total_trades': total_trades,
'winning_trades': len(winning_trades),
'losing_trades': len(losing_trades),
'win_rate': win_rate,
'total_pnl': total_pnl,
'avg_pnl': avg_pnl,
'max_win': max_win,
'max_loss': max_loss,
'profit_factor': profit_factor,
'final_balance': self.current_balance,
'total_return': ((self.current_balance - self.initial_balance) / self.initial_balance * 100)
}
# ------------------ 主程序 ------------------
def main():
print("=" * 60)
print("ETHUSDT 交易策略 - 乌云盖顶 & 刺透形态")
print(f"数据周期: {STEP}分钟K线")
print(f"分析时间: 2025年12月")
print("=" * 60)
# 1. 从CSV文件读取数据
print(f"\n📥 正在从 {CSV_FILE} 读取数据...")
try:
df = pd.read_csv(CSV_FILE)
print(f"成功读取 {len(df)} 条K线数据")
# 转换时间戳为datetime
df['datetime'] = pd.to_datetime(df['id'], unit='s')
df.set_index('datetime', inplace=True)
# 按时间排序
df = df.sort_index()
except FileNotFoundError:
print(f"❌ 错误: 文件 {CSV_FILE} 不存在")
return
except Exception as e:
print(f"❌ 读取文件时出错: {e}")
return
# 2. 过滤出12月份数据
dec_2025_start = pd.Timestamp('2025-12-01')
dec_2025_end = pd.Timestamp('2025-12-31 23:59:59')
# 确保数据在指定范围内
mask = (df.index >= dec_2025_start) & (df.index <= dec_2025_end)
dec_df = df.loc[mask].copy()
if len(dec_df) == 0:
print("❌ 未找到2025年12月的数据")
print(f"数据时间范围: {df.index[0]}{df.index[-1]}")
return
print(f"\n📊 12月份数据: {len(dec_df)} 条K线")
print(f"时间范围: {dec_df.index[0]}{dec_df.index[-1]}")
# 显示数据预览
print(f"\n数据预览:")
print(dec_df[['open', 'high', 'low', 'close', 'volume']].head())
# 3. 初始化策略
strategy = TradingStrategy(initial_balance=10000)
# 4. 运行策略
print("\n" + "=" * 60)
print("开始执行交易策略...")
print("=" * 60)
for i in range(1, len(dec_df)):
current_time = int(dec_df.iloc[i]['id'])
current_price = dec_df.iloc[i]['close']
# 首先检查止损止盈
strategy.check_stop_loss_take_profit(dec_df, i)
# 如果有持仓,跳过新信号(单次只持有一个仓位)
if len(strategy.positions) >= strategy.max_positions:
continue
# 检测形态
dark_cloud = strategy.detect_dark_cloud_cover(dec_df, i)
piercing = strategy.detect_piercing_pattern(dec_df, i)
# 处理信号
if dark_cloud:
# 乌云盖顶 - 看跌信号,开空仓
strategy.open_position('short', current_price, current_time, "乌云盖顶形态")
elif piercing:
# 刺透形态 - 看涨信号,开多仓
strategy.open_position('long', current_price, current_time, "刺透形态")
# 5. 强制平掉所有未平仓
print("\n" + "=" * 60)
print("强制平仓所有持仓...")
print("=" * 60)
if strategy.positions:
last_price = dec_df.iloc[-1]['close']
last_time = dec_df.iloc[-1]['id']
# 按索引从大到小平仓
for idx in range(len(strategy.positions) - 1, -1, -1):
strategy.close_position(idx, last_price, last_time, "策略结束强制平仓")
else:
print("没有需要平仓的持仓")
# 6. 生成交易报告
print("\n" + "=" * 60)
print("📊 交易汇总报告")
print("=" * 60)
analysis = strategy.analyze_trades()
if strategy.trades:
print(f"总交易次数: {analysis['total_trades']}")
print(f"盈利交易: {analysis['winning_trades']}")
print(f"亏损交易: {analysis['losing_trades']}")
print(f"胜率: {analysis['win_rate']:.2f}%")
print(f"总盈亏: ${analysis['total_pnl']:.2f}")
print(f"平均每笔盈亏: ${analysis['avg_pnl']:.2f}")
print(f"最大盈利: ${analysis['max_win']:.2f}")
print(f"最大亏损: ${analysis['max_loss']:.2f}")
print(f"盈利因子: {analysis['profit_factor']:.2f}")
print(f"初始资金: ${strategy.initial_balance:.2f}")
print(f"最终资金: ${analysis['final_balance']:.2f}")
print(f"总收益率: {analysis['total_return']:.2f}%")
# 打印每笔交易详情
print("\n" + "-" * 60)
print("详细交易记录:")
print("-" * 60)
for i, trade in enumerate(strategy.trades, 1):
print(f"\n交易 #{i}:")
print(f" 方向: {'做多' if trade['direction'] == 'long' else '做空'}")
print(f" 开仓时间: {datetime.fromtimestamp(trade['open_time'])}")
print(f" 开仓价格: ${trade['open_price']:.2f}")
print(f" 平仓时间: {datetime.fromtimestamp(trade['close_time'])}")
print(f" 平仓价格: ${trade['close_price']:.2f}")
print(f" 持仓时间: {trade['duration_minutes']:.1f} 分钟")
print(f" 盈亏: ${trade['pnl_amount']:.2f} ({trade['pnl_pct']:.2f}%)")
print(f" 原因: {trade['open_reason']} -> {trade['close_reason']}")
else:
print("本月无交易记录")
# 7. 保存交易记录到CSV
if strategy.trades:
trades_df = pd.DataFrame(strategy.trades)
# 格式化时间列
trades_df['open_datetime'] = trades_df['open_time'].apply(lambda x: datetime.fromtimestamp(x))
trades_df['close_datetime'] = trades_df['close_time'].apply(lambda x: datetime.fromtimestamp(x))
# 选择要保存的列
columns_to_save = [
'open_datetime', 'close_datetime', 'direction', 'open_price',
'close_price', 'size', 'pnl_amount', 'pnl_pct', 'duration_minutes',
'open_reason', 'close_reason'
]
trades_csv = f"eth_trades_{START_YEAR}_12.csv"
trades_df[columns_to_save].to_csv(trades_csv, index=False)
print(f"\n✅ 交易记录已保存到: {trades_csv}")
# 8. 保存策略参数和结果
with open(f"strategy_summary_{START_YEAR}_12.txt", 'w') as f:
f.write("=" * 60 + "\n")
f.write("ETHUSDT 交易策略总结\n")
f.write("=" * 60 + "\n\n")
f.write(f"策略名称: 乌云盖顶 & 刺透形态\n")
f.write(f"数据周期: {STEP}分钟K线\n")
f.write(f"分析时间: 2025年12月\n")
f.write(f"初始资金: ${strategy.initial_balance:.2f}\n")
f.write(f"仓位大小: {strategy.position_size} ETH\n")
f.write(f"止损比例: {strategy.stop_loss_pct * 100}%\n")
f.write(f"止盈比例: {strategy.take_profit_pct * 100}%\n\n")
if strategy.trades:
f.write(f"总交易次数: {analysis['total_trades']}\n")
f.write(f"盈利交易: {analysis['winning_trades']}\n")
f.write(f"亏损交易: {analysis['losing_trades']}\n")
f.write(f"胜率: {analysis['win_rate']:.2f}%\n")
f.write(f"总盈亏: ${analysis['total_pnl']:.2f}\n")
f.write(f"最终资金: ${analysis['final_balance']:.2f}\n")
f.write(f"总收益率: {analysis['total_return']:.2f}%\n")
else:
f.write("本月无交易记录\n")
print(f"\n✅ 策略总结已保存到: strategy_summary_{START_YEAR}_12.txt")
print("\n" + "=" * 60)
print("策略执行完成!")
print("=" * 60)
if __name__ == "__main__":
main()

83
bitmart/回测.py Normal file
View File

@@ -0,0 +1,83 @@
import time
import csv
import loguru
from bitmart.api_contract import APIContract
# ------------------ 配置 ------------------
START_YEAR = 2025
CONTRACT_SYMBOL = "ETHUSDT"
STEP = 3 # K 线周期,单位分钟
CSV_FILE = f"kline_{STEP}.csv"
memo = "合约交易"
api_key = "a0fb7b98464fd9bcce67e7c519d58ec10d0c38a8"
secret_key = "4eaeba78e77aeaab1c2027f846a276d164f264a44c2c1bb1c5f3be50c8de1ca5"
contractAPI = APIContract(api_key, secret_key, memo, timeout=(5, 15))
# ------------------ 时间戳 ------------------
start_of_year = int(time.mktime((START_YEAR, 1, 1, 0, 0, 0, 0, 0, 0)))
current_time = int(time.time())
# ------------------ 抓取数据 ------------------
all_data = []
existing_ids = set()
start_time = start_of_year
# 每次请求时间长度 = step * 500 条 K 线
request_interval_ms = STEP * 60 * 500
while start_time < current_time:
end_time = min(start_time + request_interval_ms, current_time)
loguru.logger.info(f"抓取时间段: {start_time} ~ {end_time}")
try:
response = contractAPI.get_kline(
contract_symbol=CONTRACT_SYMBOL,
step=STEP,
start_time=start_time,
end_time=end_time
)[0]["data"]
formatted = []
for k in response:
print(k)
k_id = int(k["timestamp"])
if k_id in existing_ids:
continue
existing_ids.add(k_id)
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"]),
'volume': float(k["volume"])
})
formatted.sort(key=lambda x: x['id'])
all_data.extend(formatted)
if len(response) < 500:
start_time = end_time
else:
start_time = formatted[-1]['id'] + 1
except Exception as e:
print(f"请求出错: {e},等待 60 秒后重试")
time.sleep(60)
time.sleep(0.2) # 控制速率,保证 <= 每 2 秒 12 次
# ------------------ 保存 CSV ------------------
csv_columns = ['id', 'open', 'high', 'low', 'close', 'volume']
try:
with open(CSV_FILE, 'w', newline='') as csvfile:
writer = csv.DictWriter(csvfile, fieldnames=csv_columns)
writer.writeheader()
for data in all_data:
writer.writerow(data)
print(f"数据已保存到 {CSV_FILE},共 {len(all_data)}")
except IOError:
print("I/O error")

768
bitmart/指数平均线.py Normal file
View File

@@ -0,0 +1,768 @@
"""
量化交易回测系统 - EMA交叉策略双EMA/三EMA
"""
import csv
import datetime
import numpy as np
from typing import List, Dict, Optional, Tuple
# ========================= EMA计算函数 =========================
def calculate_ema(prices: List[float], period: int) -> List[Optional[float]]:
"""
计算指数移动平均线(EMA)
Args:
prices: 价格列表(通常是收盘价)
period: EMA周期
Returns:
EMA值列表前period-1个为None
"""
if len(prices) < period:
return [None] * len(prices)
ema_values = [None] * (period - 1)
# 计算初始SMA作为EMA的起点
sma = sum(prices[:period]) / period
# EMA计算公式EMA_today = (Price_today * (2/(period+1))) + (EMA_yesterday * (1 - (2/(period+1))))
multiplier = 2 / (period + 1)
# 第一个EMA值
ema = sma
ema_values.append(ema)
# 计算后续EMA值
for price in prices[period:]:
ema = (price * multiplier) + (ema * (1 - multiplier))
ema_values.append(ema)
return ema_values
# ========================= 策略核心函数 =========================
def check_ema_cross(ema_fast: List[Optional[float]],
ema_slow: List[Optional[float]],
idx: int) -> Optional[str]:
"""
检查EMA金叉/死叉
Args:
ema_fast: 快线EMA值列表
ema_slow: 慢线EMA值列表
idx: 当前K线索引
Returns:
"golden" - 金叉(做多信号)
"dead" - 死叉(做空信号)
None - 无交叉
"""
if idx < 1:
return None
# 确保有足够的数据
if ema_fast[idx] is None or ema_fast[idx - 1] is None:
return None
if ema_slow[idx] is None or ema_slow[idx - 1] is None:
return None
# 前一根K线快线在慢线下方当前K线快线上穿慢线 -> 金叉
if ema_fast[idx - 1] < ema_slow[idx - 1] and ema_fast[idx] > ema_slow[idx]:
return "golden"
# 前一根K线快线在慢线上方当前K线快线下穿慢线 -> 死叉
if ema_fast[idx - 1] > ema_slow[idx - 1] and ema_fast[idx] < ema_slow[idx]:
return "dead"
return None
def check_triple_ema_cross(ema_fast: List[Optional[float]],
ema_mid: List[Optional[float]],
ema_slow: List[Optional[float]],
idx: int) -> Optional[str]:
"""
检查三EMA交叉更稳定的信号
规则:快线 > 中线 > 慢线 -> 多头排列 -> 做多
快线 < 中线 < 慢线 -> 空头排列 -> 做空
Args:
ema_fast: 快线如EMA7
ema_mid: 中线如EMA14
ema_slow: 慢线如EMA30
idx: 当前K线索引
Returns:
"golden" - 金叉多头排列
"dead" - 死叉空头排列
None - 无明确信号
"""
if idx < 1:
return None
# 确保有足够的数据
if any(ema[idx] is None or ema[idx - 1] is None for ema in [ema_fast, ema_mid, ema_slow]):
return None
# 检查是否形成多头排列EMA快 > 中 > 慢)
current_fast = ema_fast[idx]
current_mid = ema_mid[idx]
current_slow = ema_slow[idx]
prev_fast = ema_fast[idx - 1]
prev_mid = ema_mid[idx - 1]
prev_slow = ema_slow[idx - 1]
# 多头排列条件:快线 > 中线 > 慢线
is_golden_triple = current_fast > current_mid > current_slow
was_not_golden = not (prev_fast > prev_mid > prev_slow)
# 空头排列条件:快线 < 中线 < 慢线
is_dead_triple = current_fast < current_mid < current_slow
was_not_dead = not (prev_fast < prev_mid < prev_slow)
if is_golden_triple and was_not_golden:
return "golden"
elif is_dead_triple and was_not_dead:
return "dead"
return None
# ========================= 回测引擎 =========================
class EMABacktester:
"""EMA交叉策略回测器"""
def __init__(self,
fast_period: int = 12,
slow_period: int = 26,
signal_period: int = 9, # 用于MACD信号线
use_triple_ema: bool = False,
mid_period: int = 14, # 三EMA时的中间周期
use_macd_confirmation: bool = False,
stop_loss_pct: float = 0.02, # 2%止损
take_profit_pct: float = 0.05, # 5%止盈
trailing_stop_pct: float = 0.03): # 3%移动止损
self.fast_period = fast_period
self.slow_period = slow_period
self.signal_period = signal_period
self.use_triple_ema = use_triple_ema
self.mid_period = mid_period
self.use_macd_confirmation = use_macd_confirmation
self.stop_loss_pct = stop_loss_pct
self.take_profit_pct = take_profit_pct
self.trailing_stop_pct = trailing_stop_pct
self.stats = {
'golden_cross': {'count': 0, 'wins': 0, 'total_profit': 0.0, 'name': '金叉做多'},
'dead_cross': {'count': 0, 'wins': 0, 'total_profit': 0.0, 'name': '死叉做空'},
}
def calculate_macd(self, prices: List[float]) -> Tuple[List[Optional[float]],
List[Optional[float]],
List[Optional[float]]]:
"""
计算MACD指标
Returns: (MACD线, 信号线, 柱状图)
"""
# 计算快慢EMA
ema_fast = calculate_ema(prices, self.fast_period)
ema_slow = calculate_ema(prices, self.slow_period)
# 计算MACD线 = EMA快线 - EMA慢线
macd_line = []
for i in range(len(prices)):
if ema_fast[i] is not None and ema_slow[i] is not None:
macd_line.append(ema_fast[i] - ema_slow[i])
else:
macd_line.append(None)
# 计算信号线MACD的EMA
signal_line = calculate_ema([x for x in macd_line if x is not None] if any(macd_line) else [],
self.signal_period)
# 补全None值
signal_line_extended = [None] * (len(prices) - len(signal_line)) + signal_line if len(signal_line) < len(
prices) else signal_line
# 计算柱状图
histogram = []
for i in range(len(prices)):
if macd_line[i] is not None and signal_line_extended[i] is not None:
histogram.append(macd_line[i] - signal_line_extended[i])
else:
histogram.append(None)
return macd_line, signal_line_extended, histogram
def backtest(self, data: List[Dict]) -> Tuple[List[Dict], Dict]:
"""
执行EMA交叉策略回测
Args:
data: K线数据列表
Returns:
trades: 交易记录列表
stats: 统计数据
"""
# 提取收盘价
close_prices = [float(c['close']) for c in data]
# 计算EMA
ema_fast = calculate_ema(close_prices, self.fast_period)
ema_slow = calculate_ema(close_prices, self.slow_period)
# 如果需要三EMA计算中间EMA
ema_mid = None
if self.use_triple_ema:
ema_mid = calculate_ema(close_prices, self.mid_period)
# 如果需要MACD确认计算MACD
macd_line, signal_line, histogram = None, None, None
if self.use_macd_confirmation:
macd_line, signal_line, histogram = self.calculate_macd(close_prices)
trades: List[Dict] = []
current_position: Optional[Dict] = None
highest_price_since_entry = 0 # 用于移动止损
lowest_price_since_entry = float('inf') # 用于移动止损
# 遍历K线数据跳过前几个没有EMA值的
start_idx = max(self.fast_period, self.slow_period)
if self.use_triple_ema:
start_idx = max(start_idx, self.mid_period)
for idx in range(start_idx, len(data)):
current_bar = data[idx]
current_price = float(current_bar['close'])
open_price = float(current_bar['open'])
# ========== 信号检测 ==========
signal = None
# 基础双EMA交叉信号
if not self.use_triple_ema:
signal = check_ema_cross(ema_fast, ema_slow, idx)
# 三EMA排列信号
else:
signal = check_triple_ema_cross(ema_fast, ema_mid, ema_slow, idx)
# MACD确认可选
if signal and self.use_macd_confirmation:
macd_confirmed = False
if signal == "golden":
# 金叉确认MACD线在信号线上方且柱状图为正
if macd_line[idx] is not None and signal_line[idx] is not None:
macd_confirmed = (macd_line[idx] > signal_line[idx]) and (
histogram[idx] is not None and histogram[idx] > 0)
elif signal == "dead":
# 死叉确认MACD线在信号线下方且柱状图为负
if macd_line[idx] is not None and signal_line[idx] is not None:
macd_confirmed = (macd_line[idx] < signal_line[idx]) and (
histogram[idx] is not None and histogram[idx] < 0)
if not macd_confirmed:
signal = None
# ========== 空仓时开仓 ==========
if current_position is None and signal:
# 下一根K线开盘价入场
if idx + 1 < len(data):
entry_price = float(data[idx + 1]['open'])
if signal == "golden": # 做多
current_position = {
'direction': 'long',
'entry_price': entry_price,
'entry_time': data[idx + 1]['id'],
'entry_idx': idx + 1,
'signal': 'golden_cross',
'highest_price': entry_price, # 用于移动止损
'lowest_price': entry_price, # 用于空头的移动止损
}
self.stats['golden_cross']['count'] += 1
elif signal == "dead": # 做空
current_position = {
'direction': 'short',
'entry_price': entry_price,
'entry_time': data[idx + 1]['id'],
'entry_idx': idx + 1,
'signal': 'dead_cross',
'highest_price': entry_price,
'lowest_price': entry_price,
}
self.stats['dead_cross']['count'] += 1
# 跳过下一根因为已经在这根K线开盘入场
continue
# ========== 持仓时处理 ==========
if current_position:
pos_dir = current_position['direction']
entry_price = current_position['entry_price']
signal_key = current_position['signal']
# 更新最高/最低价(用于移动止损)
if pos_dir == 'long':
current_position['highest_price'] = max(current_position['highest_price'], current_price)
else: # short
current_position['lowest_price'] = min(current_position['lowest_price'], current_price)
# ========== 止损止盈检查 ==========
should_exit = False
exit_reason = ""
exit_price = current_price # 默认用收盘价平仓
# 固定止损
if pos_dir == 'long':
stop_loss_price = entry_price * (1 - self.stop_loss_pct)
if current_price <= stop_loss_price:
should_exit = True
exit_reason = "止损"
exit_price = stop_loss_price
# 固定止盈
take_profit_price = entry_price * (1 + self.take_profit_pct)
if current_price >= take_profit_price:
should_exit = True
exit_reason = "止盈"
exit_price = take_profit_price
# 移动止损
trailing_stop_price = current_position['highest_price'] * (1 - self.trailing_stop_pct)
if current_price <= trailing_stop_price:
should_exit = True
exit_reason = "移动止损"
exit_price = trailing_stop_price
else: # short
stop_loss_price = entry_price * (1 + self.stop_loss_pct)
if current_price >= stop_loss_price:
should_exit = True
exit_reason = "止损"
exit_price = stop_loss_price
# 固定止盈
take_profit_price = entry_price * (1 - self.take_profit_pct)
if current_price <= take_profit_price:
should_exit = True
exit_reason = "止盈"
exit_price = take_profit_price
# 移动止损
trailing_stop_price = current_position['lowest_price'] * (1 + self.trailing_stop_pct)
if current_price >= trailing_stop_price:
should_exit = True
exit_reason = "移动止损"
exit_price = trailing_stop_price
# ========== 反向信号检查 ==========
if signal and (
(signal == "dead" and pos_dir == "long") or
(signal == "golden" and pos_dir == "short")
):
should_exit = True
exit_reason = "反向信号"
# 反向信号用下一根开盘价平仓
if idx + 1 < len(data):
exit_price = float(data[idx + 1]['open'])
# ========== 执行平仓 ==========
if should_exit:
# 计算盈亏
if pos_dir == 'long':
diff = exit_price - entry_price
else: # short
diff = entry_price - exit_price
# 记录交易
trade = {
'entry_time': datetime.datetime.fromtimestamp(current_position['entry_time']),
'exit_time': datetime.datetime.fromtimestamp(current_bar['id']),
'signal': self.stats[signal_key]['name'],
'direction': '做多' if pos_dir == 'long' else '做空',
'entry': entry_price,
'exit': exit_price,
'diff': diff,
'exit_reason': exit_reason,
'holding_bars': idx - current_position['entry_idx'] + 1,
}
trades.append(trade)
# 更新统计
self.stats[signal_key]['total_profit'] += diff
if diff > 0:
self.stats[signal_key]['wins'] += 1
# 平仓
current_position = None
# 如果是因为反向信号平仓,立即反手开仓
if exit_reason == "反向信号" and signal and idx + 1 < len(data):
if signal == "golden": # 反手做多
current_position = {
'direction': 'long',
'entry_price': exit_price, # 同价反手
'entry_time': data[idx + 1]['id'],
'entry_idx': idx + 1,
'signal': 'golden_cross',
'highest_price': exit_price,
'lowest_price': exit_price,
}
self.stats['golden_cross']['count'] += 1
elif signal == "dead": # 反手做空
current_position = {
'direction': 'short',
'entry_price': exit_price,
'entry_time': data[idx + 1]['id'],
'entry_idx': idx + 1,
'signal': 'dead_cross',
'highest_price': exit_price,
'lowest_price': exit_price,
}
self.stats['dead_cross']['count'] += 1
# 跳过下一根K线
continue
# ========== 尾仓处理 ==========
if current_position:
last_bar = data[-1]
exit_price = float(last_bar['close'])
pos_dir = current_position['direction']
entry_price = current_position['entry_price']
signal_key = current_position['signal']
diff = (exit_price - entry_price) if pos_dir == 'long' else (entry_price - exit_price)
trade = {
'entry_time': datetime.datetime.fromtimestamp(current_position['entry_time']),
'exit_time': datetime.datetime.fromtimestamp(last_bar['id']),
'signal': self.stats[signal_key]['name'],
'direction': '做多' if pos_dir == 'long' else '做空',
'entry': entry_price,
'exit': exit_price,
'diff': diff,
'exit_reason': "尾仓平仓",
'holding_bars': len(data) - current_position['entry_idx'],
}
trades.append(trade)
self.stats[signal_key]['total_profit'] += diff
if diff > 0:
self.stats[signal_key]['wins'] += 1
return trades, self.stats
# ========================= 可视化分析 =========================
def analyze_trades(trades: List[Dict], stats: Dict) -> Dict:
"""深入分析交易结果"""
if not trades:
return {}
# 基础统计
total_trades = len(trades)
winning_trades = [t for t in trades if t['diff'] > 0]
losing_trades = [t for t in trades if t['diff'] <= 0]
win_rate = len(winning_trades) / total_trades * 100 if total_trades > 0 else 0
# 盈亏统计
total_profit = sum(t['diff'] for t in trades)
avg_profit_per_trade = total_profit / total_trades if total_trades > 0 else 0
# 胜率相关
avg_win = np.mean([t['diff'] for t in winning_trades]) if winning_trades else 0
avg_loss = np.mean([t['diff'] for t in losing_trades]) if losing_trades else 0
# 盈亏比
profit_factor = abs(avg_win / avg_loss) if avg_loss != 0 else float('inf')
# 最大连续盈利/亏损
max_consecutive_wins = 0
max_consecutive_losses = 0
current_wins = 0
current_losses = 0
for trade in trades:
if trade['diff'] > 0:
current_wins += 1
current_losses = 0
max_consecutive_wins = max(max_consecutive_wins, current_wins)
else:
current_losses += 1
current_wins = 0
max_consecutive_losses = max(max_consecutive_losses, current_losses)
# 持仓时间分析
holding_bars = [t.get('holding_bars', 0) for t in trades]
avg_holding_bars = np.mean(holding_bars) if holding_bars else 0
# 按平仓原因分析
exit_reasons = {}
for trade in trades:
reason = trade.get('exit_reason', '未知')
exit_reasons[reason] = exit_reasons.get(reason, 0) + 1
return {
'total_trades': total_trades,
'win_rate': win_rate,
'total_profit': total_profit,
'avg_profit_per_trade': avg_profit_per_trade,
'avg_win': avg_win,
'avg_loss': avg_loss,
'profit_factor': profit_factor,
'max_consecutive_wins': max_consecutive_wins,
'max_consecutive_losses': max_consecutive_losses,
'avg_holding_bars': avg_holding_bars,
'exit_reasons': exit_reasons,
'winning_trades_count': len(winning_trades),
'losing_trades_count': len(losing_trades),
}
# ========================= 主程序 =========================
if __name__ == '__main__':
# 从CSV文件读取数据
csv_file = "kline_3.csv" # 请替换为你的CSV文件路径
read_data = []
try:
with open(csv_file, 'r') as csvfile:
reader = csv.DictReader(csvfile)
for row in reader:
read_data.append({
'id': int(row['id']),
'open': float(row['open']),
'high': float(row['high']),
'low': float(row['low']),
'close': float(row['close'])
})
print(f"成功读取 {len(read_data)} 条K线数据")
except FileNotFoundError:
print(f"文件 {csv_file} 未找到,请检查路径")
exit(1)
except Exception as e:
print(f"读取CSV文件时出错: {e}")
exit(1)
# 按时间排序
read_data.sort(key=lambda x: x['id'])
print("\n" + "=" * 60)
print("EMA交叉策略回测系统")
print("=" * 60)
# 策略参数选择
print("\n请选择EMA策略类型")
print("1. 双EMA交叉策略 (默认: EMA12/EMA26)")
print("2. 三EMA排列策略 (默认: EMA7/EMA14/EMA30)")
print("3. EMA+MACD双重确认策略")
choice = input("\n请输入选择 (1-3默认1): ").strip()
if choice == "2":
# 三EMA策略
backtester = EMABacktester(
fast_period=7,
slow_period=30,
use_triple_ema=True,
mid_period=14,
use_macd_confirmation=False,
stop_loss_pct=0.01, # 1.5%止损
take_profit_pct=0.4, # 4%止盈
trailing_stop_pct=0.04 # 2%移动止损
)
strategy_name = "三EMA排列策略(7/14/30)"
elif choice == "3":
# EMA+MACD策略
backtester = EMABacktester(
fast_period=12,
slow_period=26,
signal_period=9,
use_triple_ema=False,
use_macd_confirmation=True,
stop_loss_pct=0.1, # 1%止损
take_profit_pct=0.4, # 3%止盈
trailing_stop_pct=0.04 # 1.5%移动止损
)
strategy_name = "EMA+MACD双重确认策略(12/26/9)"
else:
# 默认双EMA策略
backtester = EMABacktester(
fast_period=12,
slow_period=26,
use_triple_ema=False,
use_macd_confirmation=False,
stop_loss_pct=0.02, # 2%止损
take_profit_pct=0.05, # 5%止盈
trailing_stop_pct=0.03 # 3%移动止损
)
strategy_name = "双EMA交叉策略(12/26)"
print(f"\n使用策略: {strategy_name}")
print("开始回测...")
# 执行回测
trades, stats = backtester.backtest(read_data)
# ========== 交易详情和盈利计算 ==========
print(f"\n{'=' * 60}")
print(f"回测结果 - {strategy_name}")
print(f"{'=' * 60}")
# 参数设定
contract_size = 10000 # 合约规模
open_fee_fixed = 5 # 固定开仓手续费
close_fee_rate = 0.0005 # 平仓手续费率
total_points_profit = 0 # 累计点差
total_money_profit = 0 # 累计金额盈利
total_fee = 0 # 累计手续费
print(f"\n交易详情 (共{len(trades)}笔):")
print("-" * 100)
for i, t in enumerate(trades, 1):
entry = t['entry']
exit = t['exit']
direction = t['direction']
# 原始价差
point_diff = (exit - entry) if direction == '做多' else (entry - exit)
# 金额盈利
money_profit = point_diff / entry * contract_size
# 手续费
fee = open_fee_fixed + (contract_size / entry * exit * close_fee_rate)
# 净利润
net_profit = money_profit - fee
# 保存结果
t.update({
'point_diff': point_diff,
'raw_profit': money_profit,
'fee': fee,
'net_profit': net_profit
})
total_points_profit += point_diff
total_money_profit += money_profit
total_fee += fee
# 输出交易详情
profit_color = "\033[92m" if net_profit > 0 else "\033[91m"
reset_color = "\033[0m"
print(f"{i:3d}. {t['entry_time'].strftime('%Y-%m-%d %H:%M')} -> "
f"{t['exit_time'].strftime('%Y-%m-%d %H:%M')} "
f"{direction}({t['signal']}) "
f"入={entry:.2f} 出={exit:.2f} "
f"{profit_color}净利={net_profit:+.2f}{reset_color} "
f"(持有:{t.get('holding_bars', '?')}根K线, 原因:{t.get('exit_reason', '未知')})")
# ========== 汇总统计 ==========
total_net_profit = total_money_profit - total_fee
print(f"\n{'=' * 60}")
print("汇总统计")
print(f"{'=' * 60}")
print(f"总交易笔数: {len(trades)}")
print(f"总点差: {total_points_profit:.2f}")
print(f"总原始盈利(未扣费): {total_money_profit:.2f}")
print(f"总手续费: {total_fee:.2f}")
print(f"总净利润: {total_net_profit:.2f}")
# 深入分析
analysis = analyze_trades(trades, stats)
if analysis:
print(f"\n策略分析:")
print(f"- 胜率: {analysis['win_rate']:.2f}%")
print(f"- 平均每笔盈利: {analysis['avg_profit_per_trade']:.2f}")
print(f"- 平均盈利: {analysis['avg_win']:.2f}")
print(f"- 平均亏损: {analysis['avg_loss']:.2f}")
print(f"- 盈亏比: {analysis['profit_factor']:.2f}")
print(f"- 最大连续盈利: {analysis['max_consecutive_wins']}")
print(f"- 最大连续亏损: {analysis['max_consecutive_losses']}")
print(f"- 平均持仓K线数: {analysis['avg_holding_bars']:.1f}")
print(f"\n平仓原因统计:")
for reason, count in analysis['exit_reasons'].items():
percentage = count / len(trades) * 100
print(f" - {reason}: {count} 笔 ({percentage:.1f}%)")
# ========== 信号统计 ==========
print(f"\n{'=' * 60}")
print("信号统计")
print(f"{'=' * 60}")
for k, v in stats.items():
name, count, wins, total_p = v['name'], v['count'], v['wins'], v['total_profit']
if count > 0:
win_rate = (wins / count * 100)
avg_p = total_p / count
profit_color = "\033[92m" if total_p > 0 else "\033[91m"
reset_color = "\033[0m"
print(f"{name}:")
print(f" 信号次数: {count}")
print(f" 胜率: {win_rate:.2f}%")
print(f" 总价差: {profit_color}{total_p:.2f}{reset_color}")
print(f" 平均价差: {avg_p:.2f}")
# ========== 风险指标 ==========
if len(trades) > 1:
returns = [t['net_profit'] for t in trades]
# 夏普比率(简化版)
avg_return = np.mean(returns)
std_return = np.std(returns)
sharpe_ratio = avg_return / std_return if std_return > 0 else 0
# 最大回撤
cumulative_returns = np.cumsum(returns)
running_max = np.maximum.accumulate(cumulative_returns)
drawdown = cumulative_returns - running_max
max_drawdown = abs(np.min(drawdown)) if len(drawdown) > 0 else 0
print(f"\n{'=' * 60}")
print("风险指标")
print(f"{'=' * 60}")
print(f"夏普比率(简化): {sharpe_ratio:.4f}")
print(f"最大回撤: {max_drawdown:.2f}")
# 盈亏分布
print(f"\n盈亏分布:")
profit_ranges = {
"大亏 (< -100)": len([r for r in returns if r < -100]),
"中亏 (-100 ~ -50)": len([r for r in returns if -100 <= r < -50]),
"小亏 (-50 ~ 0)": len([r for r in returns if -50 <= r < 0]),
"小盈 (0 ~ 50)": len([r for r in returns if 0 <= r < 50]),
"中盈 (50 ~ 100)": len([r for r in returns if 50 <= r < 100]),
"大盈 (> 100)": len([r for r in returns if r >= 100]),
}
for range_name, count in profit_ranges.items():
if count > 0:
percentage = count / len(returns) * 100
print(f" {range_name}: {count} 笔 ({percentage:.1f}%)")
print(f"\n{'=' * 60}")
print("回测完成!")
print(f"{'=' * 60}")

View File

@@ -0,0 +1,133 @@
import csv
from datetime import datetime, timezone
import matplotlib.pyplot as plt
# ---------------- 配置 ----------------
CSV_FILE = "kline_3.csv" # CSV 文件路径
LEVERAGE = 100
CAPITAL = 10000
POSITION_RATIO = 0.01
FEE_RATE = 0.0005
# ---------------- 读取 CSV ----------------
data = []
with open(CSV_FILE, 'r') as f:
reader = csv.DictReader(f)
for row in reader:
ts = int(row['id'])
dt = datetime.fromtimestamp(ts, tz=timezone.utc)
if dt.year == 2025 and dt.month == 1:
data.append({
'time': dt,
'open': float(row['open']),
'high': float(row['high']),
'low': float(row['low']),
'close': float(row['close']),
})
# ---------------- 策略回测 ----------------
total_profit = 0
total_fee = 0
trades = [] # 保存每笔交易详情
for i in range(len(data) - 1):
k = data[i]
k_next = data[i + 1]
body = abs(k['close'] - k['open'])
upper_shadow = k['high'] - max(k['close'], k['open'])
lower_shadow = min(k['close'], k['open']) - k['low']
position_usdt = CAPITAL * POSITION_RATIO
leveraged_position = position_usdt * LEVERAGE
# ---------------- 锤子线 → 做多 ----------------
if body != 0 and lower_shadow >= 2 * body:
profit_raw = (k_next['close'] - k['close']) / k['close'] * leveraged_position
fee_open = leveraged_position * FEE_RATE
fee_close = leveraged_position * FEE_RATE
profit_net = profit_raw - fee_open - fee_close
total_profit += profit_raw
total_fee += fee_open + fee_close
trades.append({
'方向': '',
'开仓时间': k['time'].strftime("%Y-%m-%d %H:%M"),
'开仓价格': k['close'],
'平仓时间': k_next['time'].strftime("%Y-%m-%d %H:%M"),
'平仓价格': k_next['close'],
'本金': position_usdt,
'杠杆仓位': leveraged_position,
'开仓手续费': fee_open,
'平仓手续费': fee_close,
'原始盈亏': profit_raw,
'净盈亏': profit_net
})
# ---------------- 上吊线 → 做空 ----------------
elif body != 0 and upper_shadow >= 2 * body:
profit_raw = (k['close'] - k_next['close']) / k['close'] * leveraged_position
fee_open = leveraged_position * FEE_RATE
fee_close = leveraged_position * FEE_RATE
profit_net = profit_raw - fee_open - fee_close
total_profit += profit_raw
total_fee += fee_open + fee_close
trades.append({
'方向': '',
'开仓时间': k['time'].strftime("%Y-%m-%d %H:%M"),
'开仓价格': k['close'],
'平仓时间': k_next['time'].strftime("%Y-%m-%d %H:%M"),
'平仓价格': k_next['close'],
'本金': position_usdt,
'杠杆仓位': leveraged_position,
'开仓手续费': fee_open,
'平仓手续费': fee_close,
'原始盈亏': profit_raw,
'净盈亏': profit_net
})
# ---------------- 输出统计 ----------------
print(f"1月总原始盈亏: {total_profit:.2f} USDT")
print(f"1月总手续费: {total_fee:.2f} USDT")
print(f"1月净盈亏: {total_profit - total_fee:.2f} USDT")
print("\n交易明细前10笔示例:")
n = 0
for t in trades:
if t['原始盈亏'] > 10 or t['原始盈亏'] < -10:
print(f"{t['方向']}仓 | 开仓: {t['开仓时间']} @ {t['开仓价格']:.2f} | "
f"平仓: {t['平仓时间']} @ {t['平仓价格']:.2f} | "
f"本金: {t['本金']:.2f} | 杠杆仓位: {t['杠杆仓位']:.2f} | "
f"开仓手续费: {t['开仓手续费']:.2f} | 平仓手续费: {t['平仓手续费']:.2f} | "
f"原始盈亏: {t['原始盈亏']:.2f} | 净盈亏: {t['净盈亏']:.2f}")
n += t['原始盈亏']
print(n)
# # ---------------- 绘制 K 线图 + 交易点 ----------------
# times = [k['time'] for k in data]
# closes = [k['close'] for k in data]
#
# plt.figure(figsize=(16,6))
# plt.plot(times, closes, color='black', label='ETH 收盘价')
#
# for t in trades:
# open_time = datetime.strptime(t['开仓时间'], "%Y-%m-%d %H:%M")
# close_time = datetime.strptime(t['平仓时间'], "%Y-%m-%d %H:%M")
# if t['方向'] == '多':
# plt.scatter(open_time, t['开仓价格'], color='green', marker='^', s=100, label='多开' if '多开' not in plt.gca().get_legend_handles_labels()[1] else "")
# plt.scatter(close_time, t['平仓价格'], color='red', marker='v', s=100, label='多平' if '多平' not in plt.gca().get_legend_handles_labels()[1] else "")
# else:
# plt.scatter(open_time, t['开仓价格'], color='red', marker='v', s=100, label='空开' if '空开' not in plt.gca().get_legend_handles_labels()[1] else "")
# plt.scatter(close_time, t['平仓价格'], color='green', marker='^', s=100, label='空平' if '空平' not in plt.gca().get_legend_handles_labels()[1] else "")
#
# plt.xlabel('时间')
# plt.ylabel('价格(USDT)')
# plt.title('ETH 永续合约 1 月交易回测100倍杠杆')
# plt.legend()
# plt.grid(True)
# plt.gcf().autofmt_xdate()
# plt.show()

View File

@@ -1,7 +1,7 @@
from peewee import *
# 连接到 SQLite 数据库,如果文件不存在会自动创建
db = SqliteDatabase(r'E:\新建文件夹\lm_job\models\database.db')
db = SqliteDatabase(r'database.db')
import pymysql

View File

@@ -61,11 +61,11 @@ class Weex30Copy(Model):
table_name = 'weex_30_copy1'
# # 连接到数据库
# db.connect()
# 连接到数据库
db.connect()
#
# # 创建表(如果表不存在)
# db.create_tables([Weex15])
# db.create_tables([Weex1])
db.create_tables([Weex30])

321
test.py
View File

@@ -1,33 +1,300 @@
import requests
import time
import uuid
import datetime
cookies = {
'PHPSESSID': '8e48c4dd-69ef-561c-3082-e20564b88806',
'ipsb': '3oMlNJzDmbhOQ7wtF96V451PWTxSvKfE',
}
from tqdm import tqdm
from loguru import logger
headers = {
'accept': '*/*',
'accept-language': 'zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6',
'cache-control': 'no-cache',
'dnt': '1',
'pragma': 'no-cache',
'referer': 'https://ip.sb/',
'sec-ch-ua': '"Chromium";v="142", "Microsoft Edge";v="142", "Not_A Brand";v="99"',
'sec-ch-ua-mobile': '?0',
'sec-ch-ua-platform': '"Windows"',
'sec-fetch-dest': 'script',
'sec-fetch-mode': 'no-cors',
'sec-fetch-site': 'same-site',
'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/142.0.0.0 Safari/537.36 Edg/142.0.0.0',
# 'cookie': 'PHPSESSID=8e48c4dd-69ef-561c-3082-e20564b88806; ipsb=3oMlNJzDmbhOQ7wtF96V451PWTxSvKfE',
}
from bitmart.api_contract import APIContract
from bitmart.lib.cloud_exceptions import APIException
proxies = {
'http': f'http://104.168.59.92:24016',
'https': f'http://104.168.59.92:24016',
}
from 交易.tools import send_dingtalk_message
response = requests.get('https://ipv4.ip.sb/addrinfo', cookies=cookies, headers=headers, proxies=proxies)
print(f"{response.json()}")
class BitmartMarketMaker:
def __init__(self):
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.open_avg_price = None # 开仓均价字符串或float
self.current_amount = 0
self.position_cross = None
self.pbar = tqdm(total=10, desc="等待下次检查", ncols=80)
self.leverage = "10" # 低杠杆,安全做市
self.open_type = "cross"
self.fixed_size = 1 # 每次挂单量
self.spread_offset = 5.0 # 挂单偏移USDT建议5~10避免立即成交
self.max_position_threshold = 10 # 库存阈值,超过自动平仓
# 新增:止盈止损参数(基于开仓均价的百分比)
self.take_profit_pct = 2.0 # 止盈 2%
self.stop_loss_pct = 1.0 # 止损 1%(可根据风险偏好调整)
self.price_precision = 0.1
self.leverage_set = False
self.max_retries = 5
def ding(self, msg, error=False):
prefix = "❌bitmart MM" if error else "🔔bitmart MM"
if error:
for i in range(10):
send_dingtalk_message(f"{prefix}{msg}")
else:
send_dingtalk_message(f"{prefix}{msg}")
def try_set_leverage(self):
if self.leverage_set:
return True
for attempt in range(self.max_retries):
self.cancel_all_orders()
time.sleep(2)
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 杠杆设置成功")
self.leverage_set = True
return True
else:
logger.error(f"杠杆设置失败 (尝试 {attempt+1}): {response}")
except Exception as e:
logger.error(f"设置杠杆异常 (尝试 {attempt+1}): {e}")
time.sleep(10)
self.ding(error=True, msg="杠杆设置多次失败,请手动检查")
return False
def get_depth(self):
try:
response = self.contractAPI.get_depth(contract_symbol=self.contract_symbol)[0]
if response['code'] == 1000:
data = response['data']
best_bid = float(data['bids'][0][0]) if data['bids'] else None
best_ask = float(data['asks'][0][0]) if data['asks'] else None
return best_bid, best_ask
else:
logger.error(f"获取深度失败: {response}")
return None, None
except Exception as e:
logger.error(f"获取深度异常: {e}")
return None, None
def cancel_all_orders(self):
try:
response = self.contractAPI.post_cancel_orders(contract_symbol=self.contract_symbol)[0]
if response['code'] == 1000:
logger.success("所有挂单已取消")
return True
else:
logger.error(f"取消挂单失败: {response}")
return False
except Exception as e:
logger.warning(f"取消挂单异常(忽略): {e}")
return False
def place_limit_order(self, side: int, price: float, size: int):
price = round(price / self.price_precision) * self.price_precision
price_str = f"{price:.1f}"
client_order_id = f"mm_{int(time.time())}_{uuid.uuid4().hex[:8]}"
for attempt in range(self.max_retries):
try:
response = self.contractAPI.post_submit_order(
contract_symbol=self.contract_symbol,
client_order_id=client_order_id,
side=side,
mode=1,
type='limit',
leverage=self.leverage,
open_type=self.open_type,
price=price_str,
size=size
)[0]
if response['code'] == 1000:
action = "挂买(开多)" if side == 1 else "挂卖(开空)"
logger.success(f"限价单挂单成功: {action}, 价格={price}, 张数={size}")
return True
else:
logger.error(f"挂单失败 (尝试 {attempt+1}): {response}")
except APIException as e:
logger.error(f"API挂单异常 (尝试 {attempt+1}): {e}")
time.sleep(5)
return False
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
self.current_amount = 0
self.open_avg_price = None
return True
position = positions[0]
self.start = 1 if position['position_type'] == 1 else -1
self.open_avg_price = float(position['open_avg_price']) if position['open_avg_price'] else 0.0
self.current_amount = int(float(position.get('current_amount', 0)))
self.position_cross = position.get("position_cross")
return True
else:
return False
except Exception as e:
logger.error(f"持仓查询异常: {e}")
self.ding(error=True, msg="持仓查询异常")
return False
def get_available_balance(self):
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 0.0
except Exception as e:
logger.error(f"余额查询异常: {e}")
return 0.0
def check_take_profit_stop_loss(self, current_price: float):
"""检查是否触发止盈或止损(基于当前价格)"""
if self.current_amount == 0 or self.open_avg_price == 0.0:
return False, None
if self.start == 1: # 多头
pnl_pct = (current_price - self.open_avg_price) / self.open_avg_price * 100
if pnl_pct >= self.take_profit_pct:
return True, "止盈平多"
if pnl_pct <= -self.stop_loss_pct:
return True, "止损平多"
elif self.start == -1: # 空头
pnl_pct = (self.open_avg_price - current_price) / self.open_avg_price * 100
if pnl_pct >= self.take_profit_pct:
return True, "止盈平空"
if pnl_pct <= -self.stop_loss_pct:
return True, "止损平空"
return False, None
def close_position(self, reason: str = "库存阈值"):
"""市场全平仓,并发送通知"""
if self.current_amount == 0:
return
side = 3 if self.start == 1 else 2 # 3: 平多, 2: 平空
try:
response = self.contractAPI.post_submit_order(
contract_symbol=self.contract_symbol,
client_order_id=f"close_{reason}_{int(time.time())}",
side=side,
mode=1,
type='market',
leverage=self.leverage,
open_type=self.open_type,
size=999999
)[0]
if response['code'] == 1000:
direction_str = "" if self.start == 1 else ""
logger.success(f"{reason}平仓成功: 平{direction_str}")
self.ding(msg=f"{reason}触发,已平仓 {direction_str}头仓位")
self.start = 0
self.current_amount = 0
self.open_avg_price = None
return True
else:
logger.error(f"平仓失败: {response}")
return False
except APIException as e:
logger.error(f"API平仓异常: {e}")
return False
def action(self):
logger.info("程序启动,将在循环中动态尝试设置杠杆")
while True:
if not self.try_set_leverage():
logger.warning("杠杆未设置成功,继续重试...")
if not self.get_position_status():
self.ding(error=True, msg="获取仓位信息失败!!!")
time.sleep(10)
continue
# 获取当前价格(使用中价)
best_bid, best_ask = self.get_depth()
if best_bid is None or best_ask is None:
time.sleep(10)
continue
mid_price = (best_bid + best_ask) / 2
# 检查止盈止损
trigger, reason = self.check_take_profit_stop_loss(mid_price)
if trigger:
self.close_position(reason=reason)
# 平仓后继续下一轮(重新挂单)
# 检查库存阈值
if abs(self.current_amount) > self.max_position_threshold:
self.close_position(reason="库存阈值")
# 挂单
bid_price = mid_price - self.spread_offset
ask_price = mid_price + self.spread_offset
self.cancel_all_orders()
success_bid = self.place_limit_order(side=1, price=bid_price, size=self.fixed_size)
success_ask = self.place_limit_order(side=4, price=ask_price, size=self.fixed_size)
# 统计盈亏百分比
pnl_pct_str = ""
if self.current_amount != 0 and self.open_avg_price:
if self.start == 1:
pnl_pct = (mid_price - self.open_avg_price) / self.open_avg_price * 100
else:
pnl_pct = (self.open_avg_price - mid_price) / self.open_avg_price * 100
pnl_pct_str = f"浮动盈亏:{pnl_pct:+.2f}%"
balance = self.get_available_balance()
leverage_status = "已设置" if self.leverage_set else "未同步(重试中)"
msg = (
f"【BitMart {self.contract_symbol} MM】\n"
f"杠杆状态:{leverage_status}\n"
f"当前中价:{mid_price:.2f} USDT\n"
f"挂买价:{bid_price:.2f} ({'成功' if success_bid else '失败'})\n"
f"挂卖价:{ask_price:.2f} ({'成功' if success_ask else '失败'})\n"
f"持仓量:{self.current_amount}{pnl_pct_str}\n"
f"账户可用余额:{balance:.2f} USDT\n"
f"止盈:+{self.take_profit_pct}% | 止损:-{self.stop_loss_pct}%"
)
self.ding(msg=msg)
self.pbar.reset()
time.sleep(10)
if __name__ == '__main__':
BitmartMarketMaker().action()

113
test1.py
View File

@@ -1,29 +1,92 @@
import requests
import csv
from datetime import datetime, timezone
import matplotlib.pyplot as plt
headers = {
'accept': 'application/json, text/plain, */*',
'accept-language': 'zh-CN,zh;q=0.9',
'cache-control': 'no-cache',
'origin': 'https://www.websea.com',
'pragma': 'no-cache',
'priority': 'u=1, i',
'referer': 'https://www.websea.com/',
'sec-ch-ua': '"Not;A=Brand";v="99", "Google Chrome";v="139", "Chromium";v="139"',
'sec-ch-ua-mobile': '?0',
'sec-ch-ua-platform': '"Windows"',
'sec-fetch-dest': 'empty',
'sec-fetch-mode': 'cors',
'sec-fetch-site': 'same-site',
'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/139.0.0.0 Safari/537.36',
}
# ---------------- 配置 ----------------
CSV_FILE = "bitmart/kline_3.csv" # 你的 CSV 文件路径
LEVERAGE = 100
CAPITAL = 10000
POSITION_RATIO = 0.01
FEE_RATE = 0.00015
params = {
'symbol': 'ETH-USDT',
'period': '30min',
'start': '1763480074',
'end': '1766072134',
}
# ---------------- 读取 CSV ----------------
data = []
with open(CSV_FILE, 'r') as f:
reader = csv.DictReader(f)
for row in reader:
ts = int(row['id'])
dt = datetime.fromtimestamp(ts, tz=timezone.utc)
if dt.year == 2025 and dt.month == 1:
data.append({
'time': dt,
'open': float(row['open']),
'high': float(row['high']),
'low': float(row['low']),
'close': float(row['close']),
})
response = requests.get('https://capi.websea.com/webApi/market/getKline', params=params, headers=headers)
# ---------------- 识别交易信号 ----------------
trades = []
position_usdt = CAPITAL * POSITION_RATIO
leveraged_position = position_usdt * LEVERAGE
print(response.json())
for i in range(len(data)-1):
k1 = data[i]
k2 = data[i+1]
# 刺透形态Piercing Line多头
if k1['close'] < k1['open'] and k2['close'] > k2['open']:
midpoint = (k1['open'] + k1['close']) / 2
if k2['open'] < k1['close'] and k2['close'] > midpoint:
trades.append({
'方向': '',
'开仓时间': k2['time'],
'开仓价格': k2['open'],
'平仓时间': k2['time'],
'平仓价格': k2['close']
})
# 乌云盖顶Dark Cloud Cover空头
elif k1['close'] > k1['open'] and k2['close'] < k2['open']:
midpoint = (k1['open'] + k1['close']) / 2
if k2['open'] > k1['close'] and k2['close'] < midpoint:
trades.append({
'方向': '',
'开仓时间': k2['time'],
'开仓价格': k2['open'],
'平仓时间': k2['time'],
'平仓价格': k2['close']
})
# ---------------- 绘制 K 线图 ----------------
plt.figure(figsize=(16,6))
times = [k['time'] for k in data]
opens = [k['open'] for k in data]
closes = [k['close'] for k in data]
highs = [k['high'] for k in data]
lows = [k['low'] for k in data]
# 绘制 K 线(用竖线表示最高最低价,用矩形表示开收盘价)
for i in range(len(data)):
color = 'green' if closes[i] >= opens[i] else 'red'
plt.plot([times[i], times[i]], [lows[i], highs[i]], color='black') # 高低价
plt.plot([times[i]-0.0005, times[i]+0.0005], [opens[i], opens[i]], color=color, linewidth=5) # 开盘价
plt.plot([times[i]-0.0005, times[i]+0.0005], [closes[i], closes[i]], color=color, linewidth=5) # 收盘价
# ---------------- 标注交易信号 ----------------
for t in trades:
if t['方向'] == '':
plt.scatter(t['开仓时间'], t['开仓价格'], color='green', marker='^', s=100, label='多开' if '多开' not in plt.gca().get_legend_handles_labels()[1] else "")
plt.scatter(t['平仓时间'], t['平仓价格'], color='red', marker='v', s=100, label='多平' if '多平' not in plt.gca().get_legend_handles_labels()[1] else "")
else:
plt.scatter(t['开仓时间'], t['开仓价格'], color='red', marker='v', s=100, label='空开' if '空开' not in plt.gca().get_legend_handles_labels()[1] else "")
plt.scatter(t['平仓时间'], t['平仓价格'], color='green', marker='^', s=100, label='空平' if '空平' not in plt.gca().get_legend_handles_labels()[1] else "")
plt.xlabel('时间')
plt.ylabel('价格(USDT)')
plt.title('ETH 永续合约 1 月交易回测(刺透 & 乌云形态)')
plt.legend()
plt.grid(True)
plt.gcf().autofmt_xdate()
plt.show()

187
test2.py
View File

@@ -1,144 +1,63 @@
import time
import random
import oss2
import os
import uuid
from pathlib import Path
import datetime
from loguru import *
from curl_cffi import requests
def upload_file_to_oss(local_file_path):
# 建议使用环境变量存储敏感信息,这里为了示例方便,假设已通过环境变量设置
endpoint = 'https://oss-cn-beijing.aliyuncs.com'
access_key_id = "LTAI5tRMxrM95Pi8JEEmqRcg"
access_key_secret = "8vueGCsRVeFyQMcAA7sysO7LSnuJDG"
from models.combined_table import CombinedTable
from models.concrete_wallet import ConcreteWallet
from models.ips import Ips
from tools import get_evm_wallet_singed_message
if not access_key_id or not access_key_secret:
print('❌ 错误: 未找到有效的 OSS 访问密钥,请检查环境变量。')
return None
# 生成唯一 Bucket 名称
bucket_name = f'oss-bucket-yj'
print(f"创建 Bucket: {bucket_name}")
class Credentials:
def __init__(self, sql_info):
# 初始化 Bucket 对象
auth = oss2.Auth(access_key_id, access_key_secret)
bucket = oss2.Bucket(auth, endpoint, bucket_name)
self.sql_info = sql_info
try:
# 1. 创建 Bucket
bucket.create_bucket(oss2.models.BUCKET_ACL_PUBLIC_READ) # 设置 Bucket 为公共读权限
print(f'✅ 成功创建 Bucket: {bucket_name}')
except oss2.exceptions.BucketAlreadyExists:
print(f'⚠️ Bucket {bucket_name} 已经存在')
print('提示:请使用不同的 Bucket 名称或使用现有 Bucket')
except oss2.exceptions.OssError as e:
print(f'❌ OSS 错误: {e}')
print(f' 错误码: {e.code}')
print(f' 请求 ID: {e.request_id}')
return None
self.headers = {
'accept': '*/*',
'accept-language': 'zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6',
'cache-control': 'no-cache',
'content-type': 'application/json',
'pragma': 'no-cache',
'priority': 'u=1, i',
'referer': 'https://points.concrete.xyz/home',
'sec-ch-ua': '"Microsoft Edge";v="143", "Chromium";v="143", "Not A(Brand";v="24"',
'sec-ch-ua-mobile': '?0',
'sec-ch-ua-platform': '"Windows"',
'sec-fetch-dest': 'empty',
'sec-fetch-mode': 'cors',
'sec-fetch-site': 'same-origin',
'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.0.0 Safari/537.36 Edg/143.0.0.0',
# 'cookie': 'client-season=z2zi-tzc2; domain=https%3A%2F%2Fpoints.concrete.xyz; __Host-authjs.csrf-token=afc782765e3380ca973101d02f54c9516d317120fe83faea733390bfc2e46cb4%7Cdca5d6028cadbe52f05820005ed29862e6bd8fff72ae5514142c25bf0d0ad97f; __Secure-authjs.callback-url=https%3A%2F%2Fboost.absinthe.network; redirect-pathname=%2Fhome',
}
# 2. 验证本地文件是否存在
if not Path(local_file_path).exists():
print(f'❌ 文件错误: 本地文件 {local_file_path} 不存在')
return None
self.session = requests.Session()
self.session.headers.update(self.headers)
file_name = os.path.basename(local_file_path)
oss_object_name = uuid.uuid4().hex[:12] + file_name
result = bucket.put_object_from_file(oss_object_name, local_file_path)
def csrf(self):
for i in range(3):
try:
response = self.session.get('https://points.concrete.xyz/api/auth/csrf', )
if result.status == 200:
print(f'✅ 文件上传成功')
print(f' ETag: {result.etag}')
print(f' 文件大小: {result.resp.headers.get("content-length")} bytes')
return response.json()["csrfToken"]
except:
time.sleep(random.randint(1, 5))
# 生成长期可访问的 URL
public_url = f'https://{bucket_name}.{endpoint.replace("https://", "")}/{oss_object_name}'
print(f' 文件公共 URL长期有效: {public_url}')
return public_url
else:
print(f'❌ 文件上传失败,状态码: {result.status}')
return None
return False
def credentials(self, message, signature, csrfToken):
params = ''
data = {
'message': message,
'redirect': 'false',
'signature': "0x" + signature,
'csrfToken': csrfToken,
'callbackUrl': 'https://points.concrete.xyz/home',
}
for i in range(3):
try:
response = self.session.post(
'https://points.concrete.xyz/api/auth/callback/credentials',
params=params,
data=data,
)
print(response.json())
return True
except:
time.sleep(random.randint(1, 5))
return False
def add_ips_to_combined_table(self, ):
# 获取 ips 表中的所有记录
all_ips_records = Ips.select()
for ips_record in all_ips_records:
# 检查该记录是否已经存在于 CombinedTable 中
existing_record = CombinedTable.get_or_none(
CombinedTable.host == ips_record.host,
CombinedTable.port == ips_record.port,
CombinedTable.username == ips_record.username,
CombinedTable.pwd == ips_record.pwd
)
if not existing_record:
# 如果记录不存在,则将其添加到 CombinedTable 中
# 这里假设 mnemonic 字段有默认值或者你可以根据需求传入具体值
self.com_info.host = ips_record.host
self.com_info.port = ips_record.port
self.com_info.username = ips_record.username
self.com_info.pwd = ips_record.pwd
self.com_info.save()
print("成功添加记录到 CombinedTable。")
return True
print("ips 表中所有数据在 CombinedTable 中都已存在,结束操作。")
return False
def action(self):
self.com_info, type1 = CombinedTable.get_or_create(
mnemonic=self.sql_info.mnemonic,
)
if not self.com_info.host:
if self.add_ips_to_combined_table():
logger.info("添加ip成功")
else:
logger.error("没有ip了")
return
proxies = {
'http': f'socks5://{self.com_info.username}:{self.com_info.pwd}@{self.com_info.host}:{self.com_info.port}',
'https': f'socks5://{self.com_info.username}:{self.com_info.pwd}@{self.com_info.host}:{self.com_info.port}',
}
self.session.proxies.update(proxies)
csrf_token = self.csrf()
if csrf_token:
logger.info("获取签名信息成功!!!")
else:
logger.error("获取签名信息失败!!!")
# 获取当前 UTC 时间
current_utc_time = datetime.datetime.now(datetime.timezone.utc)
original_msg = f"points.concrete.xyz wants you to sign in with your Ethereum account:\n{self.sql_info.address}\n\nPlease sign with your account\n\nURI: https://points.concrete.xyz\nVersion: 1\nChain ID: 1\nNonce: {csrf_token}\nIssued At: {current_utc_time.strftime("%Y-%m-%dT%H:%M:%S.%f")[:-3] + 'Z'}\nResources:\n- connector://metaMask"
signature = get_evm_wallet_singed_message(
original_msg=original_msg,
private_key=self.sql_info.private_key,
)
self.credentials(message=original_msg, signature=signature, csrfToken=csrf_token)
if __name__ == '__main__':
for concrete_info in ConcreteWallet.select():
credentials = Credentials(sql_info=concrete_info)
credentials.action()
# 示例调用
local_file = r'C:\Users\27942\Desktop\codes\lm_code\test.py'
signed_url = upload_file_to_oss(local_file)
if signed_url:
print(f"可访问文件的 URL: {signed_url}")

View File

@@ -738,7 +738,7 @@ def run_work(x_token_info, xstart_info):
pass
# hub.to_do_tui() # 发推
hub.回复() # 发推
# hub.回复() # 发推
# hub.FollowTwitterAccount() # 关注
@@ -762,7 +762,7 @@ if __name__ == '__main__':
# time.sleep(random.randint(15, 60))
# 同时运行
max_threads = 8
max_threads = 10
delay_between_start = 15 # 每次启动线程之间的延迟时间(秒)
with ThreadPoolExecutor(max_workers=max_threads) as executor: