packages feed

binance-exports-0.1.0.0: src/Console/Binance/Exports/Main.hs

{-# LANGUAGE DeriveDataTypeable #-}
{-# LANGUAGE RecordWildCards #-}
{- | CLI application harness.
-}
module Console.Binance.Exports.Main
    ( run
    , getArgs
    , Args(..)
    ) where

import           Control.Monad.IO.Class         ( liftIO )
import           Data.List                      ( sortOn )
import           Data.Maybe                     ( fromMaybe )
import           Data.Ord                       ( Down(..) )
import           Data.Time                      ( UTCTime(..)
                                                , toGregorian
                                                )
import           Data.Time.Clock.POSIX          ( posixSecondsToUTCTime )
import           Data.Version                   ( showVersion )
import           System.Console.CmdArgs         ( (&=)
                                                , Data
                                                , Typeable
                                                , args
                                                , cmdArgs
                                                , def
                                                , explicit
                                                , help
                                                , helpArg
                                                , name
                                                , program
                                                , summary
                                                , typ
                                                )
import           System.Exit                    ( exitFailure )
import           System.IO                      ( stderr )

import           Console.Binance.Exports.Csv
import           Paths_binance_exports          ( version )
import           Web.Binance

import qualified Data.ByteString.Lazy.Char8    as LBS
import qualified Data.Text                     as T
import qualified Data.Text.IO                  as T


-- | Generate & print a trade export based on the executable arguments.
run :: Args -> IO ()
run Args {..} = do
    results <- runApi cfg $ do
        symbolDetails <-
            fmap eiSymbols
            $   getExchangeInfo (map T.pack symbols)
            >>= handleBinanceError
        rawExportData <- concat <$> mapM getTradesForSymbol symbolDetails
        return . filterYear $ sortOn (Down . tTime . tedTrade) rawExportData
    -- Write CSV to file or stdout
    let outputFileOrStdout = fromMaybe "-" outputFile
    let output             = buildTradeExport results
    if outputFileOrStdout == "-"
        then LBS.putStr output
        else LBS.writeFile outputFileOrStdout output
  where
    -- | Build a config for the Binance API requests from the CLI
    -- arguments.
    cfg :: BinanceConfig
    cfg = BinanceConfig { bcApiKey    = T.pack apiKey
                        , bcApiSecret = T.pack apiSecret
                        }
    -- | If an error is present, print the code & message to stderr, then
    -- exit with an error status code.
    handleBinanceError :: Either BinanceError a -> BinanceApiM a
    handleBinanceError = \case
        Left e -> liftIO $ do
            T.hPutStrLn stderr
                $  "[ERROR] Binance API Error Code "
                <> T.pack (show $ beCode e)
                <> ": "
                <> beMsg e
            exitFailure
        Right r -> return r
    -- | Get all trades for the given symbol & convert them into the export
    -- format.
    getTradesForSymbol :: SymbolDetails -> BinanceApiM [TradeExportData]
    getTradesForSymbol s =
        map (TradeExportData s) <$> getTradeHistory (sdSymbol s) Nothing Nothing
    -- | Filter the trades if a 'year' argument has been passed.
    filterYear :: [TradeExportData] -> [TradeExportData]
    filterYear = case year of
        Nothing -> id
        Just y ->
            filter
                $ (\(y_, _, _) -> y == y_)
                . toGregorian
                . utctDay
                . posixSecondsToUTCTime
                . tTime
                . tedTrade


-- CLI ARGS

-- | CLI arguments supported by the executable.
data Args = Args
    { apiKey     :: String
    , apiSecret  :: String
    , symbols    :: [String]
    , year       :: Maybe Integer
    , outputFile :: Maybe FilePath
    }
    deriving (Show, Read, Eq, Data, Typeable)


-- | Parse the CLI arguments with 'System.Console.CmdArgs'.
getArgs :: IO Args
getArgs = cmdArgs argSpec


-- | Defines & documents the CLI arguments.
argSpec :: Args
argSpec =
    Args
            { apiKey     = def
                           &= explicit
                           &= name "k"
                           &= name "api-key"
                           &= help "Binance API Key"
                           &= typ "KEY"
            , apiSecret  = def
                           &= explicit
                           &= name "s"
                           &= name "api-secret"
                           &= help "Binance API Secret"
                           &= typ "SECRET"
            , year       = Nothing
                           &= explicit
                           &= name "y"
                           &= name "year"
                           &= help "Limit output to year"
                           &= typ "YYYY"
            , outputFile =
                Nothing
                &= explicit
                &= name "o"
                &= name "output-file"
                &= help "File to write the export to. Default: stdout"
                &= typ "FILE"
            , symbols    = def &= args &= typ "SYMBOL [SYMBOL ...]"
            }
        &= summary
               (  "binance-exports v"
               <> showVersion version
               <> ", Pavan Rikhi 2022"
               )
        &= program "binance-exports"
        &= helpArg [name "h"]
        &= help "Export Binance Trade History to a CSV"