{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2018-11-07T19:33:34.086084Z",
     "start_time": "2018-11-07T19:33:32.723662Z"
    },
    "run_control": {
     "frozen": false,
     "read_only": false
    }
   },
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "import pandas as pd\n",
    "import sys\n",
    "from collections import defaultdict\n",
    "from pprint import pformat\n",
    "import math\n",
    "\n",
    "from pyqstrat.pq_utils import *\n",
    "from pyqstrat.marketdata import *\n",
    "from pyqstrat.evaluator import compute_return_metrics, display_return_metrics, plot_return_metrics\n",
    "from pyqstrat.account import Account"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2018-11-07T19:33:34.168783Z",
     "start_time": "2018-11-07T19:33:34.090121Z"
    },
    "code_folding": [],
    "run_control": {
     "frozen": false,
     "read_only": false
    },
    "scrolled": false
   },
   "outputs": [],
   "source": [
    "class Strategy:\n",
    "    def __init__(self, contracts, marketdata_collection, starting_equity = 1.0e6, calc_frequency = 'D', additional_order_dates = None, additional_trade_dates = None):\n",
    "        '''\n",
    "        Args:\n",
    "            contracts (list of Contract): The contracts we will potentially trade\n",
    "            starting_equity (float, optional): Starting equity in Strategy currency.  Default 1.e6\n",
    "            calc_frequency (str, optional): How often P&L is calculated.  Default is 'D' for daily\n",
    "            additional_account_dates (np.array of np.datetime64, optional): If present, we check for orders on these dates.  Default None\n",
    "            additional_tradedates (np.array of np.datetime64, optional): If present, we check for trades on these dates.  Default None\n",
    "        '''\n",
    "        self.name = None\n",
    "        date_list = []\n",
    "        if additional_order_dates is not None: date_list.append(additional_order_dates)\n",
    "        if additional_trade_dates is not None: date_list.append(additional_trade_dates)\n",
    "        self.additional_order_dates = additional_order_dates\n",
    "        self.additional_trade_dates = additional_trade_dates\n",
    "        if len(date_list): marketdata_collection.add_dates(np.concatenate(date_list))\n",
    "        self.dates = marketdata_collection.dates()\n",
    "        self.account = Account(contracts, marketdata_collection, starting_equity, calc_frequency)\n",
    "        self.symbols = [contract.symbol for contract in contracts]\n",
    "        self.indicators = {}\n",
    "        self.indicator_values = defaultdict(dict)\n",
    "        self.signals = {}\n",
    "        self.signal_values = defaultdict(dict)\n",
    "        self.rules = {}\n",
    "        self.rule_signals = {}\n",
    "        self.market_sims = {}\n",
    "        self._trades = defaultdict(list)\n",
    "        self._orders = []\n",
    "        \n",
    "    def add_indicator(self, name, indicator_function):\n",
    "        '''\n",
    "        Args:\n",
    "            name: Name of the indicator\n",
    "            indicator_function:  A function taking a MarketData object and returning a numpy array\n",
    "              containing indicator values.  The return array must have the same length as the MarketData object\n",
    "        '''\n",
    "        self.indicators[name] = indicator_function\n",
    "        \n",
    "    def add_signal(self, name, signal_function):\n",
    "        '''\n",
    "        Args:\n",
    "            name: Name of the signal\n",
    "            signal_function:  A function taking a MarketData object and a dictionary of indicator value arrays as input and returning a numpy array\n",
    "              containing signal values.  The return array must have the same length as the MarketData object\n",
    "        '''\n",
    "        self.signals[name] = signal_function\n",
    "        \n",
    "    def add_rule(self, name, rule_function, signal_name, sig_true_values = None):\n",
    "        '''Add a trading rule\n",
    "        \n",
    "        Args:\n",
    "            name (str): Name of the trading rule\n",
    "            rule_function (function): A trading rule function that returns a list of Orders\n",
    "            signal_name (str): The strategy will call the trading rule function when the signal with this name matches sig_true_values\n",
    "            sig_true_values (numpy array, optional): If the signal value at a bar is equal to one of these values, the Strategy will call the trading rule function.  \n",
    "                Default [TRUE]\n",
    "        '''\n",
    "        if sig_true_values is None: sig_true_values = [True]\n",
    "        self.rule_signals[name] = (signal_name, sig_true_values)\n",
    "        self.rules[name] = rule_function\n",
    "        \n",
    "    def add_market_sim(self, market_sim_function, symbols = None):\n",
    "        '''Add a market simulator.  A market simulator takes a list of Orders as input and returns a list of Trade objects.\n",
    "        \n",
    "        Args:\n",
    "            market_sim_function: A function that takes a list of Orders and MarketData as input and returns a list of Trade objects\n",
    "            symbols: A list of the symbols that this market_sim_function applies to. If None (default) it will apply to all symbols\n",
    "        '''\n",
    "        if symbols is None: symbols = self.symbols\n",
    "        for symbol in symbols: self.market_sims[symbol] = market_sim_function\n",
    "        \n",
    "    def run_indicators(self, indicator_names = None, symbols = None):\n",
    "        '''Calculate values of the indicators specified and store them.\n",
    "        \n",
    "        Args:\n",
    "            indicator_names: List of indicator names.  If None (default) run all indicators\n",
    "            symbols: List of symbols to run these indicators for.  If None (default) use all symbols\n",
    "        '''\n",
    "        if indicator_names is None: indicator_names = self.indicators.keys()\n",
    "        if symbols is None: symbols = self.symbols\n",
    "            \n",
    "        for indicator_name in indicator_names:\n",
    "            indicator_function = self.indicators[indicator_name]\n",
    "            for symbol in symbols:\n",
    "                marketdata = self.account.marketdata[symbol]\n",
    "                self.indicator_values[symbol][indicator_name] = series_to_array(indicator_function(marketdata))\n",
    "                \n",
    "    def run_signals(self, signal_names = None, symbols = None):\n",
    "        '''Calculate values of the signals specified and store them.\n",
    "        \n",
    "        Args:\n",
    "            signal_names: List of signal names.  If None (default) run all signals\n",
    "            symbols: List of symbols to run these signals for.  If None (default) use all symbols\n",
    "        '''\n",
    "        if signal_names is None: signal_names = self.signals.keys()\n",
    "        if symbols is None: symbols = self.symbols\n",
    "        \n",
    "        for signal_name in signal_names:\n",
    "            signal_function = self.signals[signal_name]\n",
    "            for symbol in symbols:\n",
    "                marketdata = self.account.marketdata[symbol]\n",
    "                self.signal_values[symbol][signal_name] = series_to_array(signal_function(marketdata, self.indicator_values[symbol]))\n",
    "                \n",
    "    def run_rules(self, rule_names = None, symbols = None, start_date = None, end_date = None):\n",
    "        '''Run trading rules.\n",
    "        \n",
    "        Args:\n",
    "            rule_names: List of rule names.  If None (default) run all rules\n",
    "            symbols: List of symbols to run these signals for.  If None (default) use all symbols\n",
    "            start_date: Run rules starting from this date. Default None \n",
    "            end_date: Don't run rules after this date.  Default None\n",
    "        '''\n",
    "        start_date, end_date = str2date(start_date), str2date(end_date)\n",
    "        dates, orders_iter, trades_iter = self._get_iteration_indices(rule_names, symbols, start_date, end_date)\n",
    "        # Now we know which rules, symbols need to be applied for each iteration, go through each iteration and apply them\n",
    "        # in the same order they were added to the strategy\n",
    "        for i, tup_list in enumerate(orders_iter):\n",
    "            self._check_for_trades(i, trades_iter[i])\n",
    "            self._check_for_orders(i, tup_list)\n",
    "        \n",
    "    def _get_iteration_indices(self, rule_names = None, symbols = None, start_date = None, end_date = None):\n",
    "        '''\n",
    "        >>> class MockStrat:\n",
    "        ...    def __init__(self):\n",
    "        ...        self.dates = dates\n",
    "        ...        self.account = self\n",
    "        ...        self.additional_order_dates = None\n",
    "        ...        self.additional_trade_dates = np.array(['2018-01-03'], dtype = 'M8[D]')\n",
    "        ...        self.rules = {'rule_a' : rule_a, 'rule_b' : rule_b}\n",
    "        ...        self.marketdata = {'IBM' : self, 'AAPL' : self}\n",
    "        ...        self.market_sims = {'IBM' : market_sim_ibm, 'AAPL' : market_sim_aapl}\n",
    "        ...        self.rule_signals = {'rule_a' : ('sig_a', [1]), 'rule_b' : ('sig_b', [1, -1])}\n",
    "        ...        self.signal_values = {'IBM' : {'sig_a' : np.array([0., 1., 1.]), 'sig_b' : np.array([0., 0., 0.])},\n",
    "        ...                              'AAPL' : {'sig_a' : np.array([0., 0., 0.]), 'sig_b' : np.array([0., -1., -1])}}\n",
    "        ...        self.indicator_values = {'IBM' : None, 'AAPL' : None}\n",
    "        >>>\n",
    "        >>> def market_sim_aapl(): pass\n",
    "        >>> def market_sim_ibm(): pass\n",
    "        >>> def rule_a(): pass\n",
    "        >>> def rule_b(): pass\n",
    "        >>> dates = np.array(['2018-01-01', '2018-01-02', '2018-01-03'], dtype = 'M8[D]')\n",
    "        >>> rule_names = ['rule_a', 'rule_b']\n",
    "        >>> symbols = ['IBM', 'AAPL']\n",
    "        >>> start_date = np.datetime64('2018-01-01')\n",
    "        >>> end_date = np.datetime64('2018-02-05')\n",
    "        >>> dates, orders_iter, trades_iter = Strategy._get_iteration_indices(MockStrat(), rule_names, symbols, start_date, end_date)\n",
    "        >>> assert(len(trades_iter[1]) == 0)\n",
    "        >>> assert(trades_iter[2][1][1] == \"AAPL\")\n",
    "        >>> assert(trades_iter[2][2][1] == \"IBM\")\n",
    "        >>> assert(len(orders_iter[0]) == 0)\n",
    "        >>> assert(len(orders_iter[1]) == 2)\n",
    "        >>> assert(orders_iter[1][0][1] == \"IBM\")\n",
    "        >>> assert(orders_iter[1][1][1] == \"AAPL\")\n",
    "        >>> assert(len(orders_iter[2]) == 0)\n",
    "        '''\n",
    "        start_date, end_date = str2date(start_date), str2date(end_date)\n",
    "        if rule_names is None: rule_names = self.rules.keys()\n",
    "        if symbols is None: symbols = self.symbols\n",
    "\n",
    "        num_dates = len(self.dates)\n",
    "\n",
    "        orders_iter = [[] for x in range(num_dates)]\n",
    "        trades_iter = [[] for x in range(num_dates)]\n",
    "\n",
    "        for rule_name in rule_names:\n",
    "            rule_function = self.rules[rule_name]\n",
    "            for symbol in symbols:\n",
    "                marketdata = self.account.marketdata[symbol]\n",
    "                market_sim = self.market_sims[symbol]\n",
    "                signal_name = self.rule_signals[rule_name][0]\n",
    "                sig_true_values = self.rule_signals[rule_name][1]\n",
    "                sig_values = self.signal_values[symbol][signal_name]\n",
    "                dates = marketdata.dates\n",
    "\n",
    "                null_value = False if sig_values.dtype == np.dtype('bool') else np.nan\n",
    "                if start_date: sig_values[0:np.searchsorted(dates, start_date)] = null_value\n",
    "                if end_date:   sig_values[np.searchsorted(dates, end_date):] = null_value\n",
    "\n",
    "                indices = np.nonzero(np.isin(sig_values, sig_true_values))[0]\n",
    "\n",
    "                if self.additional_order_dates is not None:\n",
    "                    additional_indices = np.searchsorted(self.dates, self.additional_order_dates)\n",
    "                    indices = np.sort(np.unique(np.concatenate([indices, additional_indices])))\n",
    "\n",
    "                if len(indices) and indices[-1] == len(sig_values) -1: indices = indices[:-1] # Don't run rules on last index since we cannot fill any orders\n",
    "\n",
    "                indicator_values = self.indicator_values[symbol]\n",
    "                iteration_params = {'market_sim' : market_sim, 'indicator_values' : indicator_values, 'signal_values' : sig_values, 'marketdata' : marketdata}\n",
    "                for idx in indices: orders_iter[idx].append((rule_function, symbol, iteration_params))\n",
    "\n",
    "                if self.additional_trade_dates is not None:\n",
    "                    trade_indices = np.sort(np.unique(np.searchsorted(self.dates, self.additional_trade_dates)))\n",
    "                    for idx in trade_indices: trades_iter[idx].append(([], symbol, iteration_params))\n",
    "\n",
    "            self.orders_iter = orders_iter\n",
    "            self.trades_iter = trades_iter # For debugging\n",
    "\n",
    "        return self.dates, orders_iter, trades_iter\n",
    "         \n",
    "    def _check_for_trades(self, i, tup_list):\n",
    "        for tup in tup_list:\n",
    "            try:\n",
    "                open_orders, symbol, params = tup\n",
    "                open_orders = self._sim_market(i, open_orders, symbol, params)\n",
    "                if len(open_orders): self.trades_iter[i + 1].append((open_orders, symbol, params))\n",
    "            except Exception as e:\n",
    "                raise type(e)(f'Exception: {str(e)} at rule: {type(tup[0])} symbol: {tup[1]} index: {i}').with_traceback(sys.exc_info()[2])\n",
    "                \n",
    "    def _check_for_orders(self, i, tup_list):\n",
    "        for tup in tup_list:\n",
    "            try:\n",
    "                rule_function, symbol, params = tup\n",
    "                open_orders = self._get_orders(i, rule_function, symbol, params)\n",
    "                self._orders += open_orders\n",
    "                if len(open_orders): self.trades_iter[i + 1].append((open_orders, symbol, params))\n",
    "            except Exception as e:\n",
    "                raise type(e)(f'Exception: {str(e)} at rule: {type(tup[0])} symbol: {tup[1]} index: {i}').with_traceback(sys.exc_info()[2])\n",
    "                    \n",
    "    def _get_orders(self, idx, rule_function, symbol, params):\n",
    "        indicator_values, signal_values, marketdata = (params['indicator_values'], params['signal_values'], params['marketdata'])\n",
    "        open_orders = rule_function(self, symbol, idx, self.dates[idx], marketdata, indicator_values, signal_values, self.account)\n",
    "        return open_orders\n",
    "        \n",
    "    def _sim_market(self, idx, open_orders, symbol, params):\n",
    "        '''\n",
    "        Keep iterating while we have open orders since they may get filled\n",
    "        TODO: For limit orders and trigger orders we can be smarter here and reduce indices like quantstrat does\n",
    "        '''\n",
    "        market_sim_function = params['market_sim']\n",
    "        trades = market_sim_function(self, open_orders, idx, self.dates[idx], self.account.marketdata[symbol])\n",
    "        if len(trades) == 0: return []\n",
    "        self._trades[symbol] += trades\n",
    "        self.account._add_trades(symbol, trades)\n",
    "        self.account.calc(idx)\n",
    "        open_orders = [order for order in open_orders if order.status == 'open']\n",
    "        return open_orders\n",
    "            \n",
    "    def df_data(self, symbols = None, add_pnl = True, start_date = None, end_date = None):\n",
    "        '''\n",
    "        Add indicators and signals to end of market data and return as a pandas dataframe.\n",
    "        \n",
    "        Args:\n",
    "            symbols: list of symbols to include.  All if set to None (default)\n",
    "            add_pnl: If True (default), include P&L columns in dataframe\n",
    "            start_date: string or numpy datetime64. Default None\n",
    "            end_date: string or numpy datetime64: Default None\n",
    "        '''\n",
    "        start_date, end_date = str2date(start_date), str2date(end_date)\n",
    "        if symbols is None: symbols = self.symbols\n",
    "        if not isinstance(symbols, list): symbols = [symbols]\n",
    "            \n",
    "        mds = []\n",
    "            \n",
    "        for symbol in symbols:\n",
    "            md = self.account.marketdata[symbol].df(start_date, end_date)\n",
    "            \n",
    "            md.insert(0, 'symbol', symbol)\n",
    "            if add_pnl: \n",
    "                df_pnl = self.account.df_pnl(symbol)\n",
    "                del df_pnl['symbol']\n",
    "\n",
    "            indicator_values = self.indicator_values[symbol]\n",
    "\n",
    "            for k in sorted(indicator_values.keys()):\n",
    "                name = k\n",
    "                if name in md.columns: name = name + '.ind' # if we have a market data column with the same name as the indicator\n",
    "                md.insert(len(md.columns), name, indicator_values[k])\n",
    "\n",
    "            signal_values = self.signal_values[symbol]\n",
    "\n",
    "            for k in sorted(signal_values.keys()):\n",
    "                name = k\n",
    "                if name in md.columns: name = name + '.sig'\n",
    "                md.insert(len(md.columns), name, signal_values[k])\n",
    "            \n",
    "            if add_pnl: md = pd.merge(md, df_pnl, left_index = True, right_index = True, how = 'left')\n",
    "            # Add counter column for debugging\n",
    "            md.insert(len(md.columns), 'i', np.arange(len(md)))\n",
    "            \n",
    "            mds.append(md)\n",
    "            \n",
    "        return pd.concat(mds)\n",
    "    \n",
    "    def marketdata(self, symbol):\n",
    "        '''Return MarketData object for this symbol'''\n",
    "        return self.account.marketdata[symbol]\n",
    "    \n",
    "    def trades(self, symbol = None, start_date = None, end_date = None):\n",
    "        '''Returns a list of trades with the given symbol and with trade date between (and including) start date and end date if they are specified.\n",
    "          If symbol is None trades for all symbols are returned'''\n",
    "        start_date, end_date = str2date(start_date), str2date(end_date)\n",
    "        return self.account.trades(symbol, start_date, end_date)\n",
    "    \n",
    "    def df_trades(self, symbol = None, start_date = None, end_date = None):\n",
    "        '''Returns a dataframe with data from trades with the given symbol and with trade date between (and including) start date and end date\n",
    "          if they are specified.  If symbol is None, trades for all symbols are returned'''\n",
    "        start_date, end_date = str2date(start_date), str2date(end_date)\n",
    "        return self.account.df_trades(symbol, start_date, end_date)\n",
    "    \n",
    "    def orders(self, symbol = None, start_date = None, end_date = None):\n",
    "        '''Returns a list of orders with the given symbol and with order date between (and including) start date and end date if they are specified.\n",
    "          If symbol is None orders for all symbols are returned'''\n",
    "        start_date, end_date = str2date(start_date), str2date(end_date)\n",
    "        return [order for order in self._orders if (symbol is None or order.symbol == symbol) and (\n",
    "            start_date is None or order.date >= start_date) and (end_date is None or order.date <= end_date)]\n",
    "    \n",
    "    def df_orders(self, symbol = None, start_date = None, end_date = None):\n",
    "        '''Returns a dataframe with data from orders with the given symbol and with order date between (and including) start date and end date\n",
    "          if they are specified.  If symbol is None, orders for all symbols are returned'''\n",
    "        start_date, end_date = str2date(start_date), str2date(end_date)\n",
    "        orders = self.orders(symbol, start_date, end_date)\n",
    "        df_orders = pd.DataFrame.from_records([(order.symbol, type(order).__name__, order.date, order.qty, order.params()) \n",
    "                                               for order in orders], columns = ['symbol', 'type', 'date', 'qty', 'params'])\n",
    "        return df_orders\n",
    "   \n",
    "    def df_pnl(self, symbol = None):\n",
    "        '''Returns a dataframe with P&L columns.  If symbol is set to None (default), sums up P&L across symbols'''\n",
    "        return self.account.df_pnl(symbol)\n",
    "    \n",
    "    def df_returns(self, symbol = None, sampling_frequency = 'D'):\n",
    "        '''Return a dataframe of returns and equity indexed by date.\n",
    "        \n",
    "        Args:\n",
    "            symbol: The symbol to get returns for.  If set to None (default), this returns the sum of PNL for all symbols\n",
    "            sampling_frequency: Downsampling frequency.  Default is None.  See pandas frequency strings for possible values\n",
    "        '''\n",
    "        pnl = self.df_pnl(symbol)[['equity']]\n",
    "        pnl.equity = pnl.equity.ffill()\n",
    "        pnl = pnl.resample(sampling_frequency).last()\n",
    "        pnl['ret'] = pnl.equity.pct_change()\n",
    "        return pnl\n",
    "    \n",
    "    def plot(self, symbols = None, md_columns = 'c', pnl_columns = 'equity', title = None, figsize = (20, 15), date_range = None, \n",
    "             date_format = None, sampling_frequency = None, trade_marker_properties = None, hspace = 0.15):\n",
    "        \n",
    "        '''Plot indicators, signals, trades, position, pnl\n",
    "        \n",
    "        Args:\n",
    "            symbols: List of symbols or None (default) for all symbols\n",
    "            md_columns: List of columns of market data to plot.  Default is 'c' for close price.  You can set this to 'ohlcv' if you want to plot\n",
    "             a candlestick of OHLCV data\n",
    "            pnl_columns: List of P&L columns to plot.  Default is 'equity'\n",
    "            title: Title of plot (None)\n",
    "            figsize: Figure size.  Default is (20, 15)\n",
    "            date_range: Tuple of strings or datetime64, e.g. (\"2018-01-01\", \"2018-04-18 15:00\") to restrict the graph.  Default None\n",
    "            date_format: Date format for tick labels on x axis.  If set to None (default), will be selected based on date range. See matplotlib date format strings\n",
    "            sampling_frequency: Downsampling frequency.  Default is None.  The graph may get too busy if you have too many bars of data, in which case you may want to \n",
    "                downsample before plotting.  See pandas frequency strings for possible values\n",
    "            trade_marker_properties: A dictionary of order reason code -> marker shape, marker size, marker color for plotting trades with different reason codes.\n",
    "              Default is None in which case the dictionary from the ReasonCode class is used\n",
    "            hspace: Height (vertical) space between subplots.  Default is 0.15\n",
    "        '''\n",
    "        date_range = strtup2date(date_range)\n",
    "        if symbols is None: symbols = self.symbols\n",
    "        if not isinstance(symbols, list): symbols = [symbols]\n",
    "        if not isinstance(md_columns, list): md_columns = [md_columns]\n",
    "        if not isinstance(pnl_columns, list): pnl_columns = [pnl_columns]\n",
    "        for symbol in symbols:\n",
    "            md = self.marketdata(symbol)\n",
    "            md_dates = md.dates\n",
    "            if md_columns == ['ohlcv']:\n",
    "                md_list = [OHLC('price', dates = md_dates, o = md.o, h = md.h, l = md.l, c = md.c, v = md.v, vwap = md.vwap)]\n",
    "            else:\n",
    "                md_list = [TimeSeries(md_column, dates = md_dates, values = getattr(md, md_column)) for md_column in md_columns]\n",
    "            indicator_list = [TimeSeries(indicator_name, dates = md_dates, values = self.indicator_values[symbol][indicator_name], line_type = '--'\n",
    "                                        ) for indicator_name in self.indicators.keys() if indicator_name in self.indicator_values[symbol]]\n",
    "            signal_list = [TimeSeries(signal_name, dates = md_dates, values = self.signal_values[symbol][signal_name]\n",
    "                                     ) for signal_name in self.signals.keys() if signal_name in self.signal_values[symbol]]\n",
    "            df_pnl_ = self.df_pnl(symbol)\n",
    "            pnl_list = [TimeSeries(pnl_column, dates = df_pnl_.index.values, values = df_pnl_[pnl_column].values) for pnl_column in pnl_columns]\n",
    "            if trade_marker_properties:\n",
    "                trade_sets = trade_sets_by_reason_code(self._trades[symbol], trade_marker_properties)\n",
    "            else:\n",
    "                trade_sets = trade_sets_by_reason_code(self._trades[symbol])\n",
    "            main_subplot = Subplot(indicator_list +  md_list + trade_sets, height_ratio = 0.5, ylabel = 'Indicators')\n",
    "            signal_subplot = Subplot(signal_list, ylabel = 'Signals', height_ratio = 0.167)\n",
    "            pnl_subplot = Subplot(pnl_list, ylabel = 'Equity', height_ratio = 0.167, log_y = True, y_tick_format = '${x:,.0f}')\n",
    "            position = df_pnl_.position.values\n",
    "            pos_subplot = Subplot([TimeSeries('position', dates = df_pnl_.index.values, values = position, plot_type = 'filled_line')], \n",
    "                                  ylabel = 'Position', height_ratio = 0.167)\n",
    "            plot = Plot([main_subplot, signal_subplot, pos_subplot, pnl_subplot], figsize = figsize,\n",
    "                                date_range = date_range, date_format = date_format, sampling_frequency = sampling_frequency, title = title, hspace = hspace)\n",
    "            plot.draw()\n",
    "            \n",
    "    def evaluate_returns(self, symbol = None, plot = True, float_precision = 4):\n",
    "        '''Returns a dictionary of common return metrics.\n",
    "        \n",
    "        Args:\n",
    "            symbol (str): Date frequency.  Default 'D' for daily so we downsample to daily returns before computing metrics\n",
    "            plot (bool): If set to True, display plots of equity, drawdowns and returns.  Default False\n",
    "            float_precision (float, optional): Number of significant figures to show in returns.  Default 4\n",
    "        '''\n",
    "        returns = self.df_returns(symbol)\n",
    "        ev = compute_return_metrics(returns.index.values, returns.ret.values, self.account.starting_equity)\n",
    "        display_return_metrics(ev.metrics(), float_precision = float_precision)\n",
    "        if plot: plot_return_metrics(ev.metrics())\n",
    "        return ev.metrics()\n",
    "    \n",
    "    def plot_returns(self, symbol = None):\n",
    "        '''Display plots of equity, drawdowns and returns for the given symbol or for all symbols if symbol is None (default)'''\n",
    "        if symbol is None:\n",
    "            symbols = self.symbols()\n",
    "        else:\n",
    "            symbols = [symbol]\n",
    "            \n",
    "        df_list = []\n",
    "            \n",
    "        for symbol in symbols:\n",
    "            df_list.append(self.df_returns(symbol))\n",
    "        \n",
    "        df = pd.concat(df_list, axis = 1)\n",
    "            \n",
    "        ev = compute_return_metrics(returns.index.values, returns.ret.values, self.account.starting_equity)\n",
    "        plot_return_metrics(ev.metrics())\n",
    "       \n",
    "    def __repr__(self):\n",
    "        return f'{pformat(self.indicators)} {pformat(self.rules)} {pformat(self.account)}'"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.7.2"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
