Browse Source

Initial commit

master
Denis Tereshkin 4 years ago
commit
fefe98e7cb
  1. 2
      .gitignore
  2. 3
      ChangeLog.md
  3. 30
      LICENSE
  4. 1
      README.md
  5. 2
      Setup.hs
  6. 4
      app/Main.hs
  7. 96
      exchange-simulator.cabal
  8. 64
      package.yaml
  9. 61
      src/ATrade/ESim/Core.hs
  10. 74
      stack.yaml
  11. 34
      stack.yaml.lock
  12. 11
      test/Spec.hs
  13. 59
      test/TestLOB.hs

2
.gitignore vendored

@ -0,0 +1,2 @@ @@ -0,0 +1,2 @@
.stack-work/
*~

3
ChangeLog.md

@ -0,0 +1,3 @@ @@ -0,0 +1,3 @@
# Changelog for exchange-simulator
## Unreleased changes

30
LICENSE

@ -0,0 +1,30 @@ @@ -0,0 +1,30 @@
Copyright Author name here (c) 2021
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following
disclaimer in the documentation and/or other materials provided
with the distribution.
* Neither the name of Author name here nor the names of other
contributors may be used to endorse or promote products derived
from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

1
README.md

@ -0,0 +1 @@ @@ -0,0 +1 @@
# exchange-simulator

2
Setup.hs

@ -0,0 +1,2 @@ @@ -0,0 +1,2 @@
import Distribution.Simple
main = defaultMain

4
app/Main.hs

@ -0,0 +1,4 @@ @@ -0,0 +1,4 @@
module Main where
main :: IO ()
main = error "Not implemented"

96
exchange-simulator.cabal

@ -0,0 +1,96 @@ @@ -0,0 +1,96 @@
cabal-version: 1.12
-- This file has been generated from package.yaml by hpack version 0.34.4.
--
-- see: https://github.com/sol/hpack
name: exchange-simulator
version: 0.1.0.0
description: Please see the README on GitHub at <https://github.com/githubuser/exchange-simulator#readme>
homepage: https://github.com/asakul/exchange-simulator#readme
bug-reports: https://github.com/asakul/exchange-simulator/issues
author: Denis Tereshkin
maintainer: denis@kasan.ws
copyright: 2021 Denis Tereshkin
license: BSD3
license-file: LICENSE
build-type: Simple
extra-source-files:
README.md
ChangeLog.md
source-repository head
type: git
location: https://github.com/asakul/exchange-simulator
library
exposed-modules:
ATrade.ESim.Core
other-modules:
Paths_exchange_simulator
hs-source-dirs:
src
default-extensions:
OverloadedStrings
ImportQualifiedPost
FlexibleContexts
FlexibleInstances
MultiParamTypeClasses
RankNTypes
LambdaCase
build-depends:
base >=4.7 && <5
, containers
, libatrade
default-language: Haskell2010
executable exchange-simulator-exe
main-is: Main.hs
other-modules:
Paths_exchange_simulator
hs-source-dirs:
app
default-extensions:
OverloadedStrings
ImportQualifiedPost
FlexibleContexts
FlexibleInstances
MultiParamTypeClasses
RankNTypes
LambdaCase
ghc-options: -threaded -rtsopts -with-rtsopts=-N
build-depends:
base >=4.7 && <5
, containers
, exchange-simulator
, libatrade
default-language: Haskell2010
test-suite exchange-simulator-test
type: exitcode-stdio-1.0
main-is: Spec.hs
other-modules:
TestLOB
Paths_exchange_simulator
hs-source-dirs:
test
default-extensions:
OverloadedStrings
ImportQualifiedPost
FlexibleContexts
FlexibleInstances
MultiParamTypeClasses
RankNTypes
LambdaCase
ghc-options: -threaded -rtsopts -with-rtsopts=-N
build-depends:
base >=4.7 && <5
, containers
, exchange-simulator
, hedgehog
, libatrade
, tasty
, tasty-hedgehog
, tasty-hunit
, tasty-quickcheck
default-language: Haskell2010

