Skip to content

Commit

Permalink
V0.9.47 更新一批代码 (#191)
Browse files Browse the repository at this point in the history
* 0.9.47 first commit

* 0.9.47 新增 clear cache 方法

* 0.9.47 update

* 0.9.47 update

* 0.9.47 fix minites split

* 0.9.47 update

* 0.9.47 coo 优化缓存逻辑

* 0.9.47 update

* 0.9.47 update

* 0.9.47 新增一个信号函数

* 0.9.47 合并XL贡献的信号函数

* 0.9.47 update

* 0.9.47 update

* 09.47 最大回撤分析组件,新增分位数分析

* 0.9.47 update

* 0.9.47 update tqsdk
  • Loading branch information
zengbin93 authored Apr 10, 2024
1 parent ff0eef0 commit 148a3a1
Show file tree
Hide file tree
Showing 23 changed files with 660 additions and 355 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.46 ]
branches: [ master, V0.9.47 ]
pull_request:
branches: [ master ]

Expand Down
6 changes: 4 additions & 2 deletions czsc/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@
home_path,
DiskCache,
disk_cache,
clear_cache,
get_dir_size,
empty_cache_path,
print_df_sample,
Expand Down Expand Up @@ -154,12 +155,13 @@
rolling_slope,
rolling_tanh,
feature_adjust,
normalize_corr,
)

__version__ = "0.9.46"
__version__ = "0.9.47"
__author__ = "zengbin93"
__email__ = "[email protected]"
__date__ = "20240318"
__date__ = "20240328"


def welcome():
Expand Down
3 changes: 1 addition & 2 deletions czsc/cmd.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,7 @@ def czsc():


@czsc.command()
def aphorism():
def a():
"""随机输出一条缠中说禅良言警句"""
from czsc.aphorism import print_one

print_one()
74 changes: 51 additions & 23 deletions czsc/connectors/cooperation.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,46 +56,52 @@ def get_symbols(name, **kwargs):
:return:
"""
if name == "股票":
df = dc.stock_basic(nobj=1, status=1)
df = dc.stock_basic(nobj=1, status=1, ttl=3600 * 6)
symbols = [f"{row['code']}#STOCK" for _, row in df.iterrows()]
return symbols

if name == "ETF":
df = dc.etf_basic(v=2, fields='code,name')
dfk = dc.pro_bar(trade_date="2023-11-17", asset="e", v=2)
df = dc.etf_basic(v=2, fields='code,name', ttl=3600 * 6)
dfk = dc.pro_bar(trade_date="2024-04-02", asset="e", v=2)
df = df[df['code'].isin(dfk['code'])].reset_index(drop=True)
symbols = [f"{row['code']}#ETF" for _, row in df.iterrows()]
return symbols

if name == "A股指数":
# 指数 https://s0cqcxuy3p.feishu.cn/wiki/KuSAweAAhicvsGk9VPTc1ZWKnAd
df = dc.index_basic(v=2, market='SSE,SZSE')
df = dc.index_basic(v=2, market='SSE,SZSE', ttl=3600 * 6)
symbols = [f"{row['code']}#INDEX" for _, row in df.iterrows()]
return symbols

if name == "南华指数":
df = dc.index_basic(v=2, market='NH')
df = dc.index_basic(v=2, market='NH', ttl=3600 * 6)
symbols = [row['code'] for _, row in df.iterrows()]
return symbols

if name == "期货主力":
kline = dc.future_klines(trade_date="20231101")
kline = dc.future_klines(trade_date="20240402", ttl=3600 * 6)
return kline['code'].unique().tolist()

if name.upper() == "ALL":
symbols = get_symbols("股票") + get_symbols("ETF")
symbols += get_symbols("A股指数") + get_symbols("南华指数") + get_symbols("期货主力")
return symbols

raise ValueError(f"{name} 分组无法识别,获取标的列表失败!")


def get_min_future_klines(code, sdt, edt, freq='1m'):
"""分段获取期货1分钟K线后合并"""
# dates = pd.date_range(start=sdt, end=edt, freq='1M')
dates = pd.date_range(start=sdt, end=edt, freq='30D')
dates = pd.date_range(start=sdt, end=edt, freq='120D')

dates = [d.strftime('%Y%m%d') for d in dates] + [sdt, edt]
dates = sorted(list(set(dates)))

rows = []
for sdt_, edt_ in tqdm(zip(dates[:-1], dates[1:]), total=len(dates) - 1):
df = dc.future_klines(code=code, sdt=sdt_, edt=edt_, freq=freq)
ttl = 60 if pd.to_datetime(edt_).date() == datetime.now().date() else -1
df = dc.future_klines(code=code, sdt=sdt_, edt=edt_, freq=freq, ttl=ttl)
if df.empty:
continue
logger.info(f"{code}获取K线范围:{df['dt'].min()} - {df['dt'].max()}")
Expand All @@ -104,8 +110,19 @@ def get_min_future_klines(code, sdt, edt, freq='1m'):
df = pd.concat(rows, ignore_index=True)
df.rename(columns={'code': 'symbol'}, inplace=True)
df['dt'] = pd.to_datetime(df['dt'])

df = df.drop_duplicates(subset=['dt', 'symbol'], keep='last')

if code in ['SFIC9001', 'SFIF9001', 'SFIH9001']:
# 股指:仅保留 09:31 - 11:30, 13:01 - 15:00
dt1 = datetime.strptime("09:31:00", "%H:%M:%S")
dt2 = datetime.strptime("11:30:00", "%H:%M:%S")
c1 = (df['dt'].dt.time >= dt1.time()) & (df['dt'].dt.time <= dt2.time())

dt3 = datetime.strptime("13:01:00", "%H:%M:%S")
dt4 = datetime.strptime("15:00:00", "%H:%M:%S")
c2 = (df['dt'].dt.time >= dt3.time()) & (df['dt'].dt.time <= dt4.time())

df = df[c1 | c2].copy().reset_index(drop=True)
return df


Expand All @@ -119,10 +136,14 @@ def get_raw_bars(symbol, freq, sdt, edt, fq='前复权', **kwargs):
:param edt: 结束时间
:param fq: 除权类型,可选值:'前复权', '后复权', '不复权'
:param kwargs:
:return:
:return: RawBar 对象列表 or DataFrame
>>> from czsc.connectors import cooperation as coo
>>> df = coo.get_raw_bars(symbol="000001.SH#INDEX", freq="日线", sdt="2001-01-01", edt="2021-12-31", fq='后复权', raw_bars=False)
"""
freq = czsc.Freq(freq)
raw_bars = kwargs.get('raw_bars', True)
ttl = kwargs.get('ttl', -1)

