openfund-core 1.0.1__py3-none-any.whl → 1.0.5__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 +298 -41
- core/smc/SMCOrderBlock.py +19 -27
- core/smc/SMCPDArray.py +0 -2
- core/smc/SMCStruct.py +24 -18
- {openfund_core-1.0.1.dist-info → openfund_core-1.0.5.dist-info}/METADATA +1 -1
- {openfund_core-1.0.1.dist-info → openfund_core-1.0.5.dist-info}/RECORD +8 -8
- {openfund_core-1.0.1.dist-info → openfund_core-1.0.5.dist-info}/WHEEL +0 -0
- {openfund_core-1.0.1.dist-info → openfund_core-1.0.5.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)
|
@@ -133,8 +142,171 @@ class Exchange:
|
|
133
142
|
self.logger.warning(f"{symbol} 取消挂单失败,正在进行第{retry_count}次重试: {str(e)}")
|
134
143
|
time.sleep(0.1) # 重试前等待0.1秒
|
135
144
|
|
145
|
+
def cancel_all_algo_orders(self, symbol, attachType=None) -> bool:
|
146
|
+
"""_summary_
|
136
147
|
|
137
|
-
|
148
|
+
Args:
|
149
|
+
symbol (_type_): _description_
|
150
|
+
attachType (_type_, optional): "TP"|"SL". Defaults to None.
|
151
|
+
"""
|
152
|
+
|
153
|
+
params = {
|
154
|
+
"ordType": "conditional",
|
155
|
+
}
|
156
|
+
try:
|
157
|
+
orders = self.fetch_open_orders(symbol=symbol,params=params)
|
158
|
+
except Exception as e:
|
159
|
+
error_message = f"!!{symbol} : Error fetching open orders: {e}"
|
160
|
+
self.logger.error(error_message)
|
161
|
+
raise Exception(error_message)
|
162
|
+
|
163
|
+
|
164
|
+
if len(orders) == 0:
|
165
|
+
self.logger.debug(f"{symbol} 未设置策略订单列表。")
|
166
|
+
return True
|
167
|
+
|
168
|
+
algo_ids = []
|
169
|
+
if attachType and attachType == 'SL':
|
170
|
+
algo_ids = [order['id'] for order in orders if order['stopLossPrice'] and order['stopLossPrice'] > 0.0 ]
|
171
|
+
elif attachType and attachType == 'TP':
|
172
|
+
algo_ids = [order['id'] for order in orders if order['takeProfitPrice'] and order['takeProfitPrice'] > 0.0]
|
173
|
+
else :
|
174
|
+
algo_ids = [order['id'] for order in orders ]
|
175
|
+
|
176
|
+
if len(algo_ids) == 0 :
|
177
|
+
self.logger.debug(f"{symbol} 未设置策略订单列表。")
|
178
|
+
return True
|
179
|
+
|
180
|
+
max_retries = 3
|
181
|
+
retry_count = 0
|
182
|
+
|
183
|
+
while retry_count < max_retries:
|
184
|
+
try:
|
185
|
+
params = {
|
186
|
+
"algoId": algo_ids,
|
187
|
+
"trigger": 'trigger'
|
188
|
+
}
|
189
|
+
rs = self.exchange.cancel_orders(ids=algo_ids, symbol=symbol, params=params)
|
190
|
+
|
191
|
+
return len(rs) > 0
|
192
|
+
|
193
|
+
except Exception as e:
|
194
|
+
retry_count += 1
|
195
|
+
if retry_count == max_retries:
|
196
|
+
error_message = f"!!{symbol} : Error cancelling order {algo_ids}: {e}"
|
197
|
+
self.logger.error(error_message)
|
198
|
+
raise Exception(error_message)
|
199
|
+
|
200
|
+
self.logger.warning(f"{symbol} : Error cancelling order {algo_ids}: {str(e)}")
|
201
|
+
time.sleep(0.1) # 重试前等待0.1秒
|
202
|
+
def place_algo_orders(self, symbol, position, price: Decimal, order_type, sl_or_tp='SL', params={}) -> bool:
|
203
|
+
"""
|
204
|
+
下单
|
205
|
+
Args:
|
206
|
+
symbol: 交易对
|
207
|
+
position: 仓位
|
208
|
+
price: 下单价格
|
209
|
+
order_type: 订单类型
|
210
|
+
"""
|
211
|
+
# 计算下单数量
|
212
|
+
amount = abs(position[self.CONTRACTS_KEY])
|
213
|
+
|
214
|
+
if amount <= 0:
|
215
|
+
self.logger.warning(f"{symbol}: amount is 0 for {symbol}")
|
216
|
+
return
|
217
|
+
|
218
|
+
# 止损单逻辑
|
219
|
+
adjusted_price = self.format_price(symbol, price)
|
220
|
+
|
221
|
+
# 默认市价止损,委托价格为-1时,执行市价止损。
|
222
|
+
sl_params = {
|
223
|
+
**params,
|
224
|
+
'slTriggerPx':adjusted_price ,
|
225
|
+
'slOrdPx':'-1', # 委托价格为-1时,执行市价止损
|
226
|
+
# 'slOrdPx' : adjusted_price,
|
227
|
+
'slTriggerPxType':'last',
|
228
|
+
'tdMode':position['marginMode'],
|
229
|
+
'sz': str(amount),
|
230
|
+
'cxlOnClosePos': True,
|
231
|
+
'reduceOnly':True,
|
232
|
+
}
|
233
|
+
|
234
|
+
tp_params = {
|
235
|
+
**params,
|
236
|
+
'tpTriggerPx':adjusted_price,
|
237
|
+
'tpOrdPx' : adjusted_price,
|
238
|
+
'tpOrdKind': 'condition',
|
239
|
+
'tpTriggerPxType':'last',
|
240
|
+
'tdMode':position['marginMode'],
|
241
|
+
'sz': str(amount),
|
242
|
+
'cxlOnClosePos': True,
|
243
|
+
'reduceOnly':True
|
244
|
+
}
|
245
|
+
|
246
|
+
order_params = sl_params if sl_or_tp == 'SL' else tp_params
|
247
|
+
# order_params.update(params)
|
248
|
+
|
249
|
+
if order_type == 'limit' and sl_or_tp =='SL':
|
250
|
+
order_params['slOrdPx'] = adjusted_price
|
251
|
+
|
252
|
+
orderSide = self.BUY_SIDE if position[self.SIDE_KEY] == self.SHORT_KEY else self.SELL_SIDE # 和持仓反向相反下单
|
253
|
+
|
254
|
+
order = {
|
255
|
+
'symbol': symbol,
|
256
|
+
'side': orderSide,
|
257
|
+
'type': order_type,
|
258
|
+
'amount': amount,
|
259
|
+
'price': adjusted_price,
|
260
|
+
'params': order_params
|
261
|
+
}
|
262
|
+
|
263
|
+
max_retries = 3
|
264
|
+
retry_count = 0
|
265
|
+
self.logger.debug(f"{symbol} : Pre Algo Order placed: {order} ")
|
266
|
+
while retry_count < max_retries:
|
267
|
+
try:
|
268
|
+
|
269
|
+
self.exchange.create_order(
|
270
|
+
**order
|
271
|
+
# symbol=symbol,
|
272
|
+
# type=order_type,
|
273
|
+
# price=adjusted_price,
|
274
|
+
# side=orderSide,
|
275
|
+
# amount=amount,
|
276
|
+
# params=order_params
|
277
|
+
)
|
278
|
+
|
279
|
+
break
|
280
|
+
|
281
|
+
except ccxt.NetworkError as e:
|
282
|
+
# 处理网络相关错误
|
283
|
+
retry_count += 1
|
284
|
+
self.logger.warning(f"{symbol} : 设置止盈止损时发生网络错误,正在进行第{retry_count}次重试: {str(e)}")
|
285
|
+
time.sleep(0.1) # 重试前等待1秒
|
286
|
+
continue
|
287
|
+
except ccxt.ExchangeError as e:
|
288
|
+
# 处理交易所API相关错误
|
289
|
+
retry_count += 1
|
290
|
+
self.logger.warning(f"{symbol} : 设置止盈止损单时发生交易所错误,正在进行第{retry_count}次重试: {str(e)}")
|
291
|
+
time.sleep(0.1)
|
292
|
+
continue
|
293
|
+
except Exception as e:
|
294
|
+
# 处理其他未预期的错误
|
295
|
+
retry_count += 1
|
296
|
+
self.logger.warning(f"{symbol} : 设置止盈止损单时发生未知错误,正在进行第{retry_count}次重试: {str(e)}")
|
297
|
+
time.sleep(0.1)
|
298
|
+
continue
|
299
|
+
|
300
|
+
if retry_count >= max_retries:
|
301
|
+
# 重试次数用完仍未成功设置止损单
|
302
|
+
error_message = f"!! {symbol}: 设置止盈止损单时重试次数用完仍未成功设置成功。 "
|
303
|
+
self.logger.error(error_message)
|
304
|
+
raise Exception(error_message)
|
305
|
+
self.logger.debug(f"{symbol} : --------- ++ Order placed done. --------")
|
306
|
+
return True
|
307
|
+
|
308
|
+
|
309
|
+
def place_order(self, symbol, price: Decimal, amount_usdt, side, leverage=20, order_type='limit', params={}) -> bool:
|
138
310
|
"""
|
139
311
|
下单
|
140
312
|
Args:
|
@@ -147,41 +319,46 @@ class Exchange:
|
|
147
319
|
# 格式化价格
|
148
320
|
adjusted_price = self.format_price(symbol, price)
|
149
321
|
|
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)
|
322
|
+
if amount_usdt <= 0:
|
323
|
+
self.logger.warning(f"{symbol}: amount_usdt must be greater than 0")
|
324
|
+
return
|
159
325
|
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
326
|
+
pos_side = self.LONG_KEY if side == self.BUY_SIDE else self.SHORT_KEY
|
327
|
+
|
328
|
+
# 设置杠杆
|
329
|
+
self.set_leverage(symbol=symbol, leverage=leverage, mgnMode='isolated',posSide=pos_side)
|
330
|
+
# 20250220 SWAP类型计算合约数量
|
331
|
+
contract_size = self.convert_contract(symbol=symbol, price = OPTools.toDecimal(adjusted_price) ,amount=amount_usdt)
|
332
|
+
|
333
|
+
order_params = {
|
334
|
+
**params,
|
335
|
+
"tdMode": 'isolated',
|
336
|
+
"side": side,
|
337
|
+
"ordType": order_type,
|
338
|
+
"sz": contract_size,
|
339
|
+
"px": adjusted_price
|
340
|
+
}
|
341
|
+
|
342
|
+
# # 模拟盘(demo_trading)需要 posSide
|
343
|
+
# if self.is_demo_trading == 1 :
|
344
|
+
# params["posSide"] = pos_side
|
168
345
|
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
346
|
+
order = {
|
347
|
+
'symbol': symbol,
|
348
|
+
'side': side,
|
349
|
+
'type': order_type,
|
350
|
+
'amount': contract_size,
|
351
|
+
'price': adjusted_price,
|
352
|
+
'params': order_params
|
353
|
+
}
|
354
|
+
|
355
|
+
max_retries = 3
|
356
|
+
retry_count = 0
|
357
|
+
|
358
|
+
while retry_count < max_retries:
|
174
359
|
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
360
|
# 使用ccxt创建订单
|
184
|
-
self.logger.debug(f"Pre Order placed: {order} ")
|
361
|
+
self.logger.debug(f"{symbol} : Pre Order placed: {order} ")
|
185
362
|
order_result = self.exchange.create_order(
|
186
363
|
**order
|
187
364
|
# symbol=symbol,
|
@@ -191,13 +368,31 @@ class Exchange:
|
|
191
368
|
# price=float(adjusted_price),
|
192
369
|
# params=params
|
193
370
|
)
|
194
|
-
|
371
|
+
except ccxt.NetworkError as e:
|
372
|
+
# 处理网络相关错误
|
373
|
+
retry_count += 1
|
374
|
+
self.logger.warning(f"{symbol} : 设置下单时发生网络错误,正在进行第{retry_count}次重试: {str(e)}")
|
375
|
+
time.sleep(0.1) # 重试前等待1秒
|
376
|
+
continue
|
377
|
+
except ccxt.ExchangeError as e:
|
378
|
+
# 处理交易所API相关错误
|
379
|
+
retry_count += 1
|
380
|
+
self.logger.warning(f"{symbol} : 设置下单时发生交易所错误,正在进行第{retry_count}次重试: {str(e)}")
|
381
|
+
time.sleep(0.1)
|
382
|
+
continue
|
195
383
|
except Exception as e:
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
384
|
+
# 处理其他未预期的错误
|
385
|
+
retry_count += 1
|
386
|
+
self.logger.warning(f"{symbol} : 设置下单时发生未知错误,正在进行第{retry_count}次重试: {str(e)}")
|
387
|
+
time.sleep(0.1)
|
388
|
+
continue
|
389
|
+
if retry_count >= max_retries:
|
390
|
+
# 重试次数用完仍未成功设置止损单
|
391
|
+
error_message = f"!! {symbol}: 设置止盈止损单时重试次数用完仍未成功设置成功。 "
|
392
|
+
self.logger.error(error_message)
|
393
|
+
raise Exception(error_message)
|
394
|
+
self.logger.debug(f"{symbol} : --------- ++ Order placed done. --------")
|
395
|
+
return True
|
201
396
|
|
202
397
|
def fetch_position(self, symbol):
|
203
398
|
"""_summary_
|
@@ -215,8 +410,8 @@ class Exchange:
|
|
215
410
|
while retry_count < max_retries:
|
216
411
|
try:
|
217
412
|
position = self.exchange.fetch_position(symbol=symbol)
|
218
|
-
if position
|
219
|
-
self.logger.debug(f"{symbol} 有持仓合约数: {position['contracts']}")
|
413
|
+
if position :
|
414
|
+
# self.logger.debug(f"{symbol} 有持仓合约数: {position['contracts']}")
|
220
415
|
return position
|
221
416
|
return None
|
222
417
|
except Exception as e:
|
@@ -228,7 +423,69 @@ class Exchange:
|
|
228
423
|
|
229
424
|
self.logger.warning(f"{symbol} 检查持仓失败,正在进行第{retry_count}次重试: {str(e)}")
|
230
425
|
time.sleep(0.1) # 重试前等待0.1秒
|
231
|
-
|
426
|
+
|
427
|
+
def fetch_positions(self):
|
428
|
+
"""_summary_
|
429
|
+
Returns:
|
430
|
+
_type_: _description_
|
431
|
+
"""
|
432
|
+
max_retries = 3
|
433
|
+
retry_count = 0
|
434
|
+
|
435
|
+
while retry_count < max_retries:
|
436
|
+
try:
|
437
|
+
positions = self.exchange.fetch_positions()
|
438
|
+
return positions
|
439
|
+
except Exception as e:
|
440
|
+
retry_count += 1
|
441
|
+
if retry_count == max_retries:
|
442
|
+
error_message = f"!! 获取持仓列表失败(重试{retry_count}次): {str(e)}"
|
443
|
+
self.logger.error(error_message)
|
444
|
+
raise Exception(error_message)
|
445
|
+
|
446
|
+
self.logger.warning(f"获取持仓列表失败,正在进行第{retry_count}次重试: {str(e)}")
|
447
|
+
time.sleep(0.1) # 重试前等待0.1秒
|
448
|
+
|
449
|
+
def fetch_open_orders(self,symbol,params={}):
|
450
|
+
max_retries = 3
|
451
|
+
retry_count = 0
|
452
|
+
|
453
|
+
while retry_count < max_retries:
|
454
|
+
try:
|
455
|
+
orders = self.exchange.fetch_open_orders(symbol=symbol,params=params)
|
456
|
+
return orders
|
457
|
+
|
458
|
+
except Exception as e:
|
459
|
+
retry_count += 1
|
460
|
+
if retry_count == max_retries:
|
461
|
+
error_message = f"{symbol} : fetching open orders(retry {retry_count} times): {str(e)}"
|
462
|
+
self.logger.error(error_message)
|
463
|
+
raise Exception(error_message)
|
464
|
+
|
465
|
+
self.logger.warning(f"{symbol} : Error fetching open orders: {str(e)}")
|
466
|
+
time.sleep(0.1) # 重试前等待0.1秒
|
467
|
+
def get_market_price(self, symbol) -> Decimal:
|
468
|
+
"""
|
469
|
+
获取最新价格
|
470
|
+
Args:
|
471
|
+
symbol: 交易对
|
472
|
+
"""
|
473
|
+
max_retries = 3
|
474
|
+
retry_count = 0
|
475
|
+
|
476
|
+
while retry_count < max_retries:
|
477
|
+
try:
|
478
|
+
ticker = self.exchange.fetch_ticker(symbol)
|
479
|
+
if ticker and 'last' in ticker:
|
480
|
+
return OPTools.toDecimal(ticker['last'])
|
481
|
+
else:
|
482
|
+
raise Exception(f"{symbol} : Unexpected response structure or missing 'last' price")
|
483
|
+
except Exception as e:
|
484
|
+
retry_count += 1
|
485
|
+
if retry_count == max_retries:
|
486
|
+
error_message = f"{symbol} 获取最新价格失败(重试{retry_count}次): {str(e)}"
|
487
|
+
self.logger.error(error_message)
|
488
|
+
raise Exception(error_message)
|
232
489
|
|
233
490
|
def get_historical_klines(self, symbol, bar='15m', limit=300, after:str=None, params={}):
|
234
491
|
"""
|
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,19 @@ 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
|
+
data = self.build_struct(df=df)
|
275
|
+
else:
|
276
|
+
data = df.copy()
|
270
277
|
|
271
|
-
# 获取prd范围内的数据
|
272
|
-
start_idx = max(0, len(data) - 1 - prd)
|
273
278
|
# 筛选有效结构且在prd范围内的数据
|
274
279
|
last_struct = None
|
275
|
-
mask = data[self.STRUCT_COL].notna()
|
280
|
+
mask = data[self.STRUCT_COL].notna()
|
276
281
|
valid_structs = data[ mask ]
|
277
282
|
if not valid_structs.empty:
|
278
283
|
# 获取最近的结构
|
@@ -281,6 +286,7 @@ class SMCStruct(SMCBase):
|
|
281
286
|
self.STRUCT_COL: last_struct[self.STRUCT_COL],
|
282
287
|
self.STRUCT_HIGH_COL: last_struct[self.STRUCT_HIGH_COL],
|
283
288
|
self.STRUCT_LOW_COL: last_struct[self.STRUCT_LOW_COL],
|
289
|
+
self.STRUCT_MID_COL: (last_struct[self.STRUCT_HIGH_COL] + last_struct[self.STRUCT_LOW_COL]) / 2,
|
284
290
|
self.STRUCT_HIGH_INDEX_COL: last_struct[self.STRUCT_HIGH_INDEX_COL],
|
285
291
|
self.STRUCT_LOW_INDEX_COL: last_struct[self.STRUCT_LOW_INDEX_COL],
|
286
292
|
self.STRUCT_DIRECTION_COL: last_struct[self.STRUCT_DIRECTION_COL]
|
@@ -1,15 +1,15 @@
|
|
1
|
-
core/Exchange.py,sha256=
|
1
|
+
core/Exchange.py,sha256=eanGV-8ZLDFxP2tV6xumo8c68yqFTUeseJA_dHMhYb0,20785
|
2
2
|
core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
3
3
|
core/main.py,sha256=E-VZzem7-0_J6EmOo9blLPokc5MRcgjqCbqAvbkPnWI,630
|
4
4
|
core/smc/SMCBase.py,sha256=epRC5bWDymx7ZMIhn_bVJRjvBHItt6BCnYASO2fhSDg,4302
|
5
5
|
core/smc/SMCFVG.py,sha256=QtqlW1oooYVA7CG5ld5X0Q5twX1XCELO118IlMUhX6M,2974
|
6
6
|
core/smc/SMCLiquidity.py,sha256=lZt2IQk3TWaT-nA7he57dUxPdLEWW61jRZWLAzOTat0,119
|
7
|
-
core/smc/SMCOrderBlock.py,sha256=
|
8
|
-
core/smc/SMCPDArray.py,sha256=
|
9
|
-
core/smc/SMCStruct.py,sha256=
|
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
10
|
core/smc/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
11
11
|
core/utils/OPTools.py,sha256=tJ1Jq_Caab6OWaX12xn4_g9ryf98Rm5I1zsJEEU8NIQ,1002
|
12
|
-
openfund_core-1.0.
|
13
|
-
openfund_core-1.0.
|
14
|
-
openfund_core-1.0.
|
15
|
-
openfund_core-1.0.
|
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
|
File without changes
|