openfund-core 1.0.5__py3-none-any.whl → 1.0.8__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 +80 -21
- core/smc/SMCFVG.py +4 -4
- core/smc/SMCLiquidity.py +202 -3
- core/smc/SMCOrderBlock.py +4 -4
- core/smc/SMCPDArray.py +42 -8
- core/smc/SMCStruct.py +8 -5
- {openfund_core-1.0.5.dist-info → openfund_core-1.0.8.dist-info}/METADATA +1 -1
- openfund_core-1.0.8.dist-info/RECORD +15 -0
- {openfund_core-1.0.5.dist-info → openfund_core-1.0.8.dist-info}/WHEEL +1 -1
- openfund_core-1.0.5.dist-info/RECORD +0 -15
- {openfund_core-1.0.5.dist-info → openfund_core-1.0.8.dist-info}/entry_points.txt +0 -0
core/Exchange.py
CHANGED
@@ -31,19 +31,8 @@ class Exchange:
|
|
31
31
|
self.exchange.load_markets()
|
32
32
|
|
33
33
|
return self.exchange.market(symbol)
|
34
|
-
|
35
|
-
def
|
36
|
-
|
37
|
-
market = self.getMarket(symbol)
|
38
|
-
if market and 'precision' in market and 'price' in market['precision']:
|
39
|
-
return OPTools.toDecimal(market['precision']['price'])
|
40
|
-
else:
|
41
|
-
raise ValueError(f"{symbol}: 无法从市场数据中获取价格精度")
|
42
|
-
|
43
|
-
def amount_to_precision(self,symbol, contract_size):
|
44
|
-
return self.exchange.amount_to_precision(symbol, contract_size)
|
45
|
-
|
46
|
-
def get_position_mode(self):
|
34
|
+
|
35
|
+
def get_position_mode(self) -> str:
|
47
36
|
|
48
37
|
try:
|
49
38
|
# 假设获取账户持仓模式的 API
|
@@ -61,6 +50,19 @@ class Exchange:
|
|
61
50
|
error_message = f"Error fetching position mode: {e}"
|
62
51
|
self.logger.error(error_message)
|
63
52
|
raise Exception(error_message)
|
53
|
+
|
54
|
+
def get_tick_size(self,symbol) -> Decimal:
|
55
|
+
|
56
|
+
market = self.getMarket(symbol)
|
57
|
+
if market and 'precision' in market and 'price' in market['precision']:
|
58
|
+
return OPTools.toDecimal(market['precision']['price'])
|
59
|
+
else:
|
60
|
+
raise ValueError(f"{symbol}: 无法从市场数据中获取价格精度")
|
61
|
+
|
62
|
+
def amount_to_precision(self,symbol, contract_size):
|
63
|
+
return self.exchange.amount_to_precision(symbol, contract_size)
|
64
|
+
|
65
|
+
|
64
66
|
|
65
67
|
def set_leverage(self,symbol, leverage, mgnMode='isolated',posSide=None):
|
66
68
|
try:
|
@@ -105,10 +107,66 @@ class Exchange:
|
|
105
107
|
elif direction == 'cost_to_contract':
|
106
108
|
contract_size = amount / price / market_contractSize
|
107
109
|
else:
|
108
|
-
raise Exception(f"{symbol}:{direction} 是无效的转换方向,请输入 'amount_to_contract' 或 'cost_to_contract'。")
|
110
|
+
raise Exception(f"{symbol} : {direction} 是无效的转换方向,请输入 'amount_to_contract' 或 'cost_to_contract'。")
|
109
111
|
|
110
112
|
return self.amount_to_precision(symbol, contract_size)
|
111
|
-
|
113
|
+
|
114
|
+
def close_position(self, symbol, position, params={}) -> dict:
|
115
|
+
|
116
|
+
amount = abs(float(position['contracts']))
|
117
|
+
|
118
|
+
if amount <= 0:
|
119
|
+
self.logger.warning(f"{symbol}: position contracts must be greater than 0")
|
120
|
+
return
|
121
|
+
|
122
|
+
max_retries = 3
|
123
|
+
retry_count = 0
|
124
|
+
while retry_count < max_retries:
|
125
|
+
|
126
|
+
try:
|
127
|
+
side = position[self.SIDE_KEY]
|
128
|
+
self.logger.debug(f"{symbol}: Preparing to close position, side= {side}, amount={amount}")
|
129
|
+
position_mode = self.get_position_mode() # 获取持仓模式
|
130
|
+
if position_mode == 'long_short_mode':
|
131
|
+
# 在双向持仓模式下,指定平仓方向
|
132
|
+
# pos_side = 'long' if side == 'long' else 'short'
|
133
|
+
pos_side = side
|
134
|
+
else:
|
135
|
+
# 在单向模式下,不指定方向
|
136
|
+
pos_side = 'net'
|
137
|
+
orderSide = 'buy' if side == 'long' else 'sell'
|
138
|
+
|
139
|
+
td_mode = position['marginMode']
|
140
|
+
params = {
|
141
|
+
'mgnMode': td_mode,
|
142
|
+
'posSide': pos_side,
|
143
|
+
# 当市价全平时,平仓单是否需要自动撤销,默认为false. false:不自动撤单 true:自动撤单
|
144
|
+
'autoCxl': 'true',
|
145
|
+
**params
|
146
|
+
|
147
|
+
}
|
148
|
+
|
149
|
+
# 发送平仓请求并获取返回值
|
150
|
+
order = self.exchange.close_position(
|
151
|
+
symbol=symbol,
|
152
|
+
side=orderSide,
|
153
|
+
params=params
|
154
|
+
)
|
155
|
+
|
156
|
+
self.logger.info(f"{symbol} Close position response : {order}")
|
157
|
+
return order
|
158
|
+
|
159
|
+
except Exception as e:
|
160
|
+
|
161
|
+
retry_count += 1
|
162
|
+
if retry_count == max_retries:
|
163
|
+
error_message = f"{symbol} Error closing position : {str(e)}"
|
164
|
+
self.logger.error(error_message)
|
165
|
+
raise Exception(error_message)
|
166
|
+
else:
|
167
|
+
self.logger.warning(f"{symbol} 平仓失败,正在进行第{retry_count}次重试: {str(e)}")
|
168
|
+
time.sleep(0.1) # 重试前等待0.1秒
|
169
|
+
|
112
170
|
|
113
171
|
def cancel_all_orders(self, symbol):
|
114
172
|
max_retries = 3
|
@@ -262,7 +320,7 @@ class Exchange:
|
|
262
320
|
|
263
321
|
max_retries = 3
|
264
322
|
retry_count = 0
|
265
|
-
self.logger.
|
323
|
+
self.logger.info(f"{symbol} : Pre Algo Order placed: {order} ")
|
266
324
|
while retry_count < max_retries:
|
267
325
|
try:
|
268
326
|
|
@@ -355,10 +413,11 @@ class Exchange:
|
|
355
413
|
max_retries = 3
|
356
414
|
retry_count = 0
|
357
415
|
|
416
|
+
self.logger.info(f"{symbol} : Pre Order placed: {order} ")
|
417
|
+
|
358
418
|
while retry_count < max_retries:
|
359
419
|
try:
|
360
420
|
# 使用ccxt创建订单
|
361
|
-
self.logger.debug(f"{symbol} : Pre Order placed: {order} ")
|
362
421
|
order_result = self.exchange.create_order(
|
363
422
|
**order
|
364
423
|
# symbol=symbol,
|
@@ -371,24 +430,24 @@ class Exchange:
|
|
371
430
|
except ccxt.NetworkError as e:
|
372
431
|
# 处理网络相关错误
|
373
432
|
retry_count += 1
|
374
|
-
self.logger.warning(f"{symbol} :
|
433
|
+
self.logger.warning(f"{symbol} : 下单时发生网络错误,正在进行第{retry_count}次重试: {str(e)}")
|
375
434
|
time.sleep(0.1) # 重试前等待1秒
|
376
435
|
continue
|
377
436
|
except ccxt.ExchangeError as e:
|
378
437
|
# 处理交易所API相关错误
|
379
438
|
retry_count += 1
|
380
|
-
self.logger.warning(f"{symbol} :
|
439
|
+
self.logger.warning(f"{symbol} : 下单时发生交易所错误,正在进行第{retry_count}次重试: {str(e)}")
|
381
440
|
time.sleep(0.1)
|
382
441
|
continue
|
383
442
|
except Exception as e:
|
384
443
|
# 处理其他未预期的错误
|
385
444
|
retry_count += 1
|
386
|
-
self.logger.warning(f"{symbol} :
|
445
|
+
self.logger.warning(f"{symbol} : 下单时发生未知错误,正在进行第{retry_count}次重试: {str(e)}")
|
387
446
|
time.sleep(0.1)
|
388
447
|
continue
|
389
448
|
if retry_count >= max_retries:
|
390
449
|
# 重试次数用完仍未成功设置止损单
|
391
|
-
error_message = f"!! {symbol}:
|
450
|
+
error_message = f"!! {symbol}: 下单时重试次数用完仍未成功设置成功。 "
|
392
451
|
self.logger.error(error_message)
|
393
452
|
raise Exception(error_message)
|
394
453
|
self.logger.debug(f"{symbol} : --------- ++ Order placed done. --------")
|
core/smc/SMCFVG.py
CHANGED
@@ -47,12 +47,12 @@ class SMCFVG(SMCStruct):
|
|
47
47
|
# 使用向量化操作替代apply,提高性能
|
48
48
|
if side == self.BUY_SIDE:
|
49
49
|
condition = df[self.HIGH_COL].shift(1) < df[self.LOW_COL].shift(-1)
|
50
|
-
side_value =
|
50
|
+
side_value = self.BULLISH_TREND
|
51
51
|
price_top = df[self.LOW_COL].shift(-1)
|
52
52
|
price_bot = df[self.HIGH_COL].shift(1)
|
53
53
|
else:
|
54
54
|
condition = df[self.LOW_COL].shift(1) > df[self.HIGH_COL].shift(-1)
|
55
|
-
side_value =
|
55
|
+
side_value = self.BEARISH_TREND
|
56
56
|
price_top = df[self.LOW_COL].shift(1)
|
57
57
|
price_bot = df[self.HIGH_COL].shift(-1)
|
58
58
|
|
@@ -64,9 +64,9 @@ class SMCFVG(SMCStruct):
|
|
64
64
|
df.loc[:, self.FVG_MID] = (df[self.FVG_TOP] + df[self.FVG_BOT]) / 2
|
65
65
|
|
66
66
|
fvg_df = df[
|
67
|
-
df[self.FVG_SIDE] ==
|
67
|
+
df[self.FVG_SIDE] == self.BULLISH_TREND
|
68
68
|
if side == self.BUY_SIDE
|
69
|
-
else df[self.FVG_SIDE] ==
|
69
|
+
else df[self.FVG_SIDE] == self.BEARISH_TREND
|
70
70
|
]
|
71
71
|
fvg_df = fvg_df.copy()
|
72
72
|
if check_balanced:
|
core/smc/SMCLiquidity.py
CHANGED
@@ -1,7 +1,206 @@
|
|
1
1
|
import logging
|
2
|
+
from core.smc.SMCStruct import SMCStruct
|
3
|
+
|
4
|
+
|
5
|
+
class SMCLiquidity(SMCStruct):
|
6
|
+
EQUAL_HIGH_COL = "equal_high"
|
7
|
+
EQUAL_LOW_COL = "equal_low"
|
8
|
+
LIQU_HIGH_COL = "liqu_high"
|
9
|
+
LIQU_LOW_COL = "liqu_low"
|
10
|
+
EQUAL_HIGH_INDEX_KEY = "equal_high_index"
|
11
|
+
EQUAL_LOW_INDEX_KEY = "equal_low_index"
|
12
|
+
HAS_EQ_KEY = "has_EQ"
|
13
|
+
LIQU_HIGH_DIFF_COL = "liqu_high_diff"
|
14
|
+
LIQU_LOW_DIFF_COL = "liqu_low_diff"
|
15
|
+
|
2
16
|
|
3
|
-
class SMCLiquidity:
|
4
17
|
def __init__(self):
|
18
|
+
super().__init__()
|
5
19
|
self.logger = logging.getLogger(__name__)
|
6
|
-
|
7
|
-
|
20
|
+
|
21
|
+
|
22
|
+
def _identify_liquidity_pivots(self, data, pivot_length=1):
|
23
|
+
"""
|
24
|
+
识别流动性的高点和低点
|
25
|
+
"""
|
26
|
+
|
27
|
+
df = data.copy()
|
28
|
+
|
29
|
+
# 识别高点
|
30
|
+
df[self.LIQU_HIGH_COL] = 0
|
31
|
+
for i in range(pivot_length, len(df) - pivot_length):
|
32
|
+
if df[self.HIGH_COL].iloc[i] == max(df[self.HIGH_COL].iloc[i-pivot_length:i+pivot_length+1]):
|
33
|
+
df.loc[df.index[i], self.LIQU_HIGH_COL] = df[self.HIGH_COL].iloc[i]
|
34
|
+
|
35
|
+
# 识别低点
|
36
|
+
df[self.LIQU_LOW_COL] = 0
|
37
|
+
for i in range(pivot_length, len(df) - pivot_length):
|
38
|
+
|
39
|
+
if df[self.LOW_COL].iloc[i] == min(df[self.LOW_COL].iloc[i-pivot_length:i+pivot_length+1]):
|
40
|
+
df.loc[df.index[i], self.LIQU_LOW_COL] = df[self.LOW_COL].iloc[i]
|
41
|
+
|
42
|
+
|
43
|
+
|
44
|
+
return df
|
45
|
+
|
46
|
+
def find_EQH_EQL(self, data, trend, end_idx=-1, atr_offset=0.1) -> dict:
|
47
|
+
"""_summary_
|
48
|
+
识别等高等低流动性
|
49
|
+
Args:
|
50
|
+
data (_type_): _description_
|
51
|
+
trend (_type_): _description_
|
52
|
+
end_idx (int, optional): _description_. Defaults to -1.
|
53
|
+
atr_offset (float, optional): _description_. Defaults to 0.1.
|
54
|
+
|
55
|
+
Returns:
|
56
|
+
dict: _description_
|
57
|
+
"""
|
58
|
+
|
59
|
+
df = data.copy() if end_idx == -1 else data.copy().iloc[:end_idx+1]
|
60
|
+
|
61
|
+
check_columns = [self.LIQU_HIGH_COL, self.LIQU_LOW_COL]
|
62
|
+
|
63
|
+
try:
|
64
|
+
self.check_columns(df, check_columns)
|
65
|
+
except ValueError as e:
|
66
|
+
self.logger.warning(f"DataFrame must contain columns {check_columns} : {str(e)}")
|
67
|
+
df = self._identify_liquidity_pivots(df)
|
68
|
+
|
69
|
+
df = df[(df[self.LIQU_HIGH_COL] > 0) | (df[self.LIQU_LOW_COL] > 0)]
|
70
|
+
# 初始化结果列
|
71
|
+
df[self.EQUAL_HIGH_COL] = 0
|
72
|
+
df[self.EQUAL_LOW_COL] = 0
|
73
|
+
df[self.ATR_COL] = self.calculate_atr(df)
|
74
|
+
# 跟踪前一个高点和低点
|
75
|
+
previous_high = None
|
76
|
+
previous_high_index = None
|
77
|
+
previous_high_pos = -1
|
78
|
+
previous_low = None
|
79
|
+
previous_low_index = None
|
80
|
+
previous_low_pos = -1
|
81
|
+
for i in range(len(df)-1, -1, -1):
|
82
|
+
|
83
|
+
offset = self.toDecimal(df[self.ATR_COL].iloc[i] * atr_offset)
|
84
|
+
|
85
|
+
if trend == self.BULLISH_TREND:
|
86
|
+
current_high = df[self.LIQU_HIGH_COL].iloc[i]
|
87
|
+
if current_high == 0:
|
88
|
+
continue
|
89
|
+
|
90
|
+
if previous_high is None:
|
91
|
+
previous_high = current_high
|
92
|
+
previous_high_index = df.index[i]
|
93
|
+
previous_high_pos = i
|
94
|
+
continue
|
95
|
+
|
96
|
+
max_val = max(current_high, previous_high)
|
97
|
+
min_val = min(current_high, previous_high)
|
98
|
+
|
99
|
+
|
100
|
+
if abs(max_val - min_val) <= offset: # EQH|EQL
|
101
|
+
|
102
|
+
df.loc[df.index[i], self.EQUAL_HIGH_COL] = previous_high_index
|
103
|
+
df.loc[df.index[previous_high_pos], self.EQUAL_HIGH_COL] = previous_high_index
|
104
|
+
|
105
|
+
else:
|
106
|
+
# 倒序遍历,等高线被高点破坏,则更新等高点位置
|
107
|
+
if current_high > previous_high:
|
108
|
+
previous_high = current_high
|
109
|
+
previous_high_index = df.index[i]
|
110
|
+
previous_high_pos = i
|
111
|
+
|
112
|
+
|
113
|
+
|
114
|
+
else:
|
115
|
+
current_low = df[self.LIQU_LOW_COL].iloc[i]
|
116
|
+
if current_low == 0:
|
117
|
+
continue
|
118
|
+
|
119
|
+
# current_low = df[self.EQUAL_LOW_COL].iloc[i]
|
120
|
+
if previous_low is None:
|
121
|
+
previous_low = current_low
|
122
|
+
previous_low_index = df.index[i]
|
123
|
+
previous_low_pos = i
|
124
|
+
continue
|
125
|
+
|
126
|
+
max_val = max(current_low, previous_low)
|
127
|
+
min_val = min(current_low, previous_low)
|
128
|
+
|
129
|
+
|
130
|
+
|
131
|
+
|
132
|
+
if abs(max_val - min_val) <= offset: # EQH|EQL
|
133
|
+
|
134
|
+
df.loc[df.index[i], self.EQUAL_LOW_COL] = previous_low_index
|
135
|
+
df.loc[df.index[previous_low_pos], self.EQUAL_LOW_COL] = previous_low_index
|
136
|
+
|
137
|
+
else:
|
138
|
+
# 倒序遍历,等高线被高点破坏,则更新等高点位置
|
139
|
+
if current_low < previous_low:
|
140
|
+
previous_low = current_low
|
141
|
+
previous_low_index = df.index[i]
|
142
|
+
previous_low_pos = i
|
143
|
+
|
144
|
+
# 筛选有效结构且在prd范围内的数据
|
145
|
+
last_EQ = {
|
146
|
+
|
147
|
+
}
|
148
|
+
if trend == self.BULLISH_TREND :
|
149
|
+
mask = df[self.EQUAL_HIGH_COL] > 0
|
150
|
+
valid_EQH_df = df[ mask ]
|
151
|
+
if not valid_EQH_df.empty:
|
152
|
+
last_EQ[self.HAS_EQ_KEY] = True
|
153
|
+
last_EQ[self.EQUAL_HIGH_COL] = valid_EQH_df.iloc[-1][self.LIQU_HIGH_COL]
|
154
|
+
last_EQ[self.EQUAL_HIGH_INDEX_KEY] = valid_EQH_df.iloc[-1][self.EQUAL_HIGH_COL]
|
155
|
+
else:
|
156
|
+
mask = df[self.EQUAL_LOW_COL] > 0
|
157
|
+
valid_EQL_df = df[ mask ]
|
158
|
+
if not valid_EQL_df.empty:
|
159
|
+
last_EQ[self.HAS_EQ_KEY] = True
|
160
|
+
last_EQ[self.EQUAL_LOW_COL] = valid_EQL_df.iloc[-1][self.LIQU_LOW_COL]
|
161
|
+
last_EQ[self.EQUAL_LOW_INDEX_KEY] = valid_EQL_df.iloc[-1][self.EQUAL_LOW_COL]
|
162
|
+
|
163
|
+
return last_EQ
|
164
|
+
|
165
|
+
def identify_dynamic_trendlines(self, data, trend, start_idx=-1, end_idx=-1, ratio=0.8) -> tuple:
|
166
|
+
"""
|
167
|
+
识别动态趋势线或隧道
|
168
|
+
Args:
|
169
|
+
data (pd.DataFrame): _description_
|
170
|
+
|
171
|
+
Returns:
|
172
|
+
pd.DataFrame: _description_
|
173
|
+
"""
|
174
|
+
|
175
|
+
df = data.copy() if start_idx == -1 or end_idx == -1 else data.copy().iloc[start_idx-1:end_idx+2] #考虑poivt值,前后各增加一个
|
176
|
+
|
177
|
+
check_columns = [self.LIQU_HIGH_COL]
|
178
|
+
|
179
|
+
try:
|
180
|
+
self.check_columns(df, check_columns)
|
181
|
+
except ValueError as e:
|
182
|
+
self.logger.warning(f"DataFrame must contain columns {check_columns} : {str(e)}")
|
183
|
+
df = self._identify_liquidity_pivots(df)
|
184
|
+
diff_ratio = 0.0
|
185
|
+
if trend == self.BEARISH_TREND:
|
186
|
+
# 判断Bearish趋势是高点不断升高,
|
187
|
+
liqu_bear_df = df[df[self.LIQU_HIGH_COL] > 0]
|
188
|
+
liqu_bear_df[self.LIQU_HIGH_DIFF_COL] = liqu_bear_df[self.LIQU_HIGH_COL].diff()
|
189
|
+
# self.logger.info(f"dynamic_trendlines:\n {liqu_bear_df[[self.TIMESTAMP_COL,self.LIQU_HIGH_COL,self.LIQU_HIGH_DIFF_COL]]}")
|
190
|
+
diff_ratio = self.toDecimal(liqu_bear_df[self.LIQU_HIGH_DIFF_COL].dropna().lt(0).mean(),2)
|
191
|
+
if diff_ratio >= ratio:
|
192
|
+
return diff_ratio,True
|
193
|
+
else:
|
194
|
+
# Bullish趋势是低点不断降低
|
195
|
+
liqu_bullish_df = df[df[self.LIQU_LOW_COL] > 0]
|
196
|
+
liqu_bullish_df[self.LIQU_LOW_DIFF_COL] = liqu_bullish_df[self.LIQU_LOW_COL].diff()
|
197
|
+
# self.logger.info(f"dynamic_trendlines:\n {liqu_bullish_df[[self.TIMESTAMP_COL,self.LIQU_LOW_COL,self.LIQU_LOW_DIFF_COL]]}")
|
198
|
+
diff_ratio = self.toDecimal(liqu_bullish_df[self.LIQU_LOW_DIFF_COL].dropna().gt(0).mean(),2)
|
199
|
+
if diff_ratio >= ratio:
|
200
|
+
return diff_ratio,True
|
201
|
+
|
202
|
+
return diff_ratio,False
|
203
|
+
|
204
|
+
|
205
|
+
|
206
|
+
|
core/smc/SMCOrderBlock.py
CHANGED
@@ -34,7 +34,7 @@ class SMCOrderBlock(SMCStruct):
|
|
34
34
|
symbol (_type_): _description_
|
35
35
|
data (pd.DataFrame): _description_
|
36
36
|
side (_type_): _description_ 如果是None, 则返回所有OB boxes(包括bullish和bearish)
|
37
|
-
|
37
|
+
start_index (int): _description_ 开始的位置
|
38
38
|
is_valid (bool): _description_ 找到有效的OB,没有被crossed
|
39
39
|
if_combine (bool): _description_ 是否合并OB
|
40
40
|
Returns:
|
@@ -63,8 +63,8 @@ class SMCOrderBlock(SMCStruct):
|
|
63
63
|
else any(df.loc[row.name + 1 :, self.HIGH_COL] >= row[self.OB_HIGH_COL]),
|
64
64
|
axis=1,
|
65
65
|
)
|
66
|
-
|
67
|
-
|
66
|
+
if is_valid :
|
67
|
+
ob_df = ob_df[~ob_df[self.OB_WAS_CROSSED]]
|
68
68
|
|
69
69
|
if if_combine:
|
70
70
|
# 合并OB
|
@@ -238,7 +238,7 @@ class SMCOrderBlock(SMCStruct):
|
|
238
238
|
# df.at[i, self.OB_START_TS_COL] = df.loc[index, self.TIMESTAMP_COL]
|
239
239
|
df.at[index, self.OB_ATR] = atr
|
240
240
|
|
241
|
-
def
|
241
|
+
def get_latest_OB(self, data, trend, start_index=-1):
|
242
242
|
"""
|
243
243
|
获取最新的Order Block
|
244
244
|
|
core/smc/SMCPDArray.py
CHANGED
@@ -15,16 +15,15 @@ class SMCPDArray(SMCFVG,SMCOrderBlock):
|
|
15
15
|
self.logger = logging.getLogger(__name__)
|
16
16
|
|
17
17
|
def find_PDArrays(
|
18
|
-
self, struct: pd.DataFrame, side, start_index=-1
|
18
|
+
self, struct: pd.DataFrame, side, start_index=-1, check_balanced=True,
|
19
19
|
) -> pd.DataFrame:
|
20
20
|
"""_summary_
|
21
21
|
寻找PDArrays,包括Fair Value Gap (FVG)|Order Block (OB)|Breaker Block(BB)|Mitigation Block(BB)
|
22
22
|
Args:
|
23
23
|
data (pd.DataFrame): K线数据
|
24
24
|
side (_type_): 交易方向 'buy'|'sell'
|
25
|
-
threshold (_type_): 阈值价格,通常为溢价和折价区的CE
|
26
|
-
check_balanced (bool): 是否检查FVG是否被平衡过,默认为True
|
27
25
|
start_index (int): 开始查找索引的起点,默认为-1
|
26
|
+
check_balanced (bool): PD是否有效,默认为True。PD被crossed过,则是无效PD
|
28
27
|
|
29
28
|
Returns:
|
30
29
|
pd.DataFrame: _description_
|
@@ -37,11 +36,11 @@ class SMCPDArray(SMCFVG,SMCOrderBlock):
|
|
37
36
|
else struct.copy().iloc[max(0, start_index - 1) :]
|
38
37
|
)
|
39
38
|
|
40
|
-
df_FVGs = self.find_FVGs(df, side)
|
39
|
+
df_FVGs = self.find_FVGs(df, side, check_balanced, start_index)
|
41
40
|
# self.logger.info(f"fvgs:\n{df_FVGs[['timestamp', self.FVG_SIDE, self.FVG_TOP, self.FVG_BOT, self.FVG_WAS_BALANCED]]}")
|
42
41
|
|
43
42
|
|
44
|
-
df_OBs = self.find_OBs(df, side)
|
43
|
+
df_OBs = self.find_OBs(df, side, start_index, is_valid=check_balanced)
|
45
44
|
# self.logger.info("find_OBs:\n %s", df_OBs)
|
46
45
|
|
47
46
|
# 使用更简洁的方式重命名和合并时间戳列
|
@@ -67,9 +66,44 @@ class SMCPDArray(SMCFVG,SMCOrderBlock):
|
|
67
66
|
df_PDArrays.loc[:, self.PD_HIGH_COL] = df_PDArrays[[self.FVG_TOP, self.OB_HIGH_COL]].max(axis=1)
|
68
67
|
df_PDArrays.loc[:, self.PD_LOW_COL] = df_PDArrays[[self.FVG_BOT, self.OB_LOW_COL]].min(axis=1)
|
69
68
|
df_PDArrays.loc[:, self.PD_MID_COL] = (df_PDArrays[self.PD_HIGH_COL] + df_PDArrays[self.PD_LOW_COL]) / 2
|
70
|
-
|
71
|
-
|
72
|
-
|
69
|
+
|
73
70
|
|
74
71
|
return df_PDArrays
|
75
72
|
|
73
|
+
|
74
|
+
def get_latest_PDArray(self, df_PDArrays: pd.DataFrame, side, start_index=-1, check_balanced=True, mask:str=None) -> dict:
|
75
|
+
"""_summary_
|
76
|
+
过滤PDArrays,只保留指定方向的PDArrays
|
77
|
+
Args:
|
78
|
+
df_PDArrays (pd.DataFrame): _description_
|
79
|
+
mask (str): _description_
|
80
|
+
|
81
|
+
Returns:
|
82
|
+
pd.DataFrame: _description_
|
83
|
+
"""
|
84
|
+
|
85
|
+
# 检查数据中是否包含必要的列
|
86
|
+
df = df_PDArrays.copy()
|
87
|
+
check_columns = [self.STRUCT_COL]
|
88
|
+
try:
|
89
|
+
self.check_columns(df, check_columns)
|
90
|
+
except ValueError as e:
|
91
|
+
df = self.build_struct(df)
|
92
|
+
|
93
|
+
df = self.find_PDArrays(df, side, start_index, check_balanced)
|
94
|
+
|
95
|
+
if mask:
|
96
|
+
df = df[df[mask]]
|
97
|
+
|
98
|
+
if len(df) == 0:
|
99
|
+
self.logger.info("未找到PDArray.")
|
100
|
+
return None
|
101
|
+
else:
|
102
|
+
last_pd = df.iloc[-1]
|
103
|
+
return {
|
104
|
+
self.TIMESTAMP_COL: last_pd[self.TIMESTAMP_COL],
|
105
|
+
self.PD_TYPE_COL: last_pd[self.PD_TYPE_COL],
|
106
|
+
self.PD_HIGH_COL: last_pd[self.PD_HIGH_COL],
|
107
|
+
self.PD_LOW_COL: last_pd[self.PD_LOW_COL],
|
108
|
+
self.PD_MID_COL: last_pd[self.PD_MID_COL],
|
109
|
+
}
|
core/smc/SMCStruct.py
CHANGED
@@ -265,15 +265,18 @@ class SMCStruct(SMCBase):
|
|
265
265
|
df.at[i, self.STRUCT_HIGH_INDEX_COL] = structure[self.HIGH_START_COL]
|
266
266
|
df.at[i, self.STRUCT_LOW_INDEX_COL] = structure[self.LOW_START_COL]
|
267
267
|
|
268
|
-
def
|
268
|
+
def get_latest_struct(self, df):
|
269
269
|
"""
|
270
270
|
获取最新的结构
|
271
271
|
"""
|
272
272
|
check_columns = [self.STRUCT_COL]
|
273
|
-
if not self.check_columns(df, check_columns):
|
274
|
-
|
275
|
-
|
276
|
-
|
273
|
+
# if not self.check_columns(df, check_columns):
|
274
|
+
try :
|
275
|
+
self.check_columns(df, check_columns)
|
276
|
+
except ValueError as e:
|
277
|
+
df = self.build_struct(df)
|
278
|
+
|
279
|
+
data = df.copy()
|
277
280
|
|
278
281
|
# 筛选有效结构且在prd范围内的数据
|
279
282
|
last_struct = None
|
@@ -0,0 +1,15 @@
|
|
1
|
+
core/Exchange.py,sha256=MDhV71jmWyM1IGaUI-iSfZqrUG9RNOpSWqm0jNV_GXU,23173
|
2
|
+
core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
3
|
+
core/main.py,sha256=E-VZzem7-0_J6EmOo9blLPokc5MRcgjqCbqAvbkPnWI,630
|
4
|
+
core/smc/SMCBase.py,sha256=epRC5bWDymx7ZMIhn_bVJRjvBHItt6BCnYASO2fhSDg,4302
|
5
|
+
core/smc/SMCFVG.py,sha256=KDyqR8dQiKYpJ25HpXeVPdxVgAxbtwc9LJ-OVrGIlZk,3009
|
6
|
+
core/smc/SMCLiquidity.py,sha256=bK5iOz0TQVd5y-xNNUmTh8pny3bWr0OVVPFunAppv1Q,7976
|
7
|
+
core/smc/SMCOrderBlock.py,sha256=lh868FUjb6RHQ0DwUeBfYymf9j-Lcv6YWJvYlJF3iUo,9918
|
8
|
+
core/smc/SMCPDArray.py,sha256=jIBo2anp9_5C9_CFSe-pbwprm14Qs__zVbsfNAu2_bE,4127
|
9
|
+
core/smc/SMCStruct.py,sha256=dW3iLKNV78pDbcpyL7SjLVUyAsc1DrO8gSCJkGbrKO4,12339
|
10
|
+
core/smc/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
11
|
+
core/utils/OPTools.py,sha256=tJ1Jq_Caab6OWaX12xn4_g9ryf98Rm5I1zsJEEU8NIQ,1002
|
12
|
+
openfund_core-1.0.8.dist-info/METADATA,sha256=is_DnPGzyBUwQUi7ysyqCnMmu9ecRpA6uP3jB5yXN0I,1953
|
13
|
+
openfund_core-1.0.8.dist-info/WHEEL,sha256=fGIA9gx4Qxk2KDKeNJCbOEwSrmLtjWCwzBz351GyrPQ,88
|
14
|
+
openfund_core-1.0.8.dist-info/entry_points.txt,sha256=g8GUw3cyKFtcG5VWs8geU5VBLqiWr59GElqERuH8zD0,48
|
15
|
+
openfund_core-1.0.8.dist-info/RECORD,,
|
@@ -1,15 +0,0 @@
|
|
1
|
-
core/Exchange.py,sha256=eanGV-8ZLDFxP2tV6xumo8c68yqFTUeseJA_dHMhYb0,20785
|
2
|
-
core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
3
|
-
core/main.py,sha256=E-VZzem7-0_J6EmOo9blLPokc5MRcgjqCbqAvbkPnWI,630
|
4
|
-
core/smc/SMCBase.py,sha256=epRC5bWDymx7ZMIhn_bVJRjvBHItt6BCnYASO2fhSDg,4302
|
5
|
-
core/smc/SMCFVG.py,sha256=QtqlW1oooYVA7CG5ld5X0Q5twX1XCELO118IlMUhX6M,2974
|
6
|
-
core/smc/SMCLiquidity.py,sha256=lZt2IQk3TWaT-nA7he57dUxPdLEWW61jRZWLAzOTat0,119
|
7
|
-
core/smc/SMCOrderBlock.py,sha256=Il5JKmVER2vT6AKZLo0mD4wRqV_Op9IBK3jB1SfgTqY,9894
|
8
|
-
core/smc/SMCPDArray.py,sha256=Vn_nTBLaIhrBhxe_hX3Iycn0gY0tmYd_qaqNalztfmA,2841
|
9
|
-
core/smc/SMCStruct.py,sha256=FZoh_F81YvONZObbDF7jzH8F6lDgo0-17tNQwOS3V_g,12260
|
10
|
-
core/smc/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
11
|
-
core/utils/OPTools.py,sha256=tJ1Jq_Caab6OWaX12xn4_g9ryf98Rm5I1zsJEEU8NIQ,1002
|
12
|
-
openfund_core-1.0.5.dist-info/METADATA,sha256=VZWu0jgtwFtrr5fqdgk3p28GZWGwoMQeNNHwFj5QHsk,1953
|
13
|
-
openfund_core-1.0.5.dist-info/WHEEL,sha256=IYZQI976HJqqOpQU6PHkJ8fb3tMNBFjg-Cn-pwAbaFM,88
|
14
|
-
openfund_core-1.0.5.dist-info/entry_points.txt,sha256=g8GUw3cyKFtcG5VWs8geU5VBLqiWr59GElqERuH8zD0,48
|
15
|
-
openfund_core-1.0.5.dist-info/RECORD,,
|
File without changes
|