Automating My Manual Scalping Trading Strategy

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.

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 Want to Fast Forward to Full Code

alpacahq/example-scalping
A working example algorithm for scalping strategy trading multiple stocks concurrently using python asyncio - alpacahq/example-scalping

Scalping Strategy

What is Scalping Strategy?

There are many types of trading strategies, but today I pick up one of them called “scalping” strategy.

There are many types of trading strategies, but this one is called “scalping” strategy.

Scalping: Small Quick Profits Can Add Up
Scalping can be very profitable for traders who decide to use it as a primary strategy or even those who use it to supplement other types of trading.

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.

Why Scalping Strategy for Algorithmic Trading?

When I look back at the intraday chart at the end of the day, I can see different missed opportunities, but I am usually working on something else in the office while the market is open and I’m unable to act on them. Also, I feel like there could be even more opportunities if I could monitor a dozen stocks independently versus just looking at one stock in a day.

For example, by looking at TradingView chart for SPY with 20 minute simple moving average on 10/04/19 (below), I can see price crossover where I could have taken a small profit if I could have gotten in the position timely.

TradingView chart for SPY with 20 minute simple moving average on 10/04/09 (This is an example and is for illustrative purposes only. Past performance is not indicative of future results.)

With the scalping strategy, I don’t want to take a lot of profit, I just want to get a small upward price movement when it’s going up. But it is too hard for me to monitor even a few charts at the same time manually.

Automate This!

Python’s asyncio to Manage Algo Flow

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.

Starting with a Single Stock

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

Subscribing Websockets

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.

Focus on the State Transitions

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.

Concurrent algorithm

Scaling This to Multiple 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.

Putting Together

You Can Read the Full Code Here

alpacahq/example-scalping
A working example algorithm for scalping strategy trading multiple stocks concurrently using python asyncio - alpacahq/example-scalping

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,

$ 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'})

I hope this example gives you some idea of how to write an algorithm that trades multiple stocks concurrently. There are other frameworks such as pylivetrader, but I am personally liking this style using asyncio more now these days.

Maybe I will put something together for other people to re-use the structure so that you don’t need to start from scratch.

I am also working on how to test this script with the past market data to get more of an idea of how it has performed previously and how to iterate quickly (though keep in mind that past performance is not indicative of future results). I will share with you all when it is ready.


You can find us @AlpacaHQ, if you use twitter.

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.You can find us @AlpacaHQ, if you use twitter.