openfund-maker 1.2.13__tar.gz → 1.3.2__tar.gz

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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: openfund-maker
3
- Version: 1.2.13
3
+ Version: 1.3.2
4
4
  Summary: Openfund-maker.
5
5
  Requires-Python: >=3.9,<4.0
6
6
  Classifier: Programming Language :: Python :: 3
@@ -9,7 +9,8 @@ Classifier: Programming Language :: Python :: 3.10
9
9
  Classifier: Programming Language :: Python :: 3.11
10
10
  Classifier: Programming Language :: Python :: 3.12
11
11
  Classifier: Programming Language :: Python :: 3.13
12
- Requires-Dist: TA-Lib (>=0.6.0,<0.7.0)
12
+ Requires-Dist: TA-Lib (>=0.5.1,<0.6.0)
13
+ Requires-Dist: apscheduler (>=3.11.0,<4.0.0)
13
14
  Requires-Dist: ccxt (>=4.4.26,<5.0.0)
14
15
  Requires-Dist: pandas (>=2.2.3,<3.0.0)
15
16
  Requires-Dist: pyyaml (>=6.0.2,<7.0.0)
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "openfund-maker"
3
- version = "1.2.13"
3
+ version = "1.3.2"
4
4
  description = "Openfund-maker."
5
5
  authors = []
6
6
  readme = "README.md"
