Backtesting.py - Simultaneous trading of stocks - python

I have a following code using python library backtest.py now I want to backtest it over 2 stocks namely (AAPL & MSFT) from 01-01-2022 until 01-12-2022 and I want to make sure that if I buy AAPL stock on 01-01-2022 and sell it on 01-03-2022 then in the next buy which will be after 01-03-2022 the below code will look for the buy signal in both stocks again before making a trade.
Please assist!
from backtesting import Backtest, Strategy
from backtesting.lib import crossover
from backtesting.test import SMA
class SmaCross(Strategy):
def init(self):
price = self.data.Close
self.ma1 = self.I(SMA, price, 10)
self.ma2 = self.I(SMA, price, 20)
def next(self):
if crossover(self.ma1, self.ma2):
self.buy()
elif crossover(self.ma2, self.ma1):
self.sell()
bt = Backtest(AAPL, SmaCross, commission=.002,
exclusive_orders=True)
stats = bt.run()

Related

Python - error importing Nasdaq stock list

Im preparing a python code , to screen stocks from the SP500 , DOW and Nasdaq.
SP500 and DOW importing data stocks is working properly , but when I try to import Nasdaq always get similar error, related to timestamp.
See below:
My code:
import talib
from yahoo_fin.stock_info import get_data
import yahoo_fin.stock_info as si
from datetime import datetime
list = si.tickers_nasdaq()
# Get current date and time
now = datetime.now().strftime("%m_%d_%Y_%I_%M_%S")
end_date = datetime.now().strftime('%Y-%m-%d')
start_date = (datetime.now() - timedelta(days=365)).strftime('%Y-%m-%d')
# Create file to save results
f = open(f'C:/Users/fco_j/OneDrive/Escritorio/CHAT GPT/Python/Reports/dow_results_{now}.csv', 'w')
# print table header to file
f.write('Ticker, ClosePrice, SMA200, SMA20, RSI, RelVol\n')
# Define cache_data function
def cache_data(data, stock):
data.to_pickle(f'C:/Users/fco_j/OneDrive/Escritorio/CHAT GPT/Python/Pickle/{stock}.pkl')
for stock in list:
# Download historical data for past year
data = si.get_data(stock, start_date=start_date, end_date=end_date)
last_price = data["close"][-1]
# Get 150 and 20 simple moving averages using Talib
sma150 = talib.SMA(data['close'], timeperiod=150)[-1]
sma20 = talib.SMA(data['close'], timeperiod=20)[-1]
rsi = talib.RSI(data['close'], timeperiod=14)
# Calculate Relative Volume
rel_vol = data['volume'] / talib.SMA(data['volume'].values.astype(float), timeperiod = 50)
# Cache data
cache_data(data, stock)
# Filter stocks with relative volume (time period 20) over 1
if last_price > sma150 and last_price > sma20 and rsi[-1] > 50 and rel_vol[-1] > 1:
# Print results to file
f.write(f"{stock},{last_price},{sma150},{sma20},{rsi[-1]},{rel_vol[-1]}\n")
f.close()
The error:
KeyError Traceback (most recent call last)
~\AppData\Local\Temp/ipykernel_11208/2663596324.py in
26 for stock in dow_list:
27 # Download historical data for past year
---> 28 data = si.get_data(stock, start_date=start_date, end_date=end_date)
29 last_price = data["close"][-1]
30 # Get 150 and 20 simple moving averages using Talib
~\anaconda3\envs\PyFinance\lib\site-packages\yahoo_fin\stock_info.py in get_data(ticker, start_date, end_date, index_as_date, interval, headers)
98
99 # get the date info
--> 100 temp_time = data["chart"]["result"][0]["timestamp"]
101
102 if interval != "1m":
KeyError: 'timestamp'
The code is working with si.tickers_dow() and si.tickers_sp500() , but not with si.tickers_nasdaq() .
Not sure if a dataframe issue.

