Skip to content

Commit

Permalink
V0.9.13 更新一批代码 (waditu#133)
Browse files Browse the repository at this point in the history
* 0.9.13 update

* 0.9.13 version start

* 0.9.13 fix bug

* 0.9.13 update

* 0.9.13 fix sig

* 0.9.13 新增两个信号函数

* 0.9.13 dummy 方法优化

* 0.9.13 add flow

* 0.9.13 新增三买信号

* 0.9.13 新增BE信号

* 0.9.13 新增BE信号

* 0.9.13 新增板块效应分析功能

* 0.9.13 新增信号函数

* 0.9.13 单品种复盘辅助

* 0.9.13 新增笔的表里关系信号

* 0.9.13 新增笔的表里关系信号

* 0.9.13 新增笔的表里关系信号

* 0.9.13 新增信号:100以内质数时序窗口辅助笔结束判断

* 0.9.13 新增交易价格敏感性分析辅助工具

* 0.9.13 SignalsPerformance

* 0.9.13 对接掘金终端

* 0.9.13 对接掘金终端

* 0.9.13 update

* 0.9.13 优化 plotly K线绘制

* 0.9.13 新增基础信号函数

* 0.9.13 新增 get_raw_bars

* 0.9.13 新增 dummy backtest

* 0.9.13 新增 dummy backtest

* 0.9.13 新增掘金终端对接
  • Loading branch information
zengbin93 authored Mar 23, 2023
1 parent 2d2bc74 commit 6d9d861
Show file tree
Hide file tree
Showing 29 changed files with 1,657 additions and 653 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.12 ]
branches: [ master, V0.9.13 ]
pull_request:
branches: [ master ]

Expand Down
7 changes: 5 additions & 2 deletions czsc/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,18 @@
from czsc.utils.cache import home_path, get_dir_size, empty_cache_path
from czsc.traders import CzscTrader, CzscSignals, generate_czsc_signals, check_signals_acc, get_unique_signals
from czsc.traders import PairsPerformance, combine_holds_and_pairs, combine_dates_and_pairs, stock_holds_performance
from czsc.traders import DummyBacktest
from czsc.strategies import CzscStrategyBase
from czsc.utils import KlineChart, BarGenerator, resample_bars, dill_dump, dill_load, read_json, save_json
from czsc.utils import get_sub_elements, get_py_namespace, freqs_sorted, x_round, import_by_name, create_grid_params
from czsc.utils import cal_trade_price
from czsc.sensors import holds_concepts_effect, StocksDaySensor, ThsConceptsSensor, SignalsPerformance


__version__ = "0.9.12"
__version__ = "0.9.13"
__author__ = "zengbin93"
__email__ = "zeng_bin8888@163.com"
__date__ = "20230312"
__date__ = "20230314"


if envs.get_welcome():
Expand Down
11 changes: 5 additions & 6 deletions czsc/analyze.py
Original file line number Diff line number Diff line change
Expand Up @@ -374,25 +374,24 @@ def last_bi_extend(self):

@property
def finished_bis(self) -> List[BI]:
"""返回当下基本确认完成的笔列表"""
"""已完成的笔"""
if not self.bi_list:
return []
else:
if self.last_bi_extend:
return self.bi_list[:-1]
if len(self.bars_ubi) < 5:
return self.bi_list[:-1]
return self.bi_list

@property
def ubi_fxs(self) -> List[FX]:
"""返回当下基本确认完成的笔列表"""
"""bars_ubi 中的分型"""
if not self.bars_ubi:
return []
else:
return check_fxs(self.bars_ubi)

@property
def fx_list(self) -> List[FX]:
"""返回当下基本确认完成的笔列表"""
"""分型列表,包括 bars_ubi 中的分型"""
fxs = []
for bi_ in self.bi_list:
fxs.extend(bi_.fxs[1:])
Expand Down
116 changes: 93 additions & 23 deletions czsc/connectors/gm_connector.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
from loguru import logger
try:
from gm.api import *
except:
except ModuleNotFoundError:
logger.warning(f"gm 模块没有安装")
from datetime import datetime, timedelta
from collections import OrderedDict
Expand All @@ -23,6 +23,7 @@
from czsc.utils import qywx as wx
from czsc.utils import BarGenerator
from czsc.objects import RawBar, Freq
from czsc.fsa import push_message


