openfund-taker 2.2.8__py3-none-any.whl → 2.3.1__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: openfund-taker
3
- Version: 2.2.8
3
+ Version: 2.3.1
4
4
  Summary: Openfund-taker
5
5
  Requires-Python: >=3.9,<4.0
6
6
  Classifier: Programming Language :: Python :: 3
@@ -10,6 +10,7 @@ Classifier: Programming Language :: Python :: 3.11
10
10
  Classifier: Programming Language :: Python :: 3.12
11
11
  Classifier: Programming Language :: Python :: 3.13
12
12
  Requires-Dist: ccxt (>=4.4.26,<5.0.0)
13
+ Requires-Dist: openfund-core (>=1.0.0,<2.0.0)
13
14
  Requires-Dist: pandas (>=2.2.3,<3.0.0)
14
15
  Requires-Dist: pyfiglet (>=1.0.2,<2.0.0)
15
16
  Requires-Dist: pyyaml (>=6.0.2,<7.0.0)
@@ -1,4 +1,6 @@
1
+ taker/BestTopDownStrategyTaker.py,sha256=2GCHFr15N-RBVYklraFnAunXi2aKS--asYAIvRgvbwo,4376
1
2
  taker/SMCSLAndTPTaker.py,sha256=zJCtwyzIuDtbxMdLOwRbpUE60o9fKe-tNWuiuej4RGs,27621
3
+ taker/StrategyTaker.py,sha256=SHm-YmT4eVATbfiYzS-zYMOn8oj1fi1QqJi6aCY3G44,26281
2
4
  taker/ThreeLineTradingTaker.py,sha256=NGEXd4baCfzF4ToGb9tQWXRMweFBpImyh2v4WcKLK3A,20580
3
5
  taker/TrailingSLAndTPTaker.py,sha256=W-kj0BhPXqusO8z4zlDU_nb1L6L7TT8jEhCSgMkEF9U,2831
4
6
  taker/TrailingSLTaker.py,sha256=diRfcIS71prbm1I4wYgwVUq3c2_ftntiC0tQ5byWeHQ,53465
@@ -9,8 +11,8 @@ taker/history_code/chua_ok.py,sha256=5pPAoEYbFuKxfZwqNvOO890s-2cy6n69QiI0ZA0GTCQ
9
11
  taker/history_code/chua_ok_all.py,sha256=2XnZM6QdB3juSE1pqQIJyh2x1XuhlTlnBKNA3owlJ9E,15267
10
12
  taker/history_code/chua_ok_bot.py,sha256=9SW0ujhi6PfN4yR1JZ9NaA37HtnXJ2QAWUfW52NG68w,13109
11
13
  taker/history_code/config.py,sha256=YPxghO5i0vgRg9Cja8kGj9O7pgSbbtzOgf3RexqXXwY,1188
