BUG: fixed some issues with capital_base

This commit is contained in:
fredfortier
2017-12-08 20:29:55 -05:00
parent 48f4d01c70
commit e41eca0d8a
6 changed files with 123 additions and 92 deletions
+3 -3
View File
@@ -34,7 +34,7 @@ def initialize(context):
# parameters or values you're going to use.
# In our example, we're looking at Neo in Ether.
context.market = symbol('rdn_eth')
context.market = symbol('neo_eth')
context.base_price = None
context.current_day = None
@@ -240,7 +240,7 @@ def analyze(context=None, perf=None):
if __name__ == '__main__':
# The execution mode: backtest or live
MODE = 'live'
MODE = 'backtest'
if MODE == 'backtest':
folder = os.path.join(
@@ -259,7 +259,7 @@ if __name__ == '__main__':
initialize=initialize,
handle_data=handle_data,
analyze=analyze,
exchange_name='bittrex',
exchange_name='bitfinex',
algo_namespace=NAMESPACE,
base_currency='eth',
start=pd.to_datetime('2017-10-01', utc=True),
+2 -2
View File
@@ -109,9 +109,9 @@ def analyze(context, perf):
run_algorithm(
capital_base=250,
start=pd.to_datetime('2017-11-1 0:00', utc=True),
start=pd.to_datetime('2017-11-9 0:00', utc=True),
end=pd.to_datetime('2017-11-10 23:59', utc=True),
data_frequency='daily',
data_frequency='minute',
initialize=initialize,
handle_data=handle_data,
analyze=analyze,
+63 -57
View File
@@ -13,7 +13,6 @@
import pickle
import signal
import sys
from collections import deque
from datetime import timedelta
from os import listdir
from os.path import isfile, join
@@ -631,50 +630,72 @@ class ExchangeTradingAlgorithmLive(ExchangeTradingAlgorithmBase):
self.validate_account_controls()
try:
# Since the clock runs 24/7, I trying to disable the daily
# Performance tracker and keep only minute and cumulative
self.perf_tracker.update_performance()
frame_stats = self.prepare_period_stats(
data.current_dt, data.current_dt + timedelta(minutes=1))
# Saving the last hour in memory
self.frame_stats.append(frame_stats)
self.add_pnl_stats(frame_stats)
if self.recorded_vars:
self.add_custom_signals_stats(frame_stats)
recorded_cols = list(self.recorded_vars.keys())
else:
recorded_cols = None
self.add_exposure_stats(frame_stats)
# print_df = pd.DataFrame(list(self.frame_stats))
log.info(
'statistics for the last {stats_minutes} minutes:\n'
'{stats}'.format(
stats_minutes=self.stats_minutes,
stats=get_pretty_stats(
stats=self.frame_stats,
recorded_cols=recorded_cols,
num_rows=self.stats_minutes
)
))
daily_stats = self.prepare_period_stats(
start_dt=today,
end_dt=pd.Timestamp.utcnow()
)
save_algo_object(
algo_name=self.algo_namespace,
key=today.strftime('%Y-%m-%d'),
obj=daily_stats,
rel_path='daily_perf'
)
self._save_stats_csv(self._process_stats(data))
except Exception as e:
log.warn('unable to calculate performance: {}'.format(e))
# TODO: pickle does not seem to work in python 3
try:
save_algo_object(
algo_name=self.algo_namespace,
key='perf_tracker',
obj=self.perf_tracker
)
except Exception as e:
log.warn('unable to save minute perfs to disk: {}'.format(e))
self.current_day = data.current_dt.floor('1D')
def _process_stats(self, data):
today = data.current_dt.floor('1D')
# Since the clock runs 24/7, I trying to disable the daily
# Performance tracker and keep only minute and cumulative
self.perf_tracker.update_performance()
frame_stats = self.prepare_period_stats(
data.current_dt, data.current_dt + timedelta(minutes=1))
# Saving the last hour in memory
self.frame_stats.append(frame_stats)
self.add_pnl_stats(frame_stats)
if self.recorded_vars:
self.add_custom_signals_stats(frame_stats)
recorded_cols = list(self.recorded_vars.keys())
else:
recorded_cols = None
self.add_exposure_stats(frame_stats)
log.info(
'statistics for the last {stats_minutes} minutes:\n'
'{stats}'.format(
stats_minutes=self.stats_minutes,
stats=get_pretty_stats(
stats=self.frame_stats,
recorded_cols=recorded_cols,
num_rows=self.stats_minutes
)
))
# Saving the daily stats in a format usable for performance
# analysis.
daily_stats = self.prepare_period_stats(
start_dt=today,
end_dt=data.current_dt
)
save_algo_object(
algo_name=self.algo_namespace,
key=today.strftime('%Y-%m-%d'),
obj=daily_stats,
rel_path='daily_perf'
)
return recorded_cols
def _save_stats_csv(self, recorded_cols):
# Writing the stats output
csv_bytes = None
try:
@@ -683,7 +704,6 @@ class ExchangeTradingAlgorithmLive(ExchangeTradingAlgorithmBase):
algo_namespace=self.algo_namespace,
recorded_cols=recorded_cols,
)
except Exception as e:
log.warn('unable save stats locally: {}'.format(e))
@@ -697,27 +717,13 @@ class ExchangeTradingAlgorithmLive(ExchangeTradingAlgorithmBase):
recorded_cols=recorded_cols,
bytes_to_write=csv_bytes
)
else:
raise ValueError(
'Only S3 stats output is supported for now.'
)
except Exception as e:
log.warn('unable save stats externally: {}'.format(e))
# TODO: pickle does not seem to work in python 3
try:
save_algo_object(
algo_name=self.algo_namespace,
key='perf_tracker',
obj=self.perf_tracker
)
except Exception as e:
log.warn('unable to save minute perfs to disk: {}'.format(e))
self.current_day = data.current_dt.floor('1D')
@api_method
def batch_market_order(self, share_counts):
raise NotImplementedError()
+9 -1
View File
@@ -244,7 +244,7 @@ class NoDataAvailableOnExchange(ZiplineError):
'exchange {exchange} '
'in `{data_frequency}` frequency at this time. '
'Check `http://enigma.co/catalyst/status` for market coverage.'
).strip()
).strip()
class NoValueForField(ZiplineError):
@@ -255,3 +255,11 @@ class OrderTypeNotSupported(ZiplineError):
msg = (
'Order type `{order_type}` not currencly supported by Catalyst. '
'Please use `limit` or `market` orders only.').strip()
class NotEnoughCapitalError(ZiplineError):
msg = (
'Not enough capital on exchange {exchange} for trading. Each '
'exchange should contain at least as much {base_currency} '
'as the specified `capital_base`. The current balance {balance} is '
'lower than the `capital_base`: {capital_base}').strip()
+24 -12
View File
@@ -192,20 +192,21 @@ def prepare_stats(stats, recorded_cols=list()):
assets = [p['sid'] for p in row_data['positions']]
asset_values = dict()
for column in recorded_cols[:]:
value = row_data[column]
if type(value) is dict:
for asset in value:
if not isinstance(asset, TradingPair):
break
if recorded_cols is not None:
for column in recorded_cols[:]:
value = row_data[column]
if type(value) is dict:
for asset in value:
if not isinstance(asset, TradingPair):
break
if asset not in assets:
assets.append(asset)
if asset not in assets:
assets.append(asset)
if asset not in asset_values:
asset_values[asset] = dict()
if asset not in asset_values:
asset_values[asset] = dict()
asset_values[asset][column] = value[asset]
asset_values[asset][column] = value[asset]
if len(assets) == 1:
row = stats[row_index]
@@ -231,8 +232,8 @@ def prepare_stats(stats, recorded_cols=list()):
]
# Removing the asset specific entries
recorded_cols = [x for x in recorded_cols if x not in asset_cols]
if recorded_cols is not None:
recorded_cols = [x for x in recorded_cols if x not in asset_cols]
for column in recorded_cols:
index_cols.append(column)
@@ -256,13 +257,24 @@ def get_pretty_stats(stats, recorded_cols=None, num_rows=10):
Parameters
----------
stats: list[Object]
An array of statistics for the period.
num_rows: int
The number of rows to display on the screen.
Returns
-------
str
"""
if isinstance(stats, pd.DataFrame):
# df = stats
# columns = [
# 'period_close', 'starting_cash', 'ending_cash', 'portfolio_value',
# 'pnl', 'long_exposure', 'short_exposure', 'orders', 'transactions',
# ]
stats = stats.T.to_dict().values()
# else:
df, columns = prepare_stats(stats, recorded_cols=recorded_cols)
pd.set_option('display.expand_frame_repr', False)
+22 -17
View File
@@ -40,7 +40,7 @@ from catalyst.exchange.exchange_data_portal import DataPortalExchangeLive, \
from catalyst.exchange.asset_finder_exchange import AssetFinderExchange
from catalyst.exchange.exchange_errors import (
ExchangeRequestError, ExchangeRequestErrorTooManyAttempts,
BaseCurrencyNotFoundError)
BaseCurrencyNotFoundError, NotEnoughCapitalError)
from catalyst.constants import LOG_LEVEL
@@ -227,28 +227,25 @@ def _run(handle_data,
)
)
if capital_base is not None \
and capital_base < base_currency_available:
log.info(
'using capital base limit: {} {}'.format(
capital_base, base_currency
)
)
amount = capital_base
else:
amount = base_currency_available
return amount
return base_currency_available
else:
raise BaseCurrencyNotFoundError(
base_currency=base_currency,
exchange=exchange_name
)
combined_capital_base = 0
for exchange_name in exchanges:
exchange = exchanges[exchange_name]
combined_capital_base += fetch_capital_base(exchange)
if not simulate_orders:
for exchange_name in exchanges:
exchange = exchanges[exchange_name]
balance = fetch_capital_base(exchange)
if balance < capital_base:
raise NotEnoughCapitalError(
exchange=exchange_name,
base_currency=base_currency,
balance=balance,
capital_base=capital_base,
)
sim_params = create_simulation_parameters(
start=start,
@@ -505,6 +502,14 @@ def run_algorithm(initialize,
default_extension, extensions, strict_extensions, environ
)
if capital_base is None:
raise ValueError(
'Please specify a `capital_base` parameter which is the maximum '
'amount of base currency available for trading. For example, '
'if the `capital_base` is 5ETH, the '
'`order_target_percent(asset, 1)` command will order 5ETH worth '
'of the specified asset.'
)
# I'm not sure that we need this since the modified DataPortal
# does not require extensions to be explicitly loaded.