NAV
  • Developer Guide
  • EPAM Systems all rights reserved, 2022

    Developer Guide

    Introduction

    In QuantOffice, a strategy is a special trading program designed to analyze market data and make trading decisions based on this analysis. QuantOffice allows you to back-test and run live your strategies from Jupyter Notebook. There you can also use Thea IDE to write and debug your Python strategies.

    This diagram illustrates all main components of the environment where strategies operate.

    1. Strategy Runtime Framework is the core QuantOffice infrastructure that executes the strategy logic, generates events, gives access to market data and trading venues.
    2. You can back-test your strategy on historical market data provided by TimeBase, test your strategy on live data using Simulator and then run your strategy live.
    3. Strategy receives events and generates trading signals for Simulator or trading venues using order executor.
    4. System generates reports and analytics based on the back-testing results.

    Strategy Lifecycle

    This is a strategy life cycle diagram.

    At the beginning of the strategy life cycle, it receives events to execute the strategy initialization logic. Initialized strategy can enter the warmup mode depending on the configurations of the strategy. In the warmup mode the strategy receives historical market data but does not send any orders. The main execution flow starts after the warmup where the strategy receives various events such as on_day_open, on_day_close, market data, and trade events. At the end on_exit event stops the execution.

    A strategy can run in backtesting and live modes. In the backtesting mode the strategy consumes historical market data and events. Only parameters of your machine can effect the strategy performance In this mode. In real-time mode, the strategy consumes real-time market data and events and communicates directly with the connected trading venues. In this mode, we may see different types of frictions determined by the market which can impact the strategy execution and performance.

    Strategy Structure

    In QuantOffice, a strategy is a special trading program designed to analyse market data and make trading decisions based on this analysis.

    The strategy consists of the following files:

    Each level (each class correspondingly) has its own set of members and events.

    You can use code fragments supplied in this guide when creating a strategy in Jupyter Notebook.

    A new folder with your strategy should include the following files:

    Portfolio Executor

    # In order for your strategy to work properly, add a list of namespaces to portfolio_executor.py file.
    import clr
    import os
    from quantoffice.quant_office_config import QuantOfficeConfig, LogLevels
    QuantOfficeConfig.set_root_log_level(LogLevels.Error)
    from System import TimeSpan, DateTime
    from QuantOffice.Commons.Data import BarUOM
    from QuantOffice.Execution import SimulationMode
    from Deltix.EMS.Simulator import MktOrderSimPrice
    from Deltix.EMS.API import *
    from Deltix.EMS.Coordinator import EMSParameters
    from quantoffice.order_executor.order_processor import OrderProcessor
    from Deltix.EMS.Simulator import OrderSimulatorParameters
    from quantoffice.strategy_runner_py import *
    from QuantOffice.Reporting.Model import ReportEngine
    from QuantOffice.StrategyRunner import ReportingWindowParams, ChartItemsParams
    from quantoffice.tick_db_helper import get_all_symbols
    from quantoffice.decorators import input_parameter, event, subscription,
    EventNames, wrapped, SubscriptionNames,
    BarKind, BarUOM, BarSize, custom_report, custom_subscription
    from quantoffice.decorators.subscription import TickSubscriptionParams
    from tqdm.auto import tqdm
    from quantoffice.executor_base import PortfolioExecutorBase
    

    PortfolioExecutor is a class that contains properties and methods to analyze market at portfolio level – it processes all instruments, for which the strategy is run. This class responds to events at the portfolio level.

    Create new PortfolioExecutor class in portfolio_executor file each new strategy and add the required namespace imports. A separate chart and a separate report instance will be created for each PortfolioExecutor instance. For each input parameter created for a strategy, a separate field is created in the PortfolioExecutor class.

    The table below lists some main PortfolioExecutor classes and objects you can use when creating a strategy.

    Class Name Description
    portfolio.slice Returns the list of all InstrumentExecutor objects of the strategy. It represents all the instruments the strategy is run for. The property provides access from the PortfolioExecutor class to each individual InstrumentExecutor object.
    portfolio.order_executor Provides access to order executor object.
    portfolio.report_engine Provides access to report engine object.
    portfolio.base_portfolio_executor Provides access to API's of QuantOffice which are not exposed through python.
    portfolio.subscribe() Adds event listener for messages of the Custom subscription for the specified set of symbols. Args:
    method (callable) - Custom subscription event listener.
    symbols (list(str)) - List of subscribed symbols. If symbols is None, then data on all symbols will be sent to the event handler. These will be all the symbols for which data of the message types specified for the subscription are contained in the data stream assigned to the subscription.
    portfolio.unsubscribe() Disconnects event listener for messages of the Custom subscription for the specified set of symbols.
    portfolio.get_custom_reporter() Return custom report object by name. Args:
    * custom_report_name (str) - Custom report name.
    portfolio.get_order_book_manager() C# OrderBook Manager getter.
    portfolio.get_current_time() Provides access to current experiment time.
    portfolio.log() Writes text to log window. Args:
    * log_message (str) - Text to be written.
    portfolio.get_output_stream() Return generated output stream object by name. Args:
    * output_stream_name (str) - Output stream name.
    portfolio.create_custom_message() Creates .net custom message object by type name. Args:
    * message_name (str) - Custom message type name.

    Instrument Executor

    # Namespaces
    import clr
    import os
    from math import floor
    from quantoffice.quant_office_config import QuantOfficeConfig
    QuantOfficeConfig.add_main_qo_libs()
    
    import System
    import Deltix
    import QuantOffice
    import FinAnalysis
    from System.Drawing import Color, Pen
    from System import TimeSpan
    from Deltix.EMS.API import *
    from Deltix.EMS.AlgoOrders import *
    from QuantOffice.Execution import LineStyle, EventSeverity
    from QuantOffice.Data import SymbolKey
    from FinAnalysis.TA import *
    from FinAnalysis.Predicates import *
    from FinAnalysis.BurstDetection import *
    from quantoffice.decorators import event, EventNames, custom_subscription, errorlog
    from quantoffice.executor_base import InstrumentExecutorBase
    from quantoffice.quant_office_orders_helper import *
    from quantoffice import dot_net_helper
    
    # Initialization
    class InstrumentExecutor(InstrumentExecutorBase):
        """This class contains instrument level event processing logic."""
        def __init__(instr, base_instrument_executor, portfolio_executor, *args, **kwargs):
            InstrumentExecutorBase.__init__(instr, base_instrument_executor, portfolio_executor, args=args, kwargs=kwargs)
    

    InstrumentExecutor is a class that contains properties and methods to analyze market data at instrument level – it processes a single symbol. This class responds to events at the instrument level.

    1. In order for your strategy to work properly, add a list of namespaces to instrument_executor.py file.
    2. Add to the instrument_executor.py file constructor which will initialize instance of the instrument executor.

    The table below lists some main InstrumentExecutor classes and objects you can use when creating a strategy.

    Class Name Description
    instr.base_instrument_executor Provides access to API's of QuantOffice which are not exposed through python.
    instr.portfolio_executor Returns an instance of the PortfolioExecutor class. It provides access to properties and methods of the portfolio level.
    instr.get_symbol() Symbol name.
    instr.get_symbol_key() Symbol key is a special class, designed for quick indexing.
    instr.get_bars() Provides access to data of Intraday subscription. It allows the user to get current bar, historical bar from the subscription stack, etc.
    instr.get_daily() Provides access to data of Daily subscription. It allows the user to get today’s and yesterday’s bar, historical bar for some day from the subscription stack etc.
    instr.get_quote() Provides access to data of Tick subscription. It allows the user to get current quote, historical quote for the given time from the subscription stack.
    instr.get_current_time() Current time of the experiment.
    instr.get_current_price() Returns current instrument price updated with data from all subscriptions included into the strategy.
    If the strategy uses Daily subscription, then at the DayOpen moment the CurrentPrice property contains the Open price of the instrument from the Daily subscription.
    If the strategy uses Intraday subscription, then each time new Intraday bar arrives, this property will be updated with the Close price of this Intraday bar.
    If for example the strategy also includes Tick subscription with Trades data, then each time new trade arrives the CurrentPrice property will be updated with the price of the trade.
    If the strategy is executed for currencies or options, then the CurrentPrice property will be updated with midpoint price (middle price between BestBid and BestAsk) from BBO data.
    instr.get_charting() Returns instance of instrument chart.
    instr.get_meta_info() Gets security meta info.
    instr.log() Outputs user information (text) to the strategy log. The output user information shares the log with QuantOffice runtime information on errors and warnings.
    instr.get_custom_reporter() Returns custom reporter, described in the Custom reports section. Args:
    custom_report_name (str) - Custom report name.
    instr.get_output_stream() Return generated output stream object by name. Args:
    output_stream_name (str) - Output stream name.
    instr.create_custom_message() Creates .net custom message object by type name. Args:
    message_name (str) - Custom message type name.

    Events

    A QuantOffice strategy is based on events model. Events provide the following to a strategy:

    Events are received either on the portfolio or on the instrument level.

    Some of the events can occur only once during a strategy run, while others can occur many times periodically or irregularly. For example, events like OnInit and OnExit occur once per strategy, OnDayOpen, OnDayClose events are invoked not more often than once per day, OnBarClose event is called on bar-size basis, while regularity of OnTrade, OnBestBid, OnBestAsk, OnBarOpen events is not defined.

    Some events are invoked on-time schedule (e.g: OnDayOpen, OnDayClose, OnBarClose). Other events depend on data in TimeBase, for example, OnBarOpen, OnTick.

    Portfolio Events

    # Use the below template to add an event handler to a strategy:
    @event(EventNames.<event name>)
    def <name>(<parameters>):
    <event handler code>
    
    # Add the **on_init** event to the **portfolio_executor.py** file that will print `Starting` message:
    @event(EventNames.OnPortfolioInit)
    def on_init(portfolio):
        print("Starting")   
    
    # OnAfterInit event handler 
    @event(EventNames.OnAfterInit)
    def on_after_init(portfolio):
        portfolio.log("portfolio initialized")      
    
    # OnPortfolioDayOpen event handler      
    @event(EventNames.OnPortfolioDayOpen)
    def on_day_open(portfolio, context):
        pass    
    
    # OnPortfolioBarClose event handler 
    @event(EventNames.OnPortfolioBarClose)
    def on_bar_close(portfolio, context):
        for instr in slice:
    
    # OnPortfolioDayClose event handler     
    @event(EventNames.OnPortfolioDayClose)
    def on_day_close(portfolio, context):
        pass
    
    # After that add the **on_exit** event to the **portfolio_executor.py** file that will print `Exit` message:    
    @event(EventNames.OnPortfolioExit)
    def on_exit(portfolio, exit_state):
        print("Exit")
    
    # OnInstrumentsActivated event handler      
    @event(EventNames.OnInstrumentsActivated)
    def on_instruments_activated(portfolio, instrumentKeys):
        for instr_key in instrumentKeys:
            portfolio.log(instr_key + " Activated")     
    
    # OnInstrumentsDeactivated event handler        
    @event(EventNames.OnInstrumentsDeactivated)
    def on_instruments_deactivated(portfolio, instrumentKeys):
        for instr_key in instrumentKeys:
            portfolio.log(instr_key + " Deactivated")       
    
    # OnWarmupEnd event handler 
    @event(EventNames.OnWarmupEnd)
    def on_warmup_end(portfolio):
        portfolio.log("End of warmup")  
    
    # OnPacketEndMessage event handler  
    @event(EventNames.OnPacketEndMessage)
    def on_packet_end_message(portfolio, packetEnd):
        portfolio.log("End of packet")  
    
    # OnAfterExit event handler 
    @event(EventNames.OnAfterExit)
    def on_after_exit(portfolio, context):
        pass
    
    # OnAfterDayOpen event handler    
    @event(EventNames.OnAfterDayOpen)
    def on_after_day_open(portfolio, context):
        pass
    

    Each portfolio-level event is invoked either for the entire experiment, or for one exchange(s). Many of the events have the Context parameter, which enumerates exchanges related to the event.

    The table below lists events you add to PortfolioExecutor and/or InstrumentExecutor when creating your trading algorithm.

    Event Description Cause
    OnPortfolioInit This event is called on the start of the strategy to perform portfolio level initialization. Experiment start time
    OnAfterInit This event is called after the portfolio-level OnInit() event to perform additional portfolio level initialization. All instrument-level OnInit events have been fired by this time. Fired immediately after the OnInit() event.
    OnPortfolioDayOpen This event is called every time when some exchange(s), for which instruments the experiment is executed, is (are) opening. Simulation time reaches Exchange open time. Or An exchange is open at the time of experiment start.
    OnPortfolioBarClose This event is called when a bar on at least one of the involved exchanges is closing. The first reason for closing a bar is reaching its end time according to bar size. If the Intraday subscription uses Time-based bars, then the bar end time is reached on time schedule. If the Intraday subscription uses Tick or Volume bars, then the bar end time depends on market data provided by the subscription. The second reason for reaching the bar end time is reaching the session close/session pause time for an exchange (either on day close or on session pause). Bar end time is reached according to bar size or session close time is reached.
    OnPortfolioDayClose This event is called every time when some of the involved exchanges, for which instruments the experiment is executed, are closing. This event is also called from OnExit event of PortfolioExecutor class if some of exchanges involved into the experiment are open at the time of experiment end. By the moment of OnDayClose call, all the OnBarClose events had been fired for the exchanges, for which the OnDayClose event is invoked. Simulation time reaches session close. The event is also called from OnExit event of PortfolioExecutor class for every exchange that is open by the time of experiment end.
    OnInstrumentsActivated This event is called when one or more instruments become active. IsActive flag for one or more instruments changes its value from false to true.
    OnInstrumentsDeactivated This event is called when one or more instruments become inactive. IsActive flag for one or more instruments changes its value from true to false.
    OnWarmupEnd This event is called when historical data warm-up ends. When running on Strategy Server the strategy switches to live trading. Simulation time reaches the end of warm-up period.
    OnStartRealTime This event is invoked at the very beginning of the experiment when the strategy is starting its execution on Strategy Server. In this case, it precedes the OnInit event. Evidently, the event is not invoked for back-testing mode. The strategy is starting its execution on Strategy Server.
    OnPacketEndMessage Available for Subscription: Daily, Intraday. This event is intended to indicate that bars for all the instruments for the given period have been updated. It is called when synchronization (PacketEnd) message comes to the strategy. Synchronization (PacketEnd) message comes to the strategy.
    OnPortfolioExit This event is called after strategy execution to perform portfolio level post processing. OnExit event is not called in case if there was an unhandled exception during strategy execution. Simulation time reaches ExperimentEnd time or the experiment execution is aborted by the user.
    OnAfterExit This event is called after the OnExit event to perform additional portfolio level post processing. All instrument-level OnExit events have been fired by this time. Fired immediately after the OnExit event.
    OnAfterBarClose Available for Subscription: Intraday. This event is called after portfolio-level OnBarClose() event to perform additional portfolio level processing. All the related instrument-level OnBarClose events have been fired by this time. Fired immediately after the OnBarClose event.
    OnAfterDayClose This event is called after portfolio-level OnDayClose() event to perform additional portfolio level processing. All instrument-level OnDayClose events have been fired by this time. Fired immediately after the OnDayClose() event.
    OnAfterDayOpen This event is called after portfolio-level OnDayOpen() event to perform additional portfolio level processing. All instrument-level OnDayOpen events have been fired by this time. Fired immediately after the OnDayOpen() event.

    Instrument Events

    # OnInit event handler
    @event(EventNames.OnInit)
    def on_init(instr):
        instr.EMAN1 = Ema(instr.portfolio_executor.N1)
    
    # OnDayOpen event handler   
    @event(EventNames.OnDayOpen)
    def on_day_open(instr):
        pass
    
    # OnBarClose event handler  
    @event(EventNames.OnBarClose)
    def on_bar_close(instr):
        if instr.get_bars().Current is None:
            return
        current_time = instr.get_current_time()
        current_price = instr.get_current_price()
    
    # OnDayClose event handler  
    @event(EventNames.OnDayClose)
    def on_day_close(instr):
        pass
    
    # OnOrderBookChanged event handler
    @event (EventNames.OnOrderBookChanged)
    def on_orderbook_changed(instr, level2Message):
        instr.log(level2Message.Price)
    
    # OnQuote event handler     
    @event(EventNames.OnQuote)
    def on_quote(instr, quote):
        instr.log(quote.Price)  
    
    # OnBestBidAsk event handler    
    @event(EventNames.OnBestBidAsk)
    def on_orderbook_changed(instr, bidAskMessage):
        instr.log("Bid " + str(bidAskMessage.BidPrice) + " Ask " + str(bidAskMessage.AskPrice)) 
    
    # OnExit event handler  
    @event(EventNames.OnExit)
    def on_exit(instr, exit_state):
        pass
    
    # OnTrade event handler 
    @event(EventNames.OnTrade)
    def on_trade(instr, trade):
        instr.log("Trade size " + str(trade.Size))  
    
    # OnBestBid event handler
    @event(EventNames.OnBestBid)
    def on_order_book_changed(instr, bidMessage):
        instr.log("Bid " + str(bidMessage.BidPrice))    
    
    # OnBestAsk event handler   
    @event(EventNames.OnBestAsk)
    def on_orderbook_changed(instr, askMessage):
        instr.log(" Ask " + str(askMessage.AskPrice))       
    
    # OnInstrumentActivated event handler
    @event(EventNames.OnInstrumentActivated)
    def on_instrument_activated(instr):
        instr.log(instr.symbol_key + " activated")      
    
    # OnInstrumentDeactivated event handler 
    @event(EventNames.OnInstrumentDeactivated)
    def on_instrument_deactivated(instr):
        instr.log(instr.symbol_key + " deactivated")        
    
    # OnOrderBookSnapshot event handler 
    @event(EventNames.OnOrderBookSnapshot)
    def on_order_book_snapshot(instr, level2Snapshot):
        instr.log(level2Snapshot.Price)
    
    # OnOrderBookL2Message event handler
    @event (EventNames.OnOrderBookL2Message)
    def on_order_book_l2_message(instr, level2Message):
        instr.log(level2Message.Price)
    
    # OnExit event handler  
    @event(EventNames.OnExit)
    def on_exit(instr):
        pass
    

    Each instrument level event is invoked for a particular market instrument.

    Event Description Cause
    OnInit This event is called from portfolio-level OnInit event to perform instrument level initialization. It is invoked for an instrument involved into the strategy execution on the start of the strategy. The event is called from OnInit event of PortfolioExecutor class.
    OnDayOpen This event is called from portfolio-level OnDayOpen event. It is invoked for an instrument which exchange is opening or for an instrument which exchange is open at the time of experiment start. The event is called from OnDayOpen event of PortfolioExecutor class.
    OnTrade Available for Subscription: Tick. This event is called when bid for the instrument is received. The received bid is passed to the event as a parameter. The event is followed by OnBestBidAsk event with the same tick data. Any bid is processed by the system.
    OnBestAsk Available for Subscription: Tick. This event is called when ask for the instrument is received. The received ask is passed to the event as a parameter. The event is followed by OnBestBidAsk event with the same tick data. Any ask is processed by the system.
    OnBestBidAsk This event is called when either bid or ask or a par of bid and ask for the instrument is received. The received bid and ask data are passed to the event as parameters. Any bid or ask or a pair of bid and ask is processed by the system.
    OnBarOpen Available for Subscription: Intraday. This event is called when a bar for the instrument opens - first piece of data on the bar is received. If the strategy works on tick data (intraday bars are aggregated from trades) the event is invoked when the first trade of a bar interval is received for an instrument. If the strategy works on intraday data this event is fired when the first intraday bar of an interval is received. E.g. if 5-minute bars are defined for the strategy execution (either in the Intraday subscription properties or in the Strategy Runner dialog) and the assigned TimeBase stream supports minute bars, then the event will be fired each time the first minute bar for the 5-minute interval is received. The event is called when intraday bar is updated for the first time (from either bar or tick data source).
    OnQuote This event is called when quote for the instrument is received, i.e. in one of the following situations: trade comes,
    bid and ask come,
    bid comes,
    ask comes.
    And appropriately the event is always followed by the event that had caused it, i.e. by:
    OnTrade
    or OnBestBidAsk
    or OnBestBid
    or OnBestAsk.
    Any tick is processed by the system
    OnBarClose Available for Subscription: Intraday. This event is called from portfolio-level OnBarClose event for an instrument for which a bar is closing. Is called from OnBarClose event of PortfolioExecutor class.
    OnDayClose This event is called from portfolio-level OnDayClose event. It is invoked for an instrument which exchange is closing or for an instrument which exchange is open at the time of experiment end. This event is called from OnDayClose of PortfolioExecutor class.
    OnOrderBookChanged Available for Subscription: Tick. This event is called when level 2 data for the instrument is received. Order Book component looks more user-friendly while working with Level2 data (for details see Order Book document). Hence this event is mostly used by those who need access to raw level2 data (probably for some deeper analysis). Level2 data for the instrument is received.
    OnInstrumentActivated This event is called from portfolio-level OnInstrumentsActivated event for an instrument becoming active. The event is called from OnInstrumentsActivated event of PortfolioExecutor class.
    OnInstrumentDeactivated This event is called from portfolio-level OnInstrumentsDeactivated for an instrument becoming inactive. The event is called from OnInstrumentsDeactivated event of PortfolioExecutor class.
    OnOrderBookSnapshot Available for Subscription: Tick. This event is called when level 2 snapshot for the instrument is received. It is a good practice while working with Level2 data to send a snapshot at the beginning of the strategy. Level2 snapshot for the instrument is received.
    OnOrderBookL2Message Available for Subscription: Tick. This event is called when L2Message data for the instrument is received. L2Message contains either a package of level 2 increment messages or a snapshot message with arbitrary number of levels. L2Message for the instrument is received.
    OnExit This event is called from portfolio-level OnExit after strategy execution to perform instrument level post processing. The event is called from OnExit event of PortfolioExecutor class.

    Subscriptions

    
    # Intraday subscription example
    class PortfolioExecutor(PortfolioExecutorBase):
        """This class contains portfolio level event processing logic."""
    
        @subscription(SubscriptionNames.Intraday, is_main=True, bar_size=BarSize(BarUOM.Minute, 1), bar_kind=BarKind.Price)
        def __init__(portfolio, base_portfolio_executor, *args, **kwargs):
            PortfolioExecutorBase.__init__(portfolio, base_portfolio_executor, args=args, kwargs=kwargs) 
    
    # Daily subscription
    @subscription(SubscriptionNames.Daily, is_main=False)
    
    # Tick subscription parameters
    TickSubscriptionParams(self, create_quotes=True, subscribe_bbo=True, subscribe_trades=True,
                     use_l2_data=False, broadcast_out_of_session_data=False)
    
    # Tick subscription 
    @subscription(SubscriptionNames.Tick, 
        is_main=True, 
        tick_subscription_params=TickSubscriptionParams(subscribe_bbo=True, 
            subscribe_trades=True, use_l2_data=False))
    
    # Level2 subscription example
    @subscription(SubscriptionNames.Tick, 
        is_main=True, 
        tick_subscription_params=TickSubscriptionParams(create_quotes=True, use_l2_data=True))
    

    Define data subscriptions to supply market data from the TimeBase to a strategy. Place the subscription annotation at the beginning of the PortfolioExecutor class and pass the subscription type and some additional parameters, if needed. During strategy execution each strategy subscription reads data from the corresponding stream (or streams) of the TimeBase.

    QuantOffice python strategies can consume the following types of data:

    1. Daily (sometimes also called Daily Bars). This subscription can consume only daily data, i.e. data which periodicity equals to 1 day (Bar message type with an interval of 1 day in TimeBase). If such a subscription is included in a strategy and there is no appropriate daily data stream in the connected TimeBase database, then error message is displayed.
    2. Intraday (sometimes also called Intraday Bars or Bars). This subscription can consume data either from intraday stream(s) or aggregate data from tick stream(s) containing trading data. You can create intraday bars a few ways:
      • Price bar. Intraday bars are created by time intervals (e.g.: all data for each 1 minute or other specified time interval are aggregated into one bar). In this case, either intraday stream (Bar message type with periodicity less than 1 day in TimeBase) or tick stream with trades messages (Trades message type in TimeBase) can be assigned to such subscription.
      • Tick bar. Intraday bars are created by a number of trades (e.g.: all data for each 10 or other specified number of trades are aggregated into one bar). In this case, only tick stream with trades messages (Trades message type in TimeBase) can be assigned to such subscription.
      • Volume bar. Intraday bars are created by volume of trades (e.g.: each time the specified trades volume is reached, new bar is created). In this case, only tick stream with trades messages (Trades message type in TimeBase) can be assigned to such subscription.
      • Extended bar. Bar containing some user-defined fields.
    3. Tick. This subscription can consume data from tick stream(s) (BestBidOffer, Trades, and Level2 message types in TimeBase).

    Bar Size

    When defining a bar subscription you will need to assign a BarSize: Millisecond, Second, Minute, Hour, Day, Week, Month, Year. For volume and tick bar you need to pass the tick_bar_size parameter to set the size of the bar.

    For daily subscriptions define only the subscription type and whether it is a main subscription or not. For tick subscription it is possible to set more parameters defined by TickSubscriptionParams. The following parameters can be passed:

    Custom Subscription

    #definition of custom subscription in instrument_executor   
    @custom_subscription("SubscriptionName", "CustomStream", ["Custom_Message"])
    def custom_message_handler(instr, message):
    
    #subscribing for messages in instrument_executor    
    instr.subscribe(instr.custom_message_handler, [instr.get_symbol()])
    
    #definition of custom subscription in portfolio_executor    
    @custom_subscription("SubscriptionName", "CustomStream", ["Custom_Message"])
    def custom_message_handler(portfolio, message):
    
    #subscribing for messages in portfolio_executor for cases 
    portfolio.subscribe(portfolio.custom_message_handler)
    

    In addition to predefined subscriptions, you can also add custom subscriptions. Custom subscriptions supply strategy with data of arbitrary nature. Custom subscription is connected to any TickDB stream and provides API for subscribing to its data.

    The parameters passed as arguments into the subscription annotation are:

    Custom subscriptions can be used without defining the main subscription.

    L2 Market Data

    """Module with OrderBook class. Some main methods."""
    from Deltix.Timebase.Api.Messages import ExchangeCodec
    from QuantOffice.L2 import OnOrderBookChangedDelegate
    from QuantOffice.L2 import OrderBook as NetOrderBook
    
    class OrderBook:
        def __init__(self, net_order_book):
            self.net_order_book = net_order_book
    
        def get_market_depth(self, is_ask):
            return self.net_order_book.GetMarketDepth(is_ask)
    
        def best_ask(self):
            return self.net_order_book.BestAsk()
    
        def best_bid(self):
            return self.net_order_book.BestBid()
    
        def best_ask_size(self):
            return self.net_order_book.BestAskSize()
    
        def best_bid_size(self):
            return self.net_order_book.BestBidSize()
    
        def best_ask_number_of_orders(self):
            return self.net_order_book.BestAskNumberOfOrders()
    
        def best_bid_number_of_orders(self):
            return self.net_order_book.BestBidNumberOfOrders()
    

    Order Book is a set of active limit orders for a certain instrument maintained by an exchange. Order Book contains bid/offer prices, size of the orders, and information about market makers. Data that contains Order Book information is called Level Two Market Data. Market depth is the number of the Levels of the order book. It is specified by Level2 data provider or the exchange itself and can different for different data providers.

    L2 market data is collected by the Aggregator and supplied to QuantOffice by TimeBase. L2 market data is used by trading algorithms via the Order Book API. Market Simulator can also use L2 market data to execute orders.

    Input Parameters

    @input_parameter("int", input_parameter_default_value="5",
    input_parameter_question="N1")
    def set_N1(portfolio, N1):
        portfolio.N1 = N1
    
    @input_parameter("int", input_parameter_default_value="8", input_parameter_question="N2")
    def set_N2(portfolio, N2):
        portfolio.N2 = N2
    
    def __init__(portfolio, base_portfolio_executor, *args, **kwargs):
        PortfolioExecutorBase.__init__(portfolio, base_portfolio_executor, args=args, kwargs=kwargs) 
        portfolio.N1= 5
        portfolio.N2 = 8
    
    # usage of input parameters in strategy
    @event(EventNames.OnInit)
    def on_init(instr):
        instr.ema_n1 = Ema(instr.portfolio_executor.N1)
        instr.ema_n2 = Ema(instr.portfolio_executor.N2)
    
    # initialize input parameters in notebook
    strategy_runner = PyStrategyRunner()
    config = strategy_runner.build_config(symbols, start_time, end_time, subscriptions=subscriptions, bar_size=bar_size, use_tick_for_intraday=True)
    input_parameters = config.StrategyParameters
    input_parameters.N1 = 5
    input_parameters.N2 = 10
    

    Input parameters are parameters specified when strategy execution is started. Input parameters allow changing strategy properties on the fly when starting strategy execution without a need to edit and recompile the strategy code. If you define an input parameter you can spefify value in script wich start backtesting or live trading of straegy and use multiple scripts with different values of input parameters.

    For each input parameter created for a strategy, a separate field is created for the PortfolioExecutor class. The default values are set in on the init method. Input parameters have a name, type, an optional default value, and an associated question. All these properties of Input Parameters are used when QuantOffice automatically generates and displays the Input Parameters dialog, which is used for strategy execution.

    Working with Orders and Order Events

    # declaration of order_executor
    def __init__(portfolio, base_portfolio_executor, *args, **kwargs):
        portfolio.order_executor = None # -> quantoffice.order_executor.order_processor.OrderProcessor
    
    @event(EventNames.OnPortfolioInit)
    def on_init(portfolio):
    
        # initialization of order_executor
        portfolio.order_executor = OrderProcessor(portfolio.base_portfolio_executor)
        portfolio.order_executor.set_parameters(portfolio.ems_parameters)
    
    @wrapped("create_ems_parameters", return_type="Deltix.EMS.Coordinator.EMSParameters")
    @staticmethod
    def create_ems_parameters(): # -> Deltix.EMS.Coordinator.EMSParameters
        # initialization of parameters of order_executor
        ems_parameters = EMSParameters()
        simulator_params = OrderSimulatorParameters()
        simulator_params.simulationMode = SimulationMode.Trade
        ems_parameters.OrderExecutor.Parameters = simulator_params
        return ems_parameters
    
    # declaration of input parameters of order_executor
    @input_parameter("Deltix.EMS.Coordinator.EMSParameters", input_parameter_default_value="create_ems_parameters()", input_parameter_question="EMS Parameters")
    def set_ems_parameters(portfolio, ems_parameters):
        portfolio.ems_parameters = ems_parameters
    

    The OrderExecutor object is a main object used to manage order execution. Introduce a portfolio.order_executor portfolio level variable to have access to order processor.

    Set the following OrderExecutor parameters to use order execution in your strategy:

    It is useful to create a special input parameter to be able to change strategy parameters on the fly when starting strategy execution without a need to edit and recompile strategy code. In the declaration of input parameters, we create a parameter for EMS parameters. When order processor parameters are defined as input parameters, they will appear in the InputParameters dialog box when the strategy is started. Input parameters of a particular strategy are saved when the strategy is launched. Next time the strategy is started this dialog box will show the saved parameters from the previous strategy run instead of default values defined in the strategy create_ems_parameters() method.

    Order processor is usually created and initialized in the PortfolioExecutor on init method.Pass PortfolioExecutor instance as a parameter into the Order processor constructor. Call order_executor.set_parameters() method before any use of the order processor. This method should be called only once when order processor is created.

    # Use order executor on instrument level
    instr.order_executor = instr.portfolio_executor.order_executor 
    

    OrderExecutor methods can be used not only in PortfolioExecutor, but in InstrumentExecutor and any user defined classes as well. In InstrumentExecutor it is possible to refer to the created order processor. If you want to use the order processor in any user class, strategy will pass the reference to the created OrderExecutor object. To send an order, create an order object of the necessary type, set additional order properties and then pass it to the OrderExecutor.send_order() method.

    When working in real-time mode, the order will be transmitted immediately. It is important to create a new order object for every order. The same order shouldn't be sent more than once. It is not allowed changing order properties after it has been sent.

    OrderExecutor.send_order() method doesn't return any value and it is actually unknown from inside of the strategy if the order was sent successfully. When trading in real time mode, order data is sent via the internet and the status of the request can't be determined until certain return notification. To check if the order was sent successfully or not, the strategy should subscribe to order events and wait for the notification of the sent order. It may take some time depending on the internet connection speed and other factors.

    Order Parameters

    Table at below shows set of mandatory order parameters wich should be passes to the order during creation.

    Parameter Description
    instr The symbol of the ticker.
    qtty Value describing the quantity of the order. The value should be positive, regardless if it is a long or short order that you want to send.
    side This argument describes if we want to buy or to sell.
    timeInForce This argument defines how long the order will live in trading system.

    The side parameter has the following possible choices:

    Value Description
    Buy Specifies buy trade in strategy.
    Sell Specifies sell trade in strategy.
    BuyCover Synonym for Buy side in back-testing mode. In real-time trading is used to buy to cover SellShort operations.
    SellShort Synonym for Sell side in back-testing mode. In real-time trading is used for short selling operations when we sell a quantity we do not actually have, and therefore borrow from a third party. This operation is covered with BuyCover.
    SellShortExempt A special trading situation where a short sale is allowed on a minustick.

    QuantOffice has three simple order types MarketOrder, LimitOrder, and StopOrder. These orders are mapped directly to real trading orders when working in real-time mode.

    Market Order

    # Market order
    @event(EventNames.OnBarClose)
    def on_bar_close(instr):
        side = OrderSide.Buy
        order = market_order(instr, 100, side, OrderTimeInForce.GTC)
        instr.order_executor.send_order(order)
    

    Example: We are going to buy 100 on bar close using instrument executor security. To create such order we should add the following code to the instrument_executor.py file.

    Limit Order

    # Limit order
    @event(EventNames.OnBarClose)
    def on_bar_close(instr):
        side = OrderSide.Buy
        order = limit_order(instr, qtty, side, OrderTimeInForce.GTC, current_price - 0.5)
        instr.order_executor.send_order(order)  
    

    Besides all common parameters, this order receives one more mandatory parameter – limitPrice.

    Example: We are going to buy 100 on bar close at current bar close price -0.5 using instrument executor security. The order will be valid until cancelled. To create such order we should add the following code to the instrument_executor.py file.

    Stop Order

    # Stop order
    @event(EventNames.OnBarClose)
    def on_bar_close(instr):
        side = OrderSide.Sell
        order = stop_order(instr, qtty, side, OrderTimeInForce.GTC, current_price - 0.5)
        instr.order_executor.send_order(order)
    

    Besides all common parameters, this order receives one more mandatory parameter – stopPrice.

    Example: We are going to sell 100 on bar close once price has dropped below the current bar close by 0.5 using instrument executor security. The order will be valid until cancelled. To create such order we should add the following code to the instrument_executor.py file:

    Order Events

    @event(EventNames.OnInit)
    def on_init(instr):
        # Add Event Listeners to orderExecutor object
        instr.order_executor.add_position_change_listener(instr.on_position_change, instr.get_symbol())
        instr.order_executor.add_position_size_change_listener(instr.on_position_size_change, instr.get_symbol())
        instr.order_executor.add_order_status_listener(instr.on_order_status, instr.get_symbol())
    
    def on_order_status(instr, sender, e): 
    
        info = e
        order = instr.order_executor.get_order_data(info.order_id)
    
        if e.order_status == OrderStatus.Submitted:
            if isinstance(order, LimitOrder):
                instr.on_LimitOrder_submitted(order, info)
            elif isinstance(order, StopLimitOrder):
                instr.on_StopLimitOrder_submitted(order, info)
    
        elif e.order_status == OrderStatus.Filled:
            if isinstance(order, LimitOrder):
                instr.on_LimitOrder_filled(order, info)
            elif isinstance(order, StopLimitOrder):
                instr.on_StopLimitOrder_filled(order, info)
    
        elif e.order_status == OrderStatus.Cancelled:
            if isinstance(order, LimitOrder):
                instr.on_LimitOrder_cancelled(order, info)
            elif isinstance(order, StopLimitOrder):
                instr.on_StopLimitOrder_cancelled(order, info)
    
    def on_position_size_change(instr, sender, e): 
    
        #position = e
        #instr.get_charting().draw_label(f"Sz: {position.quantity}", position.time, position.realized_pnl + position.unrealized_pnl,
        #str(position.instrument), instr.font, Brushes.Blue, Brushes.Yellow, 2)
        pass
    
    def on_position_change(instr, sender, e): 
    
        #position = e
        #instr.get_charting().draw_line("PNL", "", position.time, position.realized_pnl + position.unrealized_pnl,
        #str(position.instrument), Pens.Black, 2)
        pass
    

    QuantOffice supports a subscription mechanism to receive notifications when an order status or position is changed. New event is generated each time order state changes - on the diagram shown as arrows.

    This mechanism works the same way in paper trading and real-time modes. To receive them, you need to define a method that handles events and subscribe these methods to events within the on_init method of portfolio_executor. You may specify any custom logic in the event handlers analyzing the data of the order or position received in the handler.

    Reports

    Advanced reporting engine is one of the most important QuantOffice functionalities. QuantOffice supports a set of various standard reports and also provides means for creating custom reports.

    The following standard reports are available:

    Refer to QuantOffice Reporting System.pdf to learn more about reports and parameters thereof.

    Standard Reports

    def __init__(portfolio, base_portfolio_executor, *args, **kwargs):
        portfolio.report_engine = None # ->  QuantOffice.Reporting.Model.ReportEngine
    
    @event(EventNames.OnPortfolioInit)
    def on_init(portfolio):
        portfolio.report_engine = ReportEngine(portfolio.base_portfolio_executor, portfolio.order_executor, portfolio.order_executor)
        portfolio.report_engine.TradeReport.Enable()
        portfolio.report_engine.OrderReport.Enable()
        portfolio.report_engine.ExecutionReport.Enable()
        portfolio.report_engine.PerformanceReport.Enable()
        portfolio.report_engine.ByInstrumentReport.Enable()
        portfolio.report_engine.ByPeriodReport.Enable()
        portfolio.report_engine.DrawdownReport.Enable()
        portfolio.report_engine.AllocationReport.Enable()
        portfolio.report_engine.Start()
    
    @event(EventNames.OnPortfolioExit)
    def on_exit(portfolio, exit_state):
        portfolio.report_engine.InitialCapital = portfolio.initial_cap
        portfolio.report_engine.RiskFreeRate = portfolio.interest_on_cash
        portfolio.report_engine.Finish()
        portfolio.report_engine.TradeReport.Show()
        portfolio.report_engine.OrderReport.Show()
        portfolio.report_engine.ExecutionReport.Show()
        portfolio.report_engine.PerformanceReport.Show()
        portfolio.report_engine.PerformanceReport.Consolidate()
        portfolio.report_engine.ByInstrumentReport.Show()
        portfolio.report_engine.ByInstrumentReport.Consolidate()
        portfolio.report_engine.ByPeriodReport.Show()
        portfolio.report_engine.DrawdownReport.Show()
        portfolio.report_engine.AllocationReport.Show()
    

    Creation of standard reports is implemented via Report Engine. In the code sample area you may preview the example of code snippet to be added to PortfolioExecutor, which is responsible for creation of standard reports. It adds the variable report_engine, then in on_init required reports are enabled and report_engine is started. As soon as the strategy finishes its execution, the methods to Show() reports are called.

    Custom Reports

    # PortfolioExecutor example
    class PortfolioExecutor(PortfolioExecutorBase):
        """This class contains portfolio level event processing logic."""
        @custom_report('Position_Changes', [('Symbol', 'string'), ('DateTime', 'DateTime'), ('Message', 'string')])
    
        @event(EventNames.OnPortfolioInit)
        def on_init(portfolio):
            portfolio.Position_Changes = portfolio.get_custom_reporter('Position_Changes')
    
    # InstrumentExecutor example
    def __init__(instr, base_instrument_executor, portfolio_executor, *args, **kwargs):
        instr.Position_Changes = None
    
    @event(EventNames.OnInit)
    def on_init(instr):
        instr.Position_Changes = instr.portfolio_executor.get_custom_reporter('Position_Changes')
    
    @event(EventNames.OnBarClose)
    def on_position_change(instr, sender, e): 
        instr.Position_Changes.Log("Position_Changes", instr.get_symbol(), instr.get_current_time(), "Position changed")
        pass
    

    Custom reports are useful to output user-defined data. The declaration of the custom report should be added to the beginning of the PortfolioExecutor class. The first parameter of the custom report declaration defines the name of the custom report, the second parameter is an array representing columns of your future report.

    Once you have created a custom report, you can insert it into your code in the appropriate class (either InstrumentExecutor or PortfolioExecutor). For our example, let’s create the Position_Change report to the OnPositionChange method of the InstrumentExecutor class. The first parameter of the Log method is the title of the report. The others represent the columns of the report. The output custom report is similar to a log. That means that data appears in it gradually as the strategy is running against standard reports, entire results appear after the strategy execution is completed.

    Logging

    # Logging in PortfolioExecutor
    @event(EventNames.OnPortfolioInit)
    def on_init(portfolio):
        portfolio.log("Portfolio initialized")
    
    # Logging in InstrumentExecutor
    @event(EventNames.OnBarClose)
    def on_bar_close(instr):
        if instr.get_bars().BarsAgo(1) is None:
            return
        instr.log(str(instr.get_bars().BarsAgo(1).Low))
    

    Logging is provided by means of a log method from the InstrumentExecutor or PortfolioExecutor class. The output user information shares the log with QuantOffice runtime information on errors and warnings.

    Charting

    # Create lines
    def __init__(instr, base_instrument_executor, portfolio_executor, *args, **kwargs):
        InstrumentExecutorBase.__init__(instr, base_instrument_executor, portfolio_executor, args=args, kwargs=kwargs)
        instr.EMAN1 = None  # ->Ema
        instr.line_e_m_a_n1 = None  # ->Line
    
    @event(EventNames.OnInit)
    def on_init(instr):
        instr.EMAN1 = Ema(instr.portfolio_executor.N1)
        instr.line_e_m_a_n1 = instr.create_chart_line(r"EMAN1", "", instr.get_current_time(),
        instr.get_symbol(), Color.Red, 1.0, LineStyle.Line,
        instr.get_charting().get_price_chart(), 1)
    
    # Draw
    @event(EventNames.OnBarClose)
    def on_bar_close(instr):
        current_time = instr.get_current_time()
        current_price = instr.get_current_price()
        instr.EMAN1.Add(current_price, current_time)
        eMAN1_value = instr.EMAN1.Value
        eMAN1_stable = instr.EMAN1.Stable
        if eMAN1_stable:
            instr.line_e_m_a_n1.draw(current_time, eMAN1_value)
    
    # draw_tag()
    instr.get_charting().draw_tag(instr.current_time, instr.current_price, ChartTagType.Cross, Color.Red, instr.get_symbol(), "Reject", true);
    
    # draw_label()
    instr.get_charting().draw_label(str(instr.position_size), instr.position_time, position.realized_pnl + position.unrealized_pnl, 
    instr.get_symbol(), font, Brushes.Blue, instr.get_charting().get_price_chart(), Brushes.Yellow, 2);
    
    # draw_segment()
    instr.get_charting().draw_segment("", Pens.Blue, instr.submission_time, instr.current_price, instr.current_time, instr.current_price, instr.get_symbol(), 1);
    
    # draw_ellipse(), draw_rectangle()
    instr.get_charting().draw_ellipse(new Pen(Color.Red, 2), new SolidBrush(Color.FromArgb(70, Color.Red)), drawdownTime.AddDays(-1), drawdownPnl1, drawdownTime.AddDays(1), drawdownPnl2, "Total", "Portfolio Chart", 1);
    instr.get_charting().draw_rectangle(new Pen(Color.Red, 2), new SolidBrush(Color.FromArgb(70, Color.Red)), drawdownTime.AddDays(-1), drawdownPnl1, drawdownTime.AddDays(1), drawdownPnl2, "Total", "Portfolio Chart", 1);
    

    Charts are simple but powerful analytical tools. QuantOffice has a user-friendly interface and puts great emphasis on the ability to display and customize charts making charting an essential element of strategy development. Charting functionality allows creating lines and drawing on charts. You may draw indicators lines or other data that you need to display on portfolio or instrument charts. To display the line you need to:

    1. Create line method - is used to specify parameters of the chart line. Create the line variable first and then initialize it in on the init method.
    2. Draw line method - is used to draw the line data. Call the draw() method whenever you need to draw the line. You can also show other types of objects (tags, figures, labels).

    Parameters of the draw_tag() method:

    Parameters of draw_label() method:

    For some kinds of orders like LimitOrder or StopIfTouchedOrder, it is interesting when the order was submitted and when it was filled or cancelled. To show the link between these points we will use the draw_segment method.

    Parameters of draw_segment() method:

    The draw_ellipse() and draw_rectangle() methods have the same sets of parameters.

    Output Streams

    # ortfolioExecutor annotation
    from quantoffice.decorators.output_stream import OutputStreamWriteMode, output_stream
    
    class PortfolioExecutor(PortfolioExecutorBase):
        """This class contains portfolio level event processing logic."""
        @output_stream("test_output_stream", OutputStreamWriteMode.Rewrite, 'StreamKey', ['Bar', 'Cash_Flow'])
    
    # InstrumentExecutor
    from RTMath.Containers import HdDateTime
    
    my_bar = Bar(11.1, 11.1, 11.1, 11.1, 15, instr.get_symbol_key())
    cf_mes.Timestamp =  HdDateTime(System.DateTime.Now)
    instr.portfolio_executor.base_portfolio_executor.test_output_stream.Send(my_bar)
    
    cf_mes = instr.create_custom_message("Cash_Flow")
    cf_mes.Value = 3.21
    cf_mes.Timestamp =  HdDateTime(System.DateTime.Now)
    cf_mes.Instrument = instr.get_symbol_key()
    instr.portfolio_executor.base_portfolio_executor.test_output_stream.Send(cf_mes)
    

    QuantOffice provides means for outputting messages from a strategy to TimeBase database stream. The strategy can send different types of messages to the database stream: ticks data, intraday or daily bars, Security Metadata messages, custom messages that extend built-in message types and custom messages which are not based on any of the existing types.

    This sending messages to TimeBase is implemented using a so-called Output Streams. Similar to receiving market data from TimeBase database via subscriptions, a strategy can output data into TimeBase database via Output Streams. To add an output stream into a strategy, use @output_stream annotation in the strategy’s PortfolioExecutor class, which defines the name of the output stream, specifies the write mode, the target stream key and stream message types.

    When the strategy creates a new message to be sent to TimeBase database, it fills in Instrument and Time properties of the message. To fill in the Instrument property, identifier of the instrument can be retrieved using members of InstrumentExecutor or PortfolioExecutor class of the strategy:

    Time property can be set using the get_current_time() method which is accessible from both InstrumentExecutor and PortfolioExecutor.

    Provided sample presents code fragment, contained in some event handler in InstrumentExecutor, demonstrating outputting messages into TimeBase database. In this example, TimeBase contains stream with the identifier equal to StreamKey, where the stream schema contains messages Bar, Cash_Flow. cf_mes.Value is the name of the field of Cash_Flow message in the output stream. The Send() method sends messages to TimeBase database and has the following syntax:

    Conversion Engine

    @event(EventNames.OnPortfolioInit)
        def on_init(portfolio):
            portfolio.conversion_engine = ConversionEngine(portfolio.base_portfolio_executor)
            portfolio.conversion_engine.SetParams(portfolio.conversion_engine_parameters)
    
    @input_parameter('QuantOffice.Execution.Utils.ConversionEngineParams', input_parameter_question='Conversion Engine Parameters')
        def set_conversion_engine_parameters(portfolio, ce_parameters):
            portfolio.conversion_engine_parameters = ce_parameters
    
    @event(EventNames.OnBestBidAsk)
        def on_best_bid_ask(instr, best_bid_ask):
            print(f"Bid price {str(best_bid_ask.BidPrice)} USD; {str(instr.portfolio_executor.conversion_engine.Convert(best_bid_ask.BidPrice, 'USD', 'BTC'))} BTC; rate {str(instr.portfolio_executor.conversion_engine.Convert(1, 'BTC', 'USD'))}")
            print(f"Ask price {str(best_bid_ask.OfferPrice)} USD;  {str(instr.portfolio_executor.conversion_engine.Convert(best_bid_ask.OfferPrice, 'USD', 'BTC'))} BTC")
    
    from QuantOffice.Execution.Utils import ConversionEngineParams
    ce_parameters = ConversionEngineParams()
    ce_parameters.SimulationMode = SimulationMode.BBO
    ce_parameters.AddCrossRate('BTC', 'USD', 'BTCUSD', False)
    config.StrategyParameters.conversion_engine_parameters = ce_parameters
    

    Conversion Engine allows strategy and other components of QuantOffice to convert currencies. Report Engine uses it if a user wants to generate a report for a specific funding currency but the strategy works with instruments nominated in different currencies. Conversion Engine is subscribed for different currency pairs and uses price data for conversions.

    To use Conversion Engine in your strategy (refer to the provided code example):

    1. Define ConversionEngine in on_init(portfolio)
    2. Set parameters of Conversion Engine
    3. Pass Conversion Engine to Report Engine
    4. Define and initialize proper input parameters in the strategy notebook file - refer to ConversionEngine in Templates folder in JupyterHub - see screenshot.