12
- taker/main.py,sha256=gud7NagfxDrB4FrggmO4nKNEHbcn0_4jThCOaeGEJuw,2101
13
- openfund_taker-2.2.8.dist-info/METADATA,sha256=jWTmXIsLQO3gc8wvQ4ZcHzTmrTTgykgayHTgFZBLNpo,7529
14
- openfund_taker-2.2.8.dist-info/WHEEL,sha256=IYZQI976HJqqOpQU6PHkJ8fb3tMNBFjg-Cn-pwAbaFM,88
15
- openfund_taker-2.2.8.dist-info/entry_points.txt,sha256=a7mG8F7aOA5-Gk2vPWuAR4537faxaHUgM_jwIDBZoEc,50
16
- openfund_taker-2.2.8.dist-info/RECORD,,
14
+ taker/main.py,sha256=w1FaaDlqbWZkXffCA47mQTJ5n4uaE0kclc_teez6_Ek,2252
15
+ openfund_taker-2.3.1.dist-info/METADATA,sha256=CkKJcYe-zRX-yI_gNesX19Yk5cYasvBDzu9jEoT3c_8,7575
16
+ openfund_taker-2.3.1.dist-info/WHEEL,sha256=IYZQI976HJqqOpQU6PHkJ8fb3tMNBFjg-Cn-pwAbaFM,88
17
+ openfund_taker-2.3.1.dist-info/entry_points.txt,sha256=a7mG8F7aOA5-Gk2vPWuAR4537faxaHUgM_jwIDBZoEc,50
18
+ openfund_taker-2.3.1.dist-info/RECORD,,
@@ -0,0 +1,104 @@
1
+ from typing import override
2
+
3
+ from taker.StrategyTaker import StrategyTaker
4
+
5
+ class BestTopDownStrategyTaker(StrategyTaker):
6
+ """
7
+ 最佳顶底策略
8
+ """
9
+ def __init__(self, g_config, platform_config, common_config, monitor_interval=4, logger=None, exchangeKey='okx') -> None:
10
+ super().__init__(g_config=g_config, platform_config=platform_config, common_config=common_config, monitor_interval=monitor_interval, logger=logger, exchangeKey=exchangeKey)
11
+ self.has_init_SL_TPs = {}
12
+
13
+ @override
14
+ def rest_SL_TP(self, symbol):
15
+ super().rest_SL_TP(symbol)
16
+ if not symbol :
17
+ self.has_init_SL_TPs.clear()
18
+
19
+ elif symbol in self.has_init_SL_TPs:
20
+ del self.has_init_SL_TPs[symbol]
21
+
22
+ def init_SL_TP(self, symbol: str, position, tfs: dict, strategy: dict) -> bool:
23
+ """
24
+ 设置首次止盈止损
25
+ """
26
+
27
+ precision = self.get_precision_length(symbol)
28
+ # htf = tfs[self.HTF_KEY]
29
+ # etf = tfs[self.ETF_KEY]
30
+ atf = tfs[self.ATF_KEY]
31
+
32
+ # 1.1 ATF Key Support & Resistance Levels 支撑或阻力关键位置(ATF 看上下的供需区位置)
33
+ atf_df = self.get_historical_klines_df_by_cache(symbol=symbol, tf=atf)
34
+ atf_struct =self.build_struct(symbol=symbol, data=atf_df)
35
+ atf_OBs_df = self.find_OBs(symbol=symbol,struct=atf_struct)
36
+
37
+ atf_support_OB = self.get_lastest_OB(symbol=symbol,data=atf_OBs_df,trend=self.BULLISH_TREND)
38
+ if atf_support_OB :
39
+ atf_support_price = atf_support_OB.get(self.OB_MID_COL)
40
+ else:
41
+ atf_support_price = atf_struct.at[atf_struct.index[-1], self.STRUCT_LOW_COL]
42
+
43
+ atf_resistance_OB = self.get_lastest_OB(symbol=symbol,data=atf_OBs_df,trend=self.BEARISH_TREND)
44
+ if atf_resistance_OB :
45
+ atf_resistance_price = atf_resistance_OB.get(self.OB_MID_COL)
46
+ else:
47
+ atf_resistance_price = atf_struct.at[atf_struct.index[-1], self.STRUCT_HIGH_COL]
48
+ self.logger.info(f"{symbol} : ATF {atf}, Key Support={atf_support_price:.{precision}f} "
49
+ f"& Key Resistance={atf_resistance_price:.{precision}f} ")
50
+
51
+ side = self.SELL_SIDE if position[self.SIDE_KEY] == self.SHORT_KEY else self.BUY_SIDE # 和持仓反向相反下单
52
+ tick_size = self.get_tick_size(symbol)
53
+ offset = strategy.get('offset',1) # 价格偏移量, 1 代表 1 tick , 0.000001 代表 1 p
54
+ price_offset = offset * tick_size
55
+
56
+ if side == self.BUY_SIDE:
57
+ sl_price = self.toDecimal(atf_support_price) - self.toDecimal(price_offset)
58
+ tp_price = self.toDecimal(atf_resistance_price) + self.toDecimal(price_offset)
59
+ else:
60
+ sl_price = self.toDecimal(atf_resistance_price) + self.toDecimal(price_offset)
61
+ tp_price = self.toDecimal(atf_support_price) - self.toDecimal(price_offset)
62
+
63
+ self.cancel_all_algo_orders(symbol=symbol, attachType='SL')
64
+ self.set_stop_loss(symbol=symbol, position=position, sl_price=sl_price)
65
+ self.cancel_all_algo_orders(symbol=symbol, attachType='TP')
66
+ self.set_take_profit(symbol=symbol, position=position, tp_price=tp_price)
67
+
68
+
69
+ return True
70
+
71
+ @override
72
+ def process_pair(self, symbol: str, position, pair_config: dict) -> None:
73
+ """
74
+ 处理单个交易对
75
+ """
76
+
77
+ precision = self.get_precision_length(symbol)
78
+
79
+ top_down_strategy = pair_config.get('top_down_strategy',{})
80
+
81
+ """
82
+ 获取策略配置
83
+ """
84
+
85
+ tfs = {
86
+ self.HTF_KEY: str(top_down_strategy.get(self.HTF_KEY,'4h')) ,
87
+ self.ATF_KEY: str(top_down_strategy.get(self.ATF_KEY,'15m')),
88
+ self.ETF_KEY: str(top_down_strategy.get(self.ETF_KEY, '1m')),
89
+ }
90
+
91
+ htf = tfs[self.HTF_KEY]
92
+ atf = tfs[self.ATF_KEY]
93
+ etf = tfs[self.ETF_KEY]
94
+
95
+ self.logger.info(f"{symbol} : TopDownSMC策略 {htf}|{atf}|{etf} \n")
96
+
97
+ # 1.1 初始化止盈止损
98
+ if symbol not in self.has_init_SL_TPs:
99
+ has_pass = self.init_SL_TP(symbol, position, tfs, top_down_strategy)
100
+ if has_pass:
101
+ self.has_init_SL_TPs[symbol] = True
102
+
103
+
104
+ pass
taker/StrategyTaker.py ADDED
@@ -0,0 +1,670 @@
1
+ # -*- coding: utf-8 -*-
2
+ import traceback
3
+ import pandas as pd
4
+ import time
5
+ from functools import lru_cache
6
+ from datetime import datetime, timedelta
7
+ from decimal import Decimal
8
+ from abc import abstractmethod
9
+ from concurrent.futures import ThreadPoolExecutor, as_completed
10
+ from core.utils.OPTools import OPTools
11
+ from core.Exchange import Exchange
12
+ # 导入SMC相关模块
13
+ from core.smc import (
14
+ SMCBase,
15
+ SMCPDArray,
16
+ SMCStruct,
17
+ SMCOrderBlock,
18
+ SMCFVG
19
+ )
20
+
21
+ class StrategyTaker():
22
+ BUY_SIDE = 'buy'
23
+ SELL_SIDE = 'sell'
24
+ LONG_KEY = 'long'
25
+ SHORT_KEY = 'short'
26
+ SIDE_KEY = 'side'
27
+ SYMBOL_KEY = 'symbol'
28
+ ENTRY_PRICE_KEY = 'entryPrice'
29
+ MARK_PRICE_KEY = 'markPrice'
30
+ CONTRACTS_KEY = 'contracts'
31
+ BULLISH_TREND = 'Bullish'
32
+ BEARISH_TREND = 'Bearish'
33
+ HTF_KEY = 'htf'
34
+ ATF_KEY = 'atf'
35
+ ETF_KEY = 'etf'
36
+
37
+ HIGH_COL = SMCBase.SMCBase.HIGH_COL
38
+ LOW_COL = SMCBase.SMCBase.LOW_COL
39
+ CLOSE_COL = SMCBase.SMCBase.CLOSE_COL
40
+ OPEN_COL = SMCBase.SMCBase.OPEN_COL
41
+ TIMESTAMP_COL = SMCBase.SMCBase.TIMESTAMP_COL
42
+ VOLUME_COL = SMCBase.SMCBase.VOLUME_COL
43
+
44
+ STRUCT_COL = SMCStruct.SMCStruct.STRUCT_COL
45
+ STRUCT_HIGH_COL = SMCStruct.SMCStruct.STRUCT_HIGH_COL
46
+ STRUCT_LOW_COL = SMCStruct.SMCStruct.STRUCT_LOW_COL
47
+ STRUCT_MID_COL = SMCStruct.SMCStruct.STRUCT_MID_COL
48
+ STRUCT_HIGH_INDEX_COL = SMCStruct.SMCStruct.STRUCT_HIGH_INDEX_COL
49
+ STRUCT_LOW_INDEX_COL = SMCStruct.SMCStruct.STRUCT_LOW_INDEX_COL
50
+ STRUCT_DIRECTION_COL = SMCStruct.SMCStruct.STRUCT_DIRECTION_COL
51
+ HIGH_START_COL = SMCStruct.SMCStruct.HIGH_START_COL
52
+ LOW_START_COL = SMCStruct.SMCStruct.LOW_START_COL
53
+
54
+ OB_HIGH_COL = SMCOrderBlock.SMCOrderBlock.OB_HIGH_COL
55
+ OB_LOW_COL = SMCOrderBlock.SMCOrderBlock.OB_LOW_COL
56
+ OB_MID_COL = SMCOrderBlock.SMCOrderBlock.OB_MID_COL
57
+ OB_VOLUME_COL = SMCOrderBlock.SMCOrderBlock.OB_VOLUME_COL
58
+ OB_DIRECTION_COL = SMCOrderBlock.SMCOrderBlock.OB_DIRECTION_COL
59
+ OB_ATR = SMCOrderBlock.SMCOrderBlock.OB_ATR
60
+ OB_IS_COMBINED = SMCOrderBlock.SMCOrderBlock.OB_IS_COMBINED
61
+ OB_WAS_CROSSED = SMCOrderBlock.SMCOrderBlock.OB_WAS_CROSSED
62
+
63
+ PD_HIGH_COL = SMCPDArray.SMCPDArray.PD_HIGH_COL
64
+ PD_LOW_COL = SMCPDArray.SMCPDArray.PD_LOW_COL
65
+ PD_MID_COL = SMCPDArray.SMCPDArray.PD_MID_COL
66
+ PD_TYPE_COL = SMCPDArray.SMCPDArray.PD_TYPE_COL
67
+
68
+ def __init__(self, g_config, platform_config, common_config, monitor_interval=4, logger=None ,exchangeKey='okx'):
69
+ """_summary_
70
+ 初始化
71
+ Args:
72
+ config (_type_): _description_
73
+ platform_config (_type_): _description_
74
+ common_config (_type_): _description_
75
+ feishu_webhook (_type_, optional): _description_. Defaults to None.
76
+ logger (_type_, optional): _description_. Defaults to None.
77
+ """
78
+ self.logger = logger
79
+ self.g_config = g_config
80
+
81
+ self.common_config = common_config
82
+ self.monitor_interval = monitor_interval
83
+ self.feishu_webhook = self.common_config.get('feishu_webhook',"")
84
+
85
+ self.strategy_config = self.g_config.get('strategy', {})
86
+ self.trading_pairs_config = self.g_config.get('tradingPairs', {})
87
+
88
+ self.leverage_value = self.strategy_config.get('leverage', 20)
89
+ self.is_demo_trading = self.common_config.get('is_demo_trading', 1) # live trading: 0, demo trading: 1
90
+ proxies = {
91
+ "http": self.common_config.get('proxy', "http://localhost:7890"),
92
+ "https": self.common_config.get('proxy', "http://localhost:7890")
93
+ }
94
+ try:
95
+ self.exchange = Exchange({
96
+ 'apiKey': platform_config["apiKey"],
97
+ 'secret': platform_config["secret"],
98
+ 'password': platform_config["password"],
99
+ 'timeout': 3000,
100
+ 'rateLimit': 50,
101
+ 'options': {'defaultType': 'future'},
102
+ 'proxies': proxies
103
+ }, exchangeKey)
104
+ except Exception as e:
105
+ self.logger.error(f"连接交易所失败: {e}")
106
+ raise Exception(f"连接交易所失败: {e}")
107
+
108
+ self.smcPDArray = SMCPDArray.SMCPDArray()
109
+ self.smcStruct = SMCStruct.SMCStruct()
110
+ self.smcOB = SMCOrderBlock.SMCOrderBlock()
111
+ self.smcFVG = SMCFVG.SMCFVG()
112
+
113
+ self.interval_map = {
114
+ '1d': 24 * 60 * 60 , # 1天
115
+ '4h': 4 * 60 * 60 , # 4小时
116
+ '1h': 60 * 60 , # 1小时
117
+ '30m': 30 * 60 , # 30分钟
118
+ '15m': 15 * 60 , # 15分钟
119
+ '5m': 5 * 60 , # 5分钟
120
+ }
121
+
122
+ self.positions_entry_price = {} # 记录每个symbol的开仓价格
123
+ self.cache_time = {} # 记录缓存时间的字典
124
+ self.highest_total_profit = {} # 记录最高总盈利
125
+ self.stop_loss_prices = {} # 记录止损价格
126
+ self.take_profit_prices = {} # 记录止盈价格
127
+
128
+ def get_precision_length(self, symbol):
129
+ """_summary_
130
+ 获取价格的精度长度
131
+ Args:
132
+ price (_type_): _description_
133
+ Returns:
134
+ _type_: _description_
135
+ """
136
+ tick_size = self.exchange.get_tick_size(symbol)
137
+ return self.smcStruct.get_precision_length(tick_size)
138
+ def toDecimal(self, price):
139
+ """_summary_
140
+ 将价格转换为Decimal类型
141
+ Args:
142
+ price (_type_): _description_
143
+ Returns:
144
+ _type_: _description_
145
+ """
146
+ return OPTools.toDecimal(price)
147
+ def format_price(self, symbol, price:Decimal) -> str:
148
+ precision = self.get_precision_length(symbol)
149
+ return f"{price:.{precision}f}"
150
+
151
+ def get_pair_config(self,symbol):
152
+ # 获取交易对特定配置,如果没有则使用全局策略配置
153
+ pair_config = self.trading_pairs_config.get(symbol, {})
154
+
155
+ # 使用字典推导式合并配置,trading_pairs_config优先级高于strategy_config
156
+ pair_config = {
157
+ **self.strategy_config, # 基础配置
158
+ **pair_config # 交易对特定配置会覆盖基础配置
159
+ }
160
+ return pair_config
161
+
162
+
163
+ def send_feishu_notification(self, symbol, message):
164
+ if self.feishu_webhook:
165
+ try:
166
+ OPTools.send_feishu_notification(self.feishu_webhook,message)
167
+ except Exception as e:
168
+ self.logger.warning(f"{symbol} 发送飞书消息失败: {e}")
169
+
170
+ def get_tick_size(self, symbol):
171
+ """_summary_
172
+ 获取最小变动价格
173
+ Args:
174
+ symbol (_type_): _description_
175
+ Returns:
176
+ _type_: _description_
177
+ """
178
+ return self.exchange.get_tick_size(symbol)
179
+
180
+ def get_market_price(self, symbol):
181
+ """_summary_
182
+ 获取最新成交价
183
+ Args:
184
+ symbol (_type_): _description_
185
+ Returns:
186
+ _type_: _description_
187
+ """
188
+ return self.exchange.get_market_price(symbol)
189
+
190
+ def place_order(self, symbol, price:Decimal, side, pair_config, leverage:int=0, order_type='limit', params={}):
191
+ """_summary_
192
+ 下单
193
+ Args:
194
+ symbol (_type_): _description_
195
+ price (_type_): _description_
196
+ amount_usdt (_type_): _description_
197
+ side (_type_): _description_
198
+ order_type (_type_): _description_
199
+ """
200
+ # 获取做多和做空的下单金额配置
201
+ long_amount_usdt = pair_config.get('long_amount_usdt', 5)
202
+ short_amount_usdt = pair_config.get('short_amount_usdt', 5)
203
+
204
+ # 设置杠杆倍数
205
+ leverage = leverage or self.leverage_value
206
+
207
+ # 根据交易方向设置下单金额
208
+ order_amount_usdt = short_amount_usdt if side == self.SELL_SIDE else long_amount_usdt
209
+
210
+ # 记录下单日志
211
+ direction = self.BULLISH_TREND if side == self.BUY_SIDE else self.BEARISH_TREND
212
+ self.logger.debug(f"{symbol} : 触发{direction}下单条件。")
213
+
214
+ # 执行下单
215
+ self.exchange.place_order(
216
+ symbol=symbol,
217
+ price=price,
218
+ amount_usdt=order_amount_usdt,
219
+ side=side,
220
+ leverage=leverage,
221
+ order_type=order_type
222
+ )
223
+
224
+ def cancel_all_orders(self, symbol):
225
+ """_summary_
226
+ 取消所有挂单
227
+ Args:
228
+ symbol (_type_): _description_
229
+ """
230
+ self.exchange.cancel_all_orders(symbol=symbol)
231
+ def get_historical_klines(self, symbol, tf='15m'):
232
+ """_summary_
233
+ 获取历史K线数据
234
+ Args:
235
+ symbol (_type_): _description_
236
+ bar (_type_, optional): _description_. Defaults to '15m'.
237
+ Returns:
238
+ _type_: _description_
239
+ """
240
+ return self.exchange.get_historical_klines(symbol=symbol, bar=tf)
241
+
242
+ @lru_cache(maxsize=32) # 缓存最近32个不同的请求
243
+ def _get_cache_historical_klines_df(self, symbol, tf):
244
+ """被缓存的获取K线数据的方法"""
245
+ return self.get_historical_klines_df(symbol, tf)
246
+ def clear_cache_historical_klines_df(self, symbol=None):
247
+ """
248
+ 清除指定交易对和时间周期的缓存
249
+
250
+ 参数:
251
+ symbol (str, optional): 交易对符号,如为None则清除所有缓存
252
+ tf (str, optional): 时间周期,如为None则清除所有缓存
253
+ """
254
+ if symbol is None:
255
+ # 清除所有缓存
256
+ self._get_cache_historical_klines_df.cache_clear()
257
+ self.cache_time.clear()
258
+ # print("已清除所有K线数据缓存")
259
+ else:
260
+ # 删除所有包含cache_key的缓存
261
+ keys_to_delete = [k for k in self.cache_time.keys() if symbol in k]
262
+ if keys_to_delete:
263
+ for k in keys_to_delete:
264
+ del self.cache_time[k]
265
+ # 由于lru_cache无法单独清除特定键,这里只能清除所有缓存
266
+ self._get_cache_historical_klines_df.cache_clear()
267
+
268
+
269
+ def get_historical_klines_df_by_cache(self, symbol, tf='15m'):
270
+ """_summary_
271
+ 获取历史K线数据
272
+ Args:
273
+ symbol (_type_): _description_
274
+ bar (_type_, optional): _description_. Defaults to '15m'.
275
+ Returns:
276
+ _type_: _description_
277
+ """
278
+ # cache_key = (symbol, tf)
279
+ cache_valid_second = self.interval_map.get(tf, 4 * 60 * 60) # 默认缓存时间为60分钟
280
+ cache_key = (symbol, tf)
281
+
282
+ # 检查缓存是否存在且未过期
283
+ current_time = datetime.now()
284
+ if cache_key in self.cache_time:
285
+ # 计算缓存时间与当前时间的差值(秒)
286
+ cache_age = (current_time - self.cache_time[cache_key]).total_seconds()
287
+ if cache_age <= cache_valid_second:
288
+ # 缓存有效,直接返回
289
+ # print(f"使用缓存数据: {symbol} {tf} (缓存时间: {cache_age:.2f} 分钟前)")
290
+ return self._get_cache_historical_klines_df(symbol, tf)
291
+ else:
292
+ # 缓存过期,清除缓存
293
+ self.logger.debug(f"{symbol} : 缓存已过期: {symbol} {tf} (缓存时间: {cache_age:.2f} 秒前)")
294
+ self._get_cache_historical_klines_df.cache_clear()
295
+
296
+ # 获取新数据并更新缓存时间
297
+ self.logger.debug(f"{symbol} : 重新获取新数据: {symbol} {tf}")
298
+ self.cache_time[cache_key] = current_time
299
+ return self._get_cache_historical_klines_df(symbol, tf)
300
+
301
+
302
+ def get_historical_klines_df(self, symbol, tf='15m'):
303
+ """_summary_
304
+ 获取历史K线数据
305
+ Args:
306
+ symbol (_type_): _description_
307
+ bar (_type_, optional): _description_. Defaults to '15m'.
308
+ Returns:
309
+ _type_: _description_
310
+ """
311
+ return self.exchange.get_historical_klines_df(symbol=symbol, bar=tf)
312
+ def format_klines(self, klines) -> pd.DataFrame:
313
+
314
+ """_summary_
315
+ 格式化K线数据
316
+ Args:
317
+ klines (_type_): _description_
318
+ Returns:
319
+ _type_: _description_
320
+ """
321
+
322
+ return self.exchange.format_klines(klines)
323
+
324
+ def find_PDArrays(self, symbol, struct, side=None, start_index=-1, pair_config=None) -> pd.DataFrame:
325
+ """_summary_
326
+ 寻找PDArray
327
+ Args:
328
+ symbol (_type_): _description_
329
+ data (_type_): _description_
330
+ side (_type_): _description_
331
+ start_index (_type_): _description_
332
+ is_valid (bool, optional): _description_. Defaults to True.
333
+ pair_config (_type_): _description_
334
+ Returns:
335
+ _type_: _description_
336
+ """
337
+ return self.smcPDArray.find_PDArrays(struct=struct, side=side, start_index=start_index)
338
+
339
+ def find_OBs(self, symbol, struct, side=None, start_index=-1, is_valid=True, pair_config=None) -> pd.DataFrame:
340
+ """_summary_
341
+ 识别OB
342
+ Args:
343
+ symbol (_type_): _description_
344
+ data (_type_): _description_
345
+ side (_type_): _description_
346
+ start_index (_type_): _description_
347
+ is_valid (bool, optional): _description_. Defaults to True.
348
+ pair_config (_type_): _description_
349
+ Returns:
350
+ _type_: _description_
351
+ """
352
+
353
+
354
+ return self.smcOB.find_OBs(struct=struct, side=side, start_index=start_index, is_valid=is_valid)
355
+
356
+ def get_lastest_OB(self, symbol, data, trend, start_index=-1) -> dict:
357
+ """_summary_
358
+ 获取最新的Order Block
359
+ Args:
360
+ symbol (_type_): _description_
361
+ data (_type_): _description_
362
+ trend (_type_): _description_
363
+ start_index (_type_): _description_
364
+ Returns:
365
+ _type_: _description_
366
+ """
367
+
368
+ return self.smcOB.get_lastest_OB(data=data, trend=trend, start_index=start_index)
369
+
370
+
371
+ def find_FVGs(self, symbol, data, side, check_balanced=True, start_index=-1, pair_config=None) -> pd.DataFrame:
372
+ """_summary_
373
+ 寻找公允价值缺口
374
+ Args:
375
+ symbol (_type_): _description_
376
+ data (_type_): _description_
377
+ side (_type_): _description_
378
+ check_balanced (bool, optional): _description_. Defaults to True.
379
+ start_index (_type_): _description_
380
+ pair_config (_type_): _description_
381
+ Returns:
382
+ _type_: _description_
383
+ """
384
+
385
+
386
+ return self.smcFVG.find_FVGs(data, side, check_balanced, start_index)
387
+
388
+ def build_struct(self, symbol, data) -> pd.DataFrame:
389
+
390
+ """_summary_
391
+ 构建SMC结构,参考 Tradingview OP@SMC Structures and FVG
392
+ Args:
393
+ symbol (_type_): _description_
394
+ data (_type_): _description_
395
+ Returns:
396
+ _type_: _description_
397
+ """
398
+
399
+
400
+ return self.smcStruct.build_struct(data)
401
+
402
+ def get_last_struct(self, symbol, data) -> dict:
403
+ """_summary_
404
+ 获取最后一个SMC结构
405
+ Args:
406
+ symbol (_type_): _description_
407
+ data (_type_): _description_
408
+ Returns:
409
+ _type_: _description_
410
+ """
411
+ return self.smcStruct.get_last_struct(data)
412
+ def reset_highest_profit_and_tier(self,symbol=None):
413
+ """重置最高总盈利和当前档位状态"""
414
+ if not symbol:
415
+ self.highest_total_profit.clear()
416
+ else :
417
+ if symbol in self.highest_total_profit:
418
+ self.highest_total_profit[symbol] = 0.0
419
+
420
+ def rest_SL_TP(self, symbol):
421
+ """_summary_
422
+ 重置止盈止损
423
+ """
424
+ if not symbol:
425
+ self.stop_loss_prices.clear()
426
+ self.take_profit_prices.clear()
427
+ else :
428
+ if symbol in self.stop_loss_prices:
429
+ del self.stop_loss_prices[symbol]
430
+ if symbol in self.take_profit_prices:
431
+ del self.take_profit_prices[symbol]
432
+
433
+ def close_all_cache(self):
434
+ self.clear_cache_historical_klines_df()
435
+ self.positions_entry_price ={}
436
+
437
+ self.reset_highest_profit_and_tier()
438
+ self.rest_SL_TP()
439
+
440
+
441
+
442
+ def reset_all_cache(self, symbol):
443
+ """_summary_
444
+ 重置所有缓存数据
445
+ """
446
+ if symbol in self.positions_entry_price:
447
+ del self.positions_entry_price[symbol]
448
+
449
+ self.reset_highest_profit_and_tier(symbol)
450
+ self.clear_cache_historical_klines_df(symbol)
451
+ self.rest_SL_TP(symbol)
452
+
453
+ def fetch_positions(self):
454
+ """_summary_
455
+ 获取所有持仓
456
+ Returns:
457
+ _type_: _description_
458
+ """
459
+ return self.exchange.fetch_positions()
460
+ def check_position(self, symbol, position):
461
+ # TODO 检查持仓是否有异常,比如未设置止损
462
+ self.logger.debug(f"{symbol} : 检查持仓是否有异常。")
463
+ pass
464
+
465
+ def cancel_all_algo_orders(self, symbol, attachType: str = None):
466
+ """取消止盈止损订单
467
+ Args:
468
+ symbol: 交易对
469
+ attachType: 订单类型,'SL'表示止损单,'TP'表示止盈单,None表示不不区分
470
+ """
471
+
472
+ self.exchange.cancel_all_algo_orders(symbol=symbol, attachType=attachType)
473
+ if attachType == "SL" and symbol in self.stop_loss_prices:
474
+ del self.stop_loss_prices[symbol]
475
+ elif attachType == "TP" and symbol in self.take_profit_prices:
476
+ del self.take_profit_prices[symbol]
477
+ else:
478
+ self.rest_SL_TP(symbol)
479
+
480
+ def set_take_profit(self, symbol, position, tp_price:Decimal=None, order_type='optimal_limit_ioc') -> bool:
481
+
482
+ """
483
+ 设置止盈单
484
+ Args:
485
+ symbol: 交易对
486
+ position: 持仓信息
487
+ tp_price: 止盈价格
488
+ order_type: 订单类型 ord_type: 'optimal_limit_ioc'|'market'|'limit'
489
+ Returns:
490
+ 是否成功设置止盈单
491
+ """
492
+ if tp_price is None:
493
+ self.logger.waring(f"{symbol}: TP must greater than 0.0.")
494
+ return False
495
+ precision =self.get_precision_length(symbol)
496
+ if symbol in self.take_profit_prices:
497
+ last_tp_price = self.take_profit_prices[symbol]
498
+ if tp_price and tp_price == last_tp_price:
499
+ self.logger.debug(f"{symbol}: TP at {tp_price:.{precision}f} Already set.")
500
+ return True
501
+
502
+ self.logger.debug(f"{symbol}: TP at {tp_price:.{precision}f} Starting.... ")
503
+ try:
504
+ has_pass = self.exchange.place_algo_orders(symbol=symbol, position=position, price=tp_price, order_type=order_type, sl_or_tp='TP')
505
+ except Exception as e:
506
+ has_pass = False
507
+ error_message = f"{symbol}: TP at {tp_price:.{precision}f} Failed. {e}"
508
+ self.logger.error(error_message)
509
+ self.send_feishu_notification(symbol, error_message)
510
+
511
+ if has_pass:
512
+ self.take_profit_prices[symbol] = tp_price
513
+ self.logger.info(f"{symbol}: TP at {tp_price:.{precision}f} Done.")
514
+
515
+ return has_pass
516
+
517
+ def set_stop_loss(self, symbol, position, sl_price:Decimal=None , order_type='conditional') -> bool:
518
+ """
519
+ 设置止盈单
520
+ Args:
521
+ symbol: 交易对
522
+ position: 持仓信息
523
+ sl_price: 止盈价格
524
+ order_type: 订单类型 ord_type: 'conditional'|'market'|'limit'
525
+ Returns:
526
+ 是否成功设置止盈单
527
+ """
528
+
529
+ if sl_price is None:
530
+ self.logger.waring(f"{symbol}: SL must greater than 0.0.")
531
+ return False
532
+ precision =self.get_precision_length(symbol)
533
+ if symbol in self.stop_loss_prices:
534
+ last_sl_price = self.stop_loss_prices[symbol]
535
+ if sl_price and sl_price == last_sl_price:
536
+ self.logger.debug(f"{symbol}: SL at {sl_price:.{precision}f} Already set.")
537
+ return True
538
+ self.logger.debug(f"{symbol}: SL at {sl_price:.{precision}f} Starting.... ")
539
+ try:
540
+ has_pass = self.exchange.place_algo_orders(symbol=symbol, position=position, price=sl_price, order_type=order_type, sl_or_tp='SL')
541
+ except Exception as e:
542
+ has_pass = False
543
+ error_message = f"{symbol}: SL at {sl_price:.{precision}f} Failed. {e}"
544
+ self.logger.error(error_message)
545
+ self.send_feishu_notification(symbol, error_message)
546
+
547
+ if has_pass:
548
+ self.stop_loss_prices[symbol] = sl_price
549
+ self.logger.info(f"{symbol}: SL at {sl_price:.{precision}f} Done.")
550
+
551
+ return has_pass
552
+
553
+ # 计算平均利润
554
+ def calculate_average_profit(self,symbol,position):
555
+
556
+ total_profit_pct = self.toDecimal(0.0)
557
+ num_positions = 0
558
+
559
+ entry_price = self.toDecimal(position[self.ENTRY_PRICE_KEY])
560
+ current_price = self.toDecimal(position[self.MARK_PRICE_KEY])
561
+ side = position[self.SIDE_KEY]
562
+
563
+ # 计算单个仓位的浮动盈利百分比
564
+ if side not in [self.LONG_KEY,self.SHORT_KEY]:
565
+ return
566
+
567
+ # 使用三元运算符简化计算逻辑
568
+ profit_pct = ((current_price - entry_price) if side == self.LONG_KEY else (entry_price - current_price)) / entry_price * 100
569
+
570
+ # 累加总盈利百分比
571
+ total_profit_pct += profit_pct
572
+ num_positions += 1
573
+ # 记录单个仓位的盈利情况
574
+ precision = self.get_precision_length(symbol)
575
+ self.logger.info(f"仓位 {symbol},方向: {side},开仓价格: {entry_price:.{precision}},当前价格: {current_price:.{precision}},"
576
+ f"浮动盈亏: {profit_pct:.2f}%")
577
+
578
+ # 计算平均浮动盈利百分比
579
+ average_profit_pct = total_profit_pct / num_positions if num_positions > 0 else 0
580
+ return average_profit_pct
581
+
582
+ def check_total_profit(self, symbol, position):
583
+ """
584
+ 检查当前总盈利
585
+ """
586
+
587
+ total_profit = self.calculate_average_profit(symbol, position)
588
+ cur_highest_total_profit = self.highest_total_profit.get(symbol, 0.0)
589
+ if total_profit > cur_highest_total_profit:
590
+ self.highest_total_profit[symbol] = total_profit
591
+
592
+ precision = self.get_precision_length(symbol)
593
+ entryPrice = self.toDecimal(position[self.ENTRY_PRICE_KEY])
594
+ marketPrice = self.toDecimal(position[self.MARK_PRICE_KEY])
595
+ msg = f"{symbol} : 盈利={total_profit:.2f}% 方向={position[self.SIDE_KEY]} 开仓={entryPrice:.{precision}f} 市价={marketPrice:.{precision}f}"
596
+ self.logger.info(msg)
597
+ self.send_feishu_notification(symbol, msg)
598
+
599
+ @abstractmethod
600
+ def process_pair(self, symbol, position, pair_config):
601
+ """
602
+ 处理单个交易对的策略逻辑
603
+
604
+ Args:
605
+ symbol: 交易对名称
606
+ pair_config: 交易对配置信息
607
+
608
+ Raises:
609
+ NotImplementedError: 子类必须实现此方法
610
+ """
611
+ raise NotImplementedError("必须在子类中实现process_pair方法")
612
+
613
+ def monitor_total_profit(self):
614
+ self.logger.info("启动主循环,开始监控总盈利...")
615
+
616
+ while True:
617
+
618
+ try:
619
+ positions = self.fetch_positions()
620
+ # 检查是否有仓位
621
+ if len(positions) == 0:
622
+ # self.logger.debug("没有持仓,等待下一次检查...")
623
+ self.close_all_cache()
624
+ time.sleep(1)
625
+ continue
626
+
627
+ except Exception as e:
628
+ error_message = f"!!获取持仓失败: {str(e)}"
629
+ self.logger.error(error_message)
630
+ self.send_feishu_notification(" ",error_message)
631
+ continue
632
+
633
+ try:
634
+ self.logger.info("-" * 60)
635
+
636
+ for position in positions:
637
+ symbol = position[self.SYMBOL_KEY]
638
+ cur_entry_price = self.toDecimal(position[self.ENTRY_PRICE_KEY])
639
+
640
+ if symbol in self.positions_entry_price and cur_entry_price != self.positions_entry_price[symbol]:
641
+ # 新开仓
642
+ self.reset_all_cache(symbol)
643
+
644
+ if symbol not in self.positions_entry_price:
645
+ self.positions_entry_price[symbol] = cur_entry_price
646
+ precision = self.get_precision_length(symbol)
647
+ msg = f"{symbol} : ## 重新开仓。 入场方向={position[self.SIDE_KEY]} 入场价格={cur_entry_price:.{precision}} ##"
648
+ self.logger.info(msg)
649
+ self.send_feishu_notification(symbol, msg)
650
+
651
+ self.check_total_profit(symbol, position)
652
+ self.process_pair(symbol, position, self.get_pair_config(symbol))
653
+ time.sleep(0.1)
654
+ # 检查仓位和挂单是否有问题
655
+ self.check_position(symbol, position)
656
+
657
+ # self.logger.info("=" * 60 + "\n")
658
+ time.sleep(self.monitor_interval)
659
+
660
+ except Exception as e:
661
+ error_message = f"程序异常退出: {str(e)}"
662
+ self.logger.error(error_message, exc_info=True)
663
+ self.send_feishu_notification(symbol, error_message)
664
+
665
+ continue
666
+ except KeyboardInterrupt:
667
+ self.logger.info("程序收到中断信号,开始退出...")
668
+ break
669
+ finally:
670
+ self.logger.info("=" * 60 + "\n")
taker/main.py CHANGED
@@ -1,57 +1,64 @@
1
1
  import logging
