{-# LANGUAGE OverloadedStrings #-} module Data.Conduit.Finance.Yahoo ( Symbol (..), StockTime (..), StockQuote (..) , stockQuoteSource ) where import Control.Applicative import Control.Concurrent hiding (yield) import Control.Lens import Control.Monad.Trans import Data.Conduit import Data.Csv import Data.Monoid import Network.Wreq import qualified Data.Attoparsec.ByteString.Char8 as P import qualified Data.Text as T import qualified Data.Vector as V newtype Symbol = Symbol { unSymbol :: T.Text } deriving (Show, Ord, Eq) data StockTime = StockTime { st_hour :: Int , st_min :: Int } deriving (Show, Ord, Eq) data StockQuote = StockQuote { sq_symbol :: Symbol , sq_name :: T.Text , sq_time :: StockTime , sq_ask :: Double , sq_bid :: Double } deriving (Show, Ord, Eq) instance FromRecord StockQuote where parseRecord v | V.length v == 5 = StockQuote <$> (Symbol <$> v .! 0) <*> v .! 1 <*> v .! 4 <*> v .! 2 <*> v .! 3 | otherwise = fail "Can't parse as StockQuote" instance FromField StockTime where parseField bs = case P.parseOnly (dateP <* P.endOfInput) bs of Left er -> fail er Right val -> return val dateP :: P.Parser StockTime dateP = do hour <- P.decimal _ <- P.string ":" minute <- P.decimal amOrPm <- (P.string "am" <|> P.string "pm") let hour' = if amOrPm == "pm" then hour + 12 else hour return (StockTime hour' minute) stockQuoteSource :: MonadIO m => [Symbol] -> Source m StockQuote stockQuoteSource symbols = loop where loop = do r <- liftIO $ get endPoint let mVals = decode NoHeader (r ^. responseBody) case mVals of Left err -> liftIO $ putStrLn ("Error: Failed to parse finance api response: " ++ err) Right vals -> mapM_ yield (V.toList vals) liftIO $ threadDelay (9 * 100000000) -- 15 minutes stockQ = T.intercalate "+" (map unSymbol symbols) endPoint = T.unpack $ "http://finance.yahoo.com/d/quotes.csv?s=" <> stockQ <> "&f=snabt1"