Skip to content

Commit

Permalink
Add EMACrossLongOnly TSLA.NYSE trades example
Browse files Browse the repository at this point in the history
cjdsellers committed Apr 7, 2024
1 parent c7b018a commit a531435
Showing 2 changed files with 364 additions and 25 deletions.
Original file line number Diff line number Diff line change
@@ -23,9 +23,8 @@
from nautilus_trader.backtest.engine import BacktestEngine
from nautilus_trader.backtest.engine import BacktestEngineConfig
from nautilus_trader.config import LoggingConfig
from nautilus_trader.examples.algorithms.twap import TWAPExecAlgorithm
from nautilus_trader.examples.strategies.ema_cross_twap import EMACrossTWAP
from nautilus_trader.examples.strategies.ema_cross_twap import EMACrossTWAPConfig
from nautilus_trader.examples.strategies.ema_cross_long_only import EMACrossLongOnly
from nautilus_trader.examples.strategies.ema_cross_long_only import EMACrossLongOnlyConfig
from nautilus_trader.model.currencies import USD
from nautilus_trader.model.data import BarType
from nautilus_trader.model.enums import AccountType
@@ -51,46 +50,48 @@
engine = BacktestEngine(config=config)

# Add a trading venue (multiple venues possible)
NASDAQ = Venue("XNAS")
NYSE = Venue("NYSE")
engine.add_venue(
venue=NASDAQ,
venue=NYSE,
oms_type=OmsType.NETTING,
account_type=AccountType.CASH,
base_currency=USD,
starting_balances=[Money(10_000_000.0, USD)],
starting_balances=[Money(1_000_000.0, USD)],
)

# Add instruments
TSLA_NASDAQ = TestInstrumentProvider.equity(symbol="TSLA")
engine.add_instrument(TSLA_NASDAQ)
TSLA_NYSE = TestInstrumentProvider.equity(symbol="TSLA", venue="NYSE")
engine.add_instrument(TSLA_NYSE)

# Add data
loader = DatabentoDataLoader()
trades = loader.from_dbn_file(
path=TEST_DATA_DIR / "databento" / "temp" / "tsla-xnas-20240107-20240206.trades.dbn.zst",
instrument_id=TSLA_NASDAQ.id,
)
engine.add_data(trades)

filenames = [
"tsla-dbeq-basic-trades-2024-01.dbn.zst",
"tsla-dbeq-basic-trades-2024-02.dbn.zst",
"tsla-dbeq-basic-trades-2024-03.dbn.zst",
]

for filename in filenames:
trades = loader.from_dbn_file(
path=TEST_DATA_DIR / "databento" / "temp" / filename,
instrument_id=TSLA_NYSE.id,
)
engine.add_data(trades)

# Configure your strategy
config = EMACrossTWAPConfig(
instrument_id=TSLA_NASDAQ.id,
bar_type=BarType.from_str("TSLA.XNAS-5-MINUTE-LAST-INTERNAL"),
trade_size=Decimal(100),
config = EMACrossLongOnlyConfig(
instrument_id=TSLA_NYSE.id,
bar_type=BarType.from_str("TSLA.NYSE-1-MINUTE-LAST-INTERNAL"),
trade_size=Decimal(500),
fast_ema_period=10,
slow_ema_period=20,
twap_horizon_secs=10.0,
twap_interval_secs=2.5,
)

# Instantiate and add your strategy
strategy = EMACrossTWAP(config=config)
strategy = EMACrossLongOnly(config=config)
engine.add_strategy(strategy=strategy)

# Instantiate and add your execution algorithm
exec_algorithm = TWAPExecAlgorithm()
engine.add_exec_algorithm(exec_algorithm)

time.sleep(0.1)
input("Press Enter to continue...")

@@ -106,7 +107,7 @@
"display.width",
300,
):
print(engine.trader.generate_account_report(NASDAQ))
print(engine.trader.generate_account_report(NYSE))
print(engine.trader.generate_order_fills_report())
print(engine.trader.generate_positions_report())

338 changes: 338 additions & 0 deletions nautilus_trader/examples/strategies/ema_cross_long_only.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,338 @@
# -------------------------------------------------------------------------------------------------
# Copyright (C) 2015-2024 Nautech Systems Pty Ltd. All rights reserved.
# https://nautechsystems.io
#
# Licensed under the GNU Lesser General Public License Version 3.0 (the "License");
# You may not use this file except in compliance with the License.
# You may obtain a copy of the License at https://www.gnu.org/licenses/lgpl-3.0.en.html
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# -------------------------------------------------------------------------------------------------

