openfund-maker 2.2.5__py3-none-any.whl → 2.2.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.
@@ -4,25 +4,31 @@ import ccxt
4
4
  import traceback
5
5
  import requests
6
6
  import pandas as pd
7
+ from decimal import Decimal
7
8
 
8
9
  from concurrent.futures import ThreadPoolExecutor, as_completed
9
10
 
10
11
 
12
+
11
13
  class ThreeLineStrategyMaker:
12
- def __init__(self, config, platform_config, feishu_webhook=None,logger=None):
14
+ def __init__(self, config, platform_config, common_config, feishu_webhook=None,logger=None):
13
15
 
14
16
  self.g_config = config
15
17
  self.feishu_webhook = feishu_webhook
16
- self.monitor_interval = self.g_config.get("monitor_interval", 4) # 默认值为60秒 # 监控循环时间是分仓监控的3倍
18
+ self.common_config = common_config
19
+ self.strategy_config = self.g_config.get('strategy', {})
20
+
17
21
  self.trading_pairs_config = self.g_config.get('tradingPairs', {})
22
+
23
+ self.leverage_value = self.strategy_config.get('leverage', 20)
24
+ self.is_demo_trading = self.common_config.get('is_demo_trading', 1) # live trading: 0, demo trading: 1
25
+
18
26
  self.highest_total_profit = 0 # 记录最高总盈利
19
- self.leverage_value = self.g_config.get('leverage', 2)
20
- self.is_demo_trading = self.g_config.get('is_demo_trading', 1) # live trading: 0, demo trading: 1
21
27
  # self.instrument_info_dict = {}
22
28
  self.cross_directions = {} # 持仓期间,存储每个交易对的交叉方向
23
29
  proxies = {
24
- "http": self.g_config.get('proxy', "http://localhost:7890"),
25
- "https": self.g_config.get('proxy', "http://localhost:7890")
30
+ "http": self.common_config.get('proxy', "http://localhost:7890"),
31
+ "https": self.common_config.get('proxy', "http://localhost:7890")
26
32
  }
27
33
 
28
34
  # 配置交易所
@@ -35,22 +41,39 @@ class ThreeLineStrategyMaker:
35
41
  'options': {'defaultType': 'future'},
36
42
  'proxies': proxies
37
43
  })
38
-
39
-
40
44
 
41
45
  self.logger = logger
42
46
  self.position_mode = self.get_position_mode() # 获取持仓模式
43
47
 
48
+ def format_price(self, symbol, price:Decimal) -> str:
49
+ precision = self.get_precision_length(symbol)
50
+ return f"{price:.{precision}f}"
51
+
52
+ @staticmethod
53
+ def toDecimal(value):
54
+ return Decimal(str(value))
55
+
56
+
44
57
  def getMarket(self,symbol):
45
58
  self.exchange.load_markets()
46
59
  return self.exchange.market(symbol)
47
60
 
48
- def get_tick_size(self,symbol):
49
- market = self.getMarket(symbol)
50
- marketPrecision = self.exchange.safe_dict(market, 'precision')
51
- return self.exchange.safe_float(marketPrecision, 'price')
61
+ def get_tick_size(self,symbol) -> Decimal:
62
+
63
+ try:
64
+ market = self.getMarket(symbol)
65
+ if market and 'precision' in market and 'price' in market['precision']:
66
+
67
+ return self.toDecimal(market['precision']['price'])
68
+ else:
69
+ # self.logger.error(f"{symbol}: 无法从市场数据中获取价格精度")
70
+ # return self.toDecimal("0.00001") # 返回默认精度
71
+ raise ValueError(f"{symbol}: 无法从市场数据中获取价格精度")
72
+ except Exception as e:
73
+ self.logger.error(f"{symbol}: 获取市场价格精度时发生错误: {str(e)}")
74
+ self.send_feishu_notification(f"{symbol}: 获取市场价格精度时发生错误: {str(e)}")
52
75
 
53
- def convert_contract(self, symbol, amount, price:float, direction='cost_to_contract'):
76
+ def convert_contract(self, symbol, amount, price:Decimal, direction='cost_to_contract'):
54
77
  """
55
78
  进行合约与币的转换
56
79
  :param symbol: 交易对符号,如 'BTC/USDT:USDT'
@@ -60,11 +83,12 @@ class ThreeLineStrategyMaker:
60
83
  """
61
84
 
62
85
  # 获取合约规模
