simfin (empty) → 1.0.0
raw patch · 27 files changed
+3361/−0 lines, 27 filesdep +Chartdep +Chart-diagramsdep +SVGFonts
Dependencies added: Chart, Chart-diagrams, SVGFonts, aeson, base, bytestring, composition-extra, containers, exceptions, http-client, http-client-tls, http-types, multi-containers, simfin, tasty, tasty-hunit, text, time, unordered-containers, utf8-string
Files
- CHANGELOG.md +11/−0
- LICENSE +20/−0
- examples/EarningsPerShare.hs +81/−0
- examples/Prices.hs +55/−0
- examples/RelativePerformance.hs +80/−0
- simfin.cabal +92/−0
- src/SimFin/Common.hs +81/−0
- src/SimFin/Free.hs +177/−0
- src/SimFin/Internal.hs +138/−0
- src/SimFin/Plus.hs +185/−0
- src/SimFin/Types/BalanceSheet.hs +581/−0
- src/SimFin/Types/CashFlow.hs +492/−0
- src/SimFin/Types/CompanyInfo.hs +48/−0
- src/SimFin/Types/CompanyListing.hs +41/−0
- src/SimFin/Types/Derived.hs +108/−0
- src/SimFin/Types/FiscalPeriod.hs +50/−0
- src/SimFin/Types/Industry.hs +43/−0
- src/SimFin/Types/Prices.hs +62/−0
- src/SimFin/Types/PricesAndRatios.hs +43/−0
- src/SimFin/Types/PricesQuery.hs +72/−0
- src/SimFin/Types/ProfitAndLoss.hs +497/−0
- src/SimFin/Types/Ratios.hs +57/−0
- src/SimFin/Types/StatementQuery.hs +100/−0
- src/SimFin/Types/StockRef.hs +51/−0
- src/SimFin/Types/StringFrac.hs +31/−0
- src/SimFin/Util.hs +40/−0
- test/Test.hs +125/−0
+ CHANGELOG.md view
@@ -0,0 +1,11 @@+## v1.0.0++Endpoints supported:+* List companies+* Company information+* Statements+ * Balance sheet+ * Cash flow+ * Profit and loss+ * Derived figures+* Share prices
+ LICENSE view
@@ -0,0 +1,20 @@+Copyright (c) 2022 Owen Shepherd++Permission is hereby granted, free of charge, to any person obtaining+a copy of this software and associated documentation files (the+"Software"), to deal in the Software without restriction, including+without limitation the rights to use, copy, modify, merge, publish,+distribute, sublicense, and/or sell copies of the Software, and to+permit persons to whom the Software is furnished to do so, subject to+the following conditions:++The above copyright notice and this permission notice shall be included+in all copies or substantial portions of the Software.++THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,81 @@+{-# LANGUAGE DuplicateRecordFields #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE ScopedTypeVariables #-}++module Main+ ( main+ ) where++import Control.Arrow ((&&&))+import Control.Monad+import Data.Time.Clock+import Data.Time.Calendar+import Data.Time.LocalTime+import Data.List.NonEmpty (NonEmpty(..))+import qualified Data.List.NonEmpty as NE+import qualified Data.Map as M+import Data.Maybe+import qualified Data.Multimap as MM+import Data.Either+import qualified Data.Text as T+import Graphics.Rendering.Chart.Easy hiding (close)+import Graphics.Rendering.Chart.Backend.Diagrams++import SimFin.Free+import qualified SimFin.Types.Derived as D++type LinePt = (LocalTime, Double)+type LineData = (String, NonEmpty LinePt)++to5pm :: Day -> LocalTime+to5pm = flip LocalTime $ dayFractionToTimeOfDay $ 17 / 24++toPoint :: DerivedRow Double -> Maybe LinePt+toPoint row = do+ eps <- earningsPerShareDiluted row+ let day = reportDate (row :: DerivedRow Double)+ pure (to5pm day, eps)++stocks :: [StockRef]+stocks = ["GOOG", "AAPL", "TWTR", "NFLX"]++-- Caution! This can blow through API limits.+stockQueries :: Integer -> [StatementQueryFree]+stockQueries year = do+ stock <- stocks+ year <- [2016..fromInteger year]+ period <- [Q1, Q2, Q3, Q4]+ pure $ StatementQueryFree+ { stockRef = stock+ , period = period+ , year = year+ , ttm = False+ }++groupOn' :: Ord b => (a -> b) -> [a] -> [(b, NonEmpty a)]+groupOn' f lst = M.toList $ MM.toMap $ MM.fromList $ (f &&& id) <$> lst++toLines :: [DerivedRow Double] -> [LineData]+toLines rows = do+ (ticker, rows') <- groupOn' D.ticker rows+ pts <- maybeToList $ traverse toPoint rows'+ pure (T.unpack ticker, pts)++main :: IO ()+main = do+ now <- getCurrentTime+ let (year, _, _) = toGregorian $ utctDay now+ ctx <- createDefaultContext++ pricesRes :: [ApiResult (Maybe (DerivedRow Double))] <- traverse (fetchDerived ctx) $ stockQueries year+ let prices :: [LineData] = toLines $ catMaybes $ rights pricesRes++ toFile def "eps-diluted.svg" $ do+ layout_title .= "Diluted Earnings per Share"+ layout_background .= solidFillStyle (opaque white)+ layout_foreground .= opaque black+ layout_left_axis_visibility . axis_show_ticks .= False+ layout_x_axis . laxis_title .= "Date"+ layout_y_axis . laxis_title .= "Diluted EPS ($)"+ forM_ prices $ \(name, pts) -> plot $ line name [NE.toList pts]+ pure ()
+ examples/Prices.hs view
@@ -0,0 +1,55 @@+{-# LANGUAGE DuplicateRecordFields #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE ScopedTypeVariables #-}++module Main+ ( main+ ) where++import Control.Monad+import Data.Time.Calendar+import Data.Time.LocalTime+import Data.Maybe+import Data.Either+import qualified Data.Text as T+import Graphics.Rendering.Chart.Easy hiding (close)+import Graphics.Rendering.Chart.Backend.Diagrams++import SimFin.Free++to5pm :: Day -> LocalTime+to5pm = flip LocalTime $ dayFractionToTimeOfDay $ 17 / 24++toDataPoint :: PricesRow Double -> Maybe (LocalTime, Double)+toDataPoint row = do+ day <- date row+ pure (to5pm day, close row)++stonks :: [StockRef]+stonks = ["GOOG", "AAPL", "TWTR", "NFLX"]++type LineData = (String, [(LocalTime, Double)])++toLine :: [PricesRow Double] -> Maybe LineData+toLine [] = Nothing+toLine pts@(PricesRow{ticker = t} : _) = Just+ ( T.unpack t+ , catMaybes $ toDataPoint <$> pts+ )++main :: IO ()+main = do+ ctx <- createDefaultContext++ pricesRes :: [ApiResult [PricesRow Double]] <- traverse (fetchPrices ctx) stonks+ let prices :: [LineData] = catMaybes $ toLine <$> rights pricesRes++ toFile def "prices.svg" $ do+ layout_title .= "Price History"+ layout_background .= solidFillStyle (opaque white)+ layout_foreground .= opaque black+ layout_left_axis_visibility . axis_show_ticks .= False+ layout_x_axis . laxis_title .= "Date"+ layout_y_axis . laxis_title .= "Price ($)"+ forM_ prices $ \(name, pts) -> plot $ line name [pts]+ pure ()
+ examples/RelativePerformance.hs view
@@ -0,0 +1,80 @@+{-# LANGUAGE DuplicateRecordFields #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE TupleSections #-}++module Main+ ( main+ ) where++import Control.Arrow (first, second)+import Control.Monad+import Data.Time.Calendar+import Data.Time.LocalTime+import Data.List.NonEmpty (NonEmpty(..))+import qualified Data.List.NonEmpty as NE+import Data.Maybe+import Data.Either+import Data.Text (Text)+import qualified Data.Text as T+import Graphics.Rendering.Chart.Easy hiding (close)+import Graphics.Rendering.Chart.Backend.Diagrams++import SimFin.Free++type LinePt = (LocalTime, Double)++toPoint :: PricesRow Double -> Maybe LinePt+toPoint row = do+ day <- date row+ pure (to5pm day, close row)++stonks :: [StockRef]+stonks = ["GOOG", "AAPL", "TWTR", "NFLX"]++type LineData = (String, NonEmpty LinePt)++to5pm :: Day -> LocalTime+to5pm = flip LocalTime $ dayFractionToTimeOfDay $ 17 / 24++ptsAfter :: LocalTime -> NonEmpty LinePt -> Maybe (NonEmpty LinePt)+ptsAfter t = NE.nonEmpty . filter ((>= t) . fst) . NE.toList++toLine :: [PricesRow Double] -> Maybe (Text, NonEmpty LinePt)+toLine [] = Nothing+toLine pts@(PricesRow{ticker = t} : _) = (t,) <$> NE.nonEmpty (catMaybes $ toPoint <$> pts)++getStart :: LineData -> LocalTime+getStart (_, (s, _) :| _) = s++lastStart :: [LineData] -> LocalTime+lastStart = maximum . fmap getStart++afterCommonStart :: [LineData] -> [LineData]+afterCommonStart ls = catMaybes $ traverse (ptsAfter $ lastStart ls) <$> ls++scaleToRelative :: NonEmpty LinePt -> NonEmpty LinePt+scaleToRelative xs@((_, x) :| _) = second ((/ x) . (* 100)) <$> xs++toLines :: [[PricesRow Double]] -> [LineData]+toLines pts = fmap (second scaleToRelative)+ $ afterCommonStart+ $ first T.unpack+ <$> catMaybes (toLine <$> pts)++main :: IO ()+main = do+ ctx <- createDefaultContext++ pricesRes :: [ApiResult [PricesRow Double]] <- traverse (fetchPrices ctx) stonks+ let prices :: [LineData] = toLines $ rights pricesRes++ toFile def "relative-performance.svg" $ do+ layout_title .= "Relative Performance"+ layout_background .= solidFillStyle (opaque white)+ layout_foreground .= opaque black+ layout_left_axis_visibility . axis_show_ticks .= False+ layout_x_axis . laxis_title .= "Date"+ layout_y_axis . laxis_title .= "Performance (relative to start) (%)"+ forM_ prices $ \(name, pts) -> plot $ line name [NE.toList pts]+ pure ()
+ simfin.cabal view
@@ -0,0 +1,92 @@+cabal-version: 2.4+name: simfin+version: 1.0.0+synopsis: A library to fetch and parse financial data from the SimFin(+) API.+homepage: https://github.com/414owen/simfin+license: MIT+license-file: LICENSE+author: Owen Shepherd+maintainer: owen@owen.cafe+category: Web, Finance+extra-source-files: CHANGELOG.md+description:++ This library aims to wrap the [SimFin(+)](https://simfin.com/) API+ as completely as possible. SimFin provides fundamental financial data.++library+ exposed-modules: SimFin.Plus+ , SimFin.Free+ , SimFin.Common+ , SimFin.Types.BalanceSheet+ , SimFin.Types.CashFlow+ , SimFin.Types.CompanyInfo+ , SimFin.Types.CompanyListing+ , SimFin.Types.Derived+ , SimFin.Types.FiscalPeriod+ , SimFin.Types.Industry+ , SimFin.Types.Prices+ , SimFin.Types.PricesAndRatios+ , SimFin.Types.PricesQuery+ , SimFin.Types.ProfitAndLoss+ , SimFin.Types.Ratios+ , SimFin.Types.StatementQuery+ , SimFin.Types.StockRef+ , SimFin.Types.StringFrac+ , SimFin.Util+ other-modules: SimFin.Internal+ build-depends: base >= 4.13.0.0 && < 5+ , composition-extra+ , http-client-tls+ , http-client+ , exceptions+ , bytestring+ , http-types+ , utf8-string+ , aeson >=1.5.6.0 && < 2.1+ , text+ , time+ , unordered-containers+ hs-source-dirs: src+ default-language: Haskell2010+ ghc-options: -Wall++test-suite simfin-test+ default-language: Haskell2010+ type: exitcode-stdio-1.0+ hs-source-dirs: test+ main-is: Test.hs+ build-depends: base >= 4.13.0.0 && < 5+ , simfin+ , aeson+ , text+ , tasty+ , tasty-hunit++common examples-common+ default-language: Haskell2010+ hs-source-dirs: examples+ ghc-options: -Wall+ build-depends: base >= 4.13.0.0 && < 5+ , simfin+ , time+ , Chart >= 1.9.3 && < 1.9.4+ , SVGFonts < 1.8+ , Chart-diagrams >= 1.9.3 && < 1.9.4+ , text+ , simfin++executable graph-prices+ import: examples-common+ main-is: Prices.hs++executable graph-eps+ import: examples-common+ main-is: EarningsPerShare.hs+ build-depends: containers+ , multi-containers++executable graph-relative-performance+ import: examples-common+ main-is: RelativePerformance.hs+
+ src/SimFin/Common.hs view
@@ -0,0 +1,81 @@+{-|+Module : SimFin.Types.Common+Description : Types and functions common to the free and paid APIs.+Copyright : (c) Owen Shepherd, 2022+License : MIT+Maintainer : owen@owen.cafe+-}++{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE LambdaCase #-}++module SimFin.Common+ ( ApiError(..)+ , ApiResult+ , fetchCompanyList+ , performRequest+ ) where++import Control.Monad.Catch+import Control.Monad.IO.Class+import Data.Aeson+import Data.ByteString (ByteString)+import qualified Data.ByteString.Lazy as LBS+import Data.Text (Text)+import qualified Data.Text as T++import SimFin.Internal+import SimFin.Types.CompanyListing++-- | Represents all the types of error the server returns, and that we can encounter+-- on our side.++data ApiError+ -- | Can't turn ByteString into JSON+ = DecodeError LBS.ByteString String+ -- | Can't turn JSON into result type+ | ParseError Value String+ -- | Server returned '{"error": "..."}' along with a non-200 status code.+ -- This could in theory be parsed into machine-readable format,+ -- with variants such as `InvalidApiKey | RateLimited | ...`, but + -- the API doesn't guarantee error message stability.+ | Other Text++instance Show ApiError where+ show (DecodeError _ err) = "Couldn't decode reponse body. Err: " <> err+ show (ParseError _ err) = "Couldn't parse JSON value. Err: " <> err+ show (Other err) = "Server returned error: " <> T.unpack err++-- | The result of calling fetch* is either an error or a successful result.++type ApiResult = Either ApiError++instance FromJSON ApiError where+ parseJSON = withObject "Root" $ \v -> Other <$> v .: "error"++-- | Make a request, all fetch* functions call this.++performRequest+ :: ( MonadIO m+ , FromJSON a+ )+ => SimFinContext+ -> ByteString+ -> [QueryParam]+ -> m (ApiResult a)+performRequest = performGenericRequest DecodeError ParseError+++------+-- List companies+------++-- | Fetch a list of company tickers and SimFin ids.+-- This is the only endpoint common to free and paid customers.++fetchCompanyList+ :: (MonadThrow m, MonadIO m)+ => SimFinContext+ -> m (Either ApiError [CompanyListingRow])+fetchCompanyList ctx =+ fmap unKeyCompanyListing <$> performRequest ctx "companies/list" []
+ src/SimFin/Free.hs view
@@ -0,0 +1,177 @@+{-|+Module : SimFin.Free+Description : SimFin Free API.+Copyright : (c) Owen Shepherd, 2022+License : MIT+Maintainer : owen@owen.cafe+-}++{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE ScopedTypeVariables #-}++module SimFin.Free+ ( SimFinContext(..)+ , Industry(..)++ , CompanyListingRow(..)+ , CompanyInfoRow(..)++ , GeneralBalanceSheetRow(..)+ , BankBalanceSheetRow(..)+ , InsuranceBalanceSheetRow(..)++ , GeneralProfitAndLossRow(..)+ , BankProfitAndLossRow(..)+ , InsuranceProfitAndLossRow(..)++ , GeneralCashFlowRow(..)+ , BankCashFlowRow(..)+ , InsuranceCashFlowRow(..)++ , DerivedRow(..)+ , PricesRow(..)++ , PricesQueryFree+ , StatementQueryFree(..)++ , StockRef(..)+ , FiscalPeriod(..)++ , ApiError(..)+ , ApiResult++ , createDefaultContext+ , fetchCompanyList++ , fetchCompanyInfo+ , fetchBalanceSheet+ , fetchProfitAndLoss+ , fetchCashFlow+ , fetchDerived+ , fetchPrices+ ) where++import Control.Monad.Catch+import Control.Monad.IO.Class+import Data.Functor.Syntax+import Data.Maybe (listToMaybe)++import SimFin.Common+import SimFin.Internal+import SimFin.Types.BalanceSheet+import SimFin.Types.CompanyInfo+import SimFin.Types.CompanyListing+import SimFin.Types.CashFlow+import SimFin.Types.Derived+import SimFin.Types.FiscalPeriod+import SimFin.Types.Industry+import SimFin.Types.Prices+import SimFin.Types.PricesQuery+import SimFin.Types.ProfitAndLoss+import SimFin.Types.StatementQuery+import SimFin.Types.StockRef+import SimFin.Util+++------+-- General Company Info+------++-- | Fetch general company information. See the [SimFin docs](https://simfin.com/api/v2/documentation/#tag/Company/paths/~1companies~1general/get).++fetchCompanyInfo+ :: (MonadThrow m, MonadIO m)+ => SimFinContext+ -> StockRef+ -> m (ApiResult (Maybe CompanyInfoRow))+fetchCompanyInfo ctx refs = do+ rows :: ApiResult [CompanyInfoRow] <- performRequest ctx "companies/general"+ $ stockRefsToQueryParams $ pure refs+ pure $ listToMaybe <$> rows++------+-- Balance Sheets+------++-- | Fetch a company's balance sheet statement. As this is the free API version, only one statement+-- is returned. The returned statement's data is dependent on the company type.+-- See the [SimFin docs](https://simfin.com/api/v2/documentation/#tag/Company/paths/~1companies~1statements/get).++fetchBalanceSheet+ :: (MonadThrow m, MonadIO m)+ => SimFinContext+ -> StatementQueryFree+ -> m (ApiResult (Maybe IndustryBalanceSheet))+fetchBalanceSheet ctx query = do+ nested :: ApiResult [IndustryBalanceSheets] <- performRequest ctx "companies/statements"+ $ ("statement", Just "bs") : statementQueryFreeToQueryParams query+ pure $ listToMaybe . invertIndustries <$> nested++------+-- P&L+------++-- | Fetch a company's profit and loss statement. As this is the free API version, only one statement+-- is returned. The returned statement's data is dependent on the company type.+-- See the [SimFin docs](https://simfin.com/api/v2/documentation/#tag/Company/paths/~1companies~1statements/get).++fetchProfitAndLoss+ :: (MonadThrow m, MonadIO m)+ => SimFinContext+ -> StatementQueryFree+ -> m (ApiResult (Maybe IndustryProfitAndLoss))+fetchProfitAndLoss ctx query = do+ nested :: ApiResult [IndustryProfitsAndLosses] <- performRequest ctx "companies/statements"+ $ ("statement", Just "pl") : statementQueryFreeToQueryParams query+ pure $ listToMaybe . invertIndustries <$> nested++-----+-- Cash Flows+------++-- | Fetch a company's cash flow statement. As this is the free API version, only one statement+-- is returned. The returned statement's data is dependent on the company type.+-- See the [SimFin docs](https://simfin.com/api/v2/documentation/#tag/Company/paths/~1companies~1statements/get).++fetchCashFlow+ :: (MonadThrow m, MonadIO m)+ => SimFinContext+ -> StatementQueryFree+ -> m (ApiResult (Maybe IndustryCashFlow))+fetchCashFlow ctx query = do+ nested :: ApiResult [IndustryCashFlows] <- performRequest ctx "companies/statements"+ $ ("statement", Just "cf") : statementQueryFreeToQueryParams query+ pure $ listToMaybe . invertIndustries <$> nested++------+-- Derived+------++-- | Fetch a company's derived figures. As this is the free API version, only one set+-- of data is returned.+-- See the [SimFin docs](https://simfin.com/api/v2/documentation/#tag/Company/paths/~1companies~1statements/get).++fetchDerived+ :: forall m a. (Read a, RealFrac a, MonadThrow m, MonadIO m)+ => SimFinContext+ -> StatementQueryFree+ -> m (ApiResult (Maybe (DerivedRow a)))+fetchDerived ctx query = do+ nested :: ApiResult [DerivedRowsKeyed a] <- performRequest ctx "companies/statements"+ (("statement", Just "derived") : statementQueryFreeToQueryParams query)+ pure $ listToMaybe . mconcat . fmap unDerivedRows <$> nested++------+-- Prices+------++-- | Fetch a company's historical share prices. See the [SimFin docs](https://simfin.com/api/v2/documentation/#tag/Company/paths/~1companies~1prices/get).++fetchPrices+ :: (Read a, RealFrac a, MonadThrow m, MonadIO m)+ => SimFinContext+ -> PricesQueryFree+ -> m (ApiResult [PricesRow a])+fetchPrices ctx query =+ mconcat . fmap unKeyPrices <$$> performRequest ctx "companies/prices"+ (pricesQueryFreeToQueryParams query)
+ src/SimFin/Internal.hs view
@@ -0,0 +1,138 @@+{-# LANGUAGE CPP #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE ScopedTypeVariables #-}++module SimFin.Internal+ ( SimFinContext(..)+ , QueryParam+ , createKeyedRow+ , createKeyedRows+ , toCommaQueryParam+ , toBoolQueryParam+ , toTextCommaQueryParam+ , toShownCommaQueryParam+ , baseRequest+ , makeRequest+ , performGenericRequest+ ) where++import Control.Monad.IO.Class+import Data.Aeson+import Data.Aeson.Types (parse, Parser)++#if MIN_VERSION_aeson(2,0,0)+import qualified Data.Aeson.Key as K+import qualified Data.Aeson.KeyMap as KM+#else+import qualified Data.HashMap.Strict as HM+#endif++import Data.ByteString (ByteString)+import qualified Data.ByteString.Char8 as BS8+import qualified Data.ByteString.Lazy as LBS+import Data.Functor.Syntax+import Data.Text (Text)+import qualified Data.Text.Encoding as T+import Network.HTTP.Client+import Network.HTTP.Types.Status++-- | The context needed to call every fetch* function.++data SimFinContext = SimFinContext+ { simFinApiKey :: ByteString+ , simFinManager :: Manager+ }++baseRequest :: Request+baseRequest = defaultRequest+ { host = "simfin.com"+ , port = 443+ , secure = True+ , requestHeaders =+ [ ("Accept", "application/json")+ ]+ }++basePath :: ByteString+basePath = "/api/v2/"++makeRequest :: ByteString -> ByteString -> [QueryParam] -> Request+makeRequest apiKey path query =+ setQueryString (("api-key", Just apiKey) : query)+ $ baseRequest { path = basePath <> path }++performGenericRequest+ :: ( MonadIO m+ , FromJSON a+ , FromJSON e+ )+ => (LBS.ByteString -> String -> e)+ -> (Value -> String -> e)+ -> SimFinContext+ -> ByteString+ -> [QueryParam]+ -> m (Either e a)+performGenericRequest mkDecodeErr mkParseErr SimFinContext{..} path query = do+ let req = makeRequest simFinApiKey path query+ res <- liftIO $ httpLbs req simFinManager+ let body = responseBody res+ -- Try to parse body into generic JSON+ pure $ case eitherDecode $ responseBody res of+ Left err -> Left $ mkDecodeErr body err+ Right value -> case statusCode $ responseStatus res of+ 200 -> case parse parseJSON value of+ Error err -> Left $ mkParseErr value err+ Success a -> Right a+ _ -> case parse parseJSON value of+ Error err -> Left $ mkParseErr value err+ Success a -> Left a++type QueryParam = (ByteString, Maybe ByteString)++#if MIN_VERSION_aeson(2,0,0)++toKey :: Text -> Key+toKey = K.fromText++toObject :: [(K.Key, Value)] -> Value+toObject = Object . KM.fromList++#else++toKey :: Text -> Text+toKey = id++toObject :: [(Text, Value)] -> Value+toObject = Object . HM.fromList ++#endif++createKeyedRow :: Value -> Parser Value+createKeyedRow = withObject "Root" $ \root -> do+ cols <- toKey <$$> root .: "columns"+ row <- root .: "data"+ pure $ toObject $ zip cols row++createKeyedRows :: Value -> Parser [Value]+createKeyedRows = withObject "Root" $ \root -> do+ cols <- toKey <$$> root .: "columns"+ rows <- root .: "data"+ pure $ toObject . zip cols <$> rows++toCommaQueryParam :: ByteString -> (a -> ByteString) -> [a] -> [QueryParam]+toCommaQueryParam key f as = case as of+ [] -> []+ _ -> [(key, Just $ BS8.intercalate "," $ f <$> as)]++-- Chars are truncated to 8-bits+toShownCommaQueryParam :: Show a => ByteString -> [a] -> [QueryParam]+toShownCommaQueryParam key = toCommaQueryParam key (BS8.pack . show)++toTextCommaQueryParam :: ByteString -> [Text] -> [QueryParam]+toTextCommaQueryParam key = toCommaQueryParam key T.encodeUtf8++toBoolQueryParam :: ByteString -> Bool -> [QueryParam]+toBoolQueryParam key b = case b of+ False -> []+ True -> [(key, Nothing)]
+ src/SimFin/Plus.hs view
@@ -0,0 +1,185 @@+{-|+Module : SimFin.Plus+Description : SimFin+ API.+Copyright : (c) Owen Shepherd, 2022+License : MIT+Maintainer : owen@owen.cafe+-}++{-# LANGUAGE OverloadedStrings #-}++module SimFin.Plus+ ( SimFinContext(..)+ , Industry(..)++ , CompanyListingRow(..)+ , CompanyInfoRow(..)++ , GeneralBalanceSheetRow(..)+ , BankBalanceSheetRow(..)+ , InsuranceBalanceSheetRow(..)++ , GeneralProfitAndLossRow(..)+ , BankProfitAndLossRow(..)+ , InsuranceProfitAndLossRow(..)++ , GeneralCashFlowRow(..)+ , BankCashFlowRow(..)+ , InsuranceCashFlowRow(..)++ , DerivedRow(..)+ , PricesRow(..)+ , RatiosRow(..)++ , PricesAndRatiosRow(..)++ , PricesQuery(..)+ , StatementQuery(..)++ , StockRef(..)+ , FiscalPeriod(..)++ , ApiError(..)+ , ApiResult++ , createDefaultContext+ , fetchCompanyList++ , fetchCompanyInfo+ , fetchBalanceSheets+ , fetchProfitsAndLosses+ , fetchCashFlows+ , fetchDerived+ , fetchPrices+ , fetchPricesAndRatios+ ) where++import Control.Monad.Catch+import Control.Monad.IO.Class+import Data.Functor.Syntax+import Data.List.NonEmpty (NonEmpty)++import SimFin.Common+import SimFin.Internal+import SimFin.Types.BalanceSheet+import SimFin.Types.CompanyInfo+import SimFin.Types.CompanyListing+import SimFin.Types.CashFlow+import SimFin.Types.Derived+import SimFin.Types.FiscalPeriod+import SimFin.Types.Industry+import SimFin.Types.Prices+import SimFin.Types.PricesQuery+import SimFin.Types.PricesAndRatios+import SimFin.Types.ProfitAndLoss+import SimFin.Types.Ratios+import SimFin.Types.StatementQuery+import SimFin.Types.StockRef+import SimFin.Util++------+-- General Company Info+------++-- | Fetch general company information.+-- See the [SimFin docs](https://simfin.com/api/v2/documentation/#tag/Company/paths/~1companies~1general/get).++fetchCompanyInfo+ :: (MonadThrow m, MonadIO m)+ => SimFinContext+ -> NonEmpty StockRef+ -> m (ApiResult [CompanyInfoRow])+fetchCompanyInfo ctx refs =+ performRequest ctx "companies/general" (stockRefsToQueryParams refs)++------+-- Balance Sheets+------++-- | Fetch a company's balance sheet statements.+-- See the [SimFin docs](https://simfin.com/api/v2/documentation/#tag/Company/paths/~1companies~1statements/get).++fetchBalanceSheets+ :: (MonadThrow m, MonadIO m)+ => SimFinContext+ -> StatementQuery+ -> m (ApiResult [IndustryBalanceSheet])+fetchBalanceSheets ctx query =+ invertIndustries <$$> performRequest ctx "companies/statements"+ (("statement", Just "bs") : statementQueryToQueryParams query)++------+-- P&L+------++-- | Fetch a company's profit and loss statements.+-- See the [SimFin docs](https://simfin.com/api/v2/documentation/#tag/Company/paths/~1companies~1statements/get).++fetchProfitsAndLosses+ :: (MonadThrow m, MonadIO m)+ => SimFinContext+ -> StatementQuery+ -> m (ApiResult [IndustryProfitAndLoss])+fetchProfitsAndLosses ctx query =+ invertIndustries <$$> performRequest ctx "companies/statements"+ (("statement", Just "pl") : statementQueryToQueryParams query)++-----+-- Cash Flows+------++-- | Fetch a company's cash flow statements.+-- See the [SimFin docs](https://simfin.com/api/v2/documentation/#tag/Company/paths/~1companies~1statements/get).++fetchCashFlows+ :: (MonadThrow m, MonadIO m)+ => SimFinContext+ -> StatementQuery+ -> m (ApiResult [IndustryCashFlow])+fetchCashFlows ctx query =+ invertIndustries <$$> performRequest ctx "companies/statements"+ (("statement", Just "cf") : statementQueryToQueryParams query)++------+-- Derived+------++-- | Fetch a company's derived figures.+-- See the [SimFin docs](https://simfin.com/api/v2/documentation/#tag/Company/paths/~1companies~1statements/get).++fetchDerived+ :: (Read a, RealFrac a, MonadThrow m, MonadIO m)+ => SimFinContext+ -> StatementQuery+ -> m (ApiResult [DerivedRow a])+fetchDerived ctx query =+ mconcat <$$> performRequest ctx "companies/statements"+ (("statement", Just "derived") : statementQueryToQueryParams query)++------+-- Prices+------++-- | Fetch a company's historical share prices.+-- See the [SimFin docs](https://simfin.com/api/v2/documentation/#tag/Company/paths/~1companies~1prices/get).++fetchPrices+ :: (Read a, RealFrac a, MonadThrow m, MonadIO m)+ => SimFinContext+ -> PricesQuery+ -> m (ApiResult [PricesRow a])+fetchPrices ctx query =+ mconcat . fmap unKeyPrices <$$> performRequest ctx "companies/prices"+ (pricesQueryToQueryParams query)++-- | Fetch a company's historical share prices, along with key ratios.+-- See the [SimFin docs](https://simfin.com/api/v2/documentation/#tag/Company/paths/~1companies~1prices/get).++fetchPricesAndRatios+ :: (Read a, RealFrac a, MonadThrow m, MonadIO m)+ => SimFinContext+ -> PricesQuery+ -> m (ApiResult [PricesAndRatiosRow a])+fetchPricesAndRatios ctx query =+ mconcat . fmap unKeyPricesAndRatios <$$> performRequest ctx "companies/prices"+ (("ratios", Nothing) : pricesQueryToQueryParams query)
+ src/SimFin/Types/BalanceSheet.hs view
@@ -0,0 +1,581 @@+{-|+Module : SimFin.Types.BalanceSheet+Description : Types to represent a company's balance sheet statement.+Copyright : (c) Owen Shepherd, 2022+License : MIT+Maintainer : owen@owen.cafe+-}++{-# LANGUAGE DuplicateRecordFields #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE FlexibleInstances #-}++module SimFin.Types.BalanceSheet+ ( GeneralBalanceSheetRow(..)+ , BankBalanceSheetRow(..)+ , InsuranceBalanceSheetRow(..)+ , IndustryBalanceSheets+ , IndustryBalanceSheet+ ) where++import Control.Applicative ((<|>))+import Data.Aeson+import Data.Text (Text)+import Data.Time.Calendar (Day)++import SimFin.Types.Industry+import SimFin.Internal++------+-- General+------++-- | Balance sheet statement for general companies. ++data GeneralBalanceSheetRow+ = GeneralBalanceSheetRow+ { simFinId :: Int+ , ticker :: Text+ , fiscalPeriod :: String+ , fiscalYear :: Int+ , reportDate :: Day+ , publishDate :: Day+ , restatedDate :: Day+ , source :: Text+ , tTM :: Bool+ , valueCheck :: Bool+ , cashCashEquivalentsAndShortTermInvestments :: Maybe Integer+ , cashAndCashEquivalents :: Maybe Integer+ , shortTermInvestments :: Maybe Integer+ , accountsAndNotesReceivable :: Maybe Integer+ , accountsReceivableNet :: Maybe Integer+ , notesReceivableNet :: Maybe Integer+ , unbilledRevenues :: Maybe Integer+ , inventories :: Maybe Integer+ , rawMaterials :: Maybe Integer+ , workInProcess :: Maybe Integer+ , finishedGoods :: Maybe Integer+ , otherInventory :: Maybe Integer+ , otherShortTermAssets :: Maybe Integer+ , prepaidExpenses :: Maybe Integer+ , derivativeAndHedgingAssetsShortTerm :: Maybe Integer+ , assetsHeldForSale :: Maybe Integer+ , deferredTaxAssetsShortTerm :: Maybe Integer+ , incomeTaxesReceivable :: Maybe Integer+ , discontinuedOperationsShortTerm :: Maybe Integer+ , miscShortTermAssets :: Maybe Integer+ , totalCurrentAssets :: Maybe Integer+ , propertyPlantAndEquipmentNet :: Maybe Integer+ , propertyPlantAndEquipment :: Maybe Integer+ , accumulatedDepreciation :: Maybe Integer+ , longTermInvestmentsAndReceivables :: Maybe Integer+ , longTermInvestments :: Maybe Integer+ , longTermMarketableSecurities :: Maybe Integer+ , longTermReceivables :: Maybe Integer+ , otherLongTermAssets :: Maybe Integer+ , intangibleAssets :: Maybe Integer+ , goodwill :: Maybe Integer+ , otherIntangibleAssets :: Maybe Integer+ , prepaidExpense :: Maybe Integer+ , deferredTaxAssetsLongTerm :: Maybe Integer+ , derivativeAndHedgingAssetsLongTerm :: Maybe Integer+ , prepaidPensionCosts :: Maybe Integer+ , discontinuedOperationsLongTerm :: Maybe Integer+ , investmentsinAffiliates :: Maybe Integer+ , miscLongTermAssets :: Maybe Integer+ , totalNoncurrentAssets :: Maybe Integer+ , totalAssets :: Maybe Integer+ , payablesAndAccruals :: Maybe Integer+ , accountsPayable :: Maybe Integer+ , accruedTaxes :: Maybe Integer+ , interestAndDividendsPayable :: Maybe Integer+ , otherPayablesAndAccruals :: Maybe Integer+ , shortTermDebt :: Maybe Integer+ , shortTermBorrowings :: Maybe Integer+ , shortTermCapitalLeases :: Maybe Integer+ , currentPortionOfLongTermDebt :: Maybe Integer+ , otherShortTermLiabilities :: Maybe Integer+ , deferredRevenueShortTerm :: Maybe Integer+ , liabilitiesfromDerivativesAndHedgingShortTerm :: Maybe Integer+ , deferredTaxLiabilitiesShortTerm :: Maybe Integer+ , liabilitiesfromDiscontinuedOperationsShortTerm :: Maybe Integer+ , miscShortTermLiabilities :: Maybe Integer+ , totalCurrentLiabilities :: Maybe Integer+ , longTermDebt :: Maybe Integer+ , longTermBorrowings :: Maybe Integer+ , longTermCapitalLeases :: Maybe Integer+ , otherLongTermLiabilities :: Maybe Integer+ , accruedLiabilities :: Maybe Integer+ , pensionLiabilities :: Maybe Integer+ , pensions :: Maybe Integer+ , otherPostRetirementBenefits :: Maybe Integer+ , deferredCompensation :: Maybe Integer+ , deferredRevenueLongTerm :: Maybe Integer+ , deferredTaxLiabilitiesLongTerm :: Maybe Integer+ , liabilitiesfromDerivativesAndHedgingLongTerm :: Maybe Integer+ , liabilitiesfromDiscontinuedOperationsLongTerm :: Maybe Integer+ , miscLongTermLiabilities :: Maybe Integer+ , totalNoncurrentLiabilities :: Maybe Integer+ , totalLiabilities :: Maybe Integer+ , preferredEquity :: Maybe Integer+ , shareCapitalAndAdditionalPaidInCapital :: Maybe Integer+ , commonStock :: Maybe Integer+ , additionalPaidinCapital :: Maybe Integer+ , otherShareCapital :: Maybe Integer+ , treasuryStock :: Maybe Integer+ , retainedEarnings :: Maybe Integer+ , otherEquity :: Maybe Integer+ , equityBeforeMinorityInterest :: Maybe Integer+ , minorityInterest :: Maybe Integer+ , totalEquity :: Maybe Integer+ , totalLiabilitiesAndEquity :: Maybe Integer+ } deriving Show++instance FromJSON GeneralBalanceSheetRow where+ parseJSON = withObject "GeneralBalanceSheetRow" $ \v -> GeneralBalanceSheetRow+ <$> v .: "SimFinId"+ <*> v .: "Ticker"+ <*> v .: "Fiscal Period"+ <*> v .: "Fiscal Year"+ <*> v .: "Report Date"+ <*> v .: "Publish Date"+ <*> v .: "Restated Date"+ <*> v .: "Source"+ <*> v .: "TTM"+ <*> v .: "Value Check"+ <*> v .: "Cash, Cash Equivalents & Short Term Investments"+ <*> v .: "Cash & Cash Equivalents"+ <*> v .: "Short Term Investments"+ <*> v .: "Accounts & Notes Receivable"+ <*> v .: "Accounts Receivable, Net"+ <*> v .: "Notes Receivable, Net"+ <*> v .: "Unbilled Revenues"+ <*> v .: "Inventories"+ <*> v .: "Raw Materials"+ <*> v .: "Work In Process"+ <*> v .: "Finished Goods"+ <*> v .: "Other Inventory"+ <*> v .: "Other Short Term Assets"+ <*> v .: "Prepaid Expenses"+ <*> v .: "Derivative & Hedging Assets (Short Term)"+ <*> v .: "Assets Held-for-Sale"+ <*> v .: "Deferred Tax Assets (Short Term)"+ <*> v .: "Income Taxes Receivable"+ <*> v .: "Discontinued Operations (Short Term)"+ <*> v .: "Misc. Short Term Assets"+ <*> v .: "Total Current Assets"+ <*> v .: "Property, Plant & Equipment, Net"+ <*> v .: "Property, Plant & Equipment"+ <*> v .: "Accumulated Depreciation"+ <*> v .: "Long Term Investments & Receivables"+ <*> v .: "Long Term Investments"+ <*> v .: "Long Term Marketable Securities"+ <*> v .: "Long Term Receivables"+ <*> v .: "Other Long Term Assets"+ <*> v .: "Intangible Assets"+ <*> v .: "Goodwill"+ <*> v .: "Other Intangible Assets"+ <*> v .: "Prepaid Expense"+ <*> v .: "Deferred Tax Assets (Long Term)"+ <*> v .: "Derivative & Hedging Assets (Long Term)"+ <*> v .: "Prepaid Pension Costs"+ <*> v .: "Discontinued Operations (Long Term)"+ <*> v .: "Investments in Affiliates"+ <*> v .: "Misc. Long Term Assets"+ <*> v .: "Total Noncurrent Assets"+ <*> v .: "Total Assets"+ <*> v .: "Payables & Accruals"+ <*> v .: "Accounts Payable"+ <*> v .: "Accrued Taxes"+ <*> v .: "Interest & Dividends Payable"+ <*> v .: "Other Payables & Accruals"+ <*> v .: "Short Term Debt"+ <*> v .: "Short Term Borrowings"+ <*> v .: "Short Term Capital Leases"+ <*> v .: "Current Portion of Long Term Debt"+ <*> v .: "Other Short Term Liabilities"+ <*> v .: "Deferred Revenue (Short Term)"+ <*> v .: "Liabilities from Derivatives & Hedging (Short Term)"+ <*> v .: "Deferred Tax Liabilities (Short Term)"+ <*> v .: "Liabilities from Discontinued Operations (Short Term)"+ <*> v .: "Misc. Short Term Liabilities"+ <*> v .: "Total Current Liabilities"+ <*> v .: "Long Term Debt"+ <*> v .: "Long Term Borrowings"+ <*> v .: "Long Term Capital Leases"+ <*> v .: "Other Long Term Liabilities"+ <*> v .: "Accrued Liabilities"+ <*> v .: "Pension Liabilities"+ <*> v .: "Pensions"+ <*> v .: "Other Post-Retirement Benefits"+ <*> v .: "Deferred Compensation"+ <*> v .: "Deferred Revenue (Long Term)"+ <*> v .: "Deferred Tax Liabilities (Long Term)"+ <*> v .: "Liabilities from Derivatives & Hedging (Long Term)"+ <*> v .: "Liabilities from Discontinued Operations (Long Term)"+ <*> v .: "Misc. Long Term Liabilities"+ <*> v .: "Total Noncurrent Liabilities"+ <*> v .: "Total Liabilities"+ <*> v .: "Preferred Equity"+ <*> v .: "Share Capital & Additional Paid-In Capital"+ <*> v .: "Common Stock"+ <*> v .: "Additional Paid in Capital"+ <*> v .: "Other Share Capital"+ <*> v .: "Treasury Stock"+ <*> v .: "Retained Earnings"+ <*> v .: "Other Equity"+ <*> v .: "Equity Before Minority Interest"+ <*> v .: "Minority Interest"+ <*> v .: "Total Equity"+ <*> v .: "Total Liabilities & Equity"++-- | Wrapper to parse a GeneralBalanceSheetRow record from SimFin's JSON format.++newtype GeneralBalanceSheetsKeyed = GeneralBalanceSheetsKeyed { unKeyGeneralBalanceSheets :: [GeneralBalanceSheetRow] }++instance FromJSON GeneralBalanceSheetsKeyed where+ parseJSON o = GeneralBalanceSheetsKeyed <$> (traverse parseJSON =<< createKeyedRows o)+++------+-- Bank+------++-- | Balance sheet statement for banks. ++data BankBalanceSheetRow =+ BankBalanceSheetRow+ { simFinId :: Int+ , ticker :: Text+ , fiscalPeriod :: String+ , fiscalYear :: Int+ , reportDate :: Day+ , publishDate :: Day+ , restatedDate :: Day+ , source :: Text+ , tTM :: Bool+ , valueCheck :: Bool+ , cashCashEquivalentsAndShortTermInvestments :: Maybe Integer+ , interbankAssets :: Maybe Integer+ , fedFundsSoldAndRepos :: Maybe Integer+ , otherInterbankAssets :: Maybe Integer+ , shortAndLongTermInvestments :: Maybe Integer+ , tradingSecurities :: Maybe Integer+ , investmentSecuritiesAvailableforSale :: Maybe Integer+ , investmentSecuritiesHeldtoMaturity :: Maybe Integer+ , realEstateInvestments :: Maybe Integer+ , otherInvestments :: Maybe Integer+ , accountsAndNotesReceivable :: Maybe Integer+ , netLoans :: Maybe Integer+ , reserveforLoanLosses :: Maybe Integer+ , totalLoans :: Maybe Integer+ , totalCommercialLoans :: Maybe Integer+ , commercialRealEstateLoans :: Maybe Integer+ , otherCommercialLoans :: Maybe Integer+ , totalConsumerLoans :: Maybe Integer+ , creditCardLoans :: Maybe Integer+ , homeEquityLoans :: Maybe Integer+ , familyResidentialLoans :: Maybe Integer+ , autoLoans :: Maybe Integer+ , studentLoans :: Maybe Integer+ , otherConsumerLoans :: Maybe Integer+ , otherLoans :: Maybe Integer+ , netFixedAssets :: Maybe Integer+ , propertyPlantAndEquipmentNet :: Maybe Integer+ , operatingLeaseAssets :: Maybe Integer+ , otherFixedAssets :: Maybe Integer+ , intangibleAssets :: Maybe Integer+ , goodwill :: Maybe Integer+ , otherIntangibleAssets :: Maybe Integer+ , investmentsInAssociates :: Maybe Integer+ , deferredTaxAssetsShortTerm :: Maybe Integer+ , derivativesAndHedgingAssets :: Maybe Integer+ , discontinuedOperationsAssets :: Maybe Integer+ , customerAcceptancesAndLiabilities :: Maybe Integer+ , otherAssets :: Maybe Integer+ , totalAssets :: Maybe Integer+ , totalDeposits :: Maybe Integer+ , demandDeposits :: Maybe Integer+ , interestBearingDeposits :: Maybe Integer+ , savingDeposits :: Maybe Integer+ , timeDeposits :: Maybe Integer+ , otherDeposits :: Maybe Integer+ , shortTermDebt :: Maybe Integer+ , securitiesSoldUnderRepo :: Maybe Integer+ , tradingAccountLiabilities :: Maybe Integer+ , shortTermCapitalLeases :: Maybe Integer+ , currentPortionofLongTermDebt :: Maybe Integer+ , shortTermBorrowings :: Maybe Integer+ , payablesBrokerDealers :: Maybe Integer+ , longTermDebt :: Maybe Integer+ , longTermCapitalLeases :: Maybe Integer+ , longTermBorrowings :: Maybe Integer+ , pensionLiabilities :: Maybe Integer+ , pensions :: Maybe Integer+ , otherPostRetirementBenefits :: Maybe Integer+ , deferredTaxLiabilitiesShortTerm :: Maybe Integer+ , derivativesAndHedgingLiabilities :: Maybe Integer+ , discontinuedOperationsLiabilities :: Maybe Integer+ , otherLiabilities :: Maybe Integer+ , totalLiabilities :: Maybe Integer+ , preferredEquity :: Maybe Integer+ , shareCapitalAndAdditionalPaidInCapital :: Maybe Integer+ , commonStock :: Maybe Integer+ , additionalPaidInCapital :: Maybe Integer+ , otherShareCapital :: Maybe Integer+ , treasuryStock :: Maybe Integer+ , retainedEarnings :: Maybe Integer+ , otherEquity :: Maybe Integer+ , equityBeforeMinorityInterest :: Maybe Integer+ , minorityInterest :: Maybe Integer+ , totalEquity :: Maybe Integer+ , totalLiabilitiesAndEquity :: Maybe Integer+ } deriving Show++instance FromJSON BankBalanceSheetRow where+ parseJSON = withObject "BankBalanceSheetRow" $ \v -> BankBalanceSheetRow+ <$> v .: "SimFinId"+ <*> v .: "Ticker"+ <*> v .: "Fiscal Period"+ <*> v .: "Fiscal Year"+ <*> v .: "Report Date"+ <*> v .: "Publish Date"+ <*> v .: "Restated Date"+ <*> v .: "Source"+ <*> v .: "TTM"+ <*> v .: "Value Check"+ <*> v .: "Cash, Cash Equivalents & Short Term Investments"+ <*> v .: "Interbank Assets"+ <*> v .: "Fed Funds Sold & Repos"+ <*> v .: "Other Interbank Assets"+ <*> v .: "Short & Long Term Investments"+ <*> v .: "Trading Securities"+ <*> v .: "Investment Securities Available for Sale"+ <*> v .: "Investment Securities Held to Maturity"+ <*> v .: "Real Estate Investments"+ <*> v .: "Other Investments"+ <*> v .: "Accounts & Notes Receivable"+ <*> v .: "Net Loans"+ <*> v .: "Reserve for Loan Losses"+ <*> v .: "Total Loans"+ <*> v .: "Total Commercial Loans"+ <*> v .: "Commercial Real Estate Loans"+ <*> v .: "Other Commercial Loans"+ <*> v .: "Total Consumer Loans"+ <*> v .: "Credit Card Loans"+ <*> v .: "Home Equity Loans"+ <*> v .: "Family Residential Loans"+ <*> v .: "Auto Loans"+ <*> v .: "Student Loans"+ <*> v .: "Other Consumer Loans"+ <*> v .: "Other Loans"+ <*> v .: "Net Fixed Assets"+ <*> v .: "Property, Plant & Equipment, Net"+ <*> v .: "Operating Lease Assets"+ <*> v .: "Other Fixed Assets"+ <*> v .: "Intangible Assets"+ <*> v .: "Goodwill"+ <*> v .: "Other Intangible Assets"+ <*> v .: "Investments in Associates"+ <*> v .: "Deferred Tax Assets (Short Term)"+ <*> v .: "Derivatives & Hedging (Assets)"+ <*> v .: "Discontinued Operations (Assets)"+ <*> v .: "Customer Acceptances & Liabilities"+ <*> v .: "Other Assets"+ <*> v .: "Total Assets"+ <*> v .: "Total Deposits"+ <*> v .: "Demand Deposits"+ <*> v .: "Interest Bearing Deposits"+ <*> v .: "Saving Deposits"+ <*> v .: "Time Deposits"+ <*> v .: "Other Deposits"+ <*> v .: "Short Term Debt"+ <*> v .: "Securities Sold Under Repo"+ <*> v .: "Trading Account Liabilities"+ <*> v .: "Short Term Capital Leases"+ <*> v .: "Current Portion of Long Term Debt"+ <*> v .: "Short Term Borrowings"+ <*> v .: "Payables Broker Dealers"+ <*> v .: "Long Term Debt"+ <*> v .: "Long Term Capital Leases"+ <*> v .: "Long Term Borrowings"+ <*> v .: "Pension Liabilities"+ <*> v .: "Pensions"+ <*> v .: "Other Post-Retirement Benefits"+ <*> v .: "Deferred Tax Liabilities (Short Term)"+ <*> v .: "Derivatives & Hedging (Liabilities)"+ <*> v .: "Discontinued Operations (Liabilities)"+ <*> v .: "Other Liabilities"+ <*> v .: "Total Liabilities"+ <*> v .: "Preferred Equity"+ <*> v .: "Share Capital & Additional Paid-In Capital"+ <*> v .: "Common Stock"+ <*> v .: "Additional Paid in Capital"+ <*> v .: "Other Share Capital"+ <*> v .: "Treasury Stock"+ <*> v .: "Retained Earnings"+ <*> v .: "Other Equity"+ <*> v .: "Equity Before Minority Interest"+ <*> v .: "Minority Interest"+ <*> v .: "Total Equity"+ <*> v .: "Total Liabilities & Equity"++-- | Wrapper to parse a BankBalanceSheetRow record from SimFin's JSON format.++newtype BankBalanceSheetsKeyed = BankBalanceSheetsKeyed { unKeyBankBalanceSheets :: [BankBalanceSheetRow] }++instance FromJSON BankBalanceSheetsKeyed where+ parseJSON o = BankBalanceSheetsKeyed <$> (traverse parseJSON =<< createKeyedRows o)+++------+-- Insurance+------++-- | Balance sheet statement for insurance companies. ++data InsuranceBalanceSheetRow =+ InsuranceBalanceSheetRow+ { simFinId :: Int+ , ticker :: Text+ , fiscalPeriod :: String+ , fiscalYear :: Int+ , reportDate :: Day+ , publishDate :: Day+ , restatedDate :: Day+ , source :: Text+ , tTM :: Bool+ , valueCheck :: Bool+ , totalInvestments :: Maybe Integer+ , fixedIncomeTradingAFSAndShortTermInv :: Maybe Integer+ , loansAndMortgages :: Maybe Integer+ , fixedIncomeSecuritiesHTM :: Maybe Integer+ , equitySecurities :: Maybe Integer+ , realEstateInvestments :: Maybe Integer+ , otherInvestments :: Maybe Integer+ , cashCashEquivalentsAndShortTermInvestments :: Maybe Integer+ , accountsAndNotesReceivable :: Maybe Integer+ , propertyPlantAndEquipmentNet :: Maybe Integer+ , deferredPolicyAcquisitionCosts :: Maybe Integer+ , otherAssets :: Maybe Integer+ , totalAssets :: Maybe Integer+ , insuranceReserves :: Maybe Integer+ , reserveForOutstandingClaimsAndLosses :: Maybe Integer+ , premiumReserveUnearned :: Maybe Integer+ , lifePolicyBenefits :: Maybe Integer+ , otherInsuranceReserves :: Maybe Integer+ , shortTermDebt :: Maybe Integer+ , otherShortTermLiabilities :: Maybe Integer+ , longTermDebt :: Maybe Integer+ , pensionLiabilities :: Maybe Integer+ , pensions :: Maybe Integer+ , otherPostRetirementBenefits :: Maybe Integer+ , otherLongTermLiabilities :: Maybe Integer+ , fundsForFutureAppropriations :: Maybe Integer+ , totalLiabilities :: Maybe Integer+ , preferredEquity :: Maybe Integer+ , policyholdersEquity :: Maybe Integer+ , shareCapitalAndAdditionalPaidInCapital :: Maybe Integer+ , commonStock :: Maybe Integer+ , additionalPaidInCapital :: Maybe Integer+ , otherShareCapital :: Maybe Integer+ , treasuryStock :: Maybe Integer+ , retainedEarnings :: Maybe Integer+ , otherEquity :: Maybe Integer+ , equityBeforeMinorityInterest :: Maybe Integer+ , minorityInterest :: Maybe Integer+ , totalEquity :: Maybe Integer+ , totalLiabilitiesAndEquity :: Maybe Integer+ } deriving Show++instance FromJSON InsuranceBalanceSheetRow where+ parseJSON = withObject "InsuranceBalanceSheetRow" $ \v -> InsuranceBalanceSheetRow+ <$> v .: "SimFinId"+ <*> v .: "Ticker"+ <*> v .: "Fiscal Period"+ <*> v .: "Fiscal Year"+ <*> v .: "Report Date"+ <*> v .: "Publish Date"+ <*> v .: "Restated Date"+ <*> v .: "Source"+ <*> v .: "TTM"+ <*> v .: "Value Check"+ <*> v .: "Total Investments"+ <*> v .: "Fixed Income-Trading/AFS & Short Term Inv."+ <*> v .: "Loans & Mortgages"+ <*> v .: "Fixed Income Securities HTM"+ <*> v .: "Equity Securities"+ <*> v .: "Real Estate Investments"+ <*> v .: "Other Investments"+ <*> v .: "Cash, Cash Equivalents & Short Term Investments"+ <*> v .: "Accounts & Notes Receivable"+ <*> v .: "Property, Plant & Equipment, Net"+ <*> v .: "Deferred Policy Acquisition Costs"+ <*> v .: "Other Assets"+ <*> v .: "Total Assets"+ <*> v .: "Insurance Reserves"+ <*> v .: "Reserve for Outstanding Claims & Losses"+ <*> v .: "Premium Reserve (Unearned)"+ <*> v .: "Life Policy Benefits"+ <*> v .: "Other Insurance Reserves"+ <*> v .: "Short Term Debt"+ <*> v .: "Other Short Term Liabilities"+ <*> v .: "Long Term Debt"+ <*> v .: "Pension Liabilities"+ <*> v .: "Pensions"+ <*> v .: "Other Post-Retirement Benefits"+ <*> v .: "Other Long Term Liabilities"+ <*> v .: "Funds for Future Appropriations"+ <*> v .: "Total Liabilities"+ <*> v .: "Preferred Equity"+ <*> v .: "Policyholders Equity"+ <*> v .: "Share Capital & Additional Paid-In Capital"+ <*> v .: "Common Stock"+ <*> v .: "Additional Paid in Capital"+ <*> v .: "Other Share Capital"+ <*> v .: "Treasury Stock"+ <*> v .: "Retained Earnings"+ <*> v .: "Other Equity"+ <*> v .: "Equity Before Minority Interest"+ <*> v .: "Minority Interest"+ <*> v .: "Total Equity"+ <*> v .: "Total Liabilities & Equity"++-- | Wrapper to parse an InsuranceBalanceSheetRow record from SimFin's JSON format.++newtype InsuranceBalanceSheetsKeyed = InsuranceBalanceSheetsKeyed { unKeyInsuranceBalanceSheets :: [InsuranceBalanceSheetRow] }++instance FromJSON InsuranceBalanceSheetsKeyed where+ parseJSON o = InsuranceBalanceSheetsKeyed <$> (traverse parseJSON =<< createKeyedRows o)+++------+-- Industry+------++type IndustryBalanceSheetsKeyed+ = Industry GeneralBalanceSheetsKeyed BankBalanceSheetsKeyed InsuranceBalanceSheetsKeyed++-- | Discrimination of balance sheet lists.++type IndustryBalanceSheets+ = Industry [GeneralBalanceSheetRow] [BankBalanceSheetRow] [InsuranceBalanceSheetRow]++-- | Discrimination of balance sheets.++type IndustryBalanceSheet+ = Industry GeneralBalanceSheetRow BankBalanceSheetRow InsuranceBalanceSheetRow++instance FromJSON IndustryBalanceSheetsKeyed where+ parseJSON root = General <$> parseJSON root+ <|> Bank <$> parseJSON root+ <|> Insurance <$> parseJSON root++unKeyIndustryBalanceSheets :: IndustryBalanceSheetsKeyed -> IndustryBalanceSheets+unKeyIndustryBalanceSheets = mapIndustry+ unKeyGeneralBalanceSheets+ unKeyBankBalanceSheets+ unKeyInsuranceBalanceSheets++instance FromJSON IndustryBalanceSheets where+ parseJSON root = unKeyIndustryBalanceSheets <$> parseJSON root
+ src/SimFin/Types/CashFlow.hs view
@@ -0,0 +1,492 @@+{-|+Module : SimFin.Types.CashFlow+Description : Types to represent a company's cash flow statement.+Copyright : (c) Owen Shepherd, 2022+License : MIT+Maintainer : owen@owen.cafe+-}++{-# LANGUAGE DuplicateRecordFields #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE FlexibleInstances #-}++module SimFin.Types.CashFlow+ ( GeneralCashFlowRow(..)+ , BankCashFlowRow(..)+ , InsuranceCashFlowRow(..)+ , IndustryCashFlows+ , IndustryCashFlow+ ) where++import Control.Applicative ((<|>))+import Data.Aeson+import Data.Text (Text)+import Data.Time.Calendar (Day)++import SimFin.Types.Industry+import SimFin.Internal+++------+-- General+------++-- | Cash flow statement for general companies. ++data GeneralCashFlowRow+ = GeneralCashFlowRow+ { simFinId :: Int+ , ticker :: Text+ , fiscalPeriod :: String+ , fiscalYear :: Int+ , reportDate :: Day+ , publishDate :: Day+ , restatedDate :: Day+ , source :: Text+ , tTM :: Bool+ , valueCheck :: Bool+ , netIncomeStartingLine :: Maybe Integer+ , netIncome :: Maybe Integer+ , netIncomeFromDiscontinuedOperations :: Maybe Integer+ , otherAdjustmensts :: Maybe Integer+ , depreciationAndAmortizatison :: Maybe Integer+ , nonCashItems :: Maybe Integer+ , stockBasedCompensatison :: Maybe Integer+ , deferredIncomeTaxes :: Maybe Integer+ , otherNonCashAdjustments :: Maybe Integer+ , changeInWorkingCapital :: Maybe Integer+ , changeInAccountsReceivable :: Maybe Integer+ , changeInInventories :: Maybe Integer+ , changeInAccountsPayable :: Maybe Integer+ , changeInOther :: Maybe Integer+ , netCashFromDiscontinuedOperationsOperating :: Maybe Integer+ , netCashFromOpesratingActivities :: Maybe Integer+ , changeInFixedAssetsAndIntsangibles :: Maybe Integer+ , dispositionOfFixedAssetssAndIntangibles :: Maybe Integer+ , dispositionOfFixedAsssets :: Maybe Integer+ , dispositionOfIntangibleAssets :: Maybe Integer+ , acquisitionOfFixedAssetsAndIntangibles :: Maybe Integer+ , purchaseOfFixedAssets :: Maybe Integer+ , acquisitionOfIntangibleAssets :: Maybe Integer+ , otherChangeInFixedAssetsAndIntangibles :: Maybe Integer+ , netChangeInLongTermInvestment :: Maybe Integer+ , decreaseInLongTermInvestment :: Maybe Integer+ , increaseInLongTermInvestment :: Maybe Integer+ , netCashFromAcquisitionsAndDivestitures :: Maybe Integer+ , netCashFromDivestitures :: Maybe Integer+ , cashForAcquisitionOfSubsidiaries :: Maybe Integer+ , cashForJointVentures :: Maybe Integer+ , netCashFromOtherAcquisitions :: Maybe Integer+ , otherInvestingActivities :: Maybe Integer+ , netCashFromDiscontinuedOperationsInvesting :: Maybe Integer+ , netCashFromInvestingActivities :: Maybe Integer+ , dividendsPaid :: Maybe Integer+ , cashFromRepaymentOfDebt :: Maybe Integer+ , cashFromRepaymentOfShortTermDebtNet :: Maybe Integer+ , cashFromRepaymentOfLongTermDebtNet :: Maybe Integer+ , repaymentsOfLongTermDebt :: Maybe Integer+ , cashFromLongTermDebt :: Maybe Integer+ , cashFromRepurchaseOfEquity :: Maybe Integer+ , increaseInCapitalStock :: Maybe Integer+ , decreaseInCapitalStock :: Maybe Integer+ , otherFinancingActivities :: Maybe Integer+ , netCashFromDiscontinuedOperationsFinancing :: Maybe Integer+ , netCashFromFinancingActivities :: Maybe Integer+ , netCashBeforeDiscOperationsAndFX :: Maybe Integer+ , changeInCashFromDiscOperationsAndOther :: Maybe Integer+ , netCashBeforeFX :: Maybe Integer+ , effectOfForeignExchangeRates :: Maybe Integer+ , netChangeInCash :: Maybe Integer+ } deriving Show++instance FromJSON GeneralCashFlowRow where+ parseJSON = withObject "GeneralCashFlowRow" $ \v -> GeneralCashFlowRow+ <$> v .: "SimFinId"+ <*> v .: "Ticker"+ <*> v .: "Fiscal Period"+ <*> v .: "Fiscal Year"+ <*> v .: "Report Date"+ <*> v .: "Publish Date"+ <*> v .: "Restated Date"+ <*> v .: "Source"+ <*> v .: "TTM"+ <*> v .: "Value Check"+ <*> v .: "Net Income/Starting Line"+ <*> v .: "Net Income"+ <*> v .: "Net Income from Discontinued Operations"+ <*> v .: "Other Adjustments"+ <*> v .: "Depreciation & Amortization"+ <*> v .: "Non-Cash Items"+ <*> v .: "Stock-Based Compensation"+ <*> v .: "Deferred Income Taxes"+ <*> v .: "Other Non-Cash Adjustments"+ <*> v .: "Change in Working Capital"+ <*> v .: "Change in Accounts Receivable"+ <*> v .: "Change in Inventories"+ <*> v .: "Change in Accounts Payable"+ <*> v .: "Change in Other"+ <*> v .: "Net Cash from Discontinued Operations (Operating)"+ <*> v .: "Net Cash from Operating Activities"+ <*> v .: "Change in Fixed Assets & Intangibles"+ <*> v .: "Disposition of Fixed Assets & Intangibles"+ <*> v .: "Disposition of Fixed Assets"+ <*> v .: "Disposition of Intangible Assets"+ <*> v .: "Acquisition of Fixed Assets & Intangibles"+ <*> v .: "Purchase of Fixed Assets"+ <*> v .: "Acquisition of Intangible Assets"+ <*> v .: "Other Change in Fixed Assets & Intangibles"+ <*> v .: "Net Change in Long Term Investment"+ <*> v .: "Decrease in Long Term Investment"+ <*> v .: "Increase in Long Term Investment"+ <*> v .: "Net Cash from Acquisitions & Divestitures"+ <*> v .: "Net Cash from Divestitures"+ <*> v .: "Cash for Acquisition of Subsidiaries"+ <*> v .: "Cash for Joint Ventures"+ <*> v .: "Net Cash from Other Acquisitions"+ <*> v .: "Other Investing Activities"+ <*> v .: "Net Cash from Discontinued Operations (Investing)"+ <*> v .: "Net Cash from Investing Activities"+ <*> v .: "Dividends Paid"+ <*> v .: "Cash from (Repayment of) Debt"+ <*> v .: "Cash from (Repayment of) Short Term Debt, Net"+ <*> v .: "Cash from (Repayment of) Long Term Debt, Net"+ <*> v .: "Repayments of Long Term Debt"+ <*> v .: "Cash from Long Term Debt"+ <*> v .: "Cash from (Repurchase of) Equity"+ <*> v .: "Increase in Capital Stock"+ <*> v .: "Decrease in Capital Stock"+ <*> v .: "Other Financing Activities"+ <*> v .: "Net Cash from Discontinued Operations (Financing)"+ <*> v .: "Net Cash from Financing Activities"+ <*> v .: "Net Cash Before Disc. Operations and FX"+ <*> v .: "Change in Cash from Disc. Operations and Other"+ <*> v .: "Net Cash Before FX"+ <*> v .: "Effect of Foreign Exchange Rates"+ <*> v .: "Net Change in Cash"++-- | Wrapper to parse a GeneralCashFlowRow record from SimFin's JSON format.++newtype GeneralCashFlowsKeyed = GeneralCashFlowsKeyed { unKeyGeneralCashFlows :: [GeneralCashFlowRow] }++instance FromJSON GeneralCashFlowsKeyed where+ parseJSON o = GeneralCashFlowsKeyed <$> (traverse parseJSON =<< createKeyedRows o)+++------+-- Bank+------++-- | Cash flow statement for banks. ++data BankCashFlowRow+ = BankCashFlowRow+ { simFinId :: Int+ , ticker :: Text+ , fiscalPeriod :: String+ , fiscalYear :: Int+ , reportDate :: Day+ , publishDate :: Day+ , restatedDate :: Day+ , source :: Text+ , tTM :: Bool+ , valueCheck :: Bool+ , netIncomeStartingLine :: Maybe Integer+ , netIncome :: Maybe Integer+ , netIncomeFromDiscontinuedOperations :: Maybe Integer+ , otherAdjustments :: Maybe Integer+ , depreciationAndAmortization :: Maybe Integer+ , provisionForLoanLosses :: Maybe Integer+ , nonCashItems :: Maybe Integer+ , gainOnSaleOfSecuritiesAndLoans :: Maybe Integer+ , deferredIncomeTaxes :: Maybe Integer+ , stockBasedCompensation :: Maybe Integer+ , otherNonCashAdjustments :: Maybe Integer+ , changeInWorkingCapital :: Maybe Integer+ , tradingAssetsAndLiabilities :: Maybe Integer+ , netChangeOfInvestments :: Maybe Integer+ , netChangeOfInterbankAssets :: Maybe Integer+ , netChangeOfInterbankLiabilities :: Maybe Integer+ , netChangeInOperatingLoans :: Maybe Integer+ , accruedInterestReceivable :: Maybe Integer+ , accruedInterestPayable :: Maybe Integer+ , otherOperatingAssetsLiabilities :: Maybe Integer+ , netCashFromDiscontinuedOperationsOperating :: Maybe Integer+ , netCashFromOperatingActivities :: Maybe Integer+ , changeInFixedAssetsAndIntangibles :: Maybe Integer+ , dispositionOfFixedAssetsAndIntangibles :: Maybe Integer+ , capitalExpenditures :: Maybe Integer+ , netChangeInInvestments :: Maybe Integer+ , decreaseInInvestments :: Maybe Integer+ , decreaseInHTMInvestments :: Maybe Integer+ , decreaseInAFSInvestments :: Maybe Integer+ , increaseInInvestments :: Maybe Integer+ , increaseInHTMInvestments :: Maybe Integer+ , increaseInAFSInvestments :: Maybe Integer+ , netChangeInOtherInvestments :: Maybe Integer+ , netChangeInLoansAndInterbank :: Maybe Integer+ , netChangeInCustomerLoans :: Maybe Integer+ , netChangeInInterbankAssets :: Maybe Integer+ , netChangeInOtherLoans :: Maybe Integer+ , netCashFromAcquisitionsAndDivestitures :: Maybe Integer+ , netCashFromDivestitures :: Maybe Integer+ , cashForAcquisitionOfSubsidiaries :: Maybe Integer+ , cashForJointVentures :: Maybe Integer+ , netCashFromOtherAcquisitions :: Maybe Integer+ , otherInvestingActivities :: Maybe Integer+ , netCashFromDiscontinuedOperationsInvesting :: Maybe Integer+ , netCashFromInvestingActivities :: Maybe Integer+ , dividendsPaid :: Maybe Integer+ , cashFromRepaymentOfDebt :: Maybe Integer+ , cashFromRepaymentOfShortTermDebtNet :: Maybe Integer+ , netChangeInInterbankTransfers :: Maybe Integer+ , cashFromRepaymentOfLongTermDebtNet :: Maybe Integer+ , repaymentsOfLongTermDebt :: Maybe Integer+ , cashFromLongTermDebt :: Maybe Integer+ , cashFromRepurchaseOfEquity :: Maybe Integer+ , increaseInCapitalStock :: Maybe Integer+ , decreaseInCapitalStock :: Maybe Integer+ , netChangeInDeposits :: Maybe Integer+ , otherFinancingActivities :: Maybe Integer+ , netCashFromDiscontinuedOperationsFinancing :: Maybe Integer+ , netCashFromFinancingActivities :: Maybe Integer+ , netCashBeforeDiscOperationsAndFX :: Maybe Integer+ , changeInCashFromDiscOperationsAndOther :: Maybe Integer+ , netCashBeforeFX :: Maybe Integer+ , effectOfForeignExchangeRates :: Maybe Integer+ , netChangeInCash :: Maybe Integer+ } deriving Show++instance FromJSON BankCashFlowRow where+ parseJSON = withObject "BankCashFlowRow" $ \v -> BankCashFlowRow+ <$> v .: "SimFinId"+ <*> v .: "Ticker"+ <*> v .: "Fiscal Period"+ <*> v .: "Fiscal Year"+ <*> v .: "Report Date"+ <*> v .: "Publish Date"+ <*> v .: "Restated Date"+ <*> v .: "Source"+ <*> v .: "TTM"+ <*> v .: "Value Check"+ <*> v .: "Net Income/Starting Line"+ <*> v .: "Net Income"+ <*> v .: "Net Income from Discontinued Operations"+ <*> v .: "Other Adjustments"+ <*> v .: "Depreciation & Amortization"+ <*> v .: "Provision for Loan Losses"+ <*> v .: "Non-Cash Items"+ <*> v .: "Gain On Sale of Securities & Loans"+ <*> v .: "Deferred Income Taxes"+ <*> v .: "Stock-Based Compensation"+ <*> v .: "Other Non-Cash Adjustments"+ <*> v .: "Change in Working Capital"+ <*> v .: "Trading Assets & Liabilities"+ <*> v .: "Net Change of Investments"+ <*> v .: "Net Change of Interbank Assets"+ <*> v .: "Net Change of Interbank Liabilities"+ <*> v .: "Net Change in Operating Loans"+ <*> v .: "Accrued Interest Receivable"+ <*> v .: "Accrued Interest Payable"+ <*> v .: "Other Operating Assets/Liabilities"+ <*> v .: "Net Cash from Discontinued Operations (Operating)"+ <*> v .: "Net Cash from Operating Activities"+ <*> v .: "Change in Fixed Assets & Intangibles"+ <*> v .: "Disposition of Fixed Assets & Intangibles"+ <*> v .: "Capital Expenditures"+ <*> v .: "Net Change in Investments"+ <*> v .: "Decrease in Investments"+ <*> v .: "Decrease in HTM Investments"+ <*> v .: "Decrease in AFS Investments"+ <*> v .: "Increase in Investments"+ <*> v .: "Increase in HTM Investments"+ <*> v .: "Increase in AFS Investments"+ <*> v .: "Net Change in Other Investments"+ <*> v .: "Net Change in Loans & Interbank"+ <*> v .: "Net Change in Customer Loans"+ <*> v .: "Net Change in Interbank Assets"+ <*> v .: "Net Change in Other Loans"+ <*> v .: "Net Cash from Acquisitions & Divestitures"+ <*> v .: "Net Cash from Divestitures"+ <*> v .: "Cash for Acquisition of Subsidiaries"+ <*> v .: "Cash for Joint Ventures"+ <*> v .: "Net Cash from Other Acquisitions"+ <*> v .: "Other Investing Activities"+ <*> v .: "Net Cash from Discontinued Operations (Investing)"+ <*> v .: "Net Cash from Investing Activities"+ <*> v .: "Dividends Paid"+ <*> v .: "Cash from (Repayment of) Debt"+ <*> v .: "Cash from (Repayment of) Short Term Debt, Net"+ <*> v .: "Net Change in Interbank Transfers"+ <*> v .: "Cash from (Repayment of) Long Term Debt, Net"+ <*> v .: "Repayments of Long Term Debt"+ <*> v .: "Cash from Long Term Debt"+ <*> v .: "Cash from (Repurchase of) Equity"+ <*> v .: "Increase in Capital Stock"+ <*> v .: "Decrease in Capital Stock"+ <*> v .: "Net Change In Deposits"+ <*> v .: "Other Financing Activities"+ <*> v .: "Net Cash from Discontinued Operations (Financing)"+ <*> v .: "Net Cash from Financing Activities"+ <*> v .: "Net Cash Before Disc. Operations and FX"+ <*> v .: "Change in Cash from Disc. Operations and Other"+ <*> v .: "Net Cash Before FX"+ <*> v .: "Effect of Foreign Exchange Rates"+ <*> v .: "Net Change in Cash"++-- | Wrapper to parse a BankCashFlowRow record from SimFin's JSON format.++newtype BankCashFlowsKeyed = BankCashFlowsKeyed { unKeyBankCashFlows :: [BankCashFlowRow] }++instance FromJSON BankCashFlowsKeyed where+ parseJSON o = BankCashFlowsKeyed <$> (traverse parseJSON =<< createKeyedRows o)+++------+-- Insurance+------++-- | Cash flow statement for Insurance companies. ++data InsuranceCashFlowRow+ = InsuranceCashFlowRow+ { simFinId :: Int+ , ticker :: Text+ , fiscalPeriod :: String+ , fiscalYear :: Int+ , reportDate :: Day+ , publishDate :: Day+ , restatedDate :: Day+ , source :: Text+ , tTM :: Bool+ , valueCheck :: Bool+ , netIncomeStartingLine :: Maybe Integer+ , netIncome :: Maybe Integer+ , netIncomeFromDiscontinuedOperations :: Maybe Integer+ , otherAdjustments :: Maybe Integer+ , depreciationAndAmortization :: Maybe Integer+ , nonCashItems :: Maybe Integer+ , stockBasedCompensation :: Maybe Integer+ , deferredIncomeTaxes :: Maybe Integer+ , otherNonCashAdjustments :: Maybe Integer+ , changeInWorkingCapital :: Maybe Integer+ , netCashFromDiscontinuedOperationsOperating :: Maybe Integer+ , netCashFromOperatingActivities :: Maybe Integer+ , changeInFixedAssetsAndIntangibles :: Maybe Integer+ , dispositionOfFixedAssetsAndIntangibles :: Maybe Integer+ , acquisitionOfFixedAssetsAndIntangibles :: Maybe Integer+ , netChangeInInvestments :: Maybe Integer+ , increaseInInvestments :: Maybe Integer+ , decreaseInInvestments :: Maybe Integer+ , otherInvestingActivities :: Maybe Integer+ , netCashFromDiscontinuedOperationsInvesting :: Maybe Integer+ , netCashFromInvestingActivities :: Maybe Integer+ , dividendsPaid :: Maybe Integer+ , cashFromRepaymentOfDebt :: Maybe Integer+ , cashFromRepaymentOfShortTermDebtNet :: Maybe Integer+ , cashFromRepaymentOfLongTermDebtNet :: Maybe Integer+ , repaymentsOfLongTermDebt :: Maybe Integer+ , cashFromLongTermDebt :: Maybe Integer+ , cashFromRepurchaseOfEquity :: Maybe Integer+ , increaseInCapitalStock :: Maybe Integer+ , decreaseInCapitalStock :: Maybe Integer+ , changeInInsuranceReserves :: Maybe Integer+ , otherFinancingActivities :: Maybe Integer+ , netCashFromDiscontinuedOperationsFinancing :: Maybe Integer+ , netCashFromFinancingActivities :: Maybe Integer+ , netCashBeforeDiscOperationsAndFX :: Maybe Integer+ , changeInCashFromDiscOperationsAndOther :: Maybe Integer+ , netCashBeforeFX :: Maybe Integer+ , effectOfForeignExchangeRates :: Maybe Integer+ , netChangeInCash :: Maybe Integer+ } deriving Show++instance FromJSON InsuranceCashFlowRow where+ parseJSON = withObject "InsuranceCashFlowRow" $ \v -> InsuranceCashFlowRow+ <$> v .: "SimFinId"+ <*> v .: "Ticker"+ <*> v .: "Fiscal Period"+ <*> v .: "Fiscal Year"+ <*> v .: "Report Date"+ <*> v .: "Publish Date"+ <*> v .: "Restated Date"+ <*> v .: "Source"+ <*> v .: "TTM"+ <*> v .: "Value Check"+ <*> v .: "Net Income/Starting Line"+ <*> v .: "Net Income"+ <*> v .: "Net Income from Discontinued Operations"+ <*> v .: "Other Adjustments"+ <*> v .: "Depreciation & Amortization"+ <*> v .: "Non-Cash Items"+ <*> v .: "Stock-Based Compensation"+ <*> v .: "Deferred Income Taxes"+ <*> v .: "Other Non-Cash Adjustments"+ <*> v .: "Change in Working Capital"+ <*> v .: "Net Cash from Discontinued Operations (Operating)"+ <*> v .: "Net Cash from Operating Activities"+ <*> v .: "Change in Fixed Assets & Intangibles"+ <*> v .: "Disposition of Fixed Assets & Intangibles"+ <*> v .: "Acquisition of Fixed Assets & Intangibles"+ <*> v .: "Net Change in Investments"+ <*> v .: "Increase in Investments"+ <*> v .: "Decrease in Investments"+ <*> v .: "Other Investing Activities"+ <*> v .: "Net Cash from Discontinued Operations (Investing)"+ <*> v .: "Net Cash from Investing Activities"+ <*> v .: "Dividends Paid"+ <*> v .: "Cash from (Repayment of) Debt"+ <*> v .: "Cash from (Repayment of) Short Term Debt, Net"+ <*> v .: "Cash from (Repayment of) Long Term Debt, Net"+ <*> v .: "Repayments of Long Term Debt"+ <*> v .: "Cash from Long Term Debt"+ <*> v .: "Cash from (Repurchase of) Equity"+ <*> v .: "Increase in Capital Stock"+ <*> v .: "Decrease in Capital Stock"+ <*> v .: "Change in Insurance Reserves"+ <*> v .: "Other Financing Activities"+ <*> v .: "Net Cash from Discontinued Operations (Financing)"+ <*> v .: "Net Cash from Financing Activities"+ <*> v .: "Net Cash Before Disc. Operations and FX"+ <*> v .: "Change in Cash from Disc. Operations and Other"+ <*> v .: "Net Cash Before FX"+ <*> v .: "Effect of Foreign Exchange Rates"+ <*> v .: "Net Change in Cash"++-- | Wrapper to parse an InsuranceCashFlowRow record from SimFin's JSON format.++newtype InsuranceCashFlowsKeyed = InsuranceCashFlowsKeyed { unKeyInsuranceCashFlows :: [InsuranceCashFlowRow] }++instance FromJSON InsuranceCashFlowsKeyed where+ parseJSON o = InsuranceCashFlowsKeyed <$> (traverse parseJSON =<< createKeyedRows o)+++------+-- Industry+------++type IndustryCashFlowsKeyed+ = Industry GeneralCashFlowsKeyed BankCashFlowsKeyed InsuranceCashFlowsKeyed++-- | Discrimination of cash flow lists.++type IndustryCashFlows+ = Industry [GeneralCashFlowRow] [BankCashFlowRow] [InsuranceCashFlowRow]++-- | Discrimination of cash flows.++type IndustryCashFlow+ = Industry GeneralCashFlowRow BankCashFlowRow InsuranceCashFlowRow++instance FromJSON IndustryCashFlowsKeyed where+ parseJSON root = Insurance <$> parseJSON root+ <|> Bank <$> parseJSON root+ <|> General <$> parseJSON root++unKeyIndustryCashFlows :: IndustryCashFlowsKeyed -> IndustryCashFlows+unKeyIndustryCashFlows = mapIndustry+ unKeyGeneralCashFlows+ unKeyBankCashFlows+ unKeyInsuranceCashFlows++instance FromJSON IndustryCashFlows where+ parseJSON root = unKeyIndustryCashFlows <$> parseJSON root
+ src/SimFin/Types/CompanyInfo.hs view
@@ -0,0 +1,48 @@+{-|+Module : SimFin.Types.CompanyInfo+Description : General information about a company.+Copyright : (c) Owen Shepherd, 2022+License : MIT+Maintainer : owen@owen.cafe+-}++{-# LANGUAGE DuplicateRecordFields #-}+{-# LANGUAGE OverloadedStrings #-}++module SimFin.Types.CompanyInfo+ ( CompanyInfoRow(..)+ ) where++import Control.Monad ((>=>))+import Data.Aeson+import Data.Aeson.Types (Parser)+import Data.Text (Text)++import SimFin.Internal++-- | Genreal information about a company.+-- See the [SimFin docs](https://simfin.com/api/v2/documentation/#tag/Company/paths/~1companies~1general/get).++data CompanyInfoRow+ = CompanyInfoRow+ { simFinId :: Int+ , ticker :: Text+ , companyName :: Text+ , industryId :: Int+ , monthFYEnd :: Int+ , numberEmployees :: Int+ , businessSummary :: Text+ } deriving Show++instance FromJSON CompanyInfoRow where+ parseJSON = createKeyedRow >=> withObject "CompanyInfoRow" f+ where+ f :: Object -> Parser CompanyInfoRow+ f = \v -> CompanyInfoRow+ <$> v .: "SimFinId"+ <*> v .: "Ticker"+ <*> v .: "Company Name"+ <*> v .: "IndustryId"+ <*> v .: "Month FY End"+ <*> v .: "Number Employees"+ <*> v .: "Business Summary"
+ src/SimFin/Types/CompanyListing.hs view
@@ -0,0 +1,41 @@+{-|+Module : SimFin.Types.CompanyListing+Description : Item of a list of company tickers and their SimFin IDs.+Copyright : (c) Owen Shepherd, 2022+License : MIT+Maintainer : owen@owen.cafe+-}++{-# LANGUAGE DuplicateRecordFields #-}+{-# LANGUAGE OverloadedStrings #-}++module SimFin.Types.CompanyListing+ ( CompanyListingRow(..)+ , CompanyListingKeyed(..)+ ) where++import Data.Aeson+import Data.Text (Text)++import SimFin.Internal++-- | SimFin ID and company ticker. See the [SimFin docs](https://simfin.com/api/v2/documentation/#tag/Company/paths/~1companies~1list/get).++data CompanyListingRow+ = CompanyListingRow+ { simFinId :: Int+ , ticker :: Text+ } deriving Show++-- | Wrapper to parse a CompanyListing record from SimFin's JSON format.+-- You probably don't want to use this.++newtype CompanyListingKeyed = CompanyListingKeyed { unKeyCompanyListing :: [CompanyListingRow] }++instance FromJSON CompanyListingRow where+ parseJSON = withObject "CompanyListing" $ \v -> CompanyListingRow+ <$> v .: "SimFinId"+ <*> v .: "Ticker"++instance FromJSON CompanyListingKeyed where+ parseJSON o = CompanyListingKeyed <$> (traverse parseJSON =<< createKeyedRows o)
+ src/SimFin/Types/Derived.hs view
@@ -0,0 +1,108 @@+{-|+Module : SimFin.Types.Derived+Description : Types to represent SimFin derived figures.+Copyright : (c) Owen Shepherd, 2022+License : MIT+Maintainer : owen@owen.cafe+-}++{-# LANGUAGE DeriveFunctor #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE FlexibleInstances #-}++module SimFin.Types.Derived+ ( DerivedRow(..)+ , DerivedRowsKeyed(..)+ ) where++import Data.Aeson+import Data.Text (Text)+import Data.Time.Calendar (Day)++import SimFin.Types.StringFrac+import SimFin.Types.FiscalPeriod+import SimFin.Internal++-- | Cash flow statement for general companies. ++data DerivedRow a+ = DerivedRow+ { simFinId :: Int+ , ticker :: Text+ , fiscalPeriod :: FiscalPeriod+ , fiscalYear :: Int+ , reportDate :: Day+ , publishDate :: Day+ , restatedDate :: Day+ , source :: Text+ , tTM :: Bool+ , valueCheck :: Bool+ , eBITDA :: Maybe a+ , totalDebt :: Maybe a+ , freeCashFlow :: Maybe a+ , grossProfitMargin :: Maybe a+ , operatingMargin :: Maybe a+ , netProfitMargin :: Maybe a+ , returnOnEquity :: Maybe a+ , returnOnAssets :: Maybe a+ , freeCashFlowToNetIncome :: Maybe a+ , currentRatio :: Maybe a+ , liabilitiesToEquityRatio :: Maybe a+ , debtRatio :: Maybe a+ , earningsPerShareBasic :: Maybe a+ , earningsPerShareDiluted :: Maybe a+ , salesPerShare :: Maybe a+ , equityPerShare :: Maybe a+ , freeCashFlowPerShare :: Maybe a+ , dividendsPerShare :: Maybe a+ , piotroskiFScore :: Maybe Int+ , returnOnInvestedCapital :: Maybe a+ , cashReturnOnInvestedCapital :: Maybe a+ , dividendPayoutRatio :: Maybe a+ , netDebtEBITDA :: Maybe a+ , netDebtEBIT :: Maybe a+ } deriving (Functor, Show)++instance (Read a, RealFrac a) => FromJSON (DerivedRow a) where+ parseJSON = withObject "DerivedRow" $ \v -> fmap (fmap unStringFrac) $ DerivedRow+ <$> v .: "SimFinId"+ <*> v .: "Ticker"+ <*> v .: "Fiscal Period"+ <*> v .: "Fiscal Year"+ <*> v .: "Report Date"+ <*> v .: "Publish Date"+ <*> v .: "Restated Date"+ <*> v .: "Source"+ <*> v .: "TTM"+ <*> v .: "Value Check"+ <*> v .: "EBITDA"+ <*> v .: "Total Debt"+ <*> v .: "Free Cash Flow"+ <*> v .: "Gross Profit Margin"+ <*> v .: "Operating Margin"+ <*> v .: "Net Profit Margin"+ <*> v .: "Return on Equity"+ <*> v .: "Return on Assets"+ <*> v .: "Free Cash Flow to Net Income"+ <*> v .: "Current Ratio"+ <*> v .: "Liabilities to Equity Ratio"+ <*> v .: "Debt Ratio"+ <*> v .: "Earnings Per Share, Basic"+ <*> v .: "Earnings Per Share, Diluted"+ <*> v .: "Sales Per Share"+ <*> v .: "Equity Per Share"+ <*> v .: "Free Cash Flow Per Share"+ <*> v .: "Dividends Per Share"+ <*> v .: "Piotroski F-Score"+ <*> v .: "Return On Invested Capital"+ <*> v .: "Cash Return On Invested Capital"+ <*> v .: "Dividend Payout Ratio"+ <*> v .: "Net Debt / EBITDA"+ <*> v .: "Net Debt / EBIT"++-- | Wrapper to parse a DerivedRow record from SimFin's JSON format.++newtype DerivedRowsKeyed a = DerivedRowsKeyed { unDerivedRows :: [DerivedRow a] }++instance (Read a, RealFrac a) => FromJSON (DerivedRowsKeyed a) where+ parseJSON o = fmap DerivedRowsKeyed $ traverse parseJSON =<< createKeyedRows o
+ src/SimFin/Types/FiscalPeriod.hs view
@@ -0,0 +1,50 @@+{-|+Module : SimFin.Types.FiscalPeriod+Description : Types that describe a fiscal period.+Copyright : (c) Owen Shepherd, 2022+License : MIT+Maintainer : owen@owen.cafe+-}++{-# LANGUAGE OverloadedStrings #-}++module SimFin.Types.FiscalPeriod+ ( FiscalPeriod(..)+ , fiscalPeriodParam+ ) where++import Data.Aeson+import Data.ByteString (ByteString)+import qualified Data.Text as T++-- | A fiscal period as understood by the SimFin API.++data FiscalPeriod = Q1 | Q2 | Q3 | Q4 | H1 | H2 | FullYear | FirstNineMonths | SixMonths+ deriving (Eq, Show)++instance FromJSON FiscalPeriod where+ parseJSON = withText "FiscalPeriod" $ \t -> case T.toLower t of+ "q1" -> pure Q1+ "q2" -> pure Q2+ "q3" -> pure Q3+ "q4" -> pure Q4+ "h1" -> pure H1+ "h2" -> pure H2+ "fy" -> pure FullYear+ "9m" -> pure FirstNineMonths+ "6m" -> pure SixMonths+ _ -> fail "Invalid fiscal year string"++-- | Converts a fiscal period into a query string fragment as understood by the SimFin API.++fiscalPeriodParam :: FiscalPeriod -> ByteString+fiscalPeriodParam a = case a of+ Q1 -> "q1"+ Q2 -> "q2"+ Q3 -> "q3"+ Q4 -> "q4"+ H1 -> "h1"+ H2 -> "h2"+ FullYear-> "fy"+ FirstNineMonths -> "9m"+ SixMonths -> "6m"
+ src/SimFin/Types/Industry.hs view
@@ -0,0 +1,43 @@+{-|+Module : SimFin.Types.Industry+Description : Parameterized sum type to distinguish between different industry specific data.+Copyright : (c) Owen Shepherd, 2022+License : MIT+Maintainer : owen@owen.cafe+-}++module SimFin.Types.Industry+ ( Industry(..)+ , mapIndustry+ , invertIndustries+ ) where++import Data.Foldable++-- | Distinguish between different industry-specific data.++data Industry general bank insurance+ = General general+ | Bank bank+ | Insurance insurance+ deriving Show++-- | Map all discriminations of an Industry.++mapIndustry :: (a -> a') -> (b -> b') -> (c -> c') -> Industry a b c -> Industry a' b' c'+mapIndustry f g h industry = case industry of+ General a -> General $ f a+ Bank a -> Bank $ g a+ Insurance a -> Insurance $ h a++invertIndustry :: Industry [a] [b] [c] -> [Industry a b c]+invertIndustry ind = case ind of+ General as -> General <$> as+ Bank bs -> Bank <$> bs+ Insurance cs -> Insurance <$> cs++-- | List of discriminations of lists to list of discriminations.+-- Used to removing extra nesting from the statements API results.++invertIndustries :: [Industry [a] [b] [c]] -> [Industry a b c]+invertIndustries = fold . fmap invertIndustry
+ src/SimFin/Types/Prices.hs view
@@ -0,0 +1,62 @@+{-|+Module : SimFin.Types.Prices+Description : Types to represent SimFin price results.+Copyright : (c) Owen Shepherd, 2022+License : MIT+Maintainer : owen@owen.cafe+-}++{-# LANGUAGE DeriveFunctor #-}+{-# LANGUAGE DuplicateRecordFields #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE OverloadedStrings #-}++module SimFin.Types.Prices+ ( PricesRow(..)+ , PricesKeyed(..)+ ) where++import Data.Aeson+import Data.Text (Text)+import Data.Time.Calendar (Day)++import SimFin.Types.StringFrac+import SimFin.Internal++-- | Prices of a company over a single day. ++data PricesRow a+ = PricesRow+ { simFinId :: Int+ , ticker :: Text+ , date :: Maybe Day+ , open :: a+ , high :: a+ , low :: a+ , close :: a+ , adjClose :: a+ , volume :: Integer+ , dividend :: Maybe a+ , commonSharesOutstanding :: Maybe Integer+ } deriving (Functor, Show)++instance (Read a, RealFrac a) => FromJSON (PricesRow a) where+ parseJSON = withObject "PricesRow" $ \v -> fmap (fmap unStringFrac) $ PricesRow+ <$> v .: "SimFinId"+ <*> v .: "Ticker"+ <*> v .: "Date"+ <*> v .: "Open"+ <*> v .: "High"+ <*> v .: "Low"+ <*> v .: "Close"+ <*> v .: "Adj. Close"+ <*> v .: "Volume"+ <*> v .: "Dividend"+ <*> v .: "Common Shares Outstanding"++-- | Wrapper to parse a PricesRow record from SimFin's JSON format.++newtype PricesKeyed a = PricesKeyed { unKeyPrices :: [PricesRow a] }++instance (Read a, RealFrac a) => FromJSON (PricesKeyed a) where+ parseJSON o = PricesKeyed <$> (traverse parseJSON =<< createKeyedRows o)
+ src/SimFin/Types/PricesAndRatios.hs view
@@ -0,0 +1,43 @@+{-|+Module : SimFin.Types.Prices+Description : Type to represent a combination of SimFin prices and ratios.+Copyright : (c) Owen Shepherd, 2022+License : MIT+Maintainer : owen@owen.cafe+-}++{-# LANGUAGE DeriveFunctor #-}+{-# LANGUAGE DuplicateRecordFields #-}+{-# LANGUAGE OverloadedStrings #-}++module SimFin.Types.PricesAndRatios+ ( PricesAndRatiosRow(..)+ , PricesAndRatiosKeyed(..)+ ) where++import Data.Aeson++import SimFin.Types.Prices+import SimFin.Types.Ratios+import SimFin.Internal++-- | Represents a company's prices and ratios.++data PricesAndRatiosRow a+ = PricesAndRatiosRow+ { prices :: PricesRow a+ , ratios :: RatiosRow a+ } deriving (Functor, Show)++instance (Read a, RealFrac a) => FromJSON (PricesAndRatiosRow a) where+ parseJSON v = PricesAndRatiosRow+ <$> parseJSON v+ <*> parseJSON v++-- | Wrapper to parse a PricesAndRatiosRow record from SimFin's JSON format.+-- You probably don't want to use this.++newtype PricesAndRatiosKeyed a = PricesAndRatiosKeyed { unKeyPricesAndRatios :: [PricesAndRatiosRow a] }++instance (Read a, RealFrac a) => FromJSON (PricesAndRatiosKeyed a) where+ parseJSON o = PricesAndRatiosKeyed <$> (traverse parseJSON =<< createKeyedRows o)
+ src/SimFin/Types/PricesQuery.hs view
@@ -0,0 +1,72 @@+{-|+Module : SimFin.Types.PricesQuery+Description : Types to represent SimFin price queries.+Copyright : (c) Owen Shepherd, 2022+License : MIT+Maintainer : owen@owen.cafe+-}++{-# LANGUAGE DuplicateRecordFields #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE RecordWildCards #-}++module SimFin.Types.PricesQuery+ ( PricesQuery(..)+ , PricesQueryFree+ , pricesQueryToQueryParams+ , pricesQueryFreeToQueryParams+ ) where++import Data.List.NonEmpty (NonEmpty)+import Data.Maybe (maybeToList)+import Data.Time.Calendar (Day)++import SimFin.Types.StockRef+import SimFin.Internal++-- | This represents all options the prices endpoint supports.+-- Some of these parameters are only available to SimFin+ users.+-- For free users, please use 'PricesQueryFree'.++data PricesQuery+ = PricesQuery+ { stockRefs :: NonEmpty StockRef+ , start :: Maybe Day+ , end :: Maybe Day+ , asReported :: Bool+ } deriving Show++-- | Represents all the parameters available to free users.++type PricesQueryFree = StockRef++-- | Turn a 'PricesQuery' into query parameters for the SimFin "prices" endpoint.++pricesQueryToQueryParams :: PricesQuery -> [QueryParam]+pricesQueryToQueryParams PricesQuery{..} = + let+ refParams = stockRefsToQueryParams stockRefs+ startParam = toShownCommaQueryParam "start "$ maybeToList start+ endParam = toShownCommaQueryParam "end" $ maybeToList end+ asReportedParam = toBoolQueryParam "asreported" asReported+ in+ mconcat+ [ refParams+ , startParam+ , endParam+ , asReportedParam+ ]++freeToPlus :: PricesQueryFree -> PricesQuery+freeToPlus stockRef+ = PricesQuery+ { stockRefs = pure stockRef+ , start = Nothing+ , end = Nothing+ , asReported = False+ }++-- | Turn a 'PricesQueryFree' into query parameters for the SimFin "prices" endpoint.++pricesQueryFreeToQueryParams :: PricesQueryFree -> [QueryParam]+pricesQueryFreeToQueryParams = pricesQueryToQueryParams . freeToPlus
+ src/SimFin/Types/ProfitAndLoss.hs view
@@ -0,0 +1,497 @@+{-|+Module : SimFin.Types.ProfitAndLoss+Description : Types to represent a company's profit and loss statement.+Copyright : (c) Owen Shepherd, 2022+License : MIT+Maintainer : owen@owen.cafe+-}++{-# LANGUAGE DuplicateRecordFields #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE FlexibleInstances #-}++module SimFin.Types.ProfitAndLoss+ ( GeneralProfitAndLossRow(..)+ , BankProfitAndLossRow(..)+ , InsuranceProfitAndLossRow(..)+ , IndustryProfitsAndLosses+ , IndustryProfitAndLoss+ ) where++import Control.Applicative ((<|>))+import Data.Aeson+import Data.Text (Text)+import Data.Time.Calendar (Day)++import SimFin.Types.Industry+import SimFin.Internal+++------+-- General+------++-- | Cash flow statement for general companies. ++data GeneralProfitAndLossRow+ = GeneralProfitAndLossRow+ { simFinId :: Int+ , ticker :: Text+ , fiscalPeriod :: String+ , fiscalYear :: Int+ , reportDate :: Day+ , publishDate :: Day+ , restatedDate :: Day+ , source :: Text+ , tTM :: Bool+ , valueCheck :: Bool+ , revenue :: Maybe Integer+ , salesAndServicesRevenue :: Maybe Integer+ , financingRevenue :: Maybe Integer+ , otherRevenue :: Maybe Integer+ , costOfRevenue :: Maybe Integer+ , costOfGoodsAndServices :: Maybe Integer+ , costOfFinancingRevenue :: Maybe Integer+ , costOfOtherRevenue :: Maybe Integer+ , grossProfit :: Maybe Integer+ , otherOperatingIncome :: Maybe Integer+ , operatingExpenses :: Maybe Integer+ , sellingGeneralAndAdministrative :: Maybe Integer+ , sellingAndMarketing :: Maybe Integer+ , generalAndAdministrative :: Maybe Integer+ , researchAndDevelopment :: Maybe Integer+ , depreciationAndAmortization :: Maybe Integer+ , provisionForDoubtfulAccounts :: Maybe Integer+ , otherOperatingExpenses :: Maybe Integer+ , operatingIncomeLoss :: Maybe Integer+ , nonOperatingIncomeLoss :: Maybe Integer+ , interestExpenseNet :: Maybe Integer+ , interestExpense :: Maybe Integer+ , interestIncome :: Maybe Integer+ , otherInvestmentIncomeLoss :: Maybe Integer+ , foreignExchangeGainLoss :: Maybe Integer+ , incomeLossFromAffiliates :: Maybe Integer+ , otherNonOperatingIncomeLoss :: Maybe Integer+ , pretaxIncomeLossAdj :: Maybe Integer+ , abnormalGainsLosses :: Maybe Integer+ , acquiredInProcessRAndD :: Maybe Integer+ , mergerAndAcquisitionExpense :: Maybe Integer+ , abnormalDerivatives :: Maybe Integer+ , disposalOfAssets :: Maybe Integer+ , earlyExtinguishmentOfDebt :: Maybe Integer+ , assetWriteDown :: Maybe Integer+ , impairmentOfGoodwillAndIntangibles :: Maybe Integer+ , saleOfBusiness :: Maybe Integer+ , legalSettlement :: Maybe Integer+ , restructuringCharges :: Maybe Integer+ , saleOfInvestmentsAndUnrealizedInvestments :: Maybe Integer+ , insuranceSettlement :: Maybe Integer+ , otherAbnormalItems :: Maybe Integer+ , pretaxIncomeLoss :: Maybe Integer+ , incomeTaxExpenseBenefitNet :: Maybe Integer+ , currentIncomeTax :: Maybe Integer+ , deferredIncomeTax :: Maybe Integer+ , taxAllowanceCredit :: Maybe Integer+ , incomeLossFromAffiliatesNetOfTaxes :: Maybe Integer+ , incomeLossFromContinuingOperations :: Maybe Integer+ , netExtraordinaryGainsLosses :: Maybe Integer+ , discontinuedOperations :: Maybe Integer+ , accountingChargesAndOther :: Maybe Integer+ , incomeLossInclMinorityInterest :: Maybe Integer+ , minorityInterest :: Maybe Integer+ , netIncome :: Maybe Integer+ , preferredDividends :: Maybe Integer+ , otherAdjustments :: Maybe Integer+ , netIncomeCommon :: Maybe Integer+ } deriving Show++instance FromJSON GeneralProfitAndLossRow where+ parseJSON = withObject "GeneralProfitAndLossRow" $ \v -> GeneralProfitAndLossRow+ <$> v .: "SimFinId"+ <*> v .: "Ticker"+ <*> v .: "Fiscal Period"+ <*> v .: "Fiscal Year"+ <*> v .: "Report Date"+ <*> v .: "Publish Date"+ <*> v .: "Restated Date"+ <*> v .: "Source"+ <*> v .: "TTM"+ <*> v .: "Value Check"+ <*> v .: "Revenue"+ <*> v .: "Sales & Services Revenue"+ <*> v .: "Financing Revenue"+ <*> v .: "Other Revenue"+ <*> v .: "Cost of Revenue"+ <*> v .: "Cost of Goods & Services"+ <*> v .: "Cost of Financing Revenue"+ <*> v .: "Cost of Other Revenue"+ <*> v .: "Gross Profit"+ <*> v .: "Other Operating Income"+ <*> v .: "Operating Expenses"+ <*> v .: "Selling, General & Administrative"+ <*> v .: "Selling & Marketing"+ <*> v .: "General & Administrative"+ <*> v .: "Research & Development"+ <*> v .: "Depreciation & Amortization"+ <*> v .: "Provision for Doubtful Accounts"+ <*> v .: "Other Operating Expenses"+ <*> v .: "Operating Income (Loss)"+ <*> v .: "Non-Operating Income (Loss)"+ <*> v .: "Interest Expense, Net"+ <*> v .: "Interest Expense"+ <*> v .: "Interest Income"+ <*> v .: "Other Investment Income (Loss)"+ <*> v .: "Foreign Exchange Gain (Loss)"+ <*> v .: "Income (Loss) from Affiliates"+ <*> v .: "Other Non-Operating Income (Loss)"+ <*> v .: "Pretax Income (Loss), Adj."+ <*> v .: "Abnormal Gains (Losses)"+ <*> v .: "Acquired In-Process R&D"+ <*> v .: "Merger & Acquisition Expense"+ <*> v .: "Abnormal Derivatives"+ <*> v .: "Disposal of Assets"+ <*> v .: "Early Extinguishment of Debt"+ <*> v .: "Asset Write-Down"+ <*> v .: "Impairment of Goodwill & Intangibles"+ <*> v .: "Sale of Business"+ <*> v .: "Legal Settlement"+ <*> v .: "Restructuring Charges"+ <*> v .: "Sale of Investments & Unrealized Investments"+ <*> v .: "Insurance Settlement"+ <*> v .: "Other Abnormal Items"+ <*> v .: "Pretax Income (Loss)"+ <*> v .: "Income Tax (Expense) Benefit, Net"+ <*> v .: "Current Income Tax"+ <*> v .: "Deferred Income Tax"+ <*> v .: "Tax Allowance/Credit"+ <*> v .: "Income (Loss) from Affiliates, Net of Taxes"+ <*> v .: "Income (Loss) from Continuing Operations"+ <*> v .: "Net Extraordinary Gains (Losses)"+ <*> v .: "Discontinued Operations"+ <*> v .: "Accounting Charges & Other"+ <*> v .: "Income (Loss) Incl. Minority Interest"+ <*> v .: "Minority Interest"+ <*> v .: "Net Income"+ <*> v .: "Preferred Dividends"+ <*> v .: "Other Adjustments"+ <*> v .: "Net Income (Common)"++-- | Wrapper to parse a GeneralProfitAndLossRow record from SimFin's JSON format.++newtype GeneralProfitsAndLossesKeyed = GeneralProfitsAndLossesKeyed { unKeyGeneralProfitsAndLosses :: [GeneralProfitAndLossRow] }++instance FromJSON GeneralProfitsAndLossesKeyed where+ parseJSON o = GeneralProfitsAndLossesKeyed <$> (traverse parseJSON =<< createKeyedRows o)+++------+-- Bank+------++-- | Cash flow statement for banks. ++data BankProfitAndLossRow+ = BankProfitAndLossRow+ { simFinId :: Int+ , ticker :: Text+ , fiscalPeriod :: String+ , fiscalYear :: Int+ , reportDate :: Day+ , publishDate :: Day+ , restatedDate :: Day+ , source :: Text+ , tTM :: Bool+ , valueCheck :: Bool+ , revenue :: Maybe Integer+ , netInterestIncome :: Maybe Integer+ , totalInterestIncome :: Maybe Integer+ , totalInterestExpense :: Maybe Integer+ , totalNonInterestIncome :: Maybe Integer+ , tradingAccountProfitsLosses :: Maybe Integer+ , investmentIncomeLoss :: Maybe Integer+ , saleOfLoanIncomeLoss :: Maybe Integer+ , commissionsAndFeesEarned :: Maybe Integer+ , netOTTILossesRecognisedInEarnings :: Maybe Integer+ , otherNonInterestIncome :: Maybe Integer+ , provisionForLoanLosses :: Maybe Integer+ , netRevenueAfterProvisions :: Maybe Integer+ , totalNonInterestExpense :: Maybe Integer+ , commissionsAndFeesPaid :: Maybe Integer+ , otherOperatingExpenses :: Maybe Integer+ , operatingIncomeLoss :: Maybe Integer+ , nonOperatingIncomeLoss :: Maybe Integer+ , incomeLossFromAffiliates :: Maybe Integer+ , otherNonOperatingIncomeLoss :: Maybe Integer+ , pretaxIncomeLossAdj :: Maybe Integer+ , abnormalGainsLosses :: Maybe Integer+ , debtValuationAdjustment :: Maybe Integer+ , creditValuationAdjustment :: Maybe Integer+ , mergerAndAcquisitionExpense :: Maybe Integer+ , disposalOfAssets :: Maybe Integer+ , earlyExtinguishmentOfDebt :: Maybe Integer+ , assetWriteDown :: Maybe Integer+ , impairmentOfGoodwillAndIntangibles :: Maybe Integer+ , saleOfBusiness :: Maybe Integer+ , legalSettlement :: Maybe Integer+ , restructuringCharges :: Maybe Integer+ , otherAbnormalItems :: Maybe Integer+ , pretaxIncomeLoss :: Maybe Integer+ , incomeTaxExpenseBenefitNet :: Maybe Integer+ , currentIncomeTax :: Maybe Integer+ , deferredIncomeTax :: Maybe Integer+ , taxAllowanceCredit :: Maybe Integer+ , incomeLossFromAffiliatesNetOfTaxes :: Maybe Integer+ , incomeLossFromContinuingOperations :: Maybe Integer+ , netExtraordinaryGainsLosses :: Maybe Integer+ , discontinuedOperations :: Maybe Integer+ , accountingChargesAndOther :: Maybe Integer+ , incomeLossInclMinorityInterest :: Maybe Integer+ , minorityInterest :: Maybe Integer+ , netIncome :: Maybe Integer+ , preferredDividends :: Maybe Integer+ , otherAdjustments :: Maybe Integer+ , netIncomeCommon :: Maybe Integer+ } deriving Show++instance FromJSON BankProfitAndLossRow where+ parseJSON = withObject "BankProfitAndLossRow" $ \v -> BankProfitAndLossRow+ <$> v .: "SimFinId"+ <*> v .: "Ticker"+ <*> v .: "Fiscal Period"+ <*> v .: "Fiscal Year"+ <*> v .: "Report Date"+ <*> v .: "Publish Date"+ <*> v .: "Restated Date"+ <*> v .: "Source"+ <*> v .: "TTM"+ <*> v .: "Value Check"+ <*> v .: "Revenue"+ <*> v .: "Net Interest Income"+ <*> v .: "Total Interest Income"+ <*> v .: "Total Interest Expense"+ <*> v .: "Total Non-Interest Income"+ <*> v .: "Trading Account Profits/Losses"+ <*> v .: "Investment Income (Loss)"+ <*> v .: "Sale of Loan Income (Loss)"+ <*> v .: "Commissions & Fees Earned"+ <*> v .: "Net OTTI Losses Recognised in Earnings"+ <*> v .: "Other Non-Interest Income"+ <*> v .: "Provision for Loan Losses"+ <*> v .: "Net Revenue after Provisions"+ <*> v .: "Total Non-Interest Expense"+ <*> v .: "Commissions & Fees Paid"+ <*> v .: "Other Operating Expenses"+ <*> v .: "Operating Income (Loss)"+ <*> v .: "Non-Operating Income (Loss)"+ <*> v .: "Income (Loss) from Affiliates"+ <*> v .: "Other Non-Operating Income (Loss)"+ <*> v .: "Pretax Income (Loss), Adj."+ <*> v .: "Abnormal Gains (Losses)"+ <*> v .: "Debt Valuation Adjustment"+ <*> v .: "Credit Valuation Adjustment"+ <*> v .: "Merger & Acquisition Expense"+ <*> v .: "Disposal of Assets"+ <*> v .: "Early Extinguishment of Debt"+ <*> v .: "Asset Write-Down"+ <*> v .: "Impairment of Goodwill & Intangibles"+ <*> v .: "Sale of Business"+ <*> v .: "Legal Settlement"+ <*> v .: "Restructuring Charges"+ <*> v .: "Other Abnormal Items"+ <*> v .: "Pretax Income (Loss)"+ <*> v .: "Income Tax (Expense) Benefit, Net"+ <*> v .: "Current Income Tax"+ <*> v .: "Deferred Income Tax"+ <*> v .: "Tax Allowance/Credit"+ <*> v .: "Income (Loss) from Affiliates, Net of Taxes"+ <*> v .: "Income (Loss) from Continuing Operations"+ <*> v .: "Net Extraordinary Gains (Losses)"+ <*> v .: "Discontinued Operations"+ <*> v .: "Accounting Charges & Other"+ <*> v .: "Income (Loss) Incl. Minority Interest"+ <*> v .: "Minority Interest"+ <*> v .: "Net Income"+ <*> v .: "Preferred Dividends"+ <*> v .: "Other Adjustments"+ <*> v .: "Net Income (Common)"++-- | Wrapper to parse a BankProfitAndLossRow record from SimFin's JSON format.++newtype BankProfitsAndLossesKeyed = BankProfitsAndLossesKeyed { unKeyBankProfitsAndLosses :: [BankProfitAndLossRow] }++instance FromJSON BankProfitsAndLossesKeyed where+ parseJSON o = BankProfitsAndLossesKeyed <$> (traverse parseJSON =<< createKeyedRows o)+++------+-- Insurance+------++-- | Cash flow statement for insurance companies. ++data InsuranceProfitAndLossRow+ = InsuranceProfitAndLossRow+ { simFinId :: Int+ , ticker :: Text+ , fiscalPeriod :: String+ , fiscalYear :: Int+ , reportDate :: Day+ , publishDate :: Day+ , restatedDate :: Day+ , source :: Text+ , tTM :: Bool+ , valueCheck :: Bool+ , revenue :: Maybe Integer+ , netPremiumsEarned :: Maybe Integer+ , investmentIncomeLoss :: Maybe Integer+ , incomeFromRealEstate :: Maybe Integer+ , otherOperatingIncome :: Maybe Integer+ , policyChargesAndFees :: Maybe Integer+ , totalRealizedInvestmentGains :: Maybe Integer+ , totalOTTIRealized :: Maybe Integer+ , otherRealizedInvestmentGains :: Maybe Integer+ , otherIncome :: Maybe Integer+ , totalClaimsAndLosses :: Maybe Integer+ , claimsAndLosses :: Maybe Integer+ , longTermCharges :: Maybe Integer+ , otherClaimsAndLosses :: Maybe Integer+ , underwritingExpenseAndAcquisitionCost :: Maybe Integer+ , otherOperatingExpenses :: Maybe Integer+ , operatingIncomeLoss :: Maybe Integer+ , nonOperatingIncomeLoss :: Maybe Integer+ , incomeLossFromAffiliates :: Maybe Integer+ , interestExpenseNet :: Maybe Integer+ , otherNonOperatingIncomeLoss :: Maybe Integer+ , pretaxIncomeLossAdj :: Maybe Integer+ , abnormalGainsLosses :: Maybe Integer+ , mergerAndAcquisitionExpense :: Maybe Integer+ , abnormalDerivatives :: Maybe Integer+ , disposalOfAssets :: Maybe Integer+ , earlyExtinguishmentOfDebt :: Maybe Integer+ , assetWriteDown :: Maybe Integer+ , impairmentOfGoodwillAndIntangibles :: Maybe Integer+ , saleOfBusiness :: Maybe Integer+ , legalSettlement :: Maybe Integer+ , restructuringCharges :: Maybe Integer+ , netInvestmentLosses :: Maybe Integer+ , foreignExchange :: Maybe Integer+ , otherAbnormalItems :: Maybe Integer+ , pretaxIncomeLoss :: Maybe Integer+ , incomeTaxExpenseBenefitNet :: Maybe Integer+ , currentIncomeTax :: Maybe Integer+ , deferredIncomeTax :: Maybe Integer+ , taxAllowanceCredit :: Maybe Integer+ , incomeLossFromAffiliatesNetOfTaxes :: Maybe Integer+ , incomeLossFromContinuingOperations :: Maybe Integer+ , netExtraordinaryGainsLosses :: Maybe Integer+ , discontinuedOperations :: Maybe Integer+ , accountingChargesAndOther :: Maybe Integer+ , incomeLossInclMinorityInterest :: Maybe Integer+ , minorityInterest :: Maybe Integer+ , netIncome :: Maybe Integer+ , preferredDividends :: Maybe Integer+ , otherAdjustments :: Maybe Integer+ , netIncomeCommon :: Maybe Integer+ } deriving Show++instance FromJSON InsuranceProfitAndLossRow where+ parseJSON = withObject "InsuranceProfitAndLossRow" $ \v -> InsuranceProfitAndLossRow+ <$> v .: "SimFinId"+ <*> v .: "Ticker"+ <*> v .: "Fiscal Period"+ <*> v .: "Fiscal Year"+ <*> v .: "Report Date"+ <*> v .: "Publish Date"+ <*> v .: "Restated Date"+ <*> v .: "Source"+ <*> v .: "TTM"+ <*> v .: "Value Check"+ <*> v .: "Revenue"+ <*> v .: "Net Premiums Earned"+ <*> v .: "Investment Income (Loss)"+ <*> v .: "Income from Real Estate"+ <*> v .: "Other Operating Income"+ <*> v .: "Policy Charges & Fees"+ <*> v .: "Total Realized Investment Gains"+ <*> v .: "Total OTTI Realized"+ <*> v .: "Other Realized Investment Gains"+ <*> v .: "Other Income"+ <*> v .: "Total Claims & Losses"+ <*> v .: "Claims & Losses"+ <*> v .: "Long Term Charges"+ <*> v .: "Other Claims & Losses"+ <*> v .: "Underwriting Expense & Acquisition Cost"+ <*> v .: "Other Operating Expenses"+ <*> v .: "Operating Income (Loss)"+ <*> v .: "Non-Operating Income (Loss)"+ <*> v .: "Income (Loss) from Affiliates"+ <*> v .: "Interest Expense, Net"+ <*> v .: "Other Non-Operating Income (Loss)"+ <*> v .: "Pretax Income (Loss), Adj."+ <*> v .: "Abnormal Gains (Losses)"+ <*> v .: "Merger & Acquisition Expense"+ <*> v .: "Abnormal Derivatives"+ <*> v .: "Disposal of Assets"+ <*> v .: "Early Extinguishment of Debt"+ <*> v .: "Asset Write-Down"+ <*> v .: "Impairment of Goodwill & Intangibles"+ <*> v .: "Sale of Business"+ <*> v .: "Legal Settlement"+ <*> v .: "Restructuring Charges"+ <*> v .: "Net Investment Losses"+ <*> v .: "Foreign Exchange"+ <*> v .: "Other Abnormal Items"+ <*> v .: "Pretax Income (Loss)"+ <*> v .: "Income Tax (Expense) Benefit, Net"+ <*> v .: "Current Income Tax"+ <*> v .: "Deferred Income Tax"+ <*> v .: "Tax Allowance/Credit"+ <*> v .: "Income (Loss) from Affiliates, Net of Taxes"+ <*> v .: "Income (Loss) from Continuing Operations"+ <*> v .: "Net Extraordinary Gains (Losses)"+ <*> v .: "Discontinued Operations"+ <*> v .: "Accounting Charges & Other"+ <*> v .: "Income (Loss) Incl. Minority Interest"+ <*> v .: "Minority Interest"+ <*> v .: "Net Income"+ <*> v .: "Preferred Dividends"+ <*> v .: "Other Adjustments"+ <*> v .: "Net Income (Common)"++-- | Wrapper to parse an InsuranceProfitAndLossRow record from SimFin's JSON format.++newtype InsuranceProfitsAndLossesKeyed = InsuranceProfitsAndLossesKeyed { unKeyInsuranceProfitsAndLosses :: [InsuranceProfitAndLossRow] }++instance FromJSON InsuranceProfitsAndLossesKeyed where+ parseJSON o = InsuranceProfitsAndLossesKeyed <$> (traverse parseJSON =<< createKeyedRows o)++------+-- Industry+------++type IndustryProfitsAndLossesKeyed+ = Industry GeneralProfitsAndLossesKeyed BankProfitsAndLossesKeyed InsuranceProfitsAndLossesKeyed++-- | Discrimination of profit and loss lists.++type IndustryProfitsAndLosses+ = Industry [GeneralProfitAndLossRow] [BankProfitAndLossRow] [InsuranceProfitAndLossRow]++-- | Discrimination of profit and losses.++type IndustryProfitAndLoss+ = Industry GeneralProfitAndLossRow BankProfitAndLossRow InsuranceProfitAndLossRow++instance FromJSON IndustryProfitsAndLossesKeyed where+ parseJSON root = General <$> parseJSON root+ <|> Insurance <$> parseJSON root+ <|> Bank <$> parseJSON root++unKeyIndustryProfitsAndLosses :: IndustryProfitsAndLossesKeyed -> IndustryProfitsAndLosses+unKeyIndustryProfitsAndLosses = mapIndustry+ unKeyGeneralProfitsAndLosses+ unKeyBankProfitsAndLosses+ unKeyInsuranceProfitsAndLosses++instance FromJSON IndustryProfitsAndLosses where+ parseJSON root = unKeyIndustryProfitsAndLosses <$> parseJSON root
+ src/SimFin/Types/Ratios.hs view
@@ -0,0 +1,57 @@+{-|+Module : SimFin.Types.Ratios+Description : Ratios derived from a business' financial statements.+Copyright : (c) Owen Shepherd, 2022+License : MIT+Maintainer : owen@owen.cafe+-}++{-# LANGUAGE DeriveFunctor #-}+{-# LANGUAGE DuplicateRecordFields #-}+{-# LANGUAGE OverloadedStrings #-}++module SimFin.Types.Ratios+ ( RatiosRow(..)+ ) where++import Data.Aeson++import SimFin.Types.StringFrac++-- | Record modelling the extra data returned by calling the share price API endpoint with the "&ratios"+-- query parameter. See the [SimFin docs](https://simfin.com/api/v2/documentation/#tag/Company/paths/~1companies~1prices/get).++data RatiosRow a+ = RatiosRow+ { marketCap :: Integer+ , priceToEarningsRatioQuarterly :: Maybe a+ , priceToEarningsRatioTTM :: Maybe a+ , priceToSalesRatioQuarterly :: Maybe a+ , priceToSalesRatioTTM :: Maybe a+ , priceToBookValueTTM :: Maybe a+ , priceToFreeCashFlowQuarterly :: Maybe a+ , priceToFreeCashFlowTTM :: Maybe a+ , enterpriseValueTTM :: Maybe a+ , eVEBITDATTM :: Maybe a+ , eVSalesTTM :: Maybe a+ , eVFCFTTM :: Maybe a+ , bookToMarketValueTTM :: Maybe a+ , operatingIncomeEVTTM :: Maybe a+ } deriving (Functor, Show)++instance (Read a, RealFrac a) => FromJSON (RatiosRow a) where+ parseJSON = withObject "RatiosRow" $ \v -> fmap (fmap unStringFrac) $ RatiosRow+ <$> v .: "Market-Cap"+ <*> v .: "Price to Earnings Ratio (quarterly)"+ <*> v .: "Price to Earnings Ratio (ttm)"+ <*> v .: "Price to Sales Ratio (quarterly)"+ <*> v .: "Price to Sales Ratio (ttm)"+ <*> v .: "Price to Book Value (ttm)"+ <*> v .: "Price to Free Cash Flow (quarterly)"+ <*> v .: "Price to Free Cash Flow (ttm)"+ <*> v .: "Enterprise Value (ttm)"+ <*> v .: "EV/EBITDA (ttm)"+ <*> v .: "EV/Sales (ttm)"+ <*> v .: "EV/FCF (ttm)"+ <*> v .: "Book to Market Value (ttm)"+ <*> v .: "Operating Income/EV (ttm)"
+ src/SimFin/Types/StatementQuery.hs view
@@ -0,0 +1,100 @@+{-|+Module : SimFin.Types.StatementQuery+Description : Types to represent SimFin statement queries.+Copyright : (c) Owen Shepherd, 2022+License : MIT+Maintainer : owen@owen.cafe+-}++{-# LANGUAGE DuplicateRecordFields #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE RecordWildCards #-}++module SimFin.Types.StatementQuery+ ( StatementQuery(..)+ , StatementQueryFree(..)+ , statementQueryToQueryParams+ , statementQueryFreeToQueryParams+ ) where++import Data.List.NonEmpty (NonEmpty)+import Data.Maybe (maybeToList)+import Data.Time.Calendar (Day)++import SimFin.Types.FiscalPeriod+import SimFin.Types.StockRef+import SimFin.Internal++-- | This represents all options the statement endpoint supports, minus the "statement"+-- parameter itself, which is set by simply calling the right function.+-- Some of these parameters are only available to SimFin+ users.+-- For free users, please use 'StatementQueryFree'.+-- If you provide a zero-length list for any field, the query parameter will be omitted,+-- and the API will try to return all relevant available statements.++data StatementQuery+ = StatementQuery+ { stockRefs :: NonEmpty StockRef+ , periods :: [FiscalPeriod]+ , years :: [Int]+ , start :: Maybe Day+ , end :: Maybe Day+ , ttm :: Bool+ , asReported :: Bool+ -- TODO we don't model the result of this yet+ , shares :: Bool+ } deriving Show++-- | Turn a 'StatementQuery' into query parameters for the SimFin "statements" endpoint.++statementQueryToQueryParams :: StatementQuery -> [QueryParam]+statementQueryToQueryParams StatementQuery{..} =+ let+ refParams = stockRefsToQueryParams stockRefs+ startParam = toShownCommaQueryParam "start "$ maybeToList start+ endParam = toShownCommaQueryParam "end" $ maybeToList end+ periodParam = toCommaQueryParam "period" fiscalPeriodParam periods+ yearParam = toShownCommaQueryParam "fyear" years+ ttmParam = toBoolQueryParam "ttm" ttm+ asReportedParam = toBoolQueryParam "asreported" asReported+ sharesParam = toBoolQueryParam "shares" shares+ in+ mconcat+ [ refParams+ , startParam+ , endParam+ , periodParam+ , yearParam+ , ttmParam+ , asReportedParam+ , sharesParam+ ]++-- | This is a subset of the StatementQuery type, which models the parameters available+-- to non-SimFin+ users.++data StatementQueryFree+ = StatementQueryFree+ { stockRef :: StockRef+ , period :: FiscalPeriod+ , year :: Int+ , ttm :: Bool+ }++freeStatementQueryToPaidStatementQuery :: StatementQueryFree -> StatementQuery+freeStatementQueryToPaidStatementQuery StatementQueryFree{..}+ = StatementQuery+ { stockRefs = pure stockRef+ , periods = pure period+ , years = pure year+ , start = Nothing+ , end = Nothing+ , ttm = ttm+ , asReported = False+ , shares = False+ }++-- | Turn a 'StatementQueryFree' into query parameters for the SimFin "statements" endpoint.++statementQueryFreeToQueryParams :: StatementQueryFree -> [QueryParam]+statementQueryFreeToQueryParams = statementQueryToQueryParams . freeStatementQueryToPaidStatementQuery
+ src/SimFin/Types/StockRef.hs view
@@ -0,0 +1,51 @@+{-|+Module : SimFin.Types.StockRef+Description : Type to represent a reference to a company.+Copyright : (c) Owen Shepherd, 2022+License : MIT+Maintainer : owen@owen.cafe+-}++{-# LANGUAGE OverloadedStrings #-}++module SimFin.Types.StockRef+ ( StockRef(..)+ , stockRefsToQueryParams+ ) where++import Control.Arrow (first, second)+import Data.List (foldl')+import Data.List.NonEmpty (NonEmpty)+import Data.String+import Data.Text (Text)+import qualified Data.Text as T++import SimFin.Internal++-- | A stock ref is a SimSin ID or a ticker.++data StockRef = SimFinId Int | Ticker Text+ deriving Show++instance IsString StockRef where+ fromString = Ticker . T.pack++-- | Collection of discriminations to discrimination of collections.++separateStockRefs :: Foldable t => t StockRef -> ([Int], [Text])+separateStockRefs = foldl' f ([], [])+ where+ f :: ([Int], [Text]) -> StockRef -> ([Int], [Text])+ f acc (SimFinId n) = first (n:) acc+ f acc (Ticker t) = second (t:) acc++-- | Convert one or more stock references into a list of query parameters.++stockRefsToQueryParams :: NonEmpty StockRef -> [QueryParam]+stockRefsToQueryParams refs =+ let+ (ids, tickers) = separateStockRefs refs+ tickerParam = toTextCommaQueryParam "ticker" tickers+ idParam = toShownCommaQueryParam "id" ids+ in tickerParam <> idParam+
+ src/SimFin/Types/StringFrac.hs view
@@ -0,0 +1,31 @@+{-|+Module : SimFin.Types.StringFrac+Description : Wrapper for parsing JSON strings or numbers as a RealFrac.+Copyright : (c) Owen Shepherd, 2022+License : MIT+Maintainer : owen@owen.cafe+-}++module SimFin.Types.StringFrac+ ( StringFrac(..)+ ) where++import Data.Aeson hiding (String)+import qualified Data.Aeson as A+import Data.Aeson.Types (typeMismatch)+import qualified Data.Text as T+import Text.Read (readEither)++-- | Wrapper that parses the string '"1.23"' and the number '1.23' the same.+-- Uses the read instance for the 'String', and 'realToFrac' ('Data.Scientific.Scientific' -> a)+-- for the number.++newtype StringFrac a = StringFrac { unStringFrac :: a }+ deriving Show++instance (Read a, RealFrac a) => FromJSON (StringFrac a) where+ parseJSON (A.String s) = case readEither $ T.unpack s of+ Left err -> fail err+ Right a -> pure $ StringFrac a+ parseJSON (Number n) = pure $ StringFrac $ realToFrac n+ parseJSON v = typeMismatch "Number or String" v
+ src/SimFin/Util.hs view
@@ -0,0 +1,40 @@+{-|+Module : SimFin.Util+Description : Common utilities.+Copyright : (c) Owen Shepherd, 2022+License : MIT+Maintainer : owen@owen.cafe+-}++{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE ScopedTypeVariables #-}++module SimFin.Util+ ( createDefaultContext+ , apiKeyEnvVariable+ ) where++import Control.Monad.IO.Class+import qualified Data.ByteString.UTF8 as BSU+import Network.HTTP.Client.TLS+import System.Environment (lookupEnv)++import SimFin.Internal++-- | The environment variable 'createDefaultContext' will try to get your+-- API key from.++apiKeyEnvVariable :: String+apiKeyEnvVariable = "SIM_FIN_API_KEY"++-- | Try to make a new http-client manager, and parse your api key from +-- 'apiKeyEnvVariable'.++createDefaultContext :: (MonadFail m, MonadIO m) => m SimFinContext+createDefaultContext = do+ manager <- newTlsManager+ apiKeyOpt <- liftIO $ lookupEnv apiKeyEnvVariable+ case apiKeyOpt of+ Nothing -> fail $ "Couldn't find environment variable '" <> apiKeyEnvVariable <> "'"+ Just apiKey -> pure $ SimFinContext (BSU.fromString apiKey) manager+
+ test/Test.hs view
@@ -0,0 +1,125 @@+{-# LANGUAGE DuplicateRecordFields #-}+{-# LANGUAGE OverloadedStrings #-}++module Main+ ( main+ ) where++import Control.Monad.IO.Class+import Data.Aeson+import Data.Text (Text)+import Test.Tasty+import Test.Tasty.HUnit++import SimFin.Free++main :: IO ()+main = do+ ctx <- createDefaultContext+ defaultMain (tests ctx)++testStatementQuery :: Text -> StatementQueryFree+testStatementQuery ref = StatementQueryFree+ { stockRef = Ticker ref+ , period = FullYear+ , year = 2020+ , ttm = False+ }++type UnitIndustry = Industry () () ()++general :: UnitIndustry+general = General ()++bank :: UnitIndustry+bank = Bank ()++insurance :: UnitIndustry+insurance = Insurance ()++industryMatches :: Industry a b c -> Industry d e f -> Bool+industryMatches a b = case (a, b) of+ (General _, General _) -> True+ (Bank _, Bank _) -> True+ (Insurance _, Insurance _) -> True+ _ -> False++testFetchMaybe :: IO (ApiResult (Maybe a)) -> IO ()+testFetchMaybe f = do+ res <- f+ flip withRight res $ withMaybe $ const $ pure ()++testFetchIndustry :: UnitIndustry -> IO (ApiResult (Maybe (Industry a b c))) -> IO ()+testFetchIndustry ind f = do+ res <- f+ flip withRight res $ withMaybe $ assertBool "Industry Matches" . industryMatches ind++failWithEmpty :: IO ()+failWithEmpty = assertFailure "Retrieved Nothing successfully"++testFetchList :: IO (ApiResult [a]) -> IO ()+testFetchList f = do+ res <- f+ withRight ensureNE res++withRight :: Show c => (a -> IO b) -> Either c a -> IO b+withRight _ (Left a) = assertFailure $ "Retrieved Left: " <> show a+withRight f (Right a) = f a++withMaybe :: (a -> IO ()) -> Maybe a -> IO ()+withMaybe _ Nothing = failWithEmpty+withMaybe f (Just a) = f a++ensureNE :: [a] -> IO ()+ensureNE [] = failWithEmpty+ensureNE _ = pure ()++tests :: SimFinContext -> TestTree+tests ctx = testGroup "SimFin"+ [ testCase "List companies" $ testFetchList $ fetchCompanyList ctx+ , testCase "Company Info" $ testFetchMaybe $ fetchCompanyInfo ctx "AAPL"+ , testGroup "Balance Sheet"+ [ testCase "General" $ testBalanceSheet "AAPL" general+ , testCase "Bank" $ testBalanceSheet "C" bank+ , testCase "Insurance" $ testBalanceSheet "CB" insurance+ ]+ , testGroup "Profit & Loss"+ [ testCase "General" $ testProfitAndLoss "AAPL" general+ , testCase "Bank" $ testProfitAndLoss "C" bank+ , testCase "Insurance" $ testProfitAndLoss "CB" insurance+ ]+ , testGroup "Cash Flow"+ [ testCase "General" $ testCashFlow "AAPL" general+ , testCase "Bank" $ testCashFlow "C" bank+ , testCase "Insurance" $ testCashFlow "CB" insurance+ ]+ -- This endpoint isn't different by industry types+ , testGroup "Derived"+ [ testCase "General" $ testDerived "AAPL"+ , testCase "Bank" $ testDerived "C"+ , testCase "Insurance" $ testDerived "CB"+ ]+ , testGroup "Price"+ [ testCase "General" $ testFetchPrices "AAPL"+ , testCase "Bank" $ testFetchPrices "C"+ , testCase "Insurance" $ testFetchPrices "CB"+ ]+ ]+ where+ testStmt :: (SimFinContext -> StatementQueryFree -> IO a) -> Text -> IO a+ testStmt f ticker = f ctx $ testStatementQuery ticker++ testBalanceSheet :: Text -> UnitIndustry -> Assertion+ testBalanceSheet ticker industry = testFetchIndustry industry $ testStmt fetchBalanceSheet ticker++ testProfitAndLoss :: Text -> UnitIndustry -> Assertion+ testProfitAndLoss ticker industry = testFetchIndustry industry $ testStmt fetchProfitAndLoss ticker++ testCashFlow :: Text -> UnitIndustry -> Assertion+ testCashFlow ticker industry = testFetchIndustry industry $ testStmt fetchCashFlow ticker++ testDerived :: Text -> Assertion+ testDerived ticker = testFetchMaybe $ testStmt fetchDerived ticker++ testFetchPrices :: StockRef -> IO ()+ testFetchPrices ticker = testFetchList $ fetchPrices ctx ticker