Skip to content

Commit

Permalink
Merge pull request #85 from AsyncAlgoTrading/momentum
Browse files Browse the repository at this point in the history
Momentum strategy and fixes
  • Loading branch information
timkpaine committed Aug 28, 2020
2 parents 5587e1b + eaf6ce4 commit 226c3d9
Show file tree
Hide file tree
Showing 22 changed files with 632 additions and 364 deletions.
6 changes: 6 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,12 @@ iexintraday: ## Clean and make target, run target
iexpintraday: ## Clean and make target, run target
$(PYTHON) -m aat ./private_config/iex_intraday.cfg

iexmomentum: ## Clean and make target, run target
$(PYTHON) -m aat ./config/iex_intraday_momentum.cfg

iexpmomentum: ## Clean and make target, run target
$(PYTHON) -m aat ./private_config/iex_intraday_momentum.cfg

iexlive: ## Clean and make target, run target
$(PYTHON) -m aat ./config/iex_live.cfg

Expand Down
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,12 @@ class Strategy(metaclass=ABCMeta):
'''submit a sell 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 `sold` method'''

async def cancelAll(self, instrument: Instrument = None):
'''cancel all open orders'''

async def closeAll(self, instrument: Instrument = None):
'''close all open positions'''

def orders(self, instrument: Instrument = None, exchange: ExchangeType = None, side: Side = None):
'''select all open orders'''

Expand Down Expand Up @@ -165,6 +171,7 @@ class Strategy(metaclass=ABCMeta):

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

