packages feed

binance-exports 0.1.0.0 → 0.1.1.0

raw patch · 5 files changed

+231/−55 lines, 5 filesdep +directorydep +raw-strings-qqdep +xdg-basedirPVP: major bump suggested

API removals or changes: PVP suggests a major version bump

Dependencies added: directory, raw-strings-qq, xdg-basedir, yaml

API changes (from Hackage documentation)

- Console.Binance.Exports.Main: [apiKey] :: Args -> String
- Console.Binance.Exports.Main: [apiSecret] :: Args -> String
- Console.Binance.Exports.Main: [outputFile] :: Args -> Maybe FilePath
- Console.Binance.Exports.Main: [symbols] :: Args -> [String]
- Console.Binance.Exports.Main: [year] :: Args -> Maybe Integer
+ Console.Binance.Exports.Main: ConfigFile :: Maybe Text -> Maybe Text -> Maybe [Text] -> ConfigFile
+ Console.Binance.Exports.Main: [argApiKey] :: Args -> Maybe Text
+ Console.Binance.Exports.Main: [argApiSecret] :: Args -> Maybe Text
+ Console.Binance.Exports.Main: [argOutputFile] :: Args -> Maybe FilePath
+ Console.Binance.Exports.Main: [argSymbols] :: Args -> [Text]
+ Console.Binance.Exports.Main: [argYear] :: Args -> Maybe Integer
+ Console.Binance.Exports.Main: [cfgApiKey] :: ConfigFile -> Maybe Text
+ Console.Binance.Exports.Main: [cfgApiSecret] :: ConfigFile -> Maybe Text
+ Console.Binance.Exports.Main: [cfgSymbols] :: ConfigFile -> Maybe [Text]
+ Console.Binance.Exports.Main: data ConfigFile
+ Console.Binance.Exports.Main: instance Data.Aeson.Types.FromJSON.FromJSON Console.Binance.Exports.Main.ConfigFile
+ Console.Binance.Exports.Main: instance GHC.Classes.Eq Console.Binance.Exports.Main.AppConfig
+ Console.Binance.Exports.Main: instance GHC.Classes.Eq Console.Binance.Exports.Main.ConfigFile
+ Console.Binance.Exports.Main: instance GHC.Show.Show Console.Binance.Exports.Main.AppConfig
+ Console.Binance.Exports.Main: instance GHC.Show.Show Console.Binance.Exports.Main.ConfigFile
+ Console.Binance.Exports.Main: loadConfigFile :: IO ConfigFile
- Console.Binance.Exports.Main: Args :: String -> String -> [String] -> Maybe Integer -> Maybe FilePath -> Args
+ Console.Binance.Exports.Main: Args :: Maybe Text -> Maybe Text -> [Text] -> Maybe Integer -> Maybe FilePath -> Args
- Console.Binance.Exports.Main: run :: Args -> IO ()
+ Console.Binance.Exports.Main: run :: ConfigFile -> Args -> IO ()

Files

