Skip to content

Commit

Permalink
V0.9.46 更新一批代码 (#190)
Browse files Browse the repository at this point in the history
* 0.9.46 start coding

* 0.9.46 PSI 测试开发

* 0.9.46 update

* 0.9.46 新增 optuna 超参分析

* 0.9.46 disk cache 增加默认path

* 0.9.46 新增 rolling_tanh 函数

* 0.9.46 新增CCF因子函数

* 0.9.46 新增最大回撤分析组件

* 0.9.46 新增期货调仓函数
  • Loading branch information
zengbin93 authored Mar 28, 2024
1 parent 214d6ae commit ff0eef0
Show file tree
Hide file tree
Showing 21 changed files with 522 additions and 27 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/pythonpackage.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ name: Python package

on:
push:
branches: [ master, V0.9.45 ]
branches: [ master, V0.9.46 ]
pull_request:
branches: [ master ]

Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ pip install [email protected]:waditu/czsc.git -U

直接从github指定分支安装最新版:
```
pip install git+https://github.com/waditu/[email protected].41 -U
pip install git+https://github.com/waditu/[email protected].46 -U
```

`pypi`安装:
Expand Down
14 changes: 12 additions & 2 deletions czsc/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@
ExitsOptimize,
)
from czsc.utils import (
format_standard_kline,

KlineChart,
WordWriter,
BarGenerator,
Expand Down Expand Up @@ -81,6 +83,7 @@
holds_performance,
net_value_stats,
subtract_fee,
top_drawdowns,

home_path,
DiskCache,
Expand All @@ -94,6 +97,9 @@
DataClient,
set_url_token,
get_url_token,

optuna_study,
optuna_good_params,
)

# 交易日历工具
Expand Down Expand Up @@ -121,6 +127,8 @@
show_stoploss_by_direction,
show_cointegration,
show_out_in_compare,
show_optuna_study,
show_drawdowns,
)

from czsc.utils.bi_info import (
Expand All @@ -144,12 +152,14 @@
rolling_compare,
rolling_scale,
rolling_slope,
rolling_tanh,
feature_adjust,
)

__version__ = "0.9.45"
__version__ = "0.9.46"
__author__ = "zengbin93"
__email__ = "[email protected]"
__date__ = "20240308"
__date__ = "20240318"


