Execution·Paper Trading Engines·Beginner

Simple Paper Trading Engine

Build a fully-functional paper trading engine that simulates order execution against historical replay or streaming live market data, tracking virtual positions with realistic PnL accounting, trading costs, and margin requirements for strategy validation before risking real capital.

order-executionpaper-tradingtrading-strategies

Paper Trading Engine

Overview

This notebook presents a self-contained paper trading engine designed for the simulation of order execution logic. The engine operates without real market interaction, providing a controlled environment for strategy evaluation.

Design Principles

  • API Parity: The engine's API mirrors live execution classes, facilitating seamless transition from simulation to production.
  • State Management: Comprehensive tracking of simulated P&L, account balance, and historical position data is maintained in-memory.
  • Independence: The engine is implemented with minimal external dependencies, ensuring a lightweight and self-sufficient operational framework.
  • Flexible Price Input: Supports various price sources, including historical datasets and live data feeds.

Applications

  • Strategy Validation: Pre-deployment testing and validation of trading algorithms.
  • Risk Parameter Optimization: Calibration and tuning of risk management parameters, such as Take-Profit (TP) and Stop-Loss (SL) levels.
  • Educational Contexts: Utilized as a tool for teaching and demonstrating algorithmic trading concepts.

1. Module Imports

This section imports all necessary Python libraries for the paper trading engine. Each module serves a specific function within the simulation framework.

[3]
import time
import random
import datetime
import math
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from dataclasses import dataclass, field
from typing import Optional

# Confirm successful import of all required modules.
print("Required modules imported successfully.")
Required modules imported successfully.

2. Configuration Parameters

This section defines the core parameters for the paper trading engine and the simulation. These parameters control strategy behavior, financial mechanics, and simulation settings.

[4]
# ── Strategy-Specific Configuration ───────────────────────────────────────
# Parameters dictating the trading strategy's characteristics.
config_strategy = {
    "name": "btc_paper_1h",          # Unique identifier for the strategy.
    "symbol": "BTC",                  # Trading instrument symbol (e.g., 'BTC', 'ETH').
    "time_horizon": "1h",             # Timeframe of the strategy (e.g., '1h', '4h', '1d').
    "live_tp_percent": 2.0,           # Take-profit threshold as a percentage of entry price.
    "live_sl_percent": 1.0,           # Stop-loss threshold as a percentage of entry price.
}

# ── Engine-Specific Configuration ─────────────────────────────────────────
# Financial and operational parameters for the simulation engine.
INITIAL_BALANCE    = 10_000.0   # Starting capital in USDT for the simulation.
ALLOCATION_PERCENT = 50         # Percentage of the total balance allocated per trade (e.g., 50 means 50% of capital).
MAKER_FEE          = 0.0002     # Maker transaction fee rate (e.g., 0.0002 for 0.02%).
TAKER_FEE          = 0.0004     # Taker transaction fee rate (e.g., 0.0004 for 0.04%).

# Output initial balance for verification.
print(f"Paper Trading Engine Initialized | Balance: ${INITIAL_BALANCE:,.2f} USDT")
Paper Trading Engine Initialized | Balance: $10,000.00 USDT

3. Trade Record Structure

This section defines the structure for each completed trading operation within the simulation engine. Instead of a dedicated class, trade details are encapsulated in a dictionary format. Each dictionary instance serves as an immutable record, providing a comprehensive audit trail for performance analysis and post-mortem evaluation.

Trade Record Fields:

  • entry_time: Datetime of position open.
  • exit_time: Datetime of position close.
  • symbol: Asset traded.
  • direction: 'long' | 'short'.
  • entry_price: Fill price at open.
  • exit_price: Fill price at close.
  • quantity: Base asset quantity.
  • pnl_usd: Realized P&L in USD.
  • pnl_pct: Realized P&L as percentage of entry notional.
  • reason: Close reason ('take_profit', 'stop_loss', 'direction_change', 'end_of_simulation').

4. Functional Trading Engine Core

This section redefines the core logic of the paper trading engine using a functional programming paradigm, replacing the previous class-based implementation. The engine's state is explicitly passed between functions, promoting modularity and testability. Each function performs a specific operation on the engine's state or related data, adhering to the principle of statelessness where applicable.

Core Components:

  • State Initialization: Sets up the initial financial and positional parameters.
  • Price Sourcing: Retrieves or simulates current market prices.
  • Order Simulation: Handles trade execution, including slippage and fee calculations.
  • Position Management: Functions for opening, closing, and monitoring trading positions.
  • Performance Analytics: Calculates and summarizes key trading metrics.
  • Simulation Orchestration: Manages the sequential execution of trading logic over time.

4.1. initialize_engine_state Function

This function initializes the state dictionary for the paper trading engine. It sets up the initial balance, allocation parameters, fee structures, and internal tracking variables for positions and trade history. This state dictionary will be passed as an argument to subsequent functions to maintain and update the simulation's status.

[5]
def initialize_engine_state(symbol: str, initial_balance: float, allocation_pct: float,
                            tp_percent: float, sl_percent: float,
                            taker_fee: float, maker_fee: float) -> dict:
    """
    Initializes and returns the state dictionary for the paper trading engine.

    Parameters:
    - symbol (str): Base asset symbol (e.g., 'BTC').
    - initial_balance (float): Starting paper balance in USDT.
    - allocation_pct (float): Percentage of balance to allocate per trade.
    - tp_percent (float): Take-profit trigger (%).
    - sl_percent (float): Stop-loss trigger (%).
    - taker_fee (float): Taker transaction fee rate.
    - maker_fee (float): Maker transaction fee rate.

    Returns:
    - dict: A dictionary representing the initial state of the trading engine.
    """
    engine_state = {
        "symbol": symbol.upper(),
        "balance": initial_balance,
        "initial_balance": initial_balance,
        "allocation": allocation_pct / 100,
        "tp_percent": tp_percent,
        "sl_percent": sl_percent,
        "taker_fee": taker_fee,
        "maker_fee": maker_fee,
        "direction": 0,  # +1 long | -1 short | 0 flat
        "entry_price": 0.0,
        "quantity": 0.0,
        "entry_time": None,
        "trades": [],
        "balance_curve": [initial_balance],
        "time_curve": [datetime.datetime.now(datetime.UTC)], # Use timezone-aware datetime
        "_last_price": 50_000.0 # For random walk default price source
    }
    print(f"Paper Trading Engine Initialized | Balance: ${engine_state['balance']:,.2f} USDT")
    return engine_state

4.2. get_current_price Function

This function retrieves the current market price for the asset. It supports an external price_source callable or defaults to a simulated random walk for demonstration purposes. The function updates the _last_price in the engine_state if using the default random walk.

[6]
def get_current_price(engine_state: dict, price_source=None) -> float:
    """
    Retrieves the current market price.

    Parameters:
    - engine_state (dict): The current state of the trading engine.
    - price_source (Callable | None): A callable function that returns the current price.
                                       If None, a random walk simulation is used.

    Returns:
    - float: The current market price.
    """
    if price_source is not None:
        return price_source()

    # Default: random walk around last price (for standalone demo)
    last_price = engine_state.get("_last_price", 50_000.0)
    change = last_price * random.gauss(0, 0.002)
    current_price = max(100, last_price + change)
    engine_state["_last_price"] = current_price # Update last price in state
    return round(current_price, 2)

4.3. simulate_fill Function

This function simulates the execution of an order, accounting for transaction fees and potential slippage. Market orders incur a taker fee and simulated slippage, while limit orders incur a maker fee with no slippage. The fill price and fee cost are returned.

