{-# LANGUAGE DeriveDataTypeable #-} {-# LANGUAGE DeriveGeneric #-} {-# LANGUAGE GeneralizedNewtypeDeriving #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE RecordWildCards #-} module Coinbase.Exchange.Types.Private where import Control.Applicative import Control.DeepSeq import Control.Monad import Data.Aeson.Casing import Data.Aeson.Types import Data.Char import Data.Data import Data.Hashable import Data.Text (Text) import qualified Data.Text as T import Data.Time import Data.UUID import Data.Word import GHC.Generics import Coinbase.Exchange.Types import Coinbase.Exchange.Types.Core -- Accounts newtype AccountId = AccountId { unAccountId :: UUID } deriving (Eq, Ord, Show, Read, Data, Typeable, Generic, NFData, Hashable, FromJSON, ToJSON) data Account = Account { accId :: AccountId , accBalance :: CoinScientific , accHold :: CoinScientific , accAvailable :: CoinScientific , accCurrency :: CurrencyId } deriving (Show, Eq, Data, Typeable, Generic) instance NFData Account instance ToJSON Account where toJSON = genericToJSON coinbaseAesonOptions instance FromJSON Account where parseJSON = genericParseJSON coinbaseAesonOptions -- newtype EntryId = EntryId { unEntryId :: Word64 } deriving (Eq, Ord, Num, Show, Read, Data, Typeable, Generic, NFData, Hashable, FromJSON, ToJSON) data Entry = Entry { entryId :: EntryId , entryCreatedAt :: UTCTime , entryAmount :: CoinScientific , entryBalance :: CoinScientific , entryType :: EntryType , entryDetails :: EntryDetails } deriving (Show, Data, Typeable, Generic) instance NFData Entry instance ToJSON Entry where toJSON Entry{..} = object [ "id" .= entryId , "created_at" .= entryCreatedAt , "amount" .= entryAmount , "balance" .= entryBalance , "type" .= entryType , "details" .= entryDetails ] instance FromJSON Entry where parseJSON (Object m) = Entry <$> m .: "id" <*> m .: "created_at" <*> m .: "amount" <*> m .: "balance" <*> m .: "type" <*> m .: "details" parseJSON _ = mzero data EntryType = Match | Fee | Transfer deriving (Eq, Ord, Show, Read, Data, Typeable, Generic) instance NFData EntryType instance Hashable EntryType instance ToJSON EntryType where toJSON = genericToJSON defaultOptions { constructorTagModifier = map toLower } instance FromJSON EntryType where parseJSON = genericParseJSON defaultOptions { constructorTagModifier = map toLower } data EntryDetails = EntryDetails { detailOrderId :: Maybe OrderId , detailTradeId :: Maybe TradeId , detailProductId :: Maybe ProductId } deriving (Show, Data, Typeable, Generic) instance NFData EntryDetails instance ToJSON EntryDetails where toJSON = genericToJSON coinbaseAesonOptions instance FromJSON EntryDetails where parseJSON = genericParseJSON coinbaseAesonOptions -- newtype HoldId = HoldId { unHoldId :: UUID } deriving (Eq, Ord, Show, Read, Data, Typeable, Generic, NFData, Hashable, FromJSON, ToJSON) data Hold = OrderHold { holdId :: HoldId , holdAccountId :: AccountId , holdCreatedAt :: UTCTime , holdUpdatedAt :: UTCTime , holdAmount :: CoinScientific , holdOrderRef :: OrderId } | TransferHold { holdId :: HoldId , holdAccountId :: AccountId , holdCreatedAt :: UTCTime , holdUpdatedAt :: UTCTime , holdAmount :: CoinScientific , holdTransferRef :: TransferId } deriving (Show, Data, Typeable, Generic) instance NFData Hold instance ToJSON Hold where toJSON = genericToJSON coinbaseAesonOptions instance FromJSON Hold where parseJSON = genericParseJSON coinbaseAesonOptions -- Orders data OrderContigency = GoodTillCanceled | GoodTillTime | ImmediateOrCancel | FillOrKill deriving (Eq, Ord, Show, Read, Data, Typeable, Generic) instance NFData OrderContigency instance Hashable OrderContigency instance ToJSON OrderContigency where toJSON GoodTillCanceled = String "GTC" toJSON GoodTillTime = String "GTT" toJSON ImmediateOrCancel = String "IOC" toJSON FillOrKill = String "FOK" instance FromJSON OrderContigency where parseJSON (String "GTC") = return GoodTillCanceled parseJSON (String "GTT") = return GoodTillTime parseJSON (String "IOC") = return ImmediateOrCancel parseJSON (String "FOK") = return FillOrKill parseJSON _ = mzero data OrderCancelAfter = Min | Hour | Day deriving (Eq, Ord, Show, Read, Data, Typeable, Generic) instance NFData OrderCancelAfter instance Hashable OrderCancelAfter instance ToJSON OrderCancelAfter where toJSON Min = String "min" toJSON Hour = String "hour" toJSON Day = String "day" instance FromJSON OrderCancelAfter where parseJSON (String "min") = return Min parseJSON (String "hour") = return Hour parseJSON (String "day") = return Day parseJSON _ = mzero data SelfTrade = DecrementAndCancel | CancelOldest | CancelNewest | CancelBoth deriving (Eq, Ord, Show, Read, Data, Typeable, Generic) instance NFData SelfTrade instance Hashable SelfTrade instance ToJSON SelfTrade where toJSON DecrementAndCancel = String "dc" toJSON CancelOldest = String "co" toJSON CancelNewest = String "cn" toJSON CancelBoth = String "cb" instance FromJSON SelfTrade where parseJSON (String "dc") = return DecrementAndCancel parseJSON (String "co") = return CancelOldest parseJSON (String "cn") = return CancelNewest parseJSON (String "cb") = return CancelBoth parseJSON _ = mzero data NewOrder = NewLimitOrder { noProductId :: ProductId , noSide :: Side , noSelfTrade :: SelfTrade , noClientOid :: Maybe ClientOrderId --- , noPrice :: Price , noSize :: Size ,noTimeInForce:: OrderContigency ,noCancelAfter:: Maybe OrderCancelAfter , noPostOnly :: Bool } | NewMarketOrder { noProductId :: ProductId , noSide :: Side , noSelfTrade :: SelfTrade , noClientOid :: Maybe ClientOrderId --- , noSizeAndOrFunds :: Either Size (Maybe Size, Cost) } | NewStopOrder { noProductId :: ProductId , noSide :: Side , noSelfTrade :: SelfTrade , noClientOid :: Maybe ClientOrderId --- , noPrice :: Price , noSizeAndOrFunds :: Either Size (Maybe Size, Cost) } deriving (Show, Data, Typeable, Generic) instance NFData NewOrder instance ToJSON NewOrder where toJSON NewLimitOrder{..} = object ([ "type" .= ("limit" :: Text) , "product_id" .= noProductId , "side" .= noSide , "stp" .= noSelfTrade , "price" .= noPrice , "size" .= noSize , "time_in_force" .= noTimeInForce , "post_only" .= noPostOnly ] ++ clientID ++ cancelAfter ) where clientID = case noClientOid of Just cid -> [ "client_oid" .= cid ] Nothing -> [] cancelAfter = case noCancelAfter of Just time -> [ "cancel_after" .= time ] Nothing -> [] toJSON NewMarketOrder{..} = object ([ "type" .= ("market" :: Text) , "product_id" .= noProductId , "side" .= noSide , "stp" .= noSelfTrade ] ++ clientID ++ size ++ funds ) where clientID = case noClientOid of Just cid -> [ "client_oid" .= cid ] Nothing -> [] (size,funds) = case noSizeAndOrFunds of Left s -> (["size" .= s],[]) Right (ms,f) -> case ms of Nothing -> ( [] , ["funds" .= f] ) Just s' -> ( ["size" .= s'], ["funds" .= f] ) toJSON NewStopOrder{..} = object ([ "type" .= ("stop" :: Text) , "product_id" .= noProductId , "side" .= noSide , "stp" .= noSelfTrade , "price" .= noPrice ] ++ clientID ++ size ++ funds ) where clientID = case noClientOid of Just cid -> [ "client_oid" .= cid ] Nothing -> [] (size,funds) = case noSizeAndOrFunds of Left s -> (["size" .= s],[]) Right (ms,f) -> case ms of Nothing -> ( [] , ["funds" .= f] ) Just s' -> ( ["size" .= s'], ["funds" .= f] ) data OrderConfirmation = OrderConfirmation { ocId :: OrderId } deriving (Show, Data, Typeable, Generic) instance NFData OrderConfirmation instance ToJSON OrderConfirmation where toJSON = genericToJSON coinbaseAesonOptions instance FromJSON OrderConfirmation where parseJSON = genericParseJSON coinbaseAesonOptions data Order = LimitOrder { orderId :: OrderId , orderProductId :: ProductId , orderStatus :: OrderStatus , orderSelfTrade :: SelfTrade , orderSettled :: Bool , orderSide :: Side , orderCreatedAt :: UTCTime , orderFilledSize :: Maybe Size , orderFilledFees :: Maybe Price , orderDoneAt :: Maybe UTCTime , orderDoneReason :: Maybe Reason , orderPrice :: Price , orderSize :: Size , orderTimeInForce:: OrderContigency , orderCancelAfter:: Maybe OrderCancelAfter , orderPostOnly :: Bool } | MarketOrder { orderId :: OrderId , orderProductId :: ProductId , orderStatus :: OrderStatus , orderSelfTrade :: SelfTrade , orderSettled :: Bool , orderSide :: Side , orderCreatedAt :: UTCTime , orderFilledSize :: Maybe Size , orderFilledFees :: Maybe Price , orderDoneAt :: Maybe UTCTime , orderDoneReason :: Maybe Reason , orderSizeAndOrFunds :: Either Size (Maybe Size, Cost) } | StopOrder { orderId :: OrderId , orderProductId :: ProductId , orderStatus :: OrderStatus , orderSelfTrade :: SelfTrade , orderSettled :: Bool , orderSide :: Side , orderCreatedAt :: UTCTime , orderFilledSize :: Maybe Size , orderFilledFees :: Maybe Price , orderDoneAt :: Maybe UTCTime , orderDoneReason :: Maybe Reason , orderPrice :: Price , orderSizeAndOrFunds :: Either Size (Maybe Size, Cost) } deriving (Show, Data, Typeable, Generic) instance NFData Order instance ToJSON Order where toJSON LimitOrder{..} = object [ "type" .= ("limit" :: Text) , "id" .= orderId , "product_id" .= orderProductId , "status" .= orderStatus , "stp" .= orderSelfTrade , "settled" .= orderSettled , "side" .= orderSide , "created_at" .= orderCreatedAt , "filled_size" .= orderFilledSize , "filled_fees" .= orderFilledFees , "done_at" .= orderDoneAt , "done_reason" .= orderDoneReason , "price" .= orderPrice , "size" .= orderSize , "time_in_force" .= orderTimeInForce , "cancel_after" .= orderCancelAfter , "post_only" .= orderPostOnly ] toJSON MarketOrder{..} = object ([ "type" .= ("market" :: Text) , "id" .= orderId , "product_id" .= orderProductId , "status" .= orderStatus , "stp" .= orderSelfTrade , "settled" .= orderSettled , "side" .= orderSide , "created_at" .= orderCreatedAt , "filled_size" .= orderFilledSize , "filled_fees" .= orderFilledFees , "done_at" .= orderDoneAt , "done_reason" .= orderDoneReason ] ++ size ++ funds ) where (size,funds) = case orderSizeAndOrFunds of Left s -> (["size" .= s],[]) Right (ms,f) -> case ms of Nothing -> ( [] , ["funds" .= f] ) Just s' -> ( ["size" .= s'], ["funds" .= f] ) toJSON StopOrder{..} = object ([ "type" .= ("market" :: Text) , "id" .= orderId , "product_id" .= orderProductId , "status" .= orderStatus , "stp" .= orderSelfTrade , "settled" .= orderSettled , "side" .= orderSide , "created_at" .= orderCreatedAt , "filled_size" .= orderFilledSize , "filled_fees" .= orderFilledFees , "done_at" .= orderDoneAt , "done_reason" .= orderDoneReason , "price" .= orderPrice ] ++ size ++ funds ) where (size,funds) = case orderSizeAndOrFunds of Left s -> (["size" .= s],[]) Right (ms,f) -> case ms of Nothing -> ( [] , ["funds" .= f] ) Just s' -> ( ["size" .= s'], ["funds" .= f] ) instance FromJSON Order where parseJSON (Object m) = do ordertype <- m .: "type" case (ordertype :: String) of "limit" -> LimitOrder <$> m .: "id" <*> m .: "product_id" <*> m .: "status" <*> m .: "stp" <*> m .: "settled" <*> m .: "side" <*> m .: "created_at" <*> m .:? "filled_size" <*> m .:? "filled_fees" <*> m .:? "done_at" <*> m .:? "done_reason" <*> m .: "price" <*> m .: "size" <*> m .:? "time_in_force" .!= GoodTillCanceled -- older orders don't seem to have this field <*> m .:? "cancel_after" <*> m .: "post_only" "market" -> MarketOrder <$> m .: "id" <*> m .: "product_id" <*> m .: "status" <*> m .: "stp" <*> m .: "settled" <*> m .: "side" <*> m .: "created_at" <*> m .:? "filled_size" <*> m .:? "filled_fees" <*> m .:? "done_at" <*> m .:? "done_reason" <*> (do ms <- m .:? "size" mf <- m .:? "funds" case (ms,mf) of (Nothing, Nothing) -> mzero (Just s , Nothing) -> return $ Left s (Nothing, Just f ) -> return $ Right (Nothing, f) (Just s , Just f ) -> return $ Right (Just s , f) ) "stop" -> StopOrder <$> m .: "id" <*> m .: "product_id" <*> m .: "status" <*> m .: "stp" <*> m .: "settled" <*> m .: "side" <*> m .: "created_at" <*> m .:? "filled_size" <*> m .:? "filled_fees" <*> m .:? "done_at" <*> m .:? "done_reason" <*> m .: "price" <*> (do ms <- m .:? "size" mf <- m .:? "funds" case (ms,mf) of (Nothing, Nothing) -> mzero (Just s , Nothing) -> return $ Left s (Nothing, Just f ) -> return $ Right (Nothing, f) (Just s , Just f ) -> return $ Right (Just s , f) ) _ -> mzero parseJSON _ = mzero -- Fills data Liquidity = Maker | Taker deriving (Eq, Ord, Show, Read, Data, Typeable, Generic) instance NFData Liquidity instance Hashable Liquidity instance ToJSON Liquidity where toJSON Maker = String "M" toJSON Taker = String "T" instance FromJSON Liquidity where parseJSON (String "M") = return Maker parseJSON (String "T") = return Taker parseJSON _ = mzero data Fill = Fill { fillTradeId :: TradeId , fillProductId :: ProductId , fillPrice :: Price , fillSize :: Size , fillOrderId :: OrderId , fillCreatedAt :: UTCTime , fillLiquidity :: Liquidity , fillFee :: Price , fillSettled :: Bool , fillSide :: Side } deriving (Show, Data, Typeable, Generic) instance NFData Fill instance ToJSON Fill where toJSON Fill{..} = object [ "trade_id" .= fillTradeId , "product_id" .= fillProductId , "price" .= fillPrice , "size" .= fillSize , "order_id" .= fillOrderId , "created_at" .= fillCreatedAt , "liquidity" .= fillLiquidity , "fee" .= fillFee , "settled" .= fillSettled , "side" .= fillSide ] instance FromJSON Fill where parseJSON (Object m) = Fill <$> m .: "trade_id" <*> m .: "product_id" <*> m .: "price" <*> m .: "size" <*> m .: "order_id" <*> m .: "created_at" <*> m .: "liquidity" <*> m .: "fee" <*> m .: "settled" <*> m .: "side" parseJSON _ = mzero -- Transfers newtype TransferId = TransferId { unTransferId :: UUID } deriving (Eq, Ord, Show, Read, Data, Typeable, Generic, NFData, FromJSON, ToJSON) newtype CoinbaseAccountId = CoinbaseAccountId { unCoinbaseAccountId :: UUID } deriving (Eq, Ord, Show, Read, Data, Typeable, Generic, NFData, FromJSON, ToJSON) data TransferToCoinbase = Deposit { trAmount :: Size , trCoinbaseAccountId :: CoinbaseAccountId } | Withdraw { trAmount :: Size , trCoinbaseAccountId :: CoinbaseAccountId } deriving (Show, Data, Typeable, Generic) instance NFData TransferToCoinbase instance ToJSON TransferToCoinbase where toJSON = genericToJSON coinbaseAesonOptions data CryptoWithdrawal = Withdrawal { wdAmount :: Size , wdCurrency :: CurrencyId , wdCryptoAddress :: CryptoWallet } deriving (Show, Data, Typeable, Generic) instance NFData CryptoWithdrawal instance ToJSON CryptoWithdrawal where toJSON = genericToJSON coinbaseAesonOptions instance FromJSON CryptoWithdrawal where parseJSON = genericParseJSON coinbaseAesonOptions --------------------------- data TransferToCoinbaseResponse = TransferResponse { trId :: TransferId -- FIX ME! and other stuff I'm going to ignore. } deriving (Eq, Show, Generic, Typeable) instance NFData TransferToCoinbaseResponse instance FromJSON TransferToCoinbaseResponse where parseJSON (Object m) = TransferResponse <$> m .: "id" parseJSON _ = mzero data CryptoWithdrawalResp = WithdrawalResp { wdrId :: TransferId , wdrAmount :: Size , wdrCurrency :: CurrencyId } deriving (Eq, Show, Generic, Typeable) instance NFData CryptoWithdrawalResp instance ToJSON CryptoWithdrawalResp where toJSON = genericToJSON coinbaseAesonOptions instance FromJSON CryptoWithdrawalResp where parseJSON = genericParseJSON coinbaseAesonOptions --------------------------- data CryptoWallet = BTCWallet BitcoinWallet deriving (Show, Data, Typeable, Generic) -- | To Do: add other... -- | ... possibilities here later instance NFData CryptoWallet instance ToJSON CryptoWallet where toJSON = genericToJSON coinbaseAesonOptions instance FromJSON CryptoWallet where parseJSON = genericParseJSON coinbaseAesonOptions newtype BitcoinWallet = FromBTCAddress { btcAddress :: String } deriving (Show, Data, Typeable, Generic, ToJSON, FromJSON) instance NFData BitcoinWallet data BTCTransferReq = SendBitcoin { sendAmount :: Size , bitcoinWallet :: BitcoinWallet } deriving (Show, Data, Typeable, Generic) instance NFData BTCTransferReq instance ToJSON BTCTransferReq where toJSON SendBitcoin {..} = object [ "type" .= ("send" :: Text) , "currency" .= ("BTC" :: Text) , "to" .= btcAddress bitcoinWallet , "amount" .= sendAmount ] --------------------------- newtype BTCTransferId = BTCTransferId { getBtcTransferId :: UUID } deriving (Eq, Ord, Show, Read, Data, Typeable, Generic, NFData, FromJSON, ToJSON) data BTCTransferResponse = BTCTransferResponse { sendId :: BTCTransferId -- FIX ME! and other stuff I'm going to ignore. } deriving (Eq, Data, Show, Generic, Typeable) instance NFData BTCTransferResponse instance FromJSON BTCTransferResponse where parseJSON (Object m) = do transferData <- m .:? "data" -- FIX ME! I should factor this out of all responses from Coinbase case transferData of Nothing -> mzero Just da -> BTCTransferResponse <$> da .: "id" parseJSON _ = mzero --------------------------- data CoinbaseAccount = CoinbaseAccount { cbAccID :: CoinbaseAccountId , resourcePath :: String , primary :: Bool , name :: String , btcBalance :: Size } deriving (Show, Data, Typeable, Generic) instance FromJSON CoinbaseAccount where parseJSON (Object m) = do transferData <- m .:? "data" -- FIX ME! I should factor this out of all responses from Coinbase case transferData of Nothing -> mzero Just da -> CoinbaseAccount <$> da .: "id" <*> da .: "resource_path" <*> da .: "primary" <*> da .: "name" <*> (do btcBalance <- da .: "balance" case btcBalance of Object b -> b .: "amount" _ -> mzero ) parseJSON _ = mzero -- Reports newtype ReportId = ReportId { unReportId :: UUID } deriving (Eq, Ord, Show, Read, Data, Typeable, Generic, NFData, FromJSON, ToJSON) data ReportType = FillsReport | AccountReport deriving (Eq, Ord, Show, Read, Data, Typeable, Generic) instance NFData ReportType instance Hashable ReportType instance ToJSON ReportType where toJSON FillsReport = String "fills" toJSON AccountReport = String "account" instance FromJSON ReportType where parseJSON (String "fills") = return FillsReport parseJSON (String "account") = return AccountReport parseJSON _ = mzero data ReportFormat = PDF | CSV deriving (Eq, Ord, Show, Read, Data, Typeable, Generic) instance NFData ReportFormat instance Hashable ReportFormat instance ToJSON ReportFormat where toJSON PDF = String "pdf" toJSON CSV = String "csv" instance FromJSON ReportFormat where parseJSON (String "pdf") = return PDF parseJSON (String "csv") = return CSV parseJSON _ = mzero data ReportRequest -- analgous to Transfer or NewOrder = ReportRequest { rrqType :: ReportType , rrqStartDate :: UTCTime , rrqEndDate :: UTCTime , rrqProductId :: ProductId , rrqAccountId :: AccountId , rrqFormat :: ReportFormat , rrqEmail :: Maybe String } deriving (Show, Data, Typeable, Generic) instance NFData ReportRequest instance ToJSON ReportRequest where toJSON = genericToJSON coinbaseAesonOptions instance FromJSON ReportRequest where parseJSON = genericParseJSON coinbaseAesonOptions data ReportParams = ReportParams { reportStartDate :: UTCTime , reportEndDate :: UTCTime } deriving (Show, Data, Typeable, Generic) instance NFData ReportParams instance ToJSON ReportParams where toJSON ReportParams{..} = object ([ "start_date" .= reportStartDate , "end_date" .= reportEndDate ]) instance FromJSON ReportParams where parseJSON (Object m) = ReportParams <$> m .: "start_date" <*> m .: "end_date" parseJSON _ = mzero data ReportStatus = ReportPending | ReportCreating | ReportReady deriving (Eq, Ord, Show, Read, Data, Typeable, Generic) instance NFData ReportStatus instance Hashable ReportStatus instance ToJSON ReportStatus where toJSON ReportPending = String "pending" toJSON ReportCreating = String "creating" toJSON ReportReady = String "ready" instance FromJSON ReportStatus where parseJSON (String "pending") = return ReportPending parseJSON (String "creating") = return ReportCreating parseJSON (String "ready") = return ReportReady parseJSON _ = mzero data ReportInfo = ReportInfo { reportId :: ReportId , reportType :: ReportType , reportStatus :: ReportStatus , reportCreated :: Maybe UTCTime , reportCompleted :: Maybe UTCTime , reportExpires :: Maybe UTCTime , reportUrl :: Maybe String , reportParams :: Maybe ReportParams } deriving (Show, Data, Typeable, Generic) instance NFData ReportInfo instance ToJSON ReportInfo where toJSON ReportInfo{..} = object ([ "id" .= reportId , "type" .= reportType , "status" .= reportStatus , "created_at" .= reportCreated , "completed_at" .= reportCompleted , "expires_at" .= reportExpires , "file_url" .= reportUrl , "params" .= reportParams ]) instance FromJSON ReportInfo where parseJSON (Object m) = ReportInfo <$> m .: "id" <*> m .: "type" <*> m .: "status" <*> m .:? "created_at" <*> m .:? "completed_at" <*> m .:? "expires_at" <*> m .:? "file_url" <*> m .:? "params" parseJSON _ = mzero