linear programing: find max number of shares of stock to purchase given a budget

The problem I am working on is this: say i have 10k, and I want to buy 3 stocks: AMZN, CBOE, CDW. and my goal is the calculate the max number of shares I can purchase and stay within 10k.
X*amzn_price+Y*cboe_price+Z*cdw_price <= 10000
I did the below code based on this example:
from scipy.optimize import linprog
import yfinance as yf
def linearProblem(dict):
obj = list(dict.values())
lhs_ineq = [obj]
rhs_ineq = [10000]
opt = linprog(c=obj, A_ub=lhs_ineq, b_ub=rhs_ineq, method = "revised simplex")
# Press the green button in the gutter to run the script.
def getPrice():
symbol_list = ['AMZN', 'CBOE', 'CDW']
symbol_list_dict = {}
for symbol in symbol_list:
ticker = yf.Ticker(symbol).info
market_price = ticker['regularMarketPrice']
symbol_list_dict[symbol] = market_price
return symbol_list_dict
if __name__ == '__main__':
symbol_price_dict = getPrice()
linearProblem(symbol_price_dict)
but the result is confusing with a fun of 0? I don't think I am inputting my equation right.
{'AMZN': 88.45, 'CBOE': 124.14, 'CDW': 184.2}
[88.45, 124.14, 184.2]
3
1
con: array([], dtype=float64)
fun: 0.0
message: 'Optimization terminated successfully.'
nit: 0
slack: array([10000.])
status: 0
success: True
x: array([0., 0., 0.])
I have used Google-ortool's CP-SAT solver. Please have a look at the below solution. You may add more constraints based on your requirements.
import string
import random
from ortools.sat.python import cp_model as cp
stocks = list(string.ascii_lowercase)
stock_price = {}
for stk in stocks:
stock_price[stk] = random.randint(1, 100)
# decision variables
dv_pick_stocks = {}
for stk in stocks:
dv_pick_stocks[stk] = model.NewBoolVar("")
# constraint
model.Add(sum(dv_pick_stocks[stk] * stock_price[stk] for stk in stocks) <= 500)
# decision variable to hold number of stocks selected
dv_num_stock_selected = model.NewIntVar(0, len(stocks), "")
model.Add(dv_num_stock_selected == sum(dv_pick_stocks[stk] for stk in stocks))
model.Maximize(dv_num_stock_selected)
solver = cp.CpSolver()
solver.Solve(model)
# inspect the solution
solver.Value(dv_num_stock_selected)
[(stk, stock_price[stk]) for stk in stocks if solver.Value(dv_pick_stocks[stk]) > 0]

Python - have a function output multiple data frames

