{-# LANGUAGE RecordWildCards #-}
{- | Types & CSV serialization for the exports.
-}
module Console.Binance.Exports.Csv
    ( TradeExportData(..)
    , buildTradeExport
    ) where
import           Data.Csv                       ( (.=)
                                                , DefaultOrdered(..)
                                                , ToNamedRecord(..)
                                                , defaultEncodeOptions
                                                , encUseCrLf
                                                , encodeDefaultOrderedByNameWith
                                                , header
                                                , namedRecord
                                                )
import           Data.Scientific                ( FPFormat(..)
                                                , Scientific
                                                , formatScientific
                                                , isInteger
                                                )
import           Data.Time                      ( defaultTimeLocale
                                                , formatTime
                                                )
import           Data.Time.Clock.POSIX          ( posixSecondsToUTCTime )

import qualified Data.ByteString.Lazy          as LBS
import           Web.Binance                    ( SymbolDetails(..)
                                                , Trade(..)
                                                )


-- | We need both the 'SymbolDetails' & the 'Trade' to generate an export
-- line.
data TradeExportData = TradeExportData
    { TradeExportData -> SymbolDetails
tedSymbol :: SymbolDetails
    , TradeExportData -> Trade
tedTrade  :: Trade
    }
    deriving (Int -> TradeExportData -> ShowS
[TradeExportData] -> ShowS
TradeExportData -> String
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
showList :: [TradeExportData] -> ShowS
$cshowList :: [TradeExportData] -> ShowS
show :: TradeExportData -> String
$cshow :: TradeExportData -> String
showsPrec :: Int -> TradeExportData -> ShowS
$cshowsPrec :: Int -> TradeExportData -> ShowS
Show, ReadPrec [TradeExportData]
ReadPrec TradeExportData
Int -> ReadS TradeExportData
ReadS [TradeExportData]
forall a.
(Int -> ReadS a)
-> ReadS [a] -> ReadPrec a -> ReadPrec [a] -> Read a
readListPrec :: ReadPrec [TradeExportData]
$creadListPrec :: ReadPrec [TradeExportData]
readPrec :: ReadPrec TradeExportData
$creadPrec :: ReadPrec TradeExportData
readList :: ReadS [TradeExportData]
$creadList :: ReadS [TradeExportData]
readsPrec :: Int -> ReadS TradeExportData
$creadsPrec :: Int -> ReadS TradeExportData
Read, TradeExportData -> TradeExportData -> Bool
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
/= :: TradeExportData -> TradeExportData -> Bool
$c/= :: TradeExportData -> TradeExportData -> Bool
== :: TradeExportData -> TradeExportData -> Bool
$c== :: TradeExportData -> TradeExportData -> Bool
Eq, Eq TradeExportData
TradeExportData -> TradeExportData -> Bool
TradeExportData -> TradeExportData -> Ordering
TradeExportData -> TradeExportData -> TradeExportData
forall a.
Eq a
-> (a -> a -> Ordering)
-> (a -> a -> Bool)
-> (a -> a -> Bool)
-> (a -> a -> Bool)
-> (a -> a -> Bool)
-> (a -> a -> a)
-> (a -> a -> a)
-> Ord a
min :: TradeExportData -> TradeExportData -> TradeExportData
$cmin :: TradeExportData -> TradeExportData -> TradeExportData
max :: TradeExportData -> TradeExportData -> TradeExportData
$cmax :: TradeExportData -> TradeExportData -> TradeExportData
>= :: TradeExportData -> TradeExportData -> Bool
$c>= :: TradeExportData -> TradeExportData -> Bool
> :: TradeExportData -> TradeExportData -> Bool
$c> :: TradeExportData -> TradeExportData -> Bool
<= :: TradeExportData -> TradeExportData -> Bool
$c<= :: TradeExportData -> TradeExportData -> Bool
< :: TradeExportData -> TradeExportData -> Bool
$c< :: TradeExportData -> TradeExportData -> Bool
compare :: TradeExportData -> TradeExportData -> Ordering
$ccompare :: TradeExportData -> TradeExportData -> Ordering
Ord)

-- | We match the format of the old @Trade History@ export as much as
-- possible, but use the asset precisions for the @price@ & @quantity@
-- fields & output the @trade-id@ as well.
instance ToNamedRecord TradeExportData where
    toNamedRecord :: TradeExportData -> NamedRecord
toNamedRecord (TradeExportData SymbolDetails {Int
Text
sdQuoteAssetPrecision :: SymbolDetails -> Int
sdQuoteAsset :: SymbolDetails -> Text
sdBaseAssetPrecision :: SymbolDetails -> Int
sdBaseAsset :: SymbolDetails -> Text
sdSymbol :: SymbolDetails -> Text
sdQuoteAssetPrecision :: Int
sdQuoteAsset :: Text
sdBaseAssetPrecision :: Int
sdBaseAsset :: Text
sdSymbol :: Text
..} Trade {Bool
Integer
Scientific
Text
POSIXTime
tIsBestMatch :: Trade -> Bool
tIsMaker :: Trade -> Bool
tIsBuyer :: Trade -> Bool
tTime :: Trade -> POSIXTime
tCommissionAsset :: Trade -> Text
tCommission :: Trade -> Scientific
tQuoteQuantity :: Trade -> Scientific
tQuantity :: Trade -> Scientific
tPrice :: Trade -> Scientific
tOrderId :: Trade -> Integer
tId :: Trade -> Integer
tSymbol :: Trade -> Text
tIsBestMatch :: Bool
tIsMaker :: Bool
tIsBuyer :: Bool
tTime :: POSIXTime
tCommissionAsset :: Text
tCommission :: Scientific
tQuoteQuantity :: Scientific
tQuantity :: Scientific
tPrice :: Scientific
tOrderId :: Integer
tId :: Integer
tSymbol :: Text
..}) = [(ByteString, ByteString)] -> NamedRecord
namedRecord
        [ ByteString
"time" forall a. ToField a => ByteString -> a -> (ByteString, ByteString)
.= forall t. FormatTime t => TimeLocale -> String -> t -> String
formatTime TimeLocale
defaultTimeLocale
                               String
"%F %T"
                               (POSIXTime -> UTCTime
posixSecondsToUTCTime POSIXTime
tTime)
        , ByteString
"base-asset" forall a. ToField a => ByteString -> a -> (ByteString, ByteString)
.= Text
sdBaseAsset
        , ByteString
"quote-asset" forall a. ToField a => ByteString -> a -> (ByteString, ByteString)
.= Text
sdQuoteAsset
        , ByteString
"type" forall a. ToField a => ByteString -> a -> (ByteString, ByteString)
.= if Bool
tIsBuyer then String
"BUY" else (String
"SELL" :: String)
        , ByteString
"price" forall a. ToField a => ByteString -> a -> (ByteString, ByteString)
.= FPFormat -> Maybe Int -> Scientific -> String
formatScientific FPFormat
Fixed (forall a. a -> Maybe a
Just Int
sdQuoteAssetPrecision) Scientific
tPrice
        , ByteString
"quantity"
            forall a. ToField a => ByteString -> a -> (ByteString, ByteString)
.= FPFormat -> Maybe Int -> Scientific -> String
formatScientific FPFormat
Fixed (forall a. a -> Maybe a
Just Int
sdBaseAssetPrecision) Scientific
tQuantity
        , ByteString
"total" forall a. ToField a => ByteString -> a -> (ByteString, ByteString)
.= Scientific -> String
renderScientific (Scientific
tPrice forall a. Num a => a -> a -> a
* Scientific
tQuantity)
        , ByteString
"fee" forall a. ToField a => ByteString -> a -> (ByteString, ByteString)
.= Scientific -> String
renderScientific Scientific
tCommission
        , ByteString
"fee-currency" forall a. ToField a => ByteString -> a -> (ByteString, ByteString)
.= Text
tCommissionAsset
        , ByteString
"trade-id" forall a. ToField a => ByteString -> a -> (ByteString, ByteString)
.= Integer
tId
        ]
      where
        -- | Render as an integer if possible, otherwise render to the
        -- minimum decimal precision needed.
        renderScientific :: Scientific -> String
        renderScientific :: Scientific -> String
renderScientific Scientific
p = if Scientific -> Bool
isInteger Scientific
p
            then FPFormat -> Maybe Int -> Scientific -> String
formatScientific FPFormat
Fixed (forall a. a -> Maybe a
Just Int
0) Scientific
p
            else FPFormat -> Maybe Int -> Scientific -> String
formatScientific FPFormat
Fixed forall a. Maybe a
Nothing Scientific
p

instance DefaultOrdered TradeExportData where
    headerOrder :: TradeExportData -> Header
headerOrder TradeExportData
_ = [ByteString] -> Header
header
        [ ByteString
"time"
        , ByteString
"base-asset"
        , ByteString
"quote-asset"
        , ByteString
"type"
        , ByteString
"price"
        , ByteString
"quantity"
        , ByteString
"total"
        , ByteString
"fee"
        , ByteString
"fee-currency"
        , ByteString
"trade-id"
        ]

-- | Generate a CSV from the trade data.
buildTradeExport :: [TradeExportData] -> LBS.ByteString
buildTradeExport :: [TradeExportData] -> ByteString
buildTradeExport =
    forall a.
(DefaultOrdered a, ToNamedRecord a) =>
EncodeOptions -> [a] -> ByteString
encodeDefaultOrderedByNameWith (EncodeOptions
defaultEncodeOptions { encUseCrLf :: Bool
encUseCrLf = Bool
False })