63
- market_contractSize = self.getMarket(symbol)['contractSize']
86
+ market_contractSize = self.toDecimal(self.getMarket(symbol)['contractSize'])
87
+ amount = self.toDecimal(amount)
64
88
  if direction == 'amount_to_contract':
65
- contract_size = float(amount) / float(market_contractSize)
89
+ contract_size = amount / market_contractSize
66
90
  elif direction == 'cost_to_contract':
67
- contract_size = float(amount) / float(price) / float(market_contractSize)
91
+ contract_size = amount / price / market_contractSize
68
92
  else:
69
93
  raise Exception(f"{symbol}:{direction} 是无效的转换方向,请输入 'amount_to_contract' 或 'cost_to_contract'。")
70
94
 
@@ -74,7 +98,7 @@ class ThreeLineStrategyMaker:
74
98
  # 获取价格精度
75
99
  def get_precision_length(self,symbol) -> int:
76
100
  tick_size = self.get_tick_size(symbol)
77
- return len(f"{tick_size:.10f}".rstrip('0').split('.')[1]) if '.' in f"{tick_size:.10f}" else 0
101
+ return len(f"{tick_size:.15f}".rstrip('0').split('.')[1]) if '.' in f"{tick_size:.15f}" else 0
78
102
 
79
103
  def get_position_mode(self):
80
104
  try:
@@ -129,13 +153,13 @@ class ThreeLineStrategyMaker:
129
153
  else:
130
154
  raise ValueError("Unexpected response structure or missing 'last' key")
131
155
 
132
- def round_price_to_tick(self, price, tick_size):
156
+ def round_price_to_tick(self, symbol, price: Decimal) -> Decimal:
157
+ tick_size = self.get_tick_size(symbol)
133
158
  # 计算 tick_size 的小数位数
134
- tick_decimals = len(f"{tick_size:.10f}".rstrip('0').split('.')[1]) if '.' in f"{tick_size:.10f}" else 0
135
-
159
+ tick_decimals = self.get_precision_length(symbol)
136
160
  # 调整价格为 tick_size 的整数倍
137
161
  adjusted_price = round(price / tick_size) * tick_size
138
- return f"{adjusted_price:.{tick_decimals}f}"
162
+ return self.toDecimal(f"{adjusted_price:.{tick_decimals}f}")
139
163
 
140
164
  def get_historical_klines(self,symbol, bar='1m', limit=241):
141
165
  # response = market_api.get_candlesticks(instId, bar=bar, limit=limit)
@@ -214,7 +238,7 @@ class ThreeLineStrategyMaker:
214
238
 
215
239
  return diff
216
240
 
217
- def calculate_place_order_price(self, symbol,side,base_price, amplitude_limit, offset=1) -> float:
241
+ def calculate_place_order_price(self, symbol,side,base_price:Decimal, amplitude_limit: float, offset=1) -> Decimal:
218
242
  """
219
243
  计算开仓价格
220
244
  Args:
@@ -227,15 +251,16 @@ class ThreeLineStrategyMaker:
227
251
  place_order_price: 开仓价格
228
252
  """
229
253
  tick_size = self.get_tick_size(symbol)
254
+ precision= self.get_precision_length(symbol)
230
255
  place_order_price = None
231
256
  # 计算止盈价格,用市场价格(取持仓期间历史最高)减去开仓价格的利润,再乘以不同阶段的止盈百分比。
232
257
 
233
258
  if side == 'buy':
234
- place_order_price = base_price * (1- amplitude_limit/100) - offset * tick_size
259
+ place_order_price = base_price * (1- self.toDecimal(amplitude_limit)/100) - offset * tick_size
235
260
  else:
236
- place_order_price = base_price * (1 + amplitude_limit/100) + offset * tick_size
237
- self.logger.debug(f"++++ {symbol} 下单价格: {place_order_price:.9f} 方向 {side} 基准价格{base_price} 振幅限制 {amplitude_limit} ")
238
- return float(self.round_price_to_tick(place_order_price,tick_size))
261
+ place_order_price = base_price * (1 + self.toDecimal(amplitude_limit)/100) + offset * tick_size
262
+ self.logger.debug(f"++++ {symbol} 下单价格: {place_order_price:.{precision}f} 方向 {side} 基准价格{base_price:.{precision}} 振幅限制 {amplitude_limit} ")
263
+ return self.round_price_to_tick(place_order_price,tick_size)
239
264
 
240
265
  # 定义根据均线斜率判断 K 线方向的函数: 0 空 1 多 -1 平
241
266
  def judge_k_line_direction(self, symbol, ema: pd.Series, klines, period=3) -> int:
