Skip to content

Commit ff0eef0

Browse files
authored
V0.9.46 更新一批代码 (#190)
* 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 新增期货调仓函数
1 parent 214d6ae commit ff0eef0

File tree

21 files changed

+522
-27
lines changed

21 files changed

+522
-27
lines changed

.github/workflows/pythonpackage.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ name: Python package
55

66
on:
77
push:
8-
branches: [ master, V0.9.45 ]
8+
branches: [ master, V0.9.46 ]
99
pull_request:
1010
branches: [ master ]
1111

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ pip install [email protected]:waditu/czsc.git -U
5858

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

6464
`pypi`安装:

czsc/__init__.py

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,8 @@
4545
ExitsOptimize,
4646
)
4747
from czsc.utils import (
48+
format_standard_kline,
49+
4850
KlineChart,
4951
WordWriter,
5052
BarGenerator,
@@ -81,6 +83,7 @@
8183
holds_performance,
8284
net_value_stats,
8385
subtract_fee,
86+
top_drawdowns,
8487

8588
home_path,
8689
DiskCache,
@@ -94,6 +97,9 @@
9497
DataClient,
9598
set_url_token,
9699
get_url_token,
100+
101+
optuna_study,
102+
optuna_good_params,
97103
)
98104

99105
# 交易日历工具
@@ -121,6 +127,8 @@
121127
show_stoploss_by_direction,
122128
show_cointegration,
123129
show_out_in_compare,
130+
show_optuna_study,
131+
show_drawdowns,
124132
)
125133

126134
from czsc.utils.bi_info import (
@@ -144,12 +152,14 @@
144152
rolling_compare,
145153
rolling_scale,
146154
rolling_slope,
155+
rolling_tanh,
156+
feature_adjust,
147157
)
148158

149-
__version__ = "0.9.45"
159+
__version__ = "0.9.46"
150160
__author__ = "zengbin93"
151161
__email__ = "[email protected]"
152-
__date__ = "20240308"
162+
__date__ = "20240318"
153163

154164

155165
def welcome():