[7]
def simulate_fill(engine_state: dict, side: str, quantity: float,
                  price: float, order_type: str = "market") -> tuple[float, float]:
    """
    Simulates an order fill, applying realistic slippage and fees.

    Parameters:
    - engine_state (dict): The current state of the trading engine.
    - side (str): Order side ('buy' or 'sell').
    - quantity (float): Quantity of the asset to trade.
    - price (float): The requested execution price.
    - order_type (str): Type of order ('market' or 'limit').

    Returns:
    - tuple[float, float]: A tuple containing the fill price after slippage and the calculated fee cost.
    """
    fee_rate = engine_state["taker_fee"] if order_type == "market" else engine_state["maker_fee"]

    # Slippage model: normally distributed, mean = 1 bps, std = 0.3 bps
    if order_type == "market":
        slippage_bps = abs(random.gauss(1.0, 0.3))
        if side == "buy":
            fill_price = price * (1 + slippage_bps / 10_000)
        else:
            fill_price = price * (1 - slippage_bps / 10_000)
    else:
        fill_price = price

    fee_cost = quantity * fill_price * fee_rate
    return round(fill_price, 2), round(fee_cost, 4)

4.4. open_position Function

This function executes the opening of a simulated trading position based on a given prediction. It calculates the quantity to be traded based on allocation percentage, simulates the order fill (including fees), and updates the engine's state with the new position details. This function only proceeds if there is no current open position or if the new prediction differs from the current direction.

[8]
def open_position(engine_state: dict, prediction: int, current_price: Optional[float] = None) -> None:
    """
    Opens a simulated trading position in the direction of the prediction.

    Parameters:
    - engine_state (dict): The current state of the trading engine.
    - prediction (int): Trading signal: +1 for long, -1 for short. 0 implies no action.
    - current_price (float | None): Optional. The current market price. If None, `get_current_price` is used.
    """
    # If no prediction or direction matches current position, no action.
    if prediction == 0 or engine_state["direction"] == prediction:
        return

    price = current_price if current_price is not None else get_current_price(engine_state)

    # Calculate raw quantity based on allocated balance and price.
    # Using initial_balance for allocation to prevent exponential runaway growth
    raw_qty  = (engine_state["initial_balance"] * engine_state["allocation"]) / price
    quantity = round(raw_qty, 6)

    # Simulate the order fill to get fill price and fees.
    fill_price, fee = simulate_fill(
        engine_state,
        "buy" if prediction == 1 else "sell",
        quantity,
        price
    )

    # Update engine state after opening position.
    engine_state["balance"]      -= fee       # Deduct opening fee from balance.
    engine_state["direction"]     = prediction
    engine_state["entry_price"]   = fill_price
    engine_state["quantity"]      = quantity
    engine_state["entry_time"]    = datetime.datetime.now(datetime.UTC) # Use timezone-aware datetime

    print(f"[OPEN] {'LONG' if prediction==1 else 'SHORT'} | "
          f"Qty: {quantity:.6f} {engine_state['symbol']} | Price: {fill_price:.2f} | "
          f"Fee: ${fee:.4f}")

4.5. close_open_position Function

This function is responsible for closing an active trading position. It simulates the fill for the closing order, calculates the realized Profit and Loss (PnL), updates the engine_state (including balance and trade history), and then resets the position-specific variables within the state. A trade record (dictionary) is generated and appended to the trades list in the engine_state.

[9]
def close_open_position(engine_state: dict, current_price: Optional[float] = None,
                            reason: str = "direction_change") -> Optional[dict]:
    """
    Closes the current simulated position and records the trade.

    Parameters:
    - engine_state (dict): The current state of the trading engine.
    - current_price (float | None): Optional. The current market price. If None, `get_current_price` is used.
    - reason (str): The reason for closing the position (e.g., 'take_profit', 'stop_loss', 'direction_change').

    Returns:
    - dict | None: A dictionary representing the closed trade record, or None if no position was open.
    """
    if engine_state["direction"] == 0:
        return None

    price = current_price if current_price is not None else get_current_price(engine_state)
    close_side = "sell" if engine_state["direction"] == 1 else "buy"
    fill_price, fee = simulate_fill(engine_state, close_side, engine_state["quantity"], price)

    # ── Compute PnL ────────────────────────────────────────────────────
    if engine_state["direction"] == 1:   # Long
        pnl_usd = (fill_price - engine_state["entry_price"]) * engine_state["quantity"]
        pnl_pct = ((fill_price - engine_state["entry_price"]) / engine_state["entry_price"]) * 100
    else:                     # Short
        pnl_usd = (engine_state["entry_price"] - fill_price) * engine_state["quantity"]
        pnl_pct = ((engine_state["entry_price"] - fill_price) / engine_state["entry_price"]) * 100

    pnl_usd -= fee   # Deduct closing fee

    # ── Update balance ─────────────────────────────────────────────────
    # The balance update should only add the realized PnL (which includes all fees).
    # The notional value of the trade was never subtracted from the balance, only the initial fee.
    engine_state["balance"] += pnl_usd
    engine_state["balance_curve"].append(round(engine_state["balance"], 4))
    engine_state["time_curve"].append(datetime.datetime.now(datetime.UTC))

    trade = {
        "entry_time":  engine_state["entry_time"],
        "exit_time":   datetime.datetime.now(datetime.UTC),
        "symbol":      engine_state["symbol"],
        "direction":   "long" if engine_state["direction"] == 1 else "short",
        "entry_price": engine_state["entry_price"],
        "exit_price":  fill_price,
        "quantity":    engine_state["quantity"],
        "pnl_usd":     round(pnl_usd, 4),
        "pnl_pct":     round(pnl_pct, 4),
        "reason":      reason,
    }
    engine_state["trades"].append(trade)

    print(f"[CLOSE] {reason.upper()} | Price: {fill_price:.2f} | "
          f"PnL: ${pnl_usd:.2f} ({pnl_pct:.2f}%) | Balance: ${engine_state['balance']:.2f}")

    # Reset position state in the engine_state dictionary
    engine_state["direction"] = 0
    engine_state["entry_price"] = 0.0
    engine_state["quantity"] = 0.0
    engine_state["entry_time"] = None

    return trade

4.6. check_tp_sl Function

This function evaluates the current profit/loss (PnL) of an open position against predefined Take-Profit (TP) and Stop-Loss (SL) thresholds. If the PnL exceeds the TP or falls below the SL, the function triggers the close_open_position function, recording the trade and updating the engine_state. It returns True if the position was closed, False otherwise.

[10]
def check_tp_sl(engine_state: dict, current_price: Optional[float] = None) -> bool:
    """
    Evaluates Take-Profit (TP) and Stop-Loss (SL) conditions for the current open position.

    Parameters:
    - engine_state (dict): The current state of the trading engine.
    - current_price (float | None): Optional. The current market price. If None, `get_current_price` is used.

    Returns:
    - bool: True if the position was closed due to TP/SL, False otherwise.
    """
    if engine_state["direction"] == 0:
        return True # No open position, so no TP/SL to check

    price = current_price if current_price is not None else get_current_price(engine_state)

    if engine_state["direction"] == 1: # Long position
        pnl_pct = ((price - engine_state["entry_price"]) / engine_state["entry_price"]) * 100
    else: # Short position
        pnl_pct = ((engine_state["entry_price"] - price) / engine_state["entry_price"]) * 100

    print(f"[MONITOR] PnL: {pnl_pct:.2f}% | TP: +{engine_state['tp_percent']}% | SL: -{engine_state['sl_percent']}%")

    if pnl_pct >= engine_state["tp_percent"]:
        close_open_position(engine_state, price, "take_profit")
        return True
    if pnl_pct <= -engine_state["sl_percent"]:
        close_open_position(engine_state, price, "stop_loss")
        return True
    return False

