Skip to content

Commit

Permalink
V0.9.57 更新一批代码 (#210)
Browse files Browse the repository at this point in the history
* 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>
  • Loading branch information
zengbin93 and hanzch authored Aug 4, 2024
1 parent 7c26227 commit 0046c33
Show file tree
Hide file tree
Showing 18 changed files with 476 additions and 63 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.56 ]
branches: [ master, V0.9.57 ]
pull_request:
branches: [ master ]

Expand Down
22 changes: 8 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,6 @@
>源于[缠中说缠博客](http://blog.sina.com.cn/chzhshch),原始博客中的内容不太完整,且没有评论,以下是网友整理的原文备份
* 备份网址1:http://www.fxgan.com

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

Expand All @@ -25,13 +23,19 @@
> 有意愿的朋友请联系我,微信号:**zengbin93**,备注:**桌面应用开发**
> 我们将为你提供一个更好的量化交易学习和交流平台。
## 缠论精华

>学了本ID的理论,去再看其他的理论,就可以更清楚地看到其缺陷与毛病,因此,广泛地去看不同的理论,不仅不影响本ID理论的学习,更能明白本ID理论之所以与其他理论不同的根本之处。
>为什么要去了解其他理论,就是这些理论操作者的行为模式,将构成以后我们猎杀的对象,他们操作模式的缺陷,就是以后猎杀他们的最好武器,这就如同学独孤九剑,必须学会发现所有派别招数的缺陷,这也是本ID理论学习中一个极为关键的步骤。

## 知识星球

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

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

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


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


## 信号开源计划

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


## 使用前必看

* 目前的开发还在高频次的迭代中,对于已经在使用某个版本的用户,请谨慎更新,版本兼容性实在是太差,主要是因为当前还有太多考虑不完善的地方,我为此感到抱歉;
Expand Down
17 changes: 15 additions & 2 deletions czsc/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,9 @@
show_symbols_corr,
show_feature_returns,
show_czsc_trader,
show_strategies_recent,
show_factor_value,
show_code_editor,
)

from czsc.utils.bi_info import (
Expand Down Expand Up @@ -192,10 +195,20 @@
)


__version__ = "0.9.56"
from czsc.utils.portfolio import (
max_sharp,
)

from czsc.eda import (
remove_beta_effects, vwap, twap,
cross_sectional_strategy,
)


__version__ = "0.9.57"
__author__ = "zengbin93"
__email__ = "zeng_bin8888@163.com"
__date__ = "20240714"
__date__ = "20240726"


def welcome():
Expand Down
24 changes: 24 additions & 0 deletions czsc/connectors/cooperation.py
Original file line number Diff line number Diff line change
Expand Up @@ -291,3 +291,27 @@ def upload_strategy(df, meta, token=None, **kwargs):

logger.info(f"上传策略接口返回: {response.json()}")
return response.json()


def get_stk_strategy(name="STK_001", **kwargs):
"""获取 STK 系列子策略的持仓权重数据
:param name: str
子策略名称
:param kwargs: dict
sdt: str, optional
开始日期,默认为 "20170101"
edt: str, optional
结束日期,默认为当前日期
"""
dfw = dc.post_request(api_name=name, v=2, hist=1, ttl=kwargs.get("ttl", 3600 * 6))
dfw["dt"] = pd.to_datetime(dfw["dt"])
sdt = kwargs.get("sdt", "20170101")
edt = pd.Timestamp.now().strftime("%Y%m%d")
edt = kwargs.get("edt", edt)
dfw = dfw[(dfw["dt"] >= pd.to_datetime(sdt)) & (dfw["dt"] <= pd.to_datetime(edt))].copy().reset_index(drop=True)

dfb = stocks_daily_klines(sdt=sdt, edt=edt, nxb=(1, 2))
dfw = pd.merge(dfw, dfb, on=["dt", "symbol"], how="left")
dfh = dfw[["dt", "symbol", "weight", "n1b"]].copy()
return dfh
92 changes: 92 additions & 0 deletions czsc/eda.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,10 @@
create_dt: 2023/2/7 13:17
describe: 用于探索性分析的函数
"""
import loguru
import pandas as pd
import numpy as np
from sklearn.linear_model import Ridge, LinearRegression, Lasso


def vwap(price: np.array, volume: np.array, **kwargs) -> float:
Expand All @@ -25,3 +28,92 @@ def twap(price: np.array, **kwargs) -> float:
:return: 平均价
"""
return np.average(price)


def remove_beta_effects(df, **kwargs):
"""去除 beta 对因子的影响
:param df: DataFrame, 数据, 必须包含 dt、symbol、factor 和 betas 列
:param kwargs:
- factor: str, 因子列名
- betas: list, beta 列名列表
- linear_model: str, 线性模型,可选 ridge、linear 或 lasso
:return: DataFrame
"""

linear_model = kwargs.get("linear_model", "ridge")
linear = {
"ridge": Ridge(),
"linear": LinearRegression(),
"lasso": Lasso(),
}
assert linear_model in linear.keys(), "linear_model 参数必须为 ridge、linear 或 lasso"
Model = linear[linear_model]

factor = kwargs.get("factor")
betas = kwargs.get("betas")
logger = kwargs.get("logger", loguru.logger)

