One of the advantages of running automatic trading strategies is that you can quickly and consistently act on price action. Even if you have enough time to trade the same idea manually, you need to watch the market movement very closely and keep paying attention to multiple monitors. With algorithmic trading, you can automate this on Alpaca.
Also, please keep in mind that this is only an example to help get you started. Consider testing a strategy in paper trading to see if and how it works before trying it in a live brokerage account.
If you'd prefer to "fast forward" to full code, go directly to the GitHub repository!
There are many types of trading strategies, but this one is called “scalping” strategy.
Scalping attempts to take smaller profits quicker, which can add up, without risking your assets holding for long. It requires a strict exit strategy, though, because one large loss could eliminate the many small gains. The above-referenced article explains the strategy in more detail, as well as the different types of scalping.
Many day traders are applying this idea, but in order to do this manually, a lot of energy and attention is required in order to keep monitoring the large amounts of information on the screen.
When looking back at the intraday chart at the end of the day, it is possible to see different missed opportunities. However, it's not always possible to act during market hours. There could also be even more opportunities if it were possible to monitor a dozen stocks independently versus just looking at one stock.
For example, when looking at a TradingView chart for SPY with 20 minute simple moving average on 10/04/19 (below), it's possible to see price crossover where it would have been possible to have taken a small profit if the position had been timely.
With the scalping strategy, the goal isn't for a large profit margin, rather, aim for a small upward price movement while it’s going up. It's usually too hard to monitor even a few charts at the same time manually, so that's where algorithms come in.
What if I can have many of me watching each stock. I started coding based on the idea and found Python’s asyncio is pretty useful to manage algorithm flow for multiple stocks, each of which I found to be pretty simple.
Check out Python's Asyncio here!
To begin with a single stock, I wrote a class that manages simple state and event handler for a single stock using Alpaca API (simplified below).
class ScalpAlgo:
def __init__(self, symbol):
self._symbol = symbol
self._bars = []
self._state = 'TO_BUY'
def on_bar(self, bar):
'''called when new bar arrives'''
self._bars = self._bars.append(bar)
if self._state == 'TO_BUY':
if self._check_signal():
self._submit_buy()
self._state = 'BUY_SUBMITTED'
def on_order_event(self, event):
'''called if order status changes'''
if event == 'fill':
if self._state == 'BUY_SUBMITTED':
self._submit_sell( self._get_last_price() + 0.01)
self._state = 'SELL_SUBMITTED'
elif self._state == 'SELL_SUBMITTED':
self._state = 'TO_BUY'
The main flow is pretty simple as you can see. It is basically a state machine with 4 distinct states, and given the new event (signal can be triggers, order fills, etc.), the state transitions to look for the next action. In the
_check_signal() function
, I am calculating 20-minute simple moving average and compare with the price.def _calc_buy_signal(self):
mavg = self._bars.rolling(20).mean().close.values
closes = self._bars.close.values
if closes[-2] < mavg[-2] and closes[-1] > mavg[-1]:
return True
else:
return False
Since it is important to take action as quickly as the signal triggers, we subscribe to the real-time bar updates from Polygon websockets as well as Alpaca’s order event websockets. Everything is event-driven.
def main():
stream = alpaca.StreamConn()
algo = ScalpAlgo('SPY') @stream.on('AM.SPY')
async def on_bar(conn, channel, data):
algo.on_bar(data) @stream.on('trade_updates')
async def on_order_event(conn, channel, data):
algo.on_order_event(data.event)
stream.run(['AM.SPY', 'trade_updates'])
This run() function runs in definitely until the program stops. All events are dispatched to the event handlers in Python’s asyncio loop, driven by the new messages from websockets.
Once the structure is built, all you need to do is to focus on the state transitions in a couple of different cases. Beyond the simplified sample code above, you may want to handle cancel/rejection event for your buy order. If you want to forget about it since you don’t have the position, but want to get in next time the same signal triggers, then you will set the state to TO_BUY so you can reset the state. You may also want to consider setting those orders to cancel orders if they don’t fill within the reasonable timeframe after submission. Or those working orders may be canceled from dashboard.
Now, the question is how to scale this to dozens of stocks? It’s important to keep the signal as strict as possible so that you don’t get into a position under an unintended situation to buy. The more you tighten the signal rule, the less entry opportunities you have. However, you should have more opportunities if you run this against dozens of stocks.
To scale this idea to many stocks you want to watch, there is actually not much more to do. The ScalpAlgo already takes the stock symbol as parameter, and manages state for this symbol only, which means you just need to create more of this class instance with different symbols. As an example:
def main():
stream = alpaca.StreamConn()
fleet = {}
symbols = ('SPY', 'QQQ', 'DIA')
for symbol in symbols:
fleet[symbol] = ScalpAlgo(symbol) @stream.on('AM.*')
async def on_bar(conn, channel, data):
if data.symbol in fleet:
fleet[data.symbl].on_bar(data) @stream.on('trade_updates')
async def on_order_event(conn, channel, data):
order = data.order
if order['symbol']:
fleet[order['symbol']).on_order_event(data.event) stream.run(['AM.' + symbol for symbols] + ['trade_updates'])
The fleet holds each algorithm instance in a dictionary using symbol as the key. This way, each of the algorithm code does not even need to know if there is another algo working on something different at the time.
As you can see, the entire script including logging and corner case handling is less than 300 lines.
The only dependency is Alpaca Python SDK and you can also use pipenv to create virtualenv. Once API key is set in environment variables and dependency is installed you'll see something like the following:
$ python main.py SPY FB TLT
Then, you will see something like this.
2019-10-04 18:49:04 ,250:main.py:119:INFO:SPY:received bar start = 2019-10-04 14:48:00-04:00, close = 293.71, len(bars) = 319
2019-10-04 18:49:04 ,254:main.py:106:INFO:SPY:closes[-2:] = [293.64 293.71], mavg[-2:] = [293.618215 293.62921 ]
2019-10-04 18:49:04 ,281:main.py:119:INFO:FB:received bar start = 2019-10-04 14:48:00-04:00, close = 180.69, len(bars) = 319
2019-10-04 18:49:04 ,286:main.py:106:INFO:FB:closes[-2:] = [180.609 180.69 ], mavg[-2:] = [180.488985 180.511485]
2019-10-04 18:49:04 ,437:main.py:119:INFO:AAPL:received bar start = 2019-10-04 14:48:00-04:00, close = 227.33, len(bars) = 319
2019-10-04 18:49:04 ,441:main.py:106:INFO:AAPL:closes[-2:] = [227.3088 227.33 ], mavg[-2:] = [227.114355 227.13985 ]
2019-10-04 18:49:04 ,455:main.py:119:INFO:TLT:received bar start = 2019-10-04 14:48:00-04:00, close = 145.65, len(bars) = 319
2019-10-04 18:49:04 ,458:main.py:101:INFO:TLT:buy signal: closes[-2] 145.6 < mavg[-2] 145.60542 closes[-1] 145.65 > mavg[-1] 145.60992000000002
2019-10-04 18:49:04 ,676:main.py:180:INFO:TLT:submitted buy Order({ 'asset_class': 'us_equity',
'asset_id': '1f8bfbdf-083a-4ebe-ad36-eaf6b6f0cb9d',
'canceled_at': None,
'client_order_id': 'f3c7a968-baba-454b-9691-a90810a389b4',
'created_at': ' 2019-10-04 T 18:49:04 .658502572Z',
'expired_at': None,
'extended_hours': False,
'failed_at': None,
'filled_at': None,
'filled_avg_price': None,
'filled_qty': '0',
'id': '1aeeb723-34bc-4561-8016-8f916538509c',
'limit_price': '145.65',
'order_type': 'limit',
'qty': '13',
'replaced_at': None,
'replaced_by': None,
'replaces': None,
'side': 'buy',
'status': 'new',
'stop_price': None,
'submitted_at': ' 2019-10-04 T 18:49:04 .635271258Z',
'symbol': 'TLT',
'time_in_force': 'day',
'type': 'limit',
'updated_at': ' 2019-10-04 T 18:49:04 .662862495Z'})
This is just one example of how to write an algorithm that trades multiple stocks concurrently. There are other frameworks such as pylivetrader, but it's up to you and your preferences.
Technology and services are offered by AlpacaDB, Inc. Brokerage services are provided by Alpaca Securities LLC (alpaca.markets), member FINRA/SIPC. Alpaca Securities LLC is a wholly-owned subsidiary of AlpacaDB, Inc.
Tweet us @AlpacaHQ