4.7. _max_drawdown Function

This auxiliary function calculates the maximum drawdown percentage from the balance_curve stored in the engine_state. Maximum drawdown represents the largest peak-to-trough decline in the balance, indicating the largest historical loss from a peak. This metric is crucial for assessing the risk associated with the trading strategy.

[11]
def _max_drawdown(engine_state: dict) -> float:
    """
    Computes the maximum drawdown percentage of the balance curve.

    Parameters:
    - engine_state (dict): The current state of the trading engine, containing the 'balance_curve'.

    Returns:
    - float: The maximum drawdown as a percentage.
    """
    curve = np.array(engine_state["balance_curve"])
    if len(curve) < 2:
        return 0.0
    peak = np.maximum.accumulate(curve)
    drawdown = (curve - peak) / peak * 100
    return abs(drawdown.min())

4.8. _sharpe_ratio Function

This auxiliary function calculates the annualized Sharpe ratio based on the PnL percentages of completed trades. The Sharpe ratio measures the excess return per unit of risk, with a higher value indicating better risk-adjusted performance. A risk-free rate can be specified, though it defaults to 0.0 for simplicity.

[12]
def _sharpe_ratio(engine_state: dict, risk_free_rate: float = 0.0) -> float:
    """
    Calculates the annualized Sharpe ratio from trade PnL percentages.

    Parameters:
    - engine_state (dict): The current state of the trading engine, containing 'trades'.
    - risk_free_rate (float): The risk-free return rate (default is 0.0).

    Returns:
    - float: The annualized Sharpe ratio.
    """
    pnls = [t["pnl_pct"] for t in engine_state["trades"]]
    if len(pnls) < 2:
        return 0.0
    excess_returns = np.array(pnls) - risk_free_rate
    if np.std(excess_returns) == 0:
        return 0.0
    # Assuming daily returns for annualization (sqrt(252 trading days))
    return (np.mean(excess_returns) / np.std(excess_returns)) * math.sqrt(252)

4.9. get_performance_summary Function

This function compiles key performance metrics from the engine_state's trade history and balance curve. It calculates metrics such as total return, win rate, average win/loss, profit factor, maximum drawdown, and Sharpe ratio. The results are returned as a Pandas DataFrame for clear presentation and analysis.

[13]
def get_performance_summary(engine_state: dict) -> pd.DataFrame:
    """
    Computes and returns a summary of key performance metrics from the trade history.

    Parameters:
    - engine_state (dict): The current state of the trading engine, containing 'trades' and 'balance_curve'.

    Returns:
    - pd.DataFrame: A DataFrame summarizing the performance metrics.
    """
    if not engine_state["trades"]:
        return pd.DataFrame([{"Message": "No trades recorded."}])

    pnls_usd = [t["pnl_usd"] for t in engine_state["trades"]]
    pnls_pct = [t["pnl_pct"] for t in engine_state["trades"]]
    wins     = [p for p in pnls_pct if p > 0]
    losses   = [p for p in pnls_pct if p <= 0]

    total_return = ((engine_state["balance"] - engine_state["initial_balance"]) / engine_state["initial_balance"]) * 100

    # Calculate profit factor
    total_wins = sum(p for p in pnls_usd if p > 0)
    total_losses = abs(sum(p for p in pnls_usd if p < 0))
    profit_factor = round(total_wins / total_losses, 2) if total_losses != 0 else float("inf")

    summary = {
        "Initial Balance ($)":   engine_state["initial_balance"],
        "Final Balance ($)":     round(engine_state["balance"], 2),
        "Total Return (%)":      round(total_return, 2),
        "Total Trades":          len(engine_state["trades"]),
        "Win Rate (%)":          round(len(wins) / max(len(engine_state["trades"]), 1) * 100, 1),
        "Avg Win (%)":           round(np.mean(wins),   2) if wins   else 0,
        "Avg Loss (%)":          round(np.mean(losses), 2) if losses else 0,
        "Profit Factor":         profit_factor,
        "Max Drawdown (%)":      round(_max_drawdown(engine_state), 2),
        "Sharpe Ratio":          round(_sharpe_ratio(engine_state), 2),
        "Take Profit Hits":      sum(1 for t in engine_state["trades"] if t["reason"] == "take_profit"),
        "Stop Loss Hits":        sum(1 for t in engine_state["trades"] if t["reason"] == "stop_loss"),
    }
    return pd.DataFrame([summary]).T.rename(columns={0: "Value"})

4.10. run_simulation Function

This function serves as the primary orchestrator for the paper trading simulation. It iterates through a sequence of time-series predictions and corresponding market prices, applying the trading logic at each step. It handles position opening and closing based on signals and monitors for Take-Profit and Stop-Loss conditions. Finally, it generates a comprehensive performance summary upon completion.

[14]
def run_simulation(engine_state: dict, predictions: list[tuple], prices: list[float]) -> pd.DataFrame:
    """
    Runs a batch simulation using a list of time-series predictions and corresponding prices.

    Parameters:
    - engine_state (dict): The initial state of the trading engine.
    - predictions (list[tuple]): A list of (datetime, signal) tuples, where signal is +1 (long), -1 (short), or 0 (no action).
    - prices (list[float]): A list of corresponding market prices for each prediction timestamp.

    Returns:
    - pd.DataFrame: A DataFrame summarizing the performance of the simulation.
    """
    print(f"[SIM START] {len(predictions)} predictions.")
    prev_signal = 0

    for (dt, signal), price in zip(predictions, prices):
        # Ensure consistent datetime in engine state time curve
        engine_state["time_curve"][-1] = dt # Update the last datetime to current prediction time

        # Check for TP/SL if a position is currently open
        if engine_state["direction"] != 0:
            check_tp_sl(engine_state, price)

        # If the signal changes, re-evaluate position
        if signal != prev_signal:
            # Close existing position if any
            if engine_state["direction"] != 0:
                close_open_position(engine_state, price, "direction_change")

            # Open new position if signal is not neutral
            if signal != 0:
                open_position(engine_state, signal, price)

        prev_signal = signal

    # Close any remaining position at the end of the simulation
    if engine_state["direction"] != 0:
        close_open_position(engine_state, prices[-1], "end_of_simulation")

    print("[SIM END]")
    return get_performance_summary(engine_state)

5. Demo Simulation

This section demonstrates the functionality of the paper trading engine with synthetic data. It initializes the engine with predefined parameters, generates a series of simulated price movements and trading signals, and then executes the run_simulation function to process these events. Finally, it displays the performance summary and visualizes the results.

[15]
# ── Generate synthetic predictions and prices ─────────────────────────────
np.random.seed(42)
n_periods = 200

# Simulated price path (GBM-like)
returns = np.random.normal(0.0002, 0.015, n_periods)
prices  = [50_000.0]
for r in returns:
    prices.append(prices[-1] * (1 + r))
prices = prices[1:]

# Simulated prediction signals: +1, -1, 0 with realistic frequency
signals = np.random.choice([1, -1, 0], size=n_periods, p=[0.4, 0.4, 0.2])
base_dt = datetime.datetime(2025, 1, 1, 0, 0, 0, tzinfo=datetime.UTC)
predictions = [(base_dt + datetime.timedelta(hours=i), int(s)) for i, s in enumerate(signals)]