64
package.yaml

@ -0,0 +1,64 @@ @@ -0,0 +1,64 @@
name: exchange-simulator
version: 0.1.0.0
github: "asakul/exchange-simulator"
license: BSD3
author: "Denis Tereshkin"
maintainer: "denis@kasan.ws"
copyright: "2021 Denis Tereshkin"
extra-source-files:
- README.md
- ChangeLog.md
# Metadata used when publishing your package
# synopsis: Short description of your package
# category: Web
# To avoid duplicated efforts in documentation and dealing with the
# complications of embedding Haddock markup inside cabal files, it is
# common to point users to the README.md file.
description: Please see the README on GitHub at <https://github.com/githubuser/exchange-simulator#readme>
dependencies:
- base >= 4.7 && < 5
- libatrade
- containers
library:
source-dirs: src
default-extensions:
- OverloadedStrings
- ImportQualifiedPost
- FlexibleContexts
- FlexibleInstances
- MultiParamTypeClasses
- RankNTypes
- LambdaCase
executables:
exchange-simulator-exe:
main: Main.hs
source-dirs: app
ghc-options:
- -threaded
- -rtsopts
- -with-rtsopts=-N
dependencies:
- exchange-simulator
tests:
exchange-simulator-test:
main: Spec.hs
source-dirs: test
ghc-options:
- -threaded
- -rtsopts
- -with-rtsopts=-N
dependencies:
- exchange-simulator
- tasty
- tasty-quickcheck
- tasty-hunit
- tasty-hedgehog
- hedgehog

61
src/ATrade/ESim/Core.hs

@ -0,0 +1,61 @@ @@ -0,0 +1,61 @@
module ATrade.ESim.Core (
addToLob,
LimitOrderBook,
PriceTick(..),
Volume(..),
MatchingAction(..),
emptyLob,
executeMatchingActions
) where
import ATrade.Types
(Operation (Buy, Sell), Order (orderId, orderOperation, orderPrice, orderQuantity), OrderId, Price, TickerId)
import Data.Map.Strict qualified as M
import Data.Maybe (fromMaybe)
import Data.Sequence (Seq, empty, (|>))
-- | Represents price in tick size units
newtype PriceTick
= PriceTick { unPriceTick :: Int }
deriving (Eq, Ord, Show)
newtype Volume
= Volume { unVolume :: Int }
deriving (Eq, Ord, Show)
data LimitOrderBook
= LimitOrderBook
{ lobTickSize :: Price
, lobBids :: M.Map PriceTick (Seq (OrderId, Volume)) -- TODO I'll mix them up eventually, need some tags at type-level
, lobOffers :: M.Map PriceTick (Seq (OrderId, Volume))
, lobTicker :: TickerId
, lobOrders :: M.Map OrderId Order
}
data MatchingAction
= Enqueue OrderId PriceTick Volume Operation
| Match OrderId OrderId Operation PriceTick Volume
| Reject Order
deriving (Eq, Show)
newtype Exchange
= Exchange { eLobs :: M.Map TickerId LimitOrderBook }
addToLob :: Order -> LimitOrderBook -> [MatchingAction]
addToLob order lob = []
emptyLob :: TickerId -> Price -> LimitOrderBook
emptyLob tid tickSize = LimitOrderBook tickSize M.empty M.empty tid M.empty
executeMatchingActions :: LimitOrderBook -> [MatchingAction] -> LimitOrderBook
executeMatchingActions = foldl applyAction
where
applyAction lob (Enqueue oid pricetick vol op) =
if op == Buy
then lob { lobBids = addOrder oid pricetick vol (lobBids lob) }
else lob { lobOffers = addOrder oid pricetick vol (lobOffers lob) }
applyAction lob (Match subject object objectOperation pricetick vol) = error "Not implemented"
applyAction lob (Reject order) = error "Not implemented"
addOrder oid pricetick vol = M.alter (\x -> Just (fromMaybe empty x |> (oid, vol)) ) pricetick

