|
|
|
@ -16,12 +16,13 @@ import Data.Time.Calendar |
|
|
|
import Data.Time.Clock |
|
|
|
import Data.Time.Clock |
|
|
|
import Safe |
|
|
|
import Safe |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
import Hedgehog as HH |
|
|
|
|
|
|
|
import qualified Hedgehog.Gen as Gen |
|
|
|
|
|
|
|
import qualified Hedgehog.Range as Range |
|
|
|
|
|
|
|
|
|
|
|
import Test.Tasty |
|
|
|
import Test.Tasty |
|
|
|
|
|
|
|
import Test.Tasty.Hedgehog |
|
|
|
import Test.Tasty.HUnit |
|
|
|
import Test.Tasty.HUnit |
|
|
|
import Test.Tasty.QuickCheck as QC |
|
|
|
|
|
|
|
import Test.Tasty.SmallCheck as SC |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
import ArbitraryInstances |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
unitTests = testGroup "BarAggregator" [ |
|
|
|
unitTests = testGroup "BarAggregator" [ |
|
|
|
@ -36,7 +37,8 @@ unitTests = testGroup "BarAggregator" [ |
|
|
|
] |
|
|
|
] |
|
|
|
|
|
|
|
|
|
|
|
properties = testGroup "BarAggregator" [ |
|
|
|
properties = testGroup "BarAggregator" [ |
|
|
|
prop_allTicksInOneBar |
|
|
|
prop_allTicksInOneBar, |
|
|
|
|
|
|
|
prop_ticksInTwoBars |
|
|
|
] |
|
|
|
] |
|
|
|
|
|
|
|
|
|
|
|
testUnknownBarSeries :: TestTree |
|
|
|
testUnknownBarSeries :: TestTree |
|
|
|
@ -193,26 +195,61 @@ testNextBarAfterBarClose = testCase "Three bars (smaller timeframe) - next bar a |
|
|
|
barVolume = v } |
|
|
|
barVolume = v } |
|
|
|
|
|
|
|
|
|
|
|
prop_allTicksInOneBar :: TestTree |
|
|
|
prop_allTicksInOneBar :: TestTree |
|
|
|
prop_allTicksInOneBar = QC.testProperty "All ticks in one bar" $ QC.forAll (QC.choose (1, 86400)) $ \timeframe -> |
|
|
|
prop_allTicksInOneBar = testProperty "All ticks in one bar" $ property $ do |
|
|
|
QC.forAll (QC.listOf1 (genTick "TEST_TICKER" baseTime timeframe)) $ \ticks -> |
|
|
|
tf <- forAll $ Gen.integral (Range.constant 1 86400) |
|
|
|
let ticks' = sortOn timestamp ticks in |
|
|
|
ticks <- forAll $ Gen.list (Range.linear 1 100) (genTick "TEST_TICKER" baseTime tf) |
|
|
|
let (newbars, agg) = handleTicks ticks' (mkAggregator "TEST_TICKER" timeframe) in |
|
|
|
let ticks' = sortOn timestamp ticks |
|
|
|
null newbars && |
|
|
|
let (newbars, agg) = handleTicks ticks' (mkAggregator "TEST_TICKER" tf) |
|
|
|
((barHigh <$> currentBar "TEST_TICKER" agg) == Just (maximum $ value <$> ticks)) && |
|
|
|
(barHigh <$> currentBar "TEST_TICKER" agg) === Just (maximum $ value <$> ticks) |
|
|
|
((barLow <$> currentBar "TEST_TICKER" agg) == Just (minimum $ value <$> ticks)) && |
|
|
|
(barLow <$> currentBar "TEST_TICKER" agg) === Just (minimum $ value <$> ticks) |
|
|
|
((barOpen <$> currentBar "TEST_TICKER" agg) == (value <$> headMay ticks')) && |
|
|
|
(barOpen <$> currentBar "TEST_TICKER" agg) === (value <$> headMay ticks') |
|
|
|
((barClose <$> currentBar "TEST_TICKER" agg) == (value <$> lastMay ticks')) && |
|
|
|
(barClose <$> currentBar "TEST_TICKER" agg) === (value <$> lastMay ticks') |
|
|
|
((barVolume <$> currentBar "TEST_TICKER" agg) == Just (sum $ volume <$> ticks)) |
|
|
|
(barVolume <$> currentBar "TEST_TICKER" agg) === Just (sum $ volume <$> ticks) |
|
|
|
|
|
|
|
HH.assert $ null newbars |
|
|
|
|
|
|
|
|
|
|
|
where |
|
|
|
where |
|
|
|
genTick :: T.Text -> UTCTime -> Integer -> Gen Tick |
|
|
|
genTick :: T.Text -> UTCTime -> Integer -> Gen Tick |
|
|
|
genTick tickerId base tf = do |
|
|
|
genTick tickerId base tf = do |
|
|
|
difftime <- fromRational . toRational . picosecondsToDiffTime <$> choose (0, truncate 1e12 * tf) |
|
|
|
difftime <- fromRational . toRational . picosecondsToDiffTime <$> Gen.integral (Range.linear 0 (truncate 1e12 * tf)) |
|
|
|
val <- arbitrary |
|
|
|
val <- fromDouble <$> Gen.double (Range.exponentialFloat 0.00001 100) |
|
|
|
vol <- arbitrary `suchThat` (> 0) |
|
|
|
vol <- Gen.integral (Range.exponential 1 100) |
|
|
|
return $ Tick tickerId LastTradePrice (difftime `addUTCTime` baseTime) val vol |
|
|
|
return $ Tick tickerId LastTradePrice (difftime `addUTCTime` base) val vol |
|
|
|
mkAggregator tickerId tf = mkAggregatorFromBars (M.singleton tickerId (BarSeries tickerId (Timeframe tf) [])) [(0, 86400)] |
|
|
|
mkAggregator tickerId tf = mkAggregatorFromBars (M.singleton tickerId (BarSeries tickerId (Timeframe tf) [])) [(0, 86400)] |
|
|
|
|
|
|
|
|
|
|
|
currentBar tickerId agg = headMay =<< (bsBars <$> M.lookup tickerId (bars agg)) |
|
|
|
currentBar tickerId agg = headMay =<< (bsBars <$> M.lookup tickerId (bars agg)) |
|
|
|
baseTime = UTCTime (fromGregorian 1970 1 1) 0 |
|
|
|
baseTime = UTCTime (fromGregorian 1970 1 1) 0 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
prop_ticksInTwoBars :: TestTree |
|
|
|
|
|
|
|
prop_ticksInTwoBars = testProperty "Ticks in one bar, then in next bar" $ property $ do |
|
|
|
|
|
|
|
tf <- forAll $ Gen.integral (Range.constant 1 86400) |
|
|
|
|
|
|
|
ticks1 <- forAll $ Gen.list (Range.linear 1 100) (genTick "TEST_TICKER" (baseTime 0) tf) |
|
|
|
|
|
|
|
ticks2 <- forAll $ Gen.list (Range.linear 1 100) (genTick "TEST_TICKER" (baseTime $ secondsToDiffTime tf) tf) |
|
|
|
|
|
|
|
let ticks1' = sortOn timestamp ticks1 |
|
|
|
|
|
|
|
let ticks2' = sortOn timestamp ticks2 |
|
|
|
|
|
|
|
let (_, agg) = handleTicks ticks1' (mkAggregator "TEST_TICKER" tf) |
|
|
|
|
|
|
|
let ([newbar], agg') = handleTicks ticks2' agg |
|
|
|
|
|
|
|
barSecurity newbar === "TEST_TICKER" |
|
|
|
|
|
|
|
(barHigh newbar) === (maximum $ value <$> ticks1) |
|
|
|
|
|
|
|
(barLow newbar) === (minimum $ value <$> ticks1) |
|
|
|
|
|
|
|
(barOpen newbar) === (value . head $ ticks1') |
|
|
|
|
|
|
|
(barClose newbar) === (value . last $ ticks1') |
|
|
|
|
|
|
|
(barVolume newbar) === (sum $ volume <$> ticks1) |
|
|
|
|
|
|
|
(barHigh <$> currentBar "TEST_TICKER" agg') === Just (maximum $ value <$> ticks2) |
|
|
|
|
|
|
|
(barLow <$> currentBar "TEST_TICKER" agg') === Just (minimum $ value <$> ticks2) |
|
|
|
|
|
|
|
(barOpen <$> currentBar "TEST_TICKER" agg') === (value <$> headMay ticks2') |
|
|
|
|
|
|
|
(barClose <$> currentBar "TEST_TICKER" agg') === (value <$> lastMay ticks2') |
|
|
|
|
|
|
|
(barVolume <$> currentBar "TEST_TICKER" agg') === Just (sum $ volume <$> ticks2) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
where |
|
|
|
|
|
|
|
genTick :: T.Text -> UTCTime -> Integer -> Gen Tick |
|
|
|
|
|
|
|
genTick tickerId base tf = do |
|
|
|
|
|
|
|
difftime <- fromRational . toRational . picosecondsToDiffTime <$> Gen.integral (Range.linear 0 (truncate 1e12 * tf)) |
|
|
|
|
|
|
|
val <- fromDouble <$> Gen.double (Range.exponentialFloat 0.00001 100) |
|
|
|
|
|
|
|
vol <- Gen.integral (Range.exponential 1 100) |
|
|
|
|
|
|
|
return $ Tick tickerId LastTradePrice (difftime `addUTCTime` base) val vol |
|
|
|
|
|
|
|
mkAggregator tickerId tf = mkAggregatorFromBars (M.singleton tickerId (BarSeries tickerId (Timeframe tf) [])) [(0, 86400)] |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
currentBar tickerId agg = headMay =<< (bsBars <$> M.lookup tickerId (bars agg)) |
|
|
|
|
|
|
|
baseTime offset = UTCTime (fromGregorian 1970 1 1) offset |
|
|
|
|
|
|
|
|
|
|
|
|