-- | Defines types used in the REST API module OANDA.Internal.Types ( OandaEnv , apiType , accessToken , practiceAuth , liveAuth , APIType (..) , AccessToken (..) , AccountID (..) , InstrumentText , InstrumentName (..) , AccountUnits (..) , Currency (..) , OandaZonedTime (..) ) where import Control.Applicative ((<|>)) import Control.Lens (from, view) import qualified Data.ByteString as BS import Data.Thyme.Clock.POSIX (posixTime) import Data.Thyme.Time.Core (fromMicroseconds) import OANDA.Internal.Import import Debug.Trace -- | Wraps an `APIType` and an `AccessToken`. Mainly just a convenience wrapper -- to make functions have fewer arguments. To instantiate this type, use the -- `practiceAuth` or `liveAuth` functions. data OandaEnv = OandaEnv { apiType :: APIType , accessToken :: AccessToken } deriving (Show) -- | Use the practice API. practiceAuth :: AccessToken -> OandaEnv practiceAuth = OandaEnv Practice -- | Use the live API. liveAuth :: AccessToken -> OandaEnv liveAuth = OandaEnv Live -- | The three endpoint types used in the REST API. See the following link for -- details: data APIType = Practice | Live deriving (Show) -- | The token given by OANDA used to access the API newtype AccessToken = AccessToken { unAccessToken :: BS.ByteString } deriving (Show) -- | Integer representing the Account ID of an account newtype AccountID = AccountID { unAccountID :: String } deriving (Show, FromJSON, ToJSON) type InstrumentText = Text newtype InstrumentName = InstrumentName { unInstrumentName :: Text } deriving (Show, FromJSON, ToJSON, IsString) newtype AccountUnits = AccountUnits { unAccountUnits :: Text } deriving (Show, FromJSON, ToJSON, IsString) newtype Currency = Currency { unCurrency :: Text } deriving (Show, FromJSON, ToJSON, IsString) -- | Newtype wrapper around 'ZonedTime' to make a new JSON instance. Apparently -- OANDA decides to use either UNIX epoch seconds or RFC3339 on the fly. newtype OandaZonedTime = OandaZonedTime { unOandaZonedTime :: ZonedTime } deriving (Show, Eq, ToJSON) instance FromJSON OandaZonedTime where parseJSON v = OandaZonedTime <$> (parseJSON v <|> parseZonedFromEpoch v) where -- If we reach this branch then OANDA has encoded the time as a string -- with a number that represents the seconds since the epoch. parseZonedFromEpoch :: Value -> Parser ZonedTime parseZonedFromEpoch v' = do (secondsSinceEpoch :: Double) <- parseJSONFromString v' let microsecondsSinceEpoch = secondsSinceEpoch * 1e6 (timeInUTC :: UTCTime) = view (from posixTime) (fromMicroseconds (round microsecondsSinceEpoch) :: NominalDiffTime) (timeInZoned :: ZonedTime) = view zonedTime (utc, timeInUTC) return $ traceShow (secondsSinceEpoch, microsecondsSinceEpoch, timeInUTC, timeInZoned) timeInZoned