openfund-maker 1.0.17__py3-none-any.whl → 1.1.0__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/ThreeLineOrderBot.py +168 -158
- {openfund_maker-1.0.17.dist-info → openfund_maker-1.1.0.dist-info}/METADATA +1 -1
- {openfund_maker-1.0.17.dist-info → openfund_maker-1.1.0.dist-info}/RECORD +5 -5
- {openfund_maker-1.0.17.dist-info → openfund_maker-1.1.0.dist-info}/WHEEL +0 -0
- {openfund_maker-1.0.17.dist-info → openfund_maker-1.1.0.dist-info}/entry_points.txt +0 -0
maker/ThreeLineOrderBot.py
CHANGED
@@ -49,18 +49,6 @@ class ThreeLineOrdergBot:
|
|
49
49
|
def get_precision_length(self,symbol) -> int:
|
50
50
|
tick_size = self.get_tick_size(symbol)
|
51
51
|
return len(f"{tick_size:.10f}".rstrip('0').split('.')[1]) if '.' in f"{tick_size:.10f}" else 0
|
52
|
-
|
53
|
-
# def decimal_to_precision(self,symbol,price) -> float:
|
54
|
-
# from ccxt.base.decimal_to_precision import DECIMAL_PLACES, TICK_SIZE, NO_PADDING, TRUNCATE, ROUND, ROUND_UP, ROUND_DOWN, SIGNIFICANT_DIGITS
|
55
|
-
# tick_size = self.get_tick_size(symbol)
|
56
|
-
# new_px = self.exchange.decimal_to_precision(
|
57
|
-
# n=float(price),
|
58
|
-
# precision=tick_size,
|
59
|
-
# rounding_mode=ROUND,
|
60
|
-
# counting_mode=TICK_SIZE
|
61
|
-
# )
|
62
|
-
|
63
|
-
# return new_px
|
64
52
|
|
65
53
|
def get_position_mode(self):
|
66
54
|
try:
|
@@ -78,7 +66,6 @@ class ThreeLineOrdergBot:
|
|
78
66
|
except Exception as e:
|
79
67
|
self.logger.error(f"无法检测持仓模式: {e}")
|
80
68
|
return None
|
81
|
-
|
82
69
|
|
83
70
|
def fetch_and_store_all_instruments(self,instType='SWAP'):
|
84
71
|
try:
|
@@ -123,7 +110,6 @@ class ThreeLineOrdergBot:
|
|
123
110
|
else:
|
124
111
|
raise ValueError("Unexpected response structure or missing 'c' value")
|
125
112
|
|
126
|
-
|
127
113
|
def get_mark_price(self,symbol):
|
128
114
|
# response = market_api.get_ticker(instId)
|
129
115
|
ticker = self.exchange.fetch_ticker(symbol)
|
@@ -202,118 +188,47 @@ class ThreeLineOrdergBot:
|
|
202
188
|
amplitudes.append(amplitude)
|
203
189
|
average_amplitude = sum(amplitudes) / len(amplitudes)
|
204
190
|
return average_amplitude
|
205
|
-
|
206
|
-
def cancel_all_orders(self,symbol):
|
207
|
-
try:
|
208
|
-
# 获取所有未完成订单
|
209
|
-
params = {
|
210
|
-
# 'instId': instId
|
211
|
-
}
|
212
|
-
open_orders = self.exchange.fetch_open_orders(symbol=symbol,params=params)
|
213
|
-
|
214
|
-
# 取消每个订单
|
215
|
-
for order in open_orders:
|
216
|
-
self.exchange.cancel_order(order['id'], symbol,params=params)
|
217
|
-
|
218
|
-
self.logger.info(f"{symbol} 挂单取消成功.")
|
219
|
-
except Exception as e:
|
220
|
-
self.logger.error(f"{symbol} 取消订单失败: {str(e)}")
|
221
191
|
|
222
|
-
def
|
223
|
-
try:
|
224
|
-
# 设置杠杆
|
225
|
-
params = {
|
226
|
-
# 'instId': instId,
|
227
|
-
'leverage': leverage,
|
228
|
-
'marginMode': mgnMode
|
229
|
-
}
|
230
|
-
if posSide:
|
231
|
-
params['side'] = posSide
|
232
|
-
|
233
|
-
self.exchange.set_leverage(leverage, symbol=symbol, params=params)
|
234
|
-
self.logger.debug(f"{symbol} Successfully set leverage to {leverage}x")
|
235
|
-
except Exception as e:
|
236
|
-
self.logger.error(f"{symbol} Error setting leverage: {e}")
|
237
|
-
#
|
238
|
-
def check_position(self,symbol) -> bool:
|
192
|
+
def calculate_range_diff(self,prices:pd.Series) -> float:
|
239
193
|
"""
|
240
|
-
|
241
|
-
|
194
|
+
计算价格列表中最后一个价格与第一个价格的差值。
|
242
195
|
Args:
|
243
|
-
|
244
|
-
|
196
|
+
prices: 价格列表。
|
245
197
|
Returns:
|
246
|
-
|
198
|
+
diff: 计算最高价列的最大值与最小值的差值
|
199
|
+
。
|
247
200
|
"""
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
return False
|
254
|
-
except Exception as e:
|
255
|
-
self.logger.error(f"{symbol} 检查持仓失败: {str(e)}")
|
256
|
-
return False
|
257
|
-
|
258
|
-
|
259
|
-
def place_order(self,symbol, price, amount_usdt, side):
|
260
|
-
|
261
|
-
|
262
|
-
markets = self.exchange.load_markets()
|
263
|
-
if symbol not in markets:
|
264
|
-
self.logger.error(f"{symbol}: Instrument {symbol} not found in markets")
|
265
|
-
return
|
266
|
-
market = markets[symbol]
|
267
|
-
# 获取价格精度
|
268
|
-
price_precision = market['precision']['price']
|
269
|
-
adjusted_price = self.round_price_to_tick(price, price_precision)
|
270
|
-
|
271
|
-
if amount_usdt > 0:
|
272
|
-
if side == 'buy':
|
273
|
-
pos_side = 'long'
|
274
|
-
else:
|
275
|
-
pos_side = 'short'
|
276
|
-
# 设置杠杆
|
277
|
-
self.set_leverage(symbol=symbol, leverage=self.leverage_value, mgnMode='isolated',posSide=pos_side)
|
278
|
-
params = {
|
279
|
-
|
280
|
-
"tdMode": 'isolated',
|
281
|
-
"side": side,
|
282
|
-
"ordType": 'limit',
|
283
|
-
# "sz": amount_usdt,
|
284
|
-
"px": str(adjusted_price)
|
285
|
-
}
|
286
|
-
|
287
|
-
# 模拟盘(demo_trading)需要 posSide
|
288
|
-
if self.is_demo_trading == 1 :
|
289
|
-
params["posSide"] = pos_side
|
290
|
-
|
291
|
-
# self.logger.debug(f"---- Order placed params: {params}")
|
292
|
-
try:
|
293
|
-
order = {
|
294
|
-
'symbol': symbol,
|
295
|
-
'side': side,
|
296
|
-
'type': 'limit',
|
297
|
-
'amount': amount_usdt,
|
298
|
-
'price': float(adjusted_price),
|
299
|
-
'params': params
|
300
|
-
}
|
301
|
-
# 使用ccxt创建订单
|
302
|
-
self.logger.debug(f"Pre Order placed: {order} ")
|
303
|
-
order_result = self.exchange.create_order(
|
304
|
-
**order
|
305
|
-
# symbol=symbol,
|
306
|
-
# type='limit',
|
307
|
-
# side=side,
|
308
|
-
# amount=amount_usdt,
|
309
|
-
# price=float(adjusted_price),
|
310
|
-
# params=params
|
311
|
-
)
|
312
|
-
# self.logger.debug(f"{symbol} ++ Order placed rs : {order_result}")
|
313
|
-
except Exception as e:
|
314
|
-
self.logger.error(f"{symbol} Failed to place order: {e}")
|
315
|
-
self.logger.info(f"--------- ++ {symbol} Order placed done! --------")
|
201
|
+
if prices.empty:
|
202
|
+
return None
|
203
|
+
# 将价格列表转换为pandas Series格式
|
204
|
+
|
205
|
+
diff = prices.max() - prices.min()
|
316
206
|
|
207
|
+
return diff
|
208
|
+
|
209
|
+
def calculate_place_order_price(self, symbol,side,base_price, amplitude_limit, offset=1) -> float:
|
210
|
+
"""
|
211
|
+
计算开仓价格
|
212
|
+
Args:
|
213
|
+
symbol: 交易对
|
214
|
+
side: 开仓方向
|
215
|
+
base_price: 开盘价格
|
216
|
+
amplitude_limit: 振幅限制
|
217
|
+
offset: 偏移量
|
218
|
+
Returns:
|
219
|
+
place_order_price: 开仓价格
|
220
|
+
"""
|
221
|
+
tick_size = self.get_tick_size(symbol)
|
222
|
+
place_order_price = None
|
223
|
+
# 计算止盈价格,用市场价格(取持仓期间历史最高)减去开仓价格的利润,再乘以不同阶段的止盈百分比。
|
224
|
+
|
225
|
+
if side == 'buy':
|
226
|
+
place_order_price = base_price * (1- amplitude_limit/100) - offset * tick_size
|
227
|
+
else:
|
228
|
+
place_order_price = base_price * (1 + amplitude_limit/100) + offset * tick_size
|
229
|
+
self.logger.debug(f"++++ {symbol} 下单价格: {place_order_price:.9f} 方向 {side} 基准价格{base_price} 振幅限制 {amplitude_limit} ")
|
230
|
+
return float(self.round_price_to_tick(place_order_price,tick_size))
|
231
|
+
|
317
232
|
# 定义根据均线斜率判断 K 线方向的函数: 0 空 1 多 -1 平
|
318
233
|
def judge_k_line_direction(self, symbol, pair_config, ema: pd.Series, klines) -> int:
|
319
234
|
"""
|
@@ -369,7 +284,8 @@ class ThreeLineOrdergBot:
|
|
369
284
|
elif all(ema_4_diff >= 0) and any(ema_4_diff > 0) :
|
370
285
|
# 上升趋势
|
371
286
|
direction = 1
|
372
|
-
|
287
|
+
# 都是 (0 0 0) 或 (+ 0 -) 这两种情况认为都是震荡
|
288
|
+
else:
|
373
289
|
# 震荡趋势
|
374
290
|
direction = -1
|
375
291
|
self.logger.debug(f"{symbol}: EMA极差={ema_4_diff.map('{:.4f}'.format).values} ,EMA方向={direction}")
|
@@ -433,9 +349,8 @@ class ThreeLineOrdergBot:
|
|
433
349
|
is_expanding_or_contracting = all(df['slope'].tail(period) <= 0 ) and any(df['slope'].tail(period) < 0)
|
434
350
|
|
435
351
|
return is_expanding_or_contracting
|
436
|
-
|
437
|
-
|
438
|
-
def calculate_range_diff(self,prices:pd.Series) -> float:
|
352
|
+
|
353
|
+
def judge_range_diff(self,symbol,pair_config,prices:pd.Series) -> bool:
|
439
354
|
"""
|
440
355
|
计算价格列表中最后一个价格与第一个价格的差值。
|
441
356
|
Args:
|
@@ -444,37 +359,126 @@ class ThreeLineOrdergBot:
|
|
444
359
|
diff: 计算最高价列的最大值与最小值的差值
|
445
360
|
。
|
446
361
|
"""
|
362
|
+
limit = int(pair_config.get('ema_range_limit', 1))
|
363
|
+
period = int(pair_config.get('ema_range_period', 3))
|
364
|
+
tick_size = self.get_tick_size(symbol)
|
447
365
|
if prices.empty:
|
448
|
-
return None
|
449
|
-
# 将价格列表转换为pandas Series格式
|
366
|
+
return None
|
450
367
|
|
451
|
-
diff = prices.max() - prices.min()
|
452
|
-
|
453
|
-
return diff
|
454
|
-
|
455
|
-
def
|
368
|
+
diff = prices.tail(period).max() - prices.tail(period).min()
|
369
|
+
self.logger.debug(f"{symbol}: 最高价列的最大值与最小值的差值 = {diff:.9f}")
|
370
|
+
return abs(diff) <= tick_size * limit
|
371
|
+
|
372
|
+
def set_leverage(self,symbol, leverage, mgnMode='isolated',posSide=None):
|
373
|
+
try:
|
374
|
+
# 设置杠杆
|
375
|
+
params = {
|
376
|
+
# 'instId': instId,
|
377
|
+
'leverage': leverage,
|
378
|
+
'marginMode': mgnMode
|
379
|
+
}
|
380
|
+
if posSide:
|
381
|
+
params['side'] = posSide
|
382
|
+
|
383
|
+
self.exchange.set_leverage(leverage, symbol=symbol, params=params)
|
384
|
+
self.logger.debug(f"{symbol} Successfully set leverage to {leverage}x")
|
385
|
+
except Exception as e:
|
386
|
+
self.logger.error(f"{symbol} Error setting leverage: {e}")
|
387
|
+
#
|
388
|
+
def check_position(self,symbol) -> bool:
|
456
389
|
"""
|
457
|
-
|
390
|
+
检查指定交易对是否有持仓
|
391
|
+
|
458
392
|
Args:
|
459
|
-
symbol: 交易对
|
460
|
-
|
461
|
-
base_price: 开盘价格
|
462
|
-
amplitude_limit: 振幅限制
|
463
|
-
offset: 偏移量
|
393
|
+
symbol: 交易对ID
|
394
|
+
|
464
395
|
Returns:
|
465
|
-
|
396
|
+
bool: 是否有持仓
|
466
397
|
"""
|
467
|
-
|
468
|
-
|
469
|
-
|
470
|
-
|
471
|
-
|
472
|
-
|
473
|
-
|
474
|
-
|
475
|
-
|
476
|
-
|
477
|
-
|
398
|
+
try:
|
399
|
+
position = self.exchange.fetch_position(symbol=symbol)
|
400
|
+
if position and position['contracts']> 0:
|
401
|
+
self.logger.debug(f"{symbol} 有持仓合约数: {position['contracts']}")
|
402
|
+
return True
|
403
|
+
return False
|
404
|
+
except Exception as e:
|
405
|
+
self.logger.error(f"{symbol} 检查持仓失败: {str(e)}")
|
406
|
+
return False
|
407
|
+
|
408
|
+
def place_order(self,symbol, price, amount_usdt, side):
|
409
|
+
|
410
|
+
|
411
|
+
markets = self.exchange.load_markets()
|
412
|
+
if symbol not in markets:
|
413
|
+
self.logger.error(f"{symbol}: Instrument {symbol} not found in markets")
|
414
|
+
return
|
415
|
+
market = markets[symbol]
|
416
|
+
# 获取价格精度
|
417
|
+
price_precision = market['precision']['price']
|
418
|
+
adjusted_price = self.round_price_to_tick(price, price_precision)
|
419
|
+
|
420
|
+
if amount_usdt > 0:
|
421
|
+
if side == 'buy':
|
422
|
+
pos_side = 'long'
|
423
|
+
else:
|
424
|
+
pos_side = 'short'
|
425
|
+
# 设置杠杆
|
426
|
+
self.set_leverage(symbol=symbol, leverage=self.leverage_value, mgnMode='isolated',posSide=pos_side)
|
427
|
+
params = {
|
428
|
+
|
429
|
+
"tdMode": 'isolated',
|
430
|
+
"side": side,
|
431
|
+
"ordType": 'limit',
|
432
|
+
# "sz": amount_usdt,
|
433
|
+
"px": str(adjusted_price)
|
434
|
+
}
|
435
|
+
|
436
|
+
# 模拟盘(demo_trading)需要 posSide
|
437
|
+
if self.is_demo_trading == 1 :
|
438
|
+
params["posSide"] = pos_side
|
439
|
+
|
440
|
+
# self.logger.debug(f"---- Order placed params: {params}")
|
441
|
+
try:
|
442
|
+
order = {
|
443
|
+
'symbol': symbol,
|
444
|
+
'side': side,
|
445
|
+
'type': 'limit',
|
446
|
+
'amount': amount_usdt,
|
447
|
+
'price': float(adjusted_price),
|
448
|
+
'params': params
|
449
|
+
}
|
450
|
+
# 使用ccxt创建订单
|
451
|
+
self.logger.debug(f"Pre Order placed: {order} ")
|
452
|
+
order_result = self.exchange.create_order(
|
453
|
+
**order
|
454
|
+
# symbol=symbol,
|
455
|
+
# type='limit',
|
456
|
+
# side=side,
|
457
|
+
# amount=amount_usdt,
|
458
|
+
# price=float(adjusted_price),
|
459
|
+
# params=params
|
460
|
+
)
|
461
|
+
# self.logger.debug(f"{symbol} ++ Order placed rs : {order_result}")
|
462
|
+
except Exception as e:
|
463
|
+
self.logger.error(f"{symbol} Failed to place order: {e}")
|
464
|
+
self.logger.info(f"--------- ++ {symbol} Order placed done! --------")
|
465
|
+
|
466
|
+
def cancel_all_orders(self,symbol):
|
467
|
+
try:
|
468
|
+
# 获取所有未完成订单
|
469
|
+
params = {
|
470
|
+
# 'instId': instId
|
471
|
+
}
|
472
|
+
open_orders = self.exchange.fetch_open_orders(symbol=symbol,params=params)
|
473
|
+
|
474
|
+
# 取消每个订单
|
475
|
+
for order in open_orders:
|
476
|
+
self.exchange.cancel_order(order['id'], symbol,params=params)
|
477
|
+
|
478
|
+
self.logger.info(f"{symbol} 挂单取消成功.")
|
479
|
+
except Exception as e:
|
480
|
+
self.logger.error(f"{symbol} 取消订单失败: {str(e)}")
|
481
|
+
|
478
482
|
def process_pair(self,symbol,pair_config):
|
479
483
|
# 检查是否有持仓,有持仓不进行下单
|
480
484
|
if self.check_position(symbol=symbol) :
|
@@ -518,19 +522,28 @@ class ThreeLineOrdergBot:
|
|
518
522
|
# 结合金叉死叉判断是否是周期顶部和底部
|
519
523
|
is_apex = self.judge_ma_apex(symbol=symbol,pair_config=pair_config, fastklines=fastk,slowklines=slowk)
|
520
524
|
ema_direction = self.judge_ema_direction(symbol=symbol, pair_config=pair_config,ema=fastk)
|
525
|
+
if_inner_range = self.judge_range_diff(symbol=symbol, pair_config=pair_config, prices=fastk)
|
521
526
|
# 金叉死叉逻辑
|
522
527
|
if last_cross_direction and last_cross_direction['cross'] == 1 : # 金叉
|
523
528
|
self.logger.debug(f"{symbol} 金叉:{last_cross_direction},清理空单,挂多单!!")
|
524
529
|
is_bearish_trend = False
|
525
|
-
|
526
|
-
|
530
|
+
# 强校验下单条件
|
531
|
+
if is_apex or ema_direction == -1 or if_inner_range:
|
532
|
+
self.logger.debug(f"{symbol} 强校验 - 死叉:{last_cross_direction},两线收缩={is_apex} ,ema_平={ema_direction},均线振幅={if_inner_range} ,不开单!!")
|
533
|
+
# 弱校验下单条件
|
534
|
+
# if is_apex or ema_direction == -1:
|
535
|
+
# self.logger.debug(f"{symbol} 金叉:{last_cross_direction},两线收缩={is_apex} ,ema_平={ema_direction},不开单!!")
|
527
536
|
is_bullish_trend = False
|
528
537
|
|
529
538
|
elif last_cross_direction and last_cross_direction['cross'] == 0 : # 死叉
|
530
539
|
self.logger.debug(f"{symbol} 死叉:{last_cross_direction},清理多单,挂空单!!")
|
531
540
|
is_bullish_trend = False
|
532
|
-
|
533
|
-
|
541
|
+
# 强校验下单条件
|
542
|
+
if is_apex or ema_direction == -1 or if_inner_range :
|
543
|
+
self.logger.debug(f"{symbol} 强校验 - 死叉:{last_cross_direction},两线收缩={is_apex} ,ema_平={ema_direction},均线振幅={if_inner_range} ,不开单!!")
|
544
|
+
# 弱校验下单条件
|
545
|
+
# if is_apex or ema_direction == -1:
|
546
|
+
# self.logger.debug(f"{symbol} 死叉:{last_cross_direction},两线收缩={is_apex} ,ema_平={ema_direction},不开单!!")
|
534
547
|
is_bearish_trend = False
|
535
548
|
|
536
549
|
else:
|
@@ -558,9 +571,7 @@ class ThreeLineOrdergBot:
|
|
558
571
|
|
559
572
|
amplitude_limit = pair_config.get('amplitude_limit', 0.32)
|
560
573
|
|
561
|
-
self.logger.debug(f"{symbol} 当前K线的前三根K线 最高价: {max_high}, 最低价: {min_low}")
|
562
|
-
|
563
|
-
|
574
|
+
self.logger.debug(f"{symbol} 当前K线的前三根K线 最高价: {max_high}, 最低价: {min_low}")
|
564
575
|
|
565
576
|
long_amount_usdt = pair_config.get('long_amount_usdt', 5)
|
566
577
|
short_amount_usdt = pair_config.get('short_amount_usdt', 5)
|
@@ -573,6 +584,7 @@ class ThreeLineOrdergBot:
|
|
573
584
|
close_price = klines[-1][4]
|
574
585
|
self.logger.debug(f"-- {symbol} 最新K线 {klines[-1]}")
|
575
586
|
|
587
|
+
# FIXME calculate_range_diff 去掉
|
576
588
|
if is_bullish_trend:
|
577
589
|
diff = self.calculate_range_diff(prices=low_prices)
|
578
590
|
cur_amplitude_limit = diff / close_price * 100
|
@@ -602,8 +614,6 @@ class ThreeLineOrdergBot:
|
|
602
614
|
self.logger.error(error_message,exc_info=True)
|
603
615
|
traceback.print_exc()
|
604
616
|
self.send_feishu_notification(error_message)
|
605
|
-
|
606
|
-
|
607
617
|
|
608
618
|
def monitor_klines(self):
|
609
619
|
symbols = list(self.trading_pairs_config.keys()) # 获取所有币对的ID
|
@@ -1,4 +1,4 @@
|
|
1
|
-
maker/ThreeLineOrderBot.py,sha256=
|
1
|
+
maker/ThreeLineOrderBot.py,sha256=UBWtLMJtL6mAS4CLYThja5Z_XE7UCD4bBhik0T0Xp0s,27521
|
2
2
|
maker/WickReversalOrderBot.py,sha256=Oc6wChdWu39lfWh3NRHM8BqvaRIYDNZiDR6PDnE9XUM,17374
|
3
3
|
maker/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
4
4
|
maker/config.py,sha256=YPxghO5i0vgRg9Cja8kGj9O7pgSbbtzOgf3RexqXXwY,1188
|
@@ -7,7 +7,7 @@ maker/main_m.py,sha256=0PzDTnuBrxfpy5WDfsIHKAzZ_7pkuvuqqeWik0vpWio,15522
|
|
7
7
|
maker/okxapi.py,sha256=_9G0U_o0ZC8NxaT6PqpiLgxBm9gPobC9PsFHZE1c5w0,553
|
8
8
|
maker/zhen.py.bak,sha256=HNkrQbJts8G9umE9chEFsc0cLQApcM9KOVNMYPpkBXM,10918
|
9
9
|
maker/zhen_2.py,sha256=4IaHVtTCMSlrLGSTZrWpW2q-f7HZsUNRkW_-5QgWv24,10509
|
10
|
-
openfund_maker-1.0.
|
11
|
-
openfund_maker-1.0.
|
12
|
-
openfund_maker-1.0.
|
13
|
-
openfund_maker-1.0.
|
10
|
+
openfund_maker-1.1.0.dist-info/METADATA,sha256=y6UyUbgh-IJwrJiklJxR4mBCK-SQg56J2EpDI8cIIVc,1965
|
11
|
+
openfund_maker-1.1.0.dist-info/WHEEL,sha256=IYZQI976HJqqOpQU6PHkJ8fb3tMNBFjg-Cn-pwAbaFM,88
|
12
|
+
openfund_maker-1.1.0.dist-info/entry_points.txt,sha256=gKMytICEKcMRFQDFkHZLnIpID7UQFoTIM_xcpiiV6Ns,50
|
13
|
+
openfund_maker-1.1.0.dist-info/RECORD,,
|
File without changes
|
File without changes
|