Files
etf/test.py
aszerW 988c2335fb chore(config): 添加环境变量示例及.gitignore更新
- 新增 .env.example,包含 Tushare API、钉钉机器人和PostgreSQL数据库配置模板
- 更新.gitignore,忽略本地配置文件如 .env.local 和 config_local.py
- 添加对报表文件命名规则的支持,保留示例文件不忽略
- 删除废弃的 chart.py 及相关图表模块代码
- 新增 config/settings.py,实现从环境变量读取配置的统一接口
- 设置数据目录及缓存目录,确保目录存在,提高配置管理规范性
2026-03-18 23:33:40 +08:00

100 lines
3.2 KiB
Python

import pandas as pd
import numpy as np
import vectorbt as vbt
from numba import njit
import vectorbt as vbt
import pandas as pd
import numpy as np
import pandas as pd
from loguru import logger
from chart import resample_data, QuantChart
from db_config import DatabaseManager, DatabaseConfig
from vectorbt.base.reshape_fns import to_2d_array
def get_kline(code: str) -> list:
"""
获取所有指数代码
:return:
"""
db_config = DatabaseConfig()
logger.info(f"数据库连接: {db_config.connection_string}")
db_manager = DatabaseManager(db_config)
sql = f"SELECT date as time, open, high, low, close, volume FROM public.index_kline where code='{code}' order by date;"
res = db_manager.execute_query(sql)
data_list = [dict(item) for item in res]
df = pd.DataFrame(data_list)
df["time"] = pd.to_datetime(df["time"])
num_cols = ["open", "high", "low", "close", "volume"]
for col in num_cols:
if col in df.columns:
df[col] = pd.to_numeric(df[col], errors="coerce").astype(float)
return df
symbol = "399998"
timeframe = "1D"
df = get_kline(code=symbol)
df = resample_data(df, timeframe)
df.rename(columns={'time': 'date'}, inplace=True)
print(df.head())
if 'date' in df.columns:
df = df.set_index('date')
price = df['close']
# 2. 计算90天滚动波动率(年化)
returns = price.pct_change()
volatility_90d = returns.rolling(window=90, min_periods=90).std() * np.sqrt(365)
# 3. 计算波动率倒数作为权重
inv_vol = 1 / volatility_90d
# 标准化权重(可选,使其更易解释)
inv_vol_normalized = inv_vol / inv_vol.rolling(window=252).mean()
# 4. 创建每周重新平衡的信号
# 获取每周最后一个交易日
weekly_rebalance = pd.Series(False, index=price.index)
weekly_last_days = price.resample('W').last().index
for date in weekly_last_days:
# 找到最接近的交易日
idx = price.index.get_indexer([date], method='ffill')[0]
if idx >= 0:
weekly_rebalance.iloc[idx] = True
# 5. 定义订单函数
@njit
def order_func_nb(c, inv_vol_arr, rebalance_arr):
# 获取当前的波动率倒数权重
inv_vol_now = vbt.nb.flex_select_auto_nb(inv_vol_arr, c.i, c.col, False)
rebalance_now = vbt.nb.flex_select_auto_nb(rebalance_arr, c.i, c.col, False)
# 只在重新平衡日调整仓位
if not rebalance_now or np.isnan(inv_vol_now):
return vbt.nb.order_nothing_nb()
# 目标仓位 = 总价值 * 波动率倒数权重
# 这里使用 TargetPercent 类型,权重越高仓位越大
target_percent = min(inv_vol_now, 1.0) # 限制最大100%仓位
return vbt.nb.order_nb(
size=target_percent,
size_type=vbt.SizeType.TargetPercent,
direction=vbt.Direction.LongOnly
)
# 6. 运行回测
pf = vbt.Portfolio.from_order_func(
price,
order_func_nb,
to_2d_array(inv_vol_normalized),
to_2d_array(weekly_rebalance),
init_cash=100,
freq='1D'
)
# 7. 查看结果
print(pf.stats())
print(f"\n波动率倒数策略 vs 买入持有:")
print(f"总收益率: {pf.total_return():.2%}")