@@ -427,18 +452,18 @@ class ThreeLineStrategyMaker:
427
452
  self.logger.warning(f"{symbol} 检查持仓失败,正在进行第{retry_count}次重试: {str(e)}")
428
453
  time.sleep(0.1) # 重试前等待0.1秒
429
454
 
430
- def place_order(self,symbol, price, amount_usdt, side,order_type='limit'):
431
-
432
-
433
- # markets = self.exchange.load_markets()
434
- # if symbol not in markets:
435
- # self.logger.error(f"{symbol}: Instrument {symbol} not found in markets")
436
- # return
437
- # market = markets[symbol]
438
- # # 获取价格精度
439
- # price_precision = market['precision']['price']
440
- tick_size = self.get_tick_size(symbol)
441
- adjusted_price = self.round_price_to_tick(price, tick_size)
455
+ def place_order(self, symbol, price: Decimal, amount_usdt, side, order_type='limit'):
456
+ """
457
+ 下单
458
+ Args:
459
+ symbol: 交易对
460
+ price: 下单价格
461
+ amount_usdt: 下单金额
462
+ side: 下单方向
463
+ order_type: 订单类型
464
+ """
465
+ # 格式化价格
466
+ adjusted_price = self.format_price(symbol, price)
442
467
 
443
468
  if amount_usdt > 0:
444
469
  if side == 'buy':
@@ -448,7 +473,7 @@ class ThreeLineStrategyMaker:
448
473
  # 设置杠杆
449
474
  self.set_leverage(symbol=symbol, leverage=self.leverage_value, mgnMode='isolated',posSide=pos_side)
450
475
  # 20250220 SWAP类型计算合约数量
451
- contract_size = self.convert_contract(symbol=symbol, price = adjusted_price ,amount=amount_usdt)
476
+ contract_size = self.convert_contract(symbol=symbol, price = self.toDecimal(adjusted_price) ,amount=amount_usdt)
452
477
 
453
478
  params = {
454
479
 
@@ -456,7 +481,7 @@ class ThreeLineStrategyMaker:
456
481
  "side": side,
457
482
  "ordType": order_type,
458
483
  "sz": contract_size,
459
- "px": str(adjusted_price)
484
+ "px": adjusted_price
460
485
  }
461
486
 
462
487
  # 模拟盘(demo_trading)需要 posSide
@@ -470,7 +495,7 @@ class ThreeLineStrategyMaker:
470
495
  'side': side,
471
496
  'type': 'limit',
472
497
  'amount': contract_size,
473
- 'price': float(adjusted_price),
498
+ 'price': adjusted_price,
474
499
  'params': params
475
500
  }
476
501
  # 使用ccxt创建订单
@@ -486,7 +511,9 @@ class ThreeLineStrategyMaker:
486
511
  )
487
512
  # self.logger.debug(f"{symbol} ++ Order placed rs : {order_result}")
488
513
  except Exception as e:
489
- self.logger.error(f"{symbol} Failed to place order: {e}")
514
+ error_message = f"{symbol} Failed to place order: {e}"
515
+ self.logger.error(error_message)
516
+ self.send_feishu_notification(error_message)
490
517
  self.logger.info(f"--------- ++ {symbol} Order placed done! --------")
491
518
 
492
519
  def cancel_all_orders(self, symbol):
@@ -514,7 +541,9 @@ class ThreeLineStrategyMaker:
514
541
  except Exception as e:
515
542
  retry_count += 1
516
543
  if retry_count == max_retries:
517
- self.logger.warning(f"{symbol} 取消挂单失败(重试{retry_count}次): {str(e)}")
544
+ error_message = f"{symbol} 取消挂单失败(重试{retry_count}次): {str(e)}"
545
+ self.logger.error(error_message)
546
+ self.send_feishu_notification(error_message)
518
547
  return False
519
548
  self.logger.warning(f"{symbol} 取消挂单失败,正在进行第{retry_count}次重试: {str(e)}")
520
549
  time.sleep(0.1) # 重试前等待0.1秒
@@ -616,8 +645,8 @@ class ThreeLineStrategyMaker:
616
645
  df_3 = pd.DataFrame(klines[-4:-1], columns=['timestamp', 'open', 'high', 'low', 'close', 'volume'])
617
646
  low_prices = df_3['low']
618
647
  high_prices = df_3['high']
619
- max_high = high_prices.max()
620
- min_low = low_prices.min()
648
+ max_high = self.toDecimal(high_prices.max())
649
+ min_low = self.toDecimal(low_prices.min())
621
650
 