```

### Example Strategy
Expand Down
2 changes: 1 addition & 1 deletion aat/core/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,4 @@
pass

# import last
from .engine import TradingEngine # noqa: F401
from .engine import TradingEngine, StrategyManager # noqa: F401
1 change: 1 addition & 0 deletions aat/core/engine/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
from .engine import TradingEngine # noqa: F401
from .manager import StrategyManager # noqa: F401
2 changes: 2 additions & 0 deletions aat/core/handler/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
from .handler import EventHandler # noqa: F401
from .print import PrintHandler # noqa: F401
43 changes: 3 additions & 40 deletions aat/core/handler.py → aat/core/handler/handler.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
from abc import ABCMeta, abstractmethod
from inspect import isabstract
from typing import TYPE_CHECKING
from .models import Event
from ..config import EventType
from ..models import Event
from ...config import EventType

if TYPE_CHECKING:
# Circular import
from .engine.manager import StrategyManager
from ..engine import StrategyManager


class EventHandler(metaclass=ABCMeta):
Expand Down Expand Up @@ -126,43 +126,6 @@ async def onCanceled(self, event: Event):
#################


class PrintHandler(EventHandler):
#########################
# Event Handler Methods #
#########################
def onTrade(self, event: Event):
'''Called whenever a `Trade` event is received'''
print(event)

def onOrder(self, event: Event):
'''Called whenever an Order `Open` event is received'''
print(event)

def onData(self, event: Event):
'''Called whenever other data is received'''
print(event)

def onHalt(self, event: Event):
'''Called whenever an exchange `Halt` event is received, i.e. an event to stop trading'''
print(event)

def onContinue(self, event: Event):
'''Called whenever an exchange `Continue` event is received, i.e. an event to continue trading'''
print(event)

def onError(self, event: Event):
'''Called whenever an internal error occurs'''
print(event)

def onStart(self, event: Event):
'''Called once at engine initialization time'''
print(event)

def onExit(self, event: Event):
'''Called once at engine exit time'''
print(event)


setattr(EventHandler.onTrade, '_original', 1)
setattr(EventHandler.onOrder, '_original', 1)
setattr(EventHandler.onOpen, '_original', 1)
Expand Down
39 changes: 39 additions & 0 deletions aat/core/handler/print.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
from .handler import EventHandler
from ..models import Event


class PrintHandler(EventHandler):
#########################
# Event Handler Methods #
#########################
def onTrade(self, event: Event):
'''Called whenever a `Trade` event is received'''
print(event)

def onOrder(self, event: Event):
'''Called whenever an Order `Open` event is received'''
print(event)

def onData(self, event: Event):
'''Called whenever other data is received'''
print(event)

def onHalt(self, event: Event):
'''Called whenever an exchange `Halt` event is received, i.e. an event to stop trading'''
print(event)

def onContinue(self, event: Event):
'''Called whenever an exchange `Continue` event is received, i.e. an event to continue trading'''
print(event)

def onError(self, event: Event):
'''Called whenever an internal error occurs'''
print(event)

def onStart(self, event: Event):
'''Called once at engine initialization time'''
print(event)

def onExit(self, event: Event):
'''Called once at engine exit time'''
print(event)
8 changes: 4 additions & 4 deletions aat/core/models/order.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
_CPP = False


def _make_cpp_order(volume, price, side, instrument, exchange=ExchangeType(""), notional=0.0, order_type=OrderType.LIMIT, flag=OrderFlag.NONE, stop_target=None):
def _make_cpp_order(volume, price, side, instrument, exchange=ExchangeType(""), notional=0.0, order_type=OrderType.MARKET, flag=OrderFlag.NONE, stop_target=None):
'''helper method to ensure all arguments are setup'''
return OrderCpp(0, datetime.now(), volume, price, side, instrument, exchange, notional, order_type, flag, stop_target)

Expand Down Expand Up @@ -46,7 +46,7 @@ def __new__(cls, *args, **kwargs):
return _make_cpp_order(*args, **kwargs)
return super(Order, cls).__new__(cls)

def __init__(self, volume, price, side, instrument, exchange=ExchangeType(""), notional=0.0, order_type=OrderType.LIMIT, flag=OrderFlag.NONE, stop_target=None):
def __init__(self, volume, price, side, instrument, exchange=ExchangeType(""), notional=0.0, order_type=OrderType.MARKET, flag=OrderFlag.NONE, stop_target=None):
self.__id = 0 # on construction, provide no ID until exchange assigns one
self.__timestamp = datetime.now()
self.__type = DataType.ORDER
Expand Down Expand Up @@ -137,7 +137,7 @@ def id(self, id: int) -> None:
self.__id = id

@property
def timestamp(self) -> int:
def timestamp(self) -> datetime:
return self.__timestamp

@timestamp.setter
Expand Down Expand Up @@ -182,7 +182,7 @@ def __eq__(self, other) -> bool:
def to_json(self) -> Mapping[str, Union[str, int, float]]:
return \
{'id': self.id,
'timestamp': self.timestamp,
'timestamp': self.timestamp.timestamp(),
'volume': self.volume,
'price': self.price,
'side': self.side.value,
Expand Down
5 changes: 3 additions & 2 deletions aat/core/models/trade.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from collections import deque
from datetime import datetime
from typing import Mapping, Type, Union, List, Dict
from .order import Order
from ...config import DataType, Side
Expand Down Expand Up @@ -61,7 +62,7 @@ def __init__(self, volume, price, maker_orders, taker_order):
# Readonly #
# ******** #
@property
def timestamp(self) -> int:
def timestamp(self) -> datetime:
return self.taker_order.timestamp

@property
Expand Down Expand Up @@ -141,7 +142,7 @@ def to_json(self) -> Mapping[str, Union[str, int, float]]:
[{'maker_order{}.' + k: v for k, v in order.to_json().items()} for i, order in enumerate(self.maker_orders)]

ret: Dict[str, Union[str, int, float]] = \
{'id': self.id, 'timestamp': self.timestamp,
{'id': self.id, 'timestamp': self.timestamp.timestamp(),
'price': self.price, 'volume': self.volume}

# update with taker order dict
Expand Down
1 change: 0 additions & 1 deletion aat/core/risk/__init__.py
Original file line number Diff line number Diff line change
@@ -1,2 +1 @@
from .calculations import CalculationsMixin # noqa: F401
from .risk import RiskManager # noqa: F401
13 changes: 6 additions & 7 deletions aat/core/risk/risk.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,19 +32,18 @@ def newPosition(self, trade: Trade):
prev_price: float = cur_pos.price
prev_notional: float = prev_size * prev_price

# FIXME separate maker, taker
cur_pos.size = (cur_pos.size + (my_order.volume if my_order.side == Side.BUY else -1 * my_order.volume), trade.timestamp)

if (prev_size > 0 and cur_pos.size > prev_size) or (prev_size < 0 and cur_pos.size < prev_size): # type: ignore
if (prev_size >= 0 and cur_pos.size > prev_size) or (prev_size <= 0 and cur_pos.size < prev_size): # type: ignore
# increasing position size
# update average price
cur_pos.price = ((prev_notional + (my_order.volume * trade.price)) / cur_pos.size, trade.timestamp)

elif (prev_size > 0 and cur_pos.size < 0) or (prev_size < 0 and cur_pos.size > 0): # type: ignore
# decreasing position size in one direction, increasing position size in other
# update realized pnl
pnl = cur_pos.pnl + (prev_size * (trade.price - prev_price))
cur_pos.pnl = (pnl, trade.timestamp) # update realized pnl with closing position
pnl = (prev_size * (trade.price - prev_price))
cur_pos.pnl = (cur_pos.pnl + pnl, trade.timestamp) # update realized pnl with closing position

# deduct from unrealized pnl
cur_pos.unrealizedPnl = (cur_pos.unrealizedPnl - pnl, trade.timestamp)
Expand All @@ -55,13 +54,13 @@ def newPosition(self, trade: Trade):
else:
# decreasing position size
# update realized pnl
pnl = cur_pos.pnl + (prev_size * (trade.price - prev_price))
cur_pos.pnl = (pnl, trade.timestamp) # update realized pnl with closing position
pnl = (prev_size * (trade.price - prev_price))
cur_pos.pnl = (cur_pos.pnl + pnl, trade.timestamp) # update realized pnl with closing position

# deduct from unrealized pnl
cur_pos.unrealizedPnl = (cur_pos.unrealizedPnl - pnl, trade.timestamp)

# TODO close if side is 0
# TODO close if side is 0?

else:
self._active_positions[trade.instrument] = Position(price=trade.price,
Expand Down
4 changes: 2 additions & 2 deletions aat/cpp/include/aat/core/models/order.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ namespace aat {
namespace core {
struct Order : public _EventTarget {
Order(uint_t id, timestamp_t timestamp, double volume, double price, Side side, Instrument instrument,
ExchangeType exchange = NullExchange, double notional = 0.0, OrderType order_type = OrderType::LIMIT,
ExchangeType exchange = NullExchange, double notional = 0.0, OrderType order_type = OrderType::MARKET,
OrderFlag flag = OrderFlag::NONE, std::shared_ptr<Order> stop_target = nullptr);

virtual str_t toString() const;
Expand All @@ -33,7 +33,7 @@ namespace core {
double price;
const Side side;

const OrderType order_type = OrderType::LIMIT;
const OrderType order_type = OrderType::MARKET;
const OrderFlag flag = OrderFlag::NONE;
const std::shared_ptr<Order> stop_target = nullptr;
double notional = 0.0;
Expand Down
10 changes: 10 additions & 0 deletions aat/exchange/public/iex.py
Original file line number Diff line number Diff line change
Expand Up @@ -120,12 +120,18 @@ def _callback(record):

else:
dfs = []
insts = set()

if self._timeframe != '1d':
for i in tqdm(self._subscriptions, desc="Fetching data..."):
if i.name in insts:
continue

df = self._client.chartDF(i.name, timeframe=self._timeframe)
df = df[['close', 'volume']]
df.columns = ['close:{}'.format(i.name), 'volume:{}'.format(i.name)]
dfs.append(df)
insts.add(i.name)

data = pd.concat(dfs, axis=1)
data.sort_index(inplace=True)
Expand All @@ -135,6 +141,9 @@ def _callback(record):

else:
for i in tqdm(self._subscriptions, desc="Fetching data..."):
if i.name in insts:
continue

date = self._start_date
subdfs = []
while date <= self._end_date:
Expand All @@ -145,6 +154,7 @@ def _callback(record):
subdfs.append(df)
date += timedelta(days=1)
dfs.append(pd.concat(subdfs))
insts.add(i.name)

data = pd.concat(dfs, axis=1)
data.index = [x + timedelta(hours=int(y.split(':')[0]), minutes=int(y.split(':')[1])) for x, y in data.index]
Expand Down

0 comments on commit 226c3d9

Please sign in to comment.