├── .gitignore ├── PYX.py ├── README.md └── requirements.txt /.gitignore: -------------------------------------------------------------------------------- 1 | venv/ 2 | datetime 3 | .*~ 4 | *.db 5 | -------------------------------------------------------------------------------- /PYX.py: -------------------------------------------------------------------------------- 1 | # PYX (Python Exchange) 2 | # Created by Logicmn 3 | # Started 11/19/16 4 | 5 | # ----------------------------------------- 6 | # | Real-time stock trading program using | 7 | # | a basic mean reversion algorithm | 8 | # ----------------------------------------- 9 | 10 | #-------------------------------------Dependencies and database link------------------------------------------- 11 | import datetime 12 | from yahoo_finance import Share 13 | 14 | from sqlalchemy.ext.declarative import declarative_base 15 | from sqlalchemy import Column, Integer, String, Sequence, MetaData, create_engine 16 | from sqlalchemy.orm import sessionmaker 17 | 18 | engine = create_engine('sqlite:///new_db.db', echo=True) # Link the database to the SQLAlchemy engine 19 | Session = sessionmaker(bind=engine) 20 | Base = declarative_base() 21 | metadata = MetaData() 22 | session = Session() 23 | #-------------------------------------------------------------------------------------------------------------- 24 | 25 | #-------------------------------------Creating tables for database-------------------------------------------- 26 | class Wallet(Base): # Create 'wallets' table 27 | __tablename__ = 'wallets' 28 | 29 | id = Column(Integer, Sequence('wallet_id_seq'), primary_key=True) 30 | name = Column(String) 31 | balance = Column(Integer) 32 | 33 | def __repr__(self): 34 | return "" % (self.name, self.balance) 35 | 36 | class Transaction(Base): # Create 'transactions' table 37 | __tablename__ = 'transactions' 38 | 39 | id = Column(Integer, Sequence('transaction_id_seq'), primary_key=True) 40 | stock = Column(String(50)) 41 | symbol = Column(String(50)) 42 | buy_or_sell = Column(String(50)) 43 | price = Column(Integer()) 44 | ema = Column(Integer()) 45 | shares = Column(Integer()) 46 | time = Column(String(50)) 47 | 48 | def __repr__(self): 49 | return ""\ 50 | % (self.stock, self.symbol, self.buy_or_sell, self.price, self.ema, self.shares, self.time) 51 | #-------------------------------------------------------------------------------------------------------------- 52 | 53 | #----------------------------------------Creating mean reversion algorithm------------------------------------- 54 | class Strategy(object): # Create the algorithm PYX will use to trade with 55 | def __init__(self, equity): 56 | self.equity = equity 57 | 58 | def getEquity(self): 59 | return self.equity 60 | 61 | def calcEMA(self, close_price, prev_ema): # Calculate the exponential moving average 62 | multiplier = 2 / (50 + 1) 63 | ema = (close_price - prev_ema) * multiplier + prev_ema 64 | return ema 65 | 66 | def calcUpper(self, ema): # Calculate the upper Bollinger band 67 | upper_band = ema * (1 + .02) 68 | return upper_band 69 | 70 | def calcLower(self, ema): # Calculate the lower Bollinger band 71 | lower_band = ema * (1 - .02) 72 | return lower_band 73 | #-------------------------------------------------------------------------------------------------------------- 74 | 75 | #------------------------------------------Buy/sell shares of a stock------------------------------------------ 76 | def enter_position(mean_reversion, stock): # Buy shares of a stock 77 | close_price, prev_ema, ema, lower_band, upper_band, purchase_query = calculations(mean_reversion, stock) 78 | if purchase_query != None: 79 | purchase = purchase_query[0] 80 | else: 81 | purchase = 'sell' 82 | if float(stock.get_price()) <= lower_band and purchase != 'buy': # Buy shares if the last purchase was a sell 83 | print('buy') 84 | new_transaction = Transaction(stock=stock.get_name(), symbol=stock.symbol, buy_or_sell='buy', 85 | price=stock.get_price(), 86 | ema=ema, shares='100', time=datetime.datetime.now()) 87 | session.add(new_transaction) 88 | session.commit() 89 | balance, new_funds = calc_wallet() 90 | new_bal = balance[0] - new_funds # Subtract amount spent from the balance in wallet 91 | primary_wallet = Wallet(name='Primary Wallet', balance=new_bal) # Re-create wallet with new balance 92 | session.add(primary_wallet) 93 | session.commit() 94 | 95 | 96 | def exit_position(mean_reversion, stock): # Sell shares of a stock 97 | close_price, prev_ema, ema, lower_band, upper_band, purchase_query = calculations(mean_reversion, stock) 98 | if purchase_query != None: 99 | purchase = purchase_query[0] 100 | elif purchase_query == None: 101 | purchase = 'sell' 102 | else: 103 | purchase = 'buy' 104 | if float(stock.get_price()) >= upper_band and purchase != 'sell': # Sell shares if the last purchase was a buy 105 | print('sell') 106 | new_transaction = Transaction(stock=stock.get_name(), symbol=stock.symbol, buy_or_sell='sell', 107 | price=stock.get_price(), 108 | ema=ema, shares='100', time=datetime.datetime.now()) 109 | session.add(new_transaction) 110 | session.commit() 111 | balance, new_funds = calc_wallet() 112 | new_bal = balance[0] + new_funds # Add amount gained to the balance in wallet 113 | primary_wallet = Wallet(name='Primary Wallet', balance=new_bal) # Re-create wallet with new balance 114 | session.add(primary_wallet) 115 | session.commit() 116 | #-------------------------------------------------------------------------------------------------------------- 117 | 118 | #-------------------------------------------------Calculations------------------------------------------------- 119 | def calculations(mean_reversion, stock): 120 | close_price = float(stock.get_prev_close())# Calculate yesterdays close price 121 | prev_ema = float(stock.get_50day_moving_avg()) # Calculate the previous EMA 122 | ema = mean_reversion.calcEMA(close_price, prev_ema) # Calculate the EMA 123 | lower_band, upper_band= float(mean_reversion.calcLower(ema)), float(mean_reversion.calcUpper(ema)) # Calculate the bands 124 | print("-------------------------") 125 | purchase_query = session.query(Transaction.buy_or_sell).order_by( 126 | Transaction.id.desc()).first() # Find out whether the latest purchase was a buy/sell 127 | return close_price, prev_ema, ema, lower_band, upper_band, purchase_query 128 | 129 | def calc_wallet(): 130 | new_price = session.query(Transaction.price).order_by(Transaction.id.desc()).first() # Grab the bought price 131 | new_shares = session.query(Transaction.shares).order_by(Transaction.id.desc()).first() # Grab how many shares were bought 132 | new_funds = new_price[0] * new_shares[0] # Calculate the money spent 133 | balance = session.query(Wallet.balance).one() # Grab the current balance 134 | current_bal = session.query(Wallet).one() 135 | session.delete(current_bal) # Delete the wallet 136 | session.commit() 137 | return balance, new_funds 138 | #-------------------------------------------------------------------------------------------------------------- 139 | 140 | #-------------------------------------------------Main function------------------------------------------------ 141 | def main(): 142 | symbol = 'AAPL' # Which stock to monitor and invest in 143 | stock = Share(symbol) 144 | if stock.get_price() is None: 145 | print("Error : {} has no price history".format(symbol)) 146 | exit(1) 147 | print(stock.get_price()) 148 | Base.metadata.create_all(engine) 149 | session.commit() 150 | b = session.query(Wallet.balance).first() # Check if there is already a wallet 151 | if b == None: 152 | primary_wallet = Wallet(name='Primary Wallet', balance=100000) # Create the wallet with a balance of $100,000 153 | session.add(primary_wallet) 154 | session.commit() 155 | mean_reversion = Strategy(symbol) # Run the EMA, and Bollinger band calculations 156 | enter_position(mean_reversion, stock) # Buy stock if applicable 157 | exit_position(mean_reversion, stock) # Sell stock if applicable 158 | session.commit() 159 | #-------------------------------------------------------------------------------------------------------------- 160 | 161 | #----Runs the program----- 162 | if __name__ == "__main__": 163 | main() 164 | #------------------------- 165 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PYX (Python Exchange) 2 | *A program that simulates real-time stock trading using a basic mean reversion algorithm.* 3 | 4 | **What is PYX?** 5 | --------------------------------- 6 | PYX is a flexible program that simulates the trading of equity using different algorithms. The algorithm currently being used is a [mean reversion](http://www.investopedia.com/terms/m/meanreversion.asp?lgl=no-infinite) strategy. PYX buys and sells shares of stock based on the price compared to its upper and lower bollinger bands. 7 | 8 | **What PYX does:** 9 | 10 | 1. Creates a database ([SQLAlchemy](http://www.sqlalchemy.org/)) for storing each transaction and wallet 11 | 2. Creates a wallet from which simulated funds will be added to and subtracted from 12 | 3. Calculates the 50 day [exponential moving average](http://www.investopedia.com/terms/e/ema.asp?lgl=no-infinite) for specified stock 13 | 4. Calculates the upper and lower bollinger bands based on the 50 day EMA 14 | 5. Checks if the price of specified stock is `>=` or `<=` the upper and lower bollinger bands respectively 15 | 6. If the share price is `<=` the lower bollinger band, the program will buy shares 16 | 7. If the share price is `=>` the upper bollinger band, the program will sell previously bought shares 17 | 8. Each transaction is logged in a database `transaction_database.db` with the following information: 18 | `id|stock|symbol|buy/sell|price|ema|shares|date` 19 | 20 | **How does it work?** 21 | --------------------------------- 22 | 23 | **Mean Reversion:** 24 | 25 | Mean reversion is the theory suggesting that prices and returns eventually move back toward the mean or average. This mean that PYX utilizes is called a 50 day exponential moving average. 26 | 27 | **Dependencies:** 28 | 29 | PYX relies on two main dependencies, `yahoo_finance` and `sqlalchemy`. PYX also utilizes Python 3.5's built in `datetime` module. 30 | 31 | To install these modules with PyPI, first install [pip](https://pip.pypa.io/en/stable/installing/) then in cmd or bash run: 32 | 33 | `pip install yahoo_finance` 34 | 35 | `pip install sqlalchemy` 36 | 37 | Once the packages have unpacked and installed, the program is good to run. 38 | 39 | **Calculations:** 40 | 41 | Equation to calculate the 50 day EMA: 42 | 43 | ``` 44 | multiplier = 2 / (50 + 1) 45 | EMA = (closePrice - prevEMA) * multiplier + prevEMA 46 | ``` 47 | Equation to calculate upper bollinger band: 48 | 49 | `upperBand = EMA * (1 + .02)` 50 | 51 | Equation to calculate the lower bollinger band: 52 | 53 | `lowerBand = EMA * (1 - .02)` 54 | 55 | Adjust the `.02` to raise and lower the bollinger bands. The higher you raise the number to, the less likely it will be stock will be bought. 56 | 57 | Example of Bollinger bands: 58 | 59 | ![Bollinger bands](http://i.imgur.com/5qoaeKo.jpg "Bollinger bands") 60 | 61 | **Database:** 62 | 63 | A database with two tables is created on the first initialization of the program, `wallets` and `transactions`. If there is no database transactions_database.db already, one will be created. A wallet containing a name and a balance will also be created and inserted into `wallets` the first time the program is run. To change the wallets balance simply edit `balance='100000'` in the `main()` function. 64 | 65 | Everytime shares of a stock are bought and sold a row is added to the `transactions` database, code below. 66 | ``` 67 | new_transaction = Transaction(stock='Apple', symbol=apple.get_info()['symbol'], buy_or_sell='', 68 | price=apple.get_price(), 69 | ema=EMA, shares='100', time=datetime.datetime.now()) 70 | session.add(new_transaction) 71 | session.commit() 72 | ``` 73 | To adjust the number of shares being bought, change the `shares='100'` to however many shares you want the program to buy. 74 | 75 | **Running PYX on a server** 76 | --------------------------------- 77 | The best way to run PYX and test its capabilites is to run it from a server. By utilizing a time-based job scheduler like [Cron](https://en.wikipedia.org/wiki/Cron) you can run PYX every minute, hour, or even day. The more often you run it the lower the standard deviation for each bollinger band should be, this will produce more results. 78 | 79 | **DISCLAIMER** 80 | --------------------------------- 81 | *This program does not spend real money, nor does it buy real shares of stocks. It is simply a simulation.* 82 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | yahoo-finance 2 | sqlalchemy 3 | 4 | --------------------------------------------------------------------------------