czsc/connectors/research.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ def get_raw_bars(symbol, freq, sdt, edt, fq='前复权', **kwargs):
4545
:param kwargs:
4646
:return:
4747
"""
48+
raw_bars = kwargs.get('raw_bars', True)
4849
kwargs['fq'] = fq
4950
file = glob.glob(os.path.join(cache_path, "*", f"{symbol}.parquet"))[0]
5051
freq = czsc.Freq(freq)
@@ -54,5 +55,5 @@ def get_raw_bars(symbol, freq, sdt, edt, fq='前复权', **kwargs):
5455
kline = kline[(kline['dt'] >= pd.to_datetime(sdt)) & (kline['dt'] <= pd.to_datetime(edt))]
5556
if kline.empty:
5657
return []
57-
_bars = czsc.resample_bars(kline, freq, raw_bars=True, base_freq='1分钟')
58+
_bars = czsc.resample_bars(kline, freq, raw_bars=raw_bars, base_freq='1分钟')
5859
return _bars

czsc/connectors/tq_connector.py

Lines changed: 66 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -204,7 +204,14 @@ def is_trade_time(trade_time: Optional[str] = None):
204204

205205

206206
def get_daily_backup(api: TqApi, **kwargs):
207-
"""获取每日账户中需要备份的信息"""
207+
"""获取每日账户中需要备份的信息
208+
209+
https://doc.shinnytech.com/tqsdk/latest/reference/tqsdk.objs.html?highlight=account#tqsdk.objs.Order
210+
https://doc.shinnytech.com/tqsdk/latest/reference/tqsdk.objs.html?highlight=account#tqsdk.objs.Position
211+
https://doc.shinnytech.com/tqsdk/latest/reference/tqsdk.objs.html?highlight=account#tqsdk.objs.Account
212+
213+
:param api: TqApi, 天勤API实例
214+
"""
208215
orders = api.get_order()
209216
trades = api.get_trade()
210217
position = api.get_position()
@@ -229,3 +236,61 @@ def get_daily_backup(api: TqApi, **kwargs):
229236
"account": account,
230237
}
231238
return backup
239+
240+
241+
def adjust_portfolio(api: TqApi, portfolio, account=None, **kwargs):
242+
"""调整账户组合
243+
244+
**注意:** 此函数会阻塞,直到调仓完成;使用前请仔细阅读 TargetPosTask 的源码和文档,确保了解其工作原理
245+
246+
:param api: TqApi, 天勤API实例
247+
:param account: str, 天勤账户
248+
:param portfolio: dict, 组合配置,key 为合约代码,value 为配置信息; 样例数据:
249+
250+
{
251+
"[email protected]": {"target_volume": 10, "price": "PASSIVE", "offset_priority": "今昨,开"},
252+
"[email protected]": {"target_volume": 0, "price": "ACTIVE", "offset_priority": "今昨,开"},
253+
"[email protected]": {"target_volume": 30, "price": "PASSIVE", "offset_priority": "今昨,开"}
254+
}
255+
256+
:param kwargs: dict, 其他参数
257+
"""
258+
symbol_infos = {}
259+
for symbol, conf in portfolio.items():
260+
quote = api.get_quote(symbol)
261+
262+
lots = conf.get("target_volume", 0)
263+
price = conf.get("price", "PASSIVE")
264+
offset_priority = conf.get("offset_priority", "今昨,开")
265+
266+
# 踩坑记录:TargetPosTask 的 symbol 必须是合约代码
267+
contract = quote.underlying_symbol if "@" in symbol else symbol
268+
target_pos = TargetPosTask(api, contract, price=price, offset_priority=offset_priority, account=account)
269+
target_pos.set_target_volume(int(lots))
270+
symbol_infos[symbol] = {"quote": quote, "target_pos": target_pos, "lots": lots}
271+
272+
while True:
273+
api.wait_update()
274+
275+
completed = []
276+
for symbol, info in symbol_infos.items():
277+
quote = info["quote"]
278+
target_pos: TargetPosTask = info["target_pos"]
279+
lots = info["lots"]
280+
contract = quote.underlying_symbol if "@" in symbol else symbol
281+
282+
logger.info(f"调整仓位:{quote.datetime} - {contract}; 目标持仓:{lots}手; 当前持仓:{target_pos._pos.pos}手")
283+
284+
if target_pos._pos.pos == lots:
285+
completed.append(True)
286+
logger.info(f"调仓完成:{quote.datetime} - {contract}; {lots}手")
287+
else:
288+
completed.append(False)
289+
290+
if all(completed):
291+
break
292+
293+
if kwargs.get("close_api", True):
294+
api.close()
295+
296+
return api

czsc/features/__init__.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,4 +24,8 @@
2424
VPF002,
2525
VPF003,
2626
VPF004,
27+
)
28+
29+
from .tas import (
30+
CCF
2731
)

czsc/features/tas.py

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
"""
2+
技术指标因子
3+
"""
4+
import inspect
5+
import hashlib
6+
import pandas as pd
7+
8+
9+
def CCF(df, **kwargs):
10+
"""使用 CZSC 库中的 factor 识别因子,主要用于识别缠论/形态因子
11+
12+
:param df: 标准K线数据,DataFrame结构
13+
:param kwargs: 其他参数
14+
15+
- czsc_factor: dict, 缠论因子配置,样例:
16+
17+
{
18+
"signals_all": ["日线_D1_表里关系V230101_向上_任意_任意_0"],
19+
"signals_any": [],
20+
"signals_not": ["日线_D1_涨跌停V230331_涨停_任意_任意_0"],
21+
}
22+
23+
- freq: str, default '日线',K线级别
24+
- tag: str, default None,标签,用于区分不同的因子
25+
26+
:return: pd.DataFrame
27+
"""
28+
from czsc.objects import Factor
29+
from czsc.utils import format_standard_kline
30+
from czsc.traders.base import generate_czsc_signals
31+
from czsc.traders.sig_parse import get_signals_config
32+
33+
czsc_factor = kwargs.get('czsc_factor', None)
34+
freq = kwargs.get('freq', '日线')
35+
assert czsc_factor is not None and isinstance(czsc_factor, dict), "factor 参数必须指定"
36+
tag = kwargs.get('tag', hashlib.sha256(f"{czsc_factor}_{freq}".encode()).hexdigest().upper()[:6])
37+
38+
factor_name = inspect.stack()[0][3]
39+
factor_col = f'F#{factor_name}#{tag}'
40+
41+
czsc_factor = Factor.load(czsc_factor)
42+
signals_seq = czsc_factor.signals_all + czsc_factor.signals_any + czsc_factor.signals_not
43+
signals_config = get_signals_config([x.signal for x in signals_seq])
44+
45+
bars = format_standard_kline(df, freq=freq)
46+
dfs = generate_czsc_signals(bars, signals_config, init_n=300, sdt=bars[0].dt, df=True)
47+
dfs[factor_col] = dfs.apply(czsc_factor.is_match, axis=1).astype(int)
48+
49+
df = pd.merge(df, dfs[['dt', factor_col]], on='dt', how='left')
50+
df[factor_col] = df[factor_col].fillna(0)
51+
return df

czsc/features/utils.py

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,26 @@ def rolling_scale(df: pd.DataFrame, col: str, window=300, min_periods=100, new_c
187187
return df
188188

189189

190+
def rolling_tanh(df: pd.DataFrame, col: str, window=300, min_periods=100, new_col=None, **kwargs):
191+
"""对序列进行滚动 tanh 变换
192+
193+
双曲正切函数:https://baike.baidu.com/item/%E5%8F%8C%E6%9B%B2%E6%AD%A3%E5%88%87%E5%87%BD%E6%95%B0/15469414
194+
195+
:param df: pd.DataFrame, 待计算的数据
196+
:param col: str, 待计算的列
197+
:param window: int, 滚动窗口大小, 默认为300
198+
:param min_periods: int, 最小计算周期, 默认为100
199+
:param new_col: str, 新列名,默认为 None, 表示使用 f'{col}_scale' 作为新列名
200+
"""
201+
if kwargs.get("copy", False):
202+
df = df.copy()
203+
new_col = new_col if new_col else f'{col}_tanh'
204+
df = df.sort_values("dt", ascending=True).reset_index(drop=True)
205+
df[new_col] = df[col].rolling(window=window, min_periods=min_periods).apply(lambda x: np.tanh(scale(x))[-1]) # type: ignore
206+
df[new_col] = df[new_col].fillna(0)
207+
return df
208+
209+
190210
def rolling_slope(df: pd.DataFrame, col: str, window=300, min_periods=100, new_col=None, **kwargs):
191211
"""计算序列的滚动斜率
192212
@@ -234,3 +254,77 @@ def __lr_slope(x):
234254

