加入一个回测,

This commit is contained in:
ddrwode
2026-03-05 12:56:09 +08:00
parent 01b6a0fdcb
commit 5530a008b3
2 changed files with 610 additions and 93 deletions

View File

@@ -1,39 +1,39 @@
"""
布林带延迟反转策略 - 实盘交易
基于回测策略 bb_backtest_march_2026.py
使用API查询 + 浏览器自动化交易
使用框架的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
from bitmart.api_contract import APIContract
class BBDelayReversalConfig:
"""策略配置"""
# API凭证
API_KEY = "6104088c65a68d7e53df5d9395b67d78e555293a"
SECRET_KEY = "a8b14312330d8e6b9b09acfd972b34e32022fdfa9f2b06f0a0a31723b873fd01"
MEMO = "me"
# 浏览器ID从框架获取
BIT_ID = "f2320f57e24c45529a009e1541e25961"
# API凭证从框架获取
API_KEY = "a0fb7b98464fd9bcce67e7c519d58ec10d0c38a8"
SECRET_KEY = "4eaeba78e77aeaab1c2027f846a276d164f264a44c2c1bb1c5f3be50c8de1ca5"
MEMO = "合约交易"
# 合约
CONTRACT_SYMBOL = "ETHUSDT"
TRADE_URL = "https://derivatives.bitmart.com/zh-CN/futures/ETHUSDT"
# 浏览器ID
BIT_ID = "62f9107d0c674925972084e282df55b3"
# 布林带参数
BB_PERIOD = 10
BB_STD = 2.5
# 仓位管理
LEVERAGE = 50
OPEN_TYPE = "isolated"
# 仓位管理(从框架获取)
LEVERAGE = "50"
OPEN_TYPE = "isolated" # 逐仓模式
MARGIN_PCT = 0.01 # 首次开仓1%
# 运行参数
@@ -45,10 +45,16 @@ class BBDelayReversalConfig:
class BBDelayReversalTrader:
"""布林带延迟反转交易器"""
def __init__(self, cfg: BBDelayReversalConfig = None):
self.cfg = cfg or BBDelayReversalConfig()
self.api = APIContract(
self.cfg.API_KEY, self.cfg.SECRET_KEY, self.cfg.MEMO,
def __init__(self, bit_id: str = None):
self.cfg = BBDelayReversalConfig()
if bit_id:
self.cfg.BIT_ID = bit_id
# API使用框架配置
self.contractAPI = APIContract(
self.cfg.API_KEY,
self.cfg.SECRET_KEY,
self.cfg.MEMO,
timeout=(5, 15)
)
@@ -88,107 +94,104 @@ class BBDelayReversalTrader:
# ========== API查询方法 ==========
def get_klines(self) -> list | None:
"""获取5分钟K线"""
"""获取5分钟K线(使用框架方法)"""
try:
end_time = int(time.time())
start_time = end_time - 3600 * self.cfg.KLINE_HOURS
resp = self.api.get_kline(
response = self.contractAPI.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"]),
)[0]["data"]
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"])
})
klines.sort(key=lambda x: x["id"])
return klines
formatted.sort(key=lambda x: x['id'])
return formatted
except Exception as e:
logger.error(f"获取K线异常: {e}")
return None
def get_current_price(self) -> float | None:
"""获取当前价格"""
"""获取当前价格(使用框架方法)"""
try:
end_time = int(time.time())
resp = self.api.get_kline(
response = self.contractAPI.get_kline(
contract_symbol=self.cfg.CONTRACT_SYMBOL,
step=1,
start_time=end_time - 300,
start_time=end_time - 3600 * 3,
end_time=end_time
)[0]
if resp.get("code") == 1000 and resp["data"]:
return float(resp["data"][-1]["close_price"])
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_balance(self) -> float | None:
"""获取可用余额"""
"""获取可用余额(使用框架方法)"""
try:
resp = self.api.get_assets_detail()[0]
if resp.get("code") == 1000:
data = resp["data"]
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))
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))
if asset.get('currency') == 'USDT':
return float(asset.get('available_balance', 0))
return None
except Exception as e:
logger.error(f"查询余额异常: {e}")
logger.error(f"余额查询异常: {e}")
return None
def get_position_status(self) -> bool:
"""查询持仓状态"""
"""查询持仓状态(使用框架方法)"""
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.position_count = 0
self.entry_price = 0
self.current_amount = 0
response = self.contractAPI.get_position(contract_symbol=self.cfg.CONTRACT_SYMBOL)[0]
if response['code'] == 1000:
positions = response['data']
if not positions:
self.position = 0
self.position_count = 0
self.entry_price = 0
self.current_amount = 0
return True
pos = positions[0]
self.position = 1 if pos['position_type'] == 1 else -1
self.entry_price = float(pos['open_avg_price'])
self.current_amount = float(pos['current_amount'])
logger.debug(f"持仓: {'' if self.position > 0 else ''} | "
f"价格={self.entry_price:.2f} | 数量={self.current_amount:.4f}")
return True
pos = positions[0]
self.position = 1 if pos["position_type"] == 1 else -1
self.entry_price = float(pos["open_avg_price"])
self.current_amount = float(pos["current_amount"])
logger.debug(f"持仓: {'' if self.position > 0 else ''} | "
f"价格={self.entry_price:.2f} | 数量={self.current_amount:.4f}")
return True
else:
return False
except Exception as e:
logger.error(f"查询持仓异常: {e}")
logger.error(f"持仓查询异常: {e}")
return False
def set_leverage(self) -> bool:
"""设置杠杆"""
"""设置杠杆(使用框架方法)"""
try:
resp = self.api.post_submit_leverage(
response = self.contractAPI.post_submit_leverage(
contract_symbol=self.cfg.CONTRACT_SYMBOL,
leverage=str(self.cfg.LEVERAGE),
leverage=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}")
if response['code'] == 1000:
logger.success(f"{self.cfg.OPEN_TYPE}模式 + {self.cfg.LEVERAGE}x 杠杆设置成功")
return True
else:
logger.error(f"杠杆设置失败: {resp}")
logger.error(f"杠杆设置失败: {response}")
return False
except Exception as e:
logger.error(f"设置杠杆异常: {e}")
@@ -210,7 +213,7 @@ class BBDelayReversalTrader:
# ========== 浏览器自动化 ==========
def open_browser(self) -> bool:
"""打开浏览器"""
"""打开浏览器(使用框架方法)"""
try:
bit_port = openBrowser(id=self.cfg.BIT_ID)
co = ChromiumOptions()
@@ -218,39 +221,37 @@ class BBDelayReversalTrader:
self.page = ChromiumPage(addr_or_opts=co)
self.last_page_open_time = time.time()
return True
except Exception as e:
logger.error(f"打开浏览器失败: {e}")
except:
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)
ele.scroll.to_see(center=True)
time.sleep(sleep)
ele.click()
return True
except Exception as e:
logger.warning(f"点击失败: {e}")
except:
return False
def browser_open_position(self, direction: str, usdt_amount: float) -> bool:
"""浏览器开仓"""
"""浏览器开仓(使用框架的开单方法)"""
try:
logger.info(f"浏览器操作: 开{'' if direction == 'long' else ''} {usdt_amount}U")
# 点击市价
self.click_safe('x://button[normalize-space(text()) ="市价"]')
# 输入金额
self.page.ele('x://*[@id="size_0"]').input(vals=usdt_amount, clear=True)
time.sleep(0.5)
# 点击开仓按钮
# 使用框架的开单方法
if direction == 'long':
# 市价做多
self.click_safe('x://button[normalize-space(text()) ="市价"]')
self.page.ele('x://*[@id="size_0"]').input(usdt_amount)
self.click_safe('x://span[normalize-space(text()) ="买入/做多"]')
else:
# 市价做空
self.click_safe('x://button[normalize-space(text()) ="市价"]')
self.page.ele('x://*[@id="size_0"]').input(usdt_amount)
self.click_safe('x://span[normalize-space(text()) ="卖出/做空"]')
time.sleep(2)
@@ -260,22 +261,23 @@ class BBDelayReversalTrader:
return False
def browser_close_position(self, ratio: float = 1.0) -> bool:
"""浏览器平仓"""
"""浏览器平仓(使用框架方法)"""
try:
logger.info(f"浏览器操作: 平仓{int(ratio*100)}%")
if ratio >= 0.99:
# 全平
# 全平(使用框架的全平仓方法)
self.click_safe('x://span[normalize-space(text()) ="市价"]')
else:
# 平半
# 平半(使用框架的平一半方法)
self.click_safe('x://button[normalize-space(text()) ="平仓"]')
if ratio == 0.5:
self.click_safe('x://*[@id="futureTradeForm"]/div[5]/div[3]/div[3]/span[3]')
self.click_safe('x://*[@id="futureTradeForm"]/div[5]/div[3]/div[3]/span[3]')
if self.position > 0:
# 平一半多仓
self.click_safe('x://span[normalize-space(text()) ="卖出/平多"]')
else:
# 平一半空仓
self.click_safe('x://span[normalize-space(text()) ="买入/平空"]')
time.sleep(2)
@@ -597,5 +599,6 @@ class BBDelayReversalTrader:
if __name__ == "__main__":
trader = BBDelayReversalTrader()
# 使用框架的浏览器ID
trader = BBDelayReversalTrader(bit_id="f2320f57e24c45529a009e1541e25961")
trader.run()