optimize ORB strategy
This commit is contained in:
parent
a8a310ecf0
commit
e990db26a6
|
|
@ -73,7 +73,7 @@ OKX_MONITOR_CONFIG = {
|
||||||
US_STOCK_MONITOR_CONFIG = {
|
US_STOCK_MONITOR_CONFIG = {
|
||||||
"volume_monitor":{
|
"volume_monitor":{
|
||||||
"symbols": ["QQQ", "TQQQ", "MSFT", "AAPL", "GOOG", "NVDA", "META", "AMZN", "TSLA", "AVGO"],
|
"symbols": ["QQQ", "TQQQ", "MSFT", "AAPL", "GOOG", "NVDA", "META", "AMZN", "TSLA", "AVGO"],
|
||||||
"bars": ["5m"],
|
"bars": ["5", "15m", "30m", "1H"],
|
||||||
"initial_date": "2015-08-31 00:00:00"
|
"initial_date": "2015-08-31 00:00:00"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Binary file not shown.
Binary file not shown.
|
|
@ -12,7 +12,7 @@ from openpyxl.drawing.image import Image
|
||||||
import openpyxl
|
import openpyxl
|
||||||
from openpyxl.styles import Font
|
from openpyxl.styles import Font
|
||||||
from PIL import Image as PILImage
|
from PIL import Image as PILImage
|
||||||
from config import OKX_MONITOR_CONFIG, MYSQL_CONFIG, WINDOW_SIZE
|
from config import OKX_MONITOR_CONFIG, US_STOCK_MONITOR_CONFIG, MYSQL_CONFIG, WINDOW_SIZE
|
||||||
from core.db.db_market_data import DBMarketData
|
from core.db.db_market_data import DBMarketData
|
||||||
from core.db.db_huge_volume_data import DBHugeVolumeData
|
from core.db.db_huge_volume_data import DBHugeVolumeData
|
||||||
from core.utils import timestamp_to_datetime, transform_date_time_to_timestamp
|
from core.utils import timestamp_to_datetime, transform_date_time_to_timestamp
|
||||||
|
|
@ -32,7 +32,7 @@ class MaBreakStatistics:
|
||||||
之间的涨跌幅
|
之间的涨跌幅
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self, is_us_stock: bool = False):
|
||||||
mysql_user = MYSQL_CONFIG.get("user", "xch")
|
mysql_user = MYSQL_CONFIG.get("user", "xch")
|
||||||
mysql_password = MYSQL_CONFIG.get("password", "")
|
mysql_password = MYSQL_CONFIG.get("password", "")
|
||||||
if not mysql_password:
|
if not mysql_password:
|
||||||
|
|
@ -44,9 +44,19 @@ class MaBreakStatistics:
|
||||||
self.db_url = f"mysql+pymysql://{mysql_user}:{mysql_password}@{mysql_host}:{mysql_port}/{mysql_database}"
|
self.db_url = f"mysql+pymysql://{mysql_user}:{mysql_password}@{mysql_host}:{mysql_port}/{mysql_database}"
|
||||||
self.db_market_data = DBMarketData(self.db_url)
|
self.db_market_data = DBMarketData(self.db_url)
|
||||||
self.db_huge_volume_data = DBHugeVolumeData(self.db_url)
|
self.db_huge_volume_data = DBHugeVolumeData(self.db_url)
|
||||||
|
if is_us_stock:
|
||||||
|
self.symbols = US_STOCK_MONITOR_CONFIG.get("volume_monitor", {}).get(
|
||||||
|
"symbols", ["QQQ"]
|
||||||
|
)
|
||||||
|
else:
|
||||||
self.symbols = OKX_MONITOR_CONFIG.get("volume_monitor", {}).get(
|
self.symbols = OKX_MONITOR_CONFIG.get("volume_monitor", {}).get(
|
||||||
"symbols", ["XCH-USDT"]
|
"symbols", ["XCH-USDT"]
|
||||||
)
|
)
|
||||||
|
if is_us_stock:
|
||||||
|
self.bars = US_STOCK_MONITOR_CONFIG.get("volume_monitor", {}).get(
|
||||||
|
"bars", ["5m"]
|
||||||
|
)
|
||||||
|
else:
|
||||||
self.bars = OKX_MONITOR_CONFIG.get("volume_monitor", {}).get(
|
self.bars = OKX_MONITOR_CONFIG.get("volume_monitor", {}).get(
|
||||||
"bars", ["5m", "15m", "30m", "1H"]
|
"bars", ["5m", "15m", "30m", "1H"]
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import yfinance as yf
|
import yfinance as yf
|
||||||
|
import os
|
||||||
import pandas as pd
|
import pandas as pd
|
||||||
import numpy as np
|
import numpy as np
|
||||||
import matplotlib.pyplot as plt
|
import matplotlib.pyplot as plt
|
||||||
|
|
@ -22,15 +23,28 @@ class ORBStrategy:
|
||||||
max_leverage=4,
|
max_leverage=4,
|
||||||
risk_per_trade=0.01,
|
risk_per_trade=0.01,
|
||||||
commission_per_share=0.0005,
|
commission_per_share=0.0005,
|
||||||
|
is_us_stock=False,
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
初始化ORB策略参数
|
初始化ORB策略参数
|
||||||
|
ORB策略说明:
|
||||||
|
1. 每天仅1次交易机会,多头或空头,排除十字星:open1 == close1
|
||||||
|
2. 第一根5分钟K线:确定开盘区间(High1, Low1)
|
||||||
|
3. 第二根5分钟K线:根据第一根K线方向生成多空信号,open1<close1为多头,open1>close1为空头
|
||||||
|
entry_price=第二根K线开盘价,stop_price=第一根K线最低价(多头)或第一根K线最高价(空头)
|
||||||
|
4. 多头:跌破止损→止损;突破止盈→止盈
|
||||||
|
5. 空头:突破止损→止损;跌破止盈→止盈
|
||||||
|
6. 止损/止盈:根据$R计算,$R=|entry_price-stop_price|
|
||||||
|
7. 盈利目标:10R,即10*$R
|
||||||
|
8. 账户净值曲线:账户价值与市场价格
|
||||||
:param initial_capital: 初始账户资金(美元)
|
:param initial_capital: 初始账户资金(美元)
|
||||||
:param max_leverage: 最大杠杆倍数(默认4倍,符合FINRA规定)
|
:param max_leverage: 最大杠杆倍数(默认4倍,符合FINRA规定)
|
||||||
:param risk_per_trade: 单次交易风险比例(默认1%)
|
:param risk_per_trade: 单次交易风险比例(默认1%)
|
||||||
:param commission_per_share: 每股交易佣金(美元,默认0.0005)
|
:param commission_per_share: 每股交易佣金(美元,默认0.0005)
|
||||||
"""
|
"""
|
||||||
logger.info(f"初始化ORB策略参数:初始账户资金={initial_capital},最大杠杆倍数={max_leverage},单次交易风险比例={risk_per_trade},每股交易佣金={commission_per_share}")
|
logger.info(
|
||||||
|
f"初始化ORB策略参数:初始账户资金={initial_capital},最大杠杆倍数={max_leverage},单次交易风险比例={risk_per_trade},每股交易佣金={commission_per_share}"
|
||||||
|
)
|
||||||
self.initial_capital = initial_capital
|
self.initial_capital = initial_capital
|
||||||
self.max_leverage = max_leverage
|
self.max_leverage = max_leverage
|
||||||
self.risk_per_trade = risk_per_trade
|
self.risk_per_trade = risk_per_trade
|
||||||
|
|
@ -48,6 +62,9 @@ class ORBStrategy:
|
||||||
|
|
||||||
self.db_url = f"mysql+pymysql://{mysql_user}:{mysql_password}@{mysql_host}:{mysql_port}/{mysql_database}"
|
self.db_url = f"mysql+pymysql://{mysql_user}:{mysql_password}@{mysql_host}:{mysql_port}/{mysql_database}"
|
||||||
self.db_market_data = DBMarketData(self.db_url)
|
self.db_market_data = DBMarketData(self.db_url)
|
||||||
|
self.is_us_stock = is_us_stock
|
||||||
|
self.output_chart_folder = r"./output/trade_sandbox/orb_strategy/chart/"
|
||||||
|
os.makedirs(self.output_chart_folder, exist_ok=True)
|
||||||
|
|
||||||
def fetch_intraday_data(self, symbol, start_date, end_date, interval="5m"):
|
def fetch_intraday_data(self, symbol, start_date, end_date, interval="5m"):
|
||||||
"""
|
"""
|
||||||
|
|
@ -72,14 +89,20 @@ class ORBStrategy:
|
||||||
data["Low"] = data["low"]
|
data["Low"] = data["low"]
|
||||||
data["Close"] = data["close"]
|
data["Close"] = data["close"]
|
||||||
data["Volume"] = data["volume"]
|
data["Volume"] = data["volume"]
|
||||||
# 将data["date_time"]从字符串类型转换为日期
|
if self.is_us_stock:
|
||||||
data["date_time"] = pd.to_datetime(data["date_time"])
|
date_time_field = "date_time_us"
|
||||||
|
else:
|
||||||
|
date_time_field = "date_time"
|
||||||
|
data[date_time_field] = pd.to_datetime(data[date_time_field])
|
||||||
# data["Date"]为日期,不包括时分秒,即date_time如果是2025-01-01 10:00:00,则Date为2025-01-01
|
# data["Date"]为日期,不包括时分秒,即date_time如果是2025-01-01 10:00:00,则Date为2025-01-01
|
||||||
data["Date"] = data["date_time"].dt.date
|
data["Date"] = data[date_time_field].dt.date
|
||||||
# 将Date转换为datetime64[ns]类型以确保类型一致
|
# 将Date转换为datetime64[ns]类型以确保类型一致
|
||||||
data["Date"] = pd.to_datetime(data["Date"])
|
data["Date"] = pd.to_datetime(data["Date"])
|
||||||
|
|
||||||
self.data = data[["Date", "date_time", "Open", "High", "Low", "Close", "Volume"]].copy()
|
self.data = data[
|
||||||
|
["symbol", "bar", "Date", date_time_field, "Open", "High", "Low", "Close", "Volume"]
|
||||||
|
].copy()
|
||||||
|
self.data.rename(columns={date_time_field: "date_time"}, inplace=True)
|
||||||
logger.info(f"成功获取{symbol}数据:{len(self.data)}根{interval}K线")
|
logger.info(f"成功获取{symbol}数据:{len(self.data)}根{interval}K线")
|
||||||
|
|
||||||
def calculate_shares(self, account_value, entry_price, stop_price):
|
def calculate_shares(self, account_value, entry_price, stop_price):
|
||||||
|
|
@ -90,7 +113,9 @@ class ORBStrategy:
|
||||||
:param stop_price: 止损价格(多头=第一根K线最低价,空头=第一根K线最高价)
|
:param stop_price: 止损价格(多头=第一根K线最低价,空头=第一根K线最高价)
|
||||||
:return: 整数股数(Shares)
|
:return: 整数股数(Shares)
|
||||||
"""
|
"""
|
||||||
logger.info(f"开始计算交易股数:账户价值={account_value},entry价格={entry_price},止损价格={stop_price}")
|
logger.info(
|
||||||
|
f"开始计算交易股数:账户价值={account_value},entry价格={entry_price},止损价格={stop_price}"
|
||||||
|
)
|
||||||
# 计算单交易风险金额($R)
|
# 计算单交易风险金额($R)
|
||||||
risk_per_trade_dollar = abs(entry_price - stop_price) # 风险金额取绝对值
|
risk_per_trade_dollar = abs(entry_price - stop_price) # 风险金额取绝对值
|
||||||
if risk_per_trade_dollar <= 0:
|
if risk_per_trade_dollar <= 0:
|
||||||
|
|
@ -149,7 +174,7 @@ class ORBStrategy:
|
||||||
stop_price = high1 # 空头止损=第一根K线最高价
|
stop_price = high1 # 空头止损=第一根K线最高价
|
||||||
else:
|
else:
|
||||||
# 十字星→无信号
|
# 十字星→无信号
|
||||||
signal = "None"
|
signal = None
|
||||||
stop_price = None
|
stop_price = None
|
||||||
|
|
||||||
signals.append(
|
signals.append(
|
||||||
|
|
@ -167,10 +192,12 @@ class ORBStrategy:
|
||||||
# 将信号合并到原始数据
|
# 将信号合并到原始数据
|
||||||
signals_df = pd.DataFrame(signals)
|
signals_df = pd.DataFrame(signals)
|
||||||
# 确保Date列类型一致,将Date转换为datetime64[ns]类型
|
# 确保Date列类型一致,将Date转换为datetime64[ns]类型
|
||||||
signals_df['Date'] = pd.to_datetime(signals_df['Date'])
|
signals_df["Date"] = pd.to_datetime(signals_df["Date"])
|
||||||
# 使用merge而不是join来合并数据,根据signals_df的EntryTime与self.data的date_time进行匹配
|
# 使用merge而不是join来合并数据,根据signals_df的EntryTime与self.data的date_time进行匹配
|
||||||
# TODO: 这里需要优化
|
# TODO: 这里需要优化
|
||||||
self.data = self.data.merge(signals_df, left_on="date_time", right_on="EntryTime", how="left")
|
self.data = self.data.merge(
|
||||||
|
signals_df, left_on="date_time", right_on="EntryTime", how="left"
|
||||||
|
)
|
||||||
# 将Date_x和Date_y合并为Date
|
# 将Date_x和Date_y合并为Date
|
||||||
self.data["Date"] = self.data["Date_x"].combine_first(self.data["Date_y"])
|
self.data["Date"] = self.data["Date_x"].combine_first(self.data["Date_y"])
|
||||||
# 删除Date_x和Date_y
|
# 删除Date_x和Date_y
|
||||||
|
|
@ -214,6 +241,7 @@ class ORBStrategy:
|
||||||
signal = signal_row["Signal"]
|
signal = signal_row["Signal"]
|
||||||
if pd.isna(signal):
|
if pd.isna(signal):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
entry_price = signal_row["EntryPrice"]
|
entry_price = signal_row["EntryPrice"]
|
||||||
stop_price = signal_row["StopPrice"]
|
stop_price = signal_row["StopPrice"]
|
||||||
high1 = signal_row["High1"]
|
high1 = signal_row["High1"]
|
||||||
|
|
@ -254,24 +282,24 @@ class ORBStrategy:
|
||||||
if low <= stop_price:
|
if low <= stop_price:
|
||||||
exit_price = stop_price
|
exit_price = stop_price
|
||||||
exit_reason = "Stop Loss"
|
exit_reason = "Stop Loss"
|
||||||
exit_time = time
|
exit_time = row["date_time"]
|
||||||
break
|
break
|
||||||
elif high >= profit_target:
|
elif high >= profit_target:
|
||||||
exit_price = profit_target
|
exit_price = profit_target
|
||||||
exit_reason = "Profit Target (10R)"
|
exit_reason = "Profit Target (10R)"
|
||||||
exit_time = time
|
exit_time = row["date_time"]
|
||||||
break
|
break
|
||||||
elif signal == "Short":
|
elif signal == "Short":
|
||||||
# 空头:突破止损→止损;跌破止盈→止盈
|
# 空头:突破止损→止损;跌破止盈→止盈
|
||||||
if high >= stop_price:
|
if high >= stop_price:
|
||||||
exit_price = stop_price
|
exit_price = stop_price
|
||||||
exit_reason = "Stop Loss"
|
exit_reason = "Stop Loss"
|
||||||
exit_time = time
|
exit_time = row["date_time"]
|
||||||
break
|
break
|
||||||
elif low <= profit_target:
|
elif low <= profit_target:
|
||||||
exit_price = profit_target
|
exit_price = profit_target
|
||||||
exit_reason = "Profit Target (10R)"
|
exit_reason = "Profit Target (10R)"
|
||||||
exit_time = time
|
exit_time = row["date_time"]
|
||||||
break
|
break
|
||||||
|
|
||||||
# 若未触发止损/止盈,当日收盘平仓
|
# 若未触发止损/止盈,当日收盘平仓
|
||||||
|
|
@ -280,12 +308,16 @@ class ORBStrategy:
|
||||||
exit_reason = "End of Day (EoD)"
|
exit_reason = "End of Day (EoD)"
|
||||||
exit_time = daily_prices.iloc[-1].date_time
|
exit_time = daily_prices.iloc[-1].date_time
|
||||||
|
|
||||||
|
initial_account_value = account_value
|
||||||
# 计算盈亏
|
# 计算盈亏
|
||||||
if signal == "Long":
|
if signal == "Long":
|
||||||
profit_loss = (exit_price - entry_price) * shares - total_commission
|
profit_loss = (exit_price - entry_price) * shares - total_commission
|
||||||
else: # Short
|
else: # Short
|
||||||
profit_loss = (entry_price - exit_price) * shares - total_commission
|
profit_loss = (entry_price - exit_price) * shares - total_commission
|
||||||
|
|
||||||
|
# 计算盈亏百分比,profit_loss除以当期初始资金
|
||||||
|
profit_loss_percentage = (profit_loss / initial_account_value) * 100
|
||||||
|
|
||||||
# 更新账户价值
|
# 更新账户价值
|
||||||
account_value += profit_loss
|
account_value += profit_loss
|
||||||
account_value = max(account_value, 0) # 账户价值不能为负
|
account_value = max(account_value, 0) # 账户价值不能为负
|
||||||
|
|
@ -296,14 +328,16 @@ class ORBStrategy:
|
||||||
"TradeID": trade_id,
|
"TradeID": trade_id,
|
||||||
"Date": date,
|
"Date": date,
|
||||||
"Signal": signal,
|
"Signal": signal,
|
||||||
"EntryTime": signal_row.date_time,
|
"EntryTime": signal_row.date_time.strftime("%Y-%m-%d %H:%M:%S"),
|
||||||
"EntryPrice": entry_price,
|
"EntryPrice": entry_price,
|
||||||
"ExitTime": exit_time,
|
"ExitTime": exit_time.strftime("%Y-%m-%d %H:%M:%S"),
|
||||||
"ExitPrice": exit_price,
|
"ExitPrice": exit_price,
|
||||||
"Shares": shares,
|
"Shares": shares,
|
||||||
"RiskAssumed": risk_assumed,
|
"RiskAssumed": risk_assumed,
|
||||||
"ProfitLoss": profit_loss,
|
"ProfitLoss": profit_loss,
|
||||||
|
"ProfitLossPercentage": profit_loss_percentage,
|
||||||
"ExitReason": exit_reason,
|
"ExitReason": exit_reason,
|
||||||
|
"AccountValueInitial": initial_account_value,
|
||||||
"AccountValueAfter": account_value,
|
"AccountValueAfter": account_value,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
@ -313,12 +347,7 @@ class ORBStrategy:
|
||||||
trade_id += 1
|
trade_id += 1
|
||||||
|
|
||||||
# 生成净值曲线
|
# 生成净值曲线
|
||||||
self.equity_curve = pd.Series(
|
self.create_equity_curve()
|
||||||
equity_history,
|
|
||||||
index=pd.date_range(
|
|
||||||
start=self.data.index[0].date(), periods=len(equity_history), freq="D"
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
# 输出回测结果
|
# 输出回测结果
|
||||||
trades_df = pd.DataFrame(self.trades)
|
trades_df = pd.DataFrame(self.trades)
|
||||||
|
|
@ -330,6 +359,13 @@ class ORBStrategy:
|
||||||
if len(trades_df) > 0
|
if len(trades_df) > 0
|
||||||
else 0
|
else 0
|
||||||
)
|
)
|
||||||
|
# 计算盈亏比
|
||||||
|
profit_sum = trades_df[trades_df["ProfitLoss"] > 0]["ProfitLoss"].sum()
|
||||||
|
loss_sum = abs(trades_df[trades_df["ProfitLoss"] < 0]["ProfitLoss"].sum())
|
||||||
|
if loss_sum == 0:
|
||||||
|
profit_loss_ratio = float('inf')
|
||||||
|
else:
|
||||||
|
profit_loss_ratio = (profit_sum / loss_sum) * 100
|
||||||
|
|
||||||
logger.info("\n" + "=" * 50)
|
logger.info("\n" + "=" * 50)
|
||||||
logger.info("ORB策略回测结果")
|
logger.info("ORB策略回测结果")
|
||||||
|
|
@ -338,12 +374,40 @@ class ORBStrategy:
|
||||||
logger.info(f"最终资金:${account_value:,.2f}")
|
logger.info(f"最终资金:${account_value:,.2f}")
|
||||||
logger.info(f"总收益率:{total_return:.2f}%")
|
logger.info(f"总收益率:{total_return:.2f}%")
|
||||||
logger.info(f"总交易次数:{len(trades_df)}")
|
logger.info(f"总交易次数:{len(trades_df)}")
|
||||||
|
logger.info(f"盈亏比:{profit_loss_ratio:.2f}%")
|
||||||
logger.info(f"胜率:{win_rate:.2f}%")
|
logger.info(f"胜率:{win_rate:.2f}%")
|
||||||
if len(trades_df) > 0:
|
if len(trades_df) > 0:
|
||||||
logger.info(f"平均每笔盈亏:${trades_df['ProfitLoss'].mean():.2f}")
|
logger.info(f"平均每笔盈亏:${trades_df['ProfitLoss'].mean():.2f}")
|
||||||
logger.info(f"最大单笔盈利:${trades_df['ProfitLoss'].max():.2f}")
|
logger.info(f"最大单笔盈利:${trades_df['ProfitLoss'].max():.2f}")
|
||||||
logger.info(f"最大单笔亏损:${trades_df['ProfitLoss'].min():.2f}")
|
logger.info(f"最大单笔亏损:${trades_df['ProfitLoss'].min():.2f}")
|
||||||
|
|
||||||
|
def create_equity_curve(self):
|
||||||
|
"""
|
||||||
|
创建账户净值曲线
|
||||||
|
"""
|
||||||
|
equity_curve_list = []
|
||||||
|
# 将self.data.index[0].Date的值转换为字符串,且格式为YYYY-MM-DD
|
||||||
|
first_date = self.data.iloc[0].date_time.strftime("%Y-%m-%d %H:%M:%S")
|
||||||
|
first_open = float(self.data.iloc[0].Open)
|
||||||
|
equity_curve_list.append(
|
||||||
|
{
|
||||||
|
"DateTime": first_date,
|
||||||
|
"AccountValue": self.initial_capital,
|
||||||
|
"MarketPrice": first_open,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
for trade in self.trades:
|
||||||
|
equity_curve_list.append(
|
||||||
|
{
|
||||||
|
"DateTime": trade["ExitTime"],
|
||||||
|
"AccountValue": trade["AccountValueAfter"],
|
||||||
|
"MarketPrice": trade["ExitPrice"],
|
||||||
|
}
|
||||||
|
)
|
||||||
|
self.equity_curve = pd.DataFrame(equity_curve_list)
|
||||||
|
self.equity_curve.sort_values(by="DateTime", inplace=True)
|
||||||
|
self.equity_curve.reset_index(drop=True, inplace=True)
|
||||||
|
|
||||||
def plot_equity_curve(self):
|
def plot_equity_curve(self):
|
||||||
"""
|
"""
|
||||||
绘制账户净值曲线
|
绘制账户净值曲线
|
||||||
|
|
@ -359,22 +423,51 @@ class ORBStrategy:
|
||||||
plt.rcParams["font.size"] = 11 # 设置字体大小
|
plt.rcParams["font.size"] = 11 # 设置字体大小
|
||||||
plt.rcParams["axes.unicode_minus"] = False # 解决负号显示问题
|
plt.rcParams["axes.unicode_minus"] = False # 解决负号显示问题
|
||||||
|
|
||||||
|
symbol = self.data.iloc[0].symbol
|
||||||
|
bar = self.data.iloc[0].bar
|
||||||
|
first_account_value = self.equity_curve.iloc[0]["AccountValue"]
|
||||||
|
first_market_price = self.equity_curve.iloc[0]["MarketPrice"]
|
||||||
|
account_value_to_1 = self.equity_curve["AccountValue"] / first_account_value
|
||||||
|
market_price_to_1 = self.equity_curve["MarketPrice"] / first_market_price
|
||||||
plt.figure(figsize=(12, 6))
|
plt.figure(figsize=(12, 6))
|
||||||
plt.plot(
|
plt.plot(self.equity_curve["DateTime"], account_value_to_1, label="账户价值", color='blue', linewidth=2, marker='o', markersize=4)
|
||||||
self.equity_curve.index,
|
plt.plot(self.equity_curve["DateTime"], market_price_to_1, label="市场价格", color='green', linewidth=2, marker='s', markersize=4)
|
||||||
self.equity_curve.values,
|
plt.title(f"ORB策略账户净值曲线 {symbol} {bar}", fontsize=14, fontweight='bold')
|
||||||
label="ORB策略净值",
|
plt.xlabel("时间", fontsize=12)
|
||||||
color="blue",
|
plt.ylabel("涨跌变化", fontsize=12)
|
||||||
)
|
plt.legend(fontsize=11)
|
||||||
plt.axhline(
|
|
||||||
y=self.initial_capital, color="red", linestyle="--", label="初始资金"
|
|
||||||
)
|
|
||||||
plt.title("ORB策略账户净值曲线", fontsize=14)
|
|
||||||
plt.xlabel("日期", fontsize=12)
|
|
||||||
plt.ylabel("账户价值(美元)", fontsize=12)
|
|
||||||
plt.legend()
|
|
||||||
plt.grid(True, alpha=0.3)
|
plt.grid(True, alpha=0.3)
|
||||||
plt.show()
|
|
||||||
|
# 设置x轴标签,避免matplotlib警告
|
||||||
|
# 选择合适的时间间隔显示标签,避免过于密集
|
||||||
|
if len(self.equity_curve) > 30:
|
||||||
|
# 如果数据点较多,选择间隔显示,但确保第一条和最后一条始终显示
|
||||||
|
step = max(1, len(self.equity_curve) // 30)
|
||||||
|
|
||||||
|
# 创建标签索引列表,确保包含首尾数据
|
||||||
|
label_indices = [0] # 第一条
|
||||||
|
|
||||||
|
# 添加中间间隔的标签
|
||||||
|
for i in range(step, len(self.equity_curve) - 1, step):
|
||||||
|
label_indices.append(i)
|
||||||
|
|
||||||
|
# 添加最后一条(如果还没有包含的话)
|
||||||
|
if len(self.equity_curve) - 1 not in label_indices:
|
||||||
|
label_indices.append(len(self.equity_curve) - 1)
|
||||||
|
|
||||||
|
# 设置x轴标签
|
||||||
|
plt.xticks(self.equity_curve["DateTime"].iloc[label_indices],
|
||||||
|
self.equity_curve["DateTime"].iloc[label_indices],
|
||||||
|
rotation=45, ha='right', fontsize=10)
|
||||||
|
else:
|
||||||
|
# 如果数据点较少,全部显示
|
||||||
|
plt.xticks(self.equity_curve["DateTime"],
|
||||||
|
self.equity_curve["DateTime"],
|
||||||
|
rotation=45, ha='right', fontsize=10)
|
||||||
|
plt.tight_layout()
|
||||||
|
save_path = f"{self.output_chart_folder}/{symbol}_{bar}_orb_strategy_equity_curve.png"
|
||||||
|
plt.savefig(save_path, dpi=150, bbox_inches='tight')
|
||||||
|
plt.close()
|
||||||
|
|
||||||
|
|
||||||
# ------------------- 策略示例:回测QQQ的ORB策略(2016-2023) -------------------
|
# ------------------- 策略示例:回测QQQ的ORB策略(2016-2023) -------------------
|
||||||
|
|
|
||||||
|
|
@ -109,14 +109,16 @@ class MarketDataMain:
|
||||||
# 如果bar为1D, 则end_time_ts与start_time_ts相差超过10天,则按照10天为单位
|
# 如果bar为1D, 则end_time_ts与start_time_ts相差超过10天,则按照10天为单位
|
||||||
# 获取数据,直到end_time_ts
|
# 获取数据,直到end_time_ts
|
||||||
threshold = None
|
threshold = None
|
||||||
if bar in ["5m", "15m", "30m"]:
|
if bar in ["5m", "15m", "30m", "1H"]:
|
||||||
if self.is_us_stock:
|
if self.is_us_stock:
|
||||||
if bar == "5m":
|
if bar == "5m":
|
||||||
threshold = 86400000 * 4
|
threshold = 86400000 * 4
|
||||||
elif bar == "15m":
|
elif bar == "15m":
|
||||||
threshold = 86400000 * 4 * 3
|
threshold = 86400000 * 6
|
||||||
elif bar == "30m":
|
elif bar == "30m":
|
||||||
threshold = 86400000 * 4 * 6
|
threshold = 86400000 * 12
|
||||||
|
elif bar == "1H":
|
||||||
|
threshold = 86400000 * 24
|
||||||
else:
|
else:
|
||||||
threshold = 86400000
|
threshold = 86400000
|
||||||
elif bar in ["1H", "4H"]:
|
elif bar in ["1H", "4H"]:
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,24 @@
|
||||||
from core.trade.orb_trade import ORBStrategy
|
from core.trade.orb_trade import ORBStrategy
|
||||||
|
from config import US_STOCK_MONITOR_CONFIG
|
||||||
|
import core.logger as logging
|
||||||
|
|
||||||
|
logger = logging.logger
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
|
symbols = US_STOCK_MONITOR_CONFIG.get("volume_monitor", {}).get("symbols", ["QQQ"])
|
||||||
|
for symbol in symbols:
|
||||||
|
logger.info(f"开始回测 {symbol}")
|
||||||
# 初始化ORB策略
|
# 初始化ORB策略
|
||||||
orb_strategy = ORBStrategy(
|
orb_strategy = ORBStrategy(
|
||||||
initial_capital=25000,
|
initial_capital=25000,
|
||||||
max_leverage=4,
|
max_leverage=4,
|
||||||
risk_per_trade=0.01,
|
risk_per_trade=0.01,
|
||||||
commission_per_share=0.0005,
|
commission_per_share=0.0005,
|
||||||
|
is_us_stock=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
# 1. 获取QQQ的5分钟日内数据(2024-2025,注意:yfinance免费版可能限制历史日内数据,建议用专业数据源)
|
# 1. 获取QQQ的5分钟日内数据(2024-2025,注意:yfinance免费版可能限制历史日内数据,建议用专业数据源)
|
||||||
orb_strategy.fetch_intraday_data(
|
orb_strategy.fetch_intraday_data(
|
||||||
symbol="ETH-USDT", start_date="2025-05-15", end_date="2025-08-20", interval="5m"
|
symbol=symbol, start_date="2024-11-30", end_date="2025-08-30", interval="5m"
|
||||||
)
|
)
|
||||||
|
|
||||||
# 2. 生成ORB策略信号
|
# 2. 生成ORB策略信号
|
||||||
|
|
|
||||||
|
|
@ -25,8 +25,8 @@ from config import (
|
||||||
logger = logging.logger
|
logger = logging.logger
|
||||||
|
|
||||||
class TradeMaStrategyMain:
|
class TradeMaStrategyMain:
|
||||||
def __init__(self):
|
def __init__(self, is_us_stock: bool = False):
|
||||||
self.ma_break_statistics = MaBreakStatistics()
|
self.ma_break_statistics = MaBreakStatistics(is_us_stock=is_us_stock)
|
||||||
|
|
||||||
def batch_ma_break_statistics(self):
|
def batch_ma_break_statistics(self):
|
||||||
"""
|
"""
|
||||||
|
|
@ -59,5 +59,5 @@ class TradeMaStrategyMain:
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
trade_ma_strategy_main = TradeMaStrategyMain()
|
trade_ma_strategy_main = TradeMaStrategyMain(is_us_stock=True)
|
||||||
trade_ma_strategy_main.batch_ma_break_statistics()
|
trade_ma_strategy_main.batch_ma_break_statistics()
|
||||||
Loading…
Reference in New Issue