235255
df[new_col] = df[new_col].fillna(0)
236256
return df
257+
258+
259+
def feature_adjust_V230101(df: pd.DataFrame, fcol, **kwargs):
260+
"""特征调整函数:对特征进行调整,使其符合持仓权重的定义
261+
262+
方法说明:对因子进行滚动相关系数计算,然后对因子值用 maxabs_scale 进行归一化,最后乘以滚动相关系数的符号
263+
264+
:param df: pd.DataFrame, 必须包含 dt、symbol、price 列,以及因子列
265+
:param fcol: str 因子列名
266+
:param kwargs: dict
267+
"""
268+
window = kwargs.get("window", 1000)
269+
min_periods = kwargs.get("min_periods", 200)
270+
271+
df = df.copy().sort_values("dt", ascending=True).reset_index(drop=True)
272+
df['n1b'] = df['price'].shift(-1) / df['price'] - 1
273+
df['corr'] = df[fcol].rolling(window=window, min_periods=min_periods).corr(df['n1b'])
274+
df['corr'] = df['corr'].shift(5).fillna(0)
275+
276+
df = rolling_scale(df, col=fcol, window=window, min_periods=min_periods,
277+
new_col='weight', method='maxabs_scale', copy=True)
278+
df['weight'] = df['weight'] * np.sign(df['corr'])
279+
280+
df.drop(['n1b', 'corr'], axis=1, inplace=True)
281+
return df
282+
283+
284+
def feature_adjust_V240323(df: pd.DataFrame, fcol, **kwargs):
285+
"""特征调整函数:对特征进行调整,使其符合持仓权重的定义
286+
287+
方法说明:对因子进行滚动相关系数计算,然后对因子值用 scale + tanh 进行归一化,最后乘以滚动相关系数的符号
288+
289+
:param df: pd.DataFrame, 必须包含 dt、symbol、price 列,以及因子列
290+
:param fcol: str 因子列名
291+
:param kwargs: dict
292+
"""
293+
window = kwargs.get("window", 1000)
294+
min_periods = kwargs.get("min_periods", 200)
295+
296+
df = df.copy().sort_values("dt", ascending=True).reset_index(drop=True)
297+
df['n1b'] = df['price'].shift(-1) / df['price'] - 1
298+
df['corr'] = df[fcol].rolling(window=window, min_periods=min_periods).corr(df['n1b'])
299+
df['corr'] = df['corr'].shift(5).fillna(0)
300+
301+
df = rolling_tanh(df, col=fcol, window=window, min_periods=min_periods, new_col='weight')
302+
df['weight'] = df['weight'] * np.sign(df['corr'])
303+
304+
df.drop(['n1b', 'corr'], axis=1, inplace=True)
305+
return df
306+
307+
308+
def feature_adjust(df: pd.DataFrame, fcol, method, **kwargs):
309+
"""特征调整函数:对特征进行调整,使其符合持仓权重的定义
310+
311+
:param df: pd.DataFrame, 待调整的数据
312+
:param fcol: str, 因子列名
313+
:param method: str, 调整方法
314+
315+
- V230101: 对因子进行滚动相关系数计算,然后对因子值用 maxabs_scale 进行归一化,最后乘以滚动相关系数的符号
316+
- V240323: 对因子进行滚动相关系数计算,然后对因子值用 scale + tanh 进行归一化,最后乘以滚动相关系数的符号
317+
318+
:param kwargs: dict
319+
320+
- window: int, 滚动窗口大小
321+
- min_periods: int, 最小计算周期
322+
323+
:return: pd.DataFrame, 新增 weight 列
324+
"""
325+
if method == "V230101":
326+
return feature_adjust_V230101(df, fcol, **kwargs)
327+
elif method == "V240323":
328+
return feature_adjust_V240323(df, fcol, **kwargs)
329+
else:
330+
raise ValueError(f"Unknown method: {method}")

