621 lines
23 KiB
MQL5
621 lines
23 KiB
MQL5
//+------------------------------------------------------------------+
|
|
//| PaperTrading.mqh |
|
|
//| Copyright 2025, EscapeEA |
|
|
//| https://www.escapeea.com |
|
|
//+------------------------------------------------------------------+
|
|
#property copyright "Copyright 2025, EscapeEA"
|
|
#property link "https://www.escapeea.com"
|
|
#property version "2.00"
|
|
#property strict
|
|
|
|
#include <Trade\PositionInfo.mqh>
|
|
#include <Trade\DealInfo.mqh>
|
|
#include <Trade\AccountInfo.mqh>
|
|
#include <Trade\SymbolInfo.mqh>
|
|
#include <Arrays\ArrayObj.mqh>
|
|
|
|
// Forward declarations
|
|
class CRiskManager; // Forward declaration of RiskManager
|
|
|
|
// Include the actual header files after forward declarations
|
|
#include "RiskManager.mqh"
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Paper Trade Settings |
|
|
//+------------------------------------------------------------------+
|
|
input group "=== Paper Trading Settings ==="
|
|
input bool InpEnablePaperTrading = true; // Enable Paper Trading
|
|
input int InpPaperTradesRequired = 5; // Total Paper Trades
|
|
input int InpPaperWinsRequired = 3; // Required Wins for Live Trading
|
|
input double InpPaperInitialBalance = 10000.0; // Initial Paper Balance
|
|
input bool InpSimulateSlippage = true; // Simulate Slippage
|
|
input bool InpSimulatePartialFills = true; // Simulate Partial Fills
|
|
input double InpMaxSlippagePips = 1.0; // Max Slippage (pips)
|
|
input int InpFillProbability = 90; // Fill Probability (%)
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Paper Trade Structure |
|
|
//+------------------------------------------------------------------+
|
|
struct SPaperTrade {
|
|
ulong ticket; // Trade ticket
|
|
string symbol; // Symbol
|
|
ENUM_ORDER_TYPE type; // Order type
|
|
double volume; // Trade volume
|
|
double openPrice; // Open price
|
|
double stopLoss; // Stop loss
|
|
double takeProfit; // Take profit
|
|
datetime openTime; // Open time
|
|
datetime closeTime; // Close time
|
|
double closePrice; // Close price
|
|
double commission; // Commission
|
|
double swap; // Swap
|
|
double profit; // Profit/loss
|
|
bool isClosed; // Is trade closed
|
|
bool isWinner; // Is winning trade
|
|
|
|
// Constructor
|
|
SPaperTrade() {
|
|
ticket = 0;
|
|
volume = 0.0;
|
|
openPrice = 0.0;
|
|
stopLoss = 0.0;
|
|
takeProfit = 0.0;
|
|
closePrice = 0.0;
|
|
commission = 0.0;
|
|
swap = 0.0;
|
|
profit = 0.0;
|
|
isClosed = false;
|
|
isWinner = false;
|
|
}
|
|
};
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| CPaperTrading class |
|
|
//+------------------------------------------------------------------+
|
|
class CPaperTrading
|
|
{
|
|
private:
|
|
// Trade management
|
|
CArrayObj m_trades; // Array of paper trades
|
|
double m_balance; // Current paper balance
|
|
double m_equity; // Current paper equity
|
|
int m_totalTrades; // Total paper trades
|
|
int m_winningTrades; // Number of winning trades
|
|
int m_losingTrades; // Number of losing trades
|
|
bool m_liveTrading; // Live trading enabled
|
|
|
|
// Simulation parameters
|
|
double m_slippage; // Current slippage
|
|
double m_spread; // Current spread
|
|
|
|
// External components
|
|
CSymbolInfo m_symbol; // Symbol info
|
|
CAccountInfo m_account; // Account info
|
|
CRiskManager *m_riskManager; // Risk manager (forward declared)
|
|
|
|
// Private methods
|
|
double CalculateCommission(double volume, double price);
|
|
double CalculateSwap(double volume, double price, ENUM_ORDER_TYPE type);
|
|
double CalculateSlippage();
|
|
bool SimulateFill();
|
|
void UpdateEquity();
|
|
|
|
public:
|
|
// Constructor/Destructor
|
|
CPaperTrading(CRiskManager *riskManager = NULL);
|
|
~CPaperTrading();
|
|
|
|
// Initialization
|
|
bool Initialize(CSymbolInfo *symbol, CAccountInfo *account,
|
|
CEnhancedRiskManager *riskManager);
|
|
void Deinitialize();
|
|
|
|
// Trading operations
|
|
bool OpenPosition(ENUM_ORDER_TYPE type, double volume,
|
|
double price, double sl, double tp,
|
|
string comment = "");
|
|
bool ClosePosition(ulong ticket, double price);
|
|
void CheckForExits();
|
|
|
|
// Getters
|
|
double GetBalance() const { return m_balance; }
|
|
double GetEquity() const { return m_equity; }
|
|
int GetTotalTrades() const { return m_totalTrades; }
|
|
int GetWinningTrades() const { return m_winningTrades; }
|
|
int GetLosingTrades() const { return m_losingTrades; }
|
|
double GetWinRate() const {
|
|
return (m_totalTrades > 0) ? (double)m_winningTrades / m_totalTrades * 100.0 : 0.0;
|
|
}
|
|
bool IsLiveTradingAllowed() const {
|
|
return m_liveTrading || (!InpEnablePaperTrading);
|
|
}
|
|
|
|
// State management
|
|
void Reset();
|
|
void Update();
|
|
|
|
// Chart display
|
|
void UpdateChart();
|
|
};
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Constructor |
|
|
//+------------------------------------------------------------------+
|
|
CPaperTrading::CPaperTrading() : m_balance(InpPaperInitialBalance),
|
|
m_equity(InpPaperInitialBalance),
|
|
m_totalTrades(0),
|
|
m_winningTrades(0),
|
|
m_losingTrades(0),
|
|
m_liveTrading(false),
|
|
m_slippage(0.0),
|
|
m_spread(0.0),
|
|
m_symbol(NULL),
|
|
m_account(NULL),
|
|
m_riskManager(NULL)
|
|
{
|
|
m_trades.Clear();
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Destructor |
|
|
//+------------------------------------------------------------------+
|
|
CPaperTrading::~CPaperTrading()
|
|
{
|
|
Deinitialize();
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Initialize paper trading system |
|
|
//+------------------------------------------------------------------+
|
|
bool CPaperTrading::Initialize(CSymbolInfo *symbol, CAccountInfo *account,
|
|
CEnhancedRiskManager *riskManager)
|
|
{
|
|
// Validate inputs
|
|
if(symbol == NULL || account == NULL || riskManager == NULL) {
|
|
Print("PaperTrading: Invalid initialization parameters");
|
|
return false;
|
|
}
|
|
|
|
// Store references
|
|
m_symbol = symbol;
|
|
m_account = account;
|
|
m_riskManager = riskManager;
|
|
|
|
// Reset state
|
|
Reset();
|
|
|
|
// Initialize chart objects
|
|
UpdateChart();
|
|
|
|
return true;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Clean up resources |
|
|
//+------------------------------------------------------------------+
|
|
void CPaperTrading::Deinitialize()
|
|
{
|
|
// Clean up resources
|
|
m_trades.Clear();
|
|
|
|
// Remove chart objects
|
|
string prefix = "PaperTrade_";
|
|
ObjectsDeleteAll(0, prefix);
|
|
ChartRedraw();
|
|
|
|
// Reset references
|
|
m_symbol = NULL;
|
|
m_account = NULL;
|
|
m_riskManager = NULL;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Open a new paper position |
|
|
//+------------------------------------------------------------------+
|
|
bool CPaperTrading::OpenPosition(ENUM_ORDER_TYPE type, double volume,
|
|
double price, double sl, double tp,
|
|
string comment = "")
|
|
{
|
|
// Check if paper trading is enabled
|
|
if(!InpEnablePaperTrading) {
|
|
m_liveTrading = true;
|
|
return true; // Allow live trading
|
|
}
|
|
|
|
// Check if we have enough wins to trade live
|
|
if(m_winningTrades >= InpPaperWinsRequired && m_totalTrades >= InpPaperTradesRequired) {
|
|
m_liveTrading = true;
|
|
return true; // Allow live trading
|
|
}
|
|
|
|
// Check if we've reached the maximum number of paper trades
|
|
if(m_totalTrades >= InpPaperTradesRequired) {
|
|
Print("PaperTrading: Maximum number of paper trades reached (", InpPaperTradesRequired, "). ",
|
|
m_winningTrades, " wins out of ", InpPaperWinsRequired, " required for live trading.");
|
|
return false;
|
|
}
|
|
|
|
// Check risk management
|
|
if(m_riskManager != NULL && !m_riskManager.IsTradingAllowed()) {
|
|
Print("PaperTrading: Risk management restricts trading");
|
|
return false;
|
|
}
|
|
|
|
// Check if we can simulate a fill
|
|
if(!SimulateFill()) {
|
|
Print("PaperTrading: Order fill simulation failed");
|
|
return false;
|
|
}
|
|
|
|
// Calculate slippage if enabled
|
|
double actualPrice = price;
|
|
if(InpSimulateSlippage) {
|
|
double slippage = CalculateSlippage();
|
|
if(type == ORDER_TYPE_BUY || type == ORDER_TYPE_BUY_LIMIT || type == ORDER_TYPE_BUY_STOP) {
|
|
actualPrice += slippage;
|
|
} else {
|
|
actualPrice -= slippage;
|
|
}
|
|
}
|
|
|
|
// Create new trade
|
|
SPaperTrade *trade = new SPaperTrade();
|
|
if(trade == NULL) {
|
|
Print("PaperTrading: Failed to create new trade");
|
|
return false;
|
|
}
|
|
|
|
// Initialize trade
|
|
trade.ticket = (ulong)(TimeCurrent() % 1000000 + m_trades.Total());
|
|
trade.symbol = m_symbol.Name();
|
|
trade.type = type;
|
|
trade.volume = volume;
|
|
trade.openPrice = actualPrice;
|
|
trade.stopLoss = sl;
|
|
trade.takeProfit = tp;
|
|
trade.openTime = TimeCurrent();
|
|
trade.commission = CalculateCommission(volume, actualPrice);
|
|
trade.swap = 0.0; // Will be updated on bar close
|
|
trade.isClosed = false;
|
|
trade.isWinner = false;
|
|
|
|
// Add to trades array
|
|
if(!m_trades.Add(trade)) {
|
|
delete trade;
|
|
Print("PaperTrading: Failed to add trade to array");
|
|
return false;
|
|
}
|
|
|
|
// Update statistics
|
|
m_totalTrades++;
|
|
|
|
// Update balance (subtract margin, will be added back on close)
|
|
double margin = m_riskManager != NULL ?
|
|
m_riskManager.CalculateMargin(trade.symbol, trade.volume, trade.type) :
|
|
(trade.volume * m_symbol.TickValue() * m_symbol.TickSize());
|
|
m_balance -= margin;
|
|
|
|
PrintFormat("PaperTrading: Opened %s position %.2f lots at %.5f (SL: %.5f, TP: %.5f)",
|
|
EnumToString(type), volume, actualPrice, sl, tp);
|
|
|
|
// Update chart
|
|
UpdateChart();
|
|
|
|
return true;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Close a paper position |
|
|
//+------------------------------------------------------------------+
|
|
bool CPaperTrading::ClosePosition(ulong ticket, double price)
|
|
{
|
|
for(int i = 0; i < m_trades.Total(); i++) {
|
|
SPaperTrade *trade = m_trades.At(i);
|
|
if(trade == NULL || trade.isClosed || trade.ticket != ticket) continue;
|
|
|
|
// Calculate P/L
|
|
double priceDiff = price - trade.openPrice;
|
|
if(trade.type == ORDER_TYPE_SELL || trade.type == ORDER_TYPE_SELL_LIMIT || trade.type == ORDER_TYPE_SELL_STOP) {
|
|
priceDiff = -priceDiff;
|
|
}
|
|
|
|
double pips = priceDiff / m_symbol.Point();
|
|
double profit = pips * m_symbol.TickValue() * trade.volume / (m_symbol.Point() / m_symbol.TickSize());
|
|
|
|
// Update trade info
|
|
trade.closePrice = price;
|
|
trade.closeTime = TimeCurrent();
|
|
trade.profit = profit;
|
|
trade.isClosed = true;
|
|
|
|
// Update balance
|
|
m_balance += profit - trade.commission - trade.swap;
|
|
|
|
// Update statistics
|
|
if(profit > 0) {
|
|
m_winningTrades++;
|
|
} else {
|
|
m_losingTrades++;
|
|
}
|
|
|
|
// Update equity
|
|
UpdateEquity();
|
|
|
|
PrintFormat("PaperTrading: Closed position %d at %.5f, P/L: $%.2f",
|
|
ticket, price, profit - trade.commission - trade.swap);
|
|
|
|
// Update chart
|
|
UpdateChart();
|
|
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Check for exit conditions |
|
|
//+------------------------------------------------------------------+
|
|
void CPaperTrading::CheckForExits()
|
|
{
|
|
double bid = m_symbol.Bid();
|
|
double ask = m_symbol.Ask();
|
|
|
|
for(int i = m_trades.Total() - 1; i >= 0; i--) {
|
|
SPaperTrade *trade = m_trades.At(i);
|
|
if(trade == NULL || trade.isClosed) continue;
|
|
|
|
// Check if position should be closed by stop loss or take profit
|
|
double currentPrice = (trade.type == ORDER_TYPE_BUY || trade.type == ORDER_TYPE_BUY_LIMIT || trade.type == ORDER_TYPE_BUY_STOP) ? bid : ask;
|
|
|
|
// Check stop loss
|
|
if((trade.type == ORDER_TYPE_BUY || trade.type == ORDER_TYPE_BUY_LIMIT || trade.type == ORDER_TYPE_BUY_STOP) &&
|
|
trade.stopLoss > 0 && currentPrice <= trade.stopLoss) {
|
|
ClosePosition(trade.ticket, trade.stopLoss);
|
|
trade.isWinner = false;
|
|
continue;
|
|
}
|
|
|
|
if((trade.type == ORDER_TYPE_SELL || trade.type == ORDER_TYPE_SELL_LIMIT || trade.type == ORDER_TYPE_SELL_STOP) &&
|
|
trade.stopLoss > 0 && currentPrice >= trade.stopLoss) {
|
|
ClosePosition(trade.ticket, trade.stopLoss);
|
|
trade.isWinner = false;
|
|
continue;
|
|
}
|
|
|
|
// Check take profit
|
|
if((trade.type == ORDER_TYPE_BUY || trade.type == ORDER_TYPE_BUY_LIMIT || trade.type == ORDER_TYPE_BUY_STOP) &&
|
|
trade.takeProfit > 0 && currentPrice >= trade.takeProfit) {
|
|
ClosePosition(trade.ticket, trade.takeProfit);
|
|
trade.isWinner = true;
|
|
continue;
|
|
}
|
|
|
|
if((trade.type == ORDER_TYPE_SELL || trade.type == ORDER_TYPE_SELL_LIMIT || trade.type == ORDER_TYPE_SELL_STOP) &&
|
|
trade.takeProfit > 0 && currentPrice <= trade.takeProfit) {
|
|
ClosePosition(trade.ticket, trade.takeProfit);
|
|
trade.isWinner = true;
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Calculate commission for a trade |
|
|
//+------------------------------------------------------------------+
|
|
double CPaperTrading::CalculateCommission(double volume, double price)
|
|
{
|
|
// Default commission: $5 per lot
|
|
return 5.0 * volume;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Calculate swap for an open position |
|
|
//+------------------------------------------------------------------+
|
|
double CPaperTrading::CalculateSwap(double volume, double price, ENUM_ORDER_TYPE type)
|
|
{
|
|
// Simulate swap based on position type and size
|
|
double swapRate = (type == ORDER_TYPE_BUY) ? -0.5 : 0.2; // pips per lot per day
|
|
return volume * swapRate * m_symbol.Point() * 10000.0;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Calculate slippage for order execution |
|
|
//+------------------------------------------------------------------
|
|
double CPaperTrading::CalculateSlippage()
|
|
{
|
|
if(!InpSimulateSlippage) return 0.0;
|
|
|
|
// Random slippage up to max slippage
|
|
return MathRand() / 32767.0 * InpMaxSlippagePips * m_symbol.Point();
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Simulate order fill probability |
|
|
//+------------------------------------------------------------------+
|
|
bool CPaperTrading::SimulateFill()
|
|
{
|
|
if(!InpSimulatePartialFills) return true;
|
|
|
|
// Random chance of fill based on fill probability
|
|
return (MathRand() % 100) < InpFillProbability;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Update equity based on open positions |
|
|
//+------------------------------------------------------------------+
|
|
void CPaperTrading::UpdateEquity()
|
|
{
|
|
double floatingPL = 0.0;
|
|
double bid = m_symbol.Bid();
|
|
double ask = m_symbol.Ask();
|
|
double pointValue = m_symbol.Point() * m_symbol.TradeTickValue() / m_symbol.TradeTickSize();
|
|
|
|
for(int i = 0; i < m_trades.Total(); i++) {
|
|
SPaperTrade *trade = m_trades.At(i);
|
|
if(trade == NULL || trade.isClosed) continue;
|
|
|
|
if(trade.type == ORDER_TYPE_BUY) {
|
|
floatingPL += (bid - trade.openPrice) * trade.volume * pointValue;
|
|
} else {
|
|
floatingPL += (trade.openPrice - ask) * trade.volume * pointValue;
|
|
}
|
|
}
|
|
|
|
m_equity = m_balance + floatingPL;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Reset paper trading account |
|
|
//+------------------------------------------------------------------+
|
|
void CPaperTrading::Reset()
|
|
{
|
|
// Clear existing trades
|
|
m_trades.Clear();
|
|
|
|
// Reset metrics
|
|
m_balance = InpPaperInitialBalance;
|
|
m_equity = InpPaperInitialBalance;
|
|
m_totalTrades = 0;
|
|
m_winningTrades = 0;
|
|
m_losingTrades = 0;
|
|
m_liveTrading = false;
|
|
|
|
// Reset chart
|
|
UpdateChart();
|
|
|
|
Print("PaperTrading: Account reset");
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Update paper trading system |
|
|
//+------------------------------------------------------------------+
|
|
void CPaperTrading::Update()
|
|
{
|
|
// Update market data
|
|
m_symbol.RefreshRates();
|
|
|
|
// Check for exits
|
|
CheckForExits();
|
|
|
|
// Update equity
|
|
UpdateEquity();
|
|
|
|
// Update chart periodically
|
|
static datetime lastUpdate = 0;
|
|
if(TimeCurrent() - lastUpdate >= 1) {
|
|
UpdateChart();
|
|
lastUpdate = TimeCurrent();
|
|
}
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Update chart with paper trading information |
|
|
//+------------------------------------------------------------------+
|
|
void CPaperTrading::UpdateChart()
|
|
{
|
|
string prefix = "PaperTrade_";
|
|
|
|
// Create or update info panel
|
|
string objName = prefix + "Info";
|
|
|
|
// Create or update info text
|
|
string infoText = StringFormat("=== Paper Trading ===\n" +
|
|
"Balance: $%.2f\n" +
|
|
"Equity: $%.2f\n" +
|
|
"Trades: %d (%d wins, %d losses)\n" +
|
|
"Win Rate: %.1f%%\n" +
|
|
"Live Trading: %s",
|
|
m_balance, m_equity,
|
|
m_totalTrades, m_winningTrades, m_losingTrades,
|
|
GetWinRate(),
|
|
m_liveTrading ? "Enabled" : "Disabled");
|
|
|
|
// Create or update the info label
|
|
if(ObjectFind(0, objName) < 0) {
|
|
ObjectCreate(0, objName, OBJ_LABEL, 0, 0, 0);
|
|
ObjectSetInteger(0, objName, OBJPROP_CORNER, CORNER_LEFT_UPPER);
|
|
ObjectSetInteger(0, objName, OBJPROP_XDISTANCE, 20);
|
|
ObjectSetInteger(0, objName, OBJPROP_YDISTANCE, 20);
|
|
ObjectSetInteger(0, objName, OBJPROP_COLOR, clrWhite);
|
|
ObjectSetInteger(0, objName, OBJPROP_BGCOLOR, clrNavy);
|
|
ObjectSetInteger(0, objName, OBJPROP_BACK, false);
|
|
ObjectSetInteger(0, objName, OBJPROP_HIDDEN, true);
|
|
ObjectSetInteger(0, objName, OBJPROP_SELECTABLE, false);
|
|
ObjectSetInteger(0, objName, OBJPROP_SELECTED, false);
|
|
ObjectSetInteger(0, objName, OBJPROP_ZORDER, 0);
|
|
}
|
|
|
|
ObjectSetString(0, objName, OBJPROP_TEXT, infoText);
|
|
ObjectSetInteger(0, objName, OBJPROP_FONTSIZE, 8);
|
|
|
|
// Update trade markers on chart
|
|
for(int i = 0; i < m_trades.Total(); i++) {
|
|
SPaperTrade *trade = m_trades.At(i);
|
|
if(trade == NULL) continue;
|
|
|
|
string tradePrefix = prefix + IntegerToString(trade.ticket) + "_";
|
|
|
|
// Create or update entry marker
|
|
string entryName = tradePrefix + "Entry";
|
|
if(ObjectFind(0, entryName) < 0) {
|
|
ObjectCreate(0, entryName, OBJ_ARROW, 0, trade.openTime,
|
|
trade.type <= 1 ? trade.openPrice + 10 * m_symbol.Point() : trade.openPrice - 10 * m_symbol.Point());
|
|
ObjectSetInteger(0, entryName, OBJPROP_ARROWCODE, trade.type <= 1 ? 233 : 234);
|
|
ObjectSetInteger(0, entryName, OBJPROP_COLOR, trade.type <= 1 ? clrLime : clrRed);
|
|
ObjectSetInteger(0, entryName, OBJPROP_WIDTH, 2);
|
|
}
|
|
|
|
// Create or update exit marker if closed
|
|
if(trade.isClosed) {
|
|
string exitName = tradePrefix + "Exit";
|
|
if(ObjectFind(0, exitName) < 0) {
|
|
ObjectCreate(0, exitName, OBJ_ARROW, 0, trade.closeTime,
|
|
trade.type <= 1 ? trade.closePrice - 10 * m_symbol.Point() : trade.closePrice + 10 * m_symbol.Point());
|
|
ObjectSetInteger(0, exitName, OBJPROP_ARROWCODE, 251);
|
|
ObjectSetInteger(0, exitName, OBJPROP_COLOR, trade.isWinner ? clrLime : clrRed);
|
|
ObjectSetInteger(0, exitName, OBJPROP_WIDTH, 2);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Redraw the chart
|
|
ChartRedraw();
|
|
if(ObjectFind(0, objName) < 0) {
|
|
ObjectCreate(0, objName, OBJ_RECTANGLE_LABEL, 0, 0, 0);
|
|
ObjectSetInteger(0, objName, OBJPROP_XDISTANCE, 10);
|
|
ObjectSetInteger(0, objName, OBJPROP_YDISTANCE, 10);
|
|
ObjectSetInteger(0, objName, OBJPROP_XSIZE, 200);
|
|
ObjectSetInteger(0, objName, OBJPROP_YSIZE, 120);
|
|
ObjectSetInteger(0, objName, OBJPROP_BGCOLOR, clrWhiteSmoke);
|
|
ObjectSetInteger(0, objName, OBJPROP_BORDER_TYPE, BORDER_FLAT);
|
|
ObjectSetInteger(0, objName, OBJPROP_CORNER, CORNER_LEFT_UPPER);
|
|
ObjectSetInteger(0, objName, OBJPROP_BACK, false);
|
|
}
|
|
|
|
// Update info text
|
|
string infoText = "=== Paper Trading ===\n";
|
|
infoText += StringFormat("Balance: %.2f %s\n", m_balance, m_account.Currency());
|
|
infoText += StringFormat("Equity: %.2f %s\n", m_equity, m_account.Currency());
|
|
infoText += StringFormat("Trades: %d (%dW/%dL)\n",
|
|
m_totalTrades, m_winningTrades, m_losingTrades);
|
|
infoText += StringFormat("Win Rate: %.1f%%\n", GetWinRate());
|
|
infoText += StringFormat("Status: %s",
|
|
m_liveTrading ? "LIVE TRADING" : "PAPER TRADING");
|
|
|
|
objName = prefix + "InfoText";
|
|
if(ObjectFind(0, objName) < 0) {
|
|
ObjectCreate(0, objName, OBJ_LABEL, 0, 0, 0);
|
|
ObjectSetInteger(0, objName, OBJPROP_XDISTANCE, 15);
|
|
ObjectSetInteger(0, objName, OBJPROP_YDISTANCE, 15);
|
|
ObjectSetInteger(0, objName, OBJPROP_CORNER, CORNER_LEFT_UPPER);
|
|
ObjectSetInteger(0, objName, OBJPROP_COLOR, clrBlack);
|
|
ObjectSetString(0, objName, OBJPROP_FONT, "Arial");
|
|
ObjectSetInteger(0, objName, OBJPROP_FONTSIZE, 8);
|
|
}
|
|
ObjectSetString(0, objName, OBJPROP_TEXT, infoText);
|
|
|
|
// Redraw chart
|
|
ChartRedraw();
|
|
}
|
|
//+------------------------------------------------------------------+
|