openfund-core 0.0.4__py3-none-any.whl → 1.0.1__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.
- core/Exchange.py +276 -0
- core/main.py +23 -0
- core/smc/SMCBase.py +130 -0
- core/smc/SMCFVG.py +86 -0
- core/smc/SMCLiquidity.py +7 -0
- core/smc/SMCOrderBlock.py +288 -0
- core/smc/SMCPDArray.py +77 -0
- core/smc/SMCStruct.py +290 -0
- core/smc/__init__.py +0 -0
- core/utils/OPTools.py +30 -0
- openfund_core-1.0.1.dist-info/METADATA +48 -0
- openfund_core-1.0.1.dist-info/RECORD +15 -0
- {openfund_core-0.0.4.dist-info → openfund_core-1.0.1.dist-info}/WHEEL +1 -1
- openfund_core-1.0.1.dist-info/entry_points.txt +3 -0
- openfund/core/__init__.py +0 -14
- openfund/core/api_tools/__init__.py +0 -16
- openfund/core/api_tools/binance_futures_tools.py +0 -23
- openfund/core/api_tools/binance_tools.py +0 -26
- openfund/core/api_tools/enums.py +0 -539
- openfund/core/base_collector.py +0 -72
- openfund/core/base_tool.py +0 -58
- openfund/core/factory.py +0 -97
- openfund/core/openfund_old/continuous_klines.py +0 -153
- openfund/core/openfund_old/depth.py +0 -92
- openfund/core/openfund_old/historical_trades.py +0 -123
- openfund/core/openfund_old/index_info.py +0 -67
- openfund/core/openfund_old/index_price_kline.py +0 -118
- openfund/core/openfund_old/klines.py +0 -95
- openfund/core/openfund_old/klines_qrr.py +0 -103
- openfund/core/openfund_old/mark_price.py +0 -121
- openfund/core/openfund_old/mark_price_klines.py +0 -122
- openfund/core/openfund_old/ticker_24hr_price_change.py +0 -99
- openfund/core/pyopenfund.py +0 -85
- openfund/core/services/um_futures_collector.py +0 -142
- openfund/core/sycu_exam/__init__.py +0 -1
- openfund/core/sycu_exam/exam.py +0 -19
- openfund/core/sycu_exam/random_grade_cplus.py +0 -440
- openfund/core/sycu_exam/random_grade_web.py +0 -404
- openfund/core/utils/time_tools.py +0 -25
- openfund_core-0.0.4.dist-info/LICENSE +0 -201
- openfund_core-0.0.4.dist-info/METADATA +0 -67
- openfund_core-0.0.4.dist-info/RECORD +0 -30
- {openfund/core/openfund_old → core}/__init__.py +0 -0
@@ -0,0 +1,288 @@
|
|
1
|
+
import logging
|
2
|
+
import pandas as pd
|
3
|
+
|
4
|
+
from core.smc.SMCStruct import SMCStruct
|
5
|
+
|
6
|
+
|
7
|
+
class SMCOrderBlock(SMCStruct):
|
8
|
+
OB_HIGH_COL = "ob_high"
|
9
|
+
OB_LOW_COL = "ob_low"
|
10
|
+
OB_MID_COL = "ob_mid"
|
11
|
+
OB_VOLUME_COL = "ob_volume"
|
12
|
+
OB_DIRECTION_COL = "ob_direction" # 1: 向上突破, 2: 向下突破
|
13
|
+
# OB_START_INDEX_COL = "ob_start_index"
|
14
|
+
# OB_START_TS_COL = "ob_start_ts"
|
15
|
+
OB_ATR = "ob_atr"
|
16
|
+
OB_IS_COMBINED = "ob_is_combined"
|
17
|
+
OB_WAS_CROSSED = "ob_was_crossed"
|
18
|
+
|
19
|
+
def __init__(self):
|
20
|
+
super().__init__()
|
21
|
+
self.logger = logging.getLogger(__name__)
|
22
|
+
|
23
|
+
def find_OBs(
|
24
|
+
self,
|
25
|
+
struct: pd.DataFrame,
|
26
|
+
side=None,
|
27
|
+
start_index: int = -1,
|
28
|
+
is_valid: bool = True,
|
29
|
+
if_combine: bool = True,
|
30
|
+
) -> pd.DataFrame:
|
31
|
+
"""_summary_
|
32
|
+
|
33
|
+
Args:
|
34
|
+
symbol (_type_): _description_
|
35
|
+
data (pd.DataFrame): _description_
|
36
|
+
side (_type_): _description_ 如果是None, 则返回所有OB boxes(包括bullish和bearish)
|
37
|
+
pivot_index (int): _description_ 开始的位置
|
38
|
+
is_valid (bool): _description_ 找到有效的OB,没有被crossed
|
39
|
+
if_combine (bool): _description_ 是否合并OB
|
40
|
+
Returns:
|
41
|
+
list: _description_
|
42
|
+
"""
|
43
|
+
|
44
|
+
df = struct.copy() if start_index == -1 else struct.copy().iloc[start_index:]
|
45
|
+
if self.OB_DIRECTION_COL not in df.columns:
|
46
|
+
df = self.build_struct_for_ob(df)
|
47
|
+
|
48
|
+
# 获取有效的OB数据
|
49
|
+
ob_df = df[df[self.OB_DIRECTION_COL].notna()]
|
50
|
+
|
51
|
+
# 根据side过滤并生成OB
|
52
|
+
if side is not None:
|
53
|
+
direction = "Bullish" if side == self.BUY_SIDE else "Bearish"
|
54
|
+
ob_df = ob_df[ob_df[self.OB_DIRECTION_COL] == direction]
|
55
|
+
|
56
|
+
# 检查OB是否被平衡过
|
57
|
+
|
58
|
+
ob_df.loc[:, self.OB_WAS_CROSSED] = ob_df.apply(
|
59
|
+
lambda row: any(
|
60
|
+
df.loc[row.name + 1 :, self.LOW_COL] <= row[self.OB_LOW_COL]
|
61
|
+
)
|
62
|
+
if row[self.OB_DIRECTION_COL] == "Bullish"
|
63
|
+
else any(df.loc[row.name + 1 :, self.HIGH_COL] >= row[self.OB_HIGH_COL]),
|
64
|
+
axis=1,
|
65
|
+
)
|
66
|
+
|
67
|
+
ob_df = ob_df[~ob_df[self.OB_WAS_CROSSED]]
|
68
|
+
|
69
|
+
# # 过滤出有效的OB
|
70
|
+
# if is_valid is not None and not ob_df.empty:
|
71
|
+
# valid_mask = []
|
72
|
+
# for _, row in ob_df.iterrows():
|
73
|
+
# start_idx = row[self.OB_START_INDEX_COL] + 1
|
74
|
+
# if start_idx >= len(df):
|
75
|
+
# valid = False
|
76
|
+
# else:
|
77
|
+
# future_data = df.loc[start_idx:]
|
78
|
+
# if row[self.OB_DIRECTION_COL] == "Bullish":
|
79
|
+
# valid = row[self.OB_LOW_COL] < future_data[self.LOW_COL].min()
|
80
|
+
# else: # Bearish
|
81
|
+
# valid = row[self.OB_HIGH_COL] > future_data[self.HIGH_COL].max()
|
82
|
+
|
83
|
+
# valid_mask.append(valid if is_valid else not valid)
|
84
|
+
|
85
|
+
# ob_df = ob_df[pd.Series(valid_mask, index=ob_df.index)]
|
86
|
+
|
87
|
+
if if_combine:
|
88
|
+
# 合并OB
|
89
|
+
ob_df = self._combineOB(ob_df)
|
90
|
+
|
91
|
+
return ob_df
|
92
|
+
|
93
|
+
def build_struct_for_ob(
|
94
|
+
self, df, window=20, is_struct_body_break=True, atr_multiplier=0.6
|
95
|
+
):
|
96
|
+
"""
|
97
|
+
构建结构并检测Order Block
|
98
|
+
|
99
|
+
Args:
|
100
|
+
df: 数据框
|
101
|
+
window: 寻找结构极值的窗口大小
|
102
|
+
is_struct_body_break: 是否使用收盘价判断突破
|
103
|
+
ob_length: 搜索Order Block的回溯长度
|
104
|
+
atr_multiplier: ATR倍数阈值
|
105
|
+
|
106
|
+
Returns:
|
107
|
+
处理后的数据框,包含结构和Order Block相关列
|
108
|
+
"""
|
109
|
+
# 首先构建基础结构
|
110
|
+
df = self.build_struct(df, window, is_struct_body_break)
|
111
|
+
|
112
|
+
check_columns = [self.HIGH_COL, self.LOW_COL, self.CLOSE_COL]
|
113
|
+
self.check_columns(df, check_columns)
|
114
|
+
|
115
|
+
# 初始化OB相关列
|
116
|
+
ob_columns = [
|
117
|
+
self.OB_HIGH_COL,
|
118
|
+
self.OB_LOW_COL,
|
119
|
+
self.OB_MID_COL,
|
120
|
+
self.OB_VOLUME_COL,
|
121
|
+
self.OB_DIRECTION_COL,
|
122
|
+
# self.OB_START_INDEX_COL,
|
123
|
+
# self.OB_START_TS_COL,
|
124
|
+
self.OB_ATR,
|
125
|
+
]
|
126
|
+
for col in ob_columns:
|
127
|
+
df[col] = None
|
128
|
+
|
129
|
+
# 计算ATR用于阈值判断
|
130
|
+
df[self.ATR_COL] = self._calculate_atr(df)
|
131
|
+
|
132
|
+
# 检测Order Block
|
133
|
+
for i in range(1, len(df)):
|
134
|
+
# 检查是否为结构高点突破
|
135
|
+
if df.at[i, self.STRUCT_COL] and "Bullish" in df.at[i, self.STRUCT_COL]:
|
136
|
+
self._find_ob(df, i, atr_multiplier)
|
137
|
+
|
138
|
+
# 检查是否为结构低点突破
|
139
|
+
elif df.at[i, self.STRUCT_COL] and "Bearish" in df.at[i, self.STRUCT_COL]:
|
140
|
+
self._find_ob(df, i, atr_multiplier, is_bullish=False)
|
141
|
+
|
142
|
+
return df
|
143
|
+
|
144
|
+
def _combineOB(self, df_OBs, combine_atr_muiltiplier=0.2):
|
145
|
+
"""
|
146
|
+
合并OB
|
147
|
+
"""
|
148
|
+
|
149
|
+
df_ob = df_OBs.copy()
|
150
|
+
# 初始化 OB_IS_COMBINED 列为 0
|
151
|
+
df_ob[self.OB_IS_COMBINED] = 0
|
152
|
+
|
153
|
+
combine_atr_muiltiplier = self.toDecimal(combine_atr_muiltiplier)
|
154
|
+
# 遍历所有OB,检查是否需要合并
|
155
|
+
for i in range(len(df_ob)):
|
156
|
+
# 如果当前OB已被合并,跳过
|
157
|
+
if df_ob.iloc[i][self.OB_IS_COMBINED] == 1:
|
158
|
+
continue
|
159
|
+
|
160
|
+
current_direction = df_ob.iloc[i][self.OB_DIRECTION_COL]
|
161
|
+
current_mid = df_ob.iloc[i][self.OB_MID_COL]
|
162
|
+
current_atr = df_ob.iloc[i][self.OB_ATR]
|
163
|
+
|
164
|
+
# 检查后续的OB
|
165
|
+
for j in range(i + 1, len(df_ob)):
|
166
|
+
# 如果后续OB已被合并,跳过
|
167
|
+
if df_ob.iloc[j][self.OB_IS_COMBINED] == 1:
|
168
|
+
continue
|
169
|
+
|
170
|
+
# 如果方向相同且中间价差值小于阈值,标记为已合并
|
171
|
+
if (
|
172
|
+
df_ob.iloc[j][self.OB_DIRECTION_COL] == current_direction
|
173
|
+
and abs(df_ob.iloc[j][self.OB_MID_COL] - current_mid)
|
174
|
+
< current_atr * combine_atr_muiltiplier
|
175
|
+
):
|
176
|
+
df_ob.iloc[i, df_ob.columns.get_loc(self.OB_IS_COMBINED)] = 1
|
177
|
+
break
|
178
|
+
# 遍历所有的OB
|
179
|
+
|
180
|
+
return df_ob
|
181
|
+
|
182
|
+
def _calculate_atr(self, df, period=200, multiplier=1):
|
183
|
+
return super().calculate_atr(df, period, multiplier)
|
184
|
+
|
185
|
+
def _find_ob(self, df, i, atr_multiplier, is_bullish=True):
|
186
|
+
"""寻找Order Block
|
187
|
+
Args:
|
188
|
+
df: 数据框
|
189
|
+
i: 当前索引
|
190
|
+
ob_length: OB长度
|
191
|
+
atr_multiplier: ATR乘数
|
192
|
+
is_bullish: 是否为看涨OB,True为看涨,False为看跌
|
193
|
+
"""
|
194
|
+
# 根据方向获取相应的结构索引和价格,OB是取结构开始的最高价或最低价的K线
|
195
|
+
if is_bullish:
|
196
|
+
index = df.loc[i, self.STRUCT_LOW_INDEX_COL]
|
197
|
+
extreme_price = df.loc[i, self.STRUCT_LOW_COL]
|
198
|
+
|
199
|
+
# Oper_func = min
|
200
|
+
src = self.toDecimal(df.loc[index, self.HIGH_COL])
|
201
|
+
direction = "Bullish"
|
202
|
+
|
203
|
+
else:
|
204
|
+
index = df.loc[i, self.STRUCT_HIGH_INDEX_COL]
|
205
|
+
extreme_price = df.loc[i, self.STRUCT_HIGH_COL]
|
206
|
+
# Oper_func = max
|
207
|
+
src = self.toDecimal(df.loc[index, self.LOW_COL])
|
208
|
+
direction = "Bearish"
|
209
|
+
|
210
|
+
# 计算累积成交量
|
211
|
+
vol = df.loc[index:i, self.VOLUME_COL].sum()
|
212
|
+
|
213
|
+
# 应用ATR阈值,如果OB的范围小于ATR(=20)的60%(20*0.6=12),则把OB可扩展到ATR(=20)范围。
|
214
|
+
precision = self.get_precision_length(extreme_price)
|
215
|
+
atr = self.toDecimal(df.loc[i, self.ATR_COL])
|
216
|
+
atr_multiplier = self.toDecimal(atr_multiplier)
|
217
|
+
if is_bullish:
|
218
|
+
# 计算当前区间大小
|
219
|
+
current_range = src - extreme_price
|
220
|
+
target_range = atr
|
221
|
+
if current_range < atr * atr_multiplier:
|
222
|
+
# 如果区间过小,将区间扩展到目标大小,并在中心点两侧平均分配
|
223
|
+
extend_amount = (target_range - current_range) / 2
|
224
|
+
src += extend_amount
|
225
|
+
extreme_price -= extend_amount
|
226
|
+
|
227
|
+
high, low = (
|
228
|
+
self.toDecimal(src, precision),
|
229
|
+
self.toDecimal(extreme_price, precision),
|
230
|
+
)
|
231
|
+
else:
|
232
|
+
# 计算当前区间大小
|
233
|
+
current_range = extreme_price - src
|
234
|
+
target_range = atr
|
235
|
+
if current_range < atr * atr_multiplier:
|
236
|
+
# 如果区间过小,将区间扩展到目标大小,并在中心点两侧平均分配
|
237
|
+
extend_amount = (target_range - current_range) / 2
|
238
|
+
src -= extend_amount
|
239
|
+
extreme_price += extend_amount
|
240
|
+
|
241
|
+
high, low = (
|
242
|
+
self.toDecimal(extreme_price, precision),
|
243
|
+
self.toDecimal(src, precision),
|
244
|
+
)
|
245
|
+
|
246
|
+
# 计算中间值
|
247
|
+
mid = (high + low) / 2
|
248
|
+
|
249
|
+
# 更新OB信息到DataFrame
|
250
|
+
df.at[index, self.OB_HIGH_COL] = high
|
251
|
+
df.at[index, self.OB_LOW_COL] = low
|
252
|
+
df.at[index, self.OB_MID_COL] = mid
|
253
|
+
df.at[index, self.OB_VOLUME_COL] = vol
|
254
|
+
df.at[index, self.OB_DIRECTION_COL] = direction
|
255
|
+
# df.at[i, self.OB_START_INDEX_COL] = index
|
256
|
+
# df.at[i, self.OB_START_TS_COL] = df.loc[index, self.TIMESTAMP_COL]
|
257
|
+
df.at[index, self.OB_ATR] = atr
|
258
|
+
|
259
|
+
def get_last_ob(self, df, prd=-1):
|
260
|
+
"""
|
261
|
+
获取最新的Order Block
|
262
|
+
|
263
|
+
Args:
|
264
|
+
df: 包含OB信息的数据框
|
265
|
+
prd: 回溯周期,-1表示全部
|
266
|
+
|
267
|
+
Returns:
|
268
|
+
最新的Order Block信息或None
|
269
|
+
"""
|
270
|
+
# 获取prd范围内的数据
|
271
|
+
start_idx = max(0, len(df) - 1 - prd) if prd > 0 else 0
|
272
|
+
|
273
|
+
# 筛选有效OB且在prd范围内的数据
|
274
|
+
mask = df[self.OB_DIRECTION_COL].notna() & (df.index >= start_idx)
|
275
|
+
valid_obs = df[mask]
|
276
|
+
|
277
|
+
if not valid_obs.empty:
|
278
|
+
# 获取最近的OB
|
279
|
+
last_ob = valid_obs.iloc[-1]
|
280
|
+
return {
|
281
|
+
self.OB_HIGH_COL: last_ob[self.OB_HIGH_COL],
|
282
|
+
self.OB_LOW_COL: last_ob[self.OB_LOW_COL],
|
283
|
+
self.OB_MID_COL: last_ob[self.OB_MID_COL],
|
284
|
+
self.OB_VOLUME_COL: last_ob[self.OB_VOLUME_COL],
|
285
|
+
self.OB_DIRECTION_COL: last_ob[self.OB_DIRECTION_COL],
|
286
|
+
}
|
287
|
+
|
288
|
+
return None
|
core/smc/SMCPDArray.py
ADDED
@@ -0,0 +1,77 @@
|
|
1
|
+
import logging
|
2
|
+
import pandas as pd
|
3
|
+
|
4
|
+
from core.smc.SMCFVG import SMCFVG
|
5
|
+
from core.smc.SMCOrderBlock import SMCOrderBlock
|
6
|
+
|
7
|
+
class SMCPDArray(SMCFVG,SMCOrderBlock):
|
8
|
+
PD_HIGH_COL = "pd_high"
|
9
|
+
PD_LOW_COL = "pd_low"
|
10
|
+
PD_MID_COL = "pd_mid"
|
11
|
+
PD_TYPE_COL = "pd_type"
|
12
|
+
|
13
|
+
def __init__(self):
|
14
|
+
super().__init__()
|
15
|
+
self.logger = logging.getLogger(__name__)
|
16
|
+
|
17
|
+
def find_PDArrays(
|
18
|
+
self, struct: pd.DataFrame, side, start_index=-1
|
19
|
+
) -> pd.DataFrame:
|
20
|
+
"""_summary_
|
21
|
+
寻找PDArrays,包括Fair Value Gap (FVG)|Order Block (OB)|Breaker Block(BB)|Mitigation Block(BB)
|
22
|
+
Args:
|
23
|
+
data (pd.DataFrame): K线数据
|
24
|
+
side (_type_): 交易方向 'buy'|'sell'
|
25
|
+
threshold (_type_): 阈值价格,通常为溢价和折价区的CE
|
26
|
+
check_balanced (bool): 是否检查FVG是否被平衡过,默认为True
|
27
|
+
start_index (int): 开始查找索引的起点,默认为-1
|
28
|
+
|
29
|
+
Returns:
|
30
|
+
pd.DataFrame: _description_
|
31
|
+
|
32
|
+
"""
|
33
|
+
|
34
|
+
df = (
|
35
|
+
struct.copy()
|
36
|
+
if start_index == -1
|
37
|
+
else struct.copy().iloc[max(0, start_index - 1) :]
|
38
|
+
)
|
39
|
+
# check_columns = [self.HIGH_COL, self.LOW_COL, self.CLOSE_COL]
|
40
|
+
# self.check_columns(df, check_columns)
|
41
|
+
|
42
|
+
df_FVGs = self.find_FVGs(df, side)
|
43
|
+
# self.logger.info(f"fvgs:\n{df_FVGs[['timestamp', self.FVG_SIDE, self.FVG_TOP, self.FVG_BOT, self.FVG_WAS_BALANCED]]}")
|
44
|
+
|
45
|
+
|
46
|
+
df_OBs = self.find_OBs(df, side)
|
47
|
+
# self.logger.info("find_OBs:\n %s", df_OBs)
|
48
|
+
|
49
|
+
# 使用更简洁的方式重命名和合并时间戳列
|
50
|
+
timestamp_mapping = {self.TIMESTAMP_COL: ['ts_OBs', 'ts_FVGs']}
|
51
|
+
df_OBs = df_OBs.rename(columns={self.TIMESTAMP_COL: timestamp_mapping[self.TIMESTAMP_COL][0]})
|
52
|
+
df_FVGs = df_FVGs.rename(columns={self.TIMESTAMP_COL: timestamp_mapping[self.TIMESTAMP_COL][1]})
|
53
|
+
|
54
|
+
# 使用更高效的方式合并数据框
|
55
|
+
df_PDArrays = pd.concat(
|
56
|
+
[df_OBs, df_FVGs],
|
57
|
+
axis=1,
|
58
|
+
join='outer'
|
59
|
+
).sort_index()
|
60
|
+
|
61
|
+
# 使用更清晰的方式合并时间戳列
|
62
|
+
df_PDArrays[self.TIMESTAMP_COL] = df_PDArrays[timestamp_mapping[self.TIMESTAMP_COL][0]].fillna(
|
63
|
+
df_PDArrays[timestamp_mapping[self.TIMESTAMP_COL][1]]
|
64
|
+
)
|
65
|
+
df_PDArrays[self.PD_TYPE_COL] = df_PDArrays[[self.FVG_SIDE, self.OB_DIRECTION_COL]].apply(
|
66
|
+
lambda x: 'FVG-OB' if pd.notna(x.iloc[0]) and pd.notna(x.iloc[1]) else 'FVG' if pd.notna(x.iloc[0]) else 'OB', axis=1
|
67
|
+
)
|
68
|
+
|
69
|
+
df_PDArrays.loc[:, self.PD_HIGH_COL] = df_PDArrays[[self.FVG_TOP, self.OB_HIGH_COL]].max(axis=1)
|
70
|
+
df_PDArrays.loc[:, self.PD_LOW_COL] = df_PDArrays[[self.FVG_BOT, self.OB_LOW_COL]].min(axis=1)
|
71
|
+
df_PDArrays.loc[:, self.PD_MID_COL] = (df_PDArrays[self.PD_HIGH_COL] + df_PDArrays[self.PD_LOW_COL]) / 2
|
72
|
+
|
73
|
+
|
74
|
+
|
75
|
+
|
76
|
+
return df_PDArrays
|
77
|
+
|
core/smc/SMCStruct.py
ADDED
@@ -0,0 +1,290 @@
|
|
1
|
+
import logging
|
2
|
+
import operator
|
3
|
+
from core.utils.OPTools import OPTools
|
4
|
+
from core.smc.SMCBase import SMCBase
|
5
|
+
|
6
|
+
class SMCStruct(SMCBase):
|
7
|
+
STRUCT_COL = "struct"
|
8
|
+
STRUCT_HIGH_COL = "struct_high"
|
9
|
+
STRUCT_LOW_COL = "struct_low"
|
10
|
+
STRUCT_HIGH_INDEX_COL = "struct_high_index"
|
11
|
+
STRUCT_LOW_INDEX_COL = "struct_low_index"
|
12
|
+
STRUCT_DIRECTION_COL = "struct_direction"
|
13
|
+
HIGH_START_COL = "high_start"
|
14
|
+
LOW_START_COL = "low_start"
|
15
|
+
|
16
|
+
def __init__(self):
|
17
|
+
super().__init__()
|
18
|
+
self.logger = logging.getLogger(__name__)
|
19
|
+
|
20
|
+
|
21
|
+
|
22
|
+
def build_struct(self, data, window=10, is_struct_body_break=True):
|
23
|
+
"""处理价格结构,识别高低点突破和结构方向
|
24
|
+
|
25
|
+
Args:
|
26
|
+
df: 数据框
|
27
|
+
window: 寻找结构极值的窗口大小
|
28
|
+
is_struct_body_break: 是否使用收盘价判断突破
|
29
|
+
|
30
|
+
Returns:
|
31
|
+
处理后的数据框,包含结构相关列
|
32
|
+
"""
|
33
|
+
df = data.copy()
|
34
|
+
check_columns = [self.HIGH_COL, self.LOW_COL, self.CLOSE_COL]
|
35
|
+
self.check_columns(df, check_columns)
|
36
|
+
|
37
|
+
# 初始化结构相关列
|
38
|
+
# 定义结构相关的列名
|
39
|
+
struct_columns = [self.STRUCT_COL, self.STRUCT_HIGH_COL, self.STRUCT_LOW_COL,
|
40
|
+
self.STRUCT_HIGH_INDEX_COL, self.STRUCT_LOW_INDEX_COL, self.STRUCT_DIRECTION_COL]
|
41
|
+
|
42
|
+
# 初始化结构相关列的默认值
|
43
|
+
default_values = {
|
44
|
+
self.STRUCT_COL: None, # 结构类型列初始化为None
|
45
|
+
self.STRUCT_HIGH_COL: self.toDecimal('0.0'), # 结构高点价格初始化为0
|
46
|
+
self.STRUCT_LOW_COL: self.toDecimal('0.0'), # 结构低点价格初始化为0
|
47
|
+
self.STRUCT_HIGH_INDEX_COL: 0, # 结构高点索引初始化为0
|
48
|
+
self.STRUCT_LOW_INDEX_COL: 0, # 结构低点索引初始化为0
|
49
|
+
self.STRUCT_DIRECTION_COL: 0 # 结构方向初始化为0
|
50
|
+
}
|
51
|
+
|
52
|
+
# 为每个结构列赋默认值
|
53
|
+
for col in struct_columns:
|
54
|
+
df[col] = default_values[col]
|
55
|
+
|
56
|
+
# 初始化结构变量
|
57
|
+
structure = {
|
58
|
+
self.HIGH_COL: df[self.HIGH_COL].iloc[0],
|
59
|
+
self.LOW_COL: df[self.LOW_COL].iloc[0],
|
60
|
+
self.HIGH_START_COL: -1,
|
61
|
+
self.LOW_START_COL: -1,
|
62
|
+
'direction': 0
|
63
|
+
}
|
64
|
+
|
65
|
+
# 确定突破判断列
|
66
|
+
break_price_col = self.CLOSE_COL if is_struct_body_break else self.HIGH_COL
|
67
|
+
break_price_col_low = self.CLOSE_COL if is_struct_body_break else self.LOW_COL
|
68
|
+
|
69
|
+
for i in range(1, len(df)):
|
70
|
+
curr_prices = {
|
71
|
+
self.HIGH_COL: df[break_price_col].iloc[i],
|
72
|
+
self.LOW_COL: df[break_price_col_low].iloc[i]
|
73
|
+
}
|
74
|
+
|
75
|
+
# 获取前3根K线价格
|
76
|
+
prev_prices = {
|
77
|
+
self.HIGH_COL: df[break_price_col].iloc[i-3:i].values,
|
78
|
+
self.LOW_COL: df[break_price_col_low].iloc[i-3:i].values
|
79
|
+
}
|
80
|
+
|
81
|
+
# 判断结构突破
|
82
|
+
is_high_broken = self._check_structure_break(
|
83
|
+
curr_price=curr_prices[self.HIGH_COL],
|
84
|
+
struct_price=structure[self.HIGH_COL],
|
85
|
+
prev_prices=prev_prices[self.HIGH_COL],
|
86
|
+
struct_start=structure[self.HIGH_START_COL],
|
87
|
+
i=i,
|
88
|
+
direction=structure['direction'],
|
89
|
+
target_direction=1
|
90
|
+
)
|
91
|
+
|
92
|
+
is_low_broken = self._check_structure_break(
|
93
|
+
curr_price=curr_prices[self.LOW_COL],
|
94
|
+
struct_price=structure[self.LOW_COL],
|
95
|
+
prev_prices=prev_prices[self.LOW_COL],
|
96
|
+
struct_start=structure[self.LOW_START_COL],
|
97
|
+
i=i,
|
98
|
+
direction=structure['direction'],
|
99
|
+
target_direction=2,
|
100
|
+
mode=self.LOW_COL
|
101
|
+
)
|
102
|
+
|
103
|
+
if is_low_broken:
|
104
|
+
# 处理低点突破
|
105
|
+
structure = self._handle_structure_break(
|
106
|
+
df, i, window, structure,
|
107
|
+
break_type=self.LOW_COL,
|
108
|
+
struct_type='BOS' if structure['direction'] == 1 else 'CHOCH'
|
109
|
+
)
|
110
|
+
|
111
|
+
elif is_high_broken:
|
112
|
+
# 处理高点突破
|
113
|
+
structure = self._handle_structure_break(
|
114
|
+
df, i, window, structure,
|
115
|
+
break_type=self.HIGH_COL,
|
116
|
+
struct_type='BOS' if structure['direction'] == 2 else 'CHOCH'
|
117
|
+
)
|
118
|
+
|
119
|
+
else:
|
120
|
+
# 更新当前结构
|
121
|
+
structure = self._update_current_structure(
|
122
|
+
df, i, structure,
|
123
|
+
is_struct_body_break=is_struct_body_break
|
124
|
+
)
|
125
|
+
|
126
|
+
# 更新数据框结构列
|
127
|
+
self._update_structure_columns(df, i, structure)
|
128
|
+
|
129
|
+
return df
|
130
|
+
|
131
|
+
def _get_structure_extreme_bar(self, df, bar_index, struct_index, lookback=10, mode='high'):
|
132
|
+
"""
|
133
|
+
获取结构最高点或最低点
|
134
|
+
:param df: DataFrame数据
|
135
|
+
:param bar_index: 当前K线索引
|
136
|
+
:param lookback: 回溯周期
|
137
|
+
:param mode: self.HIGH_COL寻找最高点,self.LOW_COL寻找最低点
|
138
|
+
:return: 结构极值点的索引
|
139
|
+
"""
|
140
|
+
df = df.copy()
|
141
|
+
# window_start = max(0, bar_index - lookback + 1)
|
142
|
+
window_start = max(0, struct_index)
|
143
|
+
|
144
|
+
window = df.iloc[window_start : bar_index + 1]
|
145
|
+
|
146
|
+
# 获取窗口内的极值点索引
|
147
|
+
if mode == self.HIGH_COL:
|
148
|
+
extremeBar = window[self.HIGH_COL].argmax()
|
149
|
+
price_col = self.HIGH_COL
|
150
|
+
comp_func = lambda x, y: x > y
|
151
|
+
else:
|
152
|
+
extremeBar = window[self.LOW_COL].argmin()
|
153
|
+
price_col = self.LOW_COL
|
154
|
+
comp_func = lambda x, y: x < y
|
155
|
+
|
156
|
+
# 初始化记录点
|
157
|
+
pivot = 0
|
158
|
+
# 从后向前遍历寻找结构极值点
|
159
|
+
# for i in range(lookback - 1, -1, -1):
|
160
|
+
for idx in range(bar_index - 1, struct_index - 1, -1):
|
161
|
+
# 计算当前位置的索引
|
162
|
+
# idx = bar_index - i
|
163
|
+
if idx - 2 < 0 or idx + 1 >= len(df):
|
164
|
+
continue
|
165
|
+
|
166
|
+
price_prev = df[price_col].iloc[idx - 1]
|
167
|
+
price_prev2 = df[price_col].iloc[idx - 2]
|
168
|
+
price_curr = df[price_col].iloc[idx]
|
169
|
+
|
170
|
+
# 记录满足条件的点位
|
171
|
+
if (comp_func(price_prev, price_prev2) and
|
172
|
+
not comp_func(price_curr, price_prev) \
|
173
|
+
# and idx - 1 >= extremeBar
|
174
|
+
):
|
175
|
+
if pivot == 0:
|
176
|
+
pivot = idx - 1
|
177
|
+
continue
|
178
|
+
else:
|
179
|
+
# 比较当前点位与之前记录的极值点的价格,因为在区间内有多个极致点,需要比较
|
180
|
+
if comp_func(df[price_col].iloc[idx-1], df[price_col].iloc[pivot]):
|
181
|
+
pivot = idx - 1
|
182
|
+
if pivot != 0:
|
183
|
+
extremeBar = pivot
|
184
|
+
|
185
|
+
return extremeBar
|
186
|
+
|
187
|
+
def _check_structure_break(self, curr_price, struct_price, prev_prices, struct_start, i, direction, target_direction, mode='high'):
|
188
|
+
"""检查结构是否突破"""
|
189
|
+
comp = operator.gt if mode == self.HIGH_COL else operator.lt
|
190
|
+
reverse_comp = operator.le if mode == self.HIGH_COL else operator.ge
|
191
|
+
|
192
|
+
basic_break = (
|
193
|
+
comp(curr_price, struct_price) and
|
194
|
+
all(reverse_comp(p, struct_price) for p in prev_prices) and
|
195
|
+
all(i-j > struct_start for j in range(1,4))
|
196
|
+
)
|
197
|
+
|
198
|
+
direction_break = direction == target_direction and comp(curr_price, struct_price)
|
199
|
+
|
200
|
+
return basic_break or direction_break
|
201
|
+
|
202
|
+
def _handle_structure_break(self, df, i, window, structure, break_type, struct_type):
|
203
|
+
"""处理结构突破"""
|
204
|
+
is_high_break = break_type == self.HIGH_COL
|
205
|
+
|
206
|
+
struct_start = structure[self.HIGH_START_COL] if is_high_break else structure[self.LOW_START_COL]
|
207
|
+
|
208
|
+
# 获取新的极值点
|
209
|
+
extreme_idx = self._get_structure_extreme_bar(
|
210
|
+
df, i, struct_start, window,
|
211
|
+
mode=self.LOW_COL if is_high_break else self.HIGH_COL
|
212
|
+
)
|
213
|
+
|
214
|
+
# 更新结构信息
|
215
|
+
new_structure = structure.copy()
|
216
|
+
new_structure['direction'] = 2 if is_high_break else 1
|
217
|
+
|
218
|
+
if is_high_break:
|
219
|
+
new_structure.update({
|
220
|
+
self.LOW_COL: self.toDecimal(df[self.LOW_COL].iloc[extreme_idx]),
|
221
|
+
self.HIGH_COL: self.toDecimal(df[self.HIGH_COL].iloc[i]),
|
222
|
+
self.LOW_START_COL: extreme_idx,
|
223
|
+
self.HIGH_START_COL: i
|
224
|
+
})
|
225
|
+
else:
|
226
|
+
new_structure.update({
|
227
|
+
self.HIGH_COL: self.toDecimal(df[self.HIGH_COL].iloc[extreme_idx]),
|
228
|
+
self.LOW_COL: self.toDecimal(df[self.LOW_COL].iloc[i]),
|
229
|
+
self.HIGH_START_COL: extreme_idx,
|
230
|
+
self.LOW_START_COL: i
|
231
|
+
})
|
232
|
+
|
233
|
+
# 更新DataFrame结构信息
|
234
|
+
df.at[i, 'struct_direction'] = new_structure['direction']
|
235
|
+
df.at[i, 'struct'] = f"{'Bullish' if is_high_break else 'Bearish'}_{struct_type}"
|
236
|
+
|
237
|
+
return new_structure
|
238
|
+
|
239
|
+
def _update_current_structure(self, df, i, structure, is_struct_body_break):
|
240
|
+
"""更新当前结构"""
|
241
|
+
new_structure = structure.copy()
|
242
|
+
|
243
|
+
# 检查是否需要更新高点
|
244
|
+
if (structure['direction'] in (2,0)) and df.at[i,self.HIGH_COL] > structure[self.HIGH_COL]:
|
245
|
+
if not (is_struct_body_break and all(i-j > structure[self.HIGH_START_COL] for j in range(1,4))):
|
246
|
+
new_structure[self.HIGH_COL] = df.at[i,self.HIGH_COL]
|
247
|
+
new_structure[self.HIGH_START_COL] = i
|
248
|
+
|
249
|
+
# 检查是否需要更新低点
|
250
|
+
elif (structure['direction'] in (1,0)) and df.at[i,self.LOW_COL] < structure[self.LOW_COL]:
|
251
|
+
if not (is_struct_body_break and all(i-j > structure[self.LOW_START_COL] for j in range(1,4))):
|
252
|
+
new_structure[self.LOW_COL] = df.at[i,self.LOW_COL]
|
253
|
+
new_structure[self.LOW_START_COL] = i
|
254
|
+
|
255
|
+
return new_structure
|
256
|
+
|
257
|
+
def _update_structure_columns(self, df, i, structure):
|
258
|
+
"""更新数据框中的结构列"""
|
259
|
+
df.at[i, self.STRUCT_HIGH_COL] = self.toDecimal(structure[self.HIGH_COL])
|
260
|
+
df.at[i, self.STRUCT_LOW_COL] = self.toDecimal(structure[self.LOW_COL] )
|
261
|
+
df.at[i, self.STRUCT_HIGH_INDEX_COL] = structure[self.HIGH_START_COL]
|
262
|
+
df.at[i, self.STRUCT_LOW_INDEX_COL] = structure[self.LOW_START_COL]
|
263
|
+
|
264
|
+
def get_last_struct(self, df, prd=-1):
|
265
|
+
"""
|
266
|
+
获取最新的结构
|
267
|
+
"""
|
268
|
+
data = self.build_struct(df=df, window=10)
|
269
|
+
# 筛选出有效的结构
|
270
|
+
|
271
|
+
# 获取prd范围内的数据
|
272
|
+
start_idx = max(0, len(data) - 1 - prd)
|
273
|
+
# 筛选有效结构且在prd范围内的数据
|
274
|
+
last_struct = None
|
275
|
+
mask = data[self.STRUCT_COL].notna() & (data.index >= start_idx) if prd > 0 else data[self.STRUCT_COL].notna()
|
276
|
+
valid_structs = data[ mask ]
|
277
|
+
if not valid_structs.empty:
|
278
|
+
# 获取最近的结构
|
279
|
+
last_struct = valid_structs.iloc[-1]
|
280
|
+
return {
|
281
|
+
self.STRUCT_COL: last_struct[self.STRUCT_COL],
|
282
|
+
self.STRUCT_HIGH_COL: last_struct[self.STRUCT_HIGH_COL],
|
283
|
+
self.STRUCT_LOW_COL: last_struct[self.STRUCT_LOW_COL],
|
284
|
+
self.STRUCT_HIGH_INDEX_COL: last_struct[self.STRUCT_HIGH_INDEX_COL],
|
285
|
+
self.STRUCT_LOW_INDEX_COL: last_struct[self.STRUCT_LOW_INDEX_COL],
|
286
|
+
self.STRUCT_DIRECTION_COL: last_struct[self.STRUCT_DIRECTION_COL]
|
287
|
+
}
|
288
|
+
|
289
|
+
return last_struct
|
290
|
+
|
core/smc/__init__.py
ADDED
File without changes
|
core/utils/OPTools.py
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
import requests
|
2
|
+
from decimal import Decimal
|
3
|
+
|
4
|
+
class OPTools:
|
5
|
+
|
6
|
+
@staticmethod
|
7
|
+
def toDecimal(value, precision:int=None):
|
8
|
+
"""将数值转换为Decimal类型
|
9
|
+
|
10
|
+
Args:
|
11
|
+
value: 需要转换的数值
|
12
|
+
precision: 精度,如果不指定则保持原始精度
|
13
|
+
|
14
|
+
Returns:
|
15
|
+
Decimal: 转换后的Decimal对象
|
16
|
+
"""
|
17
|
+
if precision is None:
|
18
|
+
return Decimal(str(value))
|
19
|
+
return Decimal(f"{value:.{precision}f}")
|
20
|
+
|
21
|
+
@staticmethod
|
22
|
+
def send_feishu_notification(webhook, message):
|
23
|
+
if webhook:
|
24
|
+
headers = {'Content-Type': 'application/json'}
|
25
|
+
data = {"msg_type": "text", "content": {"text": message}}
|
26
|
+
response = requests.post(webhook, headers=headers, json=data)
|
27
|
+
if response.status_code != 200:
|
28
|
+
# self.logger.debug("飞书通知发送成功")
|
29
|
+
raise Exception(f"飞书通知发送失败: {response.text} {webhook}")
|
30
|
+
|