コード例 #1
0
ファイル: algo.py プロジェクト: ErnstTmp/qtpylib
class Algo(Broker):
    """Algo class initilizer (sub-class of Broker)

    :Parameters:

        instruments : list
            List of IB contract tuples
        resolution : str
            Desired bar resolution (using pandas resolution: 1T, 1H, etc). Use K for tick bars.
        tick_window : int
            Length of tick lookback window to keep. Defaults to 1
        bar_window : int
            Length of bar lookback window to keep. Defaults to 100
        timezone : str
            Convert IB timestamps to this timezone (eg. US/Central). Defaults to UTC
        preload : str
            Preload history when starting algo (using pandas resolution: 1H, 1D, etc). Use K for tick bars.
        continuous : bool
            Tells preloader to construct continuous Futures contracts (default is True)
        blotter : str
            Log trades to MySQL server used by this Blotter (default is "auto detect")
        force_resolution : bool
            Force new bar on every ``resolution`` even if no new ticks received (default is False)
    """

    __metaclass__ = ABCMeta

    def __init__(self, instruments, resolution,
        tick_window=1, bar_window=100, timezone="UTC", preload=None,
        continuous=True, blotter=None, force_resolution=False, **kwargs):

        self.name = str(self.__class__).split('.')[-1].split("'")[0]

        # ----------------------------------------------------
        # default args
        self.args = kwargs.copy()
        cli_args  = self.load_cli_args()

        # override kwargs args with cli args
        for arg in cli_args:
            if arg not in self.args or ( arg in self.args and cli_args[arg] is not None ):
                self.args[arg] = cli_args[arg]

        # fix flag args (no value)
        for arg in ["backtest"]:
            if arg in kwargs and "--"+str(arg) not in sys.argv:
                self.args[arg] = kwargs["backtest"]
        # ----------------------------------------------------


        # assign algo params
        self.bars           = pd.DataFrame()
        self.ticks          = pd.DataFrame()
        self.quotes         = {}
        self.books          = {}
        self.tick_count     = 0
        self.tick_bar_count = 0
        self.bar_count      = 0
        self.bar_hash       = 0

        self.tick_window    = tick_window if tick_window > 0 else 1
        if "V" in resolution:
            self.tick_window = 1000
        self.bar_window     = bar_window if bar_window > 0 else 100
        self.resolution     = resolution.replace("MIN", "T")
        self.timezone       = timezone
        self.preload        = preload
        self.continuous     = continuous

        self.backtest       = self.args["backtest"]
        self.backtest_start = self.args["start"]
        self.backtest_end   = self.args["end"]

        # -----------------------------------
        self.sms_numbers    = [] if self.args["sms"] is None else self.args["sms"]
        self.trade_log_dir  = self.args["log"]
        self.blotter_name   = self.args["blotter"] if self.args["blotter"] is not None else blotter
        self.record_output  = self.args["output"]

        # -----------------------------------
        # load blotter settings && initilize Blotter
        self.load_blotter_args(self.args["blotter"])
        self.blotter = Blotter(**self.blotter_args)

        # -----------------------------------
        # initiate broker/order manager
        super().__init__(instruments, ibclient=int(self.args["ibclient"]),
            ibport=int(self.args["ibport"]), ibserver=str(self.args["ibserver"]))

        # -----------------------------------
        # signal collector
        self.signals = {}
        for sym in self.symbols:
            self.signals[sym] = pd.DataFrame()

        # -----------------------------------
        # initilize output file
        self.record_ts = None
        if self.record_output:
            self.datastore = tools.DataStore(self.args["output"])

        # -----------------------------------
        # initiate strategy
        self.on_start()

        # ---------------------------------------
        # add stale ticks to allow for interval-based bars
        if force_resolution and self.resolution[-1] not in ("K", "V"):
            self.bar_timer = tools.RecurringTask(
                self.add_stale_tick, interval_sec=1, init_sec=1, daemon=True)

    # ---------------------------------------
    def add_stale_tick(self):
        if len(self.ticks) > 0:
            tick = self.ticks[-1:].to_dict(orient='records')[0]
            tick['timestamp'] = datetime.utcnow()

            tick = pd.DataFrame(index=[0], data=tick)
            tick.set_index('timestamp', inplace=True)
            tick = tools.set_timezone(tick, tz=self.timezone)

            self._tick_handler(tick, stale_tick=True)


    # ---------------------------------------
    def load_cli_args(self):
        parser = argparse.ArgumentParser(description='QTPy Algo Framework')

        parser.add_argument('--ibport', default='4001', help='IB TWS/GW Port to use (default: 4001)', required=False)
        parser.add_argument('--ibclient', default='998', help='IB TWS/GW Client ID (default: 998)', required=False)
        parser.add_argument('--ibserver', default='localhost', help='IB TWS/GW Server hostname (default: localhost)', required=False)
        parser.add_argument('--sms', nargs='+', help='Numbers to text orders', required=False)
        parser.add_argument('--log', default=None, help='Path to store trade data (default: ~/qpy/trades/)', required=False)

        parser.add_argument('--backtest', help='Work in Backtest mode', action='store_true', required=False)
        parser.add_argument('--start', help='Backtest start date', required=False)
        parser.add_argument('--end', help='Backtest end date', required=False)
        parser.add_argument('--output', help='Path to save the recorded data', required=False)

        parser.add_argument('--blotter', help='Log trades to the MySQL server used by this Blotter', required=False)

        args, unknown = parser.parse_known_args()

        return args.__dict__

    # ---------------------------------------
    def run(self):
        """Starts the algo

        Connects to the Blotter, processes market data and passes
        tick data to the ``on_tick`` function and bar data to the
        ``on_bar`` methods.
        """

        # -----------------------------------
        # backtest mode?
        if self.backtest:
            if self.record_output is None:
                logging.error("Must provide an output file for Backtest mode")
                sys.exit(0)
            if self.backtest_start is None:
                logging.error("Must provide start date for Backtest mode")
                sys.exit(0)
            if self.backtest_end is None:
                self.backtest_end = datetime.now().strftime('%Y-%m-%d %H:%M:%S.%f')

            # backtest history
            self.blotter.drip(
                symbols       = self.symbols,
                start         = self.backtest_start,
                end           = self.backtest_end,
                resolution    = self.resolution,
                tz            = self.timezone,
                continuous    = self.continuous,
                quote_handler = self._quote_handler,
                tick_handler  = self._tick_handler,
                bar_handler   = self._bar_handler,
                book_handler  = self._book_handler
            )

        # -----------------------------------
        # live data mode
        else:
            # preload history
            if self.preload is not None:
                try: # dbskip may be active
                    self.bars = self.blotter.history(
                        symbols    = self.symbols,
                        start      = tools.backdate(self.preload),
                        resolution = self.resolution,
                        tz         = self.timezone,
                        continuous = self.continuous
                    )
                except:
                    pass
                # print(self.bars)

            # add instruments to blotter in case they do not exist
            self.blotter.register(self.instruments)

            # listen for RT data
            self.blotter.listen(
                symbols       = self.symbols,
                tz            = self.timezone,
                quote_handler = self._quote_handler,
                tick_handler  = self._tick_handler,
                bar_handler   = self._bar_handler,
                book_handler  = self._book_handler
            )

    # ---------------------------------------
    @abstractmethod
    def on_start(self):
        """
        Invoked once when algo starts. Used for when the strategy
        needs to initialize parameters upon starting.

        """
        # raise NotImplementedError("Should implement on_start()")
        pass

    # ---------------------------------------
    @abstractmethod
    def on_quote(self, instrument):
        """
        Invoked on every quote captured for the selected instrument.
        This is where you'll write your strategy logic for quote events.

        :Parameters:

            symbol : string
                `Instruments Object <#instrument-api>`_

        """
        # raise NotImplementedError("Should implement on_quote()")
        pass

    # ---------------------------------------
    @abstractmethod
    def on_tick(self, instrument):
        """
        Invoked on every tick captured for the selected instrument.
        This is where you'll write your strategy logic for tick events.

        :Parameters:

            symbol : string
                `Instruments Object <#instrument-api>`_

        """
        # raise NotImplementedError("Should implement on_tick()")
        pass

    # ---------------------------------------
    @abstractmethod
    def on_bar(self, instrument):
        """
        Invoked on every tick captured for the selected instrument.
        This is where you'll write your strategy logic for tick events.

        :Parameters:

            instrument : object
                `Instruments Object <#instrument-api>`_

        """
        # raise NotImplementedError("Should implement on_bar()")
        pass

    # ---------------------------------------
    @abstractmethod
    def on_orderbook(self, instrument):
        """
        Invoked on every change to the orderbook for the selected instrument.
        This is where you'll write your strategy logic for orderbook changes events.

        :Parameters:

            symbol : string
                `Instruments Object <#instrument-api>`_

        """
        # raise NotImplementedError("Should implement on_orderbook()")
        pass

    # ---------------------------------------
    @abstractmethod
    def on_fill(self, instrument, order):
        """
        Invoked on every order fill for the selected instrument.
        This is where you'll write your strategy logic for fill events.

        :Parameters:

            instrument : object
                `Instruments Object <#instrument-api>`_
            order : object
                Filled order data

        """
        # raise NotImplementedError("Should implement on_fill()")
        pass

    # ---------------------------------------
    def get_instrument(self, symbol):
        """
        A string subclass that provides easy access to misc
        symbol-related methods and information using shorthand.
        Refer to the `Instruments API <#instrument-api>`_
        for available methods and properties

        Call from within your strategy:
        ``instrument = self.get_instrument("SYMBOL")``

        :Parameters:

            symbol : string
                instrument symbol

        """
        instrument = Instrument(self._getsymbol_(symbol))
        instrument._set_parent(self)
        return instrument

    # ---------------------------------------
    def get_history(self, symbols, start, end=None, resolution="1T", tz="UTC"):
        """Get historical market data.
        Connects to Blotter and gets historical data from storage

        :Parameters:
            symbols : list
                List of symbols to fetch history for
            start : datetime / string
                History time period start date (datetime or YYYY-MM-DD[ HH:MM[:SS]] string)

        :Optional:
            end : datetime / string
                History time period end date (datetime or YYYY-MM-DD[ HH:MM[:SS]] string)
            resolution : string
                History resoluton (Pandas resample, defaults to 1T/1min)
            tz : string
                History timezone (defaults to UTC)

        :Returns:
            history : pd.DataFrame
                Pandas DataFrame object with historical data for all symbols
        """
        return self.blotter.history(symbols, start, end, resolution, tz)


    # ---------------------------------------
    # shortcuts to broker._create_order
    # ---------------------------------------
    def order(self, signal, symbol, quantity=0, **kwargs):
        """ Send an order for the selected instrument

        :Parameters:

            direction : string
                Order Type (BUY/SELL, EXIT/FLATTEN)
            symbol : string
                instrument symbol
            quantity : int
                Order quantiry

        :Optional:

            limit_price : float
                In case of a LIMIT order, this is the LIMIT PRICE
            expiry : int
                Cancel this order if not filled after *n* seconds (default 60 seconds)
            order_type : string
                Type of order: Market (default), LIMIT (default when limit_price is passed), MODIFY (required passing or orderId)
            orderId : int
                If modifying an order, the order id of the modified order
            target : float
                target (exit) price
            initial_stop : float
                price to set hard stop
            stop_limit: bool
                Flag to indicate if the stop should be STOP or STOP LIMIT (default False=STOP)
            trail_stop_at : float
                price at which to start trailing the stop
            trail_stop_by : float
                % of trailing stop distance from current price
            fillorkill: bool
                fill entire quantiry or none at all
            iceberg: bool
                is this an iceberg (hidden) order
            tif: str
                time in force (DAY, GTC, IOC, GTD). default is ``DAY``
        """
        if signal.upper() == "EXIT" or signal.upper() == "FLATTEN":
            position = self.get_positions(symbol)
            if position['position'] == 0:
                return

            kwargs['symbol']    = symbol
            kwargs['quantity']  = abs(position['position'])
            kwargs['direction'] = "BUY" if position['position'] < 0 else "SELL"

            # print("EXIT", kwargs)

            try: self.record(position=0)
            except: pass

            if not self.backtest:
                self._create_order(**kwargs)

        else:
            if quantity == 0:
                return

            kwargs['symbol']    = symbol
            kwargs['quantity']  = abs(quantity)
            kwargs['direction'] = signal.upper()

            # print(signal.upper(), kwargs)

            # record
            try:
                quantity = -quantity if kwargs['direction'] == "BUY" else quantity
                self.record(position=quantity)
            except:
                pass

            if not self.backtest:
                self._create_order(**kwargs)


    # ---------------------------------------
    def cancel_order(self, orderId):
        """ Cancels a un-filled order

        Parameters:
            orderId : int
                Order ID
        """
        self._cancel_order(orderId)

    # ---------------------------------------
    def record(self, *args, **kwargs):
        """Records data for later analysis.
        Values will be logged to the file specified via
        ``--output [file]`` (along with bar data) as
        csv/pickle/h5 file.

        Call from within your strategy:
        ``self.record(key=value)``

        :Parameters:
            ** kwargs : mixed
                The names and values to record

        """
        if self.record_output:
            try: self.datastore.record(self.record_ts, *args, **kwargs)
            except: pass


    # ---------------------------------------
    def sms(self, text):
        """Sends an SMS message.
        Relies on properly setting up an SMS provider (refer to the
        SMS section of the documentation for more information about this)

        Call from within your strategy:
        ``self.sms("message text")``

        :Parameters:
            text : string
                The body of the SMS message to send

        """
        logging.info("SMS: "+str(text))
        sms.send_text(self.name +': '+ str(text), self.sms_numbers)

    # ---------------------------------------
    def _caller(self, caller):
        stack = [x[3] for x in inspect.stack()][1:-1]
        return caller in stack

    # ---------------------------------------
    def _book_handler(self, book):
        symbol = book['symbol']
        del book['symbol']
        del book['kind']

        self.books[symbol] = book
        self.on_orderbook(self.get_instrument(symbol))


    # ---------------------------------------
    def _quote_handler(self, quote):
        del quote['kind']
        self.quotes[quote['symbol']] = quote
        self.on_quote(self.get_instrument(quote))

    # ---------------------------------------
    def _tick_handler(self, tick, stale_tick=False):
        self._cancel_expired_pending_orders()

        # initial value
        if self.record_ts is None:
            self.record_ts = tick.index[0]

        if self.resolution[-1] not in ("S", "K", "V"):
            self.ticks = self._update_window(self.ticks, tick, window=self.tick_window)
        else:
            self.ticks = self._update_window(self.ticks, tick)
            bars = tools.resample(self.ticks, self.resolution)

            if len(bars.index) > self.tick_bar_count > 0 or stale_tick:
                self.record_ts = tick.index[0]
                self._bar_handler(bars)

                periods = int("".join([s for s in self.resolution if s.isdigit()]))
                self.ticks = self.ticks[-periods:]

            self.tick_bar_count = len(bars)

            # record tick bars
            self.record(bars[-1:])

        if not stale_tick:
            self.on_tick(self.get_instrument(tick))


    # ---------------------------------------
    def _bar_handler(self, bar):

        is_tick_or_volume_bar = False
        handle_bar  = True

        if self.resolution[-1] in ("S", "K", "V"):
            is_tick_or_volume_bar = True
            handle_bar = self._caller("_tick_handler")

        # drip is also ok
        handle_bar = handle_bar or self._caller("drip")

        if is_tick_or_volume_bar:
            # just add a bar (used by tick bar bandler)
            self.bars = self._update_window(self.bars, bar, window=self.bar_window)
        else:
            # add the bar and resample to resolution
            self.bars = self._update_window(self.bars, bar, window=self.bar_window, resolution=self.resolution)

        # new bar?
        this_bar_hash = abs(hash(str(self.bars.index.values[-1]))) % (10 ** 8)
        newbar = (self.bar_hash != this_bar_hash)
        self.bar_hash = this_bar_hash

        if newbar and handle_bar:
            self.record_ts = bar.index[0]
            self.on_bar(self.get_instrument(bar))

            if self.resolution[-1] not in ("S", "K", "V"):
                self.record(bar)


    # ---------------------------------------
    def _update_window(self, df, data, window=None, resolution=None):
        if df is None:
            df = data
        else:
            df = df.append(data)

        if resolution is not None:
            try:
                tz = str(df.index.tz)
            except:
                tz = None
            df = tools.resample(df, resolution=resolution, tz=tz)

        if window is None:
            return df

        return df[-window:]

    # ---------------------------------------
    # signal logging methods
    # ---------------------------------------
    def _add_signal_history(self, df, symbol):
        """ Initilize signal history """
        if symbol not in self.signals.keys() or len(self.signals[symbol]) == 0:
            self.signals[symbol] = [nan]*len(df)
        else:
            self.signals[symbol].append(nan)

        self.signals[symbol] = self.signals[symbol][-len(df):]
        df.loc[-len(self.signals[symbol]):, 'signal'] = self.signals[symbol]

        return df

    def _log_signal(self, symbol, signal):
        """ Log signal

        :Parameters:
            symbol : string
                instruments symbol
            signal : integer
                signal identifier (1, 0, -1)

        """
        self.signals[symbol][-1] = signal
