Pyeventbt
Event-driven backtesting and live trading framework built with Python for the MetaTrader 5 platform.
Install / Use
npx skills add marticastany/pyeventbtInstalls into whichever agent you are using.
README
pyeventbt
PyEventBT is an institutional-grade event-driven backtesting and live trading framework built with Python for the MetaTrader 5 platform.
It provides a complete mock of the MT5 API for an easy transition between backtesting and live trading, allowing traders to easily develop multi-rule, multi-timeframe and multi-instrument strategies.
Whether you're building simple moving average crossovers or complex multi-rule and multi-timeframe strategies, PyEventBT provides the tools you need to develop, test, and deploy with confidence.
Its modular architecture allows you to design your own signal sources, position sizing logic and risk management overlay as independent and interchangeable blocks.
Why PyEventBT?
If you're looking to connect Python and MetaTrader 5 for algorithmic trading, PyEventBT gives you a complete event-driven backtesting and live trading framework without writing a single line of MQL5.
Its core design principle is one codebase, two modes: the framework provides a full mock of the MT5 API, so the same strategy code you use for backtesting runs unchanged in live trading. This eliminates the common problem of backtest-to-live divergence and lets you focus on strategy logic rather than infrastructure.
- Native MT5 integration — Connect Python to MetaTrader 5 for live order execution, position management, and real-time data
- Realistic backtesting — Event-driven processing ensures your strategy only sees data available at that moment, preventing look-ahead bias by design
- Modular engines — Plug in your own signal generation, position sizing, and risk management logic as independent, interchangeable components
- Multi-everything — Handle multiple timeframes, instruments, and trading rules in a single strategy
- Python ecosystem — Built on Polars, NumPy, and Pandas — use any Python library in your strategies
Key Concepts
PyEventBT processes market data through an event pipeline. You only write the signal logic — the framework handles everything else:
BarEvent → Your Strategy → SignalEvent → Sizing Engine → Risk Engine → OrderEvent → Execution Engine → FillEvent → Portfolio
| Component | What it does | | :--- | :--- | | Signal Engine | Your strategy logic — receives market data, returns trading signals | | Sizing Engine | Calculates position size (min lot, fixed, or risk-percentage based) | | Risk Engine | Validates orders against your risk rules before execution | | Execution Engine | Routes orders to the simulated broker (backtest) or MT5 (live) | | Portfolio | Tracks positions, balance, equity, and P&L | | Data Provider | Serves historical bars (CSV) or real-time data (MT5) |
Installation
pip install pyeventbt
Quick Start
This is a summarized guide. For the full documentation, please visit <a href="https://pyeventbt.com?utm_source=github&utm_medium=readme" target="_blank">pyeventbt.com</a>.
1. Define your Strategy
Create a strategy by decorating a function with @strategy.custom_signal_engine.
from pyeventbt import (
Strategy,
BarEvent,
SignalEvent,
Modules,
StrategyTimeframes,
PassthroughRiskConfig,
MinSizingConfig
)
from pyeventbt.events.events import OrderType, SignalType
from pyeventbt.strategy.core.account_currencies import AccountCurrencies
from pyeventbt.indicators import SMA
from datetime import datetime
from decimal import Decimal
import logging
logger = logging.getLogger("pyeventbt")
# Strategy Configuration
strategy_id = "123456"
strategy = Strategy(logging_level=logging.INFO)
# Timeframes
signal_timeframe = StrategyTimeframes.ONE_DAY
strategy_timeframes = [signal_timeframe]
# Trading Configuration
symbols_to_trade = ['EURUSD']
starting_capital = 100000
# Strategy Parameters
fast_ma_period = 10
slow_ma_period = 30
@strategy.custom_signal_engine(strategy_id=strategy_id, strategy_timeframes=strategy_timeframes)
def ma_crossover_strategy(event: BarEvent, modules: Modules):
"""
Moving Average Dominance Strategy:
- Stay long while fast MA is above slow MA
- Stay short while fast MA is below slow MA
- Flat (or hold current) when both averages equal
- Always maintain at most one open position
"""
if event.timeframe != signal_timeframe:
return
symbol = event.symbol
signal_events = []
# Get bars for MA calculation
bars_needed = slow_ma_period + 10
bars = modules.DATA_PROVIDER.get_latest_bars(symbol, signal_timeframe, bars_needed)
if bars is None or bars.height < bars_needed:
return
# Calculate moving averages
close_prices = bars.select('close').to_numpy().flatten()
fast_ma_values = SMA.compute(close_prices, fast_ma_period)
slow_ma_values = SMA.compute(close_prices, slow_ma_period)
current_fast_ma = fast_ma_values[-1]
current_slow_ma = slow_ma_values[-1]
# Determine desired position state
if current_fast_ma > current_slow_ma:
desired_position = "LONG"
elif current_fast_ma < current_slow_ma:
desired_position = "SHORT"
else:
return
# Check current positions (at current bar time - no lookahead)
open_positions = modules.PORTFOLIO.get_number_of_strategy_open_positions_by_symbol(symbol)
signal_type = None
# Signal generation
if open_positions['LONG'] == 0 and desired_position == "LONG":
if open_positions['SHORT'] > 0:
modules.EXECUTION_ENGINE.close_strategy_short_positions_by_symbol(symbol)
signal_type = SignalType.BUY
if open_positions['SHORT'] == 0 and desired_position == "SHORT":
if open_positions['LONG'] > 0:
modules.EXECUTION_ENGINE.close_strategy_long_positions_by_symbol(symbol)
signal_type = SignalType.SELL
if signal_type == None:
return
# Time for signal generation (for NEXT bar)
if modules.TRADING_CONTEXT == "BACKTEST":
time_generated = event.datetime + signal_timeframe.to_timedelta()
else:
time_generated = datetime.now()
last_tick = modules.DATA_PROVIDER.get_latest_tick(symbol)
# Generate signals based on desired position
signal_events.append(SignalEvent(
symbol=symbol,
time_generated=time_generated,
strategy_id=strategy_id,
signal_type=signal_type,
order_type=OrderType.MARKET,
order_price=last_tick['ask'] if signal_type == SignalType.BUY else last_tick['bid'],
sl=Decimal(str(0.0)),
tp=Decimal(str(0.0)),
))
return signal_events
2. Configure and Run Backtest
# Configure Strategy
strategy.configure_predefined_sizing_engine(MinSizingConfig())
strategy.configure_predefined_risk_engine(PassthroughRiskConfig())
# Backtest Configuration
from_date = datetime(year=2020, month=1, day=1)
to_date = datetime(year=2023, month=12, day=1)
# csv_dir = './data' # Change it with your own path to the CSV data
csv_dir = None # If you don't have CSV data, you can set this to None
# Launch Backtest
backtest = strategy.backtest(
strategy_id=strategy_id,
initial_capital=starting_capital,
symbols_to_trade=symbols_to_trade,
csv_dir=csv_dir,
backtest_name=strategy_id,
start_date=from_date,
end_date=to_date,
export_backtest_parquet=False,
account_currency=AccountCurrencies.USD
)
backtest.plot()
Example Strategy
Here is a complete example of a Bollinger Bands breakout strategy:
from pyeventbt import (
Strategy,
BarEvent,
SignalEvent,
Modules,
StrategyTimeframes,
PassthroughRiskConfig,
MinSizingConfig
)
from pyeventbt.events.events import OrderType, SignalType
from pyeventbt.indicators.indicators import BollingerBands
from pyeventbt.strategy.core.account_currencies import AccountCurrencies
from datetime import datetime, time
from decimal import Decimal
import logging
import numpy as np
logger = logging.getLogger("pyeventbt")
# Strategy Configuration
strategy_id = "1234"
strategy = Strategy(logging_level=logging.INFO)
# Timeframes
signal_timeframe = StrategyTimeframes.ONE_HOUR
daily_timeframe = StrategyTimeframes.ONE_DAY
strategy_timeframes = [signal_timeframe, daily_timeframe]
# Trading Configuration
symbols_to_trade = ['EURUSD']
starting_capital = 100000
# Strategy Parameters
bb_period = 20
bb_std_dev = 2.5
close_hour = 21
close_minute = 0
order_placement_hour = 8
order_placement_minute = 0
# Daily tracking
orders_placed_today: dict[str, bool] = {symbol: False for symbol in symbols_to_trade}
current_trading_date: dict[str, datetime|None] = {symbol: None for symbol in symbols_to_trade}
@strategy.custom_signal_engine(strategy_id=strategy_id, strategy_timeframes=strategy_timeframes)
def bbands_breakout(event: BarEvent, modules: Modules):
"""
Bollinger Bands Breakout Strategy:
- Breakout levels: Upper and Lower Bollinger Bands
- Exit: Close all at 21:00
"""
symbol = event.symbol
signal_events = []
# Get current time and date
current_time = event.datetime.time()
current_date = event.datetime.date()
# Reset daily tracking if new day
if current_trading_date[symbol] != current_date:
current_trading_date[symbol] = current_date
orders_placed_today[symbol] = False
# Get positions and orders
open_positions = modules.PORTFOLIO.get_number_of_strategy_open_positions_by_symbol(symbol)
pending_orders = modules.PORTFOLIO.get
Related Skills
node-connect
385.6kDiagnose OpenClaw Android, iOS, or macOS node pairing, QR/setup code, route, auth, and connection failures.
blender-python-addon
40.5kBlender Python add-on rules for operators, panels, properties, registration, testing, and API-safe scripting
flutter-development-guidelines-cursorrules-prompt-file
40.5kCursor rules for Flutter development with MVVM architecture, Riverpod state management, Material widgets, and Dart style guidelines.
commit-push-pr
140.7kCommit, push, and open a PR
