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

Verified

This commit was signed with the committer’s verified signature.
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.