pdmt5 0.1.4__py3-none-any.whl → 0.1.6__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.
pdmt5/trading.py
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
5
|
from datetime import timedelta
|
|
6
|
+
from math import floor
|
|
6
7
|
from typing import TYPE_CHECKING, Any, Literal
|
|
7
8
|
|
|
8
9
|
from pydantic import ConfigDict, Field
|
|
@@ -31,12 +32,11 @@ class Mt5TradingClient(Mt5DataClient):
|
|
|
31
32
|
description="Order filling mode: 'IOC' (Immediate or Cancel), "
|
|
32
33
|
"'FOK' (Fill or Kill), 'RETURN' (Return if not filled)",
|
|
33
34
|
)
|
|
34
|
-
dry_run: bool = Field(default=False, description="Enable dry run mode for testing.")
|
|
35
35
|
|
|
36
36
|
def close_open_positions(
|
|
37
37
|
self,
|
|
38
38
|
symbols: str | list[str] | tuple[str, ...] | None = None,
|
|
39
|
-
dry_run: bool
|
|
39
|
+
dry_run: bool = False,
|
|
40
40
|
**kwargs: Any, # noqa: ANN401
|
|
41
41
|
) -> dict[str, list[dict[str, Any]]]:
|
|
42
42
|
"""Close all open positions for specified symbols.
|
|
@@ -44,8 +44,7 @@ class Mt5TradingClient(Mt5DataClient):
|
|
|
44
44
|
Args:
|
|
45
45
|
symbols: Optional symbol or list of symbols to filter positions.
|
|
46
46
|
If None, all symbols will be considered.
|
|
47
|
-
dry_run:
|
|
48
|
-
`dry_run` attribute.
|
|
47
|
+
dry_run: If True, only check the order without sending it.
|
|
49
48
|
**kwargs: Additional keyword arguments for request parameters.
|
|
50
49
|
|
|
51
50
|
Returns:
|
|
@@ -67,15 +66,14 @@ class Mt5TradingClient(Mt5DataClient):
|
|
|
67
66
|
def _fetch_and_close_position(
|
|
68
67
|
self,
|
|
69
68
|
symbol: str | None = None,
|
|
70
|
-
dry_run: bool
|
|
69
|
+
dry_run: bool = False,
|
|
71
70
|
**kwargs: Any, # noqa: ANN401
|
|
72
71
|
) -> list[dict[str, Any]]:
|
|
73
72
|
"""Close all open positions for a specific symbol.
|
|
74
73
|
|
|
75
74
|
Args:
|
|
76
75
|
symbol: Optional symbol filter.
|
|
77
|
-
dry_run:
|
|
78
|
-
`dry_run` attribute.
|
|
76
|
+
dry_run: If True, only check the order without sending it.
|
|
79
77
|
**kwargs: Additional keyword arguments for request parameters.
|
|
80
78
|
|
|
81
79
|
Returns:
|
|
@@ -92,7 +90,7 @@ class Mt5TradingClient(Mt5DataClient):
|
|
|
92
90
|
f"ORDER_FILLING_{self.order_filling_mode}",
|
|
93
91
|
)
|
|
94
92
|
return [
|
|
95
|
-
self.
|
|
93
|
+
self._send_or_check_order(
|
|
96
94
|
request={
|
|
97
95
|
"action": self.mt5.TRADE_ACTION_DEAL,
|
|
98
96
|
"symbol": p["symbol"],
|
|
@@ -112,17 +110,16 @@ class Mt5TradingClient(Mt5DataClient):
|
|
|
112
110
|
for p in positions_dict
|
|
113
111
|
]
|
|
114
112
|
|
|
115
|
-
def
|
|
113
|
+
def _send_or_check_order(
|
|
116
114
|
self,
|
|
117
115
|
request: dict[str, Any],
|
|
118
|
-
dry_run: bool
|
|
116
|
+
dry_run: bool = False,
|
|
119
117
|
) -> dict[str, Any]:
|
|
120
118
|
"""Send or check an order request.
|
|
121
119
|
|
|
122
120
|
Args:
|
|
123
121
|
request: Order request dictionary.
|
|
124
|
-
dry_run:
|
|
125
|
-
`dry_run` attribute.
|
|
122
|
+
dry_run: If True, only check the order without sending it.
|
|
126
123
|
|
|
127
124
|
Returns:
|
|
128
125
|
Dictionary with operation result.
|
|
@@ -131,17 +128,15 @@ class Mt5TradingClient(Mt5DataClient):
|
|
|
131
128
|
Mt5TradingError: If the order operation fails.
|
|
132
129
|
"""
|
|
133
130
|
self.logger.debug("request: %s", request)
|
|
134
|
-
|
|
135
|
-
self.logger.debug("is_dry_run: %s", is_dry_run)
|
|
136
|
-
if is_dry_run:
|
|
131
|
+
if dry_run:
|
|
137
132
|
response = self.order_check_as_dict(request=request)
|
|
138
133
|
order_func = "order_check"
|
|
139
134
|
else:
|
|
140
135
|
response = self.order_send_as_dict(request=request)
|
|
141
136
|
order_func = "order_send"
|
|
142
137
|
retcode = response.get("retcode")
|
|
143
|
-
if ((not
|
|
144
|
-
|
|
138
|
+
if ((not dry_run) and retcode == self.mt5.TRADE_RETCODE_DONE) or (
|
|
139
|
+
dry_run and retcode == 0
|
|
145
140
|
):
|
|
146
141
|
self.logger.info("response: %s", response)
|
|
147
142
|
return response
|
|
@@ -159,41 +154,170 @@ class Mt5TradingClient(Mt5DataClient):
|
|
|
159
154
|
error_message = f"{order_func}() failed and aborted. <= `{comment}`"
|
|
160
155
|
raise Mt5TradingError(error_message)
|
|
161
156
|
|
|
162
|
-
def
|
|
163
|
-
|
|
157
|
+
def place_market_order(
|
|
158
|
+
self,
|
|
159
|
+
symbol: str,
|
|
160
|
+
volume: float,
|
|
161
|
+
order_side: Literal["BUY", "SELL"],
|
|
162
|
+
order_filling_mode: Literal["IOC", "FOK", "RETURN"] = "IOC",
|
|
163
|
+
order_time_mode: Literal["GTC", "DAY", "SPECIFIED", "SPECIFIED_DAY"] = "GTC",
|
|
164
|
+
dry_run: bool = False,
|
|
165
|
+
**kwargs: Any, # noqa: ANN401
|
|
166
|
+
) -> dict[str, Any]:
|
|
167
|
+
"""Send or check an order request to place a market order.
|
|
164
168
|
|
|
165
169
|
Args:
|
|
166
|
-
symbol: Symbol for
|
|
170
|
+
symbol: Symbol for the order.
|
|
171
|
+
volume: Volume of the order.
|
|
172
|
+
order_side: Side of the order, either "BUY" or "SELL".
|
|
173
|
+
order_filling_mode: Order filling mode, either "IOC", "FOK", or "RETURN".
|
|
174
|
+
order_time_mode: Order time mode, either "GTC", "DAY", "SPECIFIED",
|
|
175
|
+
or "SPECIFIED_DAY".
|
|
176
|
+
dry_run: If True, only check the order without sending it.
|
|
177
|
+
**kwargs: Additional keyword arguments for request parameters.
|
|
167
178
|
|
|
168
179
|
Returns:
|
|
169
|
-
Dictionary with
|
|
180
|
+
Dictionary with operation result.
|
|
181
|
+
"""
|
|
182
|
+
return self._send_or_check_order(
|
|
183
|
+
request={
|
|
184
|
+
"action": self.mt5.TRADE_ACTION_DEAL,
|
|
185
|
+
"symbol": symbol,
|
|
186
|
+
"volume": volume,
|
|
187
|
+
"type": getattr(self.mt5, f"ORDER_TYPE_{order_side.upper()}"),
|
|
188
|
+
"type_filling": getattr(
|
|
189
|
+
self.mt5, f"ORDER_FILLING_{order_filling_mode.upper()}"
|
|
190
|
+
),
|
|
191
|
+
"type_time": getattr(self.mt5, f"ORDER_TIME_{order_time_mode.upper()}"),
|
|
192
|
+
**kwargs,
|
|
193
|
+
},
|
|
194
|
+
dry_run=dry_run,
|
|
195
|
+
)
|
|
170
196
|
|
|
171
|
-
|
|
172
|
-
|
|
197
|
+
def update_sltp_for_open_positions(
|
|
198
|
+
self,
|
|
199
|
+
symbol: str,
|
|
200
|
+
stop_loss: float | None = None,
|
|
201
|
+
take_profit: float | None = None,
|
|
202
|
+
tickets: list[int] | None = None,
|
|
203
|
+
dry_run: bool = False,
|
|
204
|
+
**kwargs: Any, # noqa: ANN401
|
|
205
|
+
) -> list[dict[str, Any]]:
|
|
206
|
+
"""Change Stop Loss and Take Profit for open positions.
|
|
207
|
+
|
|
208
|
+
Args:
|
|
209
|
+
symbol: Symbol for the position.
|
|
210
|
+
stop_loss: New Stop Loss price. If None, it will not be changed.
|
|
211
|
+
take_profit: New Take Profit price. If None, it will not be changed.
|
|
212
|
+
tickets: List of position tickets to filter positions. If None, all open
|
|
213
|
+
positions for the symbol will be considered.
|
|
214
|
+
dry_run: If True, only check the order without sending it.
|
|
215
|
+
**kwargs: Additional keyword arguments for request parameters.
|
|
216
|
+
|
|
217
|
+
Returns:
|
|
218
|
+
List of dictionaries with operation results for each updated position.
|
|
219
|
+
"""
|
|
220
|
+
positions_df = self.positions_get_as_df(symbol=symbol)
|
|
221
|
+
if positions_df.empty:
|
|
222
|
+
self.logger.warning("No open positions found for symbol: %s", symbol)
|
|
223
|
+
return []
|
|
224
|
+
elif tickets:
|
|
225
|
+
filtered_positions_df = positions_df.pipe(
|
|
226
|
+
lambda d: d[d["ticket"].isin(tickets)]
|
|
227
|
+
)
|
|
228
|
+
else:
|
|
229
|
+
filtered_positions_df = positions_df
|
|
230
|
+
if filtered_positions_df.empty:
|
|
231
|
+
self.logger.warning(
|
|
232
|
+
"No open positions found for symbol: %s with specified tickets: %s",
|
|
233
|
+
symbol,
|
|
234
|
+
tickets,
|
|
235
|
+
)
|
|
236
|
+
return []
|
|
237
|
+
else:
|
|
238
|
+
symbol_info = self.symbol_info_as_dict(symbol=symbol)
|
|
239
|
+
sl = round(stop_loss, symbol_info["digits"]) if stop_loss else None
|
|
240
|
+
tp = round(take_profit, symbol_info["digits"]) if take_profit else None
|
|
241
|
+
order_requests = [
|
|
242
|
+
{
|
|
243
|
+
"action": self.mt5.TRADE_ACTION_SLTP,
|
|
244
|
+
"symbol": p["symbol"],
|
|
245
|
+
"position": p["ticket"],
|
|
246
|
+
"sl": (sl or p["sl"]),
|
|
247
|
+
"tp": (tp or p["tp"]),
|
|
248
|
+
**kwargs,
|
|
249
|
+
}
|
|
250
|
+
for _, p in filtered_positions_df.iterrows()
|
|
251
|
+
if sl != p["sl"] or tp != p["tp"]
|
|
252
|
+
]
|
|
253
|
+
if order_requests:
|
|
254
|
+
return [
|
|
255
|
+
self._send_or_check_order(request=r, dry_run=dry_run)
|
|
256
|
+
for r in order_requests
|
|
257
|
+
]
|
|
258
|
+
else:
|
|
259
|
+
self.logger.info(
|
|
260
|
+
"No positions to update for symbol: %s with SL: %s and TP: %s",
|
|
261
|
+
symbol,
|
|
262
|
+
sl,
|
|
263
|
+
tp,
|
|
264
|
+
)
|
|
265
|
+
return []
|
|
266
|
+
|
|
267
|
+
def calculate_minimum_order_margin(
|
|
268
|
+
self,
|
|
269
|
+
symbol: str,
|
|
270
|
+
order_side: Literal["BUY", "SELL"],
|
|
271
|
+
) -> dict[str, float]:
|
|
272
|
+
"""Calculate the minimum order margins for a given symbol.
|
|
273
|
+
|
|
274
|
+
Args:
|
|
275
|
+
symbol: Symbol for which to calculate the minimum order margins.
|
|
276
|
+
order_side: Optional side of the order, either "BUY" or "SELL".
|
|
277
|
+
|
|
278
|
+
Returns:
|
|
279
|
+
Dictionary with minimum volume and margin for the specified order side.
|
|
173
280
|
"""
|
|
174
281
|
symbol_info = self.symbol_info_as_dict(symbol=symbol)
|
|
175
282
|
symbol_info_tick = self.symbol_info_tick_as_dict(symbol=symbol)
|
|
176
|
-
|
|
177
|
-
|
|
283
|
+
return {
|
|
284
|
+
"volume": symbol_info["volume_min"],
|
|
285
|
+
"margin": self.order_calc_margin(
|
|
286
|
+
action=getattr(self.mt5, f"ORDER_TYPE_{order_side.upper()}"),
|
|
287
|
+
symbol=symbol,
|
|
288
|
+
volume=symbol_info["volume_min"],
|
|
289
|
+
price=(
|
|
290
|
+
symbol_info_tick["bid"]
|
|
291
|
+
if order_side == "SELL"
|
|
292
|
+
else symbol_info_tick["ask"]
|
|
293
|
+
),
|
|
294
|
+
),
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
def calculate_volume_by_margin(
|
|
298
|
+
self,
|
|
299
|
+
symbol: str,
|
|
300
|
+
margin: float,
|
|
301
|
+
order_side: Literal["BUY", "SELL"],
|
|
302
|
+
) -> float:
|
|
303
|
+
"""Calculate volume based on margin for a given symbol and order side.
|
|
304
|
+
|
|
305
|
+
Args:
|
|
306
|
+
symbol: Symbol for which to calculate the volume.
|
|
307
|
+
margin: Margin amount to use for the calculation.
|
|
308
|
+
order_side: Side of the order, either "BUY" or "SELL".
|
|
309
|
+
|
|
310
|
+
Returns:
|
|
311
|
+
Calculated volume as a float.
|
|
312
|
+
"""
|
|
313
|
+
min_order_margin_dict = self.calculate_minimum_order_margin(
|
|
178
314
|
symbol=symbol,
|
|
179
|
-
|
|
180
|
-
price=symbol_info_tick["ask"],
|
|
315
|
+
order_side=order_side,
|
|
181
316
|
)
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
volume=symbol_info["volume_min"],
|
|
186
|
-
price=symbol_info_tick["bid"],
|
|
317
|
+
return (
|
|
318
|
+
floor(margin / min_order_margin_dict["margin"])
|
|
319
|
+
* min_order_margin_dict["volume"]
|
|
187
320
|
)
|
|
188
|
-
min_order_margins = {"ask": min_ask_order_margin, "bid": min_bid_order_margin}
|
|
189
|
-
self.logger.info("Minimum order margins for %s: %s", symbol, min_order_margins)
|
|
190
|
-
if all(min_order_margins.values()):
|
|
191
|
-
return min_order_margins
|
|
192
|
-
else:
|
|
193
|
-
error_message = (
|
|
194
|
-
f"Failed to calculate minimum order margins for symbol: {symbol}."
|
|
195
|
-
)
|
|
196
|
-
raise Mt5TradingError(error_message)
|
|
197
321
|
|
|
198
322
|
def calculate_spread_ratio(
|
|
199
323
|
self,
|
|
@@ -369,3 +493,50 @@ class Mt5TradingClient(Mt5DataClient):
|
|
|
369
493
|
)
|
|
370
494
|
.drop(columns=["buy_i", "sell_i", "sign", "underlier_increase_ratio"])
|
|
371
495
|
)
|
|
496
|
+
|
|
497
|
+
def calculate_new_position_margin_ratio(
|
|
498
|
+
self,
|
|
499
|
+
symbol: str,
|
|
500
|
+
new_position_side: Literal["BUY", "SELL"] | None = None,
|
|
501
|
+
new_position_volume: float = 0,
|
|
502
|
+
) -> float:
|
|
503
|
+
"""Calculate the margin ratio for a new position.
|
|
504
|
+
|
|
505
|
+
Args:
|
|
506
|
+
symbol: Symbol for which to calculate the margin ratio.
|
|
507
|
+
new_position_side: Side of the new position, either "BUY" or "SELL".
|
|
508
|
+
new_position_volume: Volume of the new position.
|
|
509
|
+
|
|
510
|
+
Returns:
|
|
511
|
+
float: Margin ratio for the new position as a fraction of account equity.
|
|
512
|
+
"""
|
|
513
|
+
account_info = self.account_info_as_dict()
|
|
514
|
+
if not account_info["equity"]:
|
|
515
|
+
return 0.0
|
|
516
|
+
else:
|
|
517
|
+
positions_df = self.fetch_positions_with_metrics_as_df(symbol=symbol)
|
|
518
|
+
current_signed_margin = (
|
|
519
|
+
positions_df["signed_margin"].sum() if positions_df.size else 0
|
|
520
|
+
)
|
|
521
|
+
symbol_info_tick = self.symbol_info_tick_as_dict(symbol=symbol)
|
|
522
|
+
if not (new_position_side and new_position_volume):
|
|
523
|
+
new_signed_margin = 0
|
|
524
|
+
elif new_position_side.upper() == "BUY":
|
|
525
|
+
new_signed_margin = self.order_calc_margin(
|
|
526
|
+
action=self.mt5.ORDER_TYPE_BUY,
|
|
527
|
+
symbol=symbol,
|
|
528
|
+
volume=new_position_volume,
|
|
529
|
+
price=symbol_info_tick["ask"],
|
|
530
|
+
)
|
|
531
|
+
elif new_position_side.upper() == "SELL":
|
|
532
|
+
new_signed_margin = -self.order_calc_margin(
|
|
533
|
+
action=self.mt5.ORDER_TYPE_SELL,
|
|
534
|
+
symbol=symbol,
|
|
535
|
+
volume=new_position_volume,
|
|
536
|
+
price=symbol_info_tick["bid"],
|
|
537
|
+
)
|
|
538
|
+
else:
|
|
539
|
+
new_signed_margin = 0
|
|
540
|
+
return abs(
|
|
541
|
+
(new_signed_margin + current_signed_margin) / account_info["equity"]
|
|
542
|
+
)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pdmt5
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.6
|
|
4
4
|
Summary: Pandas-based data handler for MetaTrader 5
|
|
5
5
|
Project-URL: Repository, https://github.com/dceoy/pdmt5.git
|
|
6
6
|
Author-email: dceoy <dceoy@users.noreply.github.com>
|
|
@@ -29,7 +29,6 @@ Pandas-based data handler for MetaTrader 5
|
|
|
29
29
|
[](https://www.python.org/downloads/)
|
|
30
30
|
[](https://opensource.org/licenses/MIT)
|
|
31
31
|
[](https://www.microsoft.com/windows)
|
|
32
|
-
[](https://github.com/dceoy/pdmt5)
|
|
33
32
|
|
|
34
33
|
## Overview
|
|
35
34
|
|
|
@@ -43,6 +42,8 @@ Pandas-based data handler for MetaTrader 5
|
|
|
43
42
|
- 🚀 **Context Manager Support**: Clean initialization and cleanup with `with` statements
|
|
44
43
|
- 📈 **Time Series Ready**: OHLCV data with proper datetime indexing
|
|
45
44
|
- 🛡️ **Robust Error Handling**: Custom exceptions with detailed MT5 error information
|
|
45
|
+
- 💰 **Advanced Trading Operations**: Position management, margin calculations, and risk analysis tools
|
|
46
|
+
- 🧪 **Dry Run Mode**: Test trading strategies without executing real trades
|
|
46
47
|
|
|
47
48
|
## Requirements
|
|
48
49
|
|
|
@@ -195,10 +196,13 @@ Advanced trading operations client that extends Mt5DataClient:
|
|
|
195
196
|
- `dry_run` - Test mode flag for simulating trades without execution
|
|
196
197
|
- **Position Management**:
|
|
197
198
|
- `close_open_positions()` - Close all positions for specified symbol(s)
|
|
198
|
-
- `
|
|
199
|
-
-
|
|
200
|
-
|
|
199
|
+
- `place_market_order()` - Place market orders with configurable side, volume, and execution modes
|
|
200
|
+
- `update_sltp_for_open_positions()` - Modify stop loss and take profit levels for open positions
|
|
201
|
+
- **Margin Calculations**:
|
|
202
|
+
- `calculate_minimum_order_margin()` - Calculate minimum required margin for a specific order side
|
|
203
|
+
- `calculate_volume_by_margin()` - Calculate maximum volume for given margin amount
|
|
201
204
|
- `calculate_spread_ratio()` - Calculate normalized bid-ask spread ratio
|
|
205
|
+
- `calculate_new_position_margin_ratio()` - Calculate margin ratio for potential new positions
|
|
202
206
|
- **Simplified Data Access**:
|
|
203
207
|
- `fetch_latest_rates_as_df()` - Get recent OHLC data with timeframe strings (e.g., "M1", "H1", "D1")
|
|
204
208
|
- `fetch_latest_ticks_as_df()` - Get tick data for specified seconds around last tick
|
|
@@ -288,18 +292,52 @@ from pdmt5 import Mt5TradingClient
|
|
|
288
292
|
|
|
289
293
|
# Create trading client with specific order filling mode
|
|
290
294
|
with Mt5TradingClient(config=config, order_filling_mode="IOC") as trader:
|
|
295
|
+
# Place a market buy order
|
|
296
|
+
order_result = trader.place_market_order(
|
|
297
|
+
symbol="EURUSD",
|
|
298
|
+
volume=0.1,
|
|
299
|
+
order_side="BUY",
|
|
300
|
+
order_filling_mode="IOC", # Immediate or Cancel
|
|
301
|
+
order_time_mode="GTC" # Good Till Cancelled
|
|
302
|
+
)
|
|
303
|
+
print(f"Order placed: {order_result['retcode']}")
|
|
304
|
+
|
|
305
|
+
# Update stop loss and take profit for open positions
|
|
306
|
+
update_results = trader.update_sltp_for_open_positions(
|
|
307
|
+
symbol="EURUSD",
|
|
308
|
+
stop_loss=1.0950, # New stop loss
|
|
309
|
+
take_profit=1.1050 # New take profit
|
|
310
|
+
)
|
|
311
|
+
for result in update_results:
|
|
312
|
+
print(f"Position updated: {result['retcode']}")
|
|
313
|
+
|
|
314
|
+
# Calculate margin ratio for a new position
|
|
315
|
+
margin_ratio = trader.calculate_new_position_margin_ratio(
|
|
316
|
+
symbol="EURUSD",
|
|
317
|
+
new_position_side="SELL",
|
|
318
|
+
new_position_volume=0.2
|
|
319
|
+
)
|
|
320
|
+
print(f"New position margin ratio: {margin_ratio:.2%}")
|
|
321
|
+
|
|
291
322
|
# Close all EURUSD positions
|
|
292
323
|
results = trader.close_open_positions(symbols="EURUSD")
|
|
293
324
|
|
|
294
325
|
if results:
|
|
295
|
-
for
|
|
296
|
-
|
|
326
|
+
for symbol, close_results in results.items():
|
|
327
|
+
for result in close_results:
|
|
328
|
+
print(f"Closed position {result.get('position')} with result: {result['retcode']}")
|
|
297
329
|
|
|
298
330
|
# Using dry run mode for testing
|
|
299
331
|
trader_dry = Mt5TradingClient(config=config, dry_run=True)
|
|
300
332
|
with trader_dry:
|
|
301
|
-
# Test
|
|
302
|
-
|
|
333
|
+
# Test placing an order without actual execution
|
|
334
|
+
test_order = trader_dry.place_market_order(
|
|
335
|
+
symbol="GBPUSD",
|
|
336
|
+
volume=0.1,
|
|
337
|
+
order_side="SELL",
|
|
338
|
+
dry_run=True # Override instance setting
|
|
339
|
+
)
|
|
340
|
+
print(f"Test order validation: {test_order['retcode']}")
|
|
303
341
|
```
|
|
304
342
|
|
|
305
343
|
### Market Analysis with Mt5TradingClient
|
|
@@ -310,10 +348,18 @@ with Mt5TradingClient(config=config) as trader:
|
|
|
310
348
|
spread_ratio = trader.calculate_spread_ratio("EURUSD")
|
|
311
349
|
print(f"EURUSD spread ratio: {spread_ratio:.5f}")
|
|
312
350
|
|
|
313
|
-
# Get minimum order
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
print(f"Minimum
|
|
351
|
+
# Get minimum order margin for BUY and SELL
|
|
352
|
+
buy_margin = trader.calculate_minimum_order_margin("EURUSD", "BUY")
|
|
353
|
+
sell_margin = trader.calculate_minimum_order_margin("EURUSD", "SELL")
|
|
354
|
+
print(f"Minimum BUY margin: {buy_margin['margin']} (volume: {buy_margin['volume']})")
|
|
355
|
+
print(f"Minimum SELL margin: {sell_margin['margin']} (volume: {sell_margin['volume']})")
|
|
356
|
+
|
|
357
|
+
# Calculate volume by margin
|
|
358
|
+
available_margin = 1000.0
|
|
359
|
+
max_buy_volume = trader.calculate_volume_by_margin("EURUSD", available_margin, "BUY")
|
|
360
|
+
max_sell_volume = trader.calculate_volume_by_margin("EURUSD", available_margin, "SELL")
|
|
361
|
+
print(f"Max BUY volume for ${available_margin}: {max_buy_volume}")
|
|
362
|
+
print(f"Max SELL volume for ${available_margin}: {max_sell_volume}")
|
|
317
363
|
|
|
318
364
|
# Get recent OHLC data with custom timeframe
|
|
319
365
|
rates_df = trader.fetch_latest_rates_as_df(
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
pdmt5/__init__.py,sha256=QbSFrsi7_bgFzb-ma4DmmUjR90UvrqKMnRZq1wPRmoI,446
|
|
2
2
|
pdmt5/dataframe.py,sha256=rUWtR23hrXBdBqzJhbOlIemNy73RrjSTZZJUhwoL6io,38084
|
|
3
3
|
pdmt5/mt5.py,sha256=KgxHapIrh5b4L0wIOAQIjfXNZafalihbFrh9fhYHmrI,32254
|
|
4
|
-
pdmt5/trading.py,sha256=
|
|
4
|
+
pdmt5/trading.py,sha256=GOOYsbsjKtp26dw1VB6siZ1luBttD8cv-KNZi6pPUEs,20107
|
|
5
5
|
pdmt5/utils.py,sha256=Ll5Q3OE5h1A_sZ_qVEnOPGniFlT6_MmHfuu0zqeLdeU,3913
|
|
6
|
-
pdmt5-0.1.
|
|
7
|
-
pdmt5-0.1.
|
|
8
|
-
pdmt5-0.1.
|
|
9
|
-
pdmt5-0.1.
|
|
6
|
+
pdmt5-0.1.6.dist-info/METADATA,sha256=cBYUp4lIyQuw7aRN7dphpgtejgW5jamAvYImYQiQS9o,16824
|
|
7
|
+
pdmt5-0.1.6.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
8
|
+
pdmt5-0.1.6.dist-info/licenses/LICENSE,sha256=iABrdaUGOBWLYotFupB_PGe8arV5o7rVhn-_vK6P704,1073
|
|
9
|
+
pdmt5-0.1.6.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|