if "SH" in symbol or "SZ" in symbol:
fq_map = {"前复权": "qfq", "后复权": "hfq", "不复权": None}
Expand All @@ -131,14 +152,17 @@ def get_raw_bars(symbol, freq, sdt, edt, fq='前复权', **kwargs):
code, asset = symbol.split("#")

if freq.value.endswith('分钟'):
df = dc.pro_bar(code=code, sdt=sdt, edt=edt, freq='min', adj=adj, asset=asset[0].lower(), v=2)
df = dc.pro_bar(code=code, sdt=sdt, edt=edt, freq='min', adj=adj, asset=asset[0].lower(), v=2, ttl=ttl)
df = df[~df['dt'].str.endswith("09:30:00")].reset_index(drop=True)
else:
df = dc.pro_bar(code=code, sdt=sdt, edt=edt, freq='day', adj=adj, asset=asset[0].lower(), v=2)
df.rename(columns={'code': 'symbol'}, inplace=True)
df['dt'] = pd.to_datetime(df['dt'])
return czsc.resample_bars(df, target_freq=freq, raw_bars=raw_bars, base_freq='1分钟')

df.rename(columns={'code': 'symbol'}, inplace=True)
df['dt'] = pd.to_datetime(df['dt'])
return czsc.resample_bars(df, target_freq=freq, raw_bars=raw_bars)
else:
df = dc.pro_bar(code=code, sdt=sdt, edt=edt, freq='day', adj=adj, asset=asset[0].lower(), v=2, ttl=ttl)
df.rename(columns={'code': 'symbol'}, inplace=True)
df['dt'] = pd.to_datetime(df['dt'])
return czsc.resample_bars(df, target_freq=freq, raw_bars=raw_bars)