# ── Initialize paper trading engine state ────────────────────────────────
engine_state = initialize_engine_state(
    symbol          = config_strategy["symbol"],
    initial_balance = INITIAL_BALANCE,
    allocation_pct  = ALLOCATION_PERCENT,
    tp_percent      = config_strategy["live_tp_percent"],
    sl_percent      = config_strategy["live_sl_percent"],
    taker_fee       = TAKER_FEE,
    maker_fee       = MAKER_FEE
)

# ── Run paper trading simulation ───────────────────────────────────────────
summary = run_simulation(engine_state, predictions, prices)
print("\n=== Performance Summary ===")
print(summary.to_string())
Paper Trading Engine Initialized | Balance: $10,000.00 USDT
[SIM START] 200 predictions.
[OPEN] SHORT | Qty: 0.099427 BTC | Price: 50284.09 | Fee: $1.9998
[MONITOR] PnL: -1.00% | TP: +2.0% | SL: -1.0%
[MONITOR] PnL: -3.33% | TP: +2.0% | SL: -1.0%
[CLOSE] STOP_LOSS | Price: 51962.21 | PnL: $-168.92 (-3.34%) | Balance: $9829.08
[OPEN] LONG | Qty: 0.096553 BTC | Price: 51790.23 | Fee: $2.0002
[MONITOR] PnL: -0.34% | TP: +2.0% | SL: -1.0%
[MONITOR] PnL: 2.04% | TP: +2.0% | SL: -1.0%
[CLOSE] TAKE_PROFIT | Price: 52839.86 | PnL: $99.30 (2.03%) | Balance: $9926.39
[OPEN] SHORT | Qty: 0.094019 BTC | Price: 53172.49 | Fee: $1.9997
[MONITOR] PnL: 0.66% | TP: +2.0% | SL: -1.0%
[MONITOR] PnL: 0.28% | TP: +2.0% | SL: -1.0%
[MONITOR] PnL: 3.12% | TP: +2.0% | SL: -1.0%
[CLOSE] TAKE_PROFIT | Price: 51516.99 | PnL: $153.71 (3.11%) | Balance: $10078.10
[OPEN] LONG | Qty: 0.101481 BTC | Price: 49275.56 | Fee: $2.0002
[MONITOR] PnL: -1.35% | TP: +2.0% | SL: -1.0%
[CLOSE] STOP_LOSS | Price: 48606.14 | PnL: $-69.91 (-1.36%) | Balance: $10006.19
[OPEN] SHORT | Qty: 0.102786 BTC | Price: 48637.04 | Fee: $1.9997
[MONITOR] PnL: 0.30% | TP: +2.0% | SL: -1.0%
[CLOSE] DIRECTION_CHANGE | Price: 48494.59 | PnL: $12.65 (0.29%) | Balance: $10016.84
[OPEN] LONG | Qty: 0.103115 BTC | Price: 48494.06 | Fee: $2.0002
[MONITOR] PnL: 0.11% | TP: +2.0% | SL: -1.0%
[CLOSE] DIRECTION_CHANGE | Price: 48545.63 | PnL: $3.32 (0.11%) | Balance: $10018.16
[OPEN] SHORT | Qty: 0.102990 BTC | Price: 48543.15 | Fee: $1.9998
[MONITOR] PnL: 2.11% | TP: +2.0% | SL: -1.0%
[CLOSE] TAKE_PROFIT | Price: 47524.90 | PnL: $102.91 (2.10%) | Balance: $10119.07
[OPEN] LONG | Qty: 0.106063 BTC | Price: 47147.22 | Fee: $2.0002
[MONITOR] PnL: 0.18% | TP: +2.0% | SL: -1.0%
[CLOSE] DIRECTION_CHANGE | Price: 47224.10 | PnL: $6.15 (0.16%) | Balance: $10123.22
[OPEN] SHORT | Qty: 0.105865 BTC | Price: 47225.96 | Fee: $1.9998
[MONITOR] PnL: 1.70% | TP: +2.0% | SL: -1.0%
[CLOSE] DIRECTION_CHANGE | Price: 46428.48 | PnL: $82.46 (1.69%) | Balance: $10203.68
[OPEN] LONG | Qty: 0.107703 BTC | Price: 46429.15 | Fee: $2.0002
[MONITOR] PnL: 0.57% | TP: +2.0% | SL: -1.0%
[MONITOR] PnL: -0.31% | TP: +2.0% | SL: -1.0%
[CLOSE] DIRECTION_CHANGE | Price: 46280.13 | PnL: $-18.04 (-0.32%) | Balance: $10183.63
[OPEN] LONG | Qty: 0.108483 BTC | Price: 46095.71 | Fee: $2.0002
[MONITOR] PnL: -0.89% | TP: +2.0% | SL: -1.0%
[MONITOR] PnL: 1.88% | TP: +2.0% | SL: -1.0%
[CLOSE] DIRECTION_CHANGE | Price: 46954.34 | PnL: $91.11 (1.86%) | Balance: $10272.74
[OPEN] SHORT | Qty: 0.106469 BTC | Price: 46955.38 | Fee: $1.9997
[MONITOR] PnL: -0.01% | TP: +2.0% | SL: -1.0%
[MONITOR] PnL: 1.55% | TP: +2.0% | SL: -1.0%
[CLOSE] DIRECTION_CHANGE | Price: 46229.38 | PnL: $75.33 (1.55%) | Balance: $10346.07
[OPEN] LONG | Qty: 0.108164 BTC | Price: 46232.14 | Fee: $2.0003
[MONITOR] PnL: 1.24% | TP: +2.0% | SL: -1.0%
[CLOSE] DIRECTION_CHANGE | Price: 46800.99 | PnL: $59.50 (1.23%) | Balance: $10403.57
[OPEN] SHORT | Qty: 0.106825 BTC | Price: 46800.31 | Fee: $1.9998
[MONITOR] PnL: 1.80% | TP: +2.0% | SL: -1.0%
[CLOSE] DIRECTION_CHANGE | Price: 45964.32 | PnL: $87.34 (1.79%) | Balance: $10488.91
[OPEN] LONG | Qty: 0.108795 BTC | Price: 45961.75 | Fee: $2.0002
[MONITOR] PnL: 0.32% | TP: +2.0% | SL: -1.0%
[MONITOR] PnL: -2.60% | TP: +2.0% | SL: -1.0%
[CLOSE] STOP_LOSS | Price: 44761.65 | PnL: $-132.51 (-2.61%) | Balance: $10354.40
[OPEN] SHORT | Qty: 0.111695 BTC | Price: 44761.27 | Fee: $1.9998
[MONITOR] PnL: 1.96% | TP: +2.0% | SL: -1.0%
[MONITOR] PnL: 1.66% | TP: +2.0% | SL: -1.0%
[MONITOR] PnL: 0.55% | TP: +2.0% | SL: -1.0%
[MONITOR] PnL: 0.27% | TP: +2.0% | SL: -1.0%
[CLOSE] DIRECTION_CHANGE | Price: 44641.87 | PnL: $11.34 (0.27%) | Balance: $10363.74
[OPEN] SHORT | Qty: 0.112179 BTC | Price: 44566.14 | Fee: $1.9998
[MONITOR] PnL: 0.42% | TP: +2.0% | SL: -1.0%
[CLOSE] DIRECTION_CHANGE | Price: 44380.51 | PnL: $18.83 (0.42%) | Balance: $10380.58
[OPEN] LONG | Qty: 0.112666 BTC | Price: 44382.69 | Fee: $2.0002
[MONITOR] PnL: -2.21% | TP: +2.0% | SL: -1.0%
[CLOSE] STOP_LOSS | Price: 43398.89 | PnL: $-112.80 (-2.22%) | Balance: $10265.78
[OPEN] SHORT | Qty: 0.115197 BTC | Price: 43401.47 | Fee: $1.9999
[MONITOR] PnL: 1.05% | TP: +2.0% | SL: -1.0%
[CLOSE] DIRECTION_CHANGE | Price: 42949.54 | PnL: $50.08 (1.04%) | Balance: $10313.86
[OPEN] LONG | Qty: 0.116431 BTC | Price: 42948.25 | Fee: $2.0002
[MONITOR] PnL: -0.68% | TP: +2.0% | SL: -1.0%
[CLOSE] DIRECTION_CHANGE | Price: 42652.12 | PnL: $-36.47 (-0.69%) | Balance: $10275.40
[OPEN] SHORT | Qty: 0.117218 BTC | Price: 42651.14 | Fee: $1.9998
[MONITOR] PnL: -1.62% | TP: +2.0% | SL: -1.0%
[CLOSE] STOP_LOSS | Price: 43343.88 | PnL: $-83.23 (-1.62%) | Balance: $10190.16
[OPEN] LONG | Qty: 0.115365 BTC | Price: 43343.07 | Fee: $2.0001
[MONITOR] PnL: 0.53% | TP: +2.0% | SL: -1.0%
[MONITOR] PnL: -2.11% | TP: +2.0% | SL: -1.0%
[CLOSE] STOP_LOSS | Price: 42423.92 | PnL: $-108.00 (-2.12%) | Balance: $10080.17
[OPEN] SHORT | Qty: 0.117908 BTC | Price: 42402.60 | Fee: $1.9998
[MONITOR] PnL: 0.99% | TP: +2.0% | SL: -1.0%
[MONITOR] PnL: 0.06% | TP: +2.0% | SL: -1.0%
[CLOSE] DIRECTION_CHANGE | Price: 42382.55 | PnL: $0.37 (0.05%) | Balance: $10078.53
[OPEN] LONG | Qty: 0.117987 BTC | Price: 42382.88 | Fee: $2.0003
[MONITOR] PnL: 1.55% | TP: +2.0% | SL: -1.0%
[MONITOR] PnL: 2.99% | TP: +2.0% | SL: -1.0%
[CLOSE] TAKE_PROFIT | Price: 43646.60 | PnL: $147.04 (2.98%) | Balance: $10223.57
[OPEN] SHORT | Qty: 0.115981 BTC | Price: 43107.92 | Fee: $1.9999
[MONITOR] PnL: 0.44% | TP: +2.0% | SL: -1.0%
[MONITOR] PnL: -0.08% | TP: +2.0% | SL: -1.0%
[MONITOR] PnL: -1.56% | TP: +2.0% | SL: -1.0%
[CLOSE] STOP_LOSS | Price: 43784.68 | PnL: $-80.52 (-1.57%) | Balance: $10141.05
[OPEN] LONG | Qty: 0.114205 BTC | Price: 43785.40 | Fee: $2.0002
[MONITOR] PnL: -0.71% | TP: +2.0% | SL: -1.0%
[CLOSE] DIRECTION_CHANGE | Price: 43469.42 | PnL: $-38.07 (-0.72%) | Balance: $10100.98
[OPEN] SHORT | Qty: 0.115307 BTC | Price: 43356.68 | Fee: $1.9997
[MONITOR] PnL: 1.63% | TP: +2.0% | SL: -1.0%
[MONITOR] PnL: 3.37% | TP: +2.0% | SL: -1.0%
[CLOSE] TAKE_PROFIT | Price: 41897.20 | PnL: $166.36 (3.37%) | Balance: $10265.34
[OPEN] LONG | Qty: 0.115513 BTC | Price: 43286.84 | Fee: $2.0001
[MONITOR] PnL: -0.09% | TP: +2.0% | SL: -1.0%
[MONITOR] PnL: 1.43% | TP: +2.0% | SL: -1.0%
[CLOSE] DIRECTION_CHANGE | Price: 43902.18 | PnL: $69.05 (1.42%) | Balance: $10332.39
[OPEN] SHORT | Qty: 0.113878 BTC | Price: 43903.67 | Fee: $1.9999
[MONITOR] PnL: -0.57% | TP: +2.0% | SL: -1.0%
[CLOSE] DIRECTION_CHANGE | Price: 44159.60 | PnL: $-31.16 (-0.58%) | Balance: $10299.23
[OPEN] LONG | Qty: 0.113241 BTC | Price: 44156.12 | Fee: $2.0001
[MONITOR] PnL: -0.95% | TP: +2.0% | SL: -1.0%
[MONITOR] PnL: -0.40% | TP: +2.0% | SL: -1.0%
[MONITOR] PnL: 1.92% | TP: +2.0% | SL: -1.0%
[MONITOR] PnL: 1.89% | TP: +2.0% | SL: -1.0%
[CLOSE] DIRECTION_CHANGE | Price: 44985.86 | PnL: $91.92 (1.88%) | Balance: $10389.15
[OPEN] SHORT | Qty: 0.108568 BTC | Price: 46046.85 | Fee: $1.9997
[MONITOR] PnL: 3.89% | TP: +2.0% | SL: -1.0%
[CLOSE] TAKE_PROFIT | Price: 44258.41 | PnL: $192.25 (3.88%) | Balance: $10579.40
[OPEN] LONG | Qty: 0.111587 BTC | Price: 44810.52 | Fee: $2.0001
[MONITOR] PnL: 0.15% | TP: +2.0% | SL: -1.0%
[CLOSE] DIRECTION_CHANGE | Price: 44872.74 | PnL: $4.94 (0.14%) | Balance: $10582.34
[OPEN] SHORT | Qty: 0.111419 BTC | Price: 44872.47 | Fee: $1.9999
[MONITOR] PnL: 0.42% | TP: +2.0% | SL: -1.0%
[MONITOR] PnL: 0.26% | TP: +2.0% | SL: -1.0%
[CLOSE] DIRECTION_CHANGE | Price: 44758.71 | PnL: $10.68 (0.25%) | Balance: $10591.02
[OPEN] LONG | Qty: 0.111723 BTC | Price: 44757.43 | Fee: $2.0002
[MONITOR] PnL: -2.97% | TP: +2.0% | SL: -1.0%
[CLOSE] STOP_LOSS | Price: 43425.16 | PnL: $-150.79 (-2.98%) | Balance: $10438.23
[OPEN] SHORT | Qty: 0.115132 BTC | Price: 43425.68 | Fee: $1.9999
[MONITOR] PnL: 0.30% | TP: +2.0% | SL: -1.0%
[CLOSE] DIRECTION_CHANGE | Price: 43297.34 | PnL: $12.78 (0.30%) | Balance: $10449.02
[OPEN] LONG | Qty: 0.115489 BTC | Price: 43297.89 | Fee: $2.0002
[MONITOR] PnL: 0.55% | TP: +2.0% | SL: -1.0%
[CLOSE] DIRECTION_CHANGE | Price: 43528.01 | PnL: $24.57 (0.53%) | Balance: $10471.58
[OPEN] SHORT | Qty: 0.114851 BTC | Price: 43530.44 | Fee: $1.9998
[MONITOR] PnL: -2.25% | TP: +2.0% | SL: -1.0%
[CLOSE] STOP_LOSS | Price: 44510.86 | PnL: $-114.65 (-2.25%) | Balance: $10354.93
[OPEN] LONG | Qty: 0.113196 BTC | Price: 44177.15 | Fee: $2.0003
[MONITOR] PnL: -1.21% | TP: +2.0% | SL: -1.0%
[CLOSE] STOP_LOSS | Price: 43638.71 | PnL: $-62.93 (-1.22%) | Balance: $10290.01
[OPEN] SHORT | Qty: 0.115408 BTC | Price: 43321.19 | Fee: $1.9998
[MONITOR] PnL: -1.40% | TP: +2.0% | SL: -1.0%
[CLOSE] STOP_LOSS | Price: 43931.76 | PnL: $-72.49 (-1.41%) | Balance: $10215.52
[OPEN] SHORT | Qty: 0.114125 BTC | Price: 43808.50 | Fee: $1.9999
[MONITOR] PnL: -0.80% | TP: +2.0% | SL: -1.0%
[CLOSE] DIRECTION_CHANGE | Price: 44162.35 | PnL: $-42.40 (-0.81%) | Balance: $10171.12
[OPEN] LONG | Qty: 0.113231 BTC | Price: 44159.98 | Fee: $2.0001
[MONITOR] PnL: 0.16% | TP: +2.0% | SL: -1.0%
[MONITOR] PnL: 1.64% | TP: +2.0% | SL: -1.0%
[CLOSE] DIRECTION_CHANGE | Price: 44877.45 | PnL: $79.21 (1.62%) | Balance: $10248.32
[OPEN] SHORT | Qty: 0.111402 BTC | Price: 44879.38 | Fee: $1.9999
[MONITOR] PnL: 1.03% | TP: +2.0% | SL: -1.0%
[CLOSE] DIRECTION_CHANGE | Price: 44423.90 | PnL: $48.76 (1.01%) | Balance: $10295.09
[OPEN] LONG | Qty: 0.112565 BTC | Price: 44422.34 | Fee: $2.0002
[MONITOR] PnL: -0.48% | TP: +2.0% | SL: -1.0%
[CLOSE] DIRECTION_CHANGE | Price: 44204.32 | PnL: $-26.53 (-0.49%) | Balance: $10266.55
[OPEN] SHORT | Qty: 0.113099 BTC | Price: 44205.59 | Fee: $1.9998
[MONITOR] PnL: 0.56% | TP: +2.0% | SL: -1.0%
[CLOSE] DIRECTION_CHANGE | Price: 43961.93 | PnL: $25.57 (0.55%) | Balance: $10290.12
[OPEN] SHORT | Qty: 0.116274 BTC | Price: 42997.10 | Fee: $1.9998
[MONITOR] PnL: -0.48% | TP: +2.0% | SL: -1.0%
[CLOSE] DIRECTION_CHANGE | Price: 43204.75 | PnL: $-26.15 (-0.48%) | Balance: $10261.97
[OPEN] LONG | Qty: 0.115737 BTC | Price: 43207.14 | Fee: $2.0003
[MONITOR] PnL: 0.40% | TP: +2.0% | SL: -1.0%
[CLOSE] DIRECTION_CHANGE | Price: 43377.36 | PnL: $17.69 (0.39%) | Balance: $10277.66
[OPEN] SHORT | Qty: 0.115263 BTC | Price: 43373.30 | Fee: $1.9997
[MONITOR] PnL: -0.04% | TP: +2.0% | SL: -1.0%
[MONITOR] PnL: 0.29% | TP: +2.0% | SL: -1.0%
[MONITOR] PnL: 2.39% | TP: +2.0% | SL: -1.0%
[CLOSE] TAKE_PROFIT | Price: 42341.10 | PnL: $117.02 (2.38%) | Balance: $10392.69
[OPEN] LONG | Qty: 0.118824 BTC | Price: 42083.09 | Fee: $2.0002
[MONITOR] PnL: -0.50% | TP: +2.0% | SL: -1.0%
[CLOSE] DIRECTION_CHANGE | Price: 41866.17 | PnL: $-27.77 (-0.52%) | Balance: $10362.92
[OPEN] LONG | Qty: 0.121113 BTC | Price: 41288.80 | Fee: $2.0002
[MONITOR] PnL: 0.61% | TP: +2.0% | SL: -1.0%
[MONITOR] PnL: 3.48% | TP: +2.0% | SL: -1.0%
[CLOSE] TAKE_PROFIT | Price: 42720.46 | PnL: $171.32 (3.47%) | Balance: $10532.24
[OPEN] SHORT | Qty: 0.116330 BTC | Price: 42977.06 | Fee: $1.9998
[MONITOR] PnL: 2.85% | TP: +2.0% | SL: -1.0%
[CLOSE] TAKE_PROFIT | Price: 41755.29 | PnL: $140.19 (2.84%) | Balance: $10670.43
[OPEN] LONG | Qty: 0.119753 BTC | Price: 41758.86 | Fee: $2.0003
[MONITOR] PnL: -0.03% | TP: +2.0% | SL: -1.0%
[MONITOR] PnL: 0.08% | TP: +2.0% | SL: -1.0%
[CLOSE] DIRECTION_CHANGE | Price: 41787.28 | PnL: $1.40 (0.07%) | Balance: $10669.83
[OPEN] LONG | Qty: 0.115359 BTC | Price: 43348.30 | Fee: $2.0002
[MONITOR] PnL: -0.28% | TP: +2.0% | SL: -1.0%
[CLOSE] DIRECTION_CHANGE | Price: 43221.33 | PnL: $-16.64 (-0.29%) | Balance: $10651.19
[OPEN] LONG | Qty: 0.115126 BTC | Price: 43433.10 | Fee: $2.0001
[MONITOR] PnL: -0.04% | TP: +2.0% | SL: -1.0%
[MONITOR] PnL: -1.77% | TP: +2.0% | SL: -1.0%
[CLOSE] STOP_LOSS | Price: 42661.65 | PnL: $-90.78 (-1.78%) | Balance: $10558.41
[OPEN] SHORT | Qty: 0.117194 BTC | Price: 42658.62 | Fee: $1.9997
[MONITOR] PnL: -1.75% | TP: +2.0% | SL: -1.0%
[CLOSE] STOP_LOSS | Price: 43411.31 | PnL: $-90.25 (-1.76%) | Balance: $10466.16
[OPEN] SHORT | Qty: 0.112531 BTC | Price: 44427.36 | Fee: $1.9998
[MONITOR] PnL: 1.33% | TP: +2.0% | SL: -1.0%
[CLOSE] DIRECTION_CHANGE | Price: 43838.01 | PnL: $64.35 (1.33%) | Balance: $10528.51
[OPEN] LONG | Qty: 0.111692 BTC | Price: 44771.90 | Fee: $2.0003
[MONITOR] PnL: -2.10% | TP: +2.0% | SL: -1.0%
[CLOSE] STOP_LOSS | Price: 43829.90 | PnL: $-107.17 (-2.10%) | Balance: $10419.34
[OPEN] SHORT | Qty: 0.113050 BTC | Price: 44223.97 | Fee: $1.9998
[MONITOR] PnL: -3.32% | TP: +2.0% | SL: -1.0%
[CLOSE] STOP_LOSS | Price: 45696.88 | PnL: $-168.58 (-3.33%) | Balance: $10248.76
[OPEN] SHORT | Qty: 0.111989 BTC | Price: 44644.40 | Fee: $1.9999
[MONITOR] PnL: -0.18% | TP: +2.0% | SL: -1.0%
[CLOSE] DIRECTION_CHANGE | Price: 44729.63 | PnL: $-11.55 (-0.19%) | Balance: $10235.21
[OPEN] LONG | Qty: 0.111800 BTC | Price: 44728.20 | Fee: $2.0002
[MONITOR] PnL: -0.75% | TP: +2.0% | SL: -1.0%
[CLOSE] DIRECTION_CHANGE | Price: 44389.20 | PnL: $-39.89 (-0.76%) | Balance: $10193.33
[OPEN] SHORT | Qty: 0.112628 BTC | Price: 44390.04 | Fee: $1.9998
[MONITOR] PnL: 2.30% | TP: +2.0% | SL: -1.0%
[CLOSE] TAKE_PROFIT | Price: 43373.79 | PnL: $112.50 (2.29%) | Balance: $10303.83
[OPEN] LONG | Qty: 0.115286 BTC | Price: 43374.85 | Fee: $2.0002
[MONITOR] PnL: 0.11% | TP: +2.0% | SL: -1.0%
[CLOSE] DIRECTION_CHANGE | Price: 43416.98 | PnL: $2.85 (0.10%) | Balance: $10304.69
[OPEN] SHORT | Qty: 0.116137 BTC | Price: 43046.96 | Fee: $1.9997
[MONITOR] PnL: 1.35% | TP: +2.0% | SL: -1.0%
[MONITOR] PnL: -0.97% | TP: +2.0% | SL: -1.0%
[MONITOR] PnL: 0.20% | TP: +2.0% | SL: -1.0%
[CLOSE] DIRECTION_CHANGE | Price: 42964.30 | PnL: $7.60 (0.19%) | Balance: $10310.29
[OPEN] LONG | Qty: 0.116384 BTC | Price: 42965.12 | Fee: $2.0002
[MONITOR] PnL: -0.47% | TP: +2.0% | SL: -1.0%
[CLOSE] DIRECTION_CHANGE | Price: 42756.05 | PnL: $-26.32 (-0.49%) | Balance: $10281.97
[OPEN] SHORT | Qty: 0.115493 BTC | Price: 43285.52 | Fee: $1.9997
[MONITOR] PnL: 1.81% | TP: +2.0% | SL: -1.0%
[CLOSE] DIRECTION_CHANGE | Price: 42508.86 | PnL: $87.73 (1.79%) | Balance: $10367.70
[OPEN] LONG | Qty: 0.117218 BTC | Price: 42659.16 | Fee: $2.0002
[MONITOR] PnL: 1.97% | TP: +2.0% | SL: -1.0%
[CLOSE] DIRECTION_CHANGE | Price: 43495.43 | PnL: $95.99 (1.96%) | Balance: $10461.69
[OPEN] LONG | Qty: 0.117757 BTC | Price: 42463.90 | Fee: $2.0002
[MONITOR] PnL: 0.29% | TP: +2.0% | SL: -1.0%
[MONITOR] PnL: 0.70% | TP: +2.0% | SL: -1.0%
[CLOSE] DIRECTION_CHANGE | Price: 42756.52 | PnL: $32.44 (0.69%) | Balance: $10492.13
[OPEN] LONG | Qty: 0.115551 BTC | Price: 43274.35 | Fee: $2.0002
[MONITOR] PnL: -1.84% | TP: +2.0% | SL: -1.0%
[CLOSE] STOP_LOSS | Price: 42474.12 | PnL: $-94.43 (-1.85%) | Balance: $10395.70
[OPEN] SHORT | Qty: 0.118558 BTC | Price: 42168.39 | Fee: $1.9998
[MONITOR] PnL: -0.41% | TP: +2.0% | SL: -1.0%
[MONITOR] PnL: -0.95% | TP: +2.0% | SL: -1.0%
[MONITOR] PnL: 0.06% | TP: +2.0% | SL: -1.0%
[CLOSE] DIRECTION_CHANGE | Price: 42146.09 | PnL: $0.65 (0.05%) | Balance: $10394.35
[OPEN] LONG | Qty: 0.118643 BTC | Price: 42148.84 | Fee: $2.0003
[MONITOR] PnL: 0.36% | TP: +2.0% | SL: -1.0%
[MONITOR] PnL: 0.82% | TP: +2.0% | SL: -1.0%
[CLOSE] DIRECTION_CHANGE | Price: 42489.02 | PnL: $38.34 (0.81%) | Balance: $10430.69
[OPEN] SHORT | Qty: 0.117667 BTC | Price: 42489.18 | Fee: $1.9998
[MONITOR] PnL: 1.04% | TP: +2.0% | SL: -1.0%
[MONITOR] PnL: -1.75% | TP: +2.0% | SL: -1.0%
[CLOSE] STOP_LOSS | Price: 43236.58 | PnL: $-89.98 (-1.76%) | Balance: $10338.71
[OPEN] LONG | Qty: 0.116883 BTC | Price: 42781.14 | Fee: $2.0002
[MONITOR] PnL: 1.00% | TP: +2.0% | SL: -1.0%
[CLOSE] DIRECTION_CHANGE | Price: 43204.90 | PnL: $47.51 (0.99%) | Balance: $10384.22
[OPEN] SHORT | Qty: 0.115721 BTC | Price: 43203.00 | Fee: $1.9998
[MONITOR] PnL: 1.43% | TP: +2.0% | SL: -1.0%
[CLOSE] DIRECTION_CHANGE | Price: 42589.73 | PnL: $69.00 (1.42%) | Balance: $10451.22
[OPEN] LONG | Qty: 0.117414 BTC | Price: 42588.81 | Fee: $2.0002
[MONITOR] PnL: 1.19% | TP: +2.0% | SL: -1.0%
[CLOSE] DIRECTION_CHANGE | Price: 43090.24 | PnL: $56.85 (1.18%) | Balance: $10506.07
[OPEN] SHORT | Qty: 0.116021 BTC | Price: 43091.65 | Fee: $1.9998
[MONITOR] PnL: -1.77% | TP: +2.0% | SL: -1.0%
[CLOSE] STOP_LOSS | Price: 43857.37 | PnL: $-90.88 (-1.78%) | Balance: $10413.19
[OPEN] LONG | Qty: 0.113748 BTC | Price: 43962.15 | Fee: $2.0002
[MONITOR] PnL: 0.63% | TP: +2.0% | SL: -1.0%
[MONITOR] PnL: 1.89% | TP: +2.0% | SL: -1.0%
[MONITOR] PnL: 4.81% | TP: +2.0% | SL: -1.0%
[CLOSE] TAKE_PROFIT | Price: 46072.60 | PnL: $237.96 (4.80%) | Balance: $10649.16
[OPEN] SHORT | Qty: 0.108517 BTC | Price: 46071.50 | Fee: $1.9998
[MONITOR] PnL: 0.34% | TP: +2.0% | SL: -1.0%
[MONITOR] PnL: 1.45% | TP: +2.0% | SL: -1.0%
[CLOSE] DIRECTION_CHANGE | Price: 45410.34 | PnL: $69.78 (1.44%) | Balance: $10716.93
[OPEN] LONG | Qty: 0.110119 BTC | Price: 45409.11 | Fee: $2.0002
[MONITOR] PnL: -1.32% | TP: +2.0% | SL: -1.0%
[CLOSE] STOP_LOSS | Price: 44805.04 | PnL: $-68.49 (-1.33%) | Balance: $10646.44
[OPEN] SHORT | Qty: 0.112945 BTC | Price: 44266.66 | Fee: $1.9999
[MONITOR] PnL: 0.09% | TP: +2.0% | SL: -1.0%
[CLOSE] DIRECTION_CHANGE | Price: 44231.71 | PnL: $1.95 (0.08%) | Balance: $10646.39
[OPEN] LONG | Qty: 0.113054 BTC | Price: 44230.81 | Fee: $2.0002
[MONITOR] PnL: 0.52% | TP: +2.0% | SL: -1.0%
[CLOSE] DIRECTION_CHANGE | Price: 44457.89 | PnL: $23.66 (0.51%) | Balance: $10668.05
[OPEN] SHORT | Qty: 0.111968 BTC | Price: 44651.72 | Fee: $1.9998
[MONITOR] PnL: -1.27% | TP: +2.0% | SL: -1.0%
[CLOSE] STOP_LOSS | Price: 45223.00 | PnL: $-65.99 (-1.28%) | Balance: $10600.06
[OPEN] LONG | Qty: 0.110574 BTC | Price: 45224.54 | Fee: $2.0003
[MONITOR] PnL: 0.03% | TP: +2.0% | SL: -1.0%
[MONITOR] PnL: 2.23% | TP: +2.0% | SL: -1.0%
[CLOSE] TAKE_PROFIT | Price: 46227.33 | PnL: $108.84 (2.22%) | Balance: $10706.90
[OPEN] LONG | Qty: 0.108560 BTC | Price: 46063.70 | Fee: $2.0003
[MONITOR] PnL: 4.09% | TP: +2.0% | SL: -1.0%
[CLOSE] TAKE_PROFIT | Price: 47941.60 | PnL: $201.78 (4.08%) | Balance: $10906.68
[OPEN] SHORT | Qty: 0.105520 BTC | Price: 47377.49 | Fee: $1.9997
[MONITOR] PnL: 0.30% | TP: +2.0% | SL: -1.0%
[CLOSE] DIRECTION_CHANGE | Price: 47239.80 | PnL: $12.54 (0.29%) | Balance: $10917.22
[OPEN] LONG | Qty: 0.105854 BTC | Price: 47240.40 | Fee: $2.0002
[MONITOR] PnL: 1.08% | TP: +2.0% | SL: -1.0%
[MONITOR] PnL: 1.82% | TP: +2.0% | SL: -1.0%
[CLOSE] DIRECTION_CHANGE | Price: 48095.98 | PnL: $88.53 (1.81%) | Balance: $11003.75
[OPEN] SHORT | Qty: 0.103953 BTC | Price: 48092.40 | Fee: $1.9997
[MONITOR] PnL: 0.08% | TP: +2.0% | SL: -1.0%
[MONITOR] PnL: 1.33% | TP: +2.0% | SL: -1.0%
[MONITOR] PnL: 3.55% | TP: +2.0% | SL: -1.0%
[CLOSE] TAKE_PROFIT | Price: 46390.96 | PnL: $174.94 (3.54%) | Balance: $11176.69
[OPEN] LONG | Qty: 0.108495 BTC | Price: 46091.47 | Fee: $2.0003
[MONITOR] PnL: 1.29% | TP: +2.0% | SL: -1.0%
[CLOSE] DIRECTION_CHANGE | Price: 46680.71 | PnL: $61.90 (1.28%) | Balance: $11236.59
[OPEN] SHORT | Qty: 0.107098 BTC | Price: 46682.89 | Fee: $1.9999
[MONITOR] PnL: -0.35% | TP: +2.0% | SL: -1.0%
[CLOSE] DIRECTION_CHANGE | Price: 46848.94 | PnL: $-19.79 (-0.36%) | Balance: $11214.80
[OPEN] LONG | Qty: 0.106734 BTC | Price: 46850.28 | Fee: $2.0002
[MONITOR] PnL: -1.86% | TP: +2.0% | SL: -1.0%
[CLOSE] STOP_LOSS | Price: 45972.47 | PnL: $-95.65 (-1.87%) | Balance: $11117.14
[OPEN] SHORT | Qty: 0.108744 BTC | Price: 45974.16 | Fee: $1.9998
[MONITOR] PnL: -0.29% | TP: +2.0% | SL: -1.0%
[CLOSE] DIRECTION_CHANGE | Price: 46113.15 | PnL: $-17.12 (-0.30%) | Balance: $11098.02
[OPEN] LONG | Qty: 0.108441 BTC | Price: 46114.66 | Fee: $2.0003
[MONITOR] PnL: 0.58% | TP: +2.0% | SL: -1.0%
[MONITOR] PnL: -0.73% | TP: +2.0% | SL: -1.0%
[MONITOR] PnL: -0.48% | TP: +2.0% | SL: -1.0%
[CLOSE] DIRECTION_CHANGE | Price: 45884.64 | PnL: $-26.93 (-0.50%) | Balance: $11069.09
[OPEN] SHORT | Qty: 0.108950 BTC | Price: 45888.72 | Fee: $1.9998
[MONITOR] PnL: -0.12% | TP: +2.0% | SL: -1.0%
[MONITOR] PnL: 1.58% | TP: +2.0% | SL: -1.0%
[CLOSE] END_OF_SIMULATION | Price: 45169.47 | PnL: $76.39 (1.57%) | Balance: $11143.48
[SIM END]

