Get Historical Closed Positions¶
Request: historical closed positions within a time range with sorting and pagination support.
API Information:
- Low-level API:
MT5Account.positions_history(...)(defined inpackage/MetaRpcMT5/helpers/mt5_account.py) - gRPC service:
mt5_term_api.AccountHelper - Proto definition:
PositionsHistory(defined inmt5-term-api-account-helper.proto) - Enums in this method: 2 enums with 13 total constants (1 input, 1 output)
RPC¶
- Service:
mt5_term_api.AccountHelper - Method:
PositionsHistory(PositionsHistoryRequest) -> PositionsHistoryReply - Low-level client (generated):
AccountHelperStub.PositionsHistory(request, metadata, timeout)
💬 Just the essentials¶
- What it is. Get historical closed positions filtered by open time range.
- Why you need it. Analyze trading performance, calculate statistics on closed positions.
- When to use. Use this for closed positions. Use
order_history()for order/deal history.
🎯 Purpose¶
Use it to retrieve historical closed positions:
- Get closed positions within date range
- Analyze position performance
- Calculate profit/loss by position
- Review closed trades
- Filter by open time
- Pagination support for large datasets
📚 Tutorial¶
For a detailed line-by-line explanation with examples, see: -> positions_history - How it works
Method Signature¶
async def positions_history(
self,
sort_type: account_helper_pb2.AH_ENUM_POSITIONS_HISTORY_SORT_TYPE,
open_from: Optional[datetime] = None,
open_to: Optional[datetime] = None,
page: int = 0,
size: int = 0,
deadline: Optional[datetime] = None,
cancellation_event: Optional[asyncio.Event] = None,
) -> account_helper_pb2.PositionsHistoryData
Request message:
message PositionsHistoryRequest {
AH_ENUM_POSITIONS_HISTORY_SORT_TYPE sort_type = 1;
optional google.protobuf.Timestamp position_open_time_from = 2;
optional google.protobuf.Timestamp position_open_time_to = 3;
optional int32 page_number = 4;
optional int32 items_per_page = 5;
}
Reply message:
message PositionsHistoryReply {
oneof response {
PositionsHistoryData data = 1;
Error error = 2;
}
}
message PositionsHistoryData {
repeated PositionHistoryInfo history_positions = 1;
}
🔽 Input¶
| Parameter | Type | Description |
|---|---|---|
sort_type |
AH_ENUM_POSITIONS_HISTORY_SORT_TYPE (enum, required) |
Sort mode for positions |
open_from |
datetime (optional) |
Start of open time filter (UTC) |
open_to |
datetime (optional) |
End of open time filter (UTC) |
page |
int (optional) |
Page number for pagination (default 0) |
size |
int (optional) |
Items per page (default 0 = all) |
deadline |
datetime (optional) |
Deadline for the gRPC call (UTC datetime) |
cancellation_event |
asyncio.Event (optional) |
Event to cancel the operation |
Usage:
from datetime import datetime, timedelta
import MetaRpcMT5.mt5_term_api_account_helper_pb2 as account_helper_pb2
# Get positions opened in last 7 days
to_dt = datetime.utcnow()
from_dt = to_dt - timedelta(days=7)
data = await account.positions_history(
sort_type=account_helper_pb2.AH_POSITION_OPEN_TIME_DESC,
open_from=from_dt,
open_to=to_dt,
page=0,
size=100
)
⬆️ Output - PositionsHistoryData¶
| Field | Type | Python Type | Description |
|---|---|---|---|
history_positions |
repeated PositionHistoryInfo |
list[PositionHistoryInfo] |
List of historical position records |
Each PositionHistoryInfo contains:
| Field | Type | Description |
|---|---|---|
index |
int32 |
Index in the result set |
position_ticket |
uint64 |
Position ticket ID |
order_type |
AH_ENUM_POSITIONS_HISTORY_ORDER_TYPE |
Order type (BUY, SELL, etc.) |
open_time |
Timestamp |
Position open time |
close_time |
Timestamp |
Position close time |
volume |
double |
Position volume in lots |
open_price |
double |
Open price |
close_price |
double |
Close price |
stop_loss |
double |
Stop loss level |
take_profit |
double |
Take profit level |
market_value |
double |
Market value |
commission |
double |
Commission charged |
fee |
double |
Additional fees |
profit |
double |
Position profit/loss |
swap |
double |
Swap (rollover) |
comment |
string |
Comment |
symbol |
string |
Symbol name |
magic |
int64 |
Magic number (EA identifier) |
🧱 Related enums (from proto)¶
AH_ENUM_POSITIONS_HISTORY_SORT_TYPE¶
Defined in mt5-term-api-account-helper.proto.
| Constant | Value | Description |
|---|---|---|
AH_POSITION_OPEN_TIME_ASC |
0 | Sort by open time ascending |
AH_POSITION_OPEN_TIME_DESC |
1 | Sort by open time descending |
AH_POSITION_TICKET_ASC |
2 | Sort by ticket ID ascending |
AH_POSITION_TICKET_DESC |
3 | Sort by ticket ID descending |
Usage:
import MetaRpcMT5.mt5_term_api_account_helper_pb2 as pb2
data = await account.positions_history(
sort_type=pb2.AH_POSITION_OPEN_TIME_DESC,
open_from=from_dt,
open_to=to_dt
)
AH_ENUM_POSITIONS_HISTORY_ORDER_TYPE¶
Defined in mt5-term-api-account-helper.proto. Used in PositionHistoryInfo to indicate order type.
| Constant | Value | Description |
|---|---|---|
AH_ORDER_TYPE_BUY |
0 | Market Buy order |
AH_ORDER_TYPE_SELL |
1 | Market Sell order |
AH_ORDER_TYPE_BUY_LIMIT |
2 | Buy Limit pending order |
AH_ORDER_TYPE_SELL_LIMIT |
3 | Sell Limit pending order |
AH_ORDER_TYPE_BUY_STOP |
4 | Buy Stop pending order |
AH_ORDER_TYPE_SELL_STOP |
5 | Sell Stop pending order |
AH_ORDER_TYPE_BUY_STOP_LIMIT |
6 | Buy Stop Limit pending order |
AH_ORDER_TYPE_SELL_STOP_LIMIT |
7 | Sell Stop Limit pending order |
AH_ORDER_TYPE_CLOSE_BY |
8 | Close by opposite position |
Usage:
import MetaRpcMT5.mt5_term_api_account_helper_pb2 as pb2
# Filter buy positions
for pos in data.history_positions:
if pos.order_type == pb2.AH_ORDER_TYPE_BUY:
print(f"Buy position: {pos.position_ticket}")
🧩 Notes & Tips¶
- Filter by open time: The
open_fromandopen_tofilter positions by when they were opened, not closed. - Pagination: Use
pageandsizefor large datasets. Setsize=0to get all results. - Automatic reconnection: Built-in protection against transient gRPC errors.
- Connection required: Call
connect_by_host_port()orconnect_by_server_name()first. - Thread safety: Safe to call concurrently from multiple asyncio tasks.
- UUID handling: The terminal instance UUID is auto-generated by the server if not provided. For explicit control (e.g., in streaming scenarios), pass
id_=uuid4()to constructor.
🔗 Usage Examples¶
1) Get last 30 days closed positions¶
import asyncio
from datetime import datetime, timedelta
from MetaRpcMT5 import MT5Account
import MetaRpcMT5.mt5_term_api_account_helper_pb2 as account_helper_pb2
async def main():
account = MT5Account(
user=12345678,
password="your_password",
grpc_server="mt5.mrpc.pro:443"
)
await account.connect_by_server_name(
server_name="YourBroker-Demo",
base_chart_symbol="EURUSD"
)
try:
# Last 30 days
to_dt = datetime.utcnow()
from_dt = to_dt - timedelta(days=30)
data = await account.positions_history(
sort_type=account_helper_pb2.AH_POSITION_OPEN_TIME_DESC,
open_from=from_dt,
open_to=to_dt,
size=100
)
print(f"Closed positions: {len(data.history_positions)}")
for pos in data.history_positions:
open_time = pos.open_time.ToDatetime()
close_time = pos.close_time.ToDatetime()
print(f"#{pos.position_ticket} {pos.symbol}: "
f"${pos.profit:+.2f} "
f"({open_time.date()} - {close_time.date()})")
finally:
await account.channel.close()
asyncio.run(main())
2) Calculate total profit from closed positions¶
async def calculate_closed_profit(
account: MT5Account,
from_dt: datetime,
to_dt: datetime
) -> float:
"""Calculate total profit from closed positions"""
data = await account.positions_history(
sort_type=account_helper_pb2.AH_POSITION_OPEN_TIME_ASC,
open_from=from_dt,
open_to=to_dt,
size=0 # Get all
)
total_profit = sum(pos.profit for pos in data.history_positions)
total_commission = sum(pos.commission for pos in data.history_positions)
total_swap = sum(pos.swap for pos in data.history_positions)
net_profit = total_profit - total_commission + total_swap
print(f"Closed positions statistics:")
print(f" Total positions: {len(data.history_positions)}")
print(f" Gross profit: ${total_profit:+.2f}")
print(f" Commission: ${total_commission:.2f}")
print(f" Swap: ${total_swap:+.2f}")
print(f" Net profit: ${net_profit:+.2f}")
return net_profit
# Usage:
from_dt = datetime.utcnow() - timedelta(days=30)
to_dt = datetime.utcnow()
profit = await calculate_closed_profit(account, from_dt, to_dt)
3) Analyze win rate¶
async def analyze_closed_positions(
account: MT5Account,
from_dt: datetime,
to_dt: datetime
) -> dict:
"""Analyze closed positions performance"""
data = await account.positions_history(
sort_type=account_helper_pb2.AH_POSITION_OPEN_TIME_ASC,
open_from=from_dt,
open_to=to_dt
)
wins = 0
losses = 0
total_win_profit = 0.0
total_loss_profit = 0.0
for pos in data.history_positions:
if pos.profit > 0:
wins += 1
total_win_profit += pos.profit
elif pos.profit < 0:
losses += 1
total_loss_profit += pos.profit
total = wins + losses
win_rate = (wins / total * 100) if total > 0 else 0
avg_win = total_win_profit / wins if wins > 0 else 0
avg_loss = total_loss_profit / losses if losses > 0 else 0
result = {
"total_positions": total,
"wins": wins,
"losses": losses,
"win_rate": win_rate,
"avg_win": avg_win,
"avg_loss": avg_loss,
"profit_factor": abs(total_win_profit / total_loss_profit) if total_loss_profit != 0 else 0
}
print(f"\nPosition Analysis:")
print(f" Total: {total}")
print(f" Wins: {wins} ({win_rate:.1f}%)")
print(f" Losses: {losses}")
print(f" Avg Win: ${avg_win:.2f}")
print(f" Avg Loss: ${avg_loss:.2f}")
print(f" Profit Factor: {result['profit_factor']:.2f}")
return result
# Usage:
from_dt = datetime.utcnow() - timedelta(days=30)
to_dt = datetime.utcnow()
stats = await analyze_closed_positions(account, from_dt, to_dt)
4) Get best and worst positions¶
async def get_best_worst_positions(
account: MT5Account,
from_dt: datetime,
to_dt: datetime,
limit: int = 5
):
"""Get best and worst performing positions"""
data = await account.positions_history(
sort_type=account_helper_pb2.AH_POSITION_OPEN_TIME_ASC,
open_from=from_dt,
open_to=to_dt
)
# Sort by profit
sorted_positions = sorted(data.history_positions, key=lambda p: p.profit, reverse=True)
best = sorted_positions[:limit]
worst = sorted_positions[-limit:]
print(f"\nBest {limit} positions:")
for i, pos in enumerate(best, 1):
print(f" {i}. #{pos.position_ticket} {pos.symbol}: ${pos.profit:+.2f}")
print(f"\nWorst {limit} positions:")
for i, pos in enumerate(worst, 1):
print(f" {i}. #{pos.position_ticket} {pos.symbol}: ${pos.profit:+.2f}")
return {"best": best, "worst": worst}
# Usage:
from_dt = datetime.utcnow() - timedelta(days=30)
to_dt = datetime.utcnow()
results = await get_best_worst_positions(account, from_dt, to_dt)
5) Get positions by symbol¶
async def get_symbol_closed_positions(
account: MT5Account,
symbol: str,
from_dt: datetime,
to_dt: datetime
):
"""Get closed positions for specific symbol"""
data = await account.positions_history(
sort_type=account_helper_pb2.AH_POSITION_OPEN_TIME_DESC,
open_from=from_dt,
open_to=to_dt
)
symbol_positions = [
pos for pos in data.history_positions
if pos.symbol == symbol
]
total_profit = sum(pos.profit for pos in symbol_positions)
print(f"\n{symbol} closed positions: {len(symbol_positions)}")
print(f"Total profit: ${total_profit:+.2f}")
return symbol_positions
# Usage:
from_dt = datetime.utcnow() - timedelta(days=30)
to_dt = datetime.utcnow()
eurusd_positions = await get_symbol_closed_positions(account, "EURUSD", from_dt, to_dt)
6) Export to CSV¶
import csv
async def export_positions_to_csv(
account: MT5Account,
from_dt: datetime,
to_dt: datetime,
filename: str = "positions_history.csv"
):
"""Export closed positions to CSV"""
data = await account.positions_history(
sort_type=account_helper_pb2.AH_POSITION_OPEN_TIME_ASC,
open_from=from_dt,
open_to=to_dt
)
with open(filename, 'w', newline='', encoding='utf-8') as f:
writer = csv.writer(f)
writer.writerow([
'Ticket', 'Symbol', 'Open Time', 'Close Time',
'Volume', 'Open Price', 'Close Price',
'Profit', 'Commission', 'Swap', 'Total'
])
for pos in data.history_positions:
open_time = pos.open_time.ToDatetime()
close_time = pos.close_time.ToDatetime()
total = pos.profit - pos.commission + pos.swap
writer.writerow([
pos.position_ticket,
pos.symbol,
open_time.strftime('%Y-%m-%d %H:%M:%S'),
close_time.strftime('%Y-%m-%d %H:%M:%S'),
pos.volume,
pos.open_price,
pos.close_price,
pos.profit,
pos.commission,
pos.swap,
total
])
print(f"[OK] Exported {len(data.history_positions)} positions to {filename}")
# Usage:
from_dt = datetime.utcnow() - timedelta(days=30)
to_dt = datetime.utcnow()
await export_positions_to_csv(account, from_dt, to_dt)
Common Patterns¶
Calculate daily profit¶
from collections import defaultdict
async def get_daily_profit(account: MT5Account, from_dt: datetime, to_dt: datetime):
"""Calculate profit per day from closed positions"""
data = await account.positions_history(
sort_type=account_helper_pb2.AH_POSITION_OPEN_TIME_ASC,
open_from=from_dt,
open_to=to_dt
)
daily_profit = defaultdict(float)
for pos in data.history_positions:
close_date = pos.close_time.ToDatetime().date()
daily_profit[close_date] += pos.profit
return dict(daily_profit)
Get positions with pagination¶
async def get_all_positions_paginated(
account: MT5Account,
from_dt: datetime,
to_dt: datetime,
page_size: int = 100
):
"""Get all positions using pagination"""
all_positions = []
page = 0
while True:
data = await account.positions_history(
sort_type=account_helper_pb2.AH_POSITION_OPEN_TIME_ASC,
open_from=from_dt,
open_to=to_dt,
page=page,
size=page_size
)
if not data.history_positions:
break
all_positions.extend(data.history_positions)
page += 1
return all_positions
📚 See also¶
- order_history - Get historical orders and deals
- opened_orders - Get currently open positions
- positions_total - Get count of open positions