diff --git a/catalyst/exchange/exchange.py b/catalyst/exchange/exchange.py index 91fc6733..b1ad3fb9 100644 --- a/catalyst/exchange/exchange.py +++ b/catalyst/exchange/exchange.py @@ -1,5 +1,4 @@ import abc -import re from abc import ABCMeta, abstractmethod, abstractproperty from datetime import timedelta from time import sleep @@ -16,7 +15,7 @@ from catalyst.exchange.bundle_utils import get_start_dt, \ from catalyst.exchange.exchange_bundle import ExchangeBundle from catalyst.exchange.exchange_errors import MismatchingBaseCurrencies, \ InvalidOrderStyle, BaseCurrencyNotFoundError, SymbolNotFoundOnExchange, \ - InvalidHistoryFrequencyError, PricingDataNotLoadedError, \ + PricingDataNotLoadedError, \ NoDataAvailableOnExchange from catalyst.exchange.exchange_execution import ExchangeStopLimitOrder, \ ExchangeLimitOrder, ExchangeStopOrder @@ -53,9 +52,11 @@ class Exchange: @property def portfolio(self): """ - Return the Portfolio + The exchange portfolio - :return: + Returns + ------- + ExchangePortfolio """ if self._portfolio is None: self._portfolio = ExchangePortfolio( @@ -75,9 +76,16 @@ class Exchange: def is_open(self, dt): """ - Is the exchange open? - :param dt: - :return: + Is the exchange open + + Parameters + ---------- + dt: Timestamp + + Returns + ------- + bool + """ # TODO: implement for each exchange. return True @@ -90,7 +98,9 @@ class Exchange: The application will pause if the maximum requests per minute permitted by the exchange is exceeded. - :return boolean: + Returns + ------- + bool """ now = pd.Timestamp.utcnow() @@ -122,10 +132,16 @@ class Exchange: def get_symbol(self, asset): """ - Get the exchange specific symbol of the given asset. + The the exchange specific symbol of the specified market. + + Parameters + ---------- + asset: TradingPair + + Returns + ------- + str - :param asset: Asset - :return: symbol: str """ symbol = None @@ -143,17 +159,34 @@ class Exchange: """ Get a list of symbols corresponding to each given asset. - :param assets: Asset[] - :return: + Parameters + ---------- + assets: list[TradingPair] + + Returns + ------- + list[str] + """ symbols = [] - for asset in assets: symbols.append(self.get_symbol(asset)) return symbols def get_assets(self, symbols=None): + """ + The list of markets for the specified symbols. + + Parameters + ---------- + symbols: list[str] + + Returns + ------- + list[TradingPair] + + """ assets = [] if symbols is not None: @@ -168,9 +201,16 @@ class Exchange: def get_asset(self, symbol): """ - Find an Asset on the current exchange based on its Catalyst symbol - :param symbol: the [target]_[base] currency pair symbol - :return: Asset + The market for the specified symbol. + + Parameters + ---------- + symbol: str + + Returns + ------- + TradingPair + """ asset = None @@ -201,7 +241,6 @@ class Exchange: currency pair symbol. The universal symbol is contained in the 'symbol' attribute of each asset. - Notes ----- The sid of each asset is calculated based on a numeric hash of the @@ -210,8 +249,8 @@ class Exchange: This method can be overridden if an exchange offers equivalent data via its api. - """ + """ symbol_map = self.fetch_symbol_map() for exchange_symbol in symbol_map: asset = symbol_map[exchange_symbol] @@ -272,8 +311,10 @@ class Exchange: For each executed order found, create a transaction and apply to the Portfolio. - :return: - transactions: Transaction[] + Returns + ------- + list[Transaction] + """ transactions = list() if self.portfolio.open_orders: @@ -390,14 +431,20 @@ class Exchange: """ Get a series of field data for the specified candles. - :param candles: - :param start_dt: - :param end_dt: - :param field: - :param previous_value: - :return: - """ + Parameters + ---------- + candles: list[dict[str, float]] + start_dt: datetime + end_dt: datetime + data_frequency: str + field: str + previous_value: float + Returns + ------- + Series + + """ dates = [candle['last_traded'] for candle in candles] values = [candle[field] for candle in candles] series = pd.Series(values, index=dates) @@ -430,10 +477,11 @@ class Exchange: Parameters ---------- - assets : list of catalyst.data.Asset objects + assets : list[TradingPair] The assets whose data is desired. - end_dt: not applicable to cryptocurrencies + end_dt: datetime + The date of the last bar bar_count: int The number of bars desired. @@ -493,10 +541,11 @@ class Exchange: Parameters ---------- - assets : list of catalyst.data.Asset objects + assets : list[TradingPair] The assets whose data is desired. - end_dt: not applicable to cryptocurrencies + end_dt: datetime + The date of the last bar. bar_count: int The number of bars desired. @@ -518,9 +567,10 @@ class Exchange: Returns ------- - A dataframe containing the requested data. - """ + DataFrame + A dataframe containing the requested data. + """ freq, candle_size, unit, data_frequency = get_frequency( frequency, data_frequency ) @@ -591,7 +641,6 @@ class Exchange: Update the portfolio cash and position balances based on the latest ticker prices. - :return: """ log.debug('synchronizing portfolio with exchange {}'.format(self.name)) balances = self.get_balances() @@ -635,16 +684,20 @@ class Exchange: Parameters ---------- - asset : Asset + asset : TradingPair The asset that this order is for. + amount : int The amount of shares to order. If ``amount`` is positive, this is the number of shares to buy or cover. If ``amount`` is negative, this is the number of shares to sell or short. + limit_price : float, optional The limit price for the order. + stop_price : float, optional The stop price for the order. + style : ExecutionStyle, optional The execution style for the order. @@ -669,6 +722,7 @@ class Exchange: :class:`catalyst.finance.execution.ExecutionStyle` :func:`catalyst.api.order_value` :func:`catalyst.api.order_percent` + """ if amount == 0: log.warn('skipping order amount of 0') @@ -718,8 +772,12 @@ class Exchange: @abstractmethod def get_balances(self): """ - Retrieve wallet balances for the exchange - :return balances: A dict of currency => available balance + Retrieve wallet balances for the exchange. + + Returns + ------- + dict[TradingPair, float] + """ pass @@ -728,17 +786,25 @@ class Exchange: """ Place an order on the exchange. - :param asset : Asset - The asset that this order is for. - :param amount : int + Parameters + ---------- + asset: TradingPair + The target market. + + amount: float The amount of shares to order. If ``amount`` is positive, this is the number of shares to buy or cover. If ``amount`` is negative, this is the number of shares to sell or short. - :param style : ExecutionStyle - The execution style for the order. - :param is_buy: boolean + + is_buy: bool Is it a buy order? - :return: + + style: ExecutionStyle + + Returns + ------- + Order + """ pass @@ -798,19 +864,27 @@ class Exchange: """ Retrieve OHLCV candles for the given assets - :param freq: + Parameters + ---------- + freq: str The frequency alias per convention: http://pandas.pydata.org/pandas-docs/stable/timeseries.html#offset-aliases - :param assets: list[TradingPair] + + assets: list[TradingPair] The targeted assets. - :param bar_count: + + bar_count: int The number of bar desired. (default 1) - :param end_dt: datetime, optional + + end_dt: datetime, optional The last bar date. - :param start_dt: datetime, optional + + start_dt: datetime, optional The first bar date. - :return dict[TradingPair, dict[str, Object]]: OHLCV data + Returns + ------- + dict[TradingPair, dict[str, Object]] A dictionary of OHLCV candles. Each TradingPair instance is mapped to a list of dictionaries with this structure: open: float @@ -830,8 +904,14 @@ class Exchange: """ Retrieve current tick data for the given assets - :param assets: - :return: + Parameters + ---------- + assets: list[TradingPair] + + Returns + ------- + list[dict[str, float] + """ pass @@ -839,7 +919,6 @@ class Exchange: def get_account(self): """ Retrieve the account parameters. - :return: """ pass @@ -848,11 +927,15 @@ class Exchange: """ Retrieve the the orderbook for the given trading pair. - :param asset: TradingPair - :param order_type: str + Parameters + ---------- + asset: TradingPair + order_type: str The type of orders: bid, ask or all - :param limit + limit: int - :return: + Returns + ------- + list[dict[str, float] """ pass diff --git a/catalyst/exchange/exchange_algorithm.py b/catalyst/exchange/exchange_algorithm.py index 6650fda2..c05e559f 100644 --- a/catalyst/exchange/exchange_algorithm.py +++ b/catalyst/exchange/exchange_algorithm.py @@ -127,7 +127,13 @@ class ExchangeTradingAlgorithmBase(TradingAlgorithm): """ Creates a dictionary representing the state of the tracker. + Parameters + ---------- + start_dt: datetime + end_dt: datetime + Notes + ----- I rewrote this in an attempt to better control the stats. I don't want things to happen magically through complex logic pertaining to backtesting. @@ -296,6 +302,18 @@ class ExchangeTradingAlgorithmLive(ExchangeTradingAlgorithmBase): self.exchange.minute_reader = BcolzMinuteBarReader(root) def signal_handler(self, signal, frame): + """ + Handles the keyboard interruption signal. + + Parameters + ---------- + signal + frame + + Returns + ------- + + """ self.is_running = False if self._analyze is None: @@ -384,7 +402,11 @@ class ExchangeTradingAlgorithmLive(ExchangeTradingAlgorithmBase): """ We skip the entire performance tracker business and update the portfolio directly. - :return: + + Returns + ------- + ExchangePortfolio + """ # TODO: build cumulative portfolio return self.perf_tracker.get_portfolio(False) @@ -450,6 +472,17 @@ class ExchangeTradingAlgorithmLive(ExchangeTradingAlgorithmBase): ) def add_pnl_stats(self, period_stats): + """ + Save p&l stats. + + Parameters + ---------- + period_stats + + Returns + ------- + + """ starting = period_stats['starting_cash'] current = period_stats['portfolio_value'] appreciation = (current / starting) - 1 @@ -466,6 +499,17 @@ class ExchangeTradingAlgorithmLive(ExchangeTradingAlgorithmBase): save_algo_df(self.algo_namespace, 'pnl_stats', self.pnl_stats) def add_custom_signals_stats(self, period_stats): + """ + Save custom signals stats. + + Parameters + ---------- + period_stats + + Returns + ------- + + """ log.debug('adding custom signals stats: {}'.format(self.recorded_vars)) df = pd.DataFrame( data=[self.recorded_vars], @@ -477,6 +521,17 @@ class ExchangeTradingAlgorithmLive(ExchangeTradingAlgorithmBase): self.custom_signals_stats) def add_exposure_stats(self, period_stats): + """ + Save exposure stats. + + Parameters + ---------- + period_stats + + Returns + ------- + + """ data = dict( long_exposure=period_stats['long_exposure'], base_currency=period_stats['ending_cash'] @@ -493,6 +548,14 @@ class ExchangeTradingAlgorithmLive(ExchangeTradingAlgorithmBase): self.exposure_stats) def handle_data(self, data): + """ + Wrapper around the handle_data method of each algo. + + Parameters + ---------- + data + + """ if not self.is_running: return @@ -619,15 +682,16 @@ class ExchangeTradingAlgorithmLive(ExchangeTradingAlgorithmBase): The cumulative portfolio does not contain open orders but exchange portfolios do. - :param asset: TradingPair - :param amount: float - :param limit_price: float - :param stop_price: float - :param style: Style - :return order: Order + Parameters + ---------- + asset: TradingPair + amount: float + limit_price: float + stop_price: float + style: Style + order: Order The catalyst order object or None """ - amount, style = self._calculate_order(asset, amount, limit_price, stop_price, style) @@ -689,15 +753,53 @@ class ExchangeTradingAlgorithmLive(ExchangeTradingAlgorithmBase): 'get_open_orders. Use `asset` instead.') @api_method def get_open_orders(self, asset=None): + """Retrieve all of the current open orders. + + Parameters + ---------- + asset : Asset + If passed and not None, return only the open orders for the given + asset instead of all open orders. + + Returns + ------- + open_orders : dict[list[Order]] or list[Order] + If no asset is passed this will return a dict mapping Assets + to a list containing all the open orders for the asset. + If an asset is passed then this will return a list of the open + orders for this asset. + """ return self._get_open_orders(asset) @api_method def get_order(self, order_id, exchange_name): + """Lookup an order based on the order id returned from one of the + order functions. + + Parameters + ---------- + order_id : str + The unique identifier for the order. + + Returns + ------- + order : Order + The order object. + execution_price: float + The execution price per share of the order + """ exchange = self.exchanges[exchange_name] return exchange.get_order(order_id) @api_method def cancel_order(self, order_param, exchange_name): + """Cancel an open order. + + Parameters + ---------- + order_param : str or Order + The order_id or order object to cancel. + """ exchange = self.exchanges[exchange_name] order_id = order_param diff --git a/catalyst/exchange/exchange_bcolz.py b/catalyst/exchange/exchange_bcolz.py index 326745ac..10e85488 100644 --- a/catalyst/exchange/exchange_bcolz.py +++ b/catalyst/exchange/exchange_bcolz.py @@ -39,17 +39,25 @@ class BcolzExchangeBarReader(BcolzMinuteBarReader): return self._data_frequency def load_raw_arrays(self, fields, start_dt, end_dt, sids): + """ + Parameters + ---------- + fields : list of str + 'open', 'high', 'low', 'close', or 'volume' + start_dt: Timestamp + Beginning of the window range. + end_dt: Timestamp + End of the window range. + sids : list of int + The asset identifiers in the window. - # if self._data_frequency == 'minute': - # return super(BcolzExchangeBarReader, self) \ - # .load_raw_arrays(fields, start_dt, end_dt, sids) - # - # else: - # return self._load_daily_raw_arrays(fields, start_dt, end_dt, sids) - - return self._load_raw_arrays(fields, start_dt, end_dt, sids) - - def _load_raw_arrays(self, fields, start_dt, end_dt, sids): + Returns + ------- + list of np.ndarray + A list with an entry per field of ndarrays with shape + (minutes in range, sids) with a dtype of float64, containing the + values for the respective field over start and end dt range. + """ start_idx = self._find_position_of_minute(start_dt) end_idx = self._find_position_of_minute(end_dt) diff --git a/catalyst/exchange/exchange_bundle.py b/catalyst/exchange/exchange_bundle.py index ec499334..38de8e9e 100644 --- a/catalyst/exchange/exchange_bundle.py +++ b/catalyst/exchange/exchange_bundle.py @@ -59,7 +59,10 @@ class ExchangeBundle: """ Get a data writer object, either a new object or from cache - :return: BcolzMinuteBarReader or BcolzDailyBarReader + Returns + ------- + BcolzMinuteBarReader | BcolzDailyBarReader + """ if path is None: root = get_exchange_folder(self.exchange.name) @@ -88,7 +91,10 @@ class ExchangeBundle: """ Get a data writer object, either a new object or from cache - :return: BcolzMinuteBarWriter or BcolzDailyBarWriter + Returns + ------- + BcolzMinuteBarWriter | BcolzDailyBarWriter + """ root = get_exchange_folder(self.exchange.name) path = BUNDLE_NAME_TEMPLATE.format( @@ -144,13 +150,19 @@ class ExchangeBundle: If the data exists, the chunk ingestion is complete. If any data is missing we ingest the data. - :param assets: list[TradingPair] + Parameters + ---------- + assets: list[TradingPair] The assets is scope. - :param start_dt: + start_dt: datetime The chunk start date. - :param end_dt: + end_dt: datetime The chunk end date. - :return: list[TradingPair] + data_frequency: str + + Returns + ------- + list[TradingPair] The assets missing from the bundle """ reader = self.get_reader(data_frequency) @@ -164,13 +176,6 @@ class ExchangeBundle: return missing_assets def _write(self, data, writer, data_frequency): - """ - Write data to the writer - - :param df: - :param writer: - :return: - """ try: writer.write( data=data, @@ -195,6 +200,20 @@ class ExchangeBundle: ) def get_calendar_periods_range(self, start_dt, end_dt, data_frequency): + """ + Get a list of dates for the specified range. + + Parameters + ---------- + start_dt: datetime + end_dt: datetime + data_frequency: str + + Returns + ------- + list[datetime] + + """ return self.calendar.minutes_in_range(start_dt, end_dt) \ if data_frequency == 'minute' \ else self.calendar.sessions_in_range(start_dt, end_dt) @@ -204,13 +223,14 @@ class ExchangeBundle: """ Ingest a DataFrame of OHLCV data for a given market. - :param ohlcv_df: - :param data_frequency: - :param asset: - :param writer: - :param path: - :param empty_rows_behavior: - :return: + Parameters + ---------- + ohlcv_df: DataFrame + data_frequency: str + asset: TradingPair + writer: + empty_rows_behavior: str + """ if empty_rows_behavior is not 'ignore': nan_rows = ohlcv_df[ohlcv_df.isnull().T.any().T].index @@ -269,14 +289,16 @@ class ExchangeBundle: """ Merge a ctable bundle chunk into the main bundle for the exchange. - :param asset: TradingPair - :param data_frequency: str - :param period: str - :param writer: - :param empty_rows_behavior: str + Parameters + ---------- + asset: TradingPair + data_frequency: str + period: str + writer: + empty_rows_behavior: str Ensure that the bundle does not have any missing data. - :param cleanup: bool + cleanup: bool Remove the temp bundle directory after ingestion. :return: @@ -331,13 +353,19 @@ class ExchangeBundle: def get_adj_dates(self, start, end, assets, data_frequency): """ - Contains a date range to the trading availability of the specified pairs. + Contains a date range to the trading availability of the specified + markets. - :param start: - :param end: - :param assets: - :param data_frequency: - :return: + Parameters + ---------- + start: datetime + end: datetime + assets: list[TradingPair] + data_frequency: str + + Returns + ------- + datetime, datetime """ earliest_trade = None last_entry = None @@ -380,11 +408,17 @@ class ExchangeBundle: Split a price data request into chunks corresponding to individual bundles. - :param assets: - :param data_frequency: - :param start_dt: - :param end_dt: - :return: + Parameters + ---------- + assets: list[TradingPair] + data_frequency: str + start_dt: datetime + end_dt: datetime + + Returns + ------- + dict[TradingPair, list[dict(str, Object]]] + """ reader = self.get_reader(data_frequency) @@ -456,10 +490,12 @@ class ExchangeBundle: """ Determine if data is missing from the bundle and attempt to ingest it. - :param assets: - :param start_dt: - :param end_dt: - :return: + Parameters + ---------- + assets: list[TradingPair] + start_dt: datetime + end_dt: datetime + """ if start_dt is None: @@ -538,15 +574,18 @@ class ExchangeBundle: exclude_symbols=None, start=None, end=None, show_progress=True, environ=os.environ): """ + Inject data based on specified parameters. + + Parameters + ---------- + data_frequency: str + include_symbols: str + exclude_symbols: str + start: datetime + end: datetime + show_progress: bool + environ: - :param data_frequency: - :param include_symbols: - :param exclude_symbols: - :param start: - :param end: - :param show_progress: - :param environ: - :return: """ assets = self.get_assets(include_symbols, exclude_symbols) @@ -562,16 +601,22 @@ class ExchangeBundle: data_frequency, # type: str algo_end_dt=None # type: Timestamp ): - # type: (...) -> Dict[str, Series] """ Retrieve price data history, ingest missing data. - :param assets: - :param end_dt: - :param bar_count: - :param field: - :param data_frequency: - :return: + Parameters + ---------- + assets: list[TradingPair] + end_dt: datetime + bar_count: int + field: str + data_frequency: str + algo_end_dt: datetime + + Returns + ------- + Series + """ try: series = self.get_history_window_series( diff --git a/catalyst/exchange/exchange_data_portal.py b/catalyst/exchange/exchange_data_portal.py index f8d93288..6965fe24 100644 --- a/catalyst/exchange/exchange_data_portal.py +++ b/catalyst/exchange/exchange_data_portal.py @@ -1,16 +1,3 @@ -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - import abc from time import sleep @@ -238,6 +225,25 @@ class DataPortalExchangeLive(DataPortalExchangeBase): field, data_frequency, ffill=True): + """ + Fetching price history window from the exchange. + + Parameters + ---------- + exchange: Exchange + assets: list[TradingPair] + end_dt: datetime + bar_count: int + frequency: str + field: str + data_frequency: str + ffill: bool + + Returns + ------- + DataFrame + + """ df = exchange.get_history_window( assets, end_dt, @@ -250,6 +256,22 @@ class DataPortalExchangeLive(DataPortalExchangeBase): def get_exchange_spot_value(self, exchange, assets, field, dt, data_frequency): + """ + A spot value for the exchange. + + Parameters + ---------- + exchange: Exchange + assets: list[TradingPair] + field: str + dt: datetime + data_frequency: str + + Returns + ------- + float + + """ exchange_spot_values = exchange.get_spot_value( assets, field, dt, data_frequency) @@ -288,18 +310,21 @@ class DataPortalExchangeBacktest(DataPortalExchangeBase): """ Fetching price history window from the exchange bundle. - Using a try... except approach to minimize reads most of the time, - when the data exists. + Parameters + ---------- + exchange: Exchange + assets: list[TradingPair] + end_dt: datetime + bar_count: int + frequency: str + field: str + data_frequency: str + ffill: bool + + Returns + ------- + DataFrame - :param exchange: - :param assets: - :param end_dt: - :param bar_count: - :param frequency: - :param field: - :param data_frequency: - :param ffill: - :return: """ bundle = self.exchange_bundles[exchange.name] # type: ExchangeBundle @@ -321,26 +346,30 @@ class DataPortalExchangeBacktest(DataPortalExchangeBase): return df def get_exchange_spot_value(self, - exchange, # type: Exchange - assets, # type: List[TradingPair] - field, # type: str - dt, # type: Timestamp - data_frequency # type: str + exchange, + assets, + field, + dt, + data_frequency ): - # type: (...) -> float """ A spot value for the exchange bundle. Try to ingest data if not in the bundle. - :param exchange: - :param assets: - :param field: - :param dt: - :param data_frequency: - :return: + Parameters + ---------- + exchange: Exchange + assets: list[TradingPair] + field: str + dt: datetime + data_frequency: str + + Returns + ------- + float + """ bundle = self.exchange_bundles[exchange.name] - if data_frequency == 'daily': dt = dt.floor('1D') else: diff --git a/catalyst/exchange/exchange_execution.py b/catalyst/exchange/exchange_execution.py index 6527678e..536b526a 100644 --- a/catalyst/exchange/exchange_execution.py +++ b/catalyst/exchange/exchange_execution.py @@ -4,9 +4,16 @@ from catalyst.finance.execution import LimitOrder, StopOrder, StopLimitOrder class ExchangeLimitOrder(LimitOrder): def get_limit_price(self, is_buy): """ - We may be trading Satoshis with 8 decimals, we cannot round numbers - :param is_buy: - :return: + We may be trading Satoshis with 8 decimals, we cannot round numbers. + + Parameters + ---------- + is_buy: bool + + Returns + ------- + float + """ return self.limit_price @@ -14,9 +21,16 @@ class ExchangeLimitOrder(LimitOrder): class ExchangeStopOrder(StopOrder): def get_stop_price(self, is_buy): """ - We may be trading Satoshis with 8 decimals, we cannot round numbers - :param is_buy: - :return: + We may be trading Satoshis with 8 decimals, we cannot round numbers. + + Parameters + ---------- + is_buy: bool + + Returns + ------- + float + """ return self.stop_price @@ -24,16 +38,30 @@ class ExchangeStopOrder(StopOrder): class ExchangeStopLimitOrder(StopLimitOrder): def get_limit_price(self, is_buy): """ - We may be trading Satoshis with 8 decimals, we cannot round numbers - :param is_buy: - :return: + We may be trading Satoshis with 8 decimals, we cannot round numbers. + + Parameters + ---------- + is_buy: bool + + Returns + ------- + float + """ return self.limit_price def get_stop_price(self, is_buy): """ - We may be trading Satoshis with 8 decimals, we cannot round numbers - :param is_buy: - :return: + We may be trading Satoshis with 8 decimals, we cannot round numbers. + + Parameters + ---------- + is_buy: bool + + Returns + ------- + float + """ return self.stop_price diff --git a/catalyst/exchange/exchange_portfolio.py b/catalyst/exchange/exchange_portfolio.py index 2c0b1ac2..2003654b 100644 --- a/catalyst/exchange/exchange_portfolio.py +++ b/catalyst/exchange/exchange_portfolio.py @@ -3,6 +3,7 @@ from logbook import Logger from catalyst.constants import LOG_LEVEL from catalyst.protocol import Portfolio, Positions, Position +from catalyst.utils.deprecate import deprecated log = Logger('ExchangePortfolio', level=LOG_LEVEL) @@ -29,10 +30,15 @@ class ExchangePortfolio(Portfolio): self.positions_value = 0.0 self.open_orders = dict() - def calculate_pnl(self): - log.debug('calculating pnl') - def create_order(self, order): + """ + Create an open order and store in memory. + + Parameters + ---------- + order: Order + + """ log.debug('creating order {}'.format(order.id)) self.open_orders[order.id] = order @@ -47,6 +53,18 @@ class ExchangePortfolio(Portfolio): log.debug('open order added to portfolio') def execute_order(self, order, transaction): + """ + Update the open orders and positions to apply an executed order. + + Unlike with backtesting, we do not need to add slippage and fees. + The executed price includes transaction fees. + + Parameters + ---------- + order: Order + transaction: Transaction + + """ log.debug('executing order {}'.format(order.id)) del self.open_orders[order.id] @@ -71,7 +89,9 @@ class ExchangePortfolio(Portfolio): log.debug('updated portfolio with executed order') + @deprecated def execute_transaction(self, transaction): + # TODO: almost duplicate of execute_order. Not sure why Poloniex needs this. log.debug('executing transaction {}'.format(transaction.order_id)) order_position = self.positions[transaction.asset] \ @@ -96,6 +116,14 @@ class ExchangePortfolio(Portfolio): log.debug('updated portfolio with executed order') def remove_order(self, order): + """ + Removing an open order. + + Parameters + ---------- + order: Order + + """ log.info('removing cancelled order {}'.format(order.id)) del self.open_orders[order.id] diff --git a/catalyst/exchange/exchange_utils.py b/catalyst/exchange/exchange_utils.py index 812c7776..ee242ab2 100644 --- a/catalyst/exchange/exchange_utils.py +++ b/catalyst/exchange/exchange_utils.py @@ -2,12 +2,11 @@ import json import os import pickle import re - -from catalyst.assets._assets import TradingPair -from six.moves.urllib import request from datetime import date, datetime import pandas as pd +from catalyst.assets._assets import TradingPair +from six.moves.urllib import request from catalyst.exchange.exchange_errors import ExchangeSymbolsNotFound, \ InvalidHistoryFrequencyError, InvalidHistoryFrequencyAlias @@ -22,9 +21,15 @@ def get_exchange_folder(exchange_name, environ=None): """ The root path of an exchange folder. - :param exchange_name: - :param environ: - :return: + Parameters + ---------- + exchange_name: str + environ: + + Returns + ------- + str + """ if not environ: environ = os.environ @@ -40,9 +45,15 @@ def get_exchange_symbols_filename(exchange_name, environ=None): """ The absolute path of the exchange's symbol.json file. - :param exchange_name: - :param environ: - :return: + Parameters + ---------- + exchange_name: + environ: + + Returns + ------- + str + """ exchange_folder = get_exchange_folder(exchange_name, environ) return os.path.join(exchange_folder, 'symbols.json')