Skip to content

Commit c846852

Browse files
committed
RSI indicator wip
1 parent ab6ccd4 commit c846852

File tree

3 files changed

+101
-2
lines changed

3 files changed

+101
-2
lines changed

pub/bfx/bfx.cabal

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,7 @@ library
131131
Bfx.Data.Web
132132
Bfx.Indicator.Atr
133133
Bfx.Indicator.Ma
134+
Bfx.Indicator.Rsi
134135
Bfx.Indicator.Tr
135136
Bfx.Math
136137
Bfx.Parser
@@ -180,6 +181,7 @@ test-suite bfx-test
180181
Bfx.Data.Web
181182
Bfx.Indicator.Atr
182183
Bfx.Indicator.Ma
184+
Bfx.Indicator.Rsi
183185
Bfx.Indicator.Tr
184186
Bfx.Math
185187
Bfx.Parser

pub/bfx/src/Bfx/Indicator/Rsi.hs

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
{-# OPTIONS_HADDOCK show-extensions #-}
2+
3+
module Bfx.Indicator.Rsi
4+
( Rsi (..),
5+
RsiPeriod (..),
6+
defRsiPeriod,
7+
mkRsiConduit,
8+
)
9+
where
10+
11+
import Bfx.Data.Type
12+
import Conduit ((.|))
13+
import qualified Conduit as C
14+
import qualified Data.Conduit.List as C
15+
import Functora.Money
16+
import Functora.Prelude
17+
18+
newtype Rsi = Rsi
19+
{ unRsi :: Ratio Natural
20+
}
21+
deriving stock
22+
( Eq,
23+
Ord,
24+
Show,
25+
Read,
26+
Data,
27+
Generic
28+
)
29+
30+
newtype RsiPeriod = RsiPeriod
31+
{ unRsiPeriod :: Natural
32+
}
33+
deriving stock
34+
( Eq,
35+
Ord,
36+
Show,
37+
Read,
38+
Data,
39+
Generic
40+
)
41+
42+
defRsiPeriod :: RsiPeriod
43+
defRsiPeriod = RsiPeriod 14
44+
45+
mkRsiConduit ::
46+
( Monad m
47+
) =>
48+
(a -> Candle) ->
49+
RsiPeriod ->
50+
C.ConduitT a (a, Rsi) m ()
51+
mkRsiConduit mkCandle (RsiPeriod natPer) =
52+
C.slidingWindowC 2
53+
.| ( whileM $ do
54+
mcandles <- fmap (>>= nonEmpty) C.await
55+
case mcandles of
56+
Just [c1, c2] -> do
57+
let p1 = mkCandle c1 ^. #candleClose . #unQuotePerBase
58+
let p2 = mkCandle c2 ^. #candleClose . #unQuotePerBase
59+
C.yield
60+
( c2,
61+
-- Loss
62+
if p1 >= p2
63+
then p1 - p2
64+
else 0,
65+
-- Gain
66+
if p1 <= p2
67+
then p2 - p1
68+
else 0
69+
)
70+
pure True
71+
_ ->
72+
pure False
73+
)
74+
.| ( do
75+
seed <- C.take intPer
76+
when (length seed == intPer) $ do
77+
let initAvgLoss = sum (fmap snd3 seed) / ratPer
78+
let initAvgGain = sum (fmap thd3 seed) / ratPer
79+
flip loopM (initAvgLoss, initAvgGain)
80+
$ \(prevAvgLoss, prevAvgGain) -> do
81+
mcandle <- C.await
82+
case mcandle of
83+
Nothing -> pure $ Right ()
84+
Just (c, loss, gain) -> do
85+
let nextAvgLoss = prevAvgLoss * (ratPer - 1) + loss / ratPer
86+
let nextAvgGain = prevAvgGain * (ratPer - 1) + gain / ratPer
87+
let rs = nextAvgGain / nextAvgLoss
88+
let rsi = Rsi $ 100 - (100 / (1 + rs))
89+
C.yield (c, rsi)
90+
pure $ Left (nextAvgLoss, nextAvgGain)
91+
)
92+
where
93+
ratPer = from @Natural @(Ratio Natural) natPer
94+
intPer =
95+
case unsafeFrom @Natural @Int natPer of
96+
x | x < 2 -> error $ "Bad RSI period " <> inspect natPer
97+
x -> x

pub/bfx/test/BfxSpec.hs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -148,12 +148,12 @@ spec = before sysEnv $ do
148148
buyDef <- Bfx.mkOrder req
149149
SubmitOrder.baseAmount buyDef `shouldBe` MoneyAmount 4.00400401
150150
sellDef <- Bfx.mkOrder req {Bfx.mkOrderBuyOrSell = Sell}
151-
SubmitOrder.baseAmount sellDef `shouldBe` MoneyAmount 4
151+
SubmitOrder.baseAmount sellDef `shouldBe` MoneyAmount 3.99999999
152152
let reqBase = req {Bfx.mkOrderNetBaseAmt = Just $ MoneyAmount 10}
153153
buyBase <- Bfx.mkOrder reqBase
154154
SubmitOrder.baseAmount buyBase `shouldBe` MoneyAmount 10.01001002
155155
sellBase <- Bfx.mkOrder reqBase {Bfx.mkOrderBuyOrSell = Sell}
156-
SubmitOrder.baseAmount sellBase `shouldBe` MoneyAmount 10
156+
SubmitOrder.baseAmount sellBase `shouldBe` MoneyAmount 9.99999999
157157

158158
-- describe "End2End" $ do
159159
-- itRight "submitOrderMaker" $ \env -> do

0 commit comments

Comments
 (0)