if symbol.endswith("9001"):
# https://s0cqcxuy3p.feishu.cn/wiki/WLGQwJLWQiWPCZkPV7Xc3L1engg
Expand All @@ -148,19 +172,23 @@ def get_raw_bars(symbol, freq, sdt, edt, fq='前复权', **kwargs):
freq_rd = '1m' if freq.value.endswith('分钟') else '1d'
if freq.value.endswith('分钟'):
df = get_min_future_klines(code=symbol, sdt=sdt, edt=edt, freq='1m')
df['amount'] = df['vol'] * df['close']
df = df[['symbol', 'dt', 'open', 'close', 'high', 'low', 'vol', 'amount']].copy().reset_index(drop=True)
df['dt'] = pd.to_datetime(df['dt'])
return czsc.resample_bars(df, target_freq=freq, raw_bars=raw_bars, base_freq='1分钟')

else:
df = dc.future_klines(code=symbol, sdt=sdt, edt=edt, freq=freq_rd)
df = dc.future_klines(code=symbol, sdt=sdt, edt=edt, freq=freq_rd, ttl=ttl)
df.rename(columns={'code': 'symbol'}, inplace=True)

df['amount'] = df['vol'] * df['close']
df = df[['symbol', 'dt', 'open', 'close', 'high', 'low', 'vol', 'amount']].copy().reset_index(drop=True)
df['dt'] = pd.to_datetime(df['dt'])
return czsc.resample_bars(df, target_freq=freq, raw_bars=raw_bars)
df['amount'] = df['vol'] * df['close']
df = df[['symbol', 'dt', 'open', 'close', 'high', 'low', 'vol', 'amount']].copy().reset_index(drop=True)
df['dt'] = pd.to_datetime(df['dt'])
return czsc.resample_bars(df, target_freq=freq, raw_bars=raw_bars)

if symbol.endswith(".NH"):
if freq != Freq.D:
raise ValueError("南华指数只支持日线数据")
df = dc.nh_daily(code=symbol, sdt=sdt, edt=edt)
df = dc.nh_daily(code=symbol, sdt=sdt, edt=edt, ttl=ttl)

raise ValueError(f"symbol {symbol} 无法识别,获取数据失败!")

Expand Down
53 changes: 28 additions & 25 deletions czsc/connectors/tq_connector.py
Original file line number Diff line number Diff line change
Expand Up @@ -183,26 +183,6 @@ def format_kline(df, freq=Freq.F1):
}


def is_trade_time(trade_time: Optional[str] = None):
"""判断当前是否是交易时间"""
if trade_time is None:
trade_time = datetime.now().strftime("%H:%M:%S")

if trade_time >= "09:00:00" and trade_time <= "11:30:00":
return True

if trade_time >= "13:00:00" and trade_time <= "15:00:00":
return True

if trade_time >= "21:00:00" and trade_time <= "23:59:59":
return True

if trade_time >= "00:00:00" and trade_time <= "02:30:00":
return True

return False


