mirror of
https://github.com/wassname/catalyst.git
synced 2026-06-28 19:15:15 +08:00
DOC: updating the code docstrings
This commit is contained in:
+141
-58
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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]
|
||||
|
||||
|
||||
@@ -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')
|
||||
|
||||
Reference in New Issue
Block a user