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.
- {openfund_taker-2.2.8.dist-info → openfund_taker-2.3.1.dist-info}/METADATA +2 -1
- {openfund_taker-2.2.8.dist-info → openfund_taker-2.3.1.dist-info}/RECORD +7 -5
- taker/BestTopDownStrategyTaker.py +104 -0
- taker/StrategyTaker.py +670 -0
- taker/main.py +44 -37
- {openfund_taker-2.2.8.dist-info → openfund_taker-2.3.1.dist-info}/WHEEL +0 -0
- {openfund_taker-2.2.8.dist-info → openfund_taker-2.3.1.dist-info}/entry_points.txt +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.3
|
2
2
|
Name: openfund-taker
|
3
|
-
Version: 2.
|
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=
|
13
|
-
openfund_taker-2.
|
14
|
-
openfund_taker-2.
|
15
|
-
openfund_taker-2.
|
16
|
-
openfund_taker-2.
|
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
|
-
|
6
|
-
from
|
7
|
-
from
|
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
|
-
|
16
|
-
data = yaml.safe_load(file)
|
17
|
-
return data
|
14
|
+
return yaml.safe_load(file)
|
18
15
|
except FileNotFoundError:
|
19
|
-
raise
|
16
|
+
raise FileNotFoundError(f"文件 {file_path} 未找到。")
|
20
17
|
except yaml.YAMLError as e:
|
21
|
-
raise
|
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
|
-
|
38
|
+
# 获取版本信息和包名
|
26
39
|
version = importlib.metadata.version("openfund-taker")
|
40
|
+
package_name = __package__ or "taker"
|
27
41
|
|
28
|
-
|
29
|
-
|
30
|
-
|
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
|
-
|
37
|
-
|
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()
|
File without changes
|
File without changes
|