74
stack.yaml

@ -0,0 +1,74 @@ @@ -0,0 +1,74 @@
# This file was automatically generated by 'stack init'
#
# Some commonly used options have been documented as comments in this file.
# For advanced use and comprehensive documentation of the format, please see:
# https://docs.haskellstack.org/en/stable/yaml_configuration/
# Resolver to choose a 'specific' stackage snapshot or a compiler version.
# A snapshot resolver dictates the compiler version and the set of packages
# to be used for project dependencies. For example:
#
# resolver: lts-3.5
# resolver: nightly-2015-09-21
# resolver: ghc-7.10.2
#
# The location of a snapshot can be provided as a file or url. Stack assumes
# a snapshot provided as a file might change, whereas a url resource does not.
#
# resolver: ./custom-snapshot.yaml
# resolver: https://example.com/snapshots/2018-01-01.yaml
resolver:
url: https://raw.githubusercontent.com/commercialhaskell/stackage-snapshots/master/lts/18/18.yaml
# User packages to be built.
# Various formats can be used as shown in the example below.
#
# packages:
# - some-directory
# - https://example.com/foo/bar/baz-0.0.2.tar.gz
# subdirs:
# - auto-update
# - wai
packages:
- .
- ../zeromq4-haskell-zap
- ../libatrade
# Dependency packages to be pulled from upstream that are not in the resolver.
# These entries can reference officially published versions as well as
# forks / in-progress versions pinned to a git hash. For example:
#
# extra-deps:
# - acme-missiles-0.3
# - git: https://github.com/commercialhaskell/stack.git
# commit: e7b331f14bcffb8367cd58fbfc8b40ec7642100a
#
extra-deps:
- co-log-0.4.0.1@sha256:3d4c17f37693c80d1aa2c41669bc3438fac3e89dc5f479e57d79bc3ddc4dfcc5,5087
- datetime-0.3.1@sha256:7e275bd0ce7a2f66445bedfa0006abaf4d41af4c2204c3f8004c17eab5480e74,1534
- ansi-terminal-0.10.3@sha256:e2fbcef5f980dc234c7ad8e2fa433b0e8109132c9e643bc40ea5608cd5697797,3226
# Override default flag values for local packages and extra-deps
# flags: {}
# Extra package databases containing global packages
# extra-package-dbs: []
# Control whether we use the GHC we find on the path
# system-ghc: true
#
# Require a specific version of stack, using version ranges
# require-stack-version: -any # Default
# require-stack-version: ">=2.7"
#
# Override the architecture used by stack, especially useful on Windows
# arch: i386
# arch: x86_64
#
# Extra directories used by stack for building
# extra-include-dirs: [/path/to/dir]
# extra-lib-dirs: [/path/to/dir]
#
# Allow a newer minor version of GHC than the snapshot specifies
# compiler-check: newer-minor

34
stack.yaml.lock

@ -0,0 +1,34 @@ @@ -0,0 +1,34 @@
# This file was autogenerated by Stack.
# You should not edit this file by hand.
# For more information, please see the documentation at:
# https://docs.haskellstack.org/en/stable/lock_files
packages:
- completed:
hackage: co-log-0.4.0.1@sha256:3d4c17f37693c80d1aa2c41669bc3438fac3e89dc5f479e57d79bc3ddc4dfcc5,5087
pantry-tree:
size: 1126
sha256: e73165ff8f744709428e2e87984c9d60ca1cec43d8455c413181c7c466e7497c
original:
hackage: co-log-0.4.0.1@sha256:3d4c17f37693c80d1aa2c41669bc3438fac3e89dc5f479e57d79bc3ddc4dfcc5,5087
- completed:
hackage: datetime-0.3.1@sha256:7e275bd0ce7a2f66445bedfa0006abaf4d41af4c2204c3f8004c17eab5480e74,1534
pantry-tree:
size: 334
sha256: d41d182c143676464cb1774f0b7777e870ddeaf8b6cd5fee6ff0114997a1f504
original:
hackage: datetime-0.3.1@sha256:7e275bd0ce7a2f66445bedfa0006abaf4d41af4c2204c3f8004c17eab5480e74,1534
- completed:
hackage: ansi-terminal-0.10.3@sha256:e2fbcef5f980dc234c7ad8e2fa433b0e8109132c9e643bc40ea5608cd5697797,3226
pantry-tree:
size: 1461
sha256: 02f05d52be3ffcf36c78876629cbab80b63420672685371aea4fd10e1c4aabb6
original:
hackage: ansi-terminal-0.10.3@sha256:e2fbcef5f980dc234c7ad8e2fa433b0e8109132c9e643bc40ea5608cd5697797,3226
snapshots:
- completed:
size: 586296
url: https://raw.githubusercontent.com/commercialhaskell/stackage-snapshots/master/lts/18/18.yaml
sha256: 63539429076b7ebbab6daa7656cfb079393bf644971156dc349d7c0453694ac2
original:
url: https://raw.githubusercontent.com/commercialhaskell/stackage-snapshots/master/lts/18/18.yaml