assert factor is not None and betas is not None, "factor 和 betas 参数必须指定"
assert isinstance(betas, list), "betas 参数必须为列表"
assert factor in df.columns, f"数据中不包含因子 {factor}"
assert all([x in df.columns for x in betas]), f"数据中不包含全部 beta {betas}"

logger.info(f"去除 beta 对因子 {factor} 的影响, 使用 {linear_model} 模型, betas: {betas}")

rows = []
for dt, dfg in df.groupby("dt"):
dfg = dfg.copy().dropna(subset=[factor] + betas)
if dfg.empty:
continue

x = dfg[betas].values
y = dfg[factor].values
model = Model().fit(x, y)
dfg[factor] = y - model.predict(x)
rows.append(dfg)

dfr = pd.concat(rows, ignore_index=True)
return dfr


def cross_sectional_strategy(df, factor, **kwargs):
"""根据截面因子值构建多空组合
:param df: pd.DataFrame, 包含因子列的数据, 必须包含 dt, symbol, factor 列
:param factor: str, 因子列名称
:param kwargs:
- factor_direction: str, 因子方向,positive 或 negative
- long_num: int, 多头持仓数量
- short_num: int, 空头持仓数量
- logger: loguru.logger, 日志记录器
:return: pd.DataFrame, 包含 weight 列的数据
"""
factor_direction = kwargs.get("factor_direction", "positive")
long_num = kwargs.get("long_num", 5)
short_num = kwargs.get("short_num", 5)
logger = kwargs.get("logger", loguru.logger)

assert factor in df.columns, f"{factor} 不在 df 中"
assert factor_direction in ["positive", "negative"], f"factor_direction 参数错误"

df = df.copy()
if factor_direction == "negative":
df[factor] = -df[factor]

df['weight'] = 0
for dt, dfg in df.groupby("dt"):
if len(dfg) < long_num + short_num:
logger.warning(f"{dt} 截面数据量过小,跳过;仅有 {len(dfg)} 条数据,需要 {long_num + short_num} 条数据")
continue

dfa = dfg.sort_values(factor, ascending=False).head(long_num)
dfb = dfg.sort_values(factor, ascending=True).head(short_num)
df.loc[dfa.index, "weight"] = 1 / long_num
df.loc[dfb.index, "weight"] = -1 / short_num

return df
4 changes: 3 additions & 1 deletion czsc/fsa/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
"""
import os
import time
import loguru
import requests
from loguru import logger
from tenacity import retry, stop_after_attempt, wait_random
Expand Down Expand Up @@ -58,12 +59,13 @@ def request(method, url, headers, payload=None) -> dict:


class FeishuApiBase:
def __init__(self, app_id, app_secret):
def __init__(self, app_id, app_secret, **kwargs):
self.app_id = app_id
self.app_secret = app_secret
self.host = "https://open.feishu.cn"
self.headers = {"Content-Type": "application/json"}
self.cache = dict()
self.logger = kwargs.get("logger", loguru.logger)

def get_access_token(self, key="app_access_token"):
assert key in ["app_access_token", "tenant_access_token"]
Expand Down
46 changes: 33 additions & 13 deletions czsc/fsa/bi_table.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
describe: 飞书多维表格接口
"""
import os
import loguru
import pandas as pd
from czsc.fsa.base import FeishuApiBase, request

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

def __init__(self, app_id=None, app_secret=None, app_token=None):
def __init__(self, app_id=None, app_secret=None, app_token=None, **kwargs):
"""
:param app_id: 飞书应用的唯一标识
Expand All @@ -24,8 +25,9 @@ def __init__(self, app_id=None, app_secret=None, app_token=None):
"""
app_id = app_id or os.getenv("FEISHU_APP_ID")
app_secret = app_secret or os.getenv("FEISHU_APP_SECRET")
super().__init__(app_id, app_secret)
super().__init__(app_id, app_secret, **kwargs)
self.app_token = app_token
# self.logger = kwargs.get("logger", loguru.logger)

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

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

assert len(rows) == total, "数据读取异常"
return pd.DataFrame([x["fields"] for x in rows])

def empty_table(self, table_id, **kwargs):
"""清空多维表格中指定表格的数据,保留表头
:param table_id: 表格id
:return:
"""
res = self.list_records(table_id, **kwargs)["data"]
self.logger.info(f"{table_id} 表格中共有 {res['total']} 条数据")
records = res["items"]
if records:
record_ids = [x["record_id"] for x in records]
self.table_record_batch_delete(table_id, record_ids)
self.logger.info(f"{table_id} 删除 {len(record_ids)} 条数据")

while res["has_more"]:
res = self.list_records(table_id, page_token=res["page_token"], **kwargs)["data"]
records = res["items"]
if records:
record_ids = [x["record_id"] for x in records]
self.table_record_batch_delete(table_id, record_ids)
self.logger.info(f"{table_id} 删除 {len(record_ids)} 条数据")
4 changes: 2 additions & 2 deletions czsc/fsa/im.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@
class IM(FeishuApiBase):
"""即时消息发送"""

def __init__(self, app_id, app_secret):
super().__init__(app_id, app_secret)
def __init__(self, app_id, app_secret, **kwargs):
super().__init__(app_id, app_secret, **kwargs)

def get_user_id(self, payload, user_id_type="open_id"):
"""获取用户ID
Expand Down
Loading

0 comments on commit 0046c33

Please sign in to comment.