Skip to content

Commit

Permalink
Merge pull request #75 from AsyncAlgoTrading/pricehistory
Browse files Browse the repository at this point in the history
working on price history #71, fixes #72
  • Loading branch information
timkpaine committed Aug 10, 2020
2 parents 5de17d5 + 47e5dc5 commit b307fdb
Show file tree
Hide file tree
Showing 8 changed files with 110 additions and 24 deletions.
12 changes: 12 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,9 @@ class Strategy(metaclass=ABCMeta):
async def newOrder(self, order: Order):
'''helper method, defers to buy/sell'''

async def cancelOrder(self, order: Order):
'''cancel an open order'''

async def buy(self, order: Order):
'''submit a buy order. Note that this is merely a request for an order, it provides no guarantees that the order will
execute. At a later point, if your order executes, you will receive an alert via the `bought` method'''
Expand All @@ -128,6 +131,9 @@ class Strategy(metaclass=ABCMeta):
def trades(self, instrument: Instrument = None, exchange: ExchangeType = None, side: Side = None):
'''select all past trades'''

def accounts(self) -> List:
'''get accounts from source'''

################
# Risk Methods #
################
Expand All @@ -137,6 +143,9 @@ class Strategy(metaclass=ABCMeta):
def risk(self, position=None):
'''Get risk metrics'''

def priceHistory(self, instrument: Instrument):
'''Get price history for an asset'''

#################
# Other Methods #
#################
Expand All @@ -152,6 +161,9 @@ class Strategy(metaclass=ABCMeta):

def subscribe(self, instrument=None):
'''Subscribe to market data for the given instrument'''

