# -*- coding: utf-8 -*-
"""
author: zengbin93
email: zeng_bin8888@163.com
create_dt: 2023/3/29 10:04
describe:
"""
import re
from loguru import logger
from parse import parse
from typing import List, Dict
from czsc.objects import Signal
from czsc.utils import import_by_name, sorted_freqs
[docs]class SignalsParser:
"""解析一串信号,生成信号函数配置"""
def __init__(self, signals_module: str = 'czsc.signals'):
"""
函数执行逻辑:
1. 将传入的 signals_module 参数赋给实例变量 self.signals_module,代表信号函数所在的模块,默认模块是czsc库的signals模块。
2. 使用 import_by_name 函数导入了指定名称的模块 signals_module。
3. 对于导入的模块中的每个属性名进行遍历:
- 魔法函数和私有函数不进行处理。
- 获取函数的注解信息,并通过正则表达式获取注解中的参数模板和信号列表。
- 如果解析到了参数模板,则将其存储在 sig_pats_map 中,key是函数名称。
- 如果解析到了信号列表,则将其存储在 sig_name_map 中,并且为每个信号创建了 Signal 对象并存储在列表中,key是函数名称。
4. 最后将得到的 sig_name_map 和 sig_pats_map 存储在实例变量中,以便其他方法使用。
:param signals_module: 指定信号函数所在模块
"""
self.signals_module = signals_module
sig_name_map = {}
sig_pats_map = {}
signals_module = import_by_name(signals_module)
for name in dir(signals_module):
if "_" not in name or name.startswith("__"):
continue
try:
doc = getattr(signals_module, name).__doc__
# 解析信号函数参数
pats = re.findall(r"参数模板:\"(.*)\"", doc)
if pats:
sig_pats_map[name] = pats[0]
# 解析信号列表
sigs = re.findall(r"Signal\('(.*)'\)", doc)
if sigs:
sig_name_map[name] = [Signal(x) for x in sigs]
except Exception as e:
logger.error(f"解析信号函数 {name} 出错:{e}")
self.sig_name_map = sig_name_map
self.sig_pats_map = sig_pats_map
[docs] def parse_params(self, name, signal):
"""获取信号函数参数
函数执行逻辑:
1. 首先根据传入的 name 和 signal 参数,通过 Signal(signal).key 获取一个键值。
2. 然后从实例变量 sig_pats_map 中获取与指定名称对应的参数模板,并将其存储在 pats 中。
3. 如果没有找到参数模板,则返回 None。
4. 最后将信号函数的完整名称存储在参数字典中,并返回参数字典。
:param name: 信号函数名称, 如:cxt_bi_end_V230222
:param signal: 需要解析的信号, 如:15分钟_D1K_量柱V221218_低量柱_6K_任意_0
:return:
"""
key = Signal(signal).key
pats = self.sig_pats_map.get(name, None)
if not pats:
return None
try:
params = parse(pats, key).named # type: ignore
if 'di' in params:
params['di'] = int(params['di'])
params['name'] = f"{self.signals_module}.{name}"
return params
except Exception as e:
logger.error(f"解析信号 {signal} - {name} - {pats} 出错:{e}")
return None
[docs] def get_function_name(self, signal: str):
"""获取信号对应的信号函数名称
函数执行逻辑:
1. 创建一个 _signal 对象,通过传入的信号字符串进行初始化。
2. 通过遍历 sig_name_map 中的项目,找出那些与 _signal.k3 相匹配的键,并将它们存储在 _k3_match 列表中。
3. 如果只有一个匹配项,则返回该项;否则记录错误日志并返回 None。
:param signal: 信号,数据样例:15分钟_D1K_量柱V221218_低量柱_6K_任意_0
:return: 信号函数名称
"""
sig_name_map = self.sig_name_map
_signal = Signal(signal)
_k3_match = list({k for k, v in sig_name_map.items() if v[0].k3 == _signal.k3})
if len(_k3_match) == 1:
return _k3_match[0]
else:
logger.error(f"信号 {signal} 有多个匹配函数:{_k3_match},请手动解析信号")
return None
[docs] def config_to_keys(self, config: List[Dict]):
"""将信号函数配置转换为信号key列表
函数执行逻辑:
1. 首先创建了一个空列表 keys 用于存储信号key。
2. 对于传入的 config 列表中的每个配置字典 conf 进行以下操作:
- 获取信号函数的名称。
- 如果该信号函数的名称在 self.sig_pats_map 中存在对应的模板,使用参数填充模板,并将结果添加到 keys 列表中。
:param config: 信号函数配置
config = [{'freq': '日线', 'max_overlap': '3', 'name': 'czsc.signals.cxt_bi_end_V230222'},
{'freq1': '日线', 'freq2': '60分钟', 'name': 'czsc.signals.cxt_zhong_shu_gong_zhen_V221221'}]
:return: 信号key列表
"""
keys = []
for conf in config:
name = conf['name'].split('.')[-1]
if name in self.sig_pats_map:
keys.append(self.sig_pats_map[name].format(**conf))
return keys
[docs] def parse(self, signal_seq: List[str]):
"""解析信号序列
函数执行逻辑:
1. 接受一个signal_seq 参数。
2. 定义一个空列表res ,用于存储解析结果。
3. 遍历信号序列signal_seq 中的每一个信号:
- 调用get_function_name 方法,以信号为参数,获取该信号对应的函数名。
- 进行函数名存在性判断,name 在sig_pats_map 中存在,
调用parse_params 方法,以函数名和信号为参数,解析参数并返回结果。
:param signal_seq: 信号序列, 样例:
['15分钟_D1K_量柱V221218_低量柱_6K_任意_0', '日线_D1K_量柱V221218_低量柱_6K_任意_0']
:return: 信号函数配置
"""
res = []
for signal in signal_seq:
name = self.get_function_name(signal)
if name in self.sig_pats_map:
row = self.parse_params(name, signal)
if row and row not in res:
res.append(row)
else:
logger.warning(f"未找到解析函数:{name},请手动解析信号:{signal}")
return res
[docs]def get_signals_config(signals_seq: List[str], signals_module: str = 'czsc.signals') -> List[Dict]:
"""获取信号列表对应的信号函数配置
函数执行逻辑:
1. 首先创建了一个 SignalsParser 类的实例对象 sp,传入了参数 signals_module进行初始化,
初始化工作主要是解析signals_module下的信号函数,生成了sig_pats_map信号参数模板字典和sig_name_map信号列表字典。
2. 然后使用 sp 实例调用 parse 方法,该方法解析 signals_seq 中的信号,并返回信号函数的配置信息。
:param signals_seq: 信号列表
:param signals_module: 信号函数所在模块
:return: 信号函数配置
"""
sp = SignalsParser(signals_module=signals_module)
conf = sp.parse(signals_seq)
return conf
[docs]def get_signals_freqs(signals_seq: List) -> List[str]:
"""获取信号列表对应的K线周期列表
函数执行逻辑:
1. 然后对于 signals_seq 中的每个信号进行以下操作:
- 使用正则表达式从信号中提取信号周期,并将其存储在 _freqs 变量中。
- 如果提取到了信号周期,则将其加入到 freqs 列表中。
2. 最后验证数据是否符合sorted_freqs列表规范,并且以sorted_freqs列表的排序进行返回。
:param signals_seq: 信号列表 / 信号函数配置列表
:return: K线周期列表
"""
freqs = []
for signal in signals_seq:
_freqs = re.findall('|'.join(sorted_freqs), str(signal))
if _freqs:
freqs.extend(_freqs)
return [x for x in sorted_freqs if x in freqs]