622
651
  # 计算当前 振幅是否超过amplitude_limit
623
652
  long_amount_usdt = pair_config.get('long_amount_usdt', 5)
@@ -669,7 +698,19 @@ class ThreeLineStrategyMaker:
669
698
  self.send_feishu_notification(error_message)
670
699
 
671
700
  self.logger.info("-" * 60)
672
-
701
+
702
+ def get_pair_config(self,symbol):
703
+ # 获取交易对特定配置,如果没有则使用全局策略配置
704
+ pair_config = self.trading_pairs_config.get(symbol, {})
705
+
706
+ # 使用字典推导式合并配置,trading_pairs_config优先级高于strategy_config
707
+ pair_config = {
708
+ **self.strategy_config, # 基础配置
709
+ **pair_config # 交易对特定配置会覆盖基础配置
710
+ }
711
+ return pair_config
712
+
713
+
673
714
  def monitor_klines(self):
674
715
  symbols = list(self.trading_pairs_config.keys()) # 获取所有币对的ID
675
716
  batch_size = 5 # 每批处理的数量
@@ -678,8 +719,6 @@ class ThreeLineStrategyMaker:
678
719
  for i in range(0, len(symbols), batch_size):
679
720
  batch = symbols[i:i + batch_size]
680
721
  with ThreadPoolExecutor(max_workers=batch_size) as executor:
681
- futures = [executor.submit(self.process_pair, symbol,self.trading_pairs_config[symbol]) for symbol in batch]
722
+ futures = [executor.submit(self.process_pair, symbol,self.get_pair_config(symbol)) for symbol in batch]
682
723
  for future in as_completed(futures):
683
724
  future.result() # Raise any exceptions caught during execution
684
-
685
- # time.sleep(self.monitor_interval)
maker/main.py CHANGED
@@ -1,35 +1,15 @@
1
1
  import logging
2
+ import logging.config
2
3
  import yaml
3
- from logging.handlers import TimedRotatingFileHandler
4
4
  from apscheduler.triggers.interval import IntervalTrigger
5
5
  from apscheduler.schedulers.blocking import BlockingScheduler
6
6
  from datetime import datetime
7
7
  from pyfiglet import Figlet
8
8
 
9
- from maker.WickReversalStrategyMaker import WickReversalStrategyMaker
10
9
  from maker.ThreeLineStrategyMaker import ThreeLineStrategyMaker
11
- # from maker.MACDStrategyMaker import MACDStrategyMaker
12
10
  from maker.SMCStrategyMaker import SMCStrategyMaker
13
11
  from maker.BestFVGStrategyMaker import BestFVGStrategyMaker
14
12
 
15
- def build_logger(log_config) -> logging.Logger:
16
- # 配置日志
17
-
18
- log_file = log_config["file"]
19
- logger = logging.getLogger(__name__)
20
- logger.setLevel(log_config["level"])
21
-
22
- file_handler = TimedRotatingFileHandler(log_file, when='midnight', interval=1, backupCount=7, encoding='utf-8')
23
- file_handler.suffix = "%Y-%m-%d"
24
- formatter = logging.Formatter('%(asctime)s - %(name)s - %(lineno)d - %(levelname)s - %(message)s')
25
- file_handler.setFormatter(formatter)
26
- logger.addHandler(file_handler)
27
-
28
- console_handler = logging.StreamHandler()
29
- console_handler.setFormatter(formatter)
30
- logger.addHandler(console_handler)
31
-
32
- return logger
33
13
 
34
14
  def read_config_file(file_path):
35
15
  try:
@@ -51,36 +31,39 @@ def run_bot(bot, logger):
51
31
  logger.error(f"执行任务时发生错误: {str(e)}", exc_info=True)
52
32
 
53
33
  def main():
34
+
54
35
  import importlib.metadata
36
+ package_name = __package__ or "openfund-maker"
55
37
  version = importlib.metadata.version("openfund-maker")
56
38
 
57
39
  maker_config_path = 'maker_config.yaml'
58
40
  config_data = read_config_file(maker_config_path)
59
41
 
60
- platform_config = config_data['okx']
61
- feishu_webhook_url = config_data['feishu_webhook']
62
- logger = build_logger(config_data["Logger"])
63
- package_name = __package__ or "maker"
64
-
65
-
66
- maker = config_data.get('actived_maker', 'MACDStrategyMaker')
42
+ logging.config.dictConfig(config_data["Logger"])
43
+ logger = logging.getLogger("openfund-maker")
67
44
 
