{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE DeriveGeneric #-} {-# LANGUAGE TemplateHaskell #-} module Net.Stocks ( getChart, getCompany, getBook, getDelayedQuote, getDelayedDividend, getEarnings, getEffectiveSpread, getFinancials, getStats, getNewsItem, getOHLC, getPrevious, getPeers, getPrice, getQuote, getRelevant, getSplit, getVolumeByVenue, getTS, getBatch, getBatchCompany, getMarket, getIntraDayStats, getRecentStats, getRecordStats, getHistoricalStats, -- getHistoricalDailyStats, typeQuery, Batch (..), BatchQuery (..) ) where import System.IO import GHC.Generics import Data.Aeson import Data.Char import Data.HashMap.Strict import Data.Maybe import Network.HTTP.Conduit import qualified Data.ByteString.Lazy.Char8 as L8 import qualified Data.List as DL import qualified Data.Map as DM import qualified Net.IEX.Chart as IEXChart import qualified Net.IEX.Company as IEXCompany import qualified Net.IEX.Stats as IEXStats import qualified Net.IEX.Earnings as IEXEarnings import qualified Net.IEX.NewsItem as IEXNewsItem import qualified Net.IEX.DelayedQuote as IEXDelayedQuote import qualified Net.IEX.Dividend as IEXDividend import qualified Net.IEX.EffectiveSpread as IEXEffectiveSpread import qualified Net.IEX.Financials as IEXFinancials import qualified Net.IEX.OHLC as IEXOHLC import qualified Net.IEX.PriceTime as IEXPriceTime import qualified Net.IEX.Previous as IEXPrevious import qualified Net.IEX.Quote as IEXQuote import qualified Net.IEX.Split as IEXSplit import qualified Net.IEX.VolumeByVenue as IEXVolumeByVenue import qualified Net.IEX.Relevant as IEXRelevant import qualified Net.IEX.Market as IEXMarket import qualified Net.IEX.IntraDayStats as IEXIntraDayStats import qualified Net.IEX.RecentStats as IEXRecentStats import qualified Net.IEX.RecordStats as IEXRecordStats import qualified Net.IEX.Book as IEXBook import qualified Net.IEX.TimeSeries as IEXTimeSeries type Symbol = String data BatchQuery = NewsQuery | ChartQuery | CompanyQuery | DelayedQuoteQuery | DividendQuery | EarningsQuery | EffectiveSpreadQuery | FinancialsQuery | StatsQuery | OHLCQuery | PriceTimeQuery | PreviousQuery | QuoteQuery | SplitQuery | VolumeByVenueQuery batchQueryToStr :: BatchQuery -> String batchQueryToStr NewsQuery = "news" batchQueryToStr ChartQuery = "chart" batchQueryToStr CompanyQuery = "company" batchQueryToStr DelayedQuoteQuery = "delayedquote" batchQueryToStr DividendQuery = "dividends" batchQueryToStr EarningsQuery = "earnings" batchQueryToStr EffectiveSpreadQuery = "effectivespread" batchQueryToStr FinancialsQuery = "financials" batchQueryToStr StatsQuery = "stats" batchQueryToStr OHLCQuery = "ohlc" batchQueryToStr PriceTimeQuery = "price" batchQueryToStr QuoteQuery = "quote" batchQueryToStr SplitQuery = "split" batchQueryToStr VolumeByVenueQuery = "volumebyvenue" data Batch = Batch { news :: Maybe [IEXNewsItem.NewsItem], chart :: Maybe [IEXChart.Chart], company :: Maybe IEXCompany.Company, delayedQuote :: Maybe IEXDelayedQuote.DelayedQuote, dividend :: Maybe [IEXDividend.Dividend], earnings :: Maybe IEXEarnings.Earnings, effectiveSpread :: Maybe [IEXEffectiveSpread.EffectiveSpread], financials :: Maybe IEXFinancials.Financials, stats :: Maybe IEXStats.Stats, ohlc :: Maybe IEXOHLC.OHLC, priceTime :: Maybe Integer, previous :: Maybe IEXPrevious.Previous, quote :: Maybe IEXQuote.Quote, split :: Maybe [IEXSplit.Split], volumeByVenue :: Maybe [IEXVolumeByVenue.VolumeByVenue] } deriving (Generic, Show, Eq) -- ToJSON means taking a haskell data structure and making a JSON string instance ToJSON Batch -- FromJSON means parsing the text into a haskell data structure instance FromJSON Batch baseURL :: String baseURL = "https://api.iextrading.com/1.0/stock/" marketURL :: String marketURL = "https://api.iextrading.com/1.0/market" intraDayURL :: String intraDayURL = "https://api.iextrading.com/1.0/stats/intraday" statsURL :: String statsURL = "https://api.iextrading.com/1.0/stats/" lowerString :: Symbol -> String lowerString = DL.map toLower getChart :: Symbol -> IO (Maybe [IEXChart.Chart]) getChart symb = do obj <- getNonJSONData (baseURL ++ lowerString symb ++ "/chart") return $ decode obj getCompany :: Symbol -> IO (Maybe IEXCompany.Company) getCompany symb = do obj <- getNonJSONData (baseURL ++ lowerString symb ++ "/company") return $ decode obj getBook :: Symbol -> IO (Maybe IEXBook.Book) getBook symb = do obj <- getNonJSONData (baseURL ++ lowerString symb ++ "/book") return $ decode obj getDelayedQuote :: Symbol -> IO (Maybe IEXDelayedQuote.DelayedQuote) getDelayedQuote symb = do obj <- getNonJSONData (baseURL ++ lowerString symb ++ "/delayed-quote") return $ decode obj getDelayedDividend :: Symbol -> IO (Maybe [IEXDividend.Dividend]) getDelayedDividend symb = do obj <- getNonJSONData (baseURL ++ lowerString symb ++ "/dividends/5y") return $ decode obj getEarnings :: Symbol -> IO (Maybe IEXEarnings.Earnings) getEarnings symb = do obj <- getNonJSONData (baseURL ++ lowerString symb ++ "/earnings") return $ decode obj getEffectiveSpread :: Symbol -> IO (Maybe [IEXEffectiveSpread.EffectiveSpread]) getEffectiveSpread symb = do obj <- getNonJSONData (baseURL ++ lowerString symb ++ "/effective-spread") return $ decode obj getFinancials :: Symbol -> IO (Maybe IEXFinancials.Financials) getFinancials symb = do obj <- getNonJSONData (baseURL ++ lowerString symb ++ "/financials") return $ decode obj getStats :: Symbol -> IO (Maybe IEXStats.Stats) getStats symb = do obj <- getNonJSONData (baseURL ++ lowerString symb ++ "/stats") return $ decode obj getNewsItem :: Symbol -> IO (Maybe [IEXNewsItem.NewsItem]) getNewsItem symb = do obj <- getNonJSONData (baseURL ++ lowerString symb ++ "/news/last/1") return $ decode obj getOHLC :: Symbol -> IO (Maybe IEXOHLC.OHLC) getOHLC symb = do obj <- getNonJSONData (baseURL ++ lowerString symb ++ "/ohlc") return $ decode obj getPeers :: Symbol -> IO L8.ByteString getPeers symb = do obj <- getNonJSONData (baseURL ++ lowerString symb ++ "/peers") return $ obj getPrevious :: Symbol -> IO (Maybe IEXPrevious.Previous) getPrevious symb = do obj <- getNonJSONData (baseURL ++ lowerString symb ++ "/previous") return $ decode obj -- FIXME: do not json parse an int getPrice :: Symbol -> IO (Maybe Double) getPrice symb = do obj <- getNonJSONData (baseURL ++ lowerString symb ++ "/price") return $ decode obj getQuote :: Symbol -> IO (Maybe IEXQuote.Quote) getQuote symb = do obj <- getNonJSONData (baseURL ++ lowerString symb ++ "/quote") return $ decode obj getRelevant :: Symbol -> IO (Maybe IEXRelevant.Relevant) getRelevant symb = do obj <- getNonJSONData (baseURL ++ lowerString symb ++ "/relevant") return $ decode obj getSplit :: Symbol -> IO (Maybe [IEXSplit.Split]) getSplit symb = do obj <- getNonJSONData (baseURL ++ lowerString symb ++ "/splits/5y") return $ decode obj getVolumeByVenue :: Symbol -> IO (Maybe [IEXVolumeByVenue.VolumeByVenue]) getVolumeByVenue symb = do obj <- getNonJSONData (baseURL ++ lowerString symb ++ "/volume-by-venue") return $ decode obj getTS :: Symbol -> IO (Maybe [IEXTimeSeries.TimeSeries]) getTS symb = do obj <- getNonJSONData (baseURL ++ lowerString symb ++ "/time-series") return $ decode obj -- get a list of parts we want in a batch request, and translate that -- to HTTP parameters to provide to the API call typeQuery :: [BatchQuery] -> String typeQuery [] = "" typeQuery inp = "types=" ++ (concat $ DL.intersperse "," (fmap batchQueryToStr inp)) symbolQuery :: [Symbol] -> String symbolQuery [] = "" symbolQuery inp = "symbols=" ++ (concat $ DL.intersperse "," inp) questionMark :: [BatchQuery] -> String questionMark [] = "" questionMark _ = "?" getBatch :: [Symbol] -> [BatchQuery] -> IO (Maybe (DM.Map String Batch)) getBatch symbs queryParams = let urlPt = baseURL ++ "market/batch?" fullQuery = urlPt ++ symbolQuery symbs ++ "&" ++ typeQuery queryParams in do obj <- getNonJSONData fullQuery return $ decode obj -- batch query of a *single* company getBatchCompany :: Symbol -> [BatchQuery] -> IO (Maybe Batch) getBatchCompany symb queryParams = let urlPt = (baseURL ++ lowerString symb ++ "/batch/") fullQuery = urlPt ++ (questionMark queryParams) ++ (typeQuery queryParams) in do obj <- getNonJSONData fullQuery return $ decode obj getMarket :: IO (Maybe [IEXMarket.Market]) getMarket = do obj <- getNonJSONData marketURL return $ decode obj getIntraDayStats :: IO (Maybe IEXIntraDayStats.IntraDayStats) getIntraDayStats = do obj <- getNonJSONData intraDayURL return $ decode obj getRecentStats :: IO (Maybe [IEXRecentStats.RecentStats]) getRecentStats = do obj <- getNonJSONData (statsURL ++ "recent") return $ decode obj getRecordStats :: IO (Maybe IEXRecordStats.RecordStats) getRecordStats = do obj <- getNonJSONData (statsURL ++ "records") return $ decode obj getHistoricalStats :: IO (Maybe [IEXRecentStats.RecentStats]) getHistoricalStats = undefined -- currently does not work due to inconsitency in -- IEX API. isHalfDay is Bool in one API call and Int in another -- getHistoricalDailyStats :: IO (Maybe [IEXRecentStats.RecentStats]) -- getHistoricalDailyStats = do -- obj <- getNonJSONData (statsURL ++ "historical/daily") -- return $ decode obj getNonJSONData :: String -> IO L8.ByteString getNonJSONData query = do obj <- simpleHttp query return obj