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 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
- def place_order(self, symbol, price: Decimal, amount_usdt, side, leverage=20, order_type='limit'):
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 > 0:
151
- if side == 'buy':
152
- pos_side = 'long'
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
- params = {
161
-
162
- "tdMode": 'isolated',
163
- "side": side,
164
- "ordType": order_type,
165
- "sz": contract_size,
166
- "px": adjusted_price
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
- # # 模拟盘(demo_trading)需要 posSide
170
- # if self.is_demo_trading == 1 :
171
- # params["posSide"] = pos_side
172
-
173
- # self.logger.debug(f"---- Order placed params: {params}")
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
- # self.logger.debug(f"{symbol} ++ Order placed rs : {order_result}")
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
- error_message = f"{symbol} Failed to place order: {e}"
197
- self.logger.error(error_message)
198
- raise Exception(error_message)
199
-
200
- self.logger.debug(f"--------- ++ {symbol} Order placed done! --------")
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 and position['contracts'] > 0:
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, window=20, is_struct_body_break=True, atr_multiplier=0.6
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, window, is_struct_body_break)
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 get_last_ob(self, df, prd=-1):
241
+ def get_lastest_OB(self, data, trend, start_index=-1):
260
242
  """
261
243
  获取最新的Order Block
262
244
 
263
245
  Args:
264
- df: 包含OB信息的数据框
265
- prd: 回溯周期,-1表示全部
246
+ df: 数据框
247
+ trend: 趋势,"Bullish" 或 "Bearish"
266
248
 
267
249
  Returns:
268
250
  最新的Order Block信息或None
269
251
  """
270
252
  # 获取prd范围内的数据
271
- start_idx = max(0, len(df) - 1 - prd) if prd > 0 else 0
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].notna() & (df.index >= start_idx)
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, window=10, is_struct_body_break=True):
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
- df = data.copy()
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: 0 # 结构方向初始化为0
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, window, structure,
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, window, structure,
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, lookback=10, mode='high'):
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, window, structure, break_type, struct_type):
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, window,
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, 'struct_direction'] = new_structure['direction']
235
- df.at[i, 'struct'] = f"{'Bullish' if is_high_break else 'Bearish'}_{struct_type}"
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, prd=-1):
268
+ def get_last_struct(self, df):
265
269
  """
266
270
  获取最新的结构
267
271
  """
268
- data = self.build_struct(df=df, window=10)
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() & (data.index >= start_idx) if prd > 0 else 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,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: openfund-core
3
- Version: 1.0.1
3
+ Version: 1.0.5
4
4
  Summary: Openfund-core.
5
5
  Requires-Python: >=3.9,<4.0
6
6
  Classifier: Programming Language :: Python :: 3
@@ -1,15 +1,15 @@
1
- core/Exchange.py,sha256=_nAQy0frzaY4LHWwmDvJPmjX2BNsBz-fAPi1cPTpPRc,10799
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=hdY9X_9xrlbUGQGyXjzf-Un-XWgf3QihSWVgzefyP18,10378
8
- core/smc/SMCPDArray.py,sha256=yf-ZVabQhe1xcYz2gsETHpTJCSsMiUyPVQ6DdAiUd4U,2961
9
- core/smc/SMCStruct.py,sha256=m_LsVlwGXZjDLovSO1Swvvx5JLCsJ5VYwvP-CAM3KzY,11926
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.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,,
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,,