from decimal import Decimal

import pandas as pd

from nautilus_trader.common.enums import LogColor
from nautilus_trader.config import PositiveInt
from nautilus_trader.config import StrategyConfig
from nautilus_trader.core.correctness import PyCondition
from nautilus_trader.core.data import Data
from nautilus_trader.core.message import Event
from nautilus_trader.indicators.average.ema import ExponentialMovingAverage
from nautilus_trader.model.book import OrderBook
from nautilus_trader.model.data import Bar
from nautilus_trader.model.data import BarType
from nautilus_trader.model.data import OrderBookDeltas
from nautilus_trader.model.data import QuoteTick
from nautilus_trader.model.data import TradeTick
from nautilus_trader.model.enums import OrderSide
from nautilus_trader.model.enums import TimeInForce
from nautilus_trader.model.identifiers import InstrumentId
from nautilus_trader.model.instruments import Instrument
from nautilus_trader.model.orders import MarketOrder
from nautilus_trader.trading.strategy import Strategy


# *** THIS IS A TEST STRATEGY WITH NO ALPHA ADVANTAGE WHATSOEVER. ***
# *** IT IS NOT INTENDED TO BE USED TO TRADE LIVE WITH REAL MONEY. ***


class EMACrossLongOnlyConfig(StrategyConfig, frozen=True):
"""
Configuration for ``EMACrossLongOnly`` instances.
Parameters
----------
instrument_id : InstrumentId
The instrument ID for the strategy.
bar_type : BarType
The bar type for the strategy.
trade_size : str
The position size per trade (interpreted as Decimal).
fast_ema_period : int, default 10
The fast EMA period.
slow_ema_period : int, default 20
The slow EMA period.
close_positions_on_stop : bool, default True
If all open positions should be closed on strategy stop.
order_id_tag : str
The unique order ID tag for the strategy. Must be unique
amongst all running strategies for a particular trader ID.
oms_type : OmsType
The order management system type for the strategy. This will determine
how the `ExecutionEngine` handles position IDs (see docs).
"""

instrument_id: InstrumentId
bar_type: BarType
trade_size: Decimal
fast_ema_period: PositiveInt = 10
slow_ema_period: PositiveInt = 20
close_positions_on_stop: bool = True


class EMACrossLongOnly(Strategy):
"""
A simple moving average cross LONG ONLY example strategy.
This strategy is suitable for trading equities on a CASH account.
When the fast EMA crosses the slow EMA then enter either a LONG position
at the market for BUY, or flatten any existing position for SELL.
Parameters
----------
config : EMACrossConfig
The configuration for the instance.
Raises
------
ValueError
If `config.fast_ema_period` is not less than `config.slow_ema_period`.
"""

def __init__(self, config: EMACrossLongOnlyConfig) -> None:
PyCondition.true(
config.fast_ema_period < config.slow_ema_period,
"{config.fast_ema_period=} must be less than {config.slow_ema_period=}",
)
super().__init__(config)

# Configuration
self.instrument_id = config.instrument_id
self.bar_type = config.bar_type
self.trade_size = config.trade_size

# Create the indicators for the strategy
self.fast_ema = ExponentialMovingAverage(config.fast_ema_period)
self.slow_ema = ExponentialMovingAverage(config.slow_ema_period)

self.close_positions_on_stop = config.close_positions_on_stop
self.instrument: Instrument = None

def on_start(self) -> None:
"""
Actions to be performed on strategy start.
"""
self.instrument = self.cache.instrument(self.instrument_id)
if self.instrument is None:
self.log.error(f"Could not find instrument for {self.instrument_id}")
self.stop()
return

# Register the indicators for updating
self.register_indicator_for_bars(self.bar_type, self.fast_ema)
self.register_indicator_for_bars(self.bar_type, self.slow_ema)

# Get historical data
self.request_bars(self.bar_type, start=self._clock.utc_now() - pd.Timedelta(days=1))
# self.request_quote_ticks(self.instrument_id)
# self.request_trade_ticks(self.instrument_id)

# Subscribe to live data
self.subscribe_bars(self.bar_type)
# self.subscribe_quote_ticks(self.instrument_id)
self.subscribe_trade_ticks(self.instrument_id)
# self.subscribe_ticker(self.instrument_id) # For debugging
# self.subscribe_order_book_deltas(self.instrument_id, depth=20) # For debugging
# self.subscribe_order_book_snapshots(self.instrument_id, depth=20) # For debugging