コード例 #2
0
class Algo(Broker):
    """Algo class initilizer (sub-class of Broker)

    :Parameters:

        instruments : list
            List of IB contract tuples
        resolution : str
            Desired bar resolution (using pandas resolution: 1T, 1H, etc). Use K for tick bars.
        tick_window : int
            Length of tick lookback window to keep. Defaults to 1
        bar_window : int
            Length of bar lookback window to keep. Defaults to 100
        timezone : str
            Convert IB timestamps to this timezone (eg. US/Central). Defaults to UTC
        preload : str
            Preload history when starting algo (using pandas resolution: 1H, 1D, etc). Use K for tick bars.
        blotter : str
            Log trades to MySQL server used by this Blotter (default is "auto detect")

    """

    __metaclass__ = ABCMeta

    def __init__(self, instruments, resolution, \
        tick_window=1, bar_window=100, timezone="UTC", preload=None, \
        blotter=None, **kwargs):

        self.name = str(self.__class__).split('.')[-1].split("'")[0]

        # assign algo params
        self.bars = pd.DataFrame()
        self.ticks = pd.DataFrame()
        self.quotes = {}
        self.tick_count = 0
        self.tick_bar_count = 0
        self.bar_count = 0
        self.bar_hash = 0

        self.tick_window = tick_window if tick_window > 0 else 1
        self.bar_window = bar_window if bar_window > 0 else 100
        self.resolution = resolution.replace("MIN", "T")
        self.timezone = timezone
        self.preload = preload

        self.backtest = args.backtest
        self.backtest_start = args.start
        self.backtest_end = args.end

        # -----------------------------------
        self.sms_numbers = [] if args.sms is None else args.sms
        self.trade_log_dir = args.log
        self.blotter_name = args.blotter if args.blotter is not None else blotter
        self.record_output = args.output

        # -----------------------------------
        # load blotter settings && initilize Blotter
        self.load_blotter_args(args.blotter)
        self.blotter = Blotter(**self.blotter_args)

        # -----------------------------------
        # initiate broker/order manager
        super().__init__(instruments, ibclient=int(args.ibclient), \
            ibport=int(args.ibport), ibserver=str(args.ibserver))

        # -----------------------------------
        # signal collector
        self.signals = {}
        for sym in self.symbols:
            self.signals[sym] = pd.DataFrame()

        # -----------------------------------
        # initilize output file
        self.record_ts = None
        if self.record_output:
            self.datastore = tools.DataStore(args.output)

        # -----------------------------------
        # initiate strategy
        self.on_start()

    # ---------------------------------------
    def run(self):
        """Starts the algo

        Connects to the Blotter, processes market data and passes
        tick data to the ``on_tick`` function and bar data to the
        ``on_bar`` methods.
        """

        # -----------------------------------
        # backtest mode?
        if self.backtest:
            if self.output is None:
                print(
                    "ERROR: Must provide an output file for backtesting mode")
                sys.exit(0)
            if self.backtest_start is None:
                print("ERROR: Must provide start date for backtesting mode")
                sys.exit(0)
            if self.backtest_end is None:
                self.backtest_end = datetime.now().strftime(
                    '%Y-%m-%d %H:%M:%S.%f')

            # backtest history
            self.blotter.drip(symbols=self.symbols,
                              start=self.backtest_start,
                              end=self.backtest_end,
                              resolution=self.resolution,
                              tz=self.timezone,
                              quote_handler=self._quote_handler,
                              tick_handler=self._tick_handler,
                              bar_handler=self._bar_handler)

        # -----------------------------------
        # live data mode
        else:
            # preload history
            if self.preload is not None:
                try:  # dbskip may be active
                    self.bars = self.blotter.history(
                        symbols=self.symbols,
                        start=tools.backdate(self.preload),
                        resolution=self.resolution,
                        tz=self.timezone)
                except:
                    pass
                # print(self.bars)

            # add instruments to blotter in case they do not exist
            self.blotter.register(self.instruments)

            # listen for RT data
            self.blotter.listen(symbols=self.symbols,
                                tz=self.timezone,
                                quote_handler=self._quote_handler,
                                tick_handler=self._tick_handler,
                                bar_handler=self._bar_handler)

    # ---------------------------------------
    @abstractmethod
    def on_start(self):
        """
        Invoked once when algo starts. Used for when the strategy
        needs to initialize parameters upon starting.

        """
        # raise NotImplementedError("Should implement on_start()")
        pass

    # ---------------------------------------
    @abstractmethod
    def on_quote(self, instrument):
        """
        Invoked on every quote captured for the selected instrument.
        This is where you'll write your strategy logic for quote events.

        :Parameters:

            symbol : string
                `Instruments Object <#instrument-api>`_

        """
        # raise NotImplementedError("Should implement on_quote()")
        pass

    # ---------------------------------------
    @abstractmethod
    def on_tick(self, instrument):
        """
        Invoked on every tick captured for the selected instrument.
        This is where you'll write your strategy logic for tick events.

        :Parameters:

            symbol : string
                `Instruments Object <#instrument-api>`_

        """
        # raise NotImplementedError("Should implement on_tick()")
        pass

    # ---------------------------------------
    @abstractmethod
    def on_bar(self, instrument):
        """
        Invoked on every tick captured for the selected instrument.
        This is where you'll write your strategy logic for tick events.

        :Parameters:

            instrument : object
                `Instruments Object <#instrument-api>`_

        """
        # raise NotImplementedError("Should implement on_bar()")
        pass

    # ---------------------------------------
    @abstractmethod
    def on_fill(self, instrument, order):
        """
        Invoked on every order fill for the selected instrument.
        This is where you'll write your strategy logic for fill events.

        :Parameters:

            instrument : object
                `Instruments Object <#instrument-api>`_
            order : object
                Filled order data

        """
        # raise NotImplementedError("Should implement on_fill()")
        pass

    # ---------------------------------------
    def get_instrument(self, symbol):
        """
        A string subclass that provides easy access to misc
        symbol-related methods and information using shorthand.
        Refer to the `Instruments API <#instrument-api>`_
        for available methods and properties

        Call from within your strategy:
        ``instrument = self.get_instrument("SYMBOL")``

        :Parameters:

            symbol : string
                instrument symbol

        """
        instrument = Instrument(self._getsymbol_(symbol))
        instrument._set_parent(self)
        return instrument

    # ---------------------------------------
    def get_history(self, symbols, start, end=None, resolution="1T", tz="UTC"):
        """Get historical market data.
        Connects to Blotter and gets historical data from storage

        :Parameters:
            symbols : list
                List of symbols to fetch history for
            start : datetime / string
                History time period start date (datetime or YYYY-MM-DD[ HH:MM[:SS]] string)

        :Optional:
            end : datetime / string
                History time period end date (datetime or YYYY-MM-DD[ HH:MM[:SS]] string)
            resolution : string
                History resoluton (Pandas resample, defaults to 1T/1min)
            tz : string
                History timezone (defaults to UTC)

        :Returns:
            history : pd.DataFrame
                Pandas DataFrame object with historical data for all symbols
        """
        return self.blotter.history(symbols, start, end, resolution, tz)

    # ---------------------------------------
    # shortcuts to broker._create_order
    # ---------------------------------------
    def order(self, signal, symbol, quantity=0, **kwargs):
        """ Send an order for the selected instrument

        :Parameters:

            direction : string
                Order Type (BUY/SELL, EXIT/FLATTEN)
            symbol : string
                instrument symbol
            quantity : int
                Order quantiry

        :Optional:

            limit_price : float
                In case of a LIMIT order, this is the LIMIT PRICE
            expiry : int
                Cancel this order if not filled after *n* seconds (default 60 seconds)
            order_type : string
                Type of order: Market (default), LIMIT (default when limit_price is passed), MODIFY (required passing or orderId)
            orderId : int
                If modifying an order, the order id of the modified order
            target : float
                target (exit) price
            initial_stop : float
                price to set hard stop
            trail_stop_at : float
                price at which to start trailing the stop
            trail_stop_by : float
                % of trailing stop distance from current price
            ticksize : float
                If using traling stop, pass the tick size for the instruments so you won't try to buy ES at 2200.128230 :)
            fillorkill: bool
                fill entire quantiry or none at all
            iceberg: bool
                is this an iceberg (hidden) order
        """
        if signal.upper() == "EXIT" or signal.upper() == "FLATTEN":
            position = self.get_positions(symbol)
            if position['position'] == 0:
                return

            kwargs['symbol'] = symbol
            kwargs['quantity'] = abs(position['position'])
            kwargs['direction'] = "BUY" if position['position'] < 0 else "SELL"

            # print("EXIT", kwargs)

            try:
                self.record(position=0)
            except:
                pass

            if not self.backtest:
                self._create_order(**kwargs)

        else:
            if quantity == 0:
                return

            kwargs['symbol'] = symbol
            kwargs['quantity'] = abs(quantity)
            kwargs['direction'] = signal.upper()

            # print(signal.upper(), kwargs)

            # record
            try:
                quantity = -quantity if kwargs[
                    'direction'] == "BUY" else quantity
                self.record(position=quantity)
            except:
                pass

            if not self.backtest:
                self._create_order(**kwargs)

    # ---------------------------------------
    def cancel_order(self, orderId):
        """ Cancels a un-filled order

        Parameters:
            orderId : int
                Order ID
        """
        self._cancel_order(orderId)

    # ---------------------------------------
    def record(self, *args, **kwargs):
        """Records data for later analysis.
        Values will be logged to the file specified via
        ``--output [file]`` (along with bar data) as
        csv/pickle/h5 file.

        Call from within your strategy:
        ``self.record(key=value)``

        :Parameters:
            ** kwargs : mixed
                The names and values to record

        """
        if self.record_output:
            self.datastore.record(self.record_ts, *args, **kwargs)

    # ---------------------------------------
    def sms(self, text):
        """Sends an SMS message.
        Relies on properly setting up an SMS provider (refer to the
        SMS section of the documentation for more information about this)

        Call from within your strategy:
        ``self.sms("message text")``

        :Parameters:
            text : string
                The body of the SMS message to send

        """
        logging.info("SMS: " + str(text))
        sms.send_text(self.name + ': ' + str(text), self.sms_numbers)

    # ---------------------------------------
    def _caller(self, caller):
        stack = [x[3] for x in inspect.stack()][1:-1]
        return caller in stack

    # ---------------------------------------
    def _quote_handler(self, quote):
        # self._cancel_expired_pending_orders()
        self.quotes[quote['symbol']] = quote
        self.on_quote(self.get_instrument(quote))

    # ---------------------------------------
    def _tick_handler(self, tick):
        self._cancel_expired_pending_orders()

        if "K" not in self.resolution:
            self.ticks = self._update_window(self.ticks,
                                             tick,
                                             window=self.tick_window)
        else:
            self.ticks = self._update_window(self.ticks, tick)
            bar = tools.resample(self.ticks, self.resolution)
            if len(bar) > self.tick_bar_count > 0:
                self.record_ts = tick.index[0]
                self._bar_handler(bar)

                periods = int("".join(
                    [s for s in self.resolution if s.isdigit()]))
                self.ticks = self.ticks[-periods:]

            self.tick_bar_count = len(bar)

            # record tick bar
            self.record(bar)

        self.on_tick(self.get_instrument(tick))

    # ---------------------------------------
    def _bar_handler(self, bar):

        is_tick_bar = False
        handle_bar = True

        if "K" in self.resolution:
            if self._caller("_tick_handler"):
                is_tick_bar = True
                handle_bar = True
            else:
                is_tick_bar = True
                handle_bar = False

        if is_tick_bar:
            # just add a bar (used by tick bar bandler)
            self.bars = self._update_window(self.bars,
                                            bar,
                                            window=self.bar_window)
        else:
            # add the bar and resample to resolution
            self.bars = self._update_window(self.bars,
                                            bar,
                                            window=self.bar_window,
                                            resolution=self.resolution)

        # new bar?
        this_bar_hash = abs(hash(str(self.bars.index.values[-1]))) % (10**8)
        newbar = (self.bar_hash != this_bar_hash)
        self.bar_hash = this_bar_hash

        if newbar & handle_bar:
            self.record_ts = bar.index[0]
            self.on_bar(self.get_instrument(bar))

            if "K" not in self.resolution:
                self.record(bar)

    # ---------------------------------------
    def _update_window(self, df, data, window=None, resolution=None):
        if df is None:
            df = data
        else:
            df = df.append(data)

        if resolution is not None:
            try:
                tz = str(df.index.tz)
            except:
                tz = None
            df = tools.resample(df, resolution=resolution, tz=tz)

        if window is None:
            return df

        return df[-window:]

    # ---------------------------------------
    # signal logging methods
    # ---------------------------------------
    def _add_signal_history(self, df, symbol):
        """ Initilize signal history """
        if symbol not in self.signals.keys() or len(self.signals[symbol]) == 0:
            self.signals[symbol] = [nan] * len(df)
        else:
            self.signals[symbol].append(nan)

        self.signals[symbol] = self.signals[symbol][-len(df):]
        df.loc[-len(self.signals[symbol]):, 'signal'] = self.signals[symbol]

        return df

    def _log_signal(self, symbol, signal):
        """ Log signal

        :Parameters:
            symbol : string
                instruments symbol
            signal : integer
                signal identifier (1, 0, -1)

        """
        self.signals[symbol][-1] = signal