commit
fefe98e7cb
13 changed files with 441 additions and 0 deletions
@ -0,0 +1,3 @@ |
|||||||
|
# Changelog for exchange-simulator |
||||||
|
|
||||||
|
## Unreleased changes |
||||||
@ -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. |
||||||
@ -0,0 +1,4 @@ |
|||||||
|
module Main where |
||||||
|
|
||||||
|
main :: IO () |
||||||
|
main = error "Not implemented" |
||||||
@ -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 |
||||||
@ -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 |
||||||
@ -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 |
||||||
@ -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 |
||||||
@ -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 |
||||||
@ -0,0 +1,11 @@ |
|||||||
|
|
||||||
|
import Test.Tasty |
||||||
|
|
||||||
|
import TestLOB qualified |
||||||
|
|
||||||
|
main = defaultMain tests |
||||||
|
|
||||||
|
tests = testGroup "ESim tests" |
||||||
|
[ |
||||||
|
TestLOB.tests |
||||||
|
] |
||||||
@ -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…
Reference in new issue