kaq-quant-common 0.2.12__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- kaq_quant_common/__init__.py +0 -0
- kaq_quant_common/api/__init__.py +0 -0
- kaq_quant_common/api/common/__init__.py +1 -0
- kaq_quant_common/api/common/api_interface.py +38 -0
- kaq_quant_common/api/common/auth.py +118 -0
- kaq_quant_common/api/rest/__init__.py +0 -0
- kaq_quant_common/api/rest/api_client_base.py +42 -0
- kaq_quant_common/api/rest/api_server_base.py +135 -0
- kaq_quant_common/api/rest/instruction/helper/order_helper.py +342 -0
- kaq_quant_common/api/rest/instruction/instruction_client.py +86 -0
- kaq_quant_common/api/rest/instruction/instruction_server_base.py +154 -0
- kaq_quant_common/api/rest/instruction/models/__init__.py +17 -0
- kaq_quant_common/api/rest/instruction/models/account.py +49 -0
- kaq_quant_common/api/rest/instruction/models/order.py +248 -0
- kaq_quant_common/api/rest/instruction/models/position.py +70 -0
- kaq_quant_common/api/rest/instruction/models/transfer.py +32 -0
- kaq_quant_common/api/ws/__init__.py +0 -0
- kaq_quant_common/api/ws/exchange/models.py +23 -0
- kaq_quant_common/api/ws/exchange/ws_exchange_client.py +31 -0
- kaq_quant_common/api/ws/exchange/ws_exchange_server.py +440 -0
- kaq_quant_common/api/ws/instruction/__init__.py +0 -0
- kaq_quant_common/api/ws/instruction/ws_instruction_client.py +82 -0
- kaq_quant_common/api/ws/instruction/ws_instruction_server_base.py +139 -0
- kaq_quant_common/api/ws/models.py +46 -0
- kaq_quant_common/api/ws/ws_client_base.py +235 -0
- kaq_quant_common/api/ws/ws_server_base.py +288 -0
- kaq_quant_common/common/__init__.py +0 -0
- kaq_quant_common/common/ddb_table_monitor.py +106 -0
- kaq_quant_common/common/http_monitor.py +69 -0
- kaq_quant_common/common/modules/funding_rate_helper.py +137 -0
- kaq_quant_common/common/modules/limit_order_helper.py +158 -0
- kaq_quant_common/common/modules/limit_order_symbol_monitor.py +76 -0
- kaq_quant_common/common/modules/limit_order_symbol_monitor_group.py +69 -0
- kaq_quant_common/common/monitor_base.py +84 -0
- kaq_quant_common/common/monitor_group.py +97 -0
- kaq_quant_common/common/redis_table_monitor.py +123 -0
- kaq_quant_common/common/statistics/funding_rate_history_statistics.py +208 -0
- kaq_quant_common/common/statistics/kline_history_statistics.py +211 -0
- kaq_quant_common/common/ws_wrapper.py +21 -0
- kaq_quant_common/config/config.yaml +5 -0
- kaq_quant_common/resources/__init__.py +0 -0
- kaq_quant_common/resources/kaq_ddb_pool_stream_read_resources.py +56 -0
- kaq_quant_common/resources/kaq_ddb_stream_init_resources.py +88 -0
- kaq_quant_common/resources/kaq_ddb_stream_read_resources.py +81 -0
- kaq_quant_common/resources/kaq_ddb_stream_write_resources.py +359 -0
- kaq_quant_common/resources/kaq_mysql_init_resources.py +23 -0
- kaq_quant_common/resources/kaq_mysql_resources.py +341 -0
- kaq_quant_common/resources/kaq_postgresql_resources.py +58 -0
- kaq_quant_common/resources/kaq_quant_hive_resources.py +107 -0
- kaq_quant_common/resources/kaq_redis_resources.py +117 -0
- kaq_quant_common/utils/__init__.py +0 -0
- kaq_quant_common/utils/dagster_job_check_utils.py +29 -0
- kaq_quant_common/utils/dagster_utils.py +19 -0
- kaq_quant_common/utils/date_util.py +204 -0
- kaq_quant_common/utils/enums_utils.py +79 -0
- kaq_quant_common/utils/error_utils.py +22 -0
- kaq_quant_common/utils/hash_utils.py +48 -0
- kaq_quant_common/utils/log_time_utils.py +32 -0
- kaq_quant_common/utils/logger_utils.py +97 -0
- kaq_quant_common/utils/mytt_utils.py +372 -0
- kaq_quant_common/utils/signal_utils.py +23 -0
- kaq_quant_common/utils/sqlite_utils.py +169 -0
- kaq_quant_common/utils/uuid_utils.py +5 -0
- kaq_quant_common/utils/yml_utils.py +148 -0
- kaq_quant_common-0.2.12.dist-info/METADATA +66 -0
- kaq_quant_common-0.2.12.dist-info/RECORD +67 -0
- kaq_quant_common-0.2.12.dist-info/WHEEL +4 -0
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
import datetime
|
|
2
|
+
import json
|
|
3
|
+
from typing import Optional
|
|
4
|
+
|
|
5
|
+
import pandas as pd
|
|
6
|
+
from pydantic import BaseModel
|
|
7
|
+
|
|
8
|
+
from kaq_quant_common.common.statistics.kline_history_statistics import \
|
|
9
|
+
StatisticsInfo
|
|
10
|
+
from kaq_quant_common.utils import logger_utils
|
|
11
|
+
|
|
12
|
+
platforms = ["binance", "bitget", "bybit", "gate", "htx", "okx"]
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
# 这个类是统计合约历史资金数据的,在确保已经抓取完数据后使用
|
|
16
|
+
class FuturesFundingRateHistoryStatistics:
|
|
17
|
+
def __init__(
|
|
18
|
+
self,
|
|
19
|
+
begin_timestamp: int,
|
|
20
|
+
end_timestamp: int,
|
|
21
|
+
symbols: list[str],
|
|
22
|
+
master: str,
|
|
23
|
+
redis: None,
|
|
24
|
+
mysql: None,
|
|
25
|
+
):
|
|
26
|
+
self.redis_key = "kaq_futures_funding_rate_history_statistics"
|
|
27
|
+
self.begin_timestamp = begin_timestamp
|
|
28
|
+
self.end_timestamp = end_timestamp
|
|
29
|
+
self.symbols = symbols
|
|
30
|
+
# 计算天数
|
|
31
|
+
self.day_num = (end_timestamp - begin_timestamp) // (24 * 3600 * 1000)
|
|
32
|
+
self._redis = redis
|
|
33
|
+
self._mysql = mysql
|
|
34
|
+
self.master = master
|
|
35
|
+
self._logger = logger_utils.get_logger()
|
|
36
|
+
self.table_name = "kaq_futures_funding_rate_history"
|
|
37
|
+
|
|
38
|
+
# 针对本平台的所有交易对每个进行对应统计
|
|
39
|
+
def symbols_statistics(self):
|
|
40
|
+
for symbol in self.symbols:
|
|
41
|
+
try:
|
|
42
|
+
self.get_symbol_funding_rate_all_platform(symbol)
|
|
43
|
+
except Exception as e:
|
|
44
|
+
self._logger.error(f"拉取{symbol}的资金数据出现异常: {e}")
|
|
45
|
+
ddd = 1
|
|
46
|
+
continue
|
|
47
|
+
|
|
48
|
+
# 对指定交易对进行全平台的资金数据拉取
|
|
49
|
+
def get_symbol_funding_rate_all_platform(self, symbol: str):
|
|
50
|
+
df_dict = {}
|
|
51
|
+
# 先拉自己的吧
|
|
52
|
+
master_df = self.query_symbol_funding_rate_data(symbol, self.master)
|
|
53
|
+
|
|
54
|
+
# 先记录下主平台拉到的数据量,用来对比其它平台,其它平台不是这么多就证明有问题,需要记录下日志
|
|
55
|
+
master_df_len = len(master_df)
|
|
56
|
+
|
|
57
|
+
# 没有数据就跳过
|
|
58
|
+
if master_df_len == 0:
|
|
59
|
+
raise Exception(
|
|
60
|
+
f"{self.master}平台拉取到的{symbol}的资金数据量为:{master_df_len}条,跳过后续处理"
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
# 对其它交易所进行拉取
|
|
64
|
+
for platform in platforms:
|
|
65
|
+
if platform == self.master:
|
|
66
|
+
continue
|
|
67
|
+
platform_df = self.query_symbol_funding_rate_data(symbol, platform)
|
|
68
|
+
|
|
69
|
+
if len(platform_df) != master_df_len:
|
|
70
|
+
self._logger.error(
|
|
71
|
+
f"{platform}平台拉取到的{symbol}的资金数据量与主平台不一致,主平台:{master_df_len},{platform}平台:{len(platform_df)},跳过该交易所数据"
|
|
72
|
+
)
|
|
73
|
+
continue
|
|
74
|
+
|
|
75
|
+
df_dict[platform] = platform_df
|
|
76
|
+
|
|
77
|
+
# 开始计算差异
|
|
78
|
+
symbol_diffrenence_dict = self.calculate_funding_rate_difference(
|
|
79
|
+
symbol, master_df, df_dict
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
self._redis.hset(
|
|
83
|
+
self.redis_key + ":" + self.master.upper(),
|
|
84
|
+
symbol,
|
|
85
|
+
json.dumps({k: v.model_dump() for k, v in symbol_diffrenence_dict.items()}),
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
# 计算各个平台的资金数据差异
|
|
89
|
+
def calculate_funding_rate_difference(
|
|
90
|
+
self, symbol: str, master_df: pd.DataFrame, df_dict: dict
|
|
91
|
+
):
|
|
92
|
+
res = {}
|
|
93
|
+
master_hour = master_df.iloc[0]["hour"]
|
|
94
|
+
# 自己也要计算差异
|
|
95
|
+
master_std = master_df[f"{self.master}_rate"].std()
|
|
96
|
+
master_mean = master_df[f"{self.master}_rate"].mean()
|
|
97
|
+
master_max = master_df[f"{self.master}_rate"].max()
|
|
98
|
+
master_rate_corr = master_df[f"{self.master}_rate"].corr(
|
|
99
|
+
master_df["event_time_hour"]
|
|
100
|
+
)
|
|
101
|
+
master_min = master_df[f"{self.master}_rate"].min()
|
|
102
|
+
master_quantile = master_df[f"{self.master}_rate"].quantile([0.25, 0.5, 0.75])
|
|
103
|
+
|
|
104
|
+
res[self.master] = StatisticsInfo(
|
|
105
|
+
platform=self.master,
|
|
106
|
+
std=master_std,
|
|
107
|
+
mean=master_mean,
|
|
108
|
+
max=master_max,
|
|
109
|
+
corr=master_rate_corr,
|
|
110
|
+
min=master_min,
|
|
111
|
+
quantile={str(k): float(v) for k, v in master_quantile.to_dict().items()},
|
|
112
|
+
period=master_hour,
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
# 还要加主平台的差异,主平台不需要减,只需要直接处理
|
|
116
|
+
for platform, platform_df in df_dict.items():
|
|
117
|
+
platform_hour = platform_df.iloc[0]["hour"]
|
|
118
|
+
# 合并数据,找出差异
|
|
119
|
+
merged_df = pd.merge(
|
|
120
|
+
master_df, platform_df, on=["symbol", "event_time_hour"], how="inner"
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
# 计算差值
|
|
124
|
+
merged_df["rate_diff"] = (
|
|
125
|
+
merged_df[f"{self.master}_rate"] - merged_df[f"{platform}_rate"]
|
|
126
|
+
)
|
|
127
|
+
# 标准差
|
|
128
|
+
rate_std = merged_df["rate_diff"].std()
|
|
129
|
+
# 平均值
|
|
130
|
+
rate_mean = merged_df["rate_diff"].mean()
|
|
131
|
+
# 最大值
|
|
132
|
+
rate_max = merged_df["rate_diff"].max()
|
|
133
|
+
# 皮尔逊系数(斜率)
|
|
134
|
+
rate_corr = merged_df["rate_diff"].corr(merged_df["event_time_hour"])
|
|
135
|
+
# 最小值
|
|
136
|
+
rate_min = merged_df["rate_diff"].min()
|
|
137
|
+
# 4分位数
|
|
138
|
+
rate_quantile = merged_df["rate_diff"].quantile([0.25, 0.5, 0.75])
|
|
139
|
+
# self._logger.info(
|
|
140
|
+
# f"{self.master}与{platform}平台的{symbol}的K线差异统计: 标准差={rate_std}, 平均值={rate_mean}, 最大值={rate_max}, 最小值={rate_min}, 皮尔逊系数={rate_corr}, 四分位数={rate_quantile.to_dict()}"
|
|
141
|
+
# )
|
|
142
|
+
res[platform] = StatisticsInfo(
|
|
143
|
+
platform=platform,
|
|
144
|
+
std=rate_std,
|
|
145
|
+
mean=rate_mean,
|
|
146
|
+
max=rate_max,
|
|
147
|
+
corr=rate_corr,
|
|
148
|
+
min=rate_min,
|
|
149
|
+
quantile={str(k): v for k, v in rate_quantile.to_dict().items()},
|
|
150
|
+
period=platform_hour,
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
return res
|
|
154
|
+
|
|
155
|
+
# 拉指定时间指定symbol的资金数据
|
|
156
|
+
def query_symbol_funding_rate_data(self, symbol: str, platform: str):
|
|
157
|
+
sql_result_df = pd.DataFrame()
|
|
158
|
+
# 先转成周一日期来定表名,因为数据表是按周来分表的
|
|
159
|
+
sql = f"select exchange, symbol, rate as {platform}_rate, event_time from {self.table_name} where symbol = '{symbol}' and event_time >= {self.begin_timestamp} and event_time < {self.end_timestamp} and exchange='{platform}' order by event_time desc ;"
|
|
160
|
+
result = self._mysql.fetch_data(sql)
|
|
161
|
+
sql_result_df = pd.DataFrame(result)
|
|
162
|
+
if sql_result_df.empty:
|
|
163
|
+
return sql_result_df
|
|
164
|
+
# 转换类型
|
|
165
|
+
sql_result_df[f"{platform}_rate"] = sql_result_df[f"{platform}_rate"].astype(
|
|
166
|
+
float
|
|
167
|
+
)
|
|
168
|
+
# 增加个周期字段用来补全24小时数据
|
|
169
|
+
# 我操,接口拿到的event_time毫秒有些不是整点,转成小时处理吧。
|
|
170
|
+
sql_result_df["event_time"] = sql_result_df["event_time"].astype(int)
|
|
171
|
+
sql_result_df["event_time_hour"] = sql_result_df["event_time"] // 1000
|
|
172
|
+
# 计算周期
|
|
173
|
+
sql_result_df["period"] = sql_result_df["event_time_hour"].diff(periods=-1)
|
|
174
|
+
sql_result_df["hour"] = sql_result_df["period"] // 3600
|
|
175
|
+
|
|
176
|
+
sql_result_df = sql_result_df[::-1] # 反向排序
|
|
177
|
+
|
|
178
|
+
start_ts = int(sql_result_df["event_time"].iloc[0])
|
|
179
|
+
start_dt = pd.to_datetime(start_ts // 1000, unit="s")
|
|
180
|
+
# 先生成一批完整的时间序列数据
|
|
181
|
+
temp_date_df = pd.date_range(
|
|
182
|
+
start_dt,
|
|
183
|
+
periods=self.day_num * 24,
|
|
184
|
+
freq="h",
|
|
185
|
+
)
|
|
186
|
+
temp_date_df = temp_date_df[::-1] # 反向排序
|
|
187
|
+
|
|
188
|
+
# 生成完整的毫秒级时间戳DataFrame
|
|
189
|
+
temp_time_df = pd.DataFrame(
|
|
190
|
+
{"event_time_hour": temp_date_df.astype(int) // 10**9} # 转为毫秒
|
|
191
|
+
)
|
|
192
|
+
# 合并,左侧为完整时间
|
|
193
|
+
merged_df = temp_time_df.merge(sql_result_df, on="event_time_hour", how="left")
|
|
194
|
+
# 向上对齐填充
|
|
195
|
+
merged_df = merged_df.sort_values("event_time_hour").ffill()
|
|
196
|
+
# event_time用自身时间,其它字段用ffill
|
|
197
|
+
merged_df["event_time_hour"] = temp_time_df["event_time_hour"]
|
|
198
|
+
merged_df = merged_df.sort_values(
|
|
199
|
+
"event_time_hour", ascending=False
|
|
200
|
+
).reset_index(drop=True)
|
|
201
|
+
return merged_df
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
if __name__ == "__main__":
|
|
205
|
+
klineStatistics = FuturesFundingRateHistoryStatistics(
|
|
206
|
+
1765296000000, 1765382400000, ["BTCUSDT", "ETHUSDT"]
|
|
207
|
+
)
|
|
208
|
+
klineStatistics.symbols_statistics()
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
import datetime
|
|
2
|
+
import json
|
|
3
|
+
from typing import Optional
|
|
4
|
+
|
|
5
|
+
import pandas as pd
|
|
6
|
+
from pydantic import BaseModel
|
|
7
|
+
|
|
8
|
+
from kaq_quant_common.utils import logger_utils
|
|
9
|
+
|
|
10
|
+
platforms = ["binance", "bitget", "bybit", "gate", "htx", "okx"]
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
# 对比结果结构体
|
|
14
|
+
class StatisticsInfo(BaseModel):
|
|
15
|
+
# 对比平台
|
|
16
|
+
platform: str
|
|
17
|
+
# 标准差
|
|
18
|
+
std: float
|
|
19
|
+
# 平均值
|
|
20
|
+
mean: float
|
|
21
|
+
# 最大值
|
|
22
|
+
max: float
|
|
23
|
+
# 皮尔逊系数(斜率)
|
|
24
|
+
corr: float
|
|
25
|
+
# 最小值
|
|
26
|
+
min: float
|
|
27
|
+
# 4分位数
|
|
28
|
+
quantile: dict[str, float]
|
|
29
|
+
# 相隔时间(hour)
|
|
30
|
+
period: Optional[int] = 0
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
# 这个类是统计合约K线历史数据的,在确保已经抓取完数据后使用
|
|
34
|
+
class FuturesKlineHistoryStatistics:
|
|
35
|
+
def __init__(
|
|
36
|
+
self,
|
|
37
|
+
begin_timestamp: int,
|
|
38
|
+
end_timestamp: int,
|
|
39
|
+
symbols: list[str],
|
|
40
|
+
master: str,
|
|
41
|
+
redis: None,
|
|
42
|
+
mysql: None,
|
|
43
|
+
):
|
|
44
|
+
self.redis_key = "kaq_futures_kline_history_statistics"
|
|
45
|
+
self.begin_timestamp = begin_timestamp
|
|
46
|
+
self.end_timestamp = end_timestamp
|
|
47
|
+
self.symbols = symbols
|
|
48
|
+
# 计算天数,每天都会有1440条数据
|
|
49
|
+
self.day_num = (end_timestamp - begin_timestamp) // (24 * 3600 * 1000)
|
|
50
|
+
self._redis = redis
|
|
51
|
+
self._mysql = mysql
|
|
52
|
+
self.master = master
|
|
53
|
+
self._logger = logger_utils.get_logger()
|
|
54
|
+
|
|
55
|
+
# 针对本平台的所有交易对每个进行对应统计
|
|
56
|
+
def symbols_statistics(self):
|
|
57
|
+
for symbol in self.symbols:
|
|
58
|
+
try:
|
|
59
|
+
self.get_symbol_kline_all_platform(symbol)
|
|
60
|
+
except Exception as e:
|
|
61
|
+
self._logger.error(f"拉取{symbol}的K线数据出现异常: {e}")
|
|
62
|
+
continue
|
|
63
|
+
|
|
64
|
+
# 对指定交易对进行全平台的K线拉取
|
|
65
|
+
def get_symbol_kline_all_platform(self, symbol: str):
|
|
66
|
+
df_dict = {}
|
|
67
|
+
# 先拉自己的吧
|
|
68
|
+
master_df = self.query_symbol_line_data(symbol, self.master)
|
|
69
|
+
|
|
70
|
+
# 不够数据也跳过
|
|
71
|
+
if len(master_df) < 1440 * self.day_num:
|
|
72
|
+
raise Exception(
|
|
73
|
+
f"{self.master}平台拉取到的{symbol}的K线数据量不足{1440 * self.day_num}条,跳过后续处理"
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
# 对其它交易所进行拉取
|
|
77
|
+
for platform in platforms:
|
|
78
|
+
if platform == self.master:
|
|
79
|
+
continue
|
|
80
|
+
platform_df = self.query_symbol_line_data(symbol, platform)
|
|
81
|
+
|
|
82
|
+
if len(platform_df) < 1440 * self.day_num:
|
|
83
|
+
self._logger.error(
|
|
84
|
+
f"{platform}平台拉取到的{symbol}的K线数据量不足{1440 * self.day_num}条,跳过该交易所数据"
|
|
85
|
+
)
|
|
86
|
+
continue
|
|
87
|
+
|
|
88
|
+
df_dict[platform] = platform_df
|
|
89
|
+
|
|
90
|
+
# 开始计算差异
|
|
91
|
+
symbol_diffrenence_dict = self.calculate_kline_difference(
|
|
92
|
+
symbol, master_df, df_dict
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
self._redis.hset(
|
|
96
|
+
self.redis_key + ":" + self.master.upper(),
|
|
97
|
+
symbol,
|
|
98
|
+
json.dumps({k: v.model_dump() for k, v in symbol_diffrenence_dict.items()}),
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
# 计算各个平台的K线差异
|
|
102
|
+
def calculate_kline_difference(
|
|
103
|
+
self, symbol: str, master_df: pd.DataFrame, df_dict: dict
|
|
104
|
+
):
|
|
105
|
+
res = {}
|
|
106
|
+
# 自己也要计算差异
|
|
107
|
+
master_std = master_df[f"{self.master}_close"].std()
|
|
108
|
+
master_mean = master_df[f"{self.master}_close"].mean()
|
|
109
|
+
master_max = master_df[f"{self.master}_close"].max()
|
|
110
|
+
master_rate_corr = master_df[f"{self.master}_close"].corr(
|
|
111
|
+
master_df["event_time"]
|
|
112
|
+
)
|
|
113
|
+
master_min = master_df[f"{self.master}_close"].min()
|
|
114
|
+
master_quantile = master_df[f"{self.master}_close"].quantile([0.25, 0.5, 0.75])
|
|
115
|
+
|
|
116
|
+
res[self.master] = StatisticsInfo(
|
|
117
|
+
platform=self.master,
|
|
118
|
+
std=master_std,
|
|
119
|
+
mean=master_mean,
|
|
120
|
+
max=master_max,
|
|
121
|
+
corr=master_rate_corr,
|
|
122
|
+
min=master_min,
|
|
123
|
+
quantile={str(k): float(v) for k, v in master_quantile.to_dict().items()},
|
|
124
|
+
)
|
|
125
|
+
for platform, platform_df in df_dict.items():
|
|
126
|
+
# 合并数据,找出差异
|
|
127
|
+
merged_df = pd.merge(
|
|
128
|
+
master_df, platform_df, on=["symbol", "event_time"], how="inner"
|
|
129
|
+
)
|
|
130
|
+
# 计算差值
|
|
131
|
+
merged_df["close_diff"] = (
|
|
132
|
+
merged_df[f"{self.master}_close"] - merged_df[f"{platform}_close"]
|
|
133
|
+
)
|
|
134
|
+
# 标准差
|
|
135
|
+
close_std = merged_df["close_diff"].std()
|
|
136
|
+
# 平均值
|
|
137
|
+
close_mean = merged_df["close_diff"].mean()
|
|
138
|
+
# 最大值
|
|
139
|
+
close_max = merged_df["close_diff"].max()
|
|
140
|
+
# 皮尔逊系数(斜率)
|
|
141
|
+
close_corr = merged_df["close_diff"].corr(merged_df["event_time"])
|
|
142
|
+
# 最小值
|
|
143
|
+
close_min = merged_df["close_diff"].min()
|
|
144
|
+
# 4分位数
|
|
145
|
+
close_quantile = merged_df["close_diff"].quantile([0.25, 0.5, 0.75])
|
|
146
|
+
# self._logger.info(
|
|
147
|
+
# f"{self.master}与{platform}平台的{symbol}的K线差异统计: 标准差={close_std}, 平均值={close_mean}, 最大值={close_max}, 最小值={close_min}, 皮尔逊系数={close_corr}, 四分位数={close_quantile.to_dict()}"
|
|
148
|
+
# )
|
|
149
|
+
res[platform] = StatisticsInfo(
|
|
150
|
+
platform=platform,
|
|
151
|
+
std=close_std,
|
|
152
|
+
mean=close_mean,
|
|
153
|
+
max=close_max,
|
|
154
|
+
corr=close_corr,
|
|
155
|
+
min=close_min,
|
|
156
|
+
quantile={str(k): float(v) for k, v in close_quantile.to_dict().items()},
|
|
157
|
+
)
|
|
158
|
+
|
|
159
|
+
return res
|
|
160
|
+
|
|
161
|
+
# 拉指定时间指定symbol的k线数据
|
|
162
|
+
def query_symbol_line_data(self, symbol: str, platform: str):
|
|
163
|
+
sql_result_df = pd.DataFrame()
|
|
164
|
+
zero_timestamp_list = self.get_zero_timestamp_list()
|
|
165
|
+
# 表前缀
|
|
166
|
+
table_name_prefix = f"kaq_{platform}_futures_kline_history"
|
|
167
|
+
for ts in zero_timestamp_list:
|
|
168
|
+
# 先转成周一日期来定表名,因为数据表是按周来分表的
|
|
169
|
+
date_str = self.get_monday_time(ts)
|
|
170
|
+
table_name = f"{table_name_prefix}_{date_str}"
|
|
171
|
+
sql = f"select exchange, symbol, close as {platform}_close, event_time from {table_name} where symbol = '{symbol}' and event_time >= {ts} and event_time < {ts + 86400000} order by event_time desc ;"
|
|
172
|
+
result = self._mysql.fetch_data(sql)
|
|
173
|
+
sql_result_df = pd.concat([sql_result_df, result], ignore_index=True)
|
|
174
|
+
|
|
175
|
+
return sql_result_df
|
|
176
|
+
|
|
177
|
+
# 计算某个时间戳对应的周一日期字符串
|
|
178
|
+
def get_monday_time(self, timestamp):
|
|
179
|
+
dt = datetime.datetime.fromtimestamp(timestamp / 1000)
|
|
180
|
+
monday = dt - datetime.timedelta(days=dt.weekday())
|
|
181
|
+
monday = monday.replace(hour=0, minute=0, second=0, microsecond=0)
|
|
182
|
+
return monday.strftime("%Y%m%d")
|
|
183
|
+
|
|
184
|
+
# 计算开始到结束时间所有的0时时间戳
|
|
185
|
+
def get_zero_timestamp_list(self):
|
|
186
|
+
# 毫秒转日期
|
|
187
|
+
timestamp_list = []
|
|
188
|
+
begin_date = datetime.datetime.fromtimestamp(
|
|
189
|
+
self.begin_timestamp // 1000, datetime.UTC
|
|
190
|
+
).date()
|
|
191
|
+
end_date = datetime.datetime.fromtimestamp(
|
|
192
|
+
self.end_timestamp // 1000, datetime.UTC
|
|
193
|
+
).date()
|
|
194
|
+
cur = begin_date
|
|
195
|
+
while cur <= end_date:
|
|
196
|
+
# 0点时间戳(毫秒)
|
|
197
|
+
dt = datetime.datetime.combine(
|
|
198
|
+
cur, datetime.time(0, 0), tzinfo=datetime.UTC
|
|
199
|
+
)
|
|
200
|
+
ts = int(dt.timestamp() * 1000)
|
|
201
|
+
timestamp_list.append(ts)
|
|
202
|
+
cur += datetime.timedelta(days=1)
|
|
203
|
+
|
|
204
|
+
return timestamp_list
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
if __name__ == "__main__":
|
|
208
|
+
klineStatistics = FuturesKlineHistoryStatistics(
|
|
209
|
+
1765296000000, 1765382400000, ["BTCUSDT", "ETHUSDT"]
|
|
210
|
+
)
|
|
211
|
+
klineStatistics.symbols_statistics()
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
from abc import abstractmethod
|
|
2
|
+
|
|
3
|
+
from kaq_quant_common.common.monitor_base import MonitorBase
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
# 通用的抽象类,包装一层ws操作
|
|
7
|
+
class WsWrapper(MonitorBase):
|
|
8
|
+
def __init__(self):
|
|
9
|
+
super().__init__()
|
|
10
|
+
|
|
11
|
+
# stop 就是调用close,ws 更好理解
|
|
12
|
+
def _do_stop(self):
|
|
13
|
+
self._do_close()
|
|
14
|
+
|
|
15
|
+
# 断开连接,主动关闭
|
|
16
|
+
def close(self):
|
|
17
|
+
self.stop()
|
|
18
|
+
|
|
19
|
+
@abstractmethod
|
|
20
|
+
def _do_close(self):
|
|
21
|
+
pass
|
|
File without changes
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import dolphindb as ddb
|
|
3
|
+
import dolphindb.settings as keys
|
|
4
|
+
from dolphindb.settings import SqlStd
|
|
5
|
+
from kaq_quant_common.utils import yml_utils
|
|
6
|
+
import pandas as pd
|
|
7
|
+
import threading
|
|
8
|
+
from kaq_quant_common.utils.logger_utils import get_logger
|
|
9
|
+
import traceback
|
|
10
|
+
from dolphindb.settings import PROTOCOL_PICKLE
|
|
11
|
+
|
|
12
|
+
mutex = threading.Lock()
|
|
13
|
+
|
|
14
|
+
class KaqQuantDdbPoolStreamReadRepository:
|
|
15
|
+
'''
|
|
16
|
+
连接池方式连接DolphinDB数据库, 支持流数据表读取
|
|
17
|
+
'''
|
|
18
|
+
def __init__(self, host, port, user, passwd, pool_size=1, protocal:keys=PROTOCOL_PICKLE):
|
|
19
|
+
self.logger = get_logger(self)
|
|
20
|
+
try:
|
|
21
|
+
mutex.acquire()
|
|
22
|
+
|
|
23
|
+
# 连接地址为localhost,端口为8848的DolphinDB,登录用户名为admin,密码为123456的账户,连接数为8
|
|
24
|
+
self.pool = ddb.DBConnectionPool(host, port, pool_size, user, passwd,
|
|
25
|
+
loadBalance=False,
|
|
26
|
+
compress=True,
|
|
27
|
+
protocol=protocal,
|
|
28
|
+
reConnect=True,
|
|
29
|
+
# tryReconnectNums=5, # 若不开启高可用,须与 reconnect 参数搭配使用,对单节点进行有限次重连。若不填写该参数,默认进行无限重连。
|
|
30
|
+
sqlStd=SqlStd.DolphinDB
|
|
31
|
+
)
|
|
32
|
+
except Exception as e:
|
|
33
|
+
self.logger.error(f'KaqQuantDdbPoolStreamReadRepository.__init__ is occured error: {str(e)} - {str(traceback.format_exc())}')
|
|
34
|
+
finally:
|
|
35
|
+
mutex.release()
|
|
36
|
+
|
|
37
|
+
async def query(self, script: str, pickleTableToList=False) -> pd.DataFrame:
|
|
38
|
+
'''
|
|
39
|
+
从流数据表中获取数据
|
|
40
|
+
'''
|
|
41
|
+
try:
|
|
42
|
+
data = await self.pool.run(script, pickleTableToList=pickleTableToList, clearMemory=True)
|
|
43
|
+
if data is None:
|
|
44
|
+
return pd.DataFrame()
|
|
45
|
+
big_df = pd.DataFrame(data)
|
|
46
|
+
return big_df
|
|
47
|
+
except Exception as e:
|
|
48
|
+
self.logger.error(f'KaqQuantDdbPoolStreamReadRepository.fetch is occured error: {str(e)} - {str(traceback.format_exc())}')
|
|
49
|
+
return pd.DataFrame()
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
if __name__ == '__main__':
|
|
53
|
+
host, port, user, passwd = yml_utils.get_ddb_info(os.getcwd())
|
|
54
|
+
kaq = KaqQuantDdbPoolStreamReadRepository(host, port, user, passwd)
|
|
55
|
+
df = kaq.query(f"select count(*) from kaq_all_future_limit_order_streaming")
|
|
56
|
+
print(df.head(5))
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import threading
|
|
2
|
+
import traceback
|
|
3
|
+
from string import Template
|
|
4
|
+
|
|
5
|
+
import dolphindb as ddb
|
|
6
|
+
from kaq_quant_common.utils import yml_utils
|
|
7
|
+
from kaq_quant_common.utils.logger_utils import get_logger
|
|
8
|
+
|
|
9
|
+
mutex = threading.Lock()
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class KaqQuantDdbStreamInitRepository:
|
|
13
|
+
'''
|
|
14
|
+
定义 asof_join的级联方式, 合并数据到一起, 然后可以订阅判断
|
|
15
|
+
'''
|
|
16
|
+
|
|
17
|
+
def __init__(self, host, port, user, passwd, ddb_script_files={}):
|
|
18
|
+
self.logger = get_logger(self)
|
|
19
|
+
if not host or not port or not user or not passwd:
|
|
20
|
+
self.logger.error("【DDB连接参数错误】请检查配置文件")
|
|
21
|
+
if not ddb_script_files or len(ddb_script_files) == 0:
|
|
22
|
+
self.logger.error("【DDB脚本文件列表为空】请检查配置文件")
|
|
23
|
+
|
|
24
|
+
mutex.acquire()
|
|
25
|
+
'''
|
|
26
|
+
创建ddb连接 && 添加ddb流数据表支持
|
|
27
|
+
'''
|
|
28
|
+
self.session = ddb.session(enableASYNC=True)
|
|
29
|
+
self.session.connect(host, port, user, passwd)
|
|
30
|
+
# self.session.enableStreaming()
|
|
31
|
+
|
|
32
|
+
'''
|
|
33
|
+
创建流数据表 && 创建引擎
|
|
34
|
+
'''
|
|
35
|
+
for script_file, args in ddb_script_files.items():
|
|
36
|
+
try:
|
|
37
|
+
# 读取脚本 并转换为gbk编码
|
|
38
|
+
with open(file=script_file, mode="r", encoding="utf-8") as fp:
|
|
39
|
+
script = fp.read().encode('gbk').decode('gbk')
|
|
40
|
+
if args is None or len(args) == 0:
|
|
41
|
+
# self.session.runFile(script_file)
|
|
42
|
+
pass
|
|
43
|
+
else:
|
|
44
|
+
template = Template(script)
|
|
45
|
+
script = template.substitute(**args)
|
|
46
|
+
self.session.run(script)
|
|
47
|
+
except Exception as e:
|
|
48
|
+
self.logger.error(f"【创建ddb数据流引擎】 {script_file} 错误异常: {str(e)} - {str(traceback.format_exc())}")
|
|
49
|
+
self.cancel_subscribe()
|
|
50
|
+
mutex.release()
|
|
51
|
+
|
|
52
|
+
def cancel_subscribe(self, file_path=None):
|
|
53
|
+
# return
|
|
54
|
+
'''
|
|
55
|
+
1、取消订阅
|
|
56
|
+
PS: 此处不能使用python的localhost
|
|
57
|
+
创建流订阅与引擎
|
|
58
|
+
使用的是ddb的脚步
|
|
59
|
+
传输到服务器上运行
|
|
60
|
+
会调用的节点为服务器ip、port、节点名称
|
|
61
|
+
'''
|
|
62
|
+
if file_path is None or file_path == "":
|
|
63
|
+
self.logger.info("【取消订阅】请传参数file_path")
|
|
64
|
+
return
|
|
65
|
+
self.session.runFile(file_path)
|
|
66
|
+
|
|
67
|
+
def drop_streaming(self, file_path=None):
|
|
68
|
+
# return
|
|
69
|
+
'''
|
|
70
|
+
1、取消订阅
|
|
71
|
+
PS: 此处不能使用python的localhost
|
|
72
|
+
创建流订阅与引擎
|
|
73
|
+
使用的是ddb的脚步
|
|
74
|
+
传输到服务器上运行
|
|
75
|
+
会调用的节点为服务器ip、port、节点名称
|
|
76
|
+
'''
|
|
77
|
+
if file_path is None or file_path == "":
|
|
78
|
+
self.logger.info("【取消订阅】请传参数file_path")
|
|
79
|
+
return
|
|
80
|
+
self.session.runFile(file_path)
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
if __name__ == '__main__':
|
|
84
|
+
host, port, user, passwd = yml_utils.get_ddb_info(pkg_name='kaq_binance_quant')
|
|
85
|
+
ddb_script_files = ['binance_volume_ddb_script.dos', 'binance_limit_order_ddb_script.dos', 'binance_premium_ddb_script.dos']
|
|
86
|
+
ddb_script_files = [yml_utils.get_pkg_file(None, x) for x in ddb_script_files]
|
|
87
|
+
kaq = KaqQuantDdbStreamInitRepository(host, port, user, passwd, ddb_script_files)
|
|
88
|
+
# kaq.drop_streaming()
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import dolphindb as ddb
|
|
3
|
+
from kaq_quant_common.utils import yml_utils
|
|
4
|
+
import pandas as pd
|
|
5
|
+
import threading
|
|
6
|
+
from kaq_quant_common.utils.logger_utils import get_logger
|
|
7
|
+
import traceback
|
|
8
|
+
|
|
9
|
+
mutex = threading.Lock()
|
|
10
|
+
|
|
11
|
+
class KaqQuantDdbStreamReadRepository:
|
|
12
|
+
'''
|
|
13
|
+
定义 asof_join的级联方式, 合并数据到一起, 然后可以订阅判断
|
|
14
|
+
'''
|
|
15
|
+
def __init__(self, host, port, user, passwd):
|
|
16
|
+
self.logger = get_logger(self)
|
|
17
|
+
'''
|
|
18
|
+
创建ddb连接 && 添加ddb流数据表支持
|
|
19
|
+
'''
|
|
20
|
+
try:
|
|
21
|
+
mutex.acquire()
|
|
22
|
+
# 程序读取的时候 同步读取即可, ddb的异步读取,可以使用订阅的方式来实现
|
|
23
|
+
self.session = ddb.session(enableASYNC=False)
|
|
24
|
+
self.session.setTimeout(3600)
|
|
25
|
+
self.session.connect(host, port, user, passwd, keepAliveTime=240, reconnect=True, tryReconnectNums=10)
|
|
26
|
+
# self.session.enableStreaming()
|
|
27
|
+
|
|
28
|
+
# 需要注意的是 fetchSize 取值不能小于 8192 (记录条数)
|
|
29
|
+
self.size = 8192
|
|
30
|
+
except Exception as e:
|
|
31
|
+
self.logger.error(f'KaqQuantDdbStreamReadRepository.__init__ is occured error: {str(e)} - {str(traceback.format_exc())}')
|
|
32
|
+
finally:
|
|
33
|
+
mutex.release()
|
|
34
|
+
|
|
35
|
+
def count(self, query: str) -> int:
|
|
36
|
+
'''
|
|
37
|
+
获取数量, 再去获取总数据
|
|
38
|
+
sql语句中必须返回的是 count 字段
|
|
39
|
+
'''
|
|
40
|
+
try:
|
|
41
|
+
number_blocks = self.session.run(query, fetchSize=self.size)
|
|
42
|
+
number = 0
|
|
43
|
+
while number_blocks.hasNext():
|
|
44
|
+
number = number_blocks.read()['count'][0]
|
|
45
|
+
return number
|
|
46
|
+
except Exception as e:
|
|
47
|
+
self.logger.error(f'KaqQuantDdbStreamReadRepository.countStream is occured error: {str(e)} - {str(traceback.format_exc())}')
|
|
48
|
+
return -1
|
|
49
|
+
|
|
50
|
+
def cal_page(self, number: int):
|
|
51
|
+
page = number / self.size
|
|
52
|
+
if number % self.size > 0:
|
|
53
|
+
page = page + 1
|
|
54
|
+
return int(page)
|
|
55
|
+
|
|
56
|
+
def query(self, query: str) -> pd.DataFrame:
|
|
57
|
+
'''
|
|
58
|
+
从流数据表中获取数据
|
|
59
|
+
'''
|
|
60
|
+
try:
|
|
61
|
+
block = self.session.run(query, fetchSize=self.size)
|
|
62
|
+
if block is None:
|
|
63
|
+
return pd.DataFrame()
|
|
64
|
+
big_df = pd.DataFrame()
|
|
65
|
+
while block.hasNext():
|
|
66
|
+
temp_df = block.read()
|
|
67
|
+
big_df = pd.concat([big_df, temp_df])
|
|
68
|
+
big_df = big_df.reset_index()
|
|
69
|
+
return big_df
|
|
70
|
+
except Exception as e:
|
|
71
|
+
self.logger.error(f'KaqQuantDdbStreamReadRepository.getStreamQuery is occured error: {str(e)} - {str(traceback.format_exc())}')
|
|
72
|
+
return pd.DataFrame()
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
if __name__ == '__main__':
|
|
76
|
+
host, port, user, passwd = yml_utils.get_ddb_info(os.getcwd())
|
|
77
|
+
kaq = KaqQuantDdbStreamReadRepository(host, port, user, passwd)
|
|
78
|
+
# kaq.drop_streaming()
|
|
79
|
+
count = kaq.countStream(f"select count(*) from kaq_binance_force_order_streaming where symbol == 'FIOUSDT'")
|
|
80
|
+
df = kaq.getStreamQuery(query=f"select * from kaq_binance_force_order_streaming where symbol == 'FIOUSDT' order by create_time asc")
|
|
81
|
+
print(df.head(5))
|