Skip to content

Commit 0046c33

Browse files
zengbin93hanzch
andauthored
V0.9.57 更新一批代码 (#210)
* 0.9.57 start coding * 0.9.57 新增最大夏普计算 * 0.9.57 新增最大夏普计算 * 0.9.57 新增最大夏普计算 * 0.9.57 新增 show_strategies_recent * 0.9.57 新增 get_stk_strategy * v0.9.57 (#209) 1.上传策略时, 将策略名写入SET(Weights:META:ALL) 2.增加获取所有策略了名的方法 get_strategy_names * 0.9.57 修复 table_record_batch_create 接口 * 0.9.57 优化飞书API接口 * 0.9.57 新增 remove_beta_effects * 0.9.57 新增 cross_sectional_strategy * 0.9.57 新增 show_factor_value 和 show_code_editor * 0.9.57 update * 0.9.57 update * 0.9.57 update * 0.9.57 update --------- Co-authored-by: 不归 <hanzch@foxmail.com>
1 parent 7c26227 commit 0046c33

18 files changed

+476
-63
lines changed

.github/workflows/pythonpackage.yml

+1-1
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.56 ]
8+
branches: [ master, V0.9.57 ]
99
pull_request:
1010
branches: [ master ]
1111

README.md

+8-14
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,6 @@
1515
>源于[缠中说缠博客](http://blog.sina.com.cn/chzhshch),原始博客中的内容不太完整,且没有评论,以下是网友整理的原文备份
1616
* 备份网址1:http://www.fxgan.com
1717

18-
>**假如没有了分型、笔、线段,缠论还是缠论吗?如果你的答案是“是”,这个项目是为你准备的。本项目旨在提供一个符合缠中说禅思维方式的程序化交易工具。**
19-
2018
* 已经开始用czsc库进行量化研究的朋友,欢迎[加入飞书群](https://applink.feishu.cn/client/chat/chatter/add_by_link?link_token=0bak668e-7617-452c-b935-94d2c209e6cf),快点击加入吧!
2119
* [B站视频教程合集(持续更新...)](https://space.bilibili.com/243682308/channel/series)
2220

@@ -25,13 +23,19 @@
2523
> 有意愿的朋友请联系我,微信号:**zengbin93**,备注:**桌面应用开发**
2624
> 我们将为你提供一个更好的量化交易学习和交流平台。
2725
26+
## 缠论精华
27+
28+
>学了本ID的理论,去再看其他的理论,就可以更清楚地看到其缺陷与毛病,因此,广泛地去看不同的理论,不仅不影响本ID理论的学习,更能明白本ID理论之所以与其他理论不同的根本之处。
29+
30+
>为什么要去了解其他理论,就是这些理论操作者的行为模式,将构成以后我们猎杀的对象,他们操作模式的缺陷,就是以后猎杀他们的最好武器,这就如同学独孤九剑,必须学会发现所有派别招数的缺陷,这也是本ID理论学习中一个极为关键的步骤。
31+
32+
2833
## 知识星球
2934

3035
* [CZSC小圈子(缠论、量化、专享案例)](https://s0cqcxuy3p.feishu.cn/wiki/wikcnwXSk9mWnki1b6URPhLA2Hc)
3136

3237
* 链接:https://wx.zsxq.com/dweb2/index/group/88851448582512
3338
* 加入:https://t.zsxq.com/0aMSAqcgO
34-
* 费用:100元
3539

3640
> **知识星球【CZSC小圈子】的定位是什么?**
3741
> - 为仔细研读过禅师原文并且愿意使用 CZSC 库进行量化投研的朋友提供一个深入交流的平台。
@@ -47,7 +51,7 @@
4751
* 定义并实现 `信号-因子-事件-交易` 量化交易逻辑体系,因子是信号的线性组合,事件是因子的同类合并,详见 `czsc/objects.py`
4852
* 定义并实现了若干信号函数,详见 `czsc/signals`
4953
* 缠论多级别联立决策分析交易,详见 `CzscTrader`
50-
* 基于 Tushare 数据的择时、选股策略回测研究流程
54+
* [Streamlit 量化研究组件库](https://s0cqcxuy3p.feishu.cn/wiki/AATuw5vN7iN9XbkVPuwcE186n9f)
5155

5256

5357
## 安装使用
@@ -69,16 +73,6 @@ pip install git+https://github.com/waditu/czsc.git@V0.9.46 -U
6973
pip install czsc -U -i https://pypi.python.org/simple
7074
```
7175

72-
73-
## 信号开源计划
74-
75-
>学了本ID的理论,去再看其他的理论,就可以更清楚地看到其缺陷与毛病,因此,广泛地去看不同的理论,不仅不影响本ID理论的学习,更能明白本ID理论之所以与其他理论不同的根本之处。
76-
77-
>为什么要去了解其他理论,就是这些理论操作者的行为模式,将构成以后我们猎杀的对象,他们操作模式的缺陷,就是以后猎杀他们的最好武器,这就如同学独孤九剑,必须学会发现所有派别招数的缺陷,这也是本ID理论学习中一个极为关键的步骤。
78-
79-
信号开源计划旨在为缠论学习者提供一批其他理论对应的信号计算函数,供各位以量化的方式研究其他理论的缺陷和价值。这个计划的工作量极大,需要各位的参与。有意愿加入的朋友,请点击查看详情:**[CZSC信号开源计划介绍](https://s0cqcxuy3p.feishu.cn/wiki/wikcnx7707hlakYMi4HmxdAIHJg)**
80-
81-
8276
## 使用前必看
8377

8478
* 目前的开发还在高频次的迭代中,对于已经在使用某个版本的用户,请谨慎更新,版本兼容性实在是太差,主要是因为当前还有太多考虑不完善的地方,我为此感到抱歉;

czsc/__init__.py

+15-2
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,9 @@
152152
show_symbols_corr,
153153
show_feature_returns,
154154
show_czsc_trader,
155+
show_strategies_recent,
156+
show_factor_value,
157+
show_code_editor,
155158
)
156159

157160
from czsc.utils.bi_info import (
@@ -192,10 +195,20 @@
192195
)
193196

194197

195-
__version__ = "0.9.56"
198+
from czsc.utils.portfolio import (
199+
max_sharp,
200+
)
201+
202+
from czsc.eda import (
203+
remove_beta_effects, vwap, twap,
204+
cross_sectional_strategy,
205+
)
206+
207+
208+
__version__ = "0.9.57"
196209
__author__ = "zengbin93"
197210
__email__ = "zeng_bin8888@163.com"
198-
__date__ = "20240714"
211+
__date__ = "20240726"
199212

200213

201214
def welcome():

czsc/connectors/cooperation.py

+24
Original file line numberDiff line numberDiff line change
@@ -291,3 +291,27 @@ def upload_strategy(df, meta, token=None, **kwargs):
291291

292292
logger.info(f"上传策略接口返回: {response.json()}")
293293
return response.json()
294+
295+
296+
def get_stk_strategy(name="STK_001", **kwargs):
297+
"""获取 STK 系列子策略的持仓权重数据
298+
299+
:param name: str
300+
子策略名称
301+
:param kwargs: dict
302+
sdt: str, optional
303+
开始日期,默认为 "20170101"
304+
edt: str, optional
305+
结束日期,默认为当前日期
306+
"""
307+
dfw = dc.post_request(api_name=name, v=2, hist=1, ttl=kwargs.get("ttl", 3600 * 6))
308+
dfw["dt"] = pd.to_datetime(dfw["dt"])
309+
sdt = kwargs.get("sdt", "20170101")
310+
edt = pd.Timestamp.now().strftime("%Y%m%d")
311+
edt = kwargs.get("edt", edt)
312+
dfw = dfw[(dfw["dt"] >= pd.to_datetime(sdt)) & (dfw["dt"] <= pd.to_datetime(edt))].copy().reset_index(drop=True)
313+
314+
dfb = stocks_daily_klines(sdt=sdt, edt=edt, nxb=(1, 2))
315+
dfw = pd.merge(dfw, dfb, on=["dt", "symbol"], how="left")
316+
dfh = dfw[["dt", "symbol", "weight", "n1b"]].copy()
317+
return dfh

czsc/eda.py

+92
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,10 @@
55
create_dt: 2023/2/7 13:17
66
describe: 用于探索性分析的函数
77
"""
8+
import loguru
9+
import pandas as pd
810
import numpy as np
11+
from sklearn.linear_model import Ridge, LinearRegression, Lasso
912

1013

1114
def vwap(price: np.array, volume: np.array, **kwargs) -> float:
@@ -25,3 +28,92 @@ def twap(price: np.array, **kwargs) -> float:
2528
:return: 平均价
2629
"""
2730
return np.average(price)
31+
32+
33+
def remove_beta_effects(df, **kwargs):
34+
"""去除 beta 对因子的影响
35+
36+
:param df: DataFrame, 数据, 必须包含 dt、symbol、factor 和 betas 列
37+
:param kwargs:
38+
39+
- factor: str, 因子列名
40+
- betas: list, beta 列名列表
41+
- linear_model: str, 线性模型,可选 ridge、linear 或 lasso
42+
43+
:return: DataFrame
44+
"""
45+
46+
linear_model = kwargs.get("linear_model", "ridge")
47+
linear = {
48+
"ridge": Ridge(),
49+
"linear": LinearRegression(),
50+
"lasso": Lasso(),
51+
}
52+
assert linear_model in linear.keys(), "linear_model 参数必须为 ridge、linear 或 lasso"
53+
Model = linear[linear_model]
54+
55+
factor = kwargs.get("factor")
56+
betas = kwargs.get("betas")
57+
logger = kwargs.get("logger", loguru.logger)
58+
59+
assert factor is not None and betas is not None, "factor 和 betas 参数必须指定"
60+
assert isinstance(betas, list), "betas 参数必须为列表"
61+
assert factor in df.columns, f"数据中不包含因子 {factor}"
62+
assert all([x in df.columns for x in betas]), f"数据中不包含全部 beta {betas}"
63+
64+
logger.info(f"去除 beta 对因子 {factor} 的影响, 使用 {linear_model} 模型, betas: {betas}")
65+
66+
rows = []
67+
for dt, dfg in df.groupby("dt"):
68+
dfg = dfg.copy().dropna(subset=[factor] + betas)
69+
if dfg.empty:
70+
continue
71+
72+
x = dfg[betas].values
73+
y = dfg[factor].values
74+
model = Model().fit(x, y)
75+
dfg[factor] = y - model.predict(x)
76+
rows.append(dfg)
77+
78+
dfr = pd.concat(rows, ignore_index=True)
79+
return dfr
80+
81+
82+
def cross_sectional_strategy(df, factor, **kwargs):
83+
"""根据截面因子值构建多空组合
84+
85+
:param df: pd.DataFrame, 包含因子列的数据, 必须包含 dt, symbol, factor 列
86+
:param factor: str, 因子列名称
87+
:param kwargs:
88+
89+
- factor_direction: str, 因子方向,positive 或 negative
90+
- long_num: int, 多头持仓数量
91+
- short_num: int, 空头持仓数量
92+
- logger: loguru.logger, 日志记录器
93+
94+
:return: pd.DataFrame, 包含 weight 列的数据
95+
"""
96+
factor_direction = kwargs.get("factor_direction", "positive")
97+
long_num = kwargs.get("long_num", 5)
98+
short_num = kwargs.get("short_num", 5)
99+
logger = kwargs.get("logger", loguru.logger)
100+
101+
assert factor in df.columns, f"{factor} 不在 df 中"
102+
assert factor_direction in ["positive", "negative"], f"factor_direction 参数错误"
103+
104+
df = df.copy()
105+
if factor_direction == "negative":
106+
df[factor] = -df[factor]
107+
108+
df['weight'] = 0
109+
for dt, dfg in df.groupby("dt"):
110+
if len(dfg) < long_num + short_num:
111+
logger.warning(f"{dt} 截面数据量过小,跳过;仅有 {len(dfg)} 条数据,需要 {long_num + short_num} 条数据")
112+
continue
113+
114+
dfa = dfg.sort_values(factor, ascending=False).head(long_num)
115+
dfb = dfg.sort_values(factor, ascending=True).head(short_num)
116+
df.loc[dfa.index, "weight"] = 1 / long_num
117+
df.loc[dfb.index, "weight"] = -1 / short_num
118+
119+
return df

czsc/fsa/base.py

+3-1
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
"""
1515
import os
1616
import time
17+
import loguru
1718
import requests
1819
from loguru import logger
1920
from tenacity import retry, stop_after_attempt, wait_random
@@ -58,12 +59,13 @@ def request(method, url, headers, payload=None) -> dict:
5859

5960

6061
class FeishuApiBase:
61-
def __init__(self, app_id, app_secret):
62+
def __init__(self, app_id, app_secret, **kwargs):
6263
self.app_id = app_id
6364
self.app_secret = app_secret
6465
self.host = "https://open.feishu.cn"
6566
self.headers = {"Content-Type": "application/json"}
6667
self.cache = dict()
68+
self.logger = kwargs.get("logger", loguru.logger)
6769

6870
def get_access_token(self, key="app_access_token"):
6971
assert key in ["app_access_token", "tenant_access_token"]

czsc/fsa/bi_table.py

+33-13
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
describe: 飞书多维表格接口
77
"""
88
import os
9+
import loguru
910
import pandas as pd
1011
from czsc.fsa.base import FeishuApiBase, request
1112

@@ -15,7 +16,7 @@ class BiTable(FeishuApiBase):
1516
多维表格概述: https://open.feishu.cn/document/server-docs/docs/bitable-v1/bitable-overview
1617
"""
1718

18-
def __init__(self, app_id=None, app_secret=None, app_token=None):
19+
def __init__(self, app_id=None, app_secret=None, app_token=None, **kwargs):
1920
"""
2021
2122
:param app_id: 飞书应用的唯一标识
@@ -24,8 +25,9 @@ def __init__(self, app_id=None, app_secret=None, app_token=None):
2425
"""
2526
app_id = app_id or os.getenv("FEISHU_APP_ID")
2627
app_secret = app_secret or os.getenv("FEISHU_APP_SECRET")
27-
super().__init__(app_id, app_secret)
28+
super().__init__(app_id, app_secret, **kwargs)
2829
self.app_token = app_token
30+
# self.logger = kwargs.get("logger", loguru.logger)
2931

3032
def one_record(self, table_id, record_id):
3133
"""根据 record_id 的值检索现有记录
@@ -245,14 +247,12 @@ def table_record_create(self, table_id, fields, user_id_type=None, client_token=
245247
"""数据表中新增一条记录
246248
247249
https://open.feishu.cn/document/server-docs/docs/bitable-v1/app-table-record/create
248-
:param table_id: table id
249250
251+
:param table_id: table id
250252
:param user_id_type: 非必需 用户 ID 类型
251253
:param client_token: 非必需 格式为标准的 uuidv4,操作的唯一标识,用于幂等的进行更新操作。此值为空表示将发起一次新的请求,此值非空表示幂等的进行更新操作。
252-
253254
:param fields: 必需
254255
数据表的字段,即数据表的列。当前接口支持的字段类型为:多行文本、单选、条码、多选、日期、人员、附件、复选框、超链接、数字、单向关联、双向关联、电话号码、地理位置。详情参考
255-
256256
:return: 返回数据
257257
"""
258258
url = f"{self.host}/open-apis/bitable/v1/apps/{self.app_token}/tables/{table_id}/records?1=1"
@@ -284,27 +284,25 @@ def table_record_delete(self, table_id, record_id):
284284
https://open.feishu.cn/document/server-docs/docs/bitable-v1/app-table-record/delete
285285
:param table_id: table id
286286
:param record_id: 一条记录的唯一标识 id
287-
288287
:return: 返回数据
289288
"""
290289
url = f"{self.host}/open-apis/bitable/v1/apps/{self.app_token}/tables/{table_id}/records/{record_id}"
291290
return request("DELETE", url, self.get_headers())
292291

293-
def table_record_batch_create(self, table_id, fields, user_id_type=None, client_token=None):
292+
def table_record_batch_create(self, table_id, records, user_id_type=None, client_token=None):
294293
"""在数据表中新增多条记录,单次调用最多新增 500 条记录。
295294
296295
https://open.feishu.cn/document/server-docs/docs/bitable-v1/app-table-record/batch_create
297-
:param table_id: table id
298296
297+
:param table_id: table id
299298
:param user_id_type: 非必需 用户 ID 类型
300299
:param client_token: 非必需 格式为标准的 uuidv4,操作的唯一标识,用于幂等的进行更新操作。此值为空表示将发起一次新的请求,此值非空表示幂等的进行更新操作。
301-
302-
:param fields:[] 数据表的字段,即数据表的列当前接口支持的字段类型 示例值:{"多行文本":"HelloWorld"}
300+
:param records:[] 数据表的字段,即数据表的列当前接口支持的字段类型 示例值:{"多行文本":"HelloWorld"}
303301
:return: 返回数据
304302
"""
305-
records = []
306-
for field in fields:
307-
records.append({"fields": field})
303+
# records = []
304+
# for field in fields:
305+
# records.append({"fields": field})
308306
url = f"{self.host}/open-apis/bitable/v1/apps/{self.app_token}/tables/{table_id}/records/batch_create?1=1"
309307
url = url if user_id_type is None else url + f"&user_id_type={user_id_type}"
310308
url = url if client_token is None else url + f"&client_token={client_token}"
@@ -734,3 +732,25 @@ def read_table(self, table_id, **kwargs):
734732

735733
assert len(rows) == total, "数据读取异常"
736734
return pd.DataFrame([x["fields"] for x in rows])
735+
736+
def empty_table(self, table_id, **kwargs):
737+
"""清空多维表格中指定表格的数据,保留表头
738+
739+
:param table_id: 表格id
740+
:return:
741+
"""
742+
res = self.list_records(table_id, **kwargs)["data"]
743+
self.logger.info(f"{table_id} 表格中共有 {res['total']} 条数据")
744+
records = res["items"]
745+
if records:
746+
record_ids = [x["record_id"] for x in records]
747+
self.table_record_batch_delete(table_id, record_ids)
748+
self.logger.info(f"{table_id} 删除 {len(record_ids)} 条数据")
749+
750+
while res["has_more"]:
751+
res = self.list_records(table_id, page_token=res["page_token"], **kwargs)["data"]
752+
records = res["items"]
753+
if records:
754+
record_ids = [x["record_id"] for x in records]
755+
self.table_record_batch_delete(table_id, record_ids)
756+
self.logger.info(f"{table_id} 删除 {len(record_ids)} 条数据")

czsc/fsa/im.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,8 @@
1414
class IM(FeishuApiBase):
1515
"""即时消息发送"""
1616

17-
def __init__(self, app_id, app_secret):
18-
super().__init__(app_id, app_secret)
17+
def __init__(self, app_id, app_secret, **kwargs):
18+
super().__init__(app_id, app_secret, **kwargs)
1919

2020
def get_user_id(self, payload, user_id_type="open_id"):
2121
"""获取用户ID

0 commit comments

Comments
 (0)