openfund-core 1.0.1__py3-none-any.whl → 1.0.7__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 +372 -56
- core/smc/SMCLiquidity.py +205 -3
- core/smc/SMCOrderBlock.py +19 -27
- core/smc/SMCPDArray.py +0 -2
- core/smc/SMCStruct.py +27 -18
- {openfund_core-1.0.1.dist-info → openfund_core-1.0.7.dist-info}/METADATA +1 -1
- openfund_core-1.0.7.dist-info/RECORD +15 -0
- {openfund_core-1.0.1.dist-info → openfund_core-1.0.7.dist-info}/WHEEL +1 -1
- openfund_core-1.0.1.dist-info/RECORD +0 -15
- {openfund_core-1.0.1.dist-info → openfund_core-1.0.7.dist-info}/entry_points.txt +0 -0
core/Exchange.py
CHANGED
@@ -10,6 +10,15 @@ from ccxt.base.exchange import ConstructorArgs
|
|
10
10
|
|
11
11
|
|
12
12
|
class Exchange:
|
13
|
+
BUY_SIDE = 'buy'
|
14
|
+
SELL_SIDE = 'sell'
|
15
|
+
LONG_KEY = 'long'
|
16
|
+
SHORT_KEY = 'short'
|
17
|
+
SIDE_KEY = 'side'
|
18
|
+
SYMBOL_KEY = 'symbol'
|
19
|
+
ENTRY_PRICE_KEY = 'entryPrice'
|
20
|
+
MARK_PRICE_KEY = 'markPrice'
|
21
|
+
CONTRACTS_KEY = 'contracts'
|
13
22
|
def __init__(self, config:ConstructorArgs, exchangeKey:str = "okx",) :
|
14
23
|
# 配置交易所
|
15
24
|
self.exchange = getattr(ccxt, exchangeKey)(config)
|
@@ -22,19 +31,8 @@ class Exchange:
|
|
22
31
|
self.exchange.load_markets()
|
23
32
|
|
24
33
|
return self.exchange.market(symbol)
|
25
|
-
|
26
|
-
def
|
27
|
-
|
28
|
-
market = self.getMarket(symbol)
|
29
|
-
if market and 'precision' in market and 'price' in market['precision']:
|
30
|
-
return OPTools.toDecimal(market['precision']['price'])
|
31
|
-
else:
|
32
|
-
raise ValueError(f"{symbol}: 无法从市场数据中获取价格精度")
|
33
|
-
|
34
|
-
def amount_to_precision(self,symbol, contract_size):
|
35
|
-
return self.exchange.amount_to_precision(symbol, contract_size)
|
36
|
-
|
37
|
-
def get_position_mode(self):
|
34
|
+
|
35
|
+
def get_position_mode(self) -> str:
|
38
36
|
|
39
37
|
try:
|
40
38
|
# 假设获取账户持仓模式的 API
|
@@ -52,6 +50,19 @@ class Exchange:
|
|
52
50
|
error_message = f"Error fetching position mode: {e}"
|
53
51
|
self.logger.error(error_message)
|
54
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
|
+
|
55
66
|
|
56
67
|
def set_leverage(self,symbol, leverage, mgnMode='isolated',posSide=None):
|
57
68
|
try:
|
@@ -96,10 +107,66 @@ class Exchange:
|
|
96
107
|
elif direction == 'cost_to_contract':
|
97
108
|
contract_size = amount / price / market_contractSize
|
98
109
|
else:
|
99
|
-
raise Exception(f"{symbol}:{direction} 是无效的转换方向,请输入 'amount_to_contract' 或 'cost_to_contract'。")
|
110
|
+
raise Exception(f"{symbol} : {direction} 是无效的转换方向,请输入 'amount_to_contract' 或 'cost_to_contract'。")
|
100
111
|
|
101
112
|
return self.amount_to_precision(symbol, contract_size)
|
102
|
-
|
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
|
+
|
103
170
|
|
104
171
|
def cancel_all_orders(self, symbol):
|
105
172
|
max_retries = 3
|
@@ -133,8 +200,171 @@ class Exchange:
|
|
133
200
|
self.logger.warning(f"{symbol} 取消挂单失败,正在进行第{retry_count}次重试: {str(e)}")
|
134
201
|
time.sleep(0.1) # 重试前等待0.1秒
|
135
202
|
|
203
|
+
def cancel_all_algo_orders(self, symbol, attachType=None) -> bool:
|
204
|
+
"""_summary_
|
205
|
+
|
206
|
+
Args:
|
207
|
+
symbol (_type_): _description_
|
208
|
+
attachType (_type_, optional): "TP"|"SL". Defaults to None.
|
209
|
+
"""
|
210
|
+
|
211
|
+
params = {
|
212
|
+
"ordType": "conditional",
|
213
|
+
}
|
214
|
+
try:
|
215
|
+
orders = self.fetch_open_orders(symbol=symbol,params=params)
|
216
|
+
except Exception as e:
|
217
|
+
error_message = f"!!{symbol} : Error fetching open orders: {e}"
|
218
|
+
self.logger.error(error_message)
|
219
|
+
raise Exception(error_message)
|
220
|
+
|
221
|
+
|
222
|
+
if len(orders) == 0:
|
223
|
+
self.logger.debug(f"{symbol} 未设置策略订单列表。")
|
224
|
+
return True
|
225
|
+
|
226
|
+
algo_ids = []
|
227
|
+
if attachType and attachType == 'SL':
|
228
|
+
algo_ids = [order['id'] for order in orders if order['stopLossPrice'] and order['stopLossPrice'] > 0.0 ]
|
229
|
+
elif attachType and attachType == 'TP':
|
230
|
+
algo_ids = [order['id'] for order in orders if order['takeProfitPrice'] and order['takeProfitPrice'] > 0.0]
|
231
|
+
else :
|
232
|
+
algo_ids = [order['id'] for order in orders ]
|
233
|
+
|
234
|
+
if len(algo_ids) == 0 :
|
235
|
+
self.logger.debug(f"{symbol} 未设置策略订单列表。")
|
236
|
+
return True
|
237
|
+
|
238
|
+
max_retries = 3
|
239
|
+
retry_count = 0
|
240
|
+
|
241
|
+
while retry_count < max_retries:
|
242
|
+
try:
|
243
|
+
params = {
|
244
|
+
"algoId": algo_ids,
|
245
|
+
"trigger": 'trigger'
|
246
|
+
}
|
247
|
+
rs = self.exchange.cancel_orders(ids=algo_ids, symbol=symbol, params=params)
|
248
|
+
|
249
|
+
return len(rs) > 0
|
250
|
+
|
251
|
+
except Exception as e:
|
252
|
+
retry_count += 1
|
253
|
+
if retry_count == max_retries:
|
254
|
+
error_message = f"!!{symbol} : Error cancelling order {algo_ids}: {e}"
|
255
|
+
self.logger.error(error_message)
|
256
|
+
raise Exception(error_message)
|
257
|
+
|
258
|
+
self.logger.warning(f"{symbol} : Error cancelling order {algo_ids}: {str(e)}")
|
259
|
+
time.sleep(0.1) # 重试前等待0.1秒
|
260
|
+
def place_algo_orders(self, symbol, position, price: Decimal, order_type, sl_or_tp='SL', params={}) -> bool:
|
261
|
+
"""
|
262
|
+
下单
|
263
|
+
Args:
|
264
|
+
symbol: 交易对
|
265
|
+
position: 仓位
|
266
|
+
price: 下单价格
|
267
|
+
order_type: 订单类型
|
268
|
+
"""
|
269
|
+
# 计算下单数量
|
270
|
+
amount = abs(position[self.CONTRACTS_KEY])
|
271
|
+
|
272
|
+
if amount <= 0:
|
273
|
+
self.logger.warning(f"{symbol}: amount is 0 for {symbol}")
|
274
|
+
return
|
275
|
+
|
276
|
+
# 止损单逻辑
|
277
|
+
adjusted_price = self.format_price(symbol, price)
|
278
|
+
|
279
|
+
# 默认市价止损,委托价格为-1时,执行市价止损。
|
280
|
+
sl_params = {
|
281
|
+
**params,
|
282
|
+
'slTriggerPx':adjusted_price ,
|
283
|
+
'slOrdPx':'-1', # 委托价格为-1时,执行市价止损
|
284
|
+
# 'slOrdPx' : adjusted_price,
|
285
|
+
'slTriggerPxType':'last',
|
286
|
+
'tdMode':position['marginMode'],
|
287
|
+
'sz': str(amount),
|
288
|
+
'cxlOnClosePos': True,
|
289
|
+
'reduceOnly':True,
|
290
|
+
}
|
291
|
+
|
292
|
+
tp_params = {
|
293
|
+
**params,
|
294
|
+
'tpTriggerPx':adjusted_price,
|
295
|
+
'tpOrdPx' : adjusted_price,
|
296
|
+
'tpOrdKind': 'condition',
|
297
|
+
'tpTriggerPxType':'last',
|
298
|
+
'tdMode':position['marginMode'],
|
299
|
+
'sz': str(amount),
|
300
|
+
'cxlOnClosePos': True,
|
301
|
+
'reduceOnly':True
|
302
|
+
}
|
303
|
+
|
304
|
+
order_params = sl_params if sl_or_tp == 'SL' else tp_params
|
305
|
+
# order_params.update(params)
|
306
|
+
|
307
|
+
if order_type == 'limit' and sl_or_tp =='SL':
|
308
|
+
order_params['slOrdPx'] = adjusted_price
|
309
|
+
|
310
|
+
orderSide = self.BUY_SIDE if position[self.SIDE_KEY] == self.SHORT_KEY else self.SELL_SIDE # 和持仓反向相反下单
|
311
|
+
|
312
|
+
order = {
|
313
|
+
'symbol': symbol,
|
314
|
+
'side': orderSide,
|
315
|
+
'type': order_type,
|
316
|
+
'amount': amount,
|
317
|
+
'price': adjusted_price,
|
318
|
+
'params': order_params
|
319
|
+
}
|
320
|
+
|
321
|
+
max_retries = 3
|
322
|
+
retry_count = 0
|
323
|
+
self.logger.info(f"{symbol} : Pre Algo Order placed: {order} ")
|
324
|
+
while retry_count < max_retries:
|
325
|
+
try:
|
326
|
+
|
327
|
+
self.exchange.create_order(
|
328
|
+
**order
|
329
|
+
# symbol=symbol,
|
330
|
+
# type=order_type,
|
331
|
+
# price=adjusted_price,
|
332
|
+
# side=orderSide,
|
333
|
+
# amount=amount,
|
334
|
+
# params=order_params
|
335
|
+
)
|
336
|
+
|
337
|
+
break
|
338
|
+
|
339
|
+
except ccxt.NetworkError as e:
|
340
|
+
# 处理网络相关错误
|
341
|
+
retry_count += 1
|
342
|
+
self.logger.warning(f"{symbol} : 设置止盈止损时发生网络错误,正在进行第{retry_count}次重试: {str(e)}")
|
343
|
+
time.sleep(0.1) # 重试前等待1秒
|
344
|
+
continue
|
345
|
+
except ccxt.ExchangeError as e:
|
346
|
+
# 处理交易所API相关错误
|
347
|
+
retry_count += 1
|
348
|
+
self.logger.warning(f"{symbol} : 设置止盈止损单时发生交易所错误,正在进行第{retry_count}次重试: {str(e)}")
|
349
|
+
time.sleep(0.1)
|
350
|
+
continue
|
351
|
+
except Exception as e:
|
352
|
+
# 处理其他未预期的错误
|
353
|
+
retry_count += 1
|
354
|
+
self.logger.warning(f"{symbol} : 设置止盈止损单时发生未知错误,正在进行第{retry_count}次重试: {str(e)}")
|
355
|
+
time.sleep(0.1)
|
356
|
+
continue
|
357
|
+
|
358
|
+
if retry_count >= max_retries:
|
359
|
+
# 重试次数用完仍未成功设置止损单
|
360
|
+
error_message = f"!! {symbol}: 设置止盈止损单时重试次数用完仍未成功设置成功。 "
|
361
|
+
self.logger.error(error_message)
|
362
|
+
raise Exception(error_message)
|
363
|
+
self.logger.debug(f"{symbol} : --------- ++ Order placed done. --------")
|
364
|
+
return True
|
365
|
+
|
136
366
|
|
137
|
-
def place_order(self, symbol, price: Decimal, amount_usdt,
|
367
|
+
def place_order(self, symbol, price: Decimal, amount_usdt, side, leverage=20, order_type='limit', params={}) -> bool:
|
138
368
|
"""
|
139
369
|
下单
|
140
370
|
Args:
|
@@ -147,41 +377,47 @@ class Exchange:
|
|
147
377
|
# 格式化价格
|
148
378
|
adjusted_price = self.format_price(symbol, price)
|
149
379
|
|
150
|
-
if amount_usdt
|
151
|
-
|
152
|
-
|
153
|
-
else:
|
154
|
-
pos_side = 'short'
|
155
|
-
# 设置杠杆
|
156
|
-
self.set_leverage(symbol=symbol, leverage=leverage, mgnMode='isolated',posSide=pos_side)
|
157
|
-
# 20250220 SWAP类型计算合约数量
|
158
|
-
contract_size = self.convert_contract(symbol=symbol, price = OPTools.toDecimal(adjusted_price) ,amount=amount_usdt)
|
380
|
+
if amount_usdt <= 0:
|
381
|
+
self.logger.warning(f"{symbol}: amount_usdt must be greater than 0")
|
382
|
+
return
|
159
383
|
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
384
|
+
pos_side = self.LONG_KEY if side == self.BUY_SIDE else self.SHORT_KEY
|
385
|
+
|
386
|
+
# 设置杠杆
|
387
|
+
self.set_leverage(symbol=symbol, leverage=leverage, mgnMode='isolated',posSide=pos_side)
|
388
|
+
# 20250220 SWAP类型计算合约数量
|
389
|
+
contract_size = self.convert_contract(symbol=symbol, price = OPTools.toDecimal(adjusted_price) ,amount=amount_usdt)
|
390
|
+
|
391
|
+
order_params = {
|
392
|
+
**params,
|
393
|
+
"tdMode": 'isolated',
|
394
|
+
"side": side,
|
395
|
+
"ordType": order_type,
|
396
|
+
"sz": contract_size,
|
397
|
+
"px": adjusted_price
|
398
|
+
}
|
399
|
+
|
400
|
+
# # 模拟盘(demo_trading)需要 posSide
|
401
|
+
# if self.is_demo_trading == 1 :
|
402
|
+
# params["posSide"] = pos_side
|
168
403
|
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
404
|
+
order = {
|
405
|
+
'symbol': symbol,
|
406
|
+
'side': side,
|
407
|
+
'type': order_type,
|
408
|
+
'amount': contract_size,
|
409
|
+
'price': adjusted_price,
|
410
|
+
'params': order_params
|
411
|
+
}
|
412
|
+
|
413
|
+
max_retries = 3
|
414
|
+
retry_count = 0
|
415
|
+
|
416
|
+
self.logger.info(f"{symbol} : Pre Order placed: {order} ")
|
417
|
+
|
418
|
+
while retry_count < max_retries:
|
174
419
|
try:
|
175
|
-
order = {
|
176
|
-
'symbol': symbol,
|
177
|
-
'side': side,
|
178
|
-
'type': 'limit',
|
179
|
-
'amount': contract_size,
|
180
|
-
'price': adjusted_price,
|
181
|
-
'params': params
|
182
|
-
}
|
183
420
|
# 使用ccxt创建订单
|
184
|
-
self.logger.debug(f"Pre Order placed: {order} ")
|
185
421
|
order_result = self.exchange.create_order(
|
186
422
|
**order
|
187
423
|
# symbol=symbol,
|
@@ -191,13 +427,31 @@ class Exchange:
|
|
191
427
|
# price=float(adjusted_price),
|
192
428
|
# params=params
|
193
429
|
)
|
194
|
-
|
430
|
+
except ccxt.NetworkError as e:
|
431
|
+
# 处理网络相关错误
|
432
|
+
retry_count += 1
|
433
|
+
self.logger.warning(f"{symbol} : 下单时发生网络错误,正在进行第{retry_count}次重试: {str(e)}")
|
434
|
+
time.sleep(0.1) # 重试前等待1秒
|
435
|
+
continue
|
436
|
+
except ccxt.ExchangeError as e:
|
437
|
+
# 处理交易所API相关错误
|
438
|
+
retry_count += 1
|
439
|
+
self.logger.warning(f"{symbol} : 下单时发生交易所错误,正在进行第{retry_count}次重试: {str(e)}")
|
440
|
+
time.sleep(0.1)
|
441
|
+
continue
|
195
442
|
except Exception as e:
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
443
|
+
# 处理其他未预期的错误
|
444
|
+
retry_count += 1
|
445
|
+
self.logger.warning(f"{symbol} : 下单时发生未知错误,正在进行第{retry_count}次重试: {str(e)}")
|
446
|
+
time.sleep(0.1)
|
447
|
+
continue
|
448
|
+
if retry_count >= max_retries:
|
449
|
+
# 重试次数用完仍未成功设置止损单
|
450
|
+
error_message = f"!! {symbol}: 下单时重试次数用完仍未成功设置成功。 "
|
451
|
+
self.logger.error(error_message)
|
452
|
+
raise Exception(error_message)
|
453
|
+
self.logger.debug(f"{symbol} : --------- ++ Order placed done. --------")
|
454
|
+
return True
|
201
455
|
|
202
456
|
def fetch_position(self, symbol):
|
203
457
|
"""_summary_
|
@@ -215,8 +469,8 @@ class Exchange:
|
|
215
469
|
while retry_count < max_retries:
|
216
470
|
try:
|
217
471
|
position = self.exchange.fetch_position(symbol=symbol)
|
218
|
-
if position
|
219
|
-
self.logger.debug(f"{symbol} 有持仓合约数: {position['contracts']}")
|
472
|
+
if position :
|
473
|
+
# self.logger.debug(f"{symbol} 有持仓合约数: {position['contracts']}")
|
220
474
|
return position
|
221
475
|
return None
|
222
476
|
except Exception as e:
|
@@ -228,7 +482,69 @@ class Exchange:
|
|
228
482
|
|
229
483
|
self.logger.warning(f"{symbol} 检查持仓失败,正在进行第{retry_count}次重试: {str(e)}")
|
230
484
|
time.sleep(0.1) # 重试前等待0.1秒
|
231
|
-
|
485
|
+
|
486
|
+
def fetch_positions(self):
|
487
|
+
"""_summary_
|
488
|
+
Returns:
|
489
|
+
_type_: _description_
|
490
|
+
"""
|
491
|
+
max_retries = 3
|
492
|
+
retry_count = 0
|
493
|
+
|
494
|
+
while retry_count < max_retries:
|
495
|
+
try:
|
496
|
+
positions = self.exchange.fetch_positions()
|
497
|
+
return positions
|
498
|
+
except Exception as e:
|
499
|
+
retry_count += 1
|
500
|
+
if retry_count == max_retries:
|
501
|
+
error_message = f"!! 获取持仓列表失败(重试{retry_count}次): {str(e)}"
|
502
|
+
self.logger.error(error_message)
|
503
|
+
raise Exception(error_message)
|
504
|
+
|
505
|
+
self.logger.warning(f"获取持仓列表失败,正在进行第{retry_count}次重试: {str(e)}")
|
506
|
+
time.sleep(0.1) # 重试前等待0.1秒
|
507
|
+
|
508
|
+
def fetch_open_orders(self,symbol,params={}):
|
509
|
+
max_retries = 3
|
510
|
+
retry_count = 0
|
511
|
+
|
512
|
+
while retry_count < max_retries:
|
513
|
+
try:
|
514
|
+
orders = self.exchange.fetch_open_orders(symbol=symbol,params=params)
|
515
|
+
return orders
|
516
|
+
|
517
|
+
except Exception as e:
|
518
|
+
retry_count += 1
|
519
|
+
if retry_count == max_retries:
|
520
|
+
error_message = f"{symbol} : fetching open orders(retry {retry_count} times): {str(e)}"
|
521
|
+
self.logger.error(error_message)
|
522
|
+
raise Exception(error_message)
|
523
|
+
|
524
|
+
self.logger.warning(f"{symbol} : Error fetching open orders: {str(e)}")
|
525
|
+
time.sleep(0.1) # 重试前等待0.1秒
|
526
|
+
def get_market_price(self, symbol) -> Decimal:
|
527
|
+
"""
|
528
|
+
获取最新价格
|
529
|
+
Args:
|
530
|
+
symbol: 交易对
|
531
|
+
"""
|
532
|
+
max_retries = 3
|
533
|
+
retry_count = 0
|
534
|
+
|
535
|
+
while retry_count < max_retries:
|
536
|
+
try:
|
537
|
+
ticker = self.exchange.fetch_ticker(symbol)
|
538
|
+
if ticker and 'last' in ticker:
|
539
|
+
return OPTools.toDecimal(ticker['last'])
|
540
|
+
else:
|
541
|
+
raise Exception(f"{symbol} : Unexpected response structure or missing 'last' price")
|
542
|
+
except Exception as e:
|
543
|
+
retry_count += 1
|
544
|
+
if retry_count == max_retries:
|
545
|
+
error_message = f"{symbol} 获取最新价格失败(重试{retry_count}次): {str(e)}"
|
546
|
+
self.logger.error(error_message)
|
547
|
+
raise Exception(error_message)
|
232
548
|
|
233
549
|
def get_historical_klines(self, symbol, bar='15m', limit=300, after:str=None, params={}):
|
234
550
|
"""
|
core/smc/SMCLiquidity.py
CHANGED
@@ -1,7 +1,209 @@
|
|
1
1
|
import logging
|
2
|
+
from re import S
|
3
|
+
import pandas as pd
|
4
|
+
from core.smc.SMCStruct import SMCStruct
|
5
|
+
from pandas.core.strings.accessor import F
|
6
|
+
from pandas.io.parquet import catch_warnings
|
7
|
+
|
8
|
+
class SMCLiquidity(SMCStruct):
|
9
|
+
EQUAL_HIGH_COL = "equal_high"
|
10
|
+
EQUAL_LOW_COL = "equal_low"
|
11
|
+
LIQU_HIGH_COL = "liqu_high"
|
12
|
+
LIQU_LOW_COL = "liqu_low"
|
13
|
+
EQUAL_HIGH_INDEX_KEY = "equal_high_index"
|
14
|
+
EQUAL_LOW_INDEX_KEY = "equal_low_index"
|
15
|
+
HAS_EQ_KEY = "has_EQ"
|
16
|
+
LIQU_HIGH_DIFF_COL = "liqu_high_diff"
|
17
|
+
LIQU_LOW_DIFF_COL = "liqu_low_diff"
|
18
|
+
|
2
19
|
|
3
|
-
class SMCLiquidity:
|
4
20
|
def __init__(self):
|
21
|
+
super().__init__()
|
5
22
|
self.logger = logging.getLogger(__name__)
|
6
|
-
|
7
|
-
|
23
|
+
|
24
|
+
|
25
|
+
def _identify_liquidity_pivots(self, data, pivot_length=1):
|
26
|
+
"""
|
27
|
+
识别流动性的高点和低点
|
28
|
+
"""
|
29
|
+
|
30
|
+
df = data.copy()
|
31
|
+
|
32
|
+
# 识别高点
|
33
|
+
df[self.LIQU_HIGH_COL] = 0
|
34
|
+
for i in range(pivot_length, len(df) - pivot_length):
|
35
|
+
if df[self.HIGH_COL].iloc[i] == max(df[self.HIGH_COL].iloc[i-pivot_length:i+pivot_length+1]):
|
36
|
+
df.loc[df.index[i], self.LIQU_HIGH_COL] = df[self.HIGH_COL].iloc[i]
|
37
|
+
|
38
|
+
# 识别低点
|
39
|
+
df[self.LIQU_LOW_COL] = 0
|
40
|
+
for i in range(pivot_length, len(df) - pivot_length):
|
41
|
+
|
42
|
+
if df[self.LOW_COL].iloc[i] == min(df[self.LOW_COL].iloc[i-pivot_length:i+pivot_length+1]):
|
43
|
+
df.loc[df.index[i], self.LIQU_LOW_COL] = df[self.LOW_COL].iloc[i]
|
44
|
+
|
45
|
+
|
46
|
+
|
47
|
+
return df
|
48
|
+
|
49
|
+
def find_EQH_EQL(self, data, trend, end_idx=-1, atr_offset=0.1) -> dict:
|
50
|
+
"""_summary_
|
51
|
+
识别等高等低流动性
|
52
|
+
Args:
|
53
|
+
data (_type_): _description_
|
54
|
+
trend (_type_): _description_
|
55
|
+
end_idx (int, optional): _description_. Defaults to -1.
|
56
|
+
atr_offset (float, optional): _description_. Defaults to 0.1.
|
57
|
+
|
58
|
+
Returns:
|
59
|
+
dict: _description_
|
60
|
+
"""
|
61
|
+
|
62
|
+
df = data.copy() if end_idx == -1 else data.copy().iloc[:end_idx+1]
|
63
|
+
|
64
|
+
check_columns = [self.LIQU_HIGH_COL, self.LIQU_LOW_COL]
|
65
|
+
|
66
|
+
try:
|
67
|
+
self.check_columns(df, check_columns)
|
68
|
+
except ValueError as e:
|
69
|
+
self.logger.warning(f"DataFrame must contain columns {check_columns} : {str(e)}")
|
70
|
+
df = self._identify_liquidity_pivots(df)
|
71
|
+
|
72
|
+
df = df[(df[self.LIQU_HIGH_COL] > 0) | (df[self.LIQU_LOW_COL] > 0)]
|
73
|
+
# 初始化结果列
|
74
|
+
df[self.EQUAL_HIGH_COL] = 0
|
75
|
+
df[self.EQUAL_LOW_COL] = 0
|
76
|
+
df[self.ATR_COL] = self.calculate_atr(df)
|
77
|
+
# 跟踪前一个高点和低点
|
78
|
+
previous_high = None
|
79
|
+
previous_high_index = None
|
80
|
+
previous_high_pos = -1
|
81
|
+
previous_low = None
|
82
|
+
previous_low_index = None
|
83
|
+
previous_low_pos = -1
|
84
|
+
for i in range(len(df)-1, -1, -1):
|
85
|
+
|
86
|
+
offset = self.toDecimal(df[self.ATR_COL].iloc[i] * atr_offset)
|
87
|
+
|
88
|
+
if trend == self.BULLISH_TREND:
|
89
|
+
current_high = df[self.LIQU_HIGH_COL].iloc[i]
|
90
|
+
if current_high == 0:
|
91
|
+
continue
|
92
|
+
|
93
|
+
if previous_high is None:
|
94
|
+
previous_high = current_high
|
95
|
+
previous_high_index = df.index[i]
|
96
|
+
previous_high_pos = i
|
97
|
+
continue
|
98
|
+
|
99
|
+
max_val = max(current_high, previous_high)
|
100
|
+
min_val = min(current_high, previous_high)
|
101
|
+
|
102
|
+
|
103
|
+
if abs(max_val - min_val) <= offset: # EQH|EQL
|
104
|
+
|
105
|
+
df.loc[df.index[i], self.EQUAL_HIGH_COL] = previous_high_index
|
106
|
+
df.loc[df.index[previous_high_pos], self.EQUAL_HIGH_COL] = previous_high_index
|
107
|
+
|
108
|
+
else:
|
109
|
+
# 倒序遍历,等高线被高点破坏,则更新等高点位置
|
110
|
+
if current_high > previous_high:
|
111
|
+
previous_high = current_high
|
112
|
+
previous_high_index = df.index[i]
|
113
|
+
previous_high_pos = i
|
114
|
+
|
115
|
+
|
116
|
+
|
117
|
+
else:
|
118
|
+
current_low = df[self.LIQU_LOW_COL].iloc[i]
|
119
|
+
if current_low == 0:
|
120
|
+
continue
|
121
|
+
|
122
|
+
# current_low = df[self.EQUAL_LOW_COL].iloc[i]
|
123
|
+
if previous_low is None:
|
124
|
+
previous_low = current_low
|
125
|
+
previous_low_index = df.index[i]
|
126
|
+
previous_low_pos = i
|
127
|
+
continue
|
128
|
+
|
129
|
+
max_val = max(current_low, previous_low)
|
130
|
+
min_val = min(current_low, previous_low)
|
131
|
+
|
132
|
+
|
133
|
+
|
134
|
+
|
135
|
+
if abs(max_val - min_val) <= offset: # EQH|EQL
|
136
|
+
|
137
|
+
df.loc[df.index[i], self.EQUAL_LOW_COL] = previous_low_index
|
138
|
+
df.loc[df.index[previous_low_pos], self.EQUAL_LOW_COL] = previous_low_index
|
139
|
+
|
140
|
+
else:
|
141
|
+
# 倒序遍历,等高线被高点破坏,则更新等高点位置
|
142
|
+
if current_low < previous_low:
|
143
|
+
previous_low = current_low
|
144
|
+
previous_low_index = df.index[i]
|
145
|
+
previous_low_pos = i
|
146
|
+
|
147
|
+
# 筛选有效结构且在prd范围内的数据
|
148
|
+
last_EQ = {
|
149
|
+
|
150
|
+
}
|
151
|
+
if trend == self.BULLISH_TREND :
|
152
|
+
mask = df[self.EQUAL_HIGH_COL] > 0
|
153
|
+
valid_EQH_df = df[ mask ]
|
154
|
+
if not valid_EQH_df.empty:
|
155
|
+
last_EQ[self.HAS_EQ_KEY] = True
|
156
|
+
last_EQ[self.EQUAL_HIGH_COL] = valid_EQH_df.iloc[-1][self.LIQU_HIGH_COL]
|
157
|
+
last_EQ[self.EQUAL_HIGH_INDEX_KEY] = valid_EQH_df.iloc[-1][self.EQUAL_HIGH_COL]
|
158
|
+
else:
|
159
|
+
mask = df[self.EQUAL_LOW_COL] > 0
|
160
|
+
valid_EQL_df = df[ mask ]
|
161
|
+
if not valid_EQL_df.empty:
|
162
|
+
last_EQ[self.HAS_EQ_KEY] = True
|
163
|
+
last_EQ[self.EQUAL_LOW_COL] = valid_EQL_df.iloc[-1][self.LIQU_LOW_COL]
|
164
|
+
last_EQ[self.EQUAL_LOW_INDEX_KEY] = valid_EQL_df.iloc[-1][self.EQUAL_LOW_COL]
|
165
|
+
|
166
|
+
return last_EQ
|
167
|
+
|
168
|
+
def identify_dynamic_trendlines(self, data, trend, start_idx=-1, end_idx=-1, ratio=0.8) -> tuple:
|
169
|
+
"""
|
170
|
+
识别动态趋势线或隧道
|
171
|
+
Args:
|
172
|
+
data (pd.DataFrame): _description_
|
173
|
+
|
174
|
+
Returns:
|
175
|
+
pd.DataFrame: _description_
|
176
|
+
"""
|
177
|
+
|
178
|
+
df = data.copy() if start_idx == -1 or end_idx == -1 else data.copy().iloc[start_idx-1:end_idx+2] #考虑poivt值,前后各增加一个
|
179
|
+
|
180
|
+
check_columns = [self.LIQU_HIGH_COL]
|
181
|
+
|
182
|
+
try:
|
183
|
+
self.check_columns(df, check_columns)
|
184
|
+
except ValueError as e:
|
185
|
+
self.logger.warning(f"DataFrame must contain columns {check_columns} : {str(e)}")
|
186
|
+
df = self._identify_liquidity_pivots(df)
|
187
|
+
diff_ratio = 0.0
|
188
|
+
if trend == self.BEARISH_TREND:
|
189
|
+
# 判断Bearish趋势是高点不断升高,
|
190
|
+
liqu_bear_df = df[df[self.LIQU_HIGH_COL] > 0]
|
191
|
+
liqu_bear_df[self.LIQU_HIGH_DIFF_COL] = liqu_bear_df[self.LIQU_HIGH_COL].diff()
|
192
|
+
# self.logger.info(f"dynamic_trendlines:\n {liqu_bear_df[[self.TIMESTAMP_COL,self.LIQU_HIGH_COL,self.LIQU_HIGH_DIFF_COL]]}")
|
193
|
+
diff_ratio = self.toDecimal(liqu_bear_df[self.LIQU_HIGH_DIFF_COL].dropna().lt(0).mean(),2)
|
194
|
+
if diff_ratio >= ratio:
|
195
|
+
return diff_ratio,True
|
196
|
+
else:
|
197
|
+
# Bullish趋势是低点不断降低
|
198
|
+
liqu_bullish_df = df[df[self.LIQU_LOW_COL] > 0]
|
199
|
+
liqu_bullish_df[self.LIQU_LOW_DIFF_COL] = liqu_bullish_df[self.LIQU_LOW_COL].diff()
|
200
|
+
# self.logger.info(f"dynamic_trendlines:\n {liqu_bullish_df[[self.TIMESTAMP_COL,self.LIQU_LOW_COL,self.LIQU_LOW_DIFF_COL]]}")
|
201
|
+
diff_ratio = self.toDecimal(liqu_bullish_df[self.LIQU_LOW_DIFF_COL].dropna().gt(0).mean(),2)
|
202
|
+
if diff_ratio >= ratio:
|
203
|
+
return diff_ratio,True
|
204
|
+
|
205
|
+
return diff_ratio,False
|
206
|
+
|
207
|
+
|
208
|
+
|
209
|
+
|
core/smc/SMCOrderBlock.py
CHANGED
@@ -54,7 +54,7 @@ class SMCOrderBlock(SMCStruct):
|
|
54
54
|
ob_df = ob_df[ob_df[self.OB_DIRECTION_COL] == direction]
|
55
55
|
|
56
56
|
# 检查OB是否被平衡过
|
57
|
-
|
57
|
+
ob_df = ob_df.copy()
|
58
58
|
ob_df.loc[:, self.OB_WAS_CROSSED] = ob_df.apply(
|
59
59
|
lambda row: any(
|
60
60
|
df.loc[row.name + 1 :, self.LOW_COL] <= row[self.OB_LOW_COL]
|
@@ -64,25 +64,7 @@ class SMCOrderBlock(SMCStruct):
|
|
64
64
|
axis=1,
|
65
65
|
)
|
66
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)]
|
67
|
+
ob_df = ob_df[~ob_df[self.OB_WAS_CROSSED]]
|
86
68
|
|
87
69
|
if if_combine:
|
88
70
|
# 合并OB
|
@@ -91,7 +73,7 @@ class SMCOrderBlock(SMCStruct):
|
|
91
73
|
return ob_df
|
92
74
|
|
93
75
|
def build_struct_for_ob(
|
94
|
-
self, df,
|
76
|
+
self, df, is_struct_body_break=True, atr_multiplier=0.6
|
95
77
|
):
|
96
78
|
"""
|
97
79
|
构建结构并检测Order Block
|
@@ -107,7 +89,7 @@ class SMCOrderBlock(SMCStruct):
|
|
107
89
|
处理后的数据框,包含结构和Order Block相关列
|
108
90
|
"""
|
109
91
|
# 首先构建基础结构
|
110
|
-
df = self.build_struct(df,
|
92
|
+
df = self.build_struct(df, is_struct_body_break)
|
111
93
|
|
112
94
|
check_columns = [self.HIGH_COL, self.LOW_COL, self.CLOSE_COL]
|
113
95
|
self.check_columns(df, check_columns)
|
@@ -256,22 +238,30 @@ class SMCOrderBlock(SMCStruct):
|
|
256
238
|
# df.at[i, self.OB_START_TS_COL] = df.loc[index, self.TIMESTAMP_COL]
|
257
239
|
df.at[index, self.OB_ATR] = atr
|
258
240
|
|
259
|
-
def
|
241
|
+
def get_lastest_OB(self, data, trend, start_index=-1):
|
260
242
|
"""
|
261
243
|
获取最新的Order Block
|
262
244
|
|
263
245
|
Args:
|
264
|
-
df:
|
265
|
-
|
246
|
+
df: 数据框
|
247
|
+
trend: 趋势,"Bullish" 或 "Bearish"
|
266
248
|
|
267
249
|
Returns:
|
268
250
|
最新的Order Block信息或None
|
269
251
|
"""
|
270
252
|
# 获取prd范围内的数据
|
271
|
-
|
253
|
+
df = (
|
254
|
+
data.copy()
|
255
|
+
if start_index == -1
|
256
|
+
else data.copy().iloc[start_index :]
|
257
|
+
)
|
258
|
+
|
259
|
+
# 检查数据中是否包含必要的列
|
260
|
+
check_columns = [self.OB_DIRECTION_COL]
|
261
|
+
self.check_columns(df, check_columns)
|
272
262
|
|
273
263
|
# 筛选有效OB且在prd范围内的数据
|
274
|
-
mask = df[self.OB_DIRECTION_COL]
|
264
|
+
mask = df[self.OB_DIRECTION_COL] == trend
|
275
265
|
valid_obs = df[mask]
|
276
266
|
|
277
267
|
if not valid_obs.empty:
|
@@ -283,6 +273,8 @@ class SMCOrderBlock(SMCStruct):
|
|
283
273
|
self.OB_MID_COL: last_ob[self.OB_MID_COL],
|
284
274
|
self.OB_VOLUME_COL: last_ob[self.OB_VOLUME_COL],
|
285
275
|
self.OB_DIRECTION_COL: last_ob[self.OB_DIRECTION_COL],
|
276
|
+
self.OB_ATR: last_ob[self.OB_ATR],
|
277
|
+
self.OB_WAS_CROSSED: last_ob[self.OB_WAS_CROSSED],
|
286
278
|
}
|
287
279
|
|
288
280
|
return None
|
core/smc/SMCPDArray.py
CHANGED
@@ -36,8 +36,6 @@ class SMCPDArray(SMCFVG,SMCOrderBlock):
|
|
36
36
|
if start_index == -1
|
37
37
|
else struct.copy().iloc[max(0, start_index - 1) :]
|
38
38
|
)
|
39
|
-
# check_columns = [self.HIGH_COL, self.LOW_COL, self.CLOSE_COL]
|
40
|
-
# self.check_columns(df, check_columns)
|
41
39
|
|
42
40
|
df_FVGs = self.find_FVGs(df, side)
|
43
41
|
# self.logger.info(f"fvgs:\n{df_FVGs[['timestamp', self.FVG_SIDE, self.FVG_TOP, self.FVG_BOT, self.FVG_WAS_BALANCED]]}")
|
core/smc/SMCStruct.py
CHANGED
@@ -4,9 +4,12 @@ from core.utils.OPTools import OPTools
|
|
4
4
|
from core.smc.SMCBase import SMCBase
|
5
5
|
|
6
6
|
class SMCStruct(SMCBase):
|
7
|
+
BULLISH_TREND = 'Bullish'
|
8
|
+
BEARISH_TREND = 'Bearish'
|
7
9
|
STRUCT_COL = "struct"
|
8
10
|
STRUCT_HIGH_COL = "struct_high"
|
9
11
|
STRUCT_LOW_COL = "struct_low"
|
12
|
+
STRUCT_MID_COL = "struct_mid"
|
10
13
|
STRUCT_HIGH_INDEX_COL = "struct_high_index"
|
11
14
|
STRUCT_LOW_INDEX_COL = "struct_low_index"
|
12
15
|
STRUCT_DIRECTION_COL = "struct_direction"
|
@@ -16,10 +19,9 @@ class SMCStruct(SMCBase):
|
|
16
19
|
def __init__(self):
|
17
20
|
super().__init__()
|
18
21
|
self.logger = logging.getLogger(__name__)
|
19
|
-
|
20
|
-
|
22
|
+
|
21
23
|
|
22
|
-
def build_struct(self, data,
|
24
|
+
def build_struct(self, data, is_struct_body_break=True):
|
23
25
|
"""处理价格结构,识别高低点突破和结构方向
|
24
26
|
|
25
27
|
Args:
|
@@ -30,7 +32,8 @@ class SMCStruct(SMCBase):
|
|
30
32
|
Returns:
|
31
33
|
处理后的数据框,包含结构相关列
|
32
34
|
"""
|
33
|
-
|
35
|
+
# 复制数据并去掉最后一条记录,因为最后一条记录不是完成状态的K线
|
36
|
+
df = data.copy().iloc[:-1]
|
34
37
|
check_columns = [self.HIGH_COL, self.LOW_COL, self.CLOSE_COL]
|
35
38
|
self.check_columns(df, check_columns)
|
36
39
|
|
@@ -46,7 +49,7 @@ class SMCStruct(SMCBase):
|
|
46
49
|
self.STRUCT_LOW_COL: self.toDecimal('0.0'), # 结构低点价格初始化为0
|
47
50
|
self.STRUCT_HIGH_INDEX_COL: 0, # 结构高点索引初始化为0
|
48
51
|
self.STRUCT_LOW_INDEX_COL: 0, # 结构低点索引初始化为0
|
49
|
-
self.STRUCT_DIRECTION_COL:
|
52
|
+
self.STRUCT_DIRECTION_COL: None # 结构方向初始化为0
|
50
53
|
}
|
51
54
|
|
52
55
|
# 为每个结构列赋默认值
|
@@ -103,7 +106,7 @@ class SMCStruct(SMCBase):
|
|
103
106
|
if is_low_broken:
|
104
107
|
# 处理低点突破
|
105
108
|
structure = self._handle_structure_break(
|
106
|
-
df, i,
|
109
|
+
df, i, structure,
|
107
110
|
break_type=self.LOW_COL,
|
108
111
|
struct_type='BOS' if structure['direction'] == 1 else 'CHOCH'
|
109
112
|
)
|
@@ -111,7 +114,7 @@ class SMCStruct(SMCBase):
|
|
111
114
|
elif is_high_broken:
|
112
115
|
# 处理高点突破
|
113
116
|
structure = self._handle_structure_break(
|
114
|
-
df, i,
|
117
|
+
df, i, structure,
|
115
118
|
break_type=self.HIGH_COL,
|
116
119
|
struct_type='BOS' if structure['direction'] == 2 else 'CHOCH'
|
117
120
|
)
|
@@ -128,7 +131,7 @@ class SMCStruct(SMCBase):
|
|
128
131
|
|
129
132
|
return df
|
130
133
|
|
131
|
-
def _get_structure_extreme_bar(self, df, bar_index, struct_index,
|
134
|
+
def _get_structure_extreme_bar(self, df, bar_index, struct_index, mode='high'):
|
132
135
|
"""
|
133
136
|
获取结构最高点或最低点
|
134
137
|
:param df: DataFrame数据
|
@@ -199,7 +202,7 @@ class SMCStruct(SMCBase):
|
|
199
202
|
|
200
203
|
return basic_break or direction_break
|
201
204
|
|
202
|
-
def _handle_structure_break(self, df, i,
|
205
|
+
def _handle_structure_break(self, df, i, structure, break_type, struct_type):
|
203
206
|
"""处理结构突破"""
|
204
207
|
is_high_break = break_type == self.HIGH_COL
|
205
208
|
|
@@ -207,7 +210,7 @@ class SMCStruct(SMCBase):
|
|
207
210
|
|
208
211
|
# 获取新的极值点
|
209
212
|
extreme_idx = self._get_structure_extreme_bar(
|
210
|
-
df, i, struct_start,
|
213
|
+
df, i, struct_start,
|
211
214
|
mode=self.LOW_COL if is_high_break else self.HIGH_COL
|
212
215
|
)
|
213
216
|
|
@@ -231,8 +234,9 @@ class SMCStruct(SMCBase):
|
|
231
234
|
})
|
232
235
|
|
233
236
|
# 更新DataFrame结构信息
|
234
|
-
df.at[i,
|
235
|
-
df.at[i,
|
237
|
+
# df.at[i, self.STRUCT_DIRECTION_COL] = new_structure['direction']
|
238
|
+
df.at[i, self.STRUCT_DIRECTION_COL] = self.BULLISH_TREND if is_high_break else self.BEARISH_TREND
|
239
|
+
df.at[i, self.STRUCT_COL] = f"{self.BULLISH_TREND if is_high_break else self.BEARISH_TREND}_{struct_type}"
|
236
240
|
|
237
241
|
return new_structure
|
238
242
|
|
@@ -261,18 +265,22 @@ class SMCStruct(SMCBase):
|
|
261
265
|
df.at[i, self.STRUCT_HIGH_INDEX_COL] = structure[self.HIGH_START_COL]
|
262
266
|
df.at[i, self.STRUCT_LOW_INDEX_COL] = structure[self.LOW_START_COL]
|
263
267
|
|
264
|
-
def get_last_struct(self, df
|
268
|
+
def get_last_struct(self, df):
|
265
269
|
"""
|
266
270
|
获取最新的结构
|
267
271
|
"""
|
268
|
-
|
269
|
-
#
|
272
|
+
check_columns = [self.STRUCT_COL]
|
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()
|
270
280
|
|
271
|
-
# 获取prd范围内的数据
|
272
|
-
start_idx = max(0, len(data) - 1 - prd)
|
273
281
|
# 筛选有效结构且在prd范围内的数据
|
274
282
|
last_struct = None
|
275
|
-
mask = data[self.STRUCT_COL].notna()
|
283
|
+
mask = data[self.STRUCT_COL].notna()
|
276
284
|
valid_structs = data[ mask ]
|
277
285
|
if not valid_structs.empty:
|
278
286
|
# 获取最近的结构
|
@@ -281,6 +289,7 @@ class SMCStruct(SMCBase):
|
|
281
289
|
self.STRUCT_COL: last_struct[self.STRUCT_COL],
|
282
290
|
self.STRUCT_HIGH_COL: last_struct[self.STRUCT_HIGH_COL],
|
283
291
|
self.STRUCT_LOW_COL: last_struct[self.STRUCT_LOW_COL],
|
292
|
+
self.STRUCT_MID_COL: (last_struct[self.STRUCT_HIGH_COL] + last_struct[self.STRUCT_LOW_COL]) / 2,
|
284
293
|
self.STRUCT_HIGH_INDEX_COL: last_struct[self.STRUCT_HIGH_INDEX_COL],
|
285
294
|
self.STRUCT_LOW_INDEX_COL: last_struct[self.STRUCT_LOW_INDEX_COL],
|
286
295
|
self.STRUCT_DIRECTION_COL: last_struct[self.STRUCT_DIRECTION_COL]
|
@@ -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=QtqlW1oooYVA7CG5ld5X0Q5twX1XCELO118IlMUhX6M,2974
|
6
|
+
core/smc/SMCLiquidity.py,sha256=EkfyBVXmAiRihof6w3YJvRHlbwUtqag8S6aVrV_XWvc,8100
|
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=BIp5CIipLtOq-Z7aXhHK8KwjdPZzg2_7TVANirO-HlY,12337
|
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.7.dist-info/METADATA,sha256=tLNUyBlVeEkAKu1QyHltTgtvMmGUH_OUuOgjTNmGq3I,1953
|
13
|
+
openfund_core-1.0.7.dist-info/WHEEL,sha256=fGIA9gx4Qxk2KDKeNJCbOEwSrmLtjWCwzBz351GyrPQ,88
|
14
|
+
openfund_core-1.0.7.dist-info/entry_points.txt,sha256=g8GUw3cyKFtcG5VWs8geU5VBLqiWr59GElqERuH8zD0,48
|
15
|
+
openfund_core-1.0.7.dist-info/RECORD,,
|
@@ -1,15 +0,0 @@
|
|
1
|
-
core/Exchange.py,sha256=_nAQy0frzaY4LHWwmDvJPmjX2BNsBz-fAPi1cPTpPRc,10799
|
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=hdY9X_9xrlbUGQGyXjzf-Un-XWgf3QihSWVgzefyP18,10378
|
8
|
-
core/smc/SMCPDArray.py,sha256=yf-ZVabQhe1xcYz2gsETHpTJCSsMiUyPVQ6DdAiUd4U,2961
|
9
|
-
core/smc/SMCStruct.py,sha256=m_LsVlwGXZjDLovSO1Swvvx5JLCsJ5VYwvP-CAM3KzY,11926
|
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.1.dist-info/METADATA,sha256=9PvQQtpzuz4LG5hQFaXJ83vpOaz01DTz37NRH9Fkf0E,1953
|
13
|
-
openfund_core-1.0.1.dist-info/WHEEL,sha256=IYZQI976HJqqOpQU6PHkJ8fb3tMNBFjg-Cn-pwAbaFM,88
|
14
|
-
openfund_core-1.0.1.dist-info/entry_points.txt,sha256=g8GUw3cyKFtcG5VWs8geU5VBLqiWr59GElqERuH8zD0,48
|
15
|
-
openfund_core-1.0.1.dist-info/RECORD,,
|
File without changes
|