Files
mypyeventbt/example_bbands_breakout.py
T

200 lines
6.1 KiB
Python
Raw Normal View History

"""
PyEventBT
Documentation: https://pyeventbt.com
GitHub: https://github.com/marticastany/pyeventbt
Author: Marti Castany
Copyright (c) 2025 Marti Castany
Licensed under the Apache License, Version 2.0
"""
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_number_of_strategy_pending_orders_by_symbol(symbol)
# Close positions and cancel orders at close time
if current_time >= time(close_hour, close_minute):
if open_positions['TOTAL'] > 0:
logger.info(f"{event.datetime} - Closing all positions for {symbol}")
modules.EXECUTION_ENGINE.close_all_strategy_positions()
if pending_orders['TOTAL'] > 0:
logger.info(f"{event.datetime} - Cancelling all pending orders for {symbol}")
modules.EXECUTION_ENGINE.cancel_all_strategy_pending_orders()
return
# Place orders at order placement time
if (current_time >= time(order_placement_hour, order_placement_minute) and
not orders_placed_today[symbol] and
pending_orders['TOTAL'] == 0 and
event.timeframe == signal_timeframe):
# Get bars for Bollinger Bands calculation
bars_needed = bb_period + 10
indicator_bars = modules.DATA_PROVIDER.get_latest_bars(symbol, signal_timeframe, bars_needed)
if indicator_bars is None or indicator_bars.height < bars_needed:
return
# Calculate Bollinger Bands
close = indicator_bars.select('close').to_numpy().flatten()
upper, middle, lower = BollingerBands.compute(close, bb_period, bb_std_dev)
current_upper = upper[-1]
current_lower = lower[-1]
if np.isnan(current_upper) or np.isnan(current_lower):
return
upper_breakout = Decimal(str(current_upper))
lower_breakout = Decimal(str(current_lower))
# Time for signal generation
if modules.TRADING_CONTEXT == "BACKTEST":
time_generated = event.datetime + signal_timeframe.to_timedelta()
else:
time_generated = datetime.now()
# Place BUY STOP order
signal_events.append(SignalEvent(
symbol=symbol,
time_generated=time_generated,
strategy_id=strategy_id,
signal_type=SignalType.BUY,
order_type=OrderType.STOP,
order_price=upper_breakout,
sl=Decimal(str(0.0)),
tp=Decimal(str(0.0)),
))
# Place SELL STOP order
signal_events.append(SignalEvent(
symbol=symbol,
time_generated=time_generated,
strategy_id=strategy_id,
signal_type=SignalType.SELL,
order_type=OrderType.STOP,
order_price=lower_breakout,
sl=Decimal(str(0.0)),
tp=Decimal(str(0.0)),
))
orders_placed_today[symbol] = True
return signal_events
# 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 = '/Users/marticastany/Desktop/long_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_csv=True,
export_backtest_parquet=False,
account_currency=AccountCurrencies.USD
)
print("Backtest finished")
backtest.plot()
# Example: Running live with MT5
# mt5_config = Mt5PlatformConfig(
# path="C:\\Program Files\\MetaTrader 5\\terminal64.exe",
# login=1234,
# password="1234",
# server="Demo",
# timeout=60000,
# portable=False
# )
# strategy.run_live(
# mt5_configuration=mt5_config,
# strategy_id=strategy_id,
# initial_capital=100000,
# symbols_to_trade=symbols_to_trade,
# heartbeat=0.1
# )