11
test/Spec.hs

@ -0,0 +1,11 @@ @@ -0,0 +1,11 @@
import Test.Tasty
import TestLOB qualified
main = defaultMain tests
tests = testGroup "ESim tests"
[
TestLOB.tests
]

59
test/TestLOB.hs

@ -0,0 +1,59 @@ @@ -0,0 +1,59 @@
module TestLOB
(
tests
) where
import ATrade.ESim.Core
(MatchingAction (..), PriceTick (PriceTick), Volume (..), addToLob, emptyLob, executeMatchingActions)
import ATrade.Types (Operation (..), Order (..), OrderPrice (..), OrderState (..), Price, SignalId (..))
import Hedgehog
import Hedgehog.Gen qualified as Gen
import Hedgehog.Range qualified as Range
import Test.Tasty
import Test.Tasty.Hedgehog
tests :: TestTree
tests = testGroup "LimitOrderBook"
[
testProperty "add to empty LOB => enqueues order" prop_add_to_empty_lob,
testProperty "full match" prop_full_match
]
prop_add_to_empty_lob :: Property
prop_add_to_empty_lob = property $ do
priceTick <- forAll $ Gen.integral (Range.linear 1 100000)
order <- forAll $
Order <$> Gen.integral (Range.linear 1 100000)
<*> Gen.text (Range.linear 1 50) Gen.unicode
<*> Gen.text (Range.linear 1 50) Gen.unicode
<*> pure (Limit $ fromInteger priceTick * tickSize)
<*> Gen.integral (Range.linear 1 10000)
<*> pure 0
<*> pure Buy
<*> pure Unsubmitted
<*> pure (SignalId "test" "foo" "")
addToLob order lob === [Enqueue (orderId order) (PriceTick . fromInteger $ priceTick) (Volume . fromIntegral $ orderQuantity order) (orderOperation order)]
where
lob = emptyLob "Test" tickSize
tickSize :: Price
tickSize = 0.1
prop_full_match :: Property
prop_full_match = property $ do
priceTick <- forAll $ Gen.integral (Range.linear 1 100000)
order <- forAll $
Order <$> Gen.integral (Range.linear 1 100000)
<*> Gen.text (Range.linear 1 50) Gen.unicode
<*> Gen.text (Range.linear 1 50) Gen.unicode
<*> pure Market
<*> Gen.integral (Range.linear 1 10000)
<*> pure 0
<*> pure Buy
<*> pure Unsubmitted
<*> pure (SignalId "test" "foo" "")
addToLob order (lob order priceTick) === [Match 1 (orderId order) Sell (PriceTick priceTick) (Volume . fromIntegral $ orderQuantity order)]
where
lob order priceTick = executeMatchingActions lob0 [Enqueue 1 (PriceTick priceTick) (Volume . fromIntegral $ orderQuantity order) Sell]
lob0 = emptyLob "Test" tickSize
tickSize = 0.1
Loading…
Cancel
Save