def on_instrument(self, instrument: Instrument) -> None:
"""
Actions to be performed when the strategy is running and receives an instrument.
Parameters
----------
instrument : Instrument
The instrument received.
"""
# For debugging (must add a subscription)
# self.log.info(repr(instrument), LogColor.CYAN)

def on_order_book_deltas(self, deltas: OrderBookDeltas) -> None:
"""
Actions to be performed when the strategy is running and receives order book
deltas.
Parameters
----------
deltas : OrderBookDeltas
The order book deltas received.
"""
# For debugging (must add a subscription)
# self.log.info(repr(deltas), LogColor.CYAN)

def on_order_book(self, order_book: OrderBook) -> None:
"""
Actions to be performed when the strategy is running and receives an order book.
Parameters
----------
order_book : OrderBook
The order book received.
"""
# For debugging (must add a subscription)
# self.log.info(repr(order_book), LogColor.CYAN)

def on_quote_tick(self, tick: QuoteTick) -> None:
"""
Actions to be performed when the strategy is running and receives a quote tick.
Parameters
----------
tick : QuoteTick
The tick received.
"""
# For debugging (must add a subscription)
# self.log.info(repr(tick), LogColor.CYAN)

def on_trade_tick(self, tick: TradeTick) -> None:
"""
Actions to be performed when the strategy is running and receives a trade tick.
Parameters
----------
tick : TradeTick
The tick received.
"""
# For debugging (must add a subscription)
# self.log.info(repr(tick), LogColor.CYAN)

def on_bar(self, bar: Bar) -> None:
"""
Actions to be performed when the strategy is running and receives a bar.
Parameters
----------
bar : Bar
The bar received.
"""
self.log.info(repr(bar), LogColor.CYAN)

# Check if indicators ready
if not self.indicators_initialized():
self.log.info(
f"Waiting for indicators to warm up [{self.cache.bar_count(self.bar_type)}]",
color=LogColor.BLUE,
)
return # Wait for indicators to warm up...

if bar.is_single_price():
# Implies no market information for this bar
return

# BUY LOGIC
if self.fast_ema.value >= self.slow_ema.value:
if self.portfolio.is_flat(self.instrument_id):
self.buy()
# SELL LOGIC
elif self.fast_ema.value < self.slow_ema.value:
if self.portfolio.is_net_long(self.instrument_id):
self.close_all_positions(self.instrument_id)

def buy(self) -> None:
"""
Users simple buy method (example).
"""
order: MarketOrder = self.order_factory.market(
instrument_id=self.instrument_id,
order_side=OrderSide.BUY,
quantity=self.instrument.make_qty(self.trade_size),
time_in_force=TimeInForce.IOC,
)

self.submit_order(order)

def on_data(self, data: Data) -> None:
"""
Actions to be performed when the strategy is running and receives data.
Parameters
----------
data : Data
The data received.
"""

def on_event(self, event: Event) -> None:
"""
Actions to be performed when the strategy is running and receives an event.
Parameters
----------
event : Event
The event received.
"""

def on_stop(self) -> None:
"""
Actions to be performed when the strategy is stopped.
"""
self.cancel_all_orders(self.instrument_id)
if self.close_positions_on_stop:
self.close_all_positions(self.instrument_id)

# Unsubscribe from data
self.unsubscribe_bars(self.bar_type)
# self.unsubscribe_quote_ticks(self.instrument_id)
self.unsubscribe_trade_ticks(self.instrument_id)
# self.unsubscribe_ticker(self.instrument_id)
# self.unsubscribe_order_book_deltas(self.instrument_id)
# self.unsubscribe_order_book_snapshots(self.instrument_id)

def on_reset(self) -> None:
"""
Actions to be performed when the strategy is reset.
"""
# Reset indicators here
self.fast_ema.reset()
self.slow_ema.reset()

def on_save(self) -> dict[str, bytes]:
"""
Actions to be performed when the strategy is saved.
Create and return a state dictionary of values to be saved.
Returns
-------
dict[str, bytes]
The strategy state dictionary.
"""
return {}

def on_load(self, state: dict[str, bytes]) -> None:
"""
Actions to be performed when the strategy is loaded.
Saved state values will be contained in the give state dictionary.
Parameters
----------
state : dict[str, bytes]
The strategy state dictionary.
"""

def on_dispose(self) -> None:
"""
Actions to be performed when the strategy is disposed.
Cleanup any resources used by the strategy here.
"""

0 comments on commit a531435

Please sign in to comment.