I am looking to implement Piotroski's F-score Value strategy discussed in the paper "Piotroski, J. D. (2000). Value investing: The use of historical financial statement information to separate winners from losers. Journal of Accounting Research, 38, 1-41." which lists 9 criteria for judging a company:
# 1 - Positive Net Income (1 point) Cash-flow
# 2 - Positive return on assets (net income / total assets) in the current year (1 pnt) Cash-flow & Balance Sheet
# 3 - Positive operating cash flow (NI + DeprExp + chgReceive + chgInven) for current year (1 pnt) Cash-flow
# 4 - Cash flow from operations being greater than net Income (quality of earnings) (1 pnt)
# Leverage, Liquidity and Source of Funds Criteria:
# 5 - Lower ratio of long term debt in the current period, Balance Sheet
# compared to the previous year (decreased leverage) (1 pnt)
# 6 - Higher current ratio this year compared to the previous year (more liquidity) (1 pnt) Balance Sheet
# 7 - No new shares were issued in the last year (lack of dilution) (1 pnt). Key Stats
# Operating Efficiency Criteria:
# 8 - A higher gross margin compared to the previous year (1 pnt)
# 9 - A higher asset turnover ratio compared to the previous year (1 pnt)
# Stocks with 8 or 9 points should be bought, while stocks with 3 points or less should be sold short.
I am working with the IEXfinance library in python to pull data from a company's financial sheets to determine if they meet this criteria (although I might switch to scrapping it from the EDGAR system if that proves to be a bit quicker because the IEXfinance has parts behind a pay-wall. Anyhow, I am having trouble determining if the data returned from IEXfinance is the same as what I am looking for because the terms are slightly different.
Pics of their data: Balance Sheet
On the Balance Sheet, I can see the data needed for 1,2,5,6.
Operating cash flow. is defined as Net Income - Depreciation Expense + Change in Receivables + Change in Inventory which I can see on the Cash Flow Report. So 3 is met.
My questions are this: -Is the cash flows from operations used in #4, the positive operating cash flow we calculate in 3? -Would share equity remaining the same mean that no new shares were issued? I would guess no because the stock price can change making this not the right number. In key_stats, a data point is named shares_outstanding which is what I am guessing I am looking for, yes? One website argues that the "Weighted Average Shares" number should be checked. - I have Gross profit for number 8 but not total sales. Is comparing Gross Profit across years the equivalent? -#9 stumps me. Here is very rough code:
# This is an implementation of some of the stock-selection principles outlined here:
# -Amor-Tapia, B. & Tascón, M.T. (2016). Separating winners from losers: Composite indicators
# based on fundamentals in the European context *. Finance a Uver,66(1), 70-94.
# -Piotroski, J. D. (2000). Value investing: The use of historical financial statement information
# to separate winners from losers. Journal of Accounting Research, 38, 1-41.
# No guarantees are provided about the performance of these principles as described
# or as implemented here. As with any strategy, you should validate its performance
# with backtesting and forward testing before committing to its use.
#
# List of 9 Selection Criteria
# Profitability Criteria:
# 1 - Positive Net Income (1 point) Cash-flow
# 2 - Positive return on assets (net income / total assets) in the current year (1 pnt) Cash-flow & Balance Sheet
# 3 - Positive operating cash flow (NI + DeprExp + chgReceive + chgInven) for current year (1 pnt) Cash-flow
# 4 - Cash flow from operations being greater than net Income (quality of earnings) (1 pnt)
# Leverage, Liquidity and Source of Funds Criteria:
# 5 - Lower ratio of long term debt in the current period, Balance Sheet
# compared to the previous year (decreased leverage) (1 pnt)
# 6 - Higher current ratio this year compared to the previous year (more liquidity) (1 pnt) Balance Sheet
# 7 - No new shares were issued in the last year (lack of dilution) (1 pnt). Key Stats
# Operating Efficiency Criteria:
# 8 - A higher gross margin compared to the previous year (1 pnt)
# 9 - A higher asset turnover ratio compared to the previous year (1 pnt)
# Stocks with 8 or 9 points should be bought, while stocks with 3 points or less should be sold short.
#
"""
#################################################################################################################
# Imports to set-up our Alpaca login
import os
import sys
import yaml
import json
# Imports for Alpaca specific libraries
import alpaca_trade_api as tradeapi
# Imports for IEXfinance stock data
from iexfinance.base import _IEXBase
from iexfinance.stocks import Stock
from urllib.parse import quote
#
import pandas as pd
class PiotroskiFScoreValueTrader:
# --------------------------------------------------------------------------------------------------
def __init__(self):
# Connect to the website and make sure we are good to trade.
self.api = self.login()
# Get the complete list of stocks we want to check.
self.tradeable = self.get_tradeable_assets()
# Get the relevant data we need for each stock.
# --------------------------------------------------------------------------------------------------
# --------------------------------------------------------------------------------------------------
def login(self):
# We retrieve our login info for the Alpaca API from the config.yml file here and then login.
with open(os.path.join(sys.path[0], 'config.yml'), 'r') as f:
config = yaml.load(f)
# These are the params we need to send to the Alpaca API
base_url = config['base_url']
key_id = config['key_id']
secret = config['secret']
api = tradeapi.REST(base_url=base_url, key_id=key_id, secret_key=secret)
if api.get_account().status != 'ACTIVE':
raise RuntimeError("Error: Account is not active. Account is: ", api.get_account().status)
return api
# --------------------------------------------------------------------------------------------------
# --------------------------------------------------------------------------------------------------
def get_tradeable_assets(self):
symbols = []
possible_assets = self.api.list_assets()
for asset in possible_assets:
temp_dict = vars(asset)
if temp_dict['_raw']['status'] == 'active':
symbols.append(temp_dict['_raw']['symbol'])
return sorted(symbols)
# --------------------------------------------------------------------------------------------------
# --------------------------------------------------------------------------------------------------
def get_asset_data(self):
# IEX doesn't like batch queries for more than 100 symbols at a time.
# We need to build our fundamentals info iteratively.
batch_idx = 0
batch_size = 99
fundamentals_dict = {}
while batch_idx < len(self.tradeable):
symbol_batch = [s['symbol']
for s in self.tradeable[batch_idx:batch_idx + batch_size]]
stock_batch = Stock(symbol_batch)
# Pull all the data we'll need from IEX.
financials_json = stock_batch.get_financials()
quote_json = stock_batch.get_quote()
stats_json = stock_batch.get_key_stats()
earnings_json = stock_batch.get_earnings()
for symbol in symbol_batch:
# We'll filter based on earnings first to keep our fundamentals
# info a bit cleaner.
if not self.positive_return_on_assets(earnings_json[symbol]):
continue
# Make sure we have all the data we'll need for our filters for
# this stock.
if not self.data_quality_good(
symbol,
financials_json,
quote_json,
stats_json):
continue
fundamentals_dict[symbol] = self.get_fundamental_data_for_symbol(
symbol,
financials_json,
quote_json,
stats_json
)
batch_idx += batch_size
# Transform all our data into a more filterable form - a dataframe - with
# a bit of pandas magic.
return pd.DataFrame.from_dict(fundamentals_dict).T
# Stock.get_company() is one way to get sector.
# --------------------------------------------------------------------------------------------------
# --------------------------------------------------------------------------------------------------
# Want to mimic if not eps.good and data quality good to filter out stocks we do not want to
# bother with. Then we want to get the required data so we can filter our stocks.
# --------------------------------------------------------------------------------------------------
# --------------------------------------------------------------------------------------------------
# Return on Assets = Net Income / Total Assets
def positive_return_on_assets(self, earnings_reports):
# This method contains logic for filtering based on earnings reports.
if len(earnings_reports) < 4:
# The company must be very new. We'll skip it until it's had time to
# prove itself.
return False
# earnings_reports should contain the information about the last four
# quarterly reports.
for report in earnings_reports:
# We want to see consistent positive EPS.
try:
if not (report['actualEPS']):
return False
if report['actualEPS'] < 0:
return False
except KeyError:
# A KeyError here indicates that some data was missing or that a company is
# less than two years old. We don't mind skipping over new companies until
# they've had more time in the market.
return False
return True
# --------------------------------------------------------------------------------------------------
# --------------------------------------------------------------------------------------------------
def data_quality_good(self, symbol, financials_json, quote_json, stats_json):
# This method makes sure that we're not going to be investing in
# securities we don't have accurate data for.
if len(financials_json[symbol]
) < 1 or quote_json[symbol]['latestPrice'] is None:
# No recent data was found. This can sometimes happen in case of recent
# market suspensions.
return False
try:
if not (
quote_json[symbol]['marketCap'] and
stats_json[symbol]['priceToBook'] and
stats_json[symbol]['sharesOutstanding'] and
financials_json[symbol][0]['totalAssets'] and
financials_json[symbol][0]['currentAssets'] and
quote_json[symbol]['latestPrice']
):
# Ignore companies IEX cannot report all necessary data for, or
# thinks are untradable.
return False
except KeyError:
# A KeyError here indicates that some data we need to evaluate this
# stock was missing.
return False
return True
# --------------------------------------------------------------------------------------------------
# --------------------------------------------------------------------------------------------------
def get_fundamental_data_for_symbol(self, symbol, financials_json,
quote_json, stats_json):
fundamentals_dict_for_symbol = {}
financials = financials_json[symbol][0]
# Calculate PB ratio.
fundamentals_dict_for_symbol['pb_ratio'] = stats_json[symbol]['priceToBook']
# Find the "Current Ratio" - current assets to current debt.
current_debt = financials['currentDebt'] if financials['currentDebt'] else 1
fundamentals_dict_for_symbol['current_ratio'] = financials['currentAssets'] / current_debt
# Find the ratio of long term debt to short-term liquiditable assets.
total_debt = financials['totalDebt'] if financials['totalDebt'] else 0
fundamentals_dict_for_symbol['debt_to_liq_ratio'] = total_debt / financials['currentAssets']
# Store other information for this stock so we can filter on the data
# later.
fundamentals_dict_for_symbol['pe_ratio'] = quote_json[symbol]['peRatio']
fundamentals_dict_for_symbol['market_cap'] = quote_json[symbol]['marketCap']
fundamentals_dict_for_symbol['dividend_yield'] = stats_json[symbol]['dividendYield']
return fundamentals_dict_for_symbol
# --------------------------------------------------------------------------------------------------
# **************************************************************************************************
def main():
trader = PiotroskiFScoreValueTrader()
stock_batch = Stock(['AAPL'])
# Pull all the data we'll need from IEX.
financials_json = stock_batch.get_financials()
print("Financials: ", financials_json)
quote_json = stock_batch.get_quote()
print("Quote: ", quote_json)
stats_json = stock_batch.get_key_stats()
print("Stats: " , stats_json)
earnings_json = stock_batch.get_earnings()
print("Earnings: ",earnings_json)
if __name__ == '__main__':
main()
# **************************************************************************************************
########################################################################################################################
For this code:
trader = PiotroskiFScoreValueTrader()
stock_batch = Stock(['AAPL'])
# Pull all the data we'll need from IEX.
financials_json = stock_batch.get_financials()
print("Financials: \n", json.dumps(financials_json, indent=4))
quote_json = stock_batch.get_quote()
print("Quote: \n", json.dumps(quote_json, indent=4))
stats_json = stock_batch.get_key_stats()
print("Stats: \n", json.dumps(stats_json, indent=4))
earnings_json = stock_batch.get_earnings()
print("Earnings: \n", json.dumps(earnings_json, indent=4))
balance_json = stock_batch.get_balance_sheet()
print("Balance Sheet: \n", json.dumps(balance_json, indent=4))
cash_flow_json = stock_batch.get_cash_flow()
print("Cash Flow: \n", json.dumps(cash_flow_json, indent=4))
income_json = stock_batch.get_income_statement()
print("Income Statement: \n", json.dumps(income_json, indent=4))
I get this output (which mirrors the pictures):
Financials:
[
{
"reportDate": "2018-12-31",
"grossProfit": 31719000000,
"costOfRevenue": 52654000000,
"operatingRevenue": 84373000000,
"totalRevenue": 84373000000,
"operatingIncome": 23034000000,
"netIncome": 19965000000,
"researchAndDevelopment": 3902000000,
"operatingExpense": 61339000000,
"currentAssets": 140828000000,
"totalAssets": 373719000000,
"totalLiabilities": 255827000000,
"currentCash": 44771000000,
"currentDebt": 21741000000,
"shortTermDebt": 21741000000,
"longTermDebt": 92989000000,
"totalCash": 86427000000,
"totalDebt": 114730000000,
"shareholderEquity": 117892000000,
"cashChange": 18858000000,
"cashFlow": 26690000000
}
]
Quote:
{
"symbol": "AAPL",
"companyName": "Apple, Inc.",
"calculationPrice": "tops",
"open": 196.42,
"openTime": 1554730200095,
"close": 197,
"closeTime": 1554494400394,
"high": 199.5,
"low": 196.34,
"latestPrice": 198.875,
"latestSource": "IEX real time price",
"latestTime": "12:17:00 PM",
"latestUpdate": 1554740220243,
"latestVolume": 12781502,
"iexRealtimePrice": 198.875,
"iexRealtimeSize": 100,
"iexLastUpdated": 1554740220243,
"delayedPrice": 199.06,
"delayedPriceTime": 1554739331755,
"extendedPrice": 196.5,
"extendedChange": -2.375,
"extendedChangePercent": -0.01194,
"extendedPriceTime": 1554730194696,
"previousClose": 197,
"change": 1.875,
"changePercent": 0.00952,
"iexMarketPercent": 0.027141489317922103,
"iexVolume": 346909,
"avgTotalVolume": 28583873,
"iexBidPrice": 198.87,
"iexBidSize": 100,
"iexAskPrice": 198.89,
"iexAskSize": 100,
"marketCap": 937751310000,
"peRatio": 16.21,
"week52High": 233.47,
"week52Low": 142,
"ytdChange": 0.25698699999999997
}
Stats:
{
"week52change": 0.16997299999999999,
"week52high": 233.47,
"week52low": 142,
"marketcap": 928910160000,
"employees": 132000,
"day200MovingAvg": 190.7,
"day50MovingAvg": 177.71,
"float": 4708742476,
"avg10Volume": 27937902.7,
"avg30Volume": 28583873.43,
"ttmEPS": 12.27,
"ttmDividendRate": 2.82,
"companyName": "Apple, Inc.",
"sharesOutstanding": 4715280000,
"maxChangePercent": 194.049505,
"year5ChangePercent": 1.634394,
"year2ChangePercent": 0.371293,
"year1ChangePercent": 0.169973,
"ytdChangePercent": 0.247467,
"month6ChangePercent": -0.119632,
"month3ChangePercent": 0.331711,
"month1ChangePercent": 0.12881,
"day30ChangePercent": 0.130689,
"day5ChangePercent": 0.030119,
"nextDividendRate": null,
"dividendYield": 0.01431472081218274,
"nextEarningsDate": "2019-05-01",
"exDividendDate": "2019-02-08",
"peRatio": 16.21
}
Earnings:
[
{
"actualEPS": 4.18,
"consensusEPS": 4.66,
"announceTime": "AMC",
"numberOfEstimates": 36,
"EPSSurpriseDollar": -0.48,
"EPSReportDate": "2019-01-29",
"fiscalPeriod": "Q4 2018",
"fiscalEndDate": "2018-12-31",
"yearAgo": 3.89,
"yearAgoChangePercent": 0.0746
}
]
Balance Sheet:
{
"symbol": "AAPL",
"balancesheet": [
{
"reportDate": "2018-12-31",
"currentCash": 44771000000,
"shortTermInvestments": 41656000000,
"receivables": 18077000000,
"inventory": 4988000000,
"otherCurrentAssets": 12432000000,
"currentAssets": 140828000000,
"longTermInvestments": 158608000000,
"propertyPlantEquipment": 39597000000,
"goodwill": 0,
"intangibleAssets": null,
"otherAssets": 34686000000,
"totalAssets": 373719000000,
"accountsPayable": 44293000000,
"currentLongTermDebt": 9772000000,
"otherCurrentLiabilities": 42249000000,
"totalCurrentLiabilities": 108283000000,
"longTermDebt": 92989000000,
"otherLiabilities": 23607000000,
"minorityInterest": 0,
"totalLiabilities": 255827000000,
"commonStock": 40970000000,
"retainedEarnings": 80510000000,
"treasuryStock": null,
"capitalSurplus": null,
"shareholderEquity": 117892000000,
"netTangibleAssets": 117892000000
}
]
}
Cash Flow:
{
"symbol": "AAPL",
"cashflow": [
{
"reportDate": "2018-12-31",
"netIncome": 19965000000,
"depreciation": 3395000000,
"changesInReceivables": 5109000000,
"changesInInventories": -1076000000,
"cashChange": 18858000000,
"cashFlow": 26690000000,
"capitalExpenditures": -3355000000,
"investments": 9422000000,
"investingActivityOther": -56000000,
"totalInvestingCashFlows": 5844000000,
"dividendsPaid": -3568000000,
"netBorrowings": 6000000,
"otherFinancingCashFlows": -1318000000,
"cashFlowFinancing": -13676000000,
"exchangeRateEffect": null
}
]
}
Income Statement:
{
"symbol": "AAPL",
"income": [
{
"reportDate": "2018-12-31",
"totalRevenue": 84373000000,
"costOfRevenue": 52654000000,
"grossProfit": 31719000000,
"researchAndDevelopment": 3902000000,
"sellingGeneralAndAdmin": 4783000000,
"operatingExpense": 61339000000,
"operatingIncome": 23034000000,
"otherIncomeExpenseNet": 872000000,
"ebit": 23034000000,
"interestIncome": 890000000,
"pretaxIncome": 23906000000,
"incomeTax": 3941000000,
"minorityInterest": 0,
"netIncome": 19965000000,
"netIncomeBasic": 19965000000
}
]
}