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