SPY Follows AAPL v2.0

This version of SPY Follows AAPL attempts to utilize Alpaca Markets’ web-socket for realtime data reception as opposed to spontaneous client-side data requests. The program is more a proof-of-concept than anything truly functional. Its fundamental premise is that $SPY follows $AAPL's tick changes, which is of course not always true and is not something I have proven in trialing this program.

The trading logic is quite simple.

main.py

import websocket, json, time
from config import *
from keys import alpaca_apiKey, alpaca_secretKey
from datetime import datetime as dt
from texts import *

closes = []
tradestaken = 0
socket = 'wss://stream.data.alpaca.markets/v2/iex'

def shutdownProtocol(ws):
    ws.keep_running = False
    print(f'Clock is closed so websocket is closed.')
    print('')
    account = api.get_account
    accountPercentChange = str(round(100*((float(account.equity) - float(account.last_equity))/float(account.last_equity)), 3)) + "%"
    print(f"Time for a daily update. Account % change today: {accountPercentChange}")
    response = Sms.send_message(
        {
            "from": sender,
            "to": mynumber,
            "text": f'Sir, our trading bot, SPY Follows AAPL, is done for the day. You may want to sit down '\
                f'before I tell you this. It has performed {accountPercentChange} in the entire account, sir.'
        }
    )
    print(f'{response = }')
    print('Quitting program as market is closed and text update has been sent.')
    quit()

def on_open(ws):
    auth_data = {
        "action": "auth",
        "key": alpaca_apiKey,
        "secret": alpaca_secretKey
    }
    ws.send(json.dumps(auth_data))

    subscribe_data = {
        "action": "subscribe",
        "bars": ["AAPL"]
    }
    ws.send(json.dumps(subscribe_data))

def on_message(ws, message):
    if clock.is_open == False:
        shutdownProtocol(ws)
    global closes
    print(message)
    data = json.loads(message) 
    if data[0]["T"] == "b": #if the message is a bar
        closes.append(data[0]["c"]) # add the close to closes
        lenCloses = len(closes)
        if lenCloses >= 6 and data[0]["c"] > ((closes[lenCloses - 1] + closes[lenCloses - 2] + closes[lenCloses - 3]\
             + closes[lenCloses -4] + closes[lenCloses -5])/5): #if the last close is higher than avg. of prev. 5
            buyTrigger() # buy SPY defined in config.py
        else:
            pass
    else:
        pass

ws = websocket.WebSocketApp(socket, on_open=on_open, on_message=on_message)


def main():
    print('Trading bot is online.')
    print('')
    while True:
        now = dt.now().strftime("%H-%M")
        if now == "06-35":
            ws.run_forever()
        else:
            time.sleep(1)

if __name__ == "__main__":
    main()

config.py

import alpaca_trade_api as tradeapi
import math
from main import tradestaken
from keys import alpaca_apiKey, alpaca_secretKey

base_url = 'https://paper-api.alpaca.markets'

api = tradeapi.REST(alpaca_apiKey, alpaca_secretKey, base_url)
clock = api.get_clock()
account = api.get_account()

leader = 'AAPL'
trigger = 'SPY'

def buyTrigger():
    global tradestaken
    spy_price = round(float(api.get_last_trade(trigger).price), 2) # SPY price
    api.submit_order(
        symbol=trigger,
        qty = math.floor(0.05*(math.floor(float(account.equity)/spy_price))), # 5% of account equity 
        side = 'buy',
        type = 'market',
        time_in_force='gtc',
        order_class = 'bracket', # 0.1% profit and loss
        stop_loss={'stop_price': spy_price * 0.999,
        'limit_price': spy_price * 0.998},
        take_profit={'limit_price': spy_price * 1.001}
    )
    print(f'Trade taken.')
    tradestaken = tradestaken + 1
    print(f'Total trades taken in runtime: {tradestaken}')

Other Requisites Files

texts.py contains phone numbers and client establishments from the Vonage API. It is referenced in main.py to send a text message with an account update on market close.

keys.py was omitted for obvious reasons.