def get_daily_backup(api: TqApi, **kwargs):
"""获取每日账户中需要备份的信息
Expand Down Expand Up @@ -238,6 +218,18 @@ def get_daily_backup(api: TqApi, **kwargs):
return backup


def is_trade_time(quote):
"""判断当前是否是交易时间"""
trade_time = pd.Timestamp.now().strftime("%H:%M:%S")
times = quote["trading_time"]['day'] + quote["trading_time"]['night']

for sdt, edt in times:
if trade_time >= sdt and trade_time <= edt:
logger.info(f"当前时间:{trade_time},交易时间:{sdt} - {edt}")
return True
return False


def adjust_portfolio(api: TqApi, portfolio, account=None, **kwargs):
"""调整账户组合
Expand All @@ -255,11 +247,21 @@ def adjust_portfolio(api: TqApi, portfolio, account=None, **kwargs):
:param kwargs: dict, 其他参数
"""
timeout = kwargs.get("timeout", 600)
start_time = datetime.now()

symbol_infos = {}
for symbol, conf in portfolio.items():
quote = api.get_quote(symbol)
if not is_trade_time(quote):
logger.warning(f"{symbol} 当前时间不是交易时间,跳过调仓")
continue

lots = conf.get("target_volume", None)
if lots is None:
logger.warning(f"{symbol} 目标手数为 None,跳过调仓")
continue

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

Expand All @@ -281,16 +283,17 @@ def adjust_portfolio(api: TqApi, portfolio, account=None, **kwargs):

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

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

if all(completed):
break

if kwargs.get("close_api", True):
api.close()
if (datetime.now() - start_time).seconds > timeout:
logger.error(f"调仓超时,已运行 {timeout} 秒")
break

return api
51 changes: 51 additions & 0 deletions czsc/features/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -256,6 +256,52 @@ def __lr_slope(x):
return df


def normalize_corr(df: pd.DataFrame, fcol, ycol=None, **kwargs):
"""标准化因子与收益相关性为正数
方法说明:对因子进行滚动相关系数计算,因子乘以滚动相关系数的符号
**注意:**
1. simple 模式下,计算过程有一定的未来信息泄露,在回测中使用时需要注意
2. rolling 模式下,计算过程依赖 window 参数,有可能调整后相关性为负数
:param df: pd.DataFrame, 必须包含 dt、symbol、price 列,以及因子列
:param fcol: str 因子列名
:param kwargs: dict
- window: int, 滚动窗口大小
- min_periods: int, 最小计算周期
- mode: str, 计算方法, rolling 表示使用滚动调整相关系数,simple 表示使用镜像反转相关系数
- copy: bool, 是否复制 df
:return: pd.DataFrame
"""
window = kwargs.get("window", 1000)
min_periods = kwargs.get("min_periods", 5)
mode = kwargs.get("mode", "rolling")
if kwargs.get("copy", False):
df = df.copy()

df = df.sort_values(['symbol', 'dt'], ascending=True).reset_index(drop=True)
for symbol, dfg in df.groupby("symbol"):
dfg['ycol'] = dfg['price'].pct_change().shift(-1)

if mode.lower() == "rolling":
dfg['corr_sign'] = np.sign(dfg[fcol].rolling(window=window, min_periods=min_periods).corr(dfg['ycol']))
dfg[fcol] = (dfg['corr_sign'].shift(3) * dfg[fcol]).fillna(0)

elif mode.lower() == "simple":
corr_sign = np.sign(dfg[fcol].corr(dfg['ycol']))
dfg[fcol] = corr_sign * dfg[fcol]

else:
raise ValueError(f"Unknown mode: {mode}")

df.loc[df['symbol'] == symbol, fcol] = dfg[fcol]
return df


def feature_adjust_V230101(df: pd.DataFrame, fcol, **kwargs):
"""特征调整函数:对特征进行调整,使其符合持仓权重的定义
Expand Down Expand Up @@ -312,6 +358,7 @@ def feature_adjust(df: pd.DataFrame, fcol, method, **kwargs):
:param fcol: str, 因子列名
:param method: str, 调整方法
- KEEP: 直接使用原始因子值作为权重
- V230101: 对因子进行滚动相关系数计算,然后对因子值用 maxabs_scale 进行归一化,最后乘以滚动相关系数的符号
- V240323: 对因子进行滚动相关系数计算,然后对因子值用 scale + tanh 进行归一化,最后乘以滚动相关系数的符号
Expand All @@ -322,6 +369,10 @@ def feature_adjust(df: pd.DataFrame, fcol, method, **kwargs):
:return: pd.DataFrame, 新增 weight 列
"""
if method == "KEEP":
df["weight"] = df[fcol]
return df

if method == "V230101":
return feature_adjust_V230101(df, fcol, **kwargs)
elif method == "V240323":
Expand Down
15 changes: 9 additions & 6 deletions czsc/signals/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,6 @@
create_dt: 2021/11/21 17:48
describe: 信号函数
"""
# ======================================================================================================================
# 以下是 0.9.1 开始的新标准下实现的信号函数,规范定义:
# 1. 前缀3个字符区分信号类别
# 2. 后缀 V221107 之类的标识同一个信号函数的不同版本
# ======================================================================================================================

from czsc.signals.cxt import (
cxt_fx_power_V221107,
cxt_first_buy_V221126,
Expand Down Expand Up @@ -208,6 +202,7 @@
tas_macd_bc_V230804,
tas_macd_bc_ubi_V230804,
tas_slope_V231019,
tas_macd_bc_V240307,
)

from czsc.signals.pos import (
Expand Down Expand Up @@ -263,3 +258,11 @@
zdy_dif_V230528,
pressure_support_V240222,
)


from czsc.signals.xls import (
xl_bar_position_V240328,
xl_bar_trend_V240329,
xl_bar_trend_V240330,
xl_bar_trend_V240331,
)
Loading

0 comments on commit 148a3a1

Please sign in to comment.