@@ -12,8 +12,9 @@ packages = [
12
12
  python = "^3.9"
13
13
  ccxt = "^4.4.26"
14
14
  pandas = "^2.2.3"
15
- TA-Lib = "^0.6.0"
15
+ TA-Lib = "^0.5.1"
16
16
  pyyaml = "^6.0.2"
17
+ apscheduler = "^3.11.0"
17
18
 
18
19
 
19
20
  [tool.poetry.group.dev.dependencies]
@@ -15,7 +15,7 @@ class MACDOrdergBot(ThreeLineOrdergBot):
15
15
  def __init__(self, config, platform_config, feishu_webhook=None,logger=None):
16
16
  super().__init__(config, platform_config, feishu_webhook, logger)
17
17
 
18
- def judge_order_side(self, symbol, pair_config, macd:pd.DataFrame) -> str:
18
+ def judge_order_side(self, symbol, pair_config, kLines) -> str:
19
19
  order_side = 'none'
20
20
 
21
21
  '''
@@ -31,8 +31,15 @@ class MACDOrdergBot(ThreeLineOrdergBot):
31
31
  last_down_crosses = []
32
32
  other_crosses = []
33
33
  all_cross = []
34
- # 遍历MACD数据 FIXME macd 要有根数限制,太久之前的无效
35
- # 从最新数据开始往前遍历
34
+
35
+ macd = pd.DataFrame(kLines, columns=['timestamp', 'open', 'high', 'low', 'close', 'volume'])
36
+ # 将时间戳转换为日期时间格式
37
+ macd['timestamp'] = pd.to_datetime(macd['timestamp'], unit='ms').dt.strftime('%m-%d %H:%M')
38
+
39
+ # 使用 TA-Lib 计算 MACD
40
+ macd[['macd', 'signal', 'hist']] = pd.DataFrame(ta.MACD(macd['close'], fastperiod=12, slowperiod=26, signalperiod=9)).T
41
+
42
+ # 从最新K(排除最后一根K线可能没走完)开始往前遍历
36
43
  for i in range(len(macd)-1, 2, -1):
37
44
  # 检查是否发生死叉(MACD从上方穿过Signal)
38
45
  if (macd.iloc[i-1]['macd'] <= macd.iloc[i-1]['signal'] and
@@ -116,7 +123,9 @@ class MACDOrdergBot(ThreeLineOrdergBot):
116
123
  # 检查是否有持仓,有持仓不进行下单
117
124
  if self.check_position(symbol=symbol) :
118
125
  self.logger.info(f"{symbol} 有持仓合约,不进行下单。")
126
+ self.logger.info("-" * 60)
119
127
  return
128
+
120
129
  self.cancel_all_orders(symbol=symbol)
121
130
  klines_period = str(pair_config.get('klines_period', '1m'))
122
131
 
@@ -124,20 +133,17 @@ class MACDOrdergBot(ThreeLineOrdergBot):
124
133
  kLines = self.get_historical_klines(symbol=symbol, bar=klines_period)
125
134
  self.logger.debug(f"开始监控 {symbol} : klines {klines_period} - {len(kLines)}")
126
135
 
127
- df = pd.DataFrame(kLines, columns=['timestamp', 'open', 'high', 'low', 'close', 'volume'])
128
- # 将时间戳转换为日期时间格式
129
- df['timestamp'] = pd.to_datetime(df['timestamp'], unit='ms').dt.strftime('%m-%d %H:%M')
130
-
131
- # 使用 TA-Lib 计算 MACD
132
- df[['macd', 'signal', 'hist']] = pd.DataFrame(ta.MACD(df['close'], fastperiod=12, slowperiod=26, signalperiod=9)).T
133
- self.logger.debug(f"{symbol} : MACD Values = \n {df.tail(5)}")
134
- side = self.judge_order_side(symbol, pair_config , df)
136
+
137
+ # self.logger.debug(f"{symbol} : MACD Values = \n {df.tail(5)}")
138
+ side = self.judge_order_side(symbol, pair_config , kLines)
135
139
 
136
140
  long_amount_usdt = pair_config.get('long_amount_usdt', 5)
137
141
  short_amount_usdt = pair_config.get('short_amount_usdt', 5)
138
142
  order_amount_usdt = 5
139
143
  order_type='optimal_limit_ioc'
140
- order_price = df['close'].iloc[-1]
144
+
145
+ # order_price = df['close'].iloc[-1]
146
+ order_price = float(kLines[-1][4])
141
147
  if side == 'none' :
142
148
  self.logger.debug(f"{symbol} : 没有触发下单条件。")
143
149
  return
@@ -164,13 +170,13 @@ class MACDOrdergBot(ThreeLineOrdergBot):
164
170
  def monitor_klines(self):
165
171
  symbols = list(self.trading_pairs_config.keys()) # 获取所有币对的ID
166
172
  batch_size = 5 # 每批处理的数量
167
- while True:
173
+ # while True:
168
174
 
169
- for i in range(0, len(symbols), batch_size):
170
- batch = symbols[i:i + batch_size]
171
- with ThreadPoolExecutor(max_workers=batch_size) as executor:
172
- futures = [executor.submit(self.process_pair, symbol,self.trading_pairs_config[symbol]) for symbol in batch]
173
- for future in as_completed(futures):
174
- future.result() # Raise any exceptions caught during execution
175
+ for i in range(0, len(symbols), batch_size):
176
+ batch = symbols[i:i + batch_size]
177
+ with ThreadPoolExecutor(max_workers=batch_size) as executor:
178
+ futures = [executor.submit(self.process_pair, symbol,self.trading_pairs_config[symbol]) for symbol in batch]
179
+ for future in as_completed(futures):
180
+ future.result() # Raise any exceptions caught during execution
175
181
 
176
- time.sleep(self.monitor_interval)
182
+ # time.sleep(self.monitor_interval)
@@ -397,9 +397,9 @@ class ThreeLineOrdergBot:
397
397
  except Exception as e:
398
398
  self.logger.error(f"{symbol} Error setting leverage: {e}")
399
399
  #
400
- def check_position(self,symbol) -> bool:
400
+ def check_position(self, symbol) -> bool:
401
401
  """
402
- 检查指定交易对是否有持仓
402
+ 检查指定交易对是否有持仓,失败时最多重试3次
403
403
 
404
404
  Args:
405
405
  symbol: 交易对ID
@@ -407,15 +407,23 @@ class ThreeLineOrdergBot:
407
407
  Returns:
408
408
  bool: 是否有持仓
409
409
  """
410
- try:
411
- position = self.exchange.fetch_position(symbol=symbol)
412
- if position and position['contracts']> 0:
413
- self.logger.debug(f"{symbol} 有持仓合约数: {position['contracts']}")
414
- return True
415
- return False
416
- except Exception as e:
417
- self.logger.error(f"{symbol} 检查持仓失败: {str(e)}")
418
- return False
410
+ max_retries = 3
411
+ retry_count = 0
412
+
413
+ while retry_count < max_retries:
414
+ try:
415
+ position = self.exchange.fetch_position(symbol=symbol)
416
+ if position and position['contracts'] > 0:
417
+ self.logger.debug(f"{symbol} 有持仓合约数: {position['contracts']}")
418
+ return True
419
+ return False
420
+ except Exception as e:
421
+ retry_count += 1
422
+ if retry_count == max_retries:
423
+ self.logger.warning(f"{symbol} 检查持仓失败(重试{retry_count}次): {str(e)}")
424
+ return True
425
+ self.logger.warning(f"{symbol} 检查持仓失败,正在进行第{retry_count}次重试: {str(e)}")
426
+ time.sleep(0.1) # 重试前等待0.1秒
419
427
 
420
428
  def place_order(self,symbol, price, amount_usdt, side,order_type='limit'):
421
429
 
@@ -478,27 +486,42 @@ class ThreeLineOrdergBot:
478
486
  self.logger.error(f"{symbol} Failed to place order: {e}")
479
487
  self.logger.info(f"--------- ++ {symbol} Order placed done! --------")
480
488
 
481
- def cancel_all_orders(self,symbol):
482
- try:
483
- # 获取所有未完成订单
484
- params = {
485
- # 'instId': instId
486
- }
487
- open_orders = self.exchange.fetch_open_orders(symbol=symbol,params=params)
488
-
489
- # 取消每个订单
490
- for order in open_orders:
491
- self.exchange.cancel_order(order['id'], symbol,params=params)
492
-
493
- self.logger.info(f"{symbol} 挂单取消成功.")
494
- except Exception as e:
495
- self.logger.error(f"{symbol} 取消订单失败: {str(e)}")
489
+ def cancel_all_orders(self, symbol):
490
+ max_retries = 3
491
+ retry_count = 0
496
492
 
493
+ while retry_count < max_retries:
494
+ try:
495
+ # 获取所有未完成订单
496
+ params = {
497
+ # 'instId': instId
498
+ }
499
+ open_orders = self.exchange.fetch_open_orders(symbol=symbol, params=params)
500
+
501
+ # 批量取消所有订单
502
+ if open_orders:
503
+ order_ids = [order['id'] for order in open_orders]
504
+ self.exchange.cancel_orders(order_ids, symbol, params=params)
505
+
506
+ self.logger.info(f"{symbol}: {order_ids} 挂单取消成功.")
507
+ else:
508
+ self.logger.info(f"{symbol}: 没有未完成订单.")
509
+ return True
510
+
511
+ except Exception as e:
512
+ retry_count += 1
513
+ if retry_count == max_retries:
514
+ self.logger.warning(f"{symbol} 取消订单失败(重试{retry_count}次): {str(e)}")
515
+ return False
516
+ self.logger.warning(f"{symbol} 取消订单失败,正在进行第{retry_count}次重试: {str(e)}")
517
+ time.sleep(0.1) # 重试前等待0.1秒
518
+
497
519
  def process_pair(self,symbol,pair_config):
498
520
  self.logger.info("=" * 60)
499
521
  # 检查是否有持仓,有持仓不进行下单
500
522
  if self.check_position(symbol=symbol) :
501
523
  self.logger.info(f"{symbol} 有持仓合约,不进行下单。")
524
+ self.logger.info("-" * 60)
502
525
  return
503
526
  # 取消之前的挂单
504
527
  self.cancel_all_orders(symbol=symbol)
@@ -635,7 +658,9 @@ class ThreeLineOrdergBot:
635
658
  self.logger.error(error_message,exc_info=True)
636
659
  traceback.print_exc()
637
660
  self.send_feishu_notification(error_message)
638
- self.logger.info("=" * 60)
661
+
662
+ self.logger.info("-" * 60)
663
+
639
664
  def monitor_klines(self):
640
665
  symbols = list(self.trading_pairs_config.keys()) # 获取所有币对的ID
641
666
  batch_size = 5 # 每批处理的数量
@@ -648,4 +673,4 @@ class ThreeLineOrdergBot:
648
673
  for future in as_completed(futures):
649
674
  future.result() # Raise any exceptions caught during execution
650
675
 
651
- time.sleep(self.monitor_interval)
676
+ # time.sleep(self.monitor_interval)
@@ -0,0 +1,105 @@
1
+ import logging
2
+ import yaml
3
+ from logging.handlers import TimedRotatingFileHandler
4
+ from apscheduler.triggers.interval import IntervalTrigger
5
+ from apscheduler.schedulers.blocking import BlockingScheduler
6
+ from datetime import datetime
7
+
8
+ from maker.WickReversalOrderBot import WickReversalOrderBot
9
+ from maker.ThreeLineOrderBot import ThreeLineOrdergBot
10
+ from maker.MACDOrderBot import MACDOrdergBot
11
+
12
+ def build_logger(log_config) -> logging.Logger:
13
+ # 配置日志
14
+ # log_file = "log/okx_MultiAssetNewTradingBot.log"
15
+ log_file = log_config["file"]
16
+ logger = logging.getLogger(__name__)
17
+ logger.setLevel(log_config["level"])
18
+
19
+ file_handler = TimedRotatingFileHandler(log_file, when='midnight', interval=1, backupCount=7, encoding='utf-8')
20
+ file_handler.suffix = "%Y-%m-%d"
21
+ formatter = logging.Formatter('%(asctime)s - %(name)s - %(lineno)d - %(levelname)s - %(message)s')
22
+ file_handler.setFormatter(formatter)
23
+ logger.addHandler(file_handler)
24
+
25
+ console_handler = logging.StreamHandler()
26
+ console_handler.setFormatter(formatter)
27
+ logger.addHandler(console_handler)
28
+
29
+ return logger
30
+
31
+ def read_config_file(file_path):
32
+ try:
33
+ # 打开 YAML 文件
34
+ with open(file_path, 'r', encoding='utf-8') as file:
35
+ # 使用 yaml.safe_load 方法解析 YAML 文件内容
36
+ data = yaml.safe_load(file)
37
+ return data
38
+ except FileNotFoundError:
39
+ raise Exception(f"文件 {file_path} 未找到。")
40
+ except yaml.YAMLError as e:
41
+ raise Exception(f"解析 {file_path} 文件时出错: {e}")
42
+
43
+ def run_bot(bot, logger):
44
+ try:
45
+
46
+ bot.monitor_klines()
47
+ except Exception as e:
48
+ logger.error(f"执行任务时发生错误: {str(e)}", exc_info=True)
49
+
50
+ def main():
51
+ import importlib.metadata
52
+ version = importlib.metadata.version("openfund-maker")
53
+
54
+ maker_config_path = 'maker_config.yaml'
55
+ config_data = read_config_file(maker_config_path)
56
+
57
+ platform_config = config_data['okx']
58
+ feishu_webhook_url = config_data['feishu_webhook']
59
+ logger = build_logger(config_data["Logger"])
60
+ package_name = __package__ or "maker"
61
+ logger.info(f" ++ {package_name}:{version} is doing...")
62
+ logger.info("开始执行交易任务...")
63
+ bot = MACDOrdergBot(config_data, platform_config, feishu_webhook=feishu_webhook_url, logger=logger)
64
+
65
+ # 获取计划配置
66
+ schedule_config = config_data.get('schedule', {})
67
+ if schedule_config.get('enabled', False):
68
+ scheduler = BlockingScheduler()
69
+
70
+ # 设置每5分钟执行一次的任务,从整点开始
71
+ monitor_interval = int(schedule_config.get('monitor_interval', 4))
72
+
73
+ # 计算下一个整点分钟
74
+ now = datetime.now()
75
+ # 将当前时间的秒和微秒设置为0
76
+ next_run = now.replace(second=59, microsecond=0)
77
+ # 计算下一个周期的开始时间
78
+ current_minute = next_run.minute
79
+ # 向上取整到下一个周期时间点
80
+ next_interval = ((current_minute // monitor_interval) + 1) * monitor_interval -1
81
+ # 如果下一个周期时间点超过60分钟,需要调整为下一个小时的对应分钟数
82
+ if next_interval >= 60:
83
+ next_interval = next_interval % 60
84
+ next_run = next_run.replace(hour=next_run.hour + 1)
85
+ next_run = next_run.replace(minute=next_interval)
86
+
87
+ scheduler.add_job(
88
+ run_bot,
89
+ IntervalTrigger(minutes=monitor_interval),
90
+ args=[bot, logger],
91
+ next_run_time=next_run # 从下一个周期整点开始
92
+ )
93
+
94
+ try:
95
+ logger.info(f"启动定时任务调度器,从 {next_run} 开始每5分钟执行一次...")
96
+ scheduler.start()
97
+ except (KeyboardInterrupt, SystemExit):
98
+ logger.info("程序收到中断信号,正在退出...")
99
+ scheduler.shutdown()
100
+ else:
101
+ # 如果未启用计划,直接运行
102
+ run_bot(bot, logger)
103
+
104
+ if __name__ == "__main__":
105
+ main()
@@ -1,59 +0,0 @@
1
- import logging
2
- import yaml
3
- from logging.handlers import TimedRotatingFileHandler
4
-
5
- from maker.WickReversalOrderBot import WickReversalOrderBot
6
- from maker.ThreeLineOrderBot import ThreeLineOrdergBot
7
- from maker.MACDOrderBot import MACDOrdergBot
8
- def build_logger(log_config) -> logging.Logger:
9
- # 配置日志
10
- # log_file = "log/okx_MultiAssetNewTradingBot.log"
11
- log_file = log_config["file"]
12
- logger = logging.getLogger(__name__)
13
- logger.setLevel(log_config["level"])
14
-
15
- file_handler = TimedRotatingFileHandler(log_file, when='midnight', interval=1, backupCount=7, encoding='utf-8')
16
- file_handler.suffix = "%Y-%m-%d"
17
- formatter = logging.Formatter('%(asctime)s - %(name)s - %(lineno)d - %(levelname)s - %(message)s')
18
- file_handler.setFormatter(formatter)
19
- logger.addHandler(file_handler)
20
-
21
- console_handler = logging.StreamHandler()
22
- console_handler.setFormatter(formatter)
23
- logger.addHandler(console_handler)
24
-
25
- return logger
26
-
27
- def read_config_file(file_path):
28
- try:
29
- # 打开 YAML 文件
30
- with open(file_path, 'r', encoding='utf-8') as file:
31
- # 使用 yaml.safe_load 方法解析 YAML 文件内容
32
- data = yaml.safe_load(file)
33
- return data
34
- except FileNotFoundError:
35
- raise Exception(f"文件 {file_path} 未找到。")
36
- except yaml.YAMLError as e:
37
- raise Exception(f"解析 {file_path} 文件时出错: {e}")
38
-
39
- def main():
40
- import importlib.metadata
41
- version = importlib.metadata.version("openfund-maker")
42
-
43
- maker_config_path = 'maker_config.yaml'
44
- config_data = read_config_file(maker_config_path)
45
-
46
- platform_config = config_data['okx']
47
- feishu_webhook_url = config_data['feishu_webhook']
48
- # monitor_interval = config_data.get("monitor_interval", 4) # 默认值为60秒
49
- logger = build_logger(config_data["Logger"])
50
- # 获取当前包名
51
- package_name = __package__ or "maker"
52
- logger.info(f" ++ {package_name}:{version} is doing...")
53
- # bot = WickReversalOrderBot(config_data,platform_config, feishu_webhook=feishu_webhook_url)
54
- # bot =ThreeLineOrdergBot(config_data,platform_config, feishu_webhook=feishu_webhook_url,logger=logger)
55
- bot = MACDOrdergBot(config_data,platform_config, feishu_webhook=feishu_webhook_url,logger=logger)
56
- bot.monitor_klines()
57
-
58
- if __name__ == "__main__":
59
- main()