2
2
  import logging.config
3
3
  import yaml
4
-
5
- from taker.TrailingSLTaker import TrailingSLTaker
6
- from taker.TrailingSLAndTPTaker import TrailingSLAndTPTaker
7
- from taker.ThreeLineTradingTaker import ThreeLineTradingTaker
8
- from taker.SMCSLAndTPTaker import SMCSLAndTPTaker
4
+ import importlib
5
+ import importlib.metadata
6
+ from pathlib import Path
7
+ from typing import Dict, Any
9
8
  from pyfiglet import Figlet
10
-
11
- def read_config_file(file_path):
9
+
10
+ def read_config_file(file_path: str) -> Dict[str, Any]:
11
+ """读取并解析YAML配置文件"""
12
12
  try:
13
- # 打开 YAML 文件
14
13
  with open(file_path, 'r', encoding='utf-8') as file:
15
- # 使用 yaml.safe_load 方法解析 YAML 文件内容
16
- data = yaml.safe_load(file)
17
- return data
14
+ return yaml.safe_load(file)
18
15
  except FileNotFoundError:
19
- raise Exception(f"文件 {file_path} 未找到。")
16
+ raise FileNotFoundError(f"文件 {file_path} 未找到。")
20
17
  except yaml.YAMLError as e:
21
- raise Exception(f"解析 {file_path} 文件时出错: {e}")
18
+ raise yaml.YAMLError(f"解析 {file_path} 文件时出错: {e}")
22
19
 
