openfund-maker 2.2.5__py3-none-any.whl → 2.2.7__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
maker/main_m.py DELETED
@@ -1,378 +0,0 @@
1
- import time
2
- import json
3
- import logging
4
- import requests
5
- import pandas as pd
6
-
7
- import okx.PublicData as PublicData
8
- import okx.Trade as Trade
9
- import okx.MarketData as MarketData
10
- import okx.Account as Account
11
-
12
- from concurrent.futures import ThreadPoolExecutor, as_completed
13
- from logging.handlers import TimedRotatingFileHandler
14
-
15
- # import app.okx_buou.Trade_api as TradeAPI
16
- # import app.okx_buou.Public_api as PublicAPI
17
- # import app.okx_buou.Market_api as MarketAPI
18
- # import app.okx_buou.Account_api as AccountAPI
19
-
20
- import os
21
- openfund_config_path = os.getenv('wick_reversal_config_path','config_okx.json')
22
- # 读取配置文件
23
- with open(openfund_config_path, 'r') as f:
24
- config = json.load(f)
25
-
26
- # 提取配置
27
- okx_config = config['okx']
28
- trading_pairs_config = config.get('tradingPairs', {})
29
- monitor_interval = config.get('monitor_interval', 60) # 默认60秒
30
- feishu_webhook = config.get('feishu_webhook', '')
31
- leverage_value = config.get('leverage', 10)
32
- api_key = okx_config["apiKey"]
33
- secret_key = okx_config["secret"]
34
- passphrase = okx_config["password"]
35
- flag = "0" # live trading: 0, demo trading: 1
36
-
37
- public_api = PublicData.PublicAPI(api_key, secret_key, passphrase, False, flag)
38
- trade_api = Trade.TradeAPI(api_key, secret_key, passphrase, False, flag)
39
- market_api = MarketData.MarketAPI(api_key, secret_key, passphrase, False, flag)
40
- account_api = Account.AccountAPI(api_key, secret_key, passphrase, False, flag)
41
- # trade_api = TradeAPI.TradeAPI(okx_config["apiKey"], okx_config["secret"], okx_config["password"], False, flag)
42
- # market_api = MarketAPI.MarketAPI(okx_config["apiKey"], okx_config["secret"], okx_config["password"], False, flag)
43
- # public_api = PublicAPI.PublicAPI(okx_config["apiKey"], okx_config["secret"], okx_config["password"], False, flag)
44
- # account_api = AccountAPI.AccountAPI(okx_config["apiKey"], okx_config["secret"], okx_config["password"], False, flag)
45
-
46
- log_file = "log/okx.log"
47
- logger = logging.getLogger(__name__)
48
- logger.setLevel(logging.DEBUG)
49
-
50
- file_handler = TimedRotatingFileHandler(log_file, when='midnight', interval=1, backupCount=7, encoding='utf-8')
51
- file_handler.suffix = "%Y-%m-%d"
52
- formatter = logging.Formatter('%(asctime)s - %(name)s -%(lineno)d - %(levelname)s - %(message)s')
53
- file_handler.setFormatter(formatter)
54
- logger.addHandler(file_handler)
55
-
56
- console_handler = logging.StreamHandler()
57
- console_handler.setFormatter(formatter)
58
- logger.addHandler(console_handler)
59
-
60
- instrument_info_dict = {}
61
-
62
- def fetch_and_store_all_instruments(instType='SWAP'):
63
- try:
64
- logger.info(f"Fetching all instruments for type: {instType}")
65
-
66
- response = public_api.get_instruments(instType=instType)
67
- # response = public_api.get_instruments(instType=instType)
68
- # logger.debug(f"data: {response['data']}")
69
- if 'data' in response and len(response['data']) > 0:
70
- instrument_info_dict.clear()
71
- for instrument in response['data']:
72
- instId = instrument['instId']
73
- instrument_info_dict[instId] = instrument
74
- # logger.debug(f"Stored instrument: {instId}")
75
- else:
76
- raise ValueError("Unexpected response structure or no instrument data available")
77
- except Exception as e:
78
- logger.error(f"Error fetching instruments: {e}")
79
- raise
80
-
81
- def send_feishu_notification(message):
82
- if feishu_webhook:
83
- headers = {'Content-Type': 'application/json'}
84
- data = {"msg_type": "text", "content": {"text": message}}
85
- response = requests.post(feishu_webhook, headers=headers, json=data)
86
- if response.status_code == 200:
87
- logger.debug("飞书通知发送成功")
88
- else:
89
- logger.error(f"飞书通知发送失败: {response.text}")
90
-
91
- def get_close_price(instId):
92
- '''
93
- bar =
94
- 时间粒度,默认值1m
95
- 如 [1m/3m/5m/15m/30m/1H/2H/4H]
96
- 香港时间开盘价k线:[6H/12H/1D/2D/3D/1W/1M/3M]
97
- UTC时间开盘价k线:[/6Hutc/12Hutc/1Dutc/2Dutc/3Dutc/1Wutc/1Mutc/3Mutc]
98
- '''
99
- response = market_api.get_candlesticks(instId=instId,bar='1m')
100
- if 'data' in response and len(response['data']) > 0:
101
- close_price = response['data'][0][4]
102
- return float(close_price)
103
- else:
104
- raise ValueError("Unexpected response structure or missing 'c' value")
105
-
106
-
107
- def get_mark_price(instId):
108
- response = market_api.get_ticker(instId)
109
- if 'data' in response and len(response['data']) > 0:
110
- last_price = response['data'][0]['last']
111
- return float(last_price)
112
- else:
113
- raise ValueError("Unexpected response structure or missing 'last' key")
114
-
115
- def round_price_to_tick(price, tick_size):
116
- # 计算 tick_size 的小数位数
117
- tick_decimals = len(f"{tick_size:.10f}".rstrip('0').split('.')[1]) if '.' in f"{tick_size:.10f}" else 0
118
-
119
- # 调整价格为 tick_size 的整数倍
120
- adjusted_price = round(price / tick_size) * tick_size
121
- return f"{adjusted_price:.{tick_decimals}f}"
122
-
123
- def get_historical_klines(instId, bar='1m', limit=241):
124
- response = market_api.get_candlesticks(instId, bar=bar, limit=limit)
125
- if 'data' in response and len(response['data']) > 0:
126
- return response['data']
127
- else:
128
- raise ValueError("Unexpected response structure or missing candlestick data")
129
-
130
- def calculate_atr(klines, period=60):
131
- trs = []
132
- for i in range(1, len(klines)):
133
- high = float(klines[i][2])
134
- low = float(klines[i][3])
135
- prev_close = float(klines[i-1][4])
136
- tr = max(high - low, abs(high - prev_close), abs(low - prev_close))
137
- trs.append(tr)
138
- atr = sum(trs[-period:]) / period
139
- return atr
140
-
141
- def calculate_ema_pandas(data, period):
142
- """
143
- 使用 pandas 计算 EMA
144
- :param 收盘价列表
145
- :param period: EMA 周期
146
- :return: EMA 值
147
- """
148
- df = pd.Series(data)
149
- ema = df.ewm(span=period, adjust=False).mean()
150
- return ema.iloc[-1] # 返回最后一个 EMA 值
151
-
152
-
153
- def calculate_average_amplitude(klines, period=60):
154
- amplitudes = []
155
- for i in range(len(klines) - period, len(klines)):
156
- high = float(klines[i][2])
157
- low = float(klines[i][3])
158
- close = float(klines[i][4])
159
- amplitude = ((high - low) / close) * 100
160
- amplitudes.append(amplitude)
161
- average_amplitude = sum(amplitudes) / len(amplitudes)
162
- return average_amplitude
163
-
164
- def cancel_all_orders(instId):
165
- open_orders = trade_api.get_order_list(instId=instId, state='live')
166
- order_ids = [order['ordId'] for order in open_orders['data']]
167
- for ord_id in order_ids:
168
- trade_api.cancel_order(instId=instId, ordId=ord_id)
169
- logger.info(f"{instId}挂单取消成功.")
170
-
171
- def set_leverage(instId, leverage, mgnMode='isolated',posSide=None):
172
- try:
173
- body = {
174
- "instId": instId,
175
- "lever": str(leverage),
176
- "mgnMode": mgnMode
177
- }
178
- # 模拟盘需要控制 posSide
179
- if flag =='1' and mgnMode == 'isolated' and posSide:
180
- body["posSide"] = posSide
181
- logger.debug(f"Leverage set parameter is:{body}")
182
- response = account_api.set_leverage(**body)
183
- if response['code'] == '0':
184
- logger.debug(f"Leverage set to {leverage}x for {instId} with mgnMode: {mgnMode}")
185
- else:
186
- logger.error(f"Failed to set leverage: {response['msg']}")
187
- except Exception as e:
188
- logger.error(f"Error setting leverage: {e}")
189
-
190
- def place_order(instId, price, amount_usdt, side):
191
- if instId not in instrument_info_dict:
192
- logger.error(f"Instrument {instId} not found in instrument info dictionary")
193
- return
194
- tick_size = float(instrument_info_dict[instId]['tickSz'])
195
- adjusted_price = round_price_to_tick(price, tick_size)
196
- # response = public_api.convert_contract_coin(type='1', instId=instId, sz=str(amount_usdt), px=str(adjusted_price), unit='usdt', opType='open')
197
-
198
- # https://www.okx.com/docs-v5/zh/#public-data-rest-api-unit-convert
199
- '''
200
- type 转换类型
201
- 1:币转张
202
- 2:张转币
203
- 默认为1
204
- '''
205
-
206
- response = public_api.get_convert_contract_coin(type='1', instId=instId, sz=str(amount_usdt), px=str(adjusted_price), unit='usdt')
207
- if response['code'] == '0':
208
- sz = response['data'][0]['sz']
209
- if float(sz) > 0:
210
-
211
- if side == 'buy':
212
- pos_side = 'long'
213
- else:
214
- pos_side = 'short'
215
-
216
- set_leverage(instId=instId, leverage=leverage_value, mgnMode='isolated',posSide=pos_side)
217
-
218
-
219
- params = {
220
- "instId": instId,
221
- "tdMode": 'isolated',
222
- "side": side,
223
- "ordType": 'limit',
224
- "sz": sz,
225
- "px": str(adjusted_price)
226
- }
227
- # 模拟盘需要控制 posSide
228
- if flag == 1 :
229
- params["posSide"] = pos_side
230
-
231
- logger.info(f"Order placed params: {params}")
232
- order_result = trade_api.place_order(
233
- **params
234
- # instId=instId,
235
- # #tdMode='isolated',
236
- # tdMode='cross',# 保证金模式:isolated:逐仓 ;cross:全仓
237
- # side=side, # 订单方向 buy:买, sell:卖
238
- # posSide=pos_side, #持仓方向 在开平仓模式下必填,且仅可选择 long 或 short。 仅适用交割、永续。
239
- # ordType='limit',
240
- # sz=sz,
241
- # px=str(adjusted_price)
242
- )
243
- logger.debug(f"Order placed: {order_result}")
244
- else:
245
- logger.info(f"{instId}计算出的合约张数太小,无法下单。")
246
- else:
247
- logger.info(f"{instId}转换失败: {response['msg']}")
248
- send_feishu_notification(f"{instId}转换失败: {response['msg']}")
249
- logger.info(f"------------------ {instId} Order placed done! ------------------")
250
- def check_position(instId, instType='SWAP') -> bool:
251
- """
252
- 检查指定交易对是否有持仓
253
-
254
- Args:
255
- instType: 交易对类型 SPOT、SWAP、FUTURES
256
- instId: 交易对ID
257
-
258
- Returns:
259
- bool: 是否有持仓
260
- """
261
- try:
262
-
263
- positions = account_api.get_positions(instType=instType)
264
- if positions and 'data' in positions and len(positions['data']) > 0:
265
- logger.debug(f"{instId} 有持仓,{positions['data']}")
266
- return True
267
- return False
268
- except Exception as e:
269
- logger.error(f"检查持仓失败 {instId}: {str(e)}")
270
- return False
271
-
272
-
273
- # 处理交易对
274
- def process_pair(instId, pair_config):
275
- if check_position(instId,instType='SWAP'):
276
- logger.info(f"{instId} 有持仓,不下单!")
277
- return
278
- try:
279
- use_market_price = pair_config.get('use_market_price', 1)
280
- if use_market_price == 1 :
281
- mark_price = get_mark_price(instId)
282
- else :
283
- mark_price = get_close_price(instId) # 替换成上周期的收盘价格
284
- klines = get_historical_klines(instId)
285
-
286
- # 提取收盘价数据用于计算 EMA
287
- close_prices = [float(kline[4]) for kline in klines[::-1]] # K线中的收盘价,顺序要新的在最后
288
-
289
- # 计算 EMA
290
- ema_value = pair_config.get('ema', 240)
291
- # 如果ema值为0 不区分方向,两头都挂单
292
- if ema_value == 0:
293
- is_bullish_trend = True
294
- is_bearish_trend = True
295
- else:
296
- ema60 = calculate_ema_pandas(close_prices, period=ema_value)
297
- logger.info(f"{instId} EMA60: {ema60:.6f}, 当前价格: {mark_price:.6f}")
298
- # 判断趋势:多头趋势或空头趋势
299
- is_bullish_trend = close_prices[-1] > ema60 # 收盘价在 EMA60 之上
300
- is_bearish_trend = close_prices[-1] < ema60 # 收盘价在 EMA60 之下
301
-
302
- # 计算 ATR
303
- atr = calculate_atr(klines)
304
- # 当前价格/ATR比值
305
- price_atr_ratio = (mark_price / atr) / 100
306
- logger.info(f"{instId} ATR: {atr:.3f}, 当前价格/ATR比值: {price_atr_ratio:.3f}")
307
- # 平均振幅
308
- average_amplitude = calculate_average_amplitude(klines)
309
- logger.info(f"{instId} 平均振幅: {average_amplitude:.2f}%")
310
-
311
- value_multiplier = pair_config.get('value_multiplier', 2)
312
- '''
313
- 接针的挂单距离,默认计算逻辑是atr/close 跟 振幅ma的区间求最小值 *系数,如果周期小这样其实大部分时候都是采用的振幅,
314
- 其实可以多试试其他方案,比如改成atr/close 跟 振幅ma的平均值,这样的话atr权重实际会更大,大部分行情还是atr反应更直接。
315
- '''
316
- # selected_value = (average_amplitude + price_atr_ratio)/2 * value_multiplier
317
-
318
- selected_value = min(average_amplitude, price_atr_ratio) * value_multiplier
319
- amplitude_limit = float(pair_config.get('amplitude_limit', 0.8))
320
- selected_value = max(selected_value, amplitude_limit)
321
- logger.info(f"{instId} selected_value: {selected_value} ")
322
-
323
-
324
- long_price_factor = 1 - selected_value / 100
325
- short_price_factor = 1 + selected_value / 100
326
-
327
- long_amount_usdt = pair_config.get('long_amount_usdt', 5)
328
- short_amount_usdt = pair_config.get('short_amount_usdt', 5)
329
-
330
- target_price_long = mark_price * long_price_factor
331
- target_price_short = mark_price * short_price_factor
332
-
333
- logger.info(f"{instId} mark_price: {mark_price} Long target price: {target_price_long:.6f}, Short target price: {target_price_short:.6f}")
334
-
335
- cancel_all_orders(instId)
336
-
337
- # 判断趋势后决定是否挂单
338
- if is_bullish_trend:
339
- logger.info(f"{instId} 当前为多头趋势,允许挂多单")
340
- # send_feishu_notification(f"{instId} place_order:+buy+,目标价格:{target_price_long},交易USDT:{long_amount_usdt} ")
341
- place_order(instId, target_price_long, long_amount_usdt, 'buy')
342
- else:
343
- logger.info(f"{instId} 当前非多头趋势,跳过多单挂单")
344
-
345
- if is_bearish_trend:
346
- logger.info(f"{instId} 当前为空头趋势,允许挂空单")
347
- # send_feishu_notification(f"{instId} place_order:-sell-,目标价格:{target_price_short},交易USDT:{short_amount_usdt} ")
348
- place_order(instId, target_price_short, short_amount_usdt, 'sell')
349
- else:
350
- logger.info(f"{instId} 当前非空头趋势,跳过空单挂单")
351
-
352
- except Exception as e:
353
- error_message = f'Error processing {instId}: {e}'
354
- logger.error(error_message)
355
- send_feishu_notification(error_message)
356
-
357
- def main():
358
- import importlib.metadata
359
-
360
- version = importlib.metadata.version("openfund-wick-reversal")
361
- logger.info(f" ++ openfund-wick-reversal:{version} is doing...")
362
- logger.info(f" ++ api_key : {api_key}")
363
- fetch_and_store_all_instruments()
364
- inst_ids = list(trading_pairs_config.keys()) # 获取所有币对的ID
365
- batch_size = 5 # 每批处理的数量
366
-
367
- while True:
368
- for i in range(0, len(inst_ids), batch_size):
369
- batch = inst_ids[i:i + batch_size]
370
- with ThreadPoolExecutor(max_workers=batch_size) as executor:
371
- futures = [executor.submit(process_pair, instId, trading_pairs_config[instId]) for instId in batch]
372
- for future in as_completed(futures):
373
- future.result() # Raise any exceptions caught during execution
374
-
375
- time.sleep(monitor_interval)
376
-
377
- if __name__ == '__main__':
378
- main()
@@ -1,16 +0,0 @@
1
- maker/BestFVGStrategyMaker.py,sha256=Mis9Tgn2ERtMG4uM8ppeshg8pHTnFN6pFs5b5vGJQfE,11798
2
- maker/MACDStrategyMaker.py,sha256=iS5HO04piKHFJxUI2e5QmicxzGeK-V1aphJSr2n_4Ac,12651
3
- maker/SMCStrategyMaker.py,sha256=16AEM_SKZ48xhESYeK_lhPrgsH6WW-MuEFhNC187peI,35226
4
- maker/ThreeLineStrategyMaker.py,sha256=JhPfebBLEL7LIQj1YcH0j1mco1Nuocs8ceR6bjdI8qc,30517
5
- maker/WickReversalStrategyMaker.py,sha256=7DqPDVJot4EM0_lSAcFAHrR9rNvkIds9KLMoDOiAHEc,17486
6
- maker/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
7
- maker/config.py,sha256=YPxghO5i0vgRg9Cja8kGj9O7pgSbbtzOgf3RexqXXwY,1188
8
- maker/main.py,sha256=9b7YRh0rOZlCXmTvAXE3oGhmE4b19NCc0557_jY8bV8,4564
9
- maker/main_m.py,sha256=0PzDTnuBrxfpy5WDfsIHKAzZ_7pkuvuqqeWik0vpWio,15522
10
- maker/okxapi.py,sha256=_9G0U_o0ZC8NxaT6PqpiLgxBm9gPobC9PsFHZE1c5w0,553
11
- maker/zhen.py.bak,sha256=HNkrQbJts8G9umE9chEFsc0cLQApcM9KOVNMYPpkBXM,10918
12
- maker/zhen_2.py,sha256=4IaHVtTCMSlrLGSTZrWpW2q-f7HZsUNRkW_-5QgWv24,10509
13
- openfund_maker-2.2.5.dist-info/METADATA,sha256=EPUPrQ0lPxKZKN24IZWfNmsrWO_c-cwXPrc-RNi2EfQ,1955
14
- openfund_maker-2.2.5.dist-info/WHEEL,sha256=fGIA9gx4Qxk2KDKeNJCbOEwSrmLtjWCwzBz351GyrPQ,88
15
- openfund_maker-2.2.5.dist-info/entry_points.txt,sha256=gKMytICEKcMRFQDFkHZLnIpID7UQFoTIM_xcpiiV6Ns,50
16
- openfund_maker-2.2.5.dist-info/RECORD,,
File without changes
File without changes
File without changes
File without changes