CHANGELOG.md view
@@ -1,5 +1,16 @@ # CHANGELOG ++## master+++## v0.1.1.0++* Expand documentation printed out with the `--help` flag.+* Add support for pulling API credentials & symbols from environmental+  variables and a configuration file.++ ## v0.1.0.0  * Initial release
README.md view
@@ -22,8 +22,6 @@ TODO:  * Switch between Binance & Binance US APIs-* Config file & env var overrides for CLI args-* Release v1, add package to hackage & stackage   [get-stack]: https://docs.haskellstack.org/en/stable/README/
app/Main.hs view
@@ -4,4 +4,7 @@   main :: IO ()-main = getArgs >>= run+main = do+    args <- getArgs+    cfg  <- loadConfigFile+    run cfg args
binance-exports.cabal view
@@ -5,8 +5,8 @@ -- see: https://github.com/sol/hpack  name:           binance-exports-version:        0.1.0.0-synopsis:       Generate CSV Exports o your Binance Trade History.+version:        0.1.1.0+synopsis:       Generate CSV Exports to your Binance Trade History. description:    @binance-exports@ is a CLI program that queries the Binance.us API for your                 Trade History & exports all trades to a CSV file.                 .@@ -30,7 +30,7 @@                 @                 .                 See @binance-exports --help@ for additional options.-category:       Web+category:       Web, Finance, Console homepage:       https://github.com/prikhi/binance-exports#readme bug-reports:    https://github.com/prikhi/binance-exports/issues author:         Pavan Rikhi@@ -74,14 +74,18 @@     , cassava <1     , cmdargs >=0.10 && <1     , cryptohash-sha256 <1+    , directory <2     , http-client <1     , http-types <1     , mtl <3+    , raw-strings-qq <2     , req <4     , safe-exceptions <1     , scientific <1-    , text <2+    , text <3     , time <2+    , xdg-basedir <1+    , yaml <1   default-language: Haskell2010  executable binance-exports
src/Console/Binance/Exports/Main.hs view
@@ -1,4 +1,5 @@ {-# LANGUAGE DeriveDataTypeable #-}+{-# LANGUAGE QuasiQuotes #-} {-# LANGUAGE RecordWildCards #-} {- | CLI application harness. -}@@ -6,9 +7,18 @@     ( run     , getArgs     , Args(..)+    , loadConfigFile+    , ConfigFile(..)     ) where +import           Control.Applicative            ( (<|>) )+import           Control.Exception.Safe         ( try )+import           Control.Monad                  ( (<=<) ) import           Control.Monad.IO.Class         ( liftIO )+import           Data.Aeson                     ( (.:)+                                                , FromJSON(..)+                                                , withObject+                                                ) import           Data.List                      ( sortOn ) import           Data.Maybe                     ( fromMaybe ) import           Data.Ord                       ( Down(..) )@@ -17,12 +27,17 @@                                                 ) import           Data.Time.Clock.POSIX          ( posixSecondsToUTCTime ) import           Data.Version                   ( showVersion )+import           Data.Yaml                      ( prettyPrintParseException )+import           Data.Yaml.Config               ( ignoreEnv+                                                , loadYamlSettings+                                                ) import           System.Console.CmdArgs         ( (&=)                                                 , Data                                                 , Typeable                                                 , args                                                 , cmdArgs                                                 , def+                                                , details                                                 , explicit                                                 , help                                                 , helpArg@@ -31,8 +46,14 @@                                                 , summary                                                 , typ                                                 )+import           System.Directory               ( doesFileExist )+import           System.Environment             ( lookupEnv )+import           System.Environment.XDG.BaseDir ( getUserConfigFile ) import           System.Exit                    ( exitFailure )-import           System.IO                      ( stderr )+import           System.IO                      ( hPutStrLn+                                                , stderr+                                                )+import           Text.RawString.QQ              ( r )  import           Console.Binance.Exports.Csv import           Paths_binance_exports          ( version )@@ -44,15 +65,15 @@   -- | Generate & print a trade export based on the executable arguments.-run :: Args -> IO ()-run Args {..} = do-    results <- runApi cfg $ do+run :: ConfigFile -> Args -> IO ()+run cfg cfgArgs = do+    AppConfig {..} <- mergeCfgEnvArgs cfg cfgArgs+    results        <- runApi binanceCfg $ do         symbolDetails <--            fmap eiSymbols-            $   getExchangeInfo (map T.pack symbols)-            >>= handleBinanceError+            fmap eiSymbols $ getExchangeInfo symbols >>= handleBinanceError         rawExportData <- concat <$> mapM getTradesForSymbol symbolDetails-        return . filterYear $ sortOn (Down . tTime . tedTrade) rawExportData+        return . filterYear year $ sortOn (Down . tTime . tedTrade)+                                          rawExportData     -- Write CSV to file or stdout     let outputFileOrStdout = fromMaybe "-" outputFile     let output             = buildTradeExport results@@ -60,32 +81,26 @@         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 "+        Left e ->+            liftIO+                $  exitWithErr+                $  "Binance API Error Code "                 <> T.pack (show $ beCode e)                 <> ": "                 <> beMsg e-            exitFailure-        Right r -> return r+        Right a -> return a     -- | 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+    filterYear :: Maybe Integer -> [TradeExportData] -> [TradeExportData]+    filterYear = \case         Nothing -> id         Just y ->             filter@@ -96,16 +111,97 @@                 . tTime                 . tedTrade +-- | Print some error text to 'stderr', then exit with a failure code.+exitWithErr :: T.Text -> IO a+exitWithErr = const exitFailure <=< T.hPutStrLn stderr . ("[ERROR] " <>) ++-- CONFIGURATION++data AppConfig = AppConfig+    { binanceCfg :: BinanceConfig+    , symbols    :: [T.Text]+    , year       :: Maybe Integer+    , outputFile :: Maybe FilePath+    }+    deriving (Show, Eq)++-- | Given a parsed configuration file & CLI arguments, check for+-- environmental variables and either build an AppConfig or log an error+-- & exit if no API credentials or symbols have been passed.+mergeCfgEnvArgs :: ConfigFile -> Args -> IO AppConfig+mergeCfgEnvArgs ConfigFile {..} Args {..} = do+    envApiKey    <- fmap T.pack <$> lookupEnv "BINANCE_API_KEY"+    envApiSecret <- fmap T.pack <$> lookupEnv "BINANCE_API_SECRET"+    apiKey       <-+        requiredValue "Pass a Binance API Key with `-k` or $BINANCE_API_KEY."+        $   argApiKey+        <|> envApiKey+        <|> cfgApiKey+    apiSecret <-+        requiredValue+            "Pass a Binance API Secret with `-s` or $BINANCE_API_SECRET."+        $   argApiSecret+        <|> envApiSecret+        <|> cfgApiSecret+    let binanceCfg =+            BinanceConfig { bcApiKey = apiKey, bcApiSecret = apiSecret }+    symbols <- case (argSymbols, fromMaybe [] cfgSymbols) of+        ([], []) -> exitWithErr "Pass at least one symbol."+        ([], s ) -> return s+        (s , _ ) -> return s+    return AppConfig { year = argYear, outputFile = argOutputFile, .. }+  where+    requiredValue :: T.Text -> Maybe a -> IO a+    requiredValue errMsg = maybe (exitWithErr errMsg) return+++-- CONFIG FILE++-- | Optional configuration data parsed from the config file.+data ConfigFile = ConfigFile+    { cfgApiKey    :: Maybe T.Text+    , cfgApiSecret :: Maybe T.Text+    , cfgSymbols   :: Maybe [T.Text]+    }+    deriving (Show, Eq)++instance FromJSON ConfigFile where+    parseJSON = withObject "ConfigFile" $ \o -> do+        cfgApiKey    <- o .: "api-key"+        cfgApiSecret <- o .: "api-secret"+        cfgSymbols   <- o .: "symbols"+        return ConfigFile { .. }++-- | Attempt to read a 'ConfigFile' from+-- @$XDG_CONFIG_HOME/binance-exports/config.yaml@. Print any parsing errors+-- to 'stderr'.+loadConfigFile :: IO ConfigFile+loadConfigFile = do+    configPath   <- getUserConfigFile "binance-exports" "config.yaml"+    configExists <- doesFileExist configPath+    if configExists+        then try (loadYamlSettings [configPath] [] ignoreEnv) >>= \case+            Left (lines . prettyPrintParseException -> errorMsgs) ->+                hPutStrLn stderr "[WARN] Invalid Configuration Format:"+                    >> mapM_ (hPutStrLn stderr . ("\t" <>)) errorMsgs+                    >> return defaultConfig+            Right cfg -> return cfg+        else return defaultConfig+  where+    defaultConfig :: ConfigFile+    defaultConfig = ConfigFile Nothing Nothing Nothing++ -- CLI ARGS  -- | CLI arguments supported by the executable. data Args = Args-    { apiKey     :: String-    , apiSecret  :: String-    , symbols    :: [String]-    , year       :: Maybe Integer-    , outputFile :: Maybe FilePath+    { argApiKey     :: Maybe T.Text+    , argApiSecret  :: Maybe T.Text+    , argSymbols    :: [T.Text]+    , argYear       :: Maybe Integer+    , argOutputFile :: Maybe FilePath     }     deriving (Show, Read, Eq, Data, Typeable) @@ -119,32 +215,32 @@ 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 =+            { argApiKey     = def+                              &= help "Binance API Key"+                              &= name "k"+                              &= name "api-key"+                              &= explicit+                              &= typ "KEY"+            , argApiSecret  = def+                              &= help "Binance API Secret"+                              &= name "s"+                              &= name "api-secret"+                              &= explicit+                              &= typ "SECRET"+            , argYear       = Nothing+                              &= help "Limit output to year"+                              &= name "y"+                              &= name "year"+                              &= explicit+                              &= typ "YYYY"+            , argOutputFile =                 Nothing-                &= explicit+                &= help "File to write the export to. Default: stdout"                 &= name "o"                 &= name "output-file"-                &= help "File to write the export to. Default: stdout"+                &= explicit                 &= typ "FILE"-            , symbols    = def &= args &= typ "SYMBOL [SYMBOL ...]"+            , argSymbols    = def &= args &= typ "SYMBOL [SYMBOL ...]"             }         &= summary                (  "binance-exports v"@@ -154,3 +250,67 @@         &= program "binance-exports"         &= helpArg [name "h"]         &= help "Export Binance Trade History to a CSV"+        &= details programDetails+++programDetails :: [String]+programDetails = lines [r|+binance-exports generates a CSV export of your Binances Trade History. It+is intended to replace Binance's (removed) Trade History export.+++DESCRIPTION++By default, we will pull every single trade you have made for the passed+symbols & print them out in reverse-chronological order with the following+fields:++   time,base-asset,quote-asset,type,price,quantity,total,fee,fee-currency,trade-id++This closely matches Binance's Trade History export, except we've split the+`symbol` column into `base-asset` & `quote-asset` columns and include the+`trade-id`.+++OUTPUT FILE++You can use the `-o` flag to set the file we will write the CSV data into.+By default, the export is simply printed to stdout.++Warning: the export file will always be overwritten. We do not support+appending to an existing file.+++ENVIRONMENTAL VARIABLES++Instead of passing in your API credentials via the `-k` & `-s` CLI flags,+you can set the `$BINANCE_API_KEY` & `$BINANCE_API_SECRET` environmental+variables.+++CONFIGURATION FILE++You can also set some program options in a YAML file. We attempt to parse+a configuration file at `$XDG_CONFIG_HOME/binance-exports.yaml`. It+supports the following top-level keys:++    - `api-key`:        (string) Your Binance API key+    - `api-secret`:     (string) Your Binance API secret+    - `symbols`:        (list of strings) The trade symbols to fetch++Environmental variables will override any configuration options, and CLI+flags will override both environmental variables & configuration file+options.+++USAGE EXAMPLES++Fetch all my BNB trades:+    binance-exports BNBUSD++Fetch my BTC trades from 2020:+    binance-exports -y 2020 BTCUSD++Fetch my BNB & BTC trades from 2022, write them to a file:+    binance-exports -y 2022 -o 2022-binance-trades.csv BNBUSD BTCUSD+|]