20
+ def initialize_logger(config: Dict[str, Any]) -> logging.Logger:
21
+ """初始化日志配置并返回logger实例"""
22
+ logging.config.dictConfig(config["Logger"])
23
+ return logging.getLogger("openfund-taker")
24
+
25
+ def create_strategy_instance(taker_name: str, configs: Dict[str, Any], logger: logging.Logger, monitor_interval: int, exchangeKey:str):
26
+ """创建策略实例"""
27
+ module = importlib.import_module(f"taker.{taker_name}")
28
+ strategy_class = getattr(module, taker_name)
29
+ return strategy_class(
30
+ configs,
31
+ configs['platform'][exchangeKey],
32
+ configs['common'],
33
+ monitor_interval=monitor_interval,
34
+ logger=logger
35
+ )
23
36
 
24
37
  def main():
25
- import importlib.metadata
38
+ # 获取版本信息和包名
26
39
  version = importlib.metadata.version("openfund-taker")
40
+ package_name = __package__ or "taker"
27
41
 
28
- package_name = __package__ or "taker"
29
-
30
- openfund_config_path = 'taker_config.yaml'
31
- config_data = read_config_file(openfund_config_path)
42
+ # 读取配置文件
43
+ config_path = Path('taker_config.yaml')
44
+ config_data = read_config_file(config_path)
32
45
 
