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.
- maker/BestFVGStrategyMaker.py +23 -14
- maker/MACDStrategyMaker.py +2 -2
- maker/SMCStrategyMaker.py +50 -189
- maker/ThreeLineStrategyMaker.py +88 -49
- maker/main.py +16 -33
- {openfund_maker-2.2.5.dist-info → openfund_maker-2.2.7.dist-info}/METADATA +1 -1
- openfund_maker-2.2.7.dist-info/RECORD +15 -0
- {openfund_maker-2.2.5.dist-info → openfund_maker-2.2.7.dist-info}/WHEEL +1 -1
- maker/main_m.py +0 -378
- openfund_maker-2.2.5.dist-info/RECORD +0 -16
- /maker/{WickReversalStrategyMaker.py → history_code/WickReversalStrategyMaker.py} +0 -0
- /maker/{config.py → history_code/config.py} +0 -0
- /maker/{okxapi.py → history_code/okxapi.py} +0 -0
- /maker/{zhen.py.bak → history_code/zhen.py.bak} +0 -0
- /maker/{zhen_2.py → history_code/zhen_2.py} +0 -0
- {openfund_maker-2.2.5.dist-info → openfund_maker-2.2.7.dist-info}/entry_points.txt +0 -0
maker/ThreeLineStrategyMaker.py
CHANGED
@@ -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.
|
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.
|
25
|
-
"https": self.
|
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
|
-
|
50
|
-
|
51
|
-
|
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:
|
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 =
|
89
|
+
contract_size = amount / market_contractSize
|
66
90
|
elif direction == 'cost_to_contract':
|
67
|
-
contract_size =
|
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:.
|
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,
|
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 =
|
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) ->
|
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:.
|
238
|
-
return
|
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
|
-
|
434
|
-
|
435
|
-
|
436
|
-
|
437
|
-
|
438
|
-
|
439
|
-
|
440
|
-
|
441
|
-
adjusted_price = self.
|
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":
|
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':
|
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
|
-
|
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
|
-
|
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.
|
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
|
-
|
61
|
-
|
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 =
|
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
|
-
|
83
|
-
logger.info(f"\n{f.renderText("OpenFund Maker")}")
|
66
|
+
|
84
67
|
# 计算下一个整点分钟
|
85
68
|
now = datetime.now()
|
86
69
|
# 将当前时间的秒和微秒设置为0
|
@@ -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,,
|