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 +11/−0
- README.md +0/−2
- app/Main.hs +4/−1
- binance-exports.cabal +8/−4
- src/Console/Binance/Exports/Main.hs +208/−48
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+|]