diff --git a/libatrade.cabal b/libatrade.cabal index f26203a..20acb9d 100644 --- a/libatrade.cabal +++ b/libatrade.cabal @@ -1,5 +1,5 @@ name: libatrade -version: 0.2.0.0 +version: 0.3.0.0 synopsis: ATrade infrastructure core library description: Please see README.md homepage: https://github.com/asakul/libatrade.git @@ -27,7 +27,6 @@ library , ATrade.Broker.TradeSinks.ZMQTradeSink , ATrade.Util build-depends: base >= 4.7 && < 5 - , Decimal , time , datetime , bytestring @@ -52,6 +51,7 @@ library , http-client , http-client-tls , utf8-string + , scientific default-language: Haskell2010 executable libatrade-exe @@ -77,7 +77,6 @@ test-suite libatrade-test , tasty-hspec , quickcheck-text , quickcheck-instances - , Decimal , scientific , tuple , time diff --git a/src/ATrade/Price.hs b/src/ATrade/Price.hs index b03b686..85fd107 100644 --- a/src/ATrade/Price.hs +++ b/src/ATrade/Price.hs @@ -5,10 +5,16 @@ module ATrade.Price ( fromDouble, toDouble, decompose, - compose + compose, + fromScientific, + toScientific ) where import Data.Int +import Data.Ratio + +import Data.Aeson +import Data.Scientific data Price = Price { priceQuants :: !Int64 @@ -41,9 +47,30 @@ toDouble p = fromIntegral (priceQuants p) / fromIntegral mega fromDouble :: Double -> Price fromDouble d = Price { priceQuants = truncate (d * fromIntegral mega) } +toScientific :: Price -> Scientific +toScientific p = normalize $ scientific (toInteger $ priceQuants p) (-6) + +fromScientific :: Scientific -> Price +fromScientific d = Price { priceQuants = if base10Exponent nd >= -6 then fromInteger $ coefficient nd * (10 ^ (base10Exponent nd + 6)) else 0 } + where + nd = normalize d + decompose :: Price -> (Int64, Int32) decompose Price{priceQuants = p} = (p `div` mega, (fromInteger . toInteger) $ p `mod` mega) compose :: (Int64, Int32) -> Price compose (int, frac) = Price { priceQuants = int * mega + (fromInteger . toInteger) frac } +instance FromJSON Price where + parseJSON = withScientific "number" (\x -> let nx = normalize x in + return Price { priceQuants = if base10Exponent nx >= -6 then fromInteger $ coefficient nx * (10 ^ (base10Exponent nx + 6)) else 0 }) + +instance ToJSON Price where + toJSON x = Number (normalize $ scientific (toInteger $ priceQuants x) (-6)) + +instance Real Price where + toRational a = (toInteger . priceQuants $ a) % toInteger mega + +instance Fractional Price where + fromRational a = fromInteger (numerator a) / fromInteger (denominator a) + a / b = fromDouble $ toDouble a / toDouble b diff --git a/src/ATrade/Types.hs b/src/ATrade/Types.hs index 6c9a0dc..5648ede 100644 --- a/src/ATrade/Types.hs +++ b/src/ATrade/Types.hs @@ -36,7 +36,6 @@ import Data.Binary.Builder import Data.Binary.Get import Data.ByteString.Lazy as B import Data.DateTime -import Data.Decimal import Data.Int import Data.List as L import Data.Maybe @@ -138,14 +137,6 @@ parseTick = do makeTimestamp :: Word64 -> Word32 -> UTCTime makeTimestamp sec usec = addUTCTime (fromRational $ toInteger usec % 1000000) (fromSeconds . toInteger $ sec) - makeValue :: Int64 -> Int32 -> Decimal - makeValue intpart nanopart = case eitherFromRational r of - Right v -> v + convertedIntPart - Left _ -> convertedIntPart - where - convertedIntPart = realFracToDecimal 10 (fromIntegral intpart) - r = toInteger nanopart % 1000000000 - deserializeTick :: [ByteString] -> Maybe Tick deserializeTick (header:rawData:_) = case runGetOrFail parseTick rawData of Left (_, _, _) -> Nothing @@ -161,10 +152,10 @@ deserializeTickBody bs = case runGetOrFail parseTick bs of data Bar = Bar { barSecurity :: !TickerId, barTimestamp :: !UTCTime, - barOpen :: !Decimal, - barHigh :: !Decimal, - barLow :: !Decimal, - barClose :: !Decimal, + barOpen :: !Price, + barHigh :: !Price, + barLow :: !Price, + barClose :: !Price, barVolume :: !Integer } deriving (Show, Eq, Generic) @@ -186,30 +177,28 @@ instance ToJSON SignalId where "signal-name" .= signalName sid, "comment" .= comment sid ] -instance FromJSON Decimal where - parseJSON = withScientific "number" (return . realFracToDecimal 10 . toRational) - -instance ToJSON Decimal where - toJSON = Number . fromRational . toRational - -data OrderPrice = Market | Limit Decimal | Stop Decimal Decimal | StopMarket Decimal +data OrderPrice = Market | Limit Price | Stop Price Price | StopMarket Price deriving (Show, Eq) -decimal :: (RealFrac r) => r -> Decimal -decimal = realFracToDecimal 10 - instance FromJSON OrderPrice where parseJSON (String s) = when (s /= "market") (fail "If string, then should be 'market'") >> return Market - parseJSON (Number n) = return $ Limit $ decimal n + parseJSON a@(Number n) = do + price <- parseJSON a + return $ Limit price parseJSON (Object v) = do - triggerPrice <- v .: "trigger" :: Parser Double - execPrice <- v .: "execution" - case execPrice of - (String s) -> when (s /= "market") (fail "If string, then should be 'market'") >> return $ StopMarket (decimal triggerPrice) - (Number n) -> return $ Stop (decimal triggerPrice) (decimal n) - _ -> fail "Should be either number or 'market'" + triggerPrice <- v .: "trigger" + case triggerPrice of + a@(Number trpr) -> do + trprice <- parseJSON a + execPrice <- v .: "execution" + case execPrice of + (String s) -> if s /= "market" + then (fail "If string, then should be 'market'") + else return $ StopMarket trprice + (Number n) -> return $ Stop trprice (fromScientific n) + _ -> fail "Should be either number or 'market'" parseJSON _ = fail "OrderPrice" @@ -321,9 +310,9 @@ instance ToJSON Order where data Trade = Trade { tradeOrderId :: OrderId, - tradePrice :: Decimal, + tradePrice :: Price, tradeQuantity :: Integer, - tradeVolume :: Decimal, + tradeVolume :: Price, tradeVolumeCurrency :: T.Text, tradeOperation :: Operation, tradeAccount :: T.Text, @@ -384,6 +373,6 @@ data TickerInfo = TickerInfo { tiClass :: T.Text, tiBase :: Maybe TickerId, tiLotSize :: Integer, - tiTickSize :: Decimal + tiTickSize :: Price } deriving (Show, Eq) diff --git a/test/ArbitraryInstances.hs b/test/ArbitraryInstances.hs index 16ee1ed..5e5c361 100644 --- a/test/ArbitraryInstances.hs +++ b/test/ArbitraryInstances.hs @@ -15,7 +15,6 @@ import ATrade.Price as P import ATrade.Broker.Protocol import Data.Int -import Data.Decimal import Data.Scientific import Data.Time.Clock import Data.Time.Calendar @@ -42,9 +41,6 @@ instance Arbitrary Tick where instance Arbitrary DataType where arbitrary = toEnum <$> choose (1, 10) -instance Arbitrary Decimal where - arbitrary = realFracToDecimal 10 <$> (arbitrary :: Gen Scientific) - instance Arbitrary SignalId where arbitrary = SignalId <$> arbitrary <*> arbitrary <*> arbitrary diff --git a/test/TestBrokerProtocol.hs b/test/TestBrokerProtocol.hs index cdd6757..5605d6a 100644 --- a/test/TestBrokerProtocol.hs +++ b/test/TestBrokerProtocol.hs @@ -15,7 +15,6 @@ import ATrade.Broker.Protocol import ArbitraryInstances import Data.Aeson -import Data.Decimal import Data.Scientific properties = testGroup "Broker.Protocol" [ diff --git a/test/TestTypes.hs b/test/TestTypes.hs index 4047e36..f1e3b82 100644 --- a/test/TestTypes.hs +++ b/test/TestTypes.hs @@ -16,7 +16,6 @@ import ArbitraryInstances import Data.Aeson import Data.Aeson.Types -import Data.Decimal import Data.Scientific import Data.Text import Data.Time.Calendar