def welcome():
Expand Down
3 changes: 2 additions & 1 deletion czsc/connectors/research.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ def get_raw_bars(symbol, freq, sdt, edt, fq='前复权', **kwargs):
:param kwargs:
:return:
"""
raw_bars = kwargs.get('raw_bars', True)
kwargs['fq'] = fq
file = glob.glob(os.path.join(cache_path, "*", f"{symbol}.parquet"))[0]
freq = czsc.Freq(freq)
Expand All @@ -54,5 +55,5 @@ def get_raw_bars(symbol, freq, sdt, edt, fq='前复权', **kwargs):
kline = kline[(kline['dt'] >= pd.to_datetime(sdt)) & (kline['dt'] <= pd.to_datetime(edt))]
if kline.empty:
return []
_bars = czsc.resample_bars(kline, freq, raw_bars=True, base_freq='1分钟')
_bars = czsc.resample_bars(kline, freq, raw_bars=raw_bars, base_freq='1分钟')
return _bars
67 changes: 66 additions & 1 deletion czsc/connectors/tq_connector.py
Original file line number Diff line number Diff line change
Expand Up @@ -204,7 +204,14 @@ def is_trade_time(trade_time: Optional[str] = None):


def get_daily_backup(api: TqApi, **kwargs):
"""获取每日账户中需要备份的信息"""
"""获取每日账户中需要备份的信息
https://doc.shinnytech.com/tqsdk/latest/reference/tqsdk.objs.html?highlight=account#tqsdk.objs.Order
https://doc.shinnytech.com/tqsdk/latest/reference/tqsdk.objs.html?highlight=account#tqsdk.objs.Position
https://doc.shinnytech.com/tqsdk/latest/reference/tqsdk.objs.html?highlight=account#tqsdk.objs.Account
:param api: TqApi, 天勤API实例
"""
orders = api.get_order()
trades = api.get_trade()
position = api.get_position()
Expand All @@ -229,3 +236,61 @@ def get_daily_backup(api: TqApi, **kwargs):
"account": account,
}
return backup


def adjust_portfolio(api: TqApi, portfolio, account=None, **kwargs):
"""调整账户组合
**注意:** 此函数会阻塞,直到调仓完成;使用前请仔细阅读 TargetPosTask 的源码和文档,确保了解其工作原理
:param api: TqApi, 天勤API实例
:param account: str, 天勤账户
:param portfolio: dict, 组合配置,key 为合约代码,value 为配置信息; 样例数据:
{
"[email protected]": {"target_volume": 10, "price": "PASSIVE", "offset_priority": "今昨,开"},
"[email protected]": {"target_volume": 0, "price": "ACTIVE", "offset_priority": "今昨,开"},
"[email protected]": {"target_volume": 30, "price": "PASSIVE", "offset_priority": "今昨,开"}
}
:param kwargs: dict, 其他参数
"""
symbol_infos = {}
for symbol, conf in portfolio.items():
quote = api.get_quote(symbol)

lots = conf.get("target_volume", 0)
price = conf.get("price", "PASSIVE")
offset_priority = conf.get("offset_priority", "今昨,开")

# 踩坑记录:TargetPosTask 的 symbol 必须是合约代码
contract = quote.underlying_symbol if "@" in symbol else symbol
target_pos = TargetPosTask(api, contract, price=price, offset_priority=offset_priority, account=account)
target_pos.set_target_volume(int(lots))
symbol_infos[symbol] = {"quote": quote, "target_pos": target_pos, "lots": lots}

while True:
api.wait_update()

completed = []
for symbol, info in symbol_infos.items():
quote = info["quote"]
target_pos: TargetPosTask = info["target_pos"]
lots = info["lots"]
contract = quote.underlying_symbol if "@" in symbol else symbol

logger.info(f"调整仓位:{quote.datetime} - {contract}; 目标持仓:{lots}手; 当前持仓:{target_pos._pos.pos}手")

if target_pos._pos.pos == lots:
completed.append(True)
logger.info(f"调仓完成:{quote.datetime} - {contract}; {lots}手")
else:
completed.append(False)

if all(completed):
break

if kwargs.get("close_api", True):
api.close()

return api
4 changes: 4 additions & 0 deletions czsc/features/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,8 @@
VPF002,
VPF003,
VPF004,
)

from .tas import (
CCF
)
51 changes: 51 additions & 0 deletions czsc/features/tas.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
"""
技术指标因子
"""
import inspect
import hashlib
import pandas as pd


def CCF(df, **kwargs):
"""使用 CZSC 库中的 factor 识别因子,主要用于识别缠论/形态因子
:param df: 标准K线数据,DataFrame结构
:param kwargs: 其他参数
- czsc_factor: dict, 缠论因子配置,样例:
{
"signals_all": ["日线_D1_表里关系V230101_向上_任意_任意_0"],
"signals_any": [],
"signals_not": ["日线_D1_涨跌停V230331_涨停_任意_任意_0"],
}
- freq: str, default '日线',K线级别
- tag: str, default None,标签,用于区分不同的因子
:return: pd.DataFrame
"""
from czsc.objects import Factor
from czsc.utils import format_standard_kline
from czsc.traders.base import generate_czsc_signals
from czsc.traders.sig_parse import get_signals_config

czsc_factor = kwargs.get('czsc_factor', None)
freq = kwargs.get('freq', '日线')
assert czsc_factor is not None and isinstance(czsc_factor, dict), "factor 参数必须指定"
tag = kwargs.get('tag', hashlib.sha256(f"{czsc_factor}_{freq}".encode()).hexdigest().upper()[:6])

factor_name = inspect.stack()[0][3]
factor_col = f'F#{factor_name}#{tag}'

czsc_factor = Factor.load(czsc_factor)
signals_seq = czsc_factor.signals_all + czsc_factor.signals_any + czsc_factor.signals_not
signals_config = get_signals_config([x.signal for x in signals_seq])

bars = format_standard_kline(df, freq=freq)
dfs = generate_czsc_signals(bars, signals_config, init_n=300, sdt=bars[0].dt, df=True)
dfs[factor_col] = dfs.apply(czsc_factor.is_match, axis=1).astype(int)

df = pd.merge(df, dfs[['dt', factor_col]], on='dt', how='left')
df[factor_col] = df[factor_col].fillna(0)
return df
94 changes: 94 additions & 0 deletions czsc/features/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,26 @@ def rolling_scale(df: pd.DataFrame, col: str, window=300, min_periods=100, new_c
return df


def rolling_tanh(df: pd.DataFrame, col: str, window=300, min_periods=100, new_col=None, **kwargs):
"""对序列进行滚动 tanh 变换
双曲正切函数:https://baike.baidu.com/item/%E5%8F%8C%E6%9B%B2%E6%AD%A3%E5%88%87%E5%87%BD%E6%95%B0/15469414
:param df: pd.DataFrame, 待计算的数据
:param col: str, 待计算的列
:param window: int, 滚动窗口大小, 默认为300
:param min_periods: int, 最小计算周期, 默认为100
:param new_col: str, 新列名,默认为 None, 表示使用 f'{col}_scale' 作为新列名
"""
if kwargs.get("copy", False):
df = df.copy()
new_col = new_col if new_col else f'{col}_tanh'
df = df.sort_values("dt", ascending=True).reset_index(drop=True)
df[new_col] = df[col].rolling(window=window, min_periods=min_periods).apply(lambda x: np.tanh(scale(x))[-1]) # type: ignore
df[new_col] = df[new_col].fillna(0)
return df


def rolling_slope(df: pd.DataFrame, col: str, window=300, min_periods=100, new_col=None, **kwargs):
"""计算序列的滚动斜率
Expand Down Expand Up @@ -234,3 +254,77 @@ def __lr_slope(x):

