packages feed

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 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.
+ examples/EarningsPerShare.hs view
@@ -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