=== Performance Summary ===
                        Value
Initial Balance ($)  10000.00
Final Balance ($)    11143.48
Total Return (%)        11.43
Total Trades            91.00
Win Rate (%)            60.40
Avg Win (%)              1.44
Avg Loss (%)            -1.36
Profit Factor            1.53
Max Drawdown (%)         4.47
Sharpe Ratio             3.02
Take Profit Hits        14.00
Stop Loss Hits          21.00

6. Performance Visualisation

This section visualizes the simulation results. It generates two plots:

  • Equity Curve: Displays the evolution of the trading account balance over the course of the simulation.
  • Per-Trade PnL Distribution: Illustrates the profit and loss percentage for each individual trade executed by the engine.

These visualizations provide a clear graphical representation of the strategy's performance and risk characteristics.

[16]
fig, axes = plt.subplots(1, 2, figsize=(14, 5))
fig.suptitle("Paper Trading Engine — Simulation Results", fontsize=13, fontweight="bold")

# ── Equity Curve ──────────────────────────────────────────────────────────
axes[0].plot(range(len(engine_state["balance_curve"])), engine_state["balance_curve"],
             color="#2ecc71", linewidth=2)
axes[0].axhline(INITIAL_BALANCE, linestyle="--", color="#bdc3c7", label="Initial Balance")
axes[0].set_xlabel("Trade Number")
axes[0].set_ylabel("Balance (USDT)")
axes[0].set_title("Equity Curve")
axes[0].legend()
axes[0].grid(True, alpha=0.3)

# ── Per-Trade PnL Distribution ────────────────────────────────────────────
if engine_state["trades"]:
    pnls = [t["pnl_pct"] for t in engine_state["trades"]]
    colors = ["#2ecc71" if p > 0 else "#e74c3c" for p in pnls]
    axes[1].bar(range(len(pnls)), pnls, color=colors, alpha=0.8, edgecolor="black", linewidth=0.3)
    axes[1].axhline(0, color="#333", linewidth=1)
    axes[1].set_xlabel("Trade #")
    axes[1].set_ylabel("PnL (%)")
    axes[1].set_title("Per-Trade PnL")
    axes[1].grid(True, alpha=0.3, axis="y")

plt.tight_layout()
plt.show()
cell output