mirror of
https://github.com/silencesdg/mt5_python_ea_suite.git
synced 2026-07-28 03:07:48 +00:00
193 lines
6.8 KiB
Python
193 lines
6.8 KiB
Python
import pandas as pd
|
|
import numpy as np
|
|
from datetime import datetime
|
|
from logger import logger
|
|
import json
|
|
|
|
class TradeLogger:
|
|
"""
|
|
交易日志记录器,跟踪每一笔交易的详情
|
|
"""
|
|
|
|
def __init__(self):
|
|
self.trades = [] # 存储所有交易记录
|
|
self.current_position = None # 当前持仓信息
|
|
self.trade_id = 0 # 交易ID计数器
|
|
|
|
def open_position(self, symbol, direction, price, timestamp, strategy_signal=""):
|
|
"""
|
|
开仓
|
|
"""
|
|
if self.current_position is not None:
|
|
return False
|
|
|
|
self.trade_id += 1
|
|
self.current_position = {
|
|
'trade_id': self.trade_id,
|
|
'symbol': symbol,
|
|
'direction': direction, # 'buy' 或 'sell'
|
|
'open_price': price,
|
|
'open_time': timestamp,
|
|
'strategy_signal': strategy_signal,
|
|
'status': 'open'
|
|
}
|
|
|
|
logger.info(f"开仓 #{self.trade_id}: {direction} {symbol} @ {price:.2f} 时间: {timestamp}")
|
|
return True
|
|
|
|
def close_position(self, symbol, price, timestamp, close_reason="signal"):
|
|
"""
|
|
平仓
|
|
"""
|
|
if self.current_position is None:
|
|
return False
|
|
|
|
if self.current_position['symbol'] != symbol:
|
|
return False
|
|
|
|
# 计算盈亏
|
|
if self.current_position['direction'] == 'buy':
|
|
profit_loss = price - self.current_position['open_price']
|
|
profit_loss_pct = profit_loss / self.current_position['open_price'] * 100
|
|
else: # sell
|
|
profit_loss = self.current_position['open_price'] - price
|
|
profit_loss_pct = profit_loss / self.current_position['open_price'] * 100
|
|
|
|
# 完成交易记录
|
|
trade_record = self.current_position.copy()
|
|
trade_record.update({
|
|
'close_price': price,
|
|
'close_time': timestamp,
|
|
'close_reason': close_reason,
|
|
'profit_loss': profit_loss,
|
|
'profit_loss_pct': profit_loss_pct,
|
|
'status': 'closed'
|
|
})
|
|
|
|
self.trades.append(trade_record)
|
|
|
|
logger.info(f"平仓 #{self.trade_id}: {trade_record['direction']} {symbol} @ {price:.2f} | "
|
|
f"盈亏: {profit_loss:.2f} ({profit_loss_pct:+.2f}%) | 时间: {timestamp}")
|
|
|
|
self.current_position = None
|
|
return True
|
|
|
|
def _calculate_profit_loss_pct(self, current_price, position):
|
|
"""
|
|
计算当前持仓的盈亏百分比
|
|
"""
|
|
if not position:
|
|
return 0
|
|
|
|
if position['direction'] == 'buy':
|
|
profit_loss = current_price - position['open_price']
|
|
else: # sell
|
|
profit_loss = position['open_price'] - current_price
|
|
|
|
return profit_loss / position['open_price'] * 100
|
|
|
|
def get_trade_summary(self):
|
|
"""
|
|
获取交易统计摘要
|
|
"""
|
|
if not self.trades:
|
|
return {
|
|
'total_trades': 0,
|
|
'winning_trades': 0,
|
|
'losing_trades': 0,
|
|
'win_rate': 0,
|
|
'total_profit_loss': 0,
|
|
'avg_profit_loss': 0,
|
|
'max_profit': 0,
|
|
'max_loss': 0
|
|
}
|
|
|
|
closed_trades = [t for t in self.trades if t['status'] == 'closed']
|
|
if not closed_trades:
|
|
return {
|
|
'total_trades': len(self.trades),
|
|
'winning_trades': 0,
|
|
'losing_trades': 0,
|
|
'win_rate': 0,
|
|
'total_profit_loss': 0,
|
|
'avg_profit_loss': 0,
|
|
'max_profit': 0,
|
|
'max_loss': 0
|
|
}
|
|
|
|
profits = [t['profit_loss'] for t in closed_trades]
|
|
winning_trades = len([p for p in profits if p > 0])
|
|
losing_trades = len([p for p in profits if p < 0])
|
|
|
|
return {
|
|
'total_trades': len(closed_trades),
|
|
'winning_trades': winning_trades,
|
|
'losing_trades': losing_trades,
|
|
'win_rate': winning_trades / len(closed_trades) * 100 if closed_trades else 0,
|
|
'total_profit_loss': sum(profits),
|
|
'avg_profit_loss': np.mean(profits) if profits else 0,
|
|
'max_profit': max(profits) if profits else 0,
|
|
'max_loss': min(profits) if profits else 0
|
|
}
|
|
|
|
def print_all_trades(self):
|
|
"""
|
|
打印所有交易记录
|
|
"""
|
|
if not self.trades:
|
|
logger.info("暂无交易记录")
|
|
return
|
|
|
|
logger.info("=" * 80)
|
|
logger.info("交易记录详情")
|
|
logger.info("=" * 80)
|
|
|
|
for trade in self.trades:
|
|
if trade['status'] == 'closed':
|
|
logger.info(f"交易 #{trade['trade_id']}:")
|
|
logger.info(f" {trade['direction'].upper()} {trade['symbol']}")
|
|
logger.info(f" 开仓: {trade['open_price']:.2f} @ {trade['open_time']}")
|
|
logger.info(f" 平仓: {trade['close_price']:.2f} @ {trade['close_time']}")
|
|
logger.info(f" 盈亏: {trade['profit_loss']:.2f} ({trade['profit_loss_pct']:+.2f}%)")
|
|
logger.info(f" 原因: {trade['close_reason']}")
|
|
logger.info("-" * 40)
|
|
|
|
# 打印统计摘要
|
|
summary = self.get_trade_summary()
|
|
logger.info("交易统计摘要:")
|
|
logger.info(f" 总交易次数: {summary['total_trades']}")
|
|
logger.info(f" 盈利次数: {summary['winning_trades']}")
|
|
logger.info(f" 亏损次数: {summary['losing_trades']}")
|
|
logger.info(f" 胜率: {summary['win_rate']:.1f}%")
|
|
logger.info(f" 总盈亏: {summary['total_profit_loss']:.2f}")
|
|
logger.info(f" 平均盈亏: {summary['avg_profit_loss']:.2f}")
|
|
logger.info(f" 最大盈利: {summary['max_profit']:.2f}")
|
|
logger.info(f" 最大亏损: {summary['max_loss']:.2f}")
|
|
logger.info("=" * 80)
|
|
|
|
def save_to_csv(self, filename="trades.csv"):
|
|
"""
|
|
保存交易记录到CSV文件
|
|
"""
|
|
if not self.trades:
|
|
logger.info("无交易记录可保存")
|
|
return
|
|
|
|
df = pd.DataFrame(self.trades)
|
|
df.to_csv(filename, index=False, encoding='utf-8-sig')
|
|
logger.info(f"交易记录已保存到 {filename}")
|
|
|
|
def save_to_json(self, filename="trades.json"):
|
|
"""
|
|
保存交易记录到JSON文件
|
|
"""
|
|
if not self.trades:
|
|
logger.info("无交易记录可保存")
|
|
return
|
|
|
|
with open(filename, 'w', encoding='utf-8') as f:
|
|
json.dump(self.trades, f, ensure_ascii=False, indent=2, default=str)
|
|
logger.info(f"交易记录已保存到 {filename}")
|
|
|
|
# 全局交易日志记录器实例
|
|
trade_logger = TradeLogger() |