openfund-maker 1.1.5__py3-none-any.whl → 1.2.3__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/MACDOrderBot.py +172 -0
- maker/ThreeLineOrderBot.py +30 -24
- maker/main.py +3 -1
- {openfund_maker-1.1.5.dist-info → openfund_maker-1.2.3.dist-info}/METADATA +1 -1
- {openfund_maker-1.1.5.dist-info → openfund_maker-1.2.3.dist-info}/RECORD +7 -6
- {openfund_maker-1.1.5.dist-info → openfund_maker-1.2.3.dist-info}/WHEEL +0 -0
- {openfund_maker-1.1.5.dist-info → openfund_maker-1.2.3.dist-info}/entry_points.txt +0 -0
maker/MACDOrderBot.py
ADDED
@@ -0,0 +1,172 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
import time
|
3
|
+
import ccxt
|
4
|
+
import traceback
|
5
|
+
import requests
|
6
|
+
import pandas as pd
|
7
|
+
import talib as ta
|
8
|
+
|
9
|
+
from logging.handlers import TimedRotatingFileHandler
|
10
|
+
from concurrent.futures import ThreadPoolExecutor, as_completed
|
11
|
+
from maker.ThreeLineOrderBot import ThreeLineOrdergBot
|
12
|
+
|
13
|
+
|
14
|
+
class MACDOrdergBot(ThreeLineOrdergBot):
|
15
|
+
def __init__(self, config, platform_config, feishu_webhook=None,logger=None):
|
16
|
+
super().__init__(config, platform_config, feishu_webhook, logger)
|
17
|
+
|
18
|
+
def judge_order_side(self, symbol, pair_config, macd:pd.DataFrame) -> str:
|
19
|
+
order_side = 'none'
|
20
|
+
|
21
|
+
'''
|
22
|
+
零轴之上的macd与signal形成金叉
|
23
|
+
零轴之下的死叉
|
24
|
+
零轴之上的死叉-金叉-死叉
|
25
|
+
零轴之下的金叉-死叉-金叉
|
26
|
+
'''
|
27
|
+
|
28
|
+
|
29
|
+
# 计算最近三个交叉点
|
30
|
+
last_up_crosses = []
|
31
|
+
last_down_crosses = []
|
32
|
+
other_crosses = []
|
33
|
+
all_cross = []
|
34
|
+
# 遍历MACD数据 FIXME macd 要有根数限制,太久之前的无效
|
35
|
+
# 从最新数据开始往前遍历
|
36
|
+
for i in range(len(macd)-1, 2, -1):
|
37
|
+
# 检查是否发生死叉(MACD从上方穿过Signal)
|
38
|
+
if (macd.iloc[i-1]['macd'] <= macd.iloc[i-1]['signal'] and
|
39
|
+
macd.iloc[i]['macd'] > macd.iloc[i]['signal']
|
40
|
+
):
|
41
|
+
all_cross.append(('golden', i))
|
42
|
+
|
43
|
+
# 判断如果都在零轴之上加入last_up_crosses , 判断如果都在零轴之下加入last_down_crosses
|
44
|
+
if macd.iloc[i]['macd'] > 0 and macd.iloc[i]['signal'] > 0 :
|
45
|
+
last_up_crosses.append(('golden', i))
|
46
|
+
elif macd.iloc[i]['macd'] < 0 and macd.iloc[i]['signal'] < 0 :
|
47
|
+
last_down_crosses.append(('golden', i))
|
48
|
+
else:
|
49
|
+
other_crosses.append(('golden', i))
|
50
|
+
|
51
|
+
# 检查是否发生死叉(MACD从上方穿过Signal)
|
52
|
+
elif macd.iloc[i-1]['macd'] >= macd.iloc[i-1]['signal'] and macd.iloc[i]['macd'] < macd.iloc[i]['signal']:
|
53
|
+
all_cross.append(('death', i))
|
54
|
+
# 判断如果都在零轴之上加入last_up_crosses , 判断如果都在零轴之下加入last_down_crosses
|
55
|
+
if macd.iloc[i]['macd'] > 0 and macd.iloc[i]['signal'] > 0 :
|
56
|
+
last_up_crosses.append(('death', i))
|
57
|
+
elif macd.iloc[i]['macd'] < 0 and macd.iloc[i]['signal'] < 0 :
|
58
|
+
last_down_crosses.append(('death', i))
|
59
|
+
else:
|
60
|
+
other_crosses.append(('golden', i))
|
61
|
+
# 只保留最后三个交叉点
|
62
|
+
if len(last_up_crosses) == 3 or len(last_down_crosses) == 3:
|
63
|
+
break
|
64
|
+
|
65
|
+
self.logger.debug(f"{symbol} : \n- 所有cross {all_cross} \n- 零轴之上cross {last_up_crosses} \n- 零轴之下cross {last_down_crosses} \n- 其他corss {other_crosses}。")
|
66
|
+
valid_klines = pair_config.get('valid_klines', 3)
|
67
|
+
# 如果最新的交叉是金叉,且又是零轴上方的金叉
|
68
|
+
if all_cross[0][0] == 'golden' and all_cross[0][1] == last_up_crosses[0][1] and len(macd) - all_cross[0][1] <= valid_klines:
|
69
|
+
order_side = 'buy'
|
70
|
+
self.logger.debug(f"{symbol} : 零轴之上的macd与signal形成金叉{all_cross[0]} 。")
|
71
|
+
|
72
|
+
# 如果最新的交叉是死叉,且又是零轴下方的死叉
|
73
|
+
elif all_cross[0][0] == 'death' and all_cross[0][1] == last_down_crosses[0][1] and len(macd) - all_cross[0][1] <= valid_klines:
|
74
|
+
order_side ='sell'
|
75
|
+
self.logger.debug(f"{symbol} : 零轴之下的macd与signal形成死叉{all_cross[0]} 。")
|
76
|
+
# 分析交叉点模式,要满足连续的三个交叉都是零上
|
77
|
+
elif len(last_up_crosses) == 3 and len(all_cross) == 3:
|
78
|
+
|
79
|
+
# 零轴之上的死叉-金叉-死叉模式
|
80
|
+
if (last_up_crosses[0][0] == 'death' and
|
81
|
+
last_up_crosses[1][0] == 'golden' and
|
82
|
+
last_up_crosses[2][0] == 'death'):
|
83
|
+
order_side = 'sell'
|
84
|
+
self.logger.debug(f"{symbol} : 零轴之上的死叉-金叉-死叉模式 {order_side}。")
|
85
|
+
|
86
|
+
elif len(last_down_crosses) == 3 and len(all_cross) == 3:
|
87
|
+
# 零轴之下的金叉-死叉-金叉模式
|
88
|
+
if (last_down_crosses[0][0] == 'golden' and
|
89
|
+
last_down_crosses[1][0] == 'death' and
|
90
|
+
last_down_crosses[2][0] == 'golden'):
|
91
|
+
order_side = 'buy'
|
92
|
+
self.logger.debug(f"{symbol} : 零轴之下的金叉-死叉-金叉模式 {order_side}。")
|
93
|
+
|
94
|
+
|
95
|
+
|
96
|
+
# # 零轴之上的macd与signal形成金叉
|
97
|
+
# if macd.iloc[-1]['macd'] > 0 and macd.iloc[-1]['signal'] > 0 and macd.iloc[-1]['macd'] >= macd.iloc[-1]['signal'] and macd.iloc[-2]['macd'] < macd.iloc[-2]['signal']:
|
98
|
+
# order_side = 'buy'
|
99
|
+
# self.logger.debug(f"{symbol} : 零轴之上的macd与signal形成金叉 {order_side}。")
|
100
|
+
# elif macd.iloc[-1]['macd'] < 0 and macd.iloc[-1]['signal'] < 0 and macd.iloc[-1]['macd'] <= macd.iloc[-1]['signal'] and macd.iloc[-2]['macd'] > macd.iloc[-2]['signal']:
|
101
|
+
# order_side = 'sell'
|
102
|
+
# self.logger.debug(f"{symbol} : 零轴之下的macd与signal形成死叉 {order_side}。")
|
103
|
+
# else:
|
104
|
+
# order_side = 'none'
|
105
|
+
|
106
|
+
|
107
|
+
return order_side
|
108
|
+
|
109
|
+
|
110
|
+
def process_pair(self,symbol,pair_config):
|
111
|
+
self.logger.info("=" * 60)
|
112
|
+
# 检查是否有持仓,有持仓不进行下单
|
113
|
+
if self.check_position(symbol=symbol) :
|
114
|
+
self.logger.info(f"{symbol} 有持仓合约,不进行下单。")
|
115
|
+
return
|
116
|
+
self.cancel_all_orders(symbol=symbol)
|
117
|
+
klines_period = str(pair_config.get('klines_period', '1m'))
|
118
|
+
|
119
|
+
try:
|
120
|
+
kLines = self.get_historical_klines(symbol=symbol, bar=klines_period)
|
121
|
+
self.logger.debug(f"开始监控 {symbol} : klines {klines_period} - {len(kLines)}")
|
122
|
+
|
123
|
+
df = pd.DataFrame(kLines, columns=['timestamp', 'open', 'high', 'low', 'close', 'volume'])
|
124
|
+
# 将时间戳转换为日期时间格式
|
125
|
+
df['timestamp'] = pd.to_datetime(df['timestamp'], unit='ms').dt.strftime('%m-%d %H:%M')
|
126
|
+
|
127
|
+
# 使用 TA-Lib 计算 MACD
|
128
|
+
df[['macd', 'signal', 'hist']] = pd.DataFrame(ta.MACD(df['close'], fastperiod=12, slowperiod=26, signalperiod=9)).T
|
129
|
+
self.logger.debug(f"{symbol} : MACD Values = \n {df.tail(5)}")
|
130
|
+
side = self.judge_order_side(symbol, pair_config , df)
|
131
|
+
|
132
|
+
long_amount_usdt = pair_config.get('long_amount_usdt', 5)
|
133
|
+
short_amount_usdt = pair_config.get('short_amount_usdt', 5)
|
134
|
+
order_amount_usdt = 5
|
135
|
+
order_type='optimal_limit_ioc'
|
136
|
+
order_price = df['close'].iloc[-1]
|
137
|
+
if side == 'none' :
|
138
|
+
self.logger.debug(f"{symbol} : 没有触发下单条件。")
|
139
|
+
return
|
140
|
+
elif side == 'sell' :
|
141
|
+
self.logger.debug(f"{symbol} : 触发做空下单条件。")
|
142
|
+
order_amount_usdt = short_amount_usdt
|
143
|
+
elif side == 'buy' :
|
144
|
+
self.logger.debug(f"{symbol} : 触发做多下单条件。")
|
145
|
+
order_amount_usdt = long_amount_usdt
|
146
|
+
# 下单
|
147
|
+
self.place_order(symbol=symbol, price=order_price, amount_usdt=order_amount_usdt, side=side,order_type=order_type)
|
148
|
+
|
149
|
+
|
150
|
+
except KeyboardInterrupt:
|
151
|
+
self.logger.info("程序收到中断信号,开始退出...")
|
152
|
+
except Exception as e:
|
153
|
+
error_message = f"程序异常退出: {str(e)}"
|
154
|
+
self.logger.error(error_message,exc_info=True)
|
155
|
+
traceback.print_exc()
|
156
|
+
self.send_feishu_notification(error_message)
|
157
|
+
|
158
|
+
self.logger.info("-" * 60)
|
159
|
+
|
160
|
+
def monitor_klines(self):
|
161
|
+
symbols = list(self.trading_pairs_config.keys()) # 获取所有币对的ID
|
162
|
+
batch_size = 5 # 每批处理的数量
|
163
|
+
while True:
|
164
|
+
|
165
|
+
for i in range(0, len(symbols), batch_size):
|
166
|
+
batch = symbols[i:i + batch_size]
|
167
|
+
with ThreadPoolExecutor(max_workers=batch_size) as executor:
|
168
|
+
futures = [executor.submit(self.process_pair, symbol,self.trading_pairs_config[symbol]) for symbol in batch]
|
169
|
+
for future in as_completed(futures):
|
170
|
+
future.result() # Raise any exceptions caught during execution
|
171
|
+
|
172
|
+
time.sleep(self.monitor_interval)
|
maker/ThreeLineOrderBot.py
CHANGED
@@ -45,6 +45,27 @@ class ThreeLineOrdergBot:
|
|
45
45
|
def get_tick_size(self,symbol):
|
46
46
|
return float(self.getMarket(symbol)['precision']['price'])
|
47
47
|
|
48
|
+
def convert_contract(self, symbol, amount, price:float, direction='cost_to_contract'):
|
49
|
+
"""
|
50
|
+
进行合约与币的转换
|
51
|
+
:param symbol: 交易对符号,如 'BTC/USDT:USDT'
|
52
|
+
:param amount: 输入的数量,可以是合约数量或币的数量
|
53
|
+
:param direction: 转换方向,'amount_to_contract' 表示从数量转换为合约,'cost_to_contract' 表示从金额转换为合约
|
54
|
+
:return: 转换后的数量
|
55
|
+
"""
|
56
|
+
|
57
|
+
# 获取合约规模
|
58
|
+
market_contractSize = self.getMarket(symbol)['contractSize']
|
59
|
+
if direction == 'amount_to_contract':
|
60
|
+
contract_size = float(amount) / float(market_contractSize)
|
61
|
+
elif direction == 'cost_to_contract':
|
62
|
+
contract_size = float(amount) / float(price) / float(market_contractSize)
|
63
|
+
else:
|
64
|
+
raise Exception(f"{symbol}:{direction} 是无效的转换方向,请输入 'amount_to_contract' 或 'cost_to_contract'。")
|
65
|
+
|
66
|
+
return self.exchange.amount_to_precision(symbol, contract_size)
|
67
|
+
|
68
|
+
|
48
69
|
# 获取价格精度
|
49
70
|
def get_precision_length(self,symbol) -> int:
|
50
71
|
tick_size = self.get_tick_size(symbol)
|
@@ -91,24 +112,6 @@ class ThreeLineOrdergBot:
|
|
91
112
|
self.logger.debug("飞书通知发送成功")
|
92
113
|
else:
|
93
114
|
self.logger.error(f"飞书通知发送失败: {response.text}")
|
94
|
-
# 获取K线收盘价格
|
95
|
-
def get_close_price(self,symbol):
|
96
|
-
'''
|
97
|
-
bar =
|
98
|
-
时间粒度,默认值1m
|
99
|
-
如 [1m/3m/5m/15m/30m/1H/2H/4H]
|
100
|
-
香港时间开盘价k线:[6H/12H/1D/2D/3D/1W/1M/3M]
|
101
|
-
UTC时间开盘价k线:[/6Hutc/12Hutc/1Dutc/2Dutc/3Dutc/1Wutc/1Mutc/3Mutc]
|
102
|
-
'''
|
103
|
-
# response = market_api.get_candlesticks(instId=instId,bar='1m')
|
104
|
-
klines = self.exchange.fetch_ohlcv(symbol, timeframe='1m',limit=3)
|
105
|
-
if klines:
|
106
|
-
# close_price = response['data'][0][4]
|
107
|
-
# 获取前一个K线 close price
|
108
|
-
close_price = klines[-1][4]
|
109
|
-
return float(close_price)
|
110
|
-
else:
|
111
|
-
raise ValueError("Unexpected response structure or missing 'c' value")
|
112
115
|
|
113
116
|
def get_mark_price(self,symbol):
|
114
117
|
# response = market_api.get_ticker(instId)
|
@@ -414,7 +417,7 @@ class ThreeLineOrdergBot:
|
|
414
417
|
self.logger.error(f"{symbol} 检查持仓失败: {str(e)}")
|
415
418
|
return False
|
416
419
|
|
417
|
-
def place_order(self,symbol, price, amount_usdt, side):
|
420
|
+
def place_order(self,symbol, price, amount_usdt, side,order_type='limit'):
|
418
421
|
|
419
422
|
|
420
423
|
markets = self.exchange.load_markets()
|
@@ -433,12 +436,15 @@ class ThreeLineOrdergBot:
|
|
433
436
|
pos_side = 'short'
|
434
437
|
# 设置杠杆
|
435
438
|
self.set_leverage(symbol=symbol, leverage=self.leverage_value, mgnMode='isolated',posSide=pos_side)
|
439
|
+
# 20250220 SWAP类型计算合约数量
|
440
|
+
contract_size = self.convert_contract(symbol=symbol, price = adjusted_price ,amount=amount_usdt)
|
441
|
+
|
436
442
|
params = {
|
437
443
|
|
438
444
|
"tdMode": 'isolated',
|
439
445
|
"side": side,
|
440
|
-
"ordType":
|
441
|
-
|
446
|
+
"ordType": order_type,
|
447
|
+
"sz": contract_size,
|
442
448
|
"px": str(adjusted_price)
|
443
449
|
}
|
444
450
|
|
@@ -452,7 +458,7 @@ class ThreeLineOrdergBot:
|
|
452
458
|
'symbol': symbol,
|
453
459
|
'side': side,
|
454
460
|
'type': 'limit',
|
455
|
-
'amount':
|
461
|
+
'amount': contract_size,
|
456
462
|
'price': float(adjusted_price),
|
457
463
|
'params': params
|
458
464
|
}
|
@@ -496,9 +502,9 @@ class ThreeLineOrdergBot:
|
|
496
502
|
return
|
497
503
|
# 取消之前的挂单
|
498
504
|
self.cancel_all_orders(symbol=symbol)
|
499
|
-
|
505
|
+
klines_period = str(pair_config.get('klines_period', '1m'))
|
500
506
|
try:
|
501
|
-
klines = self.get_historical_klines(symbol=symbol)
|
507
|
+
klines = self.get_historical_klines(symbol=symbol,bar=klines_period)
|
502
508
|
# 提取收盘价数据用于计算 EMA
|
503
509
|
# 从K线数据中提取收盘价,按时间顺序排列(新数据在后)
|
504
510
|
# close_prices = [float(kline[4]) for kline in klines]
|
maker/main.py
CHANGED
@@ -5,6 +5,7 @@ from logging.handlers import TimedRotatingFileHandler
|
|
5
5
|
|
6
6
|
from maker.WickReversalOrderBot import WickReversalOrderBot
|
7
7
|
from maker.ThreeLineOrderBot import ThreeLineOrdergBot
|
8
|
+
from maker.MACDOrderBot import MACDOrdergBot
|
8
9
|
def build_logger(log_config) -> logging.Logger:
|
9
10
|
# 配置日志
|
10
11
|
# log_file = "log/okx_MultiAssetNewTradingBot.log"
|
@@ -40,7 +41,8 @@ def main():
|
|
40
41
|
package_name = __package__ or "maker"
|
41
42
|
logger.info(f" ++ {package_name}:{version} is doing...")
|
42
43
|
# bot = WickReversalOrderBot(config_data,platform_config, feishu_webhook=feishu_webhook_url)
|
43
|
-
bot =ThreeLineOrdergBot(config_data,platform_config, feishu_webhook=feishu_webhook_url,logger=logger)
|
44
|
+
# bot =ThreeLineOrdergBot(config_data,platform_config, feishu_webhook=feishu_webhook_url,logger=logger)
|
45
|
+
bot = MACDOrdergBot(config_data,platform_config, feishu_webhook=feishu_webhook_url,logger=logger)
|
44
46
|
bot.monitor_klines()
|
45
47
|
|
46
48
|
if __name__ == "__main__":
|
@@ -1,13 +1,14 @@
|
|
1
|
-
maker/
|
1
|
+
maker/MACDOrderBot.py,sha256=4H82wAmTvXNcv9JloJD9bVf-TLyJyfUumVcB8C1IY0k,8722
|
2
|
+
maker/ThreeLineOrderBot.py,sha256=I-Y-bMUfBKUB00rNF1UtTc08T5IND1LEBVjHUzG89vU,29064
|
2
3
|
maker/WickReversalOrderBot.py,sha256=Oc6wChdWu39lfWh3NRHM8BqvaRIYDNZiDR6PDnE9XUM,17374
|
3
4
|
maker/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
4
5
|
maker/config.py,sha256=YPxghO5i0vgRg9Cja8kGj9O7pgSbbtzOgf3RexqXXwY,1188
|
5
|
-
maker/main.py,sha256=
|
6
|
+
maker/main.py,sha256=qnf212MFvcbqAUHWfV0pNDoGrDZMVhGoMWNBOamKOjg,2023
|
6
7
|
maker/main_m.py,sha256=0PzDTnuBrxfpy5WDfsIHKAzZ_7pkuvuqqeWik0vpWio,15522
|
7
8
|
maker/okxapi.py,sha256=_9G0U_o0ZC8NxaT6PqpiLgxBm9gPobC9PsFHZE1c5w0,553
|
8
9
|
maker/zhen.py.bak,sha256=HNkrQbJts8G9umE9chEFsc0cLQApcM9KOVNMYPpkBXM,10918
|
9
10
|
maker/zhen_2.py,sha256=4IaHVtTCMSlrLGSTZrWpW2q-f7HZsUNRkW_-5QgWv24,10509
|
10
|
-
openfund_maker-1.
|
11
|
-
openfund_maker-1.
|
12
|
-
openfund_maker-1.
|
13
|
-
openfund_maker-1.
|
11
|
+
openfund_maker-1.2.3.dist-info/METADATA,sha256=O5Fnst4_dQnjVXuvxv5X6_XiA7KoVt26whJi7gyzgdY,1965
|
12
|
+
openfund_maker-1.2.3.dist-info/WHEEL,sha256=IYZQI976HJqqOpQU6PHkJ8fb3tMNBFjg-Cn-pwAbaFM,88
|
13
|
+
openfund_maker-1.2.3.dist-info/entry_points.txt,sha256=gKMytICEKcMRFQDFkHZLnIpID7UQFoTIM_xcpiiV6Ns,50
|
14
|
+
openfund_maker-1.2.3.dist-info/RECORD,,
|
File without changes
|
File without changes
|