czsc/utils/__init__.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,20 +10,21 @@
1010
from .echarts_plot import kline_pro, heat_map
1111
from .word_writer import WordWriter
1212
from .corr import nmi_matrix, single_linear, cross_sectional_ic
13-
from .bar_generator import BarGenerator, freq_end_time, resample_bars
13+
from .bar_generator import BarGenerator, freq_end_time, resample_bars, format_standard_kline
1414
from .bar_generator import is_trading_time, get_intraday_times, check_freq_and_market
1515
from .io import dill_dump, dill_load, read_json, save_json
1616
from .sig import check_pressure_support, check_gap_info, is_bis_down, is_bis_up, get_sub_elements, is_symmetry_zs
1717
from .sig import same_dir_counts, fast_slow_cross, count_last_same, create_single_signal
1818
from .plotly_plot import KlineChart
1919
from .trade import cal_trade_price, update_nbars, update_bbars, update_tbars, risk_free_returns, resample_to_daily
2020
from .cross import CrossSectionalPerformance, cross_sectional_ranker
21-
from .stats import daily_performance, net_value_stats, subtract_fee, weekly_performance, holds_performance
21+
from .stats import daily_performance, net_value_stats, subtract_fee, weekly_performance, holds_performance, top_drawdowns
2222
from .signal_analyzer import SignalAnalyzer, SignalPerformance
2323
from .cache import home_path, get_dir_size, empty_cache_path, DiskCache, disk_cache
2424
from .index_composition import index_composition
2525
from .data_client import DataClient, set_url_token, get_url_token
2626
from .oss import AliyunOSS
27+
from .optuna import optuna_study, optuna_good_params
2728

2829

2930
sorted_freqs = ['Tick', '1分钟', '2分钟', '3分钟', '4分钟', '5分钟', '6分钟', '10分钟', '12分钟',

czsc/utils/bar_generator.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,30 @@ def get_intraday_times(freq='1分钟', market="A股"):
3939
return freq_market_times[f"{freq}_{market}"]
4040

4141

42+
def format_standard_kline(df: pd.DataFrame, freq: str):
43+
"""格式化标准K线数据为 CZSC 标准数据结构 RawBar 列表
44+
45+
:param df: 标准K线数据,DataFrame结构
46+
47+
=================== ========= ====== ======= ====== ===== =========== ===========
48+
dt symbol open close high low vol amount
49+
=================== ========= ====== ======= ====== ===== =========== ===========
50+
2023-11-17 00:00:00 689009.SH 33.52 33.41 33.69 33.38 1.97575e+06 6.61661e+07
51+
2023-11-20 00:00:00 689009.SH 33.4 32.91 33.45 32.25 5.15016e+06 1.68867e+08
52+
=================== ========= ====== ======= ====== ===== =========== ===========
53+
54+
:param freq: K线级别
55+
:return: list of RawBar
56+
"""
57+
# from czsc.objects import RawBar, Freq
58+
bars = []
59+
for i, row in df.iterrows():
60+
bar = RawBar(id=i, symbol=row['symbol'], dt=row['dt'], open=row['open'], close=row['close'],
61+
high=row['high'], low=row['low'], vol=row['vol'], amount=row['amount'], freq=Freq(freq))
62+
bars.append(bar)
63+
return bars
64+
65+
4266
def check_freq_and_market(time_seq: List[AnyStr], freq: Optional[AnyStr] = None):
4367
"""检查时间序列是否为同一周期,是否为同一市场
4468

0 commit comments

Comments
 (0)