def lookup(self, instrument):
'''lookup an instrument on the exchange'''
```

### Example Strategy
Expand Down
3 changes: 3 additions & 0 deletions aat/core/engine/manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,9 @@ def positions(self, instrument=None, exchange=None, side=None):
def risk(self, position=None):
return self._risk_mgr.risk(position=position)

def priceHistory(self, instrument=None):
return self._risk_mgr.priceHistory(instrument=instrument)

# **********************
# EventHandler methods *
# **********************
Expand Down
75 changes: 56 additions & 19 deletions aat/core/risk/calculations.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,19 @@ def _getInstruments(self):
return instruments

def _getPrice(self):
portfolio = []
price_cols = []
for instrument, price_history in self.priceHistory().items():
#########
# Price #
#########
price_col = instrument.name
price_cols.append(price_col)
price_history.set_index('when', inplace=True)
portfolio.append(price_history)
return self._constructDf(portfolio)

def _getAssetPrice(self):
portfolio = []
price_cols = []
for position in self.positions():
Expand Down Expand Up @@ -147,7 +160,7 @@ def collectStats(self):
self._df_pnl = self._getPnl()
self._df_pnl.fillna(0.0, inplace=True)

self._df_price = self._getPrice()
self._df_asset_price = self._getAssetPrice()

self._df_total_pnl = self._df_pnl[[c for c in self._df_pnl if 'pnl:' in c]]
self._df_total_pnl.columns = [c.replace('pnl:', '') for c in self._df_total_pnl.columns]
Expand All @@ -163,15 +176,29 @@ def collectStats(self):

def plotPrice(self, ax=None, **plot_kwargs):
self._df_price = self._getPrice()
self._df_price.plot(ax=ax, **plot_kwargs)

if not self._df_price.empty:
self._df_price.plot(ax=ax, **plot_kwargs)

if ax:
ax.set_ylabel('Price')

def plotAssetPrice(self, ax=None, **plot_kwargs):
self._df_asset_price = self._getAssetPrice()

if not self._df_asset_price.empty:
self._df_asset_price.plot(ax=ax, **plot_kwargs)

if ax:
ax.set_ylabel('Price')

def plotPositions(self, ax=None, **plot_kwargs):
self._df_size = self._getSize()
self._df_size.columns = [c.replace('s:', '') for c in self._df_size.columns]

self._df_size.plot(kind='area', ax=ax, stacked=True, linewidth=0, **plot_kwargs)
if not self._df_size.empty:
self._df_size.plot(kind='area', ax=ax, stacked=True, linewidth=0, **plot_kwargs)

if ax:
ax.set_ylabel('Positions')

Expand All @@ -180,10 +207,12 @@ def plotNotional(self, ax=None, **plot_kwargs):
df_position_notional.columns = [c.replace('s:', '') for c in self._df_size.columns]

for col in df_position_notional.columns:
df_position_notional[col] = df_position_notional[col] * self._df_price[col]
df_position_notional[col] = df_position_notional[col] * self._df_asset_price[col]

if not df_position_notional.empty:
df_position_notional.fillna(method='ffill', inplace=True)
df_position_notional.plot(kind='area', ax=ax, stacked=True, linewidth=0, **plot_kwargs)

df_position_notional.fillna(method='ffill', inplace=True)
df_position_notional.plot(kind='area', ax=ax, stacked=True, linewidth=0, **plot_kwargs)
if ax:
ax.set_ylabel('Notional')

Expand All @@ -198,7 +227,10 @@ def plotPnl(self, ax=None, **plot_kwargs):

self._df_total_pnl = self._df_pnl[[c for c in self._df_pnl if 'pnl:' in c]]
self._df_total_pnl.columns = [c.replace('pnl:', '') for c in self._df_total_pnl.columns]
self._df_total_pnl.plot(ax=ax)

if not self._df_total_pnl.empty:
self._df_total_pnl.plot(ax=ax)

if ax:
ax.set_ylabel('PNL')

Expand All @@ -211,7 +243,10 @@ def plotUpDown(self, ax=None, **plot_kwargs):
df2['neg'] = df2['alpha']
df2['pos'][df2['pos'] <= 0] = np.nan
df2['neg'][df2['neg'] > 0] = np.nan
df2.plot(ax=ax, y=['pos', 'neg'], kind='area', stacked=False, color=['green', 'red'], legend=False, linewidth=0, fontsize=5, rot=0, **plot_kwargs)

if not df2.empty:
df2.plot(ax=ax, y=['pos', 'neg'], kind='area', stacked=False, color=['green', 'red'], legend=False, linewidth=0, fontsize=5, rot=0, **plot_kwargs)

if ax:
ax.set_ylabel('Alpha')

Expand All @@ -225,19 +260,20 @@ def plotReturnHistograms(self, **plot_kwargs):
# drop if exactly -100% (e.g. "sold")
df_returns.append(self._df_notional[col].drop_duplicates().pct_change(1).fillna(0.0))

df_returns = pd.concat(df_returns, axis=1, sort=True)
df_returns.sort_index(inplace=True)
df_returns = df_returns.groupby(df_returns.index).last()
df_returns.drop_duplicates(inplace=True)
if df_returns:
df_returns = pd.concat(df_returns, axis=1, sort=True)
df_returns.sort_index(inplace=True)
df_returns = df_returns.groupby(df_returns.index).last()
df_returns.drop_duplicates(inplace=True)

fig2 = plt.figure(figsize=(9, 5))
grid = plt.GridSpec(2, len(df_returns.columns), figure=fig2, wspace=0.4, hspace=0.3)
axes2 = []
for _ in range(len(df_returns.columns)):
axes2.append(plt.subplot(grid[0, _]))
fig2 = plt.figure(figsize=(9, 5))
grid = plt.GridSpec(2, len(df_returns.columns), figure=fig2, wspace=0.4, hspace=0.3)
axes2 = []
for _ in range(len(df_returns.columns)):
axes2.append(plt.subplot(grid[0, _]))

for i, col in enumerate(df_returns.columns):
df_returns.hist(column=col, ax=axes2[i], grid=False)
for i, col in enumerate(df_returns.columns):
df_returns.hist(column=col, ax=axes2[i], grid=False)

def plotStdDev(self, ax, **plot_kwargs):
self._df_notional = self._getNotional()
Expand Down Expand Up @@ -276,6 +312,7 @@ def performanceCharts(self):

# Plot prices
self.plotPrice(ax=axes[0])
# self.plotAssetPrice(ax=axes[1])

# Plot Positions
self.plotPositions(ax=axes[1])
Expand Down
21 changes: 21 additions & 0 deletions aat/core/risk/risk.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,18 @@
import pandas as pd # type: ignore
from aat.config import Side
from aat.core import Event, Order, Trade, Instrument, ExchangeType, Position


class RiskManager(object):
def __init__(self):
# Track prices over time
self._prices = {}
self._trades = {}

# Track active (open) orders
self._active_orders = []

# Track active positions
self._active_positions = {}

def _setManager(self, manager):
Expand Down Expand Up @@ -69,7 +77,13 @@ def newPosition(self, trade: Trade):
def positions(self, instrument: Instrument = None, exchange: ExchangeType = None, side: Side = None):
return list(self._active_positions.values())

def priceHistory(self, instrument: Instrument = None):
if instrument:
return pd.DataFrame(self._prices[instrument], columns=[instrument.name, 'when'])
return {i: pd.DataFrame(self._prices[i], columns=[i.name, 'when']) for i in self._prices}

def risk(self, position=None):
# TODO
return "risk"

# *********************
Expand All @@ -92,6 +106,13 @@ async def onTrade(self, event: Event):
pos.pnl = (pos.pnl, trade.timestamp)
pos.instrumentPrice = (trade.price, trade.timestamp)

if trade.instrument not in self._prices:
self._prices[trade.instrument] = [(trade.price, trade.timestamp)]
self._trades[trade.instrument] = [trade]
else:
self._prices[trade.instrument].append((trade.price, trade.timestamp))
self._trades[trade.instrument].append(trade)

async def onCancel(self, event):
# TODO
pass
Expand Down
2 changes: 1 addition & 1 deletion aat/exchange/public/iex.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ async def instruments(self):
instruments = []
symbols = self._client.symbols()
for record in symbols:
if not record['isEnabled'] or not record['type']:
if not record['isEnabled'] or not record['type'] or record['type'] == 'temp':
continue
symbol = record['symbol']
brokerExchange = record['exchange']
Expand Down
16 changes: 14 additions & 2 deletions aat/strategy/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ class Strategy(EventHandler, CalculationsMixin):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)

def __repr__(self):
return self.__class__.__name__

#########################
# Event Handler Methods #
#########################
Expand Down Expand Up @@ -203,7 +206,6 @@ def positions(self, instrument: Instrument = None, exchange: ExchangeType = None
Returns:
list (Position): list of positions
'''
# TODO move me to manager
return self._manager.positions(instrument=instrument, exchange=exchange, side=side)

def risk(self, position=None):
Expand All @@ -214,12 +216,22 @@ def risk(self, position=None):
Returns:
dict: metrics
'''
# TODO move me to manager
return self._manager.risk(position=position)

def priceHistory(self, instrument: Instrument = None):
'''Get price history for asset
Args:
instrument (Instrument): get price history for instrument
Returns:
DataFrame: price history
'''
return self._manager.priceHistory(instrument=instrument)

#################
# Other Methods #
#################

def now(self):
'''Return the current datetime. Useful to avoid code changes between
live trading and backtesting. Defaults to `datetime.now`'''
Expand Down
3 changes: 2 additions & 1 deletion aat/strategy/sample/golden_death.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@


class GoldenDeathStrategy(Strategy):
def __init__(self, symbol, long_ma=15, short_ma=5, *args, **kwargs):
def __init__(self, symbol, long_ma=30, short_ma=5, *args, **kwargs):
super(GoldenDeathStrategy, self).__init__(*args, **kwargs)

# Long moving average size
Expand Down Expand Up @@ -119,4 +119,5 @@ async def onRejected(self, event: Event):

async def onExit(self, event: Event):
print('Finishing...')

self.performanceCharts()
2 changes: 1 addition & 1 deletion config/iex_intraday.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ trading_type=backtest

[exchange]
exchanges=
aat.exchange.public.iex:IEX,Tpk_ecc89ddf30a611e9958142010a80043c,True,1d,20200807
aat.exchange.public.iex:IEX,Tpk_ecc89ddf30a611e9958142010a80043c,True,1d,20200810

[strategy]
strategies =
Expand Down

0 comments on commit b307fdb

Please sign in to comment.