45
+ f = Figlet(font="standard") # 字体可选(如 "block", "bubble")
46
+ logger.info(f"\n{f.renderText("OpenFund Maker")}")
47
+
48
+ common_config = config_data['common']
49
+ feishu_webhook_url = common_config['feishu_webhook']
50
+ maker = common_config.get('actived_maker', 'MACDStrategyMaker')
51
+ logger.info(f" ++ {package_name}.{maker}:{version} is doing...")
68
52
 
53
+ platform_config = config_data['platform']['okx']
69
54
  # 根据配置动态创建策略实例
70
55
  strategy_class = globals()[maker]
71
- bot = strategy_class(config_data, platform_config, feishu_webhook=feishu_webhook_url, logger=logger)
56
+ bot = strategy_class(config_data, platform_config, common_config, feishu_webhook=feishu_webhook_url, logger=logger)
72
57
 
73
- logger.info(f" ++ {package_name}.{maker}:{version} is doing...")
74
58
 
75
59
  # 获取计划配置
76
- schedule_config = config_data.get('schedule', {})
60
+ schedule_config = common_config.get('schedule', {})
77
61
  if schedule_config.get('enabled', False):
78
62
  scheduler = BlockingScheduler()
79
63
 
80
64
  # 设置每5分钟执行一次的任务,从整点开始
81
65
  monitor_interval = int(schedule_config.get('monitor_interval', 4))
82
- f = Figlet(font="standard") # 字体可选(如 "block", "bubble")
83
- logger.info(f"\n{f.renderText("OpenFund Maker")}")
66
+
84
67
  # 计算下一个整点分钟
85
68
  now = datetime.now()
86
69
  # 将当前时间的秒和微秒设置为0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: openfund-maker
3
- Version: 2.2.5
3
+ Version: 2.2.7
4
4
  Summary: Openfund-maker.
5
5
  Requires-Python: >=3.9,<4.0
6
6
  Classifier: Programming Language :: Python :: 3
@@ -0,0 +1,15 @@
1
+ maker/BestFVGStrategyMaker.py,sha256=xIiDIvMw8RwHayUuz5Won3stSG9BxdYIQgeXDok_SqM,12336
2
+ maker/MACDStrategyMaker.py,sha256=WX8wqpF9h5W4WclN1NjZ_Bur7KFi_aMTvacfLyHzEcI,12681
3
+ maker/SMCStrategyMaker.py,sha256=hkDqymWnuyYDo1gTYY_uyO4H4yOwCw8NBTM9RcfLRPc,28780
4
+ maker/ThreeLineStrategyMaker.py,sha256=jCdyTvI9oTMixCM0tQF9D_-6ig0-bYstKuRFECE4deE,32099
5
+ maker/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
6
+ maker/history_code/WickReversalStrategyMaker.py,sha256=7DqPDVJot4EM0_lSAcFAHrR9rNvkIds9KLMoDOiAHEc,17486
7
+ maker/history_code/config.py,sha256=YPxghO5i0vgRg9Cja8kGj9O7pgSbbtzOgf3RexqXXwY,1188
8
+ maker/history_code/okxapi.py,sha256=_9G0U_o0ZC8NxaT6PqpiLgxBm9gPobC9PsFHZE1c5w0,553
9
+ maker/history_code/zhen.py.bak,sha256=HNkrQbJts8G9umE9chEFsc0cLQApcM9KOVNMYPpkBXM,10918
10
+ maker/history_code/zhen_2.py,sha256=4IaHVtTCMSlrLGSTZrWpW2q-f7HZsUNRkW_-5QgWv24,10509
11
+ maker/main.py,sha256=kumgkgyYj1tVKrHTQmSx0prFs9K4i5KpvcOnqimV2eA,3805
12
+ openfund_maker-2.2.7.dist-info/METADATA,sha256=Flc_fXTunuA_45YJsL-nT4u0-0z7nWm1TYch6PdFKOI,1955
13
+ openfund_maker-2.2.7.dist-info/WHEEL,sha256=IYZQI976HJqqOpQU6PHkJ8fb3tMNBFjg-Cn-pwAbaFM,88
14
+ openfund_maker-2.2.7.dist-info/entry_points.txt,sha256=gKMytICEKcMRFQDFkHZLnIpID7UQFoTIM_xcpiiV6Ns,50
15
+ openfund_maker-2.2.7.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: poetry-core 2.1.2
2
+ Generator: poetry-core 2.0.1
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any