I would like to achieve the following.
I would like the function to output a separate data frame for each stock for example
I would like to save each data frame to a separate csv
the code doesnt work and i am unable to output the and save the separate csvs. Can you help me output separate dataframes and export the respective CSVS?
def getdata(stock: str):
# Company Quote Group of Items
company_quote = requests.get(f"https://financialmodelingprep.com/api/v3/quote/{stock}?apikey=demo")
company_quote = company_quote.json()
share_price = float("{0:.2f}".format(company_quote[0]['price']))
# Balance Sheet Group of Items
BS = requests.get(f"https://financialmodelingprep.com/api/v3/income-statement/{stock}?period=quarter&limit=400&apikey=demo")
BS = BS.json()
#Total Debt
debt = float("{0:.2f}".format(float(BS[0]['totalDebt'])/10**9))
#Total Cash
cash = float("{0:.2f}".format(float(BS[0]['cashAndShortTermInvestments'])/10**9))
# Income Statement Group of Items
IS = requests.get(f"https://financialmodelingprep.com/api/v3/income-statement/{stock}?period=quarter&limit=400&apikey=demo")
IS = IS.json()
# Most Recent Quarterly Revenue
qRev = float("{0:.2f}".format(float(IS[0]['revenue'])/10**9))
# Company Profile Group of Items
company_info = requests.get(f"https://financialmodelingprep.com/api/v3/profile/{stock}?apikey=demo")
company_info = company_info.json()
# Chief Executive Officer
ceo = (company_info[0]['ceo'])
return (share_price, cash, debt, qRev, ceo)
stocks = ('AAPL')
d = {}
for stock in stocks:
df[stock] = pd.DataFrame(getdata, columns=['Share Price','Total Cash', 'Total Debt', 'Q3 2019 Revenue', 'CEO'], index=tickers)
print(d)
I'm not sure to understand your question. But if the fact is that you want a different DataFrame for each ticker, here is a solution.
Instead of :
for stock in stocks:
df[stock] = pd.DataFrame(....
Try:
for stock in stocks:
globals()['df_%s' %stock] = pd.DataFrame(...
# And to save it, inside the loop
globals()['df_%s' %stock].to_csv(stock+'.csv')
EDIT:
Thanx for the add. Here is the code
import joblib
from joblib import Parallel,delayed
import requests
import pandas as pd
def getdata(stock):
# Company Quote Group of Items
company_quote = requests.get(f"https://financialmodelingprep.com/api/v3/quote/{stock}?apikey=demo")
company_quote = company_quote.json()
share_price = float("{0:.2f}".format(company_quote[0]['price']))
# Balance Sheet Group of Items
BS = requests.get(f"https://financialmodelingprep.com/api/v3/balance-sheet-statement/{stock}?period=quarter&limit=400&apikey=demo")
BS = BS.json()
#Total Debt
debt = float("{0:.2f}".format(float(BS[0]['totalDebt'])/10**9))
#Total Cash
cash = float("{0:.2f}".format(float(BS[0]['cashAndShortTermInvestments'])/10**9))
# Income Statement Group of Items
IS = requests.get(f"https://financialmodelingprep.com/api/v3/income-statement/{stock}?period=quarter&limit=400&apikey=demo")
IS = IS.json()
# Most Recent Quarterly Revenue
qRev = float("{0:.2f}".format(float(IS[0]['revenue'])/10**9))
# Company Profile Group of Items
company_info = requests.get(f"https://financialmodelingprep.com/api/v3/profile/{stock}?apikey=demo")
company_info = company_info.json()
# Chief Executive Officer
ceo = (company_info[0]['ceo'])
globals()['df_%s' %stock] = pd.DataFrame({'symbol':[stock],'debt':[debt],'cash':[cash],'qRev':[qRev],'ceo':[ceo]})
globals()['df_%s' %stock].to_csv(stock+'.csv')
return(globals()['df_%s' %stock])
stocks = ['AAPL'] #, 'MSFT', 'GOOG', 'T', 'CSCO', 'INTC', 'ORCL', 'AMZN', 'FB', 'TSLA', 'NVDA']
number_of_cpu = joblib.cpu_count()
delayed_funcs = [delayed(getdata)(stock) for stock in stocks]
parallel_pool = Parallel(n_jobs=number_of_cpu,prefer="processes") # processes threads
globals()['df_%s' %stock] = parallel_pool(delayed_funcs)
df_AAPL
OUTPUT
It isn't necessary to return the DataFrame, as you save it in the function. But I did it to show you the possibility.

Improving a stock market algorithm

I am trying to make this code look more attractive for potential employers that view it on my GitHub account. The code essentially loops through a CSV file and searches each symbol with the yfinance wrapper for the Yahoo-Finance API. It makes a few checks about the stock and decides whether it is a suitable investment. There are many try except clauses since API can return empty fields in the pandas dataframe. Currently I think it can be improved since it has multiple nested if statements with many try except statements. All feedback is greatly appreciated.
import yfinance as yf
import pandas as pd
import openpyxl
import csv
import math
import traceback
# Not a penny stock
# Earnings increase of at least 33% over 10 years using 3 year averages - 10% over 4 years since the API only contains the most recent 4 years
# Current price no more than 1.5x book value per share
# P/E ratio <= 15
# Long term debt no more than 110% current assets
# Current assets 1.5x current liabilities
symbol_array = []
failed_search = []
with open('companylist.csv') as file:
reader = csv.reader(file)
ticker_data = iter(reader) # skip the first value since it is the header
next(ticker_data)
for row in ticker_data:
ticker = row[0]
print('Searching: ', ticker)
try:
try:
company = yf.Ticker(ticker)
company_info = company.info
except:
print('Not a company')
continue # skip the ticker since it is not a company or the API doesn't have any information about the security
company_balance_sheet = company.balance_sheet
company_earnings = company.earnings
if company_balance_sheet.empty or company_earnings.empty:
continue # if balance sheets or earnings reports are not available, skip the search
column_date = company.balance_sheet.columns[0] # latest date on balance sheet to take data from
current_assets = company.balance_sheet.at['Total Current Assets', column_date]
try: # previous close price can be under 'previousClose' or 'regularMarketPrice' in company_info
current_price = company_info['previousClose']
except:
current_price = company_info['regularMarketPrice']
if current_price >= 10: # check if stock is penny stock
try:
long_term_debt = company.balance_sheet.at['Long Term Debt', column_date]
if math.isnan(long_term_debt):
long_term_debt = 0
except:
long_term_debt=0
if long_term_debt < (current_assets * 1.1):
current_liabilities = company.balance_sheet.at['Total Current Liabilities', column_date]
if current_liabilities < (1.5 * current_assets):
try:
pe_ratio = company_info['trailingPE'] # check if P/E ratio is available, assign pe_ratio 0 if it is not
except:
pe_ratio = 0
if pe_ratio <= 15:
try:
book_value = company_info['bookValue']
if type(book_value) != float: # book_value can be "None" in the company_info object
book_value = 0
except:
book_value = 0
if current_price < (book_value*1.5):
earnings_first = company.earnings.iat[0, 1]
earnings_last = company.earnings.iat[len(company.earnings)-1, 1]
if earnings_last >= earnings_first*1.1:
symbol_array.append(company_info['symbol'])
else:
print('Step 6 fail. Earnings growth too low')
else:
print('Step 5 fail. Current price too high')
else:
print('Step 4 fail. P/E ratio too high')
else:
print('Step 3 fail. Current liabilities too high')
else:
print('Step 2 fail. Long term debt too high')
else:
print('Step 1 fail. Penny stock')
except Exception as e:
print(traceback.format_exc()) # code to point out any errors in the main try statement
failed_search.append(ticker)
print(ticker, ' failed to search.')
print(e)
print('Failed searches:')
for failure in failed_search:
print(failure)
print('Potential Investments:')
for symbol in symbol_array:
print(symbol)

Failing to obtain correct accrued interest with QuantLib Inflation Bond pricer in Python

I am trying to price an inflation bond using QuantLib-Python. I seem to be producing numbers very close to Reuters and Bloomberg when I compare my data, however, I can not get the accrued interest to match.
Both Reuters and Bloomberg are showing 0.062230211, however, my code is producing 0.062285744677396664. I have tried modifying my daycount assumptions, settlement day assumptions and a few other things to no avail.
import QuantLib as ql
import datetime as dt
import numpy as np
import pandas as pd
calendar = ql.UnitedKingdom()
lag = 3
today = ql.Date(23, 8, 2019)
evaluationDate = calendar.advance(today, 1, ql.Days)
dayCounter = ql.ActualActual(ql.ActualActual.ISMA)
convention = ql.ModifiedFollowing
lag = 3
# ISIN: GB00BDX8CX86
# inflation fixing lag period (3mths for gilts)
issue_date = ql.Date(25, 9, 2013)
maturity_date = ql.Date(22, 3, 2068)
fixing_date = calendar.advance(evaluationDate,-lag, ql.Months)
observationLag = ql.Period(lag, ql.Months)
# assign evaluation date
ql.Settings.instance().setEvaluationDate(evaluationDate)
# derives a quantlib yield term structure for discounting
yTS = ql.YieldTermStructureHandle(ql.FlatForward(evaluationDate, 0.01033692, dayCounter))
# define period gap
tenor = ql.Period(1, ql.Months)
# define RPI schedule within Quantlib
from_date = ql.Date(31, ql.August, 2018);
to_date = ql.Date(31, ql.July, 2019);
convention = ql.Unadjusted
rpiSchedule = ql.Schedule(from_date, to_date, tenor, calendar,
convention, convention,
ql.DateGeneration.Backward, True)
# manually input RPI fixings for now
fixData = [284.2, 284.1, 284.5, 284.6, 285.6,
283, 285, 285.1, 288.2, 289.2, 289.6, 289.5]
dte_fixings=[dtes for dtes in rpiSchedule]
# this is the going to be holder of the zero swap inflation curve.
cpiTS = ql.RelinkableZeroInflationTermStructureHandle()
inflationIndex = ql.UKRPI(False, cpiTS)
#current interpolated RPI rate
fixing_rate = 289.53548
inflationIndex.addFixing(fixing_date, fixing_rate)
zciisData = [(ql.Date(31,7,2020), 3.1500000000137085),
(ql.Date(31,7,2021), 3.547500000013759),
(ql.Date(31,7,2022), 3.675000000013573),
(ql.Date(31,7,2023), 3.7250000000134342),
(ql.Date(31,7,2024), 3.750000000013265),
(ql.Date(31,7,2025), 3.7430000000129526),
(ql.Date(31,7,2026), 3.741200000012679),
(ql.Date(31,7,2027), 3.7337000000123632),
(ql.Date(31,7,2028), 3.725000000011902),
(ql.Date(31,7,2029), 3.720000000011603),
(ql.Date(31,7,2030), 3.712517289063011),
(ql.Date(31,7,2031), 3.7013000000108764),
(ql.Date(31,7,2032), 3.686986039205209),
(ql.Date(31,7,2033), 3.671102614032895),
(ql.Date(31,7,2034), 3.655000000009778),
(ql.Date(31,7,2035), 3.6394715951305834),
(ql.Date(31,7,2036), 3.624362044800966),
(ql.Date(31,7,2037), 3.6093619727979087),
(ql.Date(31,7,2038), 3.59421438364369),
(ql.Date(31,7,2039), 3.5787000000081948),
(ql.Date(31,7,2040), 3.5626192748395624),
(ql.Date(31,7,2041), 3.545765016376823),
(ql.Date(31,7,2042), 3.527943521613608),
(ql.Date(31,7,2043), 3.508977137925462),
(ql.Date(31,7,2044), 3.48870000000685),
(ql.Date(31,7,2045), 3.467083068721011),
(ql.Date(31,7,2046), 3.4445738220594935),
(ql.Date(31,7,2047), 3.4216470902302065),
(ql.Date(31,7,2048), 3.3986861494999188),
(ql.Date(31,7,2049), 3.376000000005752),
(ql.Date(31,7,2050), 3.3538412080641233),
(ql.Date(31,7,2051), 3.3324275806807746),
(ql.Date(31,7,2052), 3.311938788306623),
(ql.Date(31,7,2053), 3.2925208131865835),
(ql.Date(31,7,2054), 3.274293040759302),
(ql.Date(31,7,2055), 3.2573541974782794),
(ql.Date(31,7,2056), 3.241787355503245),
(ql.Date(31,7,2057), 3.227664186159851),
(ql.Date(31,7,2058), 3.2150486140060774),
(ql.Date(31,7,2059), 3.204000000004159),
(ql.Date(31,7,2060), 3.1945334946674064),
(ql.Date(31,7,2061), 3.1865047145143377),
(ql.Date(31,7,2062), 3.179753073456304),
(ql.Date(31,7,2063), 3.1741427790361154),
(ql.Date(31,7,2064), 3.1695593261025223),
(ql.Date(31,7,2065), 3.1659065919088736),
(ql.Date(31,7,2066), 3.163104428386987),
(ql.Date(31,7,2067), 3.1610866681252903),
(ql.Date(31,7,2068), 3.1597994770515836),
(ql.Date(31,7,2069), 3.159200000003204),
(ql.Date(31,7,2070), 3.159242349440139),
(ql.Date(31,7,2071), 3.1598400898057433),
(ql.Date(31,7,2072), 3.16090721831932),
(ql.Date(31,7,2073), 3.162369676612098),
(ql.Date(31,7,2074), 3.1641636543027207)]
# bootstrap the inflation swap curve (quantlib)
zeroSwapHelpers = [ql.ZeroCouponInflationSwapHelper(rate / 100,observationLag,
date, calendar, convention, dayCounter, inflationIndex) for date,rate in zciisData]
# the derived inflation curve
jj=ql.PiecewiseZeroInflation(evaluationDate, calendar, dayCounter, observationLag,\
inflationIndex.frequency(), inflationIndex.interpolated(),\
zciisData[0][1],#baseZeroRate,\
yTS, zeroSwapHelpers, 1.0e-12, ql.Linear())
cpiTS.linkTo(jj)
notional = 1000000
# real coupon of bond
fixedRates = [0.125 / 100]
# bond settings
fixedDayCounter = ql.ActualActual(ql.ActualActual.ISMA)
fixedPaymentConvention = ql.ModifiedFollowing
fixedPaymentCalendar = ql.UnitedKingdom()
contractObservationLag = ql.Period(3, ql.Months)
# how do you observe the index? as-is, AsIndex, Flat, Linear?
observationInterpolation = ql.CPI.Flat
settlementDays = 1
# Boolean switch
# - only price elements of bond that appreciate in value due to inflation
growthOnly = False
# base CPI fixing (generated on bond issue date)
baseCPI = 249.7
# generate quantlib schedule
fixedSchedule = ql.Schedule(issue_date,
maturity_date,
ql.Period(ql.Semiannual),
fixedPaymentCalendar,
ql.ModifiedFollowing,
ql.ModifiedFollowing,
ql.DateGeneration.Backward,
False)
# create bond within quantlib library
bond = ql.CPIBond(settlementDays,
notional,
growthOnly,
baseCPI,
contractObservationLag,
inflationIndex,
observationInterpolation,
fixedSchedule,
fixedRates,
fixedDayCounter,
fixedPaymentConvention)
# assign bond pricing engine and set pricing within quantlib
bondEngine=ql.DiscountingBondEngine(yTS)
bond.setPricingEngine(bondEngine)
# calc bond yield
print(bond.NPV())
print(bond.cleanPrice())
compounding = ql.Compounded
yield_rate = bond.bondYield(fixedDayCounter,compounding,ql.Semiannual)
y_curve = ql.InterestRate(yield_rate,fixedDayCounter,compounding,ql.Semiannual)
print(y_curve)
##Collate results
print("Clean Price:", bond.cleanPrice())
print("Dirty Price:", bond.dirtyPrice())
print("Notional:", bond.notional())
print("Yield:", yield_rate)
print("Accrual Period", ql.BondFunctions.accrualPeriod(bond, ql.Date(27,8,2019)))
print("Accrued Period", ql.BondFunctions.accruedPeriod(bond, ql.Date(27,8,2019)))
print("Accrued Days", ql.BondFunctions.accruedDays(bond, ql.Date(27,8,2019)))
print("Accrued Amount:", bond.accruedAmount())
print("Settlement Value:", bond.settlementValue())
Expecting accrued interest : 0.062230211
Actual accrued interest: 0.062285744677396664

Categories