Created
June 23, 2014 11:02
-
-
Save thewinterwind/8cf323b127b7e9ed262f to your computer and use it in GitHub Desktop.
Entire Stock class for tutorial on using PHP with the Yahoo Finance API
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php namespace SS\Stock; | |
use DB, File, Cache, Input, Response; | |
class Stock { | |
public function __construct() | |
{ | |
ini_set("memory_limit", "-1"); | |
set_time_limit(0); | |
} | |
/** | |
* Update the stock streaks after the market has closed | |
* | |
* @param $overwrite (whether to update all data from beginning) | |
* @return void | |
*/ | |
public function updateStreaks($overwrite = false) | |
{ | |
$date = DB::table('summaries')->max('date'); | |
$stocks = DB::table('stocks')->select('symbol')->orderBy('symbol', 'asc')->get(); | |
foreach ($stocks as $stock) | |
{ | |
$stored = DB::table('stocks') | |
->where('symbol', $stock->symbol) | |
->where('streak_stored', $date) | |
->first(); | |
if ($stored && !$overwrite) continue; | |
$days = DB::table('summaries') | |
->select('close', 'volume') | |
->where('symbol', $stock->symbol) | |
->orderBy('date', 'desc') | |
->groupBy('date') | |
->limit(20) | |
->get(); | |
$streak = 0; | |
for ($i = 0; $i < count($days); $i++) | |
{ | |
// if we're at the earliest day recorded for a stock, the streak is over | |
if ( ! isset($days[$i + 1])) break; | |
// if the next days price is the same as the current, the streak is over | |
if ($days[$i]->close === $days[$i + 1]->close) break; | |
// one time check for the first iteration | |
if ($i === 0) | |
{ | |
if ($days[$i] > $days[$i + 1]) | |
{ | |
$streak++; continue; | |
} | |
if ($days[$i] < $days[$i + 1]) | |
{ | |
$streak--; continue; | |
} | |
} | |
// check if the winning streak is over or not | |
if ($streak > 0) | |
{ | |
// if current day's close is less than the day before it, the winning streak is over | |
if ($days[$i]->close < $days[$i + 1]->close) break; | |
// the winning streak continues | |
$streak++; | |
} | |
elseif ($streak < 0) | |
{ | |
// if the current day's close is more than the day before it, the losing streak is over | |
if ($days[$i]->close > $days[$i + 1]->close) break; | |
// the losing streak continues | |
$streak--; | |
} | |
} | |
$amount = $this->calculateMovePercentage($days, $streak); | |
$volume = $this->calculateStreakVolume($days, $streak); | |
DB::table('stocks') | |
->where('symbol', $stock->symbol) | |
->update([ | |
'streak' => $streak, | |
'move_percentage' => $amount, | |
'streak_volume' => $volume, | |
'streak_stored' => $date, | |
]); | |
print "Symbol: " . $stock->symbol . " # Streak: " . $streak . " # "; | |
print "Amount: " . $amount . " # Volume: " . $volume . "\n"; | |
} | |
return 1; // lets the artisan command know the method finished without issue | |
} | |
/** | |
* Update the stock streaks after the market has closed | |
* | |
* @param array $days (daily summaries) | |
* @param int $days (streak) | |
* @return int (shares traded volume) | |
*/ | |
public function calculateStreakVolume(array $days, $streak) | |
{ | |
$daysOnStreak = array_slice($days, 0, abs($streak)); | |
$volume = 0; | |
foreach ($daysOnStreak as $day) { | |
$volume += $day->volume; | |
} | |
return $volume; | |
} | |
/** | |
* Get the percentage the stock moved over the streak's duration | |
* | |
* @param array $days (daily summaries) | |
* @param int $days (streak) | |
* @return double (move percentage) | |
*/ | |
public function calculateMovePercentage(array $days, $streak) | |
{ | |
if ($streak == 0) return 0; | |
$days = array_slice($days, 0, abs($streak) + 1); | |
return round((($days[0]->close / end($days)->close) - 1) * 100, 2); | |
} | |
/** | |
* Store basic information about a stock | |
* | |
* @return void | |
*/ | |
public function storeStockInfo() | |
{ | |
$files = File::files(app_path() . '/resources/stock_lists'); | |
$stocks = []; | |
foreach ($files as $file) | |
{ | |
$handle = fopen($file, "r"); | |
while( ! feof($handle)) | |
{ | |
$stock = fgetcsv($handle); | |
$stock[9] = basename($file, '.csv'); | |
if (count($stock) == 10) | |
{ | |
$stocks[] = $stock; | |
} | |
} | |
fclose($handle); | |
} | |
echo count($stocks) . ' to create' . PHP_EOL; | |
foreach ($stocks as $stock) | |
{ | |
$row = Stock::create( | |
[ | |
'symbol' => remove_whitespace($stock[0]), | |
'name' => $stock[1], | |
'exchange' => remove_whitespace($stock[9]), | |
'ipo_year' => remove_whitespace($stock[5]), | |
'sector' => $stock[6], | |
'industry' => remove_whitespace($stock[7]), | |
'last_sale' => remove_whitespace($stock[2]), | |
'market_cap' => remove_whitespace($stock[3]), | |
'summary_link' => remove_whitespace($stock[8]), | |
'updated_at' => new Datetime, | |
] | |
); | |
echo 'created stock: ' . $row->id . PHP_EOL; | |
} | |
} | |
public function storeStockHistory() | |
{ | |
$date = date('Y-m-d'); | |
$files = File::files(app_path() . '/resources/historical_lists/' . $date); | |
$completed_stocks = DB::table('stocks') | |
->select('symbol') | |
->where('history_updated', $date) | |
->groupBy('symbol') | |
->orderBy('symbol', 'asc') | |
->get(); | |
$completed_symbols = array_pluck($completed_stocks, 'symbol'); | |
foreach ($files as $file) | |
{ | |
$symbol = basename($file, '.csv'); | |
if (in_array($symbol, $completed_symbols)) continue; | |
$handle = fopen($file, "r"); | |
while( ! feof($handle)) | |
{ | |
// the stock's daily summary (closing price, volume, etc.) | |
$summary = fgetcsv($handle); | |
// 1. continue to next iteration if it's the header | |
// 2. continue to next iteration if there isn't seven elements (invalid record) | |
if ($summary[0] == 'Date' || count($summary) !== 7) continue; | |
// if the date is less than today, we've already stored it, break out of the loop | |
if (remove_whitespace($summary[0]) < $date) break; | |
DB::table('summaries')->insert([ | |
'date' => remove_whitespace($summary[0]), | |
'symbol' => remove_whitespace($symbol), | |
'open' => remove_whitespace($summary[1]), | |
'high' => remove_whitespace($summary[2]), | |
'low' => remove_whitespace($summary[3]), | |
'close' => remove_whitespace($summary[4]), | |
'adjusted_close' => remove_whitespace($summary[6]), | |
'volume' => remove_whitespace($summary[5]), | |
'updated_at' => new Datetime, | |
]); | |
print "Inserted: " . $symbol . ". Date: " . $summary[0] . PHP_EOL; | |
} | |
fclose($handle); | |
DB::table('stocks') | |
->where('symbol', $symbol) | |
->update([ | |
'history_updated' => $date, | |
]); | |
print "--------------------------------". PHP_EOL; | |
print "Finished inserting for: " . $symbol . PHP_EOL; | |
print "--------------------------------". PHP_EOL; | |
} | |
} | |
public function fetchStockData($symbol = null) | |
{ | |
// get stock data from artisan command or query string | |
$symbol ?: Input::get('symbol'); | |
// cache key is a unique string of resource type, date, and resource id | |
$cache_key = 'stock_data' . date('Y-m-d') . $symbol; | |
// build query at https://developer.yahoo.com/yql/console/ | |
if ( ! Cache::has($cache_key)) | |
{ | |
$resource = $this->getApiUrl($symbol); | |
// fetch the json | |
$data = json_decode(file_get_contents($resource)); | |
// $data has one property with everything in it (query), let's save its contents | |
if ($data) Cache::put($cache_key, $data->query, 60); | |
} | |
return Cache::get($cache_key); | |
} | |
public function seeStockData() | |
{ | |
$resource = $this->getApiUrl(Input::get('symbol')); | |
$data = json_decode(file_get_contents($resource)); | |
var_dump($data); | |
} | |
protected function getApiUrl($symbol) | |
{ | |
$url = "https://query.yahooapis.com/v1/public/yql?q="; | |
$url .= urlencode("select * from yahoo.finance.quotes "); | |
$url .= urlencode("where symbol in ('$symbol')"); | |
$url .= "&format=json&diagnostics=true"; | |
$url .= "&env=store%3A%2F%2Fdatatables.org%2Falltableswithkeys&callback="; | |
return $url; | |
} | |
public function storeStockData($symbol) | |
{ | |
$stock = $this->fetchStockData($symbol); | |
if ( ! isset($stock->results)) return print $symbol . " is not an object." . PHP_EOL; | |
$quote = $stock->results->quote; | |
$lastTrade = strip_tags($quote->LastTradeWithTime); | |
list($lastTradeTime, $lastTradePrice) = explode(' - ', $lastTrade); | |
$lastTradeTime = date('Y-m-d', strtotime($lastTradeTime)); | |
$dailySummary = DB::table('summaries') | |
->where('symbol', $symbol) | |
->where('date', $lastTradeTime)->first(); | |
if (is_null($dailySummary)) | |
{ | |
DB::table('summaries') | |
->insert([ | |
'symbol' => $symbol, | |
'date' => $lastTradeTime, | |
'open' => $quote->Open, | |
'close' => $lastTradePrice, | |
'adjusted_close' => $quote->LastTradePriceOnly, | |
'low' => $quote->DaysLow, | |
'high' => $quote->DaysHigh, | |
'volume' => $quote->Volume, | |
'updated_at' => new \Datetime, | |
]); | |
return print "Stored daily stock information for: " . $symbol . PHP_EOL; | |
} | |
print "Stock is already stored for: " . $symbol . PHP_EOL; | |
} | |
public function getValidStocks($date = null) | |
{ | |
if (!$date) $date = date('Y-m-d'); | |
return DB::table('stocks') | |
->select('symbol') | |
->where('symbol', 'NOT LIKE', '%^%') // some stocks have ^ in them (not doing these now) | |
->where('symbol', 'NOT LIKE', '%/%') // some stocks have / in them (not doing these now) | |
->where('symbol', 'NOT LIKE', '%~%') // some bad data from CSV has stocks with ~ in them | |
->where('history_downloaded', '!=', $date) | |
->orderBy('symbol', 'asc') | |
->get(); | |
} | |
public function fetchStockHistory() | |
{ | |
$stocks = $this->getValidStocks(date('Y-m-d')); | |
$target_dir = app_path() . '/resources/' . $date; | |
if ( ! is_dir($target_dir)) mkdir($target_dir, 0755); | |
foreach ($stocks as $stock) | |
{ | |
$symbol = $stock->symbol; | |
$stock_data = @file_get_contents('http://ichart.finance.yahoo.com/table.csv?s=' . $stock->symbol); | |
if ($stock_data) | |
{ | |
$bytes = file_put_contents( | |
$target_dir . '/' . remove_whitespace($stock->symbol) . '.csv', | |
$stock_data | |
); | |
if ($bytes) | |
{ | |
print 'Stored csv for: ' . $stock->symbol . ' (' . $bytes . ' bytes)' . PHP_EOL; | |
// store the date to confirm it was downloaded | |
// if the script fails midway we can pick up from last time | |
DB::table('stocks') | |
->where('symbol', $stock->symbol) | |
->update([ | |
'history_downloaded' => $date | |
]); | |
} | |
} | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
It's Great!