33
-
34
- platform_config = config_data['platform']["okx"]
46
+ # 获取配置参数
35
47
  common_config = config_data['common']
36
- taker = common_config.get('actived_taker', 'SMCSLAndTPTaker')
37
- feishu_webhook_url = common_config['feishu_webhook']
38
- monitor_interval = common_config.get("monitor_interval", 60) # 默认值为60秒
39
-
40
- logging.config.dictConfig(config_data["Logger"])
41
- logger = logging.getLogger("openfund-taker")
42
-
43
- f = Figlet(font="standard") # 字体可选(如 "block", "bubble")
44
- logger.info(f"\n{f.renderText("OpenFund Taker")}")
45
-
46
- logger.info(f" ++ {package_name}.{taker}:{version} is doing...")
47
- # 根据配置动态创建策略实例
48
- strategy_class = globals()[taker]
49
- bot = strategy_class(config_data, platform_config, common_config, feishu_webhook=feishu_webhook_url, monitor_interval=monitor_interval,logger=logger)
50
-
48
+ taker_name = common_config.get('actived_taker', 'StrategyTaker')
49
+ monitor_interval = common_config.get("monitor_interval", 60)
51
50
 
51
+ # 初始化日志
52
+ logger = initialize_logger(config_data)
53
+
54
+ # 显示启动信息
55
+ f = Figlet(font="standard")
56
+ logger.info(f"\n{f.renderText('OpenFund Taker')}")
57
+ logger.info(f" ++ {package_name}.{taker_name}:{version} is doing...")
58
+ exchangeKey = common_config.get("exchange_key", "okx")
59
+ # 创建并运行策略实例
60
+ bot = create_strategy_instance(taker_name, config_data, logger, monitor_interval, exchangeKey)
52
61
  bot.monitor_total_profit()
53
- # bot = ThreeLineTradingBot(platform_config, feishu_webhook=feishu_webhook_url, monitor_interval=monitor_interval)
54
- # bot.monitor_klines()
55
62
 
56
63
  if __name__ == "__main__":
57
64
  main()