df[new_col] = df[new_col].fillna(0)
return df


def feature_adjust_V230101(df: pd.DataFrame, fcol, **kwargs):
"""特征调整函数:对特征进行调整,使其符合持仓权重的定义
方法说明:对因子进行滚动相关系数计算,然后对因子值用 maxabs_scale 进行归一化,最后乘以滚动相关系数的符号
:param df: pd.DataFrame, 必须包含 dt、symbol、price 列,以及因子列
:param fcol: str 因子列名
:param kwargs: dict
"""
window = kwargs.get("window", 1000)
min_periods = kwargs.get("min_periods", 200)

df = df.copy().sort_values("dt", ascending=True).reset_index(drop=True)
df['n1b'] = df['price'].shift(-1) / df['price'] - 1
df['corr'] = df[fcol].rolling(window=window, min_periods=min_periods).corr(df['n1b'])
df['corr'] = df['corr'].shift(5).fillna(0)

df = rolling_scale(df, col=fcol, window=window, min_periods=min_periods,
new_col='weight', method='maxabs_scale', copy=True)
df['weight'] = df['weight'] * np.sign(df['corr'])

df.drop(['n1b', 'corr'], axis=1, inplace=True)
return df


def feature_adjust_V240323(df: pd.DataFrame, fcol, **kwargs):
"""特征调整函数:对特征进行调整,使其符合持仓权重的定义
方法说明:对因子进行滚动相关系数计算,然后对因子值用 scale + tanh 进行归一化,最后乘以滚动相关系数的符号
:param df: pd.DataFrame, 必须包含 dt、symbol、price 列,以及因子列
:param fcol: str 因子列名
:param kwargs: dict
"""
window = kwargs.get("window", 1000)
min_periods = kwargs.get("min_periods", 200)

df = df.copy().sort_values("dt", ascending=True).reset_index(drop=True)
df['n1b'] = df['price'].shift(-1) / df['price'] - 1
df['corr'] = df[fcol].rolling(window=window, min_periods=min_periods).corr(df['n1b'])
df['corr'] = df['corr'].shift(5).fillna(0)

df = rolling_tanh(df, col=fcol, window=window, min_periods=min_periods, new_col='weight')
df['weight'] = df['weight'] * np.sign(df['corr'])

df.drop(['n1b', 'corr'], axis=1, inplace=True)
return df


