Browse Source

Streaming parser

master
Denis Tereshkin 2 years ago
parent
commit
df568e6aa7
  1. 143
      bench/Bench.hs
  2. 79
      src/Transaq.hs
  3. 1056
      src/Transaq/Parsing.hs
  4. 10
      stack.yaml
  5. 16
      stack.yaml.lock
  6. 8
      test/Spec.hs
  7. 29
      test/Test/Transaq.hs
  8. 520
      test/Test/Transaq/Parsing.hs
  9. 55
      transaq-connector.cabal

143
bench/Bench.hs

@ -0,0 +1,143 @@ @@ -0,0 +1,143 @@
{-# LANGUAGE OverloadedRecordDot #-}
{-# LANGUAGE QuasiQuotes #-}
import Criterion.Main
import qualified Data.ByteString as BS
import Data.Text.Encoding
import Data.Text.Encoding.Error
import Safe
import Text.RawString.QQ
import Text.XML.Light.Input (parseXMLDoc)
import Transaq
import Transaq.Parsing
validClient =
[r|
<client id="FOO">
<type>TYPE</type>
<currency>CURRENCY</currency>
<market>MARKET</market>
<union>UNION</union>
<forts_acc>FORTS_ACC</forts_acc>
</client>
|] :: BS.ByteString
validCandleKinds =
[r|
<candlekinds>
<kind>
<id>4</id>
<period>600</period>
<name>M10</name>
</kind>
<kind>
<id>5</id>
<period>900</period>
<name>M15</name>
</kind>
</candlekinds>
|] :: BS.ByteString
validCandles =
[r|
<candles secid="12" period="1" status="0" board="foo" seccode="bar">
<candle date="05.01.2024 12:30:00.123" open="41.3" high="42" low="40" close="41.6" volume="1234" oi="78" />
<candle date="05.01.2024 12:30:01.123" open="40.3" high="44" low="40" close="41.8" volume="1234" />
<candle date="05.01.2024 12:30:02.123" open="40.3" high="44" low="40" close="41.8" volume="1234" />
<candle date="05.01.2024 12:30:03.123" open="40.3" high="44" low="40" close="41.8" volume="1234" />
</candles>
|] :: BS.ByteString
validServerStatus =
[r|<server_status id="22" connected="true" recover="true" server_tz="TZ" sys_ver="42" build="51" /> |] :: BS.ByteString
validMarkets =
[r|
<markets>
<market id="1">FOO</market>
<market id="2">BAR</market>
</markets>
|] :: BS.ByteString
validSecurities =
[r|
<securities>
<security secid="1" active="true">
<seccode>SECCODE</seccode>
<instrclass>CLASS</instrclass>
<board>BOARD</board>
<market>15</market>
<currency>CURRENCY</currency>
<shortname>SHORTNAME</shortname>
<decimals>3</decimals>
<minstep>0.1</minstep>
<lotsize>10</lotsize>
<lotdivider>1</lotdivider>
<point_cost>6.28</point_cost>
<optmask usecredit="yes" bymarket="no" nosplit="yes" fok="no" ioc="no" />
<sectype>SECTYPE</sectype>
<sec_tz>SECTZ</sec_tz>
<quotestype>1</quotestype>
<MIC>FOO</MIC>
<currencyid>CURRENCYID</currencyid>
</security>
</securities>
|] :: BS.ByteString
validAllTrades =
[r|
<alltrades>
<trade secid="1">
<seccode>SEC1</seccode>
<tradeno>14</tradeno>
<time>05.01.2024 19:34:18.234</time>
<board>BOARD</board>
<price>12.34</price>
<quantity>10</quantity>
<buysell>B</buysell>
<openinterest>100</openinterest>
<period>N</period>
</trade>
<trade secid="2">
<seccode>SEC2</seccode>
<tradeno>15</tradeno>
<time>05.01.2024 19:34:19.234</time>
<board>BOARD</board>
<price>12.35</price>
<quantity>11</quantity>
<buysell>S</buysell>
<openinterest>200</openinterest>
<period>N</period>
</trade>
</alltrades>
|] :: BS.ByteString
parseDom :: BS.ByteString -> Maybe TransaqResponse
parseDom d = parseXMLDoc (decodeUtf8With lenientDecode d) >>= fromXml
parseSax :: BS.ByteString -> Maybe TransaqResponse
parseSax = headMay . parseTransaqResponses
main = defaultMain
[
bgroup "DOM"
[
bench "<client>" $ whnf parseDom validClient
, bench "<candlekinds>" $ whnf parseDom validCandleKinds
, bench "<candles>" $ whnf parseDom validCandles
, bench "<server_status>" $ whnf parseDom validServerStatus
, bench "<markets>" $ whnf parseDom validMarkets
, bench "<securities>" $ whnf parseDom validSecurities
, bench "<alltrades>" $ whnf parseDom validAllTrades
]
, bgroup "SAX"
[
bench "<client>" $ whnf parseSax validClient
, bench "<candlekinds>" $ whnf parseSax validCandleKinds
, bench "<candles>" $ whnf parseSax validCandles
, bench "<server_status>" $ whnf parseSax validServerStatus
, bench "<markets>" $ whnf parseSax validMarkets
, bench "<securities>" $ whnf parseSax validSecurities
, bench "<alltrades>" $ whnf parseSax validAllTrades
]
]

79
src/Transaq.hs

@ -1,12 +1,16 @@ @@ -1,12 +1,16 @@
{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE DeriveAnyClass #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE DuplicateRecordFields #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE StandaloneDeriving #-}
{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE DeriveAnyClass #-}
{-# LANGUAGE DeriveFunctor #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE DerivingStrategies #-}
{-# LANGUAGE DuplicateRecordFields #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE StandaloneDeriving #-}
module Transaq
(
@ -72,28 +76,36 @@ module Transaq @@ -72,28 +76,36 @@ module Transaq
import Barbies
import Barbies.Bare
import Control.Applicative ((<|>))
import Control.Error.Util (hush)
import Control.Monad (void)
import Data.Attoparsec.Text (Parser, char, decimal, many',
maybeResult, parse, parseOnly,
skipSpace)
import Data.Decimal (DecimalRaw (..))
import Data.Functor.Identity (Identity (..))
import Data.Int (Int64)
import Data.Maybe (catMaybes, fromMaybe, mapMaybe,
maybeToList)
import qualified Data.Text as T
import Data.Time (fromGregorian)
import Data.Time.Clock (UTCTime (UTCTime))
import Control.Applicative ((<|>))
import Control.Error.Util (hush)
import Control.Monad (void, when)
import Control.Monad.State (State (..), gets, modify)
import Control.Monad.State.Class (MonadState (..))
import Data.Attoparsec.Text (Parser, char, decimal, many',
maybeResult, parse, parseOnly,
skipSpace)
import qualified Data.ByteString as BS
import Data.ByteString.Char8 (readInteger)
import Data.Decimal (DecimalRaw (..))
import Data.Functor.Identity (Identity (..))
import Data.Int (Int64)
import Data.Maybe (catMaybes, fromMaybe, mapMaybe,
maybeToList)
import qualified Data.Text as T
import Data.Text.Encoding (decodeUtf8With)
import Data.Text.Encoding.Error (lenientDecode)
import Data.Time (fromGregorian)
import Data.Time.Clock (UTCTime (UTCTime))
import GHC.Generics
import Text.Read (readMaybe)
import Text.XML.Light (Attr (..), CData (cdData),
Element (elName), Node (..), QName (..),
elChildren, findAttr, findChild,
onlyText, strContent, unode)
import Text.XML.Light.Output (showElement)
import Text.XML.Light.Types (Element (elContent), blank_name)
import Text.Read (readMaybe)
import Text.XML.Light (Attr (..), CData (cdData),
Element (elName), Node (..),
QName (..), elChildren, findAttr,
findChild, onlyText, strContent,
unode)
import Text.XML.Light.Output (showElement)
import Text.XML.Light.Types (Element (elContent), blank_name)
import Xeno.SAX (Process (..))
data Language = LanguageRu | LanguageEn
deriving (Show, Eq, Ord)
@ -558,7 +570,7 @@ data ClientDataB t f = @@ -558,7 +570,7 @@ data ClientDataB t f =
, cCurrency :: Wear t f T.Text
, cMarket :: Wear t f T.Text
, cUnion :: Wear t f T.Text
, cForts :: Wear t f (Maybe T.Text)
, cForts :: Maybe T.Text
} deriving (Generic)
type ClientData = ClientDataB Bare Identity
@ -743,7 +755,7 @@ data QuotationB t f = @@ -743,7 +755,7 @@ data QuotationB t f =
, qOpenPositions :: Maybe Int
, qLastPrice :: Maybe Double
, qQuantity :: Maybe Int
, qTimestamp :: Wear t f UTCTime
, qTimestamp :: Maybe UTCTime
, qValToday :: Maybe Double
} deriving (Generic)
@ -785,7 +797,7 @@ instance TransaqResponseC Element ResponseQuotations where @@ -785,7 +797,7 @@ instance TransaqResponseC Element ResponseQuotations where
let !qOpenPositions = childContent "openpositions" tag >>= readMaybe
let !qLastPrice = childContent "last" tag >>= readMaybe
let !qQuantity = childContent "quantity" tag >>= readMaybe
!qTimestamp <- childContent "time" tag >>= (parseTimestamp . T.pack)
let !qTimestamp = childContent "time" tag >>= (parseTimestamp . T.pack)
let !qValToday = childContent "valtoday" tag >>= readMaybe
pure $ Just (Quotation {..} :: Quotation)
@ -1105,3 +1117,4 @@ instance TransaqResponseC Element TransaqResponse where @@ -1105,3 +1117,4 @@ instance TransaqResponseC Element TransaqResponse where
"orders" -> TransaqResponseOrders <$> fromXml root
"trades" -> TransaqResponseTrades <$> fromXml root
_ -> Nothing

1056
src/Transaq/Parsing.hs

File diff suppressed because it is too large Load Diff

10
stack.yaml

@ -17,7 +17,7 @@ @@ -17,7 +17,7 @@
#
# resolver: ./custom-snapshot.yaml
# resolver: https://example.com/snapshots/2018-01-01.yaml
resolver: lts-20.18
resolver: lts-20.26
# User packages to be built.
# Various formats can be used as shown in the example below.
@ -51,6 +51,7 @@ extra-deps: @@ -51,6 +51,7 @@ extra-deps:
- typerep-map-0.5.0.0@sha256:34f1ba9b268a6d52e26ae460011a5571e8099b50a3f4a7c8db25dd8efe3be8ee,4667
# Override default flag values for local packages and extra-deps
# flags: {}
flags:
@ -61,8 +62,8 @@ flags: @@ -61,8 +62,8 @@ flags:
# extra-package-dbs: []
# Control whether we use the GHC we find on the path
# system-ghc: true
#
system-ghc: true
# Require a specific version of stack, using version ranges
# require-stack-version: -any # Default
# require-stack-version: ">=2.7"
@ -73,7 +74,8 @@ flags: @@ -73,7 +74,8 @@ flags:
#
# Extra directories used by stack for building
# extra-include-dirs: [/path/to/dir]
# extra-lib-dirs: [/path/to/dir]
extra-lib-dirs:
- lib
#
# Allow a newer minor version of GHC than the snapshot specifies
# compiler-check: newer-minor

16
stack.yaml.lock

@ -7,34 +7,34 @@ packages: @@ -7,34 +7,34 @@ packages:
- completed:
hackage: datetime-0.3.1@sha256:7e275bd0ce7a2f66445bedfa0006abaf4d41af4c2204c3f8004c17eab5480e74,1534
pantry-tree:
size: 334
sha256: d41d182c143676464cb1774f0b7777e870ddeaf8b6cd5fee6ff0114997a1f504
size: 334
original:
hackage: datetime-0.3.1
- completed:
hackage: co-log-0.5.0.0@sha256:a7e84650eaef7eba2d59ee7664309e79317a7ca67011abedf971f0e6bd6475bb,5448
pantry-tree:
size: 1043
sha256: 33b838c07c8b7e70b2e82bddc889bb1e6386d7e12a9d1593c0b4b263b1fcb925
size: 1043
original:
hackage: co-log-0.5.0.0
- completed:
hackage: chronos-1.1.5@sha256:ca35be5fdbbb384414226b4467c6d1c8b44defe59a9c8a3af32c1c5fb250c781,3830
pantry-tree:
size: 581
sha256: 329bf39a05362a9c1f507a4a529725c757208843b562c55e0b7c88538dc3160f
size: 581
original:
hackage: chronos-1.1.5@sha256:ca35be5fdbbb384414226b4467c6d1c8b44defe59a9c8a3af32c1c5fb250c781,3830
- completed:
hackage: typerep-map-0.5.0.0@sha256:34f1ba9b268a6d52e26ae460011a5571e8099b50a3f4a7c8db25dd8efe3be8ee,4667
pantry-tree:
size: 1487
sha256: ca5565de307d260dc67f6dae0d4d33eee42a3238183461569b5142ceb909c91d
size: 1487
original:
hackage: typerep-map-0.5.0.0@sha256:34f1ba9b268a6d52e26ae460011a5571e8099b50a3f4a7c8db25dd8efe3be8ee,4667
snapshots:
- completed:
size: 649606
url: https://raw.githubusercontent.com/commercialhaskell/stackage-snapshots/master/lts/20/18.yaml
sha256: 9fa4bece7acfac1fc7930c5d6e24606004b09e80aa0e52e9f68b148201008db9
original: lts-20.18
sha256: 5a59b2a405b3aba3c00188453be172b85893cab8ebc352b1ef58b0eae5d248a2
size: 650475
url: https://raw.githubusercontent.com/commercialhaskell/stackage-snapshots/master/lts/20/26.yaml
original: lts-20.26

8
test/Spec.hs

@ -1,10 +1,11 @@ @@ -1,10 +1,11 @@
import Debug.EventCounters (initEventCounters)
import System.Metrics (newStore)
import Debug.EventCounters (initEventCounters)
import System.Metrics (newStore)
import Test.Tasty
import qualified Test.FSM
import qualified Test.TickTable
import qualified Test.Transaq
import qualified Test.Transaq.Parsing
main :: IO ()
main = do
@ -16,4 +17,5 @@ unitTests :: TestTree @@ -16,4 +17,5 @@ unitTests :: TestTree
unitTests = testGroup "Unit Tests"
[ Test.TickTable.unitTests
, Test.FSM.unitTests
, Test.Transaq.unitTests ]
, Test.Transaq.unitTests
, Test.Transaq.Parsing.unitTests ]

29
test/Test/Transaq.hs

@ -6,16 +6,25 @@ module Test.Transaq @@ -6,16 +6,25 @@ module Test.Transaq
unitTests
) where
import Control.Monad.State (execState)
import Data.AEq ((~==))
import qualified Data.ByteString as BS
import qualified Data.Text as T
import Test.Tasty
import Test.Tasty.HUnit (assertBool, testCase, (@=?))
import Text.RawString.QQ
import Text.XML.Light.Input (parseXMLDoc)
import Transaq
import Xeno.SAX (process)
unitTests :: TestTree
unitTests = testGroup "Parsing"
[
unitTestsXml
]
unitTestsXml :: TestTree
unitTestsXml = testGroup "Parsing via haskell-xml"
[
testParseResult
, testParseCandles
@ -41,18 +50,17 @@ testParseResult = testGroup "Parsing result" @@ -41,18 +50,17 @@ testParseResult = testGroup "Parsing result"
, testParseResultFailure
]
where
resultSuccess = [r|<result success="true />"|] :: T.Text
resultSuccess = [r|<result success="true" />|] :: T.Text
testParseResultSuccess = testCase "Parse success result" $ do
let (Just (ResponseSuccess Nothing)) = (parseXMLDoc resultSuccess >>= fromXml :: Maybe ResponseResult)
return ()
resultSuccessWithTransactionId = [r|<result success="true" transactionid="12"|] :: T.Text
(Just (ResponseSuccess Nothing)) @=? (parseXMLDoc resultSuccess >>= fromXml :: Maybe ResponseResult)
resultSuccessWithTransactionId = [r|<result success="true" transactionid="12" />|] :: T.Text
testParseResultSuccessWithTransactionId = testCase "Parse success result with transaction ID" $ do
let (Just (ResponseSuccess (Just 12))) = (parseXMLDoc resultSuccessWithTransactionId >>= fromXml :: Maybe ResponseResult)
return ()
resultFailure = [r|<result success="false />"|] :: T.Text
testParseResultFailure = testCase "Parse success result" $ do
let (Just (ResponseFailure _)) = (parseXMLDoc resultFailure >>= fromXml :: Maybe ResponseResult)
return ()
(Just (ResponseSuccess (Just 12))) @=? (parseXMLDoc resultSuccessWithTransactionId >>= fromXml :: Maybe ResponseResult)
resultFailure = [r|<result success="false" />|] :: T.Text
testParseResultFailure = testCase "Parse failure result" $ do
(Just (ResponseFailure "")) @=? (parseXMLDoc resultFailure >>= fromXml :: Maybe ResponseResult)
testParseCandles :: TestTree
testParseCandles = testCase "Parse ResponseCandles - valid XML" $ do
@ -508,3 +516,4 @@ testParseTrades = testCase "Parse ResponseTrades - valid XML (full)" $ do @@ -508,3 +516,4 @@ testParseTrades = testCase "Parse ResponseTrades - valid XML (full)" $ do
</trade>
</trades>
|] :: T.Text

520
test/Test/Transaq/Parsing.hs

@ -0,0 +1,520 @@ @@ -0,0 +1,520 @@
{-# LANGUAGE OverloadedRecordDot #-}
{-# LANGUAGE QuasiQuotes #-}
module Test.Transaq.Parsing
(
unitTests
) where
import Control.Monad.State (execState)
import Data.AEq ((~==))
import qualified Data.ByteString as BS
import Test.Tasty (TestTree, testGroup)
import Test.Tasty.HUnit (assertBool, assertFailure, testCase,
(@=?))
import Text.RawString.QQ
import Transaq (AllTradesTradeB (..), CandleB (..),
CandleKind, CandleKindB (..),
ClientDataB (..), ConnectionState (..),
MarketInfoB (..), OrderNotificationB (..),
OrderStatus (..), QuotationB (..),
QuoteB (..), ResponseAllTrades (..),
ResponseCandleKinds (..),
ResponseCandlesB (..),
ResponseCandlesStatus (..),
ResponseClient (..), ResponseMarkets (..),
ResponseOrders (..),
ResponseQuotations (..),
ResponseQuotes (..), ResponseResult (..),
ResponseSecInfoB (..),
ResponseSecurities (..),
ResponseServerStatusB (..),
ResponseTrades (..), SecurityB (..),
SecurityId (..), TradeDirection (..),
TradeNotificationB (..),
TradingPeriod (..), TransaqResponse (..))
import Transaq.Parsing
import Xeno.SAX (process)
unitTests :: TestTree
unitTests = testGroup "Parsing"
[
unitTestsStreaming
]
unitTestsStreaming :: TestTree
unitTestsStreaming = testGroup "Parsing via xeno-sax"
[
testParseResultStreaming
, testParseClientStreaming
, testParseCandleKindsStreaming
, testParseCandlesStreaming
, testParseServerStatusStreaming
, testParseMarketsStreaming
, testParseSecuritiesStreaming
, testParseSecInfoStreaming
, testParseQuotationsStreaming
, testParseAllTradesStreaming
, testParseQuotesStreaming
, testParseOrdersStreaming
, testParseTradesStreaming
]
testParseResultStreaming :: TestTree
testParseResultStreaming = testGroup "Parsing result"
[
testParseResultSuccess
, testParseResultSuccessWithTransactionId
, testParseResultFailure
]
where
resultSuccess = [r|<result success="true" />|] :: BS.ByteString
testParseResultSuccess = testCase "Parse success result" $ do
[TransaqResponseResult (ResponseSuccess Nothing)] @=?
parseTransaqResponses resultSuccess
resultSuccessWithTransactionId = [r|<result success="true" transactionid="12" />|] :: BS.ByteString
testParseResultSuccessWithTransactionId = testCase "Parse success result with transaction ID" $ do
[TransaqResponseResult (ResponseSuccess (Just 12))] @=?
parseTransaqResponses resultSuccessWithTransactionId
resultFailure = [r|<result success="false" />|] :: BS.ByteString
testParseResultFailure = testCase "Parse failure result" $ do
[TransaqResponseResult (ResponseFailure "")] @=?
parseTransaqResponses resultFailure
testParseClientStreaming :: TestTree
testParseClientStreaming = testCase "Parse ResponseClient - valid XML" $ do
case parseTransaqResponses validClient of
[TransaqResponseClient (ResponseClient clientData)] -> do
"FOO" @=? cClientId clientData
"TYPE" @=? cType clientData
"CURRENCY" @=? cCurrency clientData
"MARKET" @=? cMarket clientData
"UNION" @=? cUnion clientData
Just "FORTS_ACC" @=? cForts clientData
_ -> assertFailure "Invalid <client> parse"
where
validClient =
[r|
<client id="FOO">
<type>TYPE</type>
<currency>CURRENCY</currency>
<market>MARKET</market>
<union>UNION</union>
<forts_acc>FORTS_ACC</forts_acc>
</client>
|] :: BS.ByteString
testParseCandleKindsStreaming :: TestTree
testParseCandleKindsStreaming = testCase "Parse CandleKind - valid XML" $ do
let candleKinds = parseTransaqResponses validCandleKinds
let [TransaqResponseCandleKinds (ResponseCandleKinds [m10, m15])] = candleKinds
4 @=? kCandleKindId m10
600 @=? kPeriod m10
"M10" @=? kName m10
5 @=? kCandleKindId m15
900 @=? kPeriod m15
"M15" @=? kName m15
where
validCandleKinds =
[r|
<candlekinds>
<kind>
<id>4</id>
<period>600</period>
<name>M10</name>
</kind>
<kind>
<id>5</id>
<period>900</period>
<name>M15</name>
</kind>
</candlekinds>
|] :: BS.ByteString
testParseCandlesStreaming :: TestTree
testParseCandlesStreaming = testCase "Parse Candles - valid XML" $ do
let [TransaqResponseCandles responseCandles] = parseTransaqResponses validCandles
SecurityId "foo" "bar" @=? cSecurity responseCandles
1 @=? cPeriodId responseCandles
StatusEndOfHistory @=? cStatus responseCandles
1 @=? (length . cCandles) responseCandles
let candle = head . cCandles $ responseCandles
assertBool "Candle open mismatch" (41.3 ~== cOpen candle)
assertBool "Candle high mismatch" (42 ~== cHigh candle)
assertBool "Candle low mismatch" (40 ~== cLow candle)
assertBool "Candle close mismatch" (41.6 ~== cClose candle)
1234 @=? cVolume candle
78 @=? cOpenInterest candle
where
validCandles =
[r|
<candles secid="12" period="1" status="0" board="foo" seccode="bar">
<candle date="05.01.2024 12:30:00.123" open="41.3" high="42" low="40" close="41.6" volume="1234" oi="78" />
</candles>
|] :: BS.ByteString
testParseServerStatusStreaming :: TestTree
testParseServerStatusStreaming = testCase "Parse ServerStatus - valid XML" $ do
let [TransaqResponseServerStatus serverStatus] = parseTransaqResponses validServerStatus
Just 22 @=? serverId serverStatus
Connected @=? state serverStatus
Just True @=? recover serverStatus
Just "TZ" @=? serverTimezone serverStatus
Just 42 @=? systemVersion serverStatus
Just 51 @=? build serverStatus
where
validServerStatus =
[r|<server_status id="22" connected="true" recover="true" server_tz="TZ" sys_ver="42" build="51" /> |] :: BS.ByteString
testParseMarketsStreaming :: TestTree
testParseMarketsStreaming = testCase "Parse ResponseMarkets - valid XML" $ do
let markets = parseTransaqResponses validMarkets
let [TransaqResponseMarkets (ResponseMarkets [foo, bar])] = markets
1 @=? marketId foo
"FOO" @=? marketName foo
2 @=? marketId bar
"BAR" @=? marketName bar
where
validMarkets =
[r|
<markets>
<market id="1">FOO</market>
<market id="2">BAR</market>
</markets>
|] :: BS.ByteString
testParseSecuritiesStreaming :: TestTree
testParseSecuritiesStreaming = testCase "Parse ResponseSecurities - valid XML" $ do
let [TransaqResponseSecurities (ResponseSecurities securities)] = parseTransaqResponses validSecurities
1 @=? length securities
let sec = head securities
"SECCODE" @=? sSeccode sec
"CLASS" @=? sInstrClass sec
"BOARD" @=? sBoard sec
"15" @=? sMarket sec
"CURRENCY" @=? sCurrency sec
"SHORTNAME" @=? sShortName sec
3 @=? sDecimals sec
assertBool "Minstep is not valid" (0.1 ~== sMinStep sec)
10 @=? sLotSize sec
1 @=? sLotDivider sec
assertBool "Point cost is not valid" (6.28 ~== sPointCost sec)
"SECTYPE" @=? sSecType sec
where
validSecurities =
[r|
<securities>
<security secid="1" active="true">
<seccode>SECCODE</seccode>
<instrclass>CLASS</instrclass>
<board>BOARD</board>
<market>15</market>
<currency>CURRENCY</currency>
<shortname>SHORTNAME</shortname>
<decimals>3</decimals>
<minstep>0.1</minstep>
<lotsize>10</lotsize>
<lotdivider>1</lotdivider>
<point_cost>6.28</point_cost>
<optmask usecredit="yes" bymarket="no" nosplit="yes" fok="no" ioc="no" />
<sectype>SECTYPE</sectype>
<sec_tz>SECTZ</sec_tz>
<quotestype>1</quotestype>
<MIC>FOO</MIC>
<currencyid>CURRENCYID</currencyid>
</security>
</securities>
|] :: BS.ByteString
testParseSecInfoStreaming :: TestTree
testParseSecInfoStreaming = testCase "Parse ResponseSecInfo - valid XML" $ do
let [TransaqResponseSecInfo sec] = parseTransaqResponses validSecInfo
"SECNAME" @=? secName sec
"SECCODE" @=? sec.secCode
44 @=? market sec
"PNAME" @=? pname sec
assertBool "Clearing price is not valid" (12.34 ~== clearingPrice sec)
assertBool "Min price is not valid" (10 ~== minPrice sec)
assertBool "Max price is not valid" (20 ~== maxPrice sec)
assertBool "Point cost is not valid" (6.28 ~== pointCost sec)
where
validSecInfo =
[r|
<sec_info secid="4">
<secname>SECNAME</secname>
<seccode>SECCODE</seccode>
<market>44</market>
<pname>PNAME</pname>
<clearing_price>12.34</clearing_price>
<minprice>10</minprice>
<maxprice>20</maxprice>
<point_cost>6.28</point_cost>
</sec_info>
|] :: BS.ByteString
testParseQuotationsStreaming :: TestTree
testParseQuotationsStreaming = testCase "Parse ResponseQuotations - valid XML (full)" $ do
let [TransaqResponseQuotations (ResponseQuotations quotations)] = parseTransaqResponses validQuotations
1 @=? length quotations
let q = head quotations
1 @=? qSecId q
"BOARD" @=? qBoard q
"SECCODE" @=? qSeccode q
let (Just open') = qOpen q
assertBool "Open is not valid" (12.34 ~== open')
let (Just waprice') = qWaprice q
assertBool "WA price is not valid" (13.8 ~== waprice')
Just 40 @=? qBidDepth q
Just 140 @=? qBidDepthT q
Just 10 @=? qNumBids q
let (Just bid') = qBid q
assertBool "Bid is not valid" (11.01 ~== bid')
Just 50 @=? qOfferDepth q
Just 150 @=? qOfferDepthT q
Just 15 @=? qNumOffers q
let (Just offer') = qOffer q
assertBool "Offer is not valid" (11.05 ~== offer')
Just 1000 @=? qNumTrades q
Just 50000 @=? qVolToday q
Just 1000 @=? qOpenPositions q
let (Just last') = qLastPrice q
assertBool "Last price is not valid" (11.03 ~== last')
Just 8 @=? qQuantity q
-- TODO check time
let (Just valtoday') = qValToday q
assertBool "Val today is not valid" (12345678 ~== valtoday')
where
validQuotations =
[r|
<quotations>
<quotation secid="1">
<board>BOARD</board>
<seccode>SECCODE</seccode>
<open>12.34</open>
<waprice>13.8</waprice>
<biddepth>40</biddepth>
<biddeptht>140</biddeptht>
<numbids>10</numbids>
<bid>11.01</bid>
<offerdepth>50</offerdepth>
<offerdeptht>150</offerdeptht>
<numoffers>15</numoffers>
<offer>11.05</offer>
<numtrades>1000</numtrades>
<voltoday>50000</voltoday>
<openpositions>1000</openpositions>
<last>11.03</last>
<quantity>8</quantity>
<time>05.01.2024 19:34:18.234</time>
<valtoday>12345678</valtoday>
</quotation>
</quotations>
|] :: BS.ByteString
testParseAllTradesStreaming :: TestTree
testParseAllTradesStreaming = testCase "Parse ResponseAllTrades - valid XML (full)" $ do
let [TransaqResponseAllTrades (ResponseAllTrades trades)] = parseTransaqResponses validAllTrades
2 @=? length trades
let [t1, t2] = trades
1 @=? attSecId t1
"SEC1" @=? attSecCode t1
14 @=? attTradeNo t1
-- TODO check time
"BOARD" @=? attBoard t1
assertBool "Price is not valid" (12.34 ~== attPrice t1)
10 @=? attQuantity t1
Buy @=? attBuysell t1
100 @=? attOpenInterest t1
PeriodNormal @=? attPeriod t1
2 @=? attSecId t2
"SEC2" @=? attSecCode t2
15 @=? attTradeNo t2
-- TODO check time
"BOARD" @=? attBoard t2
assertBool "Price is not valid" (12.35 ~== attPrice t2)
11 @=? attQuantity t2
Sell @=? attBuysell t2
200 @=? attOpenInterest t2
PeriodNormal @=? attPeriod t2
where
validAllTrades =
[r|
<alltrades>
<trade secid="1">
<seccode>SEC1</seccode>
<tradeno>14</tradeno>
<time>05.01.2024 19:34:18.234</time>
<board>BOARD</board>
<price>12.34</price>
<quantity>10</quantity>
<buysell>B</buysell>
<openinterest>100</openinterest>
<period>N</period>
</trade>
<trade secid="2">
<seccode>SEC2</seccode>
<tradeno>15</tradeno>
<time>05.01.2024 19:34:19.234</time>
<board>BOARD</board>
<price>12.35</price>
<quantity>11</quantity>
<buysell>S</buysell>
<openinterest>200</openinterest>
<period>N</period>
</trade>
</alltrades>
|] :: BS.ByteString
testParseQuotesStreaming :: TestTree
testParseQuotesStreaming = testCase "Parse ResponseQuotes - valid XML (full)" $ do
let [TransaqResponseQuotes (ResponseQuotes quotes)] = parseTransaqResponses validQuotes
2 @=? length quotes
let [q1, q2] = quotes
1 @=? q1.secId
"BOARD" @=? q1.board
assertBool "Price is not valid" (12.34 ~== q1.price)
Just "SOURCE" @=? q1.source
Just 10 @=? q1.buy
Nothing @=? q1.sell
2 @=? q2.secId
"BOARD" @=? q2.board
assertBool "Price is not valid" (12.35 ~== q2.price)
Just "SOURCE" @=? q2.source
Nothing @=? q2.buy
Just 11 @=? q2.sell
where
validQuotes =
[r|
<quotes>
<quote secid="1">
<board>BOARD</board>
<seccode>SEC1</seccode>
<price>12.34</price>
<source>SOURCE</source>
<buy>10</buy>
</quote>
<quote secid="2">
<board>BOARD</board>
<seccode>SEC2</seccode>
<price>12.35</price>
<source>SOURCE</source>
<sell>11</sell>
</quote>
</quotes>
|] :: BS.ByteString
testParseOrdersStreaming :: TestTree
testParseOrdersStreaming = testCase "Parse ResponseOrders - valid XML (full)" $ do
let [TransaqResponseOrders (ResponseOrders orders)] = parseTransaqResponses validOrders
1 @=? length orders
let order = head orders
12 @=? oTransactionId order
42 @=? oOrderNo order
1 @=? oSecId order
"BOARD" @=? oBoard order
"SEC1" @=? oSecCode order
Just "CLIENT" @=? oClient order
Just "UNION" @=? oUnion order
Just OrderActive @=? oStatus order
Just Buy @=? oBuysell order
let Just price' = oPrice order
assertBool "Price is not valid" (12.34 ~== price')
Just 12 @=? oQuantity order
where
validOrders =
[r|
<orders>
<order transactionid="12">
<orderno>42</orderno>
<secid>1</secid>
<board>BOARD</board>
<seccode>SEC1</seccode>
<client>CLIENT</client>
<union>UNION</union>
<status>active</status>
<buysell>B</buysell>
<time>05.01.2024 19:34:19.234</time>
<price>12.34</price>
<quantity>12</quantity>
</order>
</orders>
|] :: BS.ByteString
testParseTradesStreaming :: TestTree
testParseTradesStreaming = testCase "Parse ResponseTrades - valid XML (full)" $ do
let [TransaqResponseTrades (ResponseTrades trades)] = parseTransaqResponses validTrades
1 @=? length trades
let trade = head trades
1 @=? tSecId trade
12 @=? tTradeNo trade
42 @=? tOrderNo trade
"BOARD" @=? tBoard trade
"SEC1" @=? tSecCode trade
"CLIENT" @=? tClient trade
"UNION" @=? tUnion trade
Buy @=? tBuysell trade
-- TODO check time
True @=? (123.4 ~== tValue trade)
True @=? (0.5 ~== tComission trade)
10 @=? tQuantity trade
True @=? (12.34 ~== tPrice trade)
where
validTrades =
[r|
<trades>
<trade>
<secid>1</secid>
<tradeno>12</tradeno>
<orderno>42</orderno>
<board>BOARD</board>
<seccode>SEC1</seccode>
<client>CLIENT</client>
<union>UNION</union>
<buysell>B</buysell>
<time>05.01.2024 19:34:19.234</time>
<value>123.40</value>
<comission>0.5</comission>
<quantity>10</quantity>
<price>12.34</price>
</trade>
</trades>
|] :: BS.ByteString

55
transaq-connector.cabal

@ -20,6 +20,7 @@ executable transaq-connector @@ -20,6 +20,7 @@ executable transaq-connector
other-modules: Paths_transaq_connector
, Config
, Transaq
, Transaq.Parsing
, TickerInfoServer
, HistoryProviderServer
, Version
@ -65,6 +66,7 @@ executable transaq-connector @@ -65,6 +66,7 @@ executable transaq-connector
, th-printf
, barbies
, xeno
, megaparsec
extra-lib-dirs: lib
ghc-options: -Wall
-Wcompat
@ -77,6 +79,7 @@ executable transaq-connector @@ -77,6 +79,7 @@ executable transaq-connector
-Wredundant-constraints
-threaded -rtsopts -with-rtsopts=-N
if os(windows)
extra-lib-dirs: lib
extra-libraries: txmlconnector64
other-modules: Win32.TXML
else
@ -89,11 +92,13 @@ test-suite transaq-connector-test @@ -89,11 +92,13 @@ test-suite transaq-connector-test
other-modules: Test.TickTable
, Test.FSM
, Test.Transaq
, Test.Transaq.Parsing
, TXMLConnector
, TXMLConnector.Internal
, FSM
, TickTable
, Transaq
, Transaq.Parsing
, Commissions
, Config
, TXML
@ -136,6 +141,56 @@ test-suite transaq-connector-test @@ -136,6 +141,56 @@ test-suite transaq-connector-test
, barbies
, raw-strings-qq
, ieee754
, megaparsec
default-extensions: OverloadedStrings
, MultiWayIf
, MultiParamTypeClasses
benchmark parsing-benchmark
type: exitcode-stdio-1.0
hs-source-dirs: bench src
main-is: Bench.hs
other-modules: Transaq
, Transaq.Parsing
build-depends: base
, containers
, libatrade
, stm
, criterion
, dhall
, eventcounters
, libatrade == 0.15.0.0
, text
, transformers
, co-log
, zeromq4-haskell
, aeson
, bytestring
, BoundedChan
, containers
, xml
, Decimal
, time
, attoparsec
, stm
, extra
, errors
, mtl
, vector
, binary
, bimap
, deque
, network-uri
, ekg-statsd
, ekg-core
, slave-thread
, xeno
, barbies
, raw-strings-qq
, ieee754
, megaparsec
, safe
default-extensions: OverloadedStrings
, MultiWayIf
, MultiParamTypeClasses

Loading…
Cancel
Save