{-# 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 -> IO ()
run Args {String
[String]
Maybe Integer
Maybe String
outputFile :: Args -> Maybe String
year :: Args -> Maybe Integer
symbols :: Args -> [String]
apiSecret :: Args -> String
apiKey :: Args -> String
outputFile :: Maybe String
year :: Maybe Integer
symbols :: [String]
apiSecret :: String
apiKey :: String
..} = do
    [TradeExportData]
results <- BinanceConfig
-> BinanceApiM [TradeExportData] -> IO [TradeExportData]
forall a. BinanceConfig -> BinanceApiM a -> IO a
runApi BinanceConfig
cfg (BinanceApiM [TradeExportData] -> IO [TradeExportData])
-> BinanceApiM [TradeExportData] -> IO [TradeExportData]
forall a b. (a -> b) -> a -> b
$ do
        [SymbolDetails]
symbolDetails <-
            (ExchangeInfo -> [SymbolDetails])
-> BinanceApiM ExchangeInfo -> BinanceApiM [SymbolDetails]
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap ExchangeInfo -> [SymbolDetails]
eiSymbols
            (BinanceApiM ExchangeInfo -> BinanceApiM [SymbolDetails])
-> BinanceApiM ExchangeInfo -> BinanceApiM [SymbolDetails]
forall a b. (a -> b) -> a -> b
$   [Text] -> BinanceApiM (Either BinanceError ExchangeInfo)
forall (m :: * -> *).
(MonadHttp m, MonadCatch m) =>
[Text] -> m (Either BinanceError ExchangeInfo)
getExchangeInfo ((String -> Text) -> [String] -> [Text]
forall a b. (a -> b) -> [a] -> [b]
map String -> Text
T.pack [String]
symbols)
            BinanceApiM (Either BinanceError ExchangeInfo)
-> (Either BinanceError ExchangeInfo -> BinanceApiM ExchangeInfo)
-> BinanceApiM ExchangeInfo
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= Either BinanceError ExchangeInfo -> BinanceApiM ExchangeInfo
forall a. Either BinanceError a -> BinanceApiM a
handleBinanceError
        [TradeExportData]
rawExportData <- [[TradeExportData]] -> [TradeExportData]
forall (t :: * -> *) a. Foldable t => t [a] -> [a]
concat ([[TradeExportData]] -> [TradeExportData])
-> BinanceApiM [[TradeExportData]] -> BinanceApiM [TradeExportData]
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> (SymbolDetails -> BinanceApiM [TradeExportData])
-> [SymbolDetails] -> BinanceApiM [[TradeExportData]]
forall (t :: * -> *) (m :: * -> *) a b.
(Traversable t, Monad m) =>
(a -> m b) -> t a -> m (t b)
mapM SymbolDetails -> BinanceApiM [TradeExportData]
getTradesForSymbol [SymbolDetails]
symbolDetails
        [TradeExportData] -> BinanceApiM [TradeExportData]
forall (m :: * -> *) a. Monad m => a -> m a
return ([TradeExportData] -> BinanceApiM [TradeExportData])
-> ([TradeExportData] -> [TradeExportData])
-> [TradeExportData]
-> BinanceApiM [TradeExportData]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. [TradeExportData] -> [TradeExportData]
filterYear ([TradeExportData] -> BinanceApiM [TradeExportData])
-> [TradeExportData] -> BinanceApiM [TradeExportData]
forall a b. (a -> b) -> a -> b
$ (TradeExportData -> Down POSIXTime)
-> [TradeExportData] -> [TradeExportData]
forall b a. Ord b => (a -> b) -> [a] -> [a]
sortOn (POSIXTime -> Down POSIXTime
forall a. a -> Down a
Down (POSIXTime -> Down POSIXTime)
-> (TradeExportData -> POSIXTime)
-> TradeExportData
-> Down POSIXTime
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Trade -> POSIXTime
tTime (Trade -> POSIXTime)
-> (TradeExportData -> Trade) -> TradeExportData -> POSIXTime
forall b c a. (b -> c) -> (a -> b) -> a -> c
. TradeExportData -> Trade
tedTrade) [TradeExportData]
rawExportData
    -- Write CSV to file or stdout
    let outputFileOrStdout :: String
outputFileOrStdout = String -> Maybe String -> String
forall a. a -> Maybe a -> a
fromMaybe String
"-" Maybe String
outputFile
    let output :: ByteString
output             = [TradeExportData] -> ByteString
buildTradeExport [TradeExportData]
results
    if String
outputFileOrStdout String -> String -> Bool
forall a. Eq a => a -> a -> Bool
== String
"-"
        then ByteString -> IO ()
LBS.putStr ByteString
output
        else String -> ByteString -> IO ()
LBS.writeFile String
outputFileOrStdout ByteString
output
  where
    -- | Build a config for the Binance API requests from the CLI
    -- arguments.
    cfg :: BinanceConfig
    cfg :: BinanceConfig
cfg = BinanceConfig :: Text -> Text -> BinanceConfig
BinanceConfig { bcApiKey :: Text
bcApiKey    = String -> Text
T.pack String
apiKey
                        , bcApiSecret :: Text
bcApiSecret = String -> Text
T.pack String
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 :: Either BinanceError a -> BinanceApiM a
handleBinanceError = \case
        Left BinanceError
e -> IO a -> BinanceApiM a
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO (IO a -> BinanceApiM a) -> IO a -> BinanceApiM a
forall a b. (a -> b) -> a -> b
$ do
            Handle -> Text -> IO ()
T.hPutStrLn Handle
stderr
                (Text -> IO ()) -> Text -> IO ()
forall a b. (a -> b) -> a -> b
$  Text
"[ERROR] Binance API Error Code "
                Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> String -> Text
T.pack (Int -> String
forall a. Show a => a -> String
show (Int -> String) -> Int -> String
forall a b. (a -> b) -> a -> b
$ BinanceError -> Int
beCode BinanceError
e)
                Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
": "
                Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> BinanceError -> Text
beMsg BinanceError
e
            IO a
forall a. IO a
exitFailure
        Right a
r -> a -> BinanceApiM a
forall (m :: * -> *) a. Monad m => a -> m a
return a
r
    -- | Get all trades for the given symbol & convert them into the export
    -- format.
    getTradesForSymbol :: SymbolDetails -> BinanceApiM [TradeExportData]
    getTradesForSymbol :: SymbolDetails -> BinanceApiM [TradeExportData]
getTradesForSymbol SymbolDetails
s =
        (Trade -> TradeExportData) -> [Trade] -> [TradeExportData]
forall a b. (a -> b) -> [a] -> [b]
map (SymbolDetails -> Trade -> TradeExportData
TradeExportData SymbolDetails
s) ([Trade] -> [TradeExportData])
-> BinanceApiM [Trade] -> BinanceApiM [TradeExportData]
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> Text -> Maybe UTCTime -> Maybe UTCTime -> BinanceApiM [Trade]
forall (m :: * -> *).
(MonadHttp m, MonadReader BinanceConfig m) =>
Text -> Maybe UTCTime -> Maybe UTCTime -> m [Trade]
getTradeHistory (SymbolDetails -> Text
sdSymbol SymbolDetails
s) Maybe UTCTime
forall a. Maybe a
Nothing Maybe UTCTime
forall a. Maybe a
Nothing
    -- | Filter the trades if a 'year' argument has been passed.
    filterYear :: [TradeExportData] -> [TradeExportData]
    filterYear :: [TradeExportData] -> [TradeExportData]
filterYear = case Maybe Integer
year of
        Maybe Integer
Nothing -> [TradeExportData] -> [TradeExportData]
forall a. a -> a
id
        Just Integer
y ->
            (TradeExportData -> Bool) -> [TradeExportData] -> [TradeExportData]
forall a. (a -> Bool) -> [a] -> [a]
filter
                ((TradeExportData -> Bool)
 -> [TradeExportData] -> [TradeExportData])
-> (TradeExportData -> Bool)
-> [TradeExportData]
-> [TradeExportData]
forall a b. (a -> b) -> a -> b
$ (\(Integer
y_, Int
_, Int
_) -> Integer
y Integer -> Integer -> Bool
forall a. Eq a => a -> a -> Bool
== Integer
y_)
                ((Integer, Int, Int) -> Bool)
-> (TradeExportData -> (Integer, Int, Int))
-> TradeExportData
-> Bool
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Day -> (Integer, Int, Int)
toGregorian
                (Day -> (Integer, Int, Int))
-> (TradeExportData -> Day)
-> TradeExportData
-> (Integer, Int, Int)
forall b c a. (b -> c) -> (a -> b) -> a -> c
. UTCTime -> Day
utctDay
                (UTCTime -> Day)
-> (TradeExportData -> UTCTime) -> TradeExportData -> Day
forall b c a. (b -> c) -> (a -> b) -> a -> c
. POSIXTime -> UTCTime
posixSecondsToUTCTime
                (POSIXTime -> UTCTime)
-> (TradeExportData -> POSIXTime) -> TradeExportData -> UTCTime
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Trade -> POSIXTime
tTime
                (Trade -> POSIXTime)
-> (TradeExportData -> Trade) -> TradeExportData -> POSIXTime
forall b c a. (b -> c) -> (a -> b) -> a -> c
. TradeExportData -> Trade
tedTrade


-- CLI ARGS

-- | CLI arguments supported by the executable.
data Args = Args
    { Args -> String
apiKey     :: String
    , Args -> String
apiSecret  :: String
    , Args -> [String]
symbols    :: [String]
    , Args -> Maybe Integer
year       :: Maybe Integer
    , Args -> Maybe String
outputFile :: Maybe FilePath
    }
    deriving (Int -> Args -> ShowS
[Args] -> ShowS
Args -> String
(Int -> Args -> ShowS)
-> (Args -> String) -> ([Args] -> ShowS) -> Show Args
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
showList :: [Args] -> ShowS
$cshowList :: [Args] -> ShowS
show :: Args -> String
$cshow :: Args -> String
showsPrec :: Int -> Args -> ShowS
$cshowsPrec :: Int -> Args -> ShowS
Show, ReadPrec [Args]
ReadPrec Args
Int -> ReadS Args
ReadS [Args]
(Int -> ReadS Args)
-> ReadS [Args] -> ReadPrec Args -> ReadPrec [Args] -> Read Args
forall a.
(Int -> ReadS a)
-> ReadS [a] -> ReadPrec a -> ReadPrec [a] -> Read a
readListPrec :: ReadPrec [Args]
$creadListPrec :: ReadPrec [Args]
readPrec :: ReadPrec Args
$creadPrec :: ReadPrec Args
readList :: ReadS [Args]
$creadList :: ReadS [Args]
readsPrec :: Int -> ReadS Args
$creadsPrec :: Int -> ReadS Args
Read, Args -> Args -> Bool
(Args -> Args -> Bool) -> (Args -> Args -> Bool) -> Eq Args
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
/= :: Args -> Args -> Bool
$c/= :: Args -> Args -> Bool
== :: Args -> Args -> Bool
$c== :: Args -> Args -> Bool
Eq, Typeable Args
DataType
Constr
Typeable Args
-> (forall (c :: * -> *).
    (forall d b. Data d => c (d -> b) -> d -> c b)
    -> (forall g. g -> c g) -> Args -> c Args)
-> (forall (c :: * -> *).
    (forall b r. Data b => c (b -> r) -> c r)
    -> (forall r. r -> c r) -> Constr -> c Args)
-> (Args -> Constr)
-> (Args -> DataType)
-> (forall (t :: * -> *) (c :: * -> *).
    Typeable t =>
    (forall d. Data d => c (t d)) -> Maybe (c Args))
-> (forall (t :: * -> * -> *) (c :: * -> *).
    Typeable t =>
    (forall d e. (Data d, Data e) => c (t d e)) -> Maybe (c Args))
-> ((forall b. Data b => b -> b) -> Args -> Args)
-> (forall r r'.
    (r -> r' -> r) -> r -> (forall d. Data d => d -> r') -> Args -> r)
-> (forall r r'.
    (r' -> r -> r) -> r -> (forall d. Data d => d -> r') -> Args -> r)
-> (forall u. (forall d. Data d => d -> u) -> Args -> [u])
-> (forall u. Int -> (forall d. Data d => d -> u) -> Args -> u)
-> (forall (m :: * -> *).
    Monad m =>
    (forall d. Data d => d -> m d) -> Args -> m Args)
-> (forall (m :: * -> *).
    MonadPlus m =>
    (forall d. Data d => d -> m d) -> Args -> m Args)
-> (forall (m :: * -> *).
    MonadPlus m =>
    (forall d. Data d => d -> m d) -> Args -> m Args)
-> Data Args
Args -> DataType
Args -> Constr
(forall b. Data b => b -> b) -> Args -> Args
(forall d b. Data d => c (d -> b) -> d -> c b)
-> (forall g. g -> c g) -> Args -> c Args
(forall b r. Data b => c (b -> r) -> c r)
-> (forall r. r -> c r) -> Constr -> c Args
forall a.
Typeable a
-> (forall (c :: * -> *).
    (forall d b. Data d => c (d -> b) -> d -> c b)
    -> (forall g. g -> c g) -> a -> c a)
-> (forall (c :: * -> *).
    (forall b r. Data b => c (b -> r) -> c r)
    -> (forall r. r -> c r) -> Constr -> c a)
-> (a -> Constr)
-> (a -> DataType)
-> (forall (t :: * -> *) (c :: * -> *).
    Typeable t =>
    (forall d. Data d => c (t d)) -> Maybe (c a))
-> (forall (t :: * -> * -> *) (c :: * -> *).
    Typeable t =>
    (forall d e. (Data d, Data e) => c (t d e)) -> Maybe (c a))
-> ((forall b. Data b => b -> b) -> a -> a)
-> (forall r r'.
    (r -> r' -> r) -> r -> (forall d. Data d => d -> r') -> a -> r)
-> (forall r r'.
    (r' -> r -> r) -> r -> (forall d. Data d => d -> r') -> a -> r)
-> (forall u. (forall d. Data d => d -> u) -> a -> [u])
-> (forall u. Int -> (forall d. Data d => d -> u) -> a -> u)
-> (forall (m :: * -> *).
    Monad m =>
    (forall d. Data d => d -> m d) -> a -> m a)
-> (forall (m :: * -> *).
    MonadPlus m =>
    (forall d. Data d => d -> m d) -> a -> m a)
-> (forall (m :: * -> *).
    MonadPlus m =>
    (forall d. Data d => d -> m d) -> a -> m a)
-> Data a
forall u. Int -> (forall d. Data d => d -> u) -> Args -> u
forall u. (forall d. Data d => d -> u) -> Args -> [u]
forall r r'.
(r -> r' -> r) -> r -> (forall d. Data d => d -> r') -> Args -> r
forall r r'.
(r' -> r -> r) -> r -> (forall d. Data d => d -> r') -> Args -> r
forall (m :: * -> *).
Monad m =>
(forall d. Data d => d -> m d) -> Args -> m Args
forall (m :: * -> *).
MonadPlus m =>
(forall d. Data d => d -> m d) -> Args -> m Args
forall (c :: * -> *).
(forall b r. Data b => c (b -> r) -> c r)
-> (forall r. r -> c r) -> Constr -> c Args
forall (c :: * -> *).
(forall d b. Data d => c (d -> b) -> d -> c b)
-> (forall g. g -> c g) -> Args -> c Args
forall (t :: * -> *) (c :: * -> *).
Typeable t =>
(forall d. Data d => c (t d)) -> Maybe (c Args)
forall (t :: * -> * -> *) (c :: * -> *).
Typeable t =>
(forall d e. (Data d, Data e) => c (t d e)) -> Maybe (c Args)
$cArgs :: Constr
$tArgs :: DataType
gmapMo :: (forall d. Data d => d -> m d) -> Args -> m Args
$cgmapMo :: forall (m :: * -> *).
MonadPlus m =>
(forall d. Data d => d -> m d) -> Args -> m Args
gmapMp :: (forall d. Data d => d -> m d) -> Args -> m Args
$cgmapMp :: forall (m :: * -> *).
MonadPlus m =>
(forall d. Data d => d -> m d) -> Args -> m Args
gmapM :: (forall d. Data d => d -> m d) -> Args -> m Args
$cgmapM :: forall (m :: * -> *).
Monad m =>
(forall d. Data d => d -> m d) -> Args -> m Args
gmapQi :: Int -> (forall d. Data d => d -> u) -> Args -> u
$cgmapQi :: forall u. Int -> (forall d. Data d => d -> u) -> Args -> u
gmapQ :: (forall d. Data d => d -> u) -> Args -> [u]
$cgmapQ :: forall u. (forall d. Data d => d -> u) -> Args -> [u]
gmapQr :: (r' -> r -> r) -> r -> (forall d. Data d => d -> r') -> Args -> r
$cgmapQr :: forall r r'.
(r' -> r -> r) -> r -> (forall d. Data d => d -> r') -> Args -> r
gmapQl :: (r -> r' -> r) -> r -> (forall d. Data d => d -> r') -> Args -> r
$cgmapQl :: forall r r'.
(r -> r' -> r) -> r -> (forall d. Data d => d -> r') -> Args -> r
gmapT :: (forall b. Data b => b -> b) -> Args -> Args
$cgmapT :: (forall b. Data b => b -> b) -> Args -> Args
dataCast2 :: (forall d e. (Data d, Data e) => c (t d e)) -> Maybe (c Args)
$cdataCast2 :: forall (t :: * -> * -> *) (c :: * -> *).
Typeable t =>
(forall d e. (Data d, Data e) => c (t d e)) -> Maybe (c Args)
dataCast1 :: (forall d. Data d => c (t d)) -> Maybe (c Args)
$cdataCast1 :: forall (t :: * -> *) (c :: * -> *).
Typeable t =>
(forall d. Data d => c (t d)) -> Maybe (c Args)
dataTypeOf :: Args -> DataType
$cdataTypeOf :: Args -> DataType
toConstr :: Args -> Constr
$ctoConstr :: Args -> Constr
gunfold :: (forall b r. Data b => c (b -> r) -> c r)
-> (forall r. r -> c r) -> Constr -> c Args
$cgunfold :: forall (c :: * -> *).
(forall b r. Data b => c (b -> r) -> c r)
-> (forall r. r -> c r) -> Constr -> c Args
gfoldl :: (forall d b. Data d => c (d -> b) -> d -> c b)
-> (forall g. g -> c g) -> Args -> c Args
$cgfoldl :: forall (c :: * -> *).
(forall d b. Data d => c (d -> b) -> d -> c b)
-> (forall g. g -> c g) -> Args -> c Args
$cp1Data :: Typeable Args
Data, Typeable)


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


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