def feature_adjust(df: pd.DataFrame, fcol, method, **kwargs):
"""特征调整函数:对特征进行调整,使其符合持仓权重的定义
:param df: pd.DataFrame, 待调整的数据
:param fcol: str, 因子列名
:param method: str, 调整方法
- V230101: 对因子进行滚动相关系数计算,然后对因子值用 maxabs_scale 进行归一化,最后乘以滚动相关系数的符号
- V240323: 对因子进行滚动相关系数计算,然后对因子值用 scale + tanh 进行归一化,最后乘以滚动相关系数的符号
:param kwargs: dict
- window: int, 滚动窗口大小
- min_periods: int, 最小计算周期
:return: pd.DataFrame, 新增 weight 列
"""
if method == "V230101":
return feature_adjust_V230101(df, fcol, **kwargs)
elif method == "V240323":
return feature_adjust_V240323(df, fcol, **kwargs)
else:
raise ValueError(f"Unknown method: {method}")
5 changes: 3 additions & 2 deletions czsc/utils/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,20 +10,21 @@
from .echarts_plot import kline_pro, heat_map
from .word_writer import WordWriter
from .corr import nmi_matrix, single_linear, cross_sectional_ic
from .bar_generator import BarGenerator, freq_end_time, resample_bars
from .bar_generator import BarGenerator, freq_end_time, resample_bars, format_standard_kline
from .bar_generator import is_trading_time, get_intraday_times, check_freq_and_market
from .io import dill_dump, dill_load, read_json, save_json
from .sig import check_pressure_support, check_gap_info, is_bis_down, is_bis_up, get_sub_elements, is_symmetry_zs
from .sig import same_dir_counts, fast_slow_cross, count_last_same, create_single_signal
from .plotly_plot import KlineChart
from .trade import cal_trade_price, update_nbars, update_bbars, update_tbars, risk_free_returns, resample_to_daily
from .cross import CrossSectionalPerformance, cross_sectional_ranker
from .stats import daily_performance, net_value_stats, subtract_fee, weekly_performance, holds_performance
from .stats import daily_performance, net_value_stats, subtract_fee, weekly_performance, holds_performance, top_drawdowns
from .signal_analyzer import SignalAnalyzer, SignalPerformance
from .cache import home_path, get_dir_size, empty_cache_path, DiskCache, disk_cache
from .index_composition import index_composition
from .data_client import DataClient, set_url_token, get_url_token
from .oss import AliyunOSS
from .optuna import optuna_study, optuna_good_params


sorted_freqs = ['Tick', '1分钟', '2分钟', '3分钟', '4分钟', '5分钟', '6分钟', '10分钟', '12分钟',
Expand Down
24 changes: 24 additions & 0 deletions czsc/utils/bar_generator.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,30 @@ def get_intraday_times(freq='1分钟', market="A股"):
return freq_market_times[f"{freq}_{market}"]


def format_standard_kline(df: pd.DataFrame, freq: str):
"""格式化标准K线数据为 CZSC 标准数据结构 RawBar 列表
:param df: 标准K线数据,DataFrame结构
=================== ========= ====== ======= ====== ===== =========== ===========
dt symbol open close high low vol amount
=================== ========= ====== ======= ====== ===== =========== ===========
2023-11-17 00:00:00 689009.SH 33.52 33.41 33.69 33.38 1.97575e+06 6.61661e+07
2023-11-20 00:00:00 689009.SH 33.4 32.91 33.45 32.25 5.15016e+06 1.68867e+08
=================== ========= ====== ======= ====== ===== =========== ===========
:param freq: K线级别
:return: list of RawBar
"""
# from czsc.objects import RawBar, Freq
bars = []
for i, row in df.iterrows():
bar = RawBar(id=i, symbol=row['symbol'], dt=row['dt'], open=row['open'], close=row['close'],
high=row['high'], low=row['low'], vol=row['vol'], amount=row['amount'], freq=Freq(freq))
bars.append(bar)
return bars


def check_freq_and_market(time_seq: List[AnyStr], freq: Optional[AnyStr] = None):
"""检查时间序列是否为同一周期,是否为同一市场
Expand Down
2 changes: 1 addition & 1 deletion czsc/utils/cache.py
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@ def remove(self, k: str, suffix: str = "pkl"):
Path.unlink(file) if Path.exists(file) else None


def disk_cache(path: str, suffix: str = "pkl", ttl: int = -1):
def disk_cache(path: str = home_path, suffix: str = "pkl", ttl: int = -1):
"""缓存装饰器,支持多种数据格式
:param path: 缓存文件夹路径
Expand Down
Loading

0 comments on commit ff0eef0

Please sign in to comment.