dt_fmt = "%Y-%m-%d %H:%M:%S"
Expand All @@ -43,6 +44,37 @@ def set_gm_token(token):
set_token(gm_token)


def gm_push_message(msg: str, msg_type: str = 'text', **kwargs):
"""统一飞书和企业微信消息推送服务
:param msg:
:param msg_type:
:param kwargs:
:return:
"""
wx_key = kwargs.get('wx_key')
fs_app = kwargs.get('fs_app')
if wx_key:
try:
if msg_type == 'text':
wx.push_text(msg, wx_key)
elif msg_type == 'file':
wx.push_file(msg, wx_key)
else:
logger.debug(f"企业微信通道仅支持 text 和 file 类型,当前类型为 {msg_type}")
except Exception as _e:
logger.debug(f"企业微信消息推送失败:{_e}")

if fs_app:
try:
push_message(msg=msg, msg_type=msg_type,
feishu_app_secret=fs_app['feishu_app_secret'],
feishu_app_id=fs_app['feishu_app_id'],
feishu_members=fs_app['feishu_members'])
except Exception as _e:
logger.debug(f"飞书消息推送失败:{_e}")


def is_trade_date(dt):
"""判断 dt 时刻是不是交易日期"""
dt = pd.to_datetime(dt)
Expand Down Expand Up @@ -109,6 +141,48 @@ def get_kline(symbol, end_time, freq='60s', count=33000, adjust=ADJUST_PREV):
return format_kline(df, freq_map_[freq])


def to_tz_datetime(dt):
"""格式化日期时间"""
end_dt = pd.to_datetime(dt, utc=True)
end_dt = end_dt.tz_convert('dateutil/PRC')
# 时区转换之后,要减去8个小时才是设置的时间
end_dt = end_dt - timedelta(hours=8)
return end_dt


def get_raw_bars(symbol, freq, sdt, edt, fq='前复权', **kwargs):
"""获取 CZSC 库定义的标准 RawBar 对象列表
:param symbol: 标的代码
:param freq: 周期
:param sdt: 开始时间
:param edt: 结束时间
:param fq: 除权类型
:param kwargs:
:return:
"""
freq = Freq(freq)
sdt = to_tz_datetime(sdt)
edt = to_tz_datetime(edt)

freq_map_ = {'60s': Freq.F1, '300s': Freq.F5, '900s': Freq.F15, '1800s': Freq.F30,
'3600s': Freq.F60, '1d': Freq.D}
freq_map_ = {v: k for k, v in freq_map_.items()}
period = freq_map_[freq]

if fq == '前复权':
adjust = ADJUST_PREV
elif fq == '后复权':
adjust = ADJUST_POST
else:
assert fq == '不复权'
adjust = ADJUST_NONE

bars = get_kline(symbol, freq=period, count=33000, end_time=edt, adjust=adjust)
bars = [bar for bar in bars if bar.dt >= sdt]
return bars


def get_init_bg(symbol: str,
end_dt: [str, datetime],
base_freq: str,
Expand Down Expand Up @@ -215,7 +289,7 @@ def on_order_status(context, order):

logger.info(msg.replace("\n", " - ").replace('*', ""))
if context.mode != MODE_BACKTEST and order.status in [1, 3, 5, 8, 9, 12]:
wx.push_text(content=str(msg), key=context.wx_key)
gm_push_message(str(msg), msg_type='text', **context.push_msg_conf)


def on_execution_report(context, execrpt):
Expand Down Expand Up @@ -243,7 +317,7 @@ def on_execution_report(context, execrpt):

logger.info(msg.replace("\n", " - ").replace('*', ""))
if context.mode != MODE_BACKTEST and execrpt.exec_type in [1, 5, 6, 8, 12, 19]:
wx.push_text(content=str(msg), key=context.wx_key)
gm_push_message(str(msg), msg_type='text', **context.push_msg_conf)


def on_backtest_finished(context, indicator):
Expand All @@ -254,10 +328,7 @@ def on_backtest_finished(context, indicator):
https://www.myquant.cn/docs/python/python_object_trade#bd7f5adf22081af5
:return:
"""
wx_key = context.wx_key
symbols = context.symbols
data_path = context.data_path

logger.info(str(indicator))
logger.info("回测结束 ... ")
cash = context.account().cash
Expand Down Expand Up @@ -295,7 +366,7 @@ def on_backtest_finished(context, indicator):
content = ""
for k, v in row.items():
content += "{}: {}\n".format(k, v)
wx.push_text(content=content, key=wx_key)
gm_push_message(content, msg_type='text', **context.push_msg_conf)


def on_error(context, code, info):
Expand All @@ -305,7 +376,7 @@ def on_error(context, code, info):
msg = "{} - {}".format(code, info)
logger.warning(msg)
if context.mode != MODE_BACKTEST:
wx.push_text(content=msg, key=context.wx_key)
gm_push_message(msg, msg_type='text', **context.push_msg_conf)


def on_account_status(context, account):
Expand All @@ -323,7 +394,7 @@ def on_account_status(context, account):
msg = f"{str(account)}"
logger.warning(msg)
if context.mode != MODE_BACKTEST:
wx.push_text(content=msg, key=context.wx_key)
gm_push_message(msg, msg_type='text', **context.push_msg_conf)


def is_order_exist(context, symbol, side) -> bool:
Expand Down Expand Up @@ -447,7 +518,7 @@ def on_bar(context, bars):
for bar_ in bars_new:
trader.update(bar_)

# sync_long_position(context, trader)
sync_long_position(context, trader)


def report_account_status(context):
Expand Down Expand Up @@ -481,7 +552,7 @@ def report_account_status(context):
f"可用资金:{int(cash.available)}\n" \
f"浮动盈亏:{int(cash.fpnl)}\n" \
f"标的数量:{len(positions)}\n"
wx.push_text(msg.strip("\n *"), key=context.wx_key)
gm_push_message(msg, msg_type='text', **context.push_msg_conf)

results = []
for symbol, info in context.symbols_info.items():
Expand All @@ -491,7 +562,9 @@ def report_account_status(context):

row = {'交易标的': symbol, '标的名称': name,
'最新时间': trader.end_dt.strftime(dt_fmt),
'最新价格': trader.latest_price}
'最新价格': trader.latest_price,
'集成仓位': trader.get_ensemble_pos(),
}

if "日线" in trader.kas.keys():
bar1, bar2 = trader.kas['日线'].bars_raw[-2:]
Expand All @@ -508,10 +581,10 @@ def report_account_status(context):
results.append(row)

df = pd.DataFrame(results)
df.sort_values(['多头持仓', '多头收益'], ascending=False, inplace=True, ignore_index=True)
df.sort_values(['集成仓位'], ascending=False, inplace=True, ignore_index=True)
file_xlsx = os.path.join(context.data_path, f"holds_{context.now.strftime('%Y%m%d_%H%M')}.xlsx")
df.to_excel(file_xlsx, index=False)
wx.push_file(file_xlsx, key=context.wx_key)
gm_push_message(file_xlsx, msg_type='file', **context.push_msg_conf)
os.remove(file_xlsx)

# 提示非策略交易标的持仓
Expand Down Expand Up @@ -584,7 +657,7 @@ def process_out_of_symbols(context):
# order_target_volume(symbol=symbol, volume=0, position_side=PositionSide_Long,
# order_type=OrderType_Limit, price=p.price, account=account.id)
if oos:
wx.push_text(f"不在交易列表的持仓股:{', '.join(oos)}", context.wx_key)
gm_push_message(f"不在交易列表的持仓股:{', '.join(oos)}", msg_type='text', **context.push_msg_conf)


def init_context_universal(context, name):
Expand Down Expand Up @@ -615,11 +688,7 @@ def init_context_universal(context, name):


def init_context_env(context):
"""通用 context 初始化:2、读入环境变量
:param context:
"""
context.wx_key = os.environ['wx_key']
"""通用 context 初始化:2、读入环境变量"""
context.account_id = os.environ.get('account_id', '')
if context.mode != MODE_BACKTEST:
assert len(context.account_id) > 10, "非回测模式,必须设置 account_id "
Expand Down Expand Up @@ -671,16 +740,17 @@ def init_context_traders(context, symbols: List[str], strategy):
dill.dump(trader, open(file_trader, 'wb'))

symbols_info[symbol]['trader'] = trader
logger.info("{} Trader 构建成功,最新时间:{},多仓:{}".format(symbol, trader.end_dt, trader.long_pos.pos))
logger.info("{} Trader 构建成功,最新时间:{},多仓:{}".format(symbol, trader.end_dt, trader.get_ensemble_pos('mean')))

except Exception as e1:
del symbols_info[symbol]
logger.exception(f"{e1}{symbol} - {context.stocks.get(symbol, '无名')} 初始化失败,当前时间:{context.now}")
context.symbols_info = symbols_info

# 订阅K线数据
subscribe(",".join(symbols_info.keys()), frequency=frequency, count=300, wait_group=False)
logger.info(f"订阅成功数量:{len(symbols_info)}")
logger.info(f"订阅成功数量:{len(symbols_info)},订阅K线周期:{frequency}")
logger.info(f"交易标的配置:{symbols_info}")
context.symbols_info = symbols_info


def init_context_schedule(context):
Expand Down
4 changes: 3 additions & 1 deletion czsc/sensors/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@
turn_over_rate,
discretizer,
compound_returns,
get_index_beta
get_index_beta,
holds_concepts_effect,
SignalsPerformance,
)


49 changes: 46 additions & 3 deletions czsc/sensors/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,10 @@
email: zeng_bin8888@163.com
create_dt: 2021/11/17 18:50
"""
import os
import traceback
import pandas as pd
import numpy as np
from tqdm import tqdm
from datetime import timedelta
from collections import Counter
from typing import Callable, List, AnyStr
from sklearn.preprocessing import KBinsDiscretizer

Expand Down Expand Up @@ -259,3 +257,48 @@ def report(self, file_xlsx=None):
df_.to_excel(writer, sheet_name=sn, index=False)
writer.close()
return res


def holds_concepts_effect(holds: pd.DataFrame, concepts: dict, top_n=20, min_n=3, **kwargs):
"""股票持仓列表的板块效应
原理概述:在选股时,如果股票的概念板块与组合中的其他股票的概念板块有重合,那么这个股票的表现会更好。
:param holds: 组合股票池数据,样例:
成分日期 证券代码 n1b 持仓权重
0 2020-01-02 000001.SZ 183.758194 0.001232
1 2020-01-02 000002.SZ -156.633896 0.001232
2 2020-01-02 000063.SZ 310.296204 0.001232
3 2020-01-02 000066.SZ -131.824997 0.001232
4 2020-01-02 000069.SZ -38.561699 0.001232
:param concepts: 股票的概念板块,样例:
{
'002507.SZ': ['电子商务', '超级品牌', '国企改革'],
'002508.SZ': ['家用电器', '杭州亚运会', '恒大概念']
}
:param top_n: 选取前 n 个密集概念
:param min_n: 单股票至少要有 n 个概念在 top_n 中
:return: 过滤后的选股结果,每个时间点的 top_n 概念
"""
if kwargs.get('copy', True):
holds = holds.copy()

holds['概念板块'] = holds['证券代码'].map(concepts).fillna('')
holds['概念数量'] = holds['概念板块'].apply(len)
holds = holds[holds['概念数量'] > 0]

new_holds = []
dt_key_concepts = {}
for dt, dfg in tqdm(holds.groupby('成分日期'), desc='计算板块效应'):
# 计算密集出现的概念
key_concepts = [k for k, v in Counter([x for y in dfg['概念板块'] for x in y]).most_common(top_n)]
dt_key_concepts[dt] = key_concepts

# 计算在密集概念中出现次数超过min_n的股票
dfg['强势概念'] = dfg['概念板块'].apply(lambda x: ','.join(set(x) & set(key_concepts)))
sel = dfg[dfg['强势概念'].apply(lambda x: len(x.split(',')) >= min_n)]
new_holds.append(sel)

dfh = pd.concat(new_holds, ignore_index=True)
dfk = pd.DataFrame([{"成分日期": k, '强势概念': v} for k, v in dt_key_concepts.items()])
return dfh, dfk
Loading

0 comments on commit 6d9d861

Please sign in to comment.