-
Notifications
You must be signed in to change notification settings - Fork 10
/
Impl.hs
139 lines (124 loc) · 4.14 KB
/
Impl.hs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
{-# LANGUAGE MultiWayIf #-}
{- |
Module : GeniusYield.OrderBot.Strategies.Impl
Copyright : (c) 2023 GYELD GMBH
License : Apache 2.0
Maintainer : [email protected]
Stability : develop
-}
module GeniusYield.OrderBot.Strategies.Impl (
BotStrategy (..),
allStrategies,
MatchResult,
IndependentStrategy,
mkIndependentStrategy,
) where
import Control.Monad.State.Strict (State, execState, modify')
import Data.Aeson
import Data.Aeson.Types (Parser)
import Data.Data (Typeable)
import Data.Text (Text)
import GHC.Generics (Generic)
import GHC.Natural (Natural)
import GeniusYield.OrderBot.OrderBook
import GeniusYield.OrderBot.OrderBook.Extra
import GeniusYield.OrderBot.Types
import System.Envy (Var (..))
data BotStrategy = OneSellToManyBuy
deriving stock (Show, Eq, Generic)
deriving anyclass (ToJSON, Typeable)
instance FromJSON BotStrategy where
parseJSON = withText "BotStrategy" parse
where
parse :: Text -> Parser BotStrategy
parse "OneSellToManyBuy" = return OneSellToManyBuy
parse _ = fail "Undefined strategy name"
instance Var BotStrategy where
fromVar s = case s of
"OneSellToManyBuy" -> Just OneSellToManyBuy
_ -> Nothing
toVar = show
allStrategies :: [BotStrategy]
allStrategies = [OneSellToManyBuy]
type MatchResult = [MatchExecutionInfo]
type IndependentStrategy = (OrderAssetPair -> OrderBook -> [MatchResult])
mkIndependentStrategy :: BotStrategy -> Natural -> IndependentStrategy
mkIndependentStrategy bs maxOrders _ bk =
case bs of
OneSellToManyBuy -> oneSellToManyBuy maxOrders bk
-- | Strategy state containing the matchings found and the remaining buy orders.
data StrategyState = StrategyState
{ matchResults :: ![MatchResult]
, remainingOrders :: !(Orders 'BuyOrder)
}
-- | Utility function for updating the state, after one run of the strategy.
updateStrategyState ::
MatchResult ->
Orders 'BuyOrder ->
StrategyState ->
StrategyState
updateStrategyState [] bos' ss = ss {remainingOrders = bos'}
updateStrategyState mr' bos' StrategyState {matchResults = mr} =
StrategyState
{ matchResults = mr ++ [mr']
, remainingOrders = bos'
}
{- | Strategy matching: Picking one sell order and matching it with many (up to
`maxOrders`) buy orders.
-}
oneSellToManyBuy :: Natural -> OrderBook -> [MatchResult]
oneSellToManyBuy maxOrders ob =
matchResults $
execState (mapMOrders_ go $ sellOrders ob) $
StrategyState {matchResults = [], remainingOrders = buyOrders ob}
where
go ::
OrderInfo 'SellOrder ->
State StrategyState ()
go order = modify' $
\st ->
uncurry
updateStrategyState
(multiFill (maxOrders - 1) (<=) order (remainingOrders st))
st
-- | General matching orders function.
multiFill ::
forall b b'.
Natural ->
(Price -> Price -> Bool) ->
OrderInfo b ->
Orders b' ->
(MatchResult, Orders b')
multiFill maxOrders checkPrices order = go (maxOrders - 1) vh
where
(Volume vl vh) = volume order
checkPrice = checkPrices $ price order
go :: Natural -> Natural -> Orders b' -> (MatchResult, Orders b')
go _ 0 os = ([completeFill order], os)
go 0 v os
| (vh - v) >= vl = ([partialFill order (vh - v)], os)
| otherwise = ([], os)
go limitO remVol os' =
case unconsOrders os' of
Nothing ->
if
| (vh - remVol) > vl -> ([partialFill order (vh - remVol)], emptyOrders)
| otherwise -> ([], emptyOrders)
Just (o, os) ->
if
| remVol == maxFillX && checkPrice xP ->
let !b = completeFill o
in ([completeFill order, b], os)
| remVol > maxFillX && remVol >= minFillX && checkPrice xP ->
case go (limitO - 1) (remVol - maxFillX) os of
([], _) -> updateRemaining o $ go limitO remVol os
(bs, s) -> (completeFill o : bs, s)
| remVol < maxFillX
&& remVol >= minFillX
&& checkPrice xP ->
([completeFill order, partialFill o remVol], os)
| otherwise -> updateRemaining o $ go limitO remVol os
where
xP = price o
(Volume minFillX maxFillX) = volume o
updateRemaining x (a, b) = (a, insertOrder x b)