-
Notifications
You must be signed in to change notification settings - Fork 39
/
Copy path06-introduce-buydown-interval.Rmd
167 lines (129 loc) · 6.27 KB
/
06-introduce-buydown-interval.Rmd
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
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
# Introduce a `buy_down_interval` to make a single trader more profitable
## Objectives
- present reasons why to introduce `buy_down_interval`
- add `buy_down interval` to `Naive.Trader`'s state and calculate buy price
- add `buy_down interval` to `Naive.Trader`'s state compiled by the `Naive.Leader`
- manually test the implementation inside iex
## Why we need to buy below the current price? Feature overview
```{r, fig.align="center", out.width="50%", echo=FALSE}
knitr::include_graphics("images/chapter_06_01_current_buy_price.png")
```
The `Naive.Trader` process(marked in above diagram with blue color) at the arrival of the first trade event, immediately places a buy order at the current price. At the moment when the buy order gets filled, it places the sell order which later also gets filled.
The Trader A exits and a new trader B is started which again immediately places a buy order *at the same price* as the previous trader just sold. When this gets filled sell order gets placed and the loop continues on and on.
We can see that there's a problem here as we just paid a fee twice(once for selling by the Trader A and once for buying by the Trader B) without really gaining anything(the Trader A could just hold the currency and could simply cash in on double profit in this specific situation).
\newpage
The solution is to be more clever about our buy order's price. The idea is simple, instead of placing a new buy order at the current price(price from the last TradeEvent), we will introduce a `buy_down_interval`:
```{r, fig.align="center", out.width="50%", echo=FALSE}
knitr::include_graphics("images/chapter_06_02_rebuy_expl.png")
```
So every new `Naive.Trader` process as it receives the first trade event, the trader will take its price and will calculate a decreased price by using the `buy_down_interval` value(for example 0.005 would be 0.5%) and place a buy order at that calculated price.
When looking at the chart above we can figure out that `buy_down_interval` should never be smaller than double the fee(at the moment of writing transaction fee is 0.1%) that you are paying per transaction.
## `Naive.Trader` implementation
Let's open the `Naive.Trader` module's file(`/apps/naive/lib/naive/trader.ex`) and add `buy_down_interval` to its state:
```{r, engine = 'elixir', eval = FALSE}
# /apps/naive/lib/naive/trader.ex
...
defmodule State do
@enforce_keys [
:symbol,
:buy_down_interval, # <= add this line
:profit_interval,
:tick_size
]
defstruct [
:symbol,
:buy_order,
:sell_order,
:buy_down_interval, # <= add this line
:profit_interval,
:tick_size
]
end
...
```
Next, we need to update the initial `handle_info/2` callback which places the buy order. We need to retrieve the `buy_down_interval` and the `tick_size` from the `state` of the trader to be able to calculate the buy price. We will put the logic to calculate that price in a separate function at the end of the file:
```{r, engine = 'elixir', eval = FALSE}
# /apps/naive/lib/naive/trader.ex
...
def handle_info(
%TradeEvent{price: price},
%State{
symbol: symbol,
buy_order: nil,
buy_down_interval: buy_down_interval, # <= add this line
tick_size: tick_size # <= add this line
} = state
) do
price = calculate_buy_price(price, buy_down_interval, tick_size)
# ^ add above call
...
```
To calculate the buy price we will use a very similar method to the one used
before to calculate the sell price. First, we will need to cast all variables
into the `Decimal` structs and then, we will simply subtract the `buy_down_interval` of the price from the price. The number that we will end up with won't necessarily be a legal price as every price needs to be divisible by the `tick_size` which we will assure in the last calculation:
```{r, engine = 'elixir', eval = FALSE}
# /apps/naive/lib/naive/trader.ex
...
defp calculate_buy_price(current_price, buy_down_interval, tick_size) do
# not necessarily legal price
exact_buy_price =
D.sub(
current_price,
D.mult(current_price, buy_down_interval)
)
D.to_string(
D.mult(
D.div_int(exact_buy_price, tick_size),
tick_size
),
:normal
)
end
...
```
## `Naive.Leader` implementation
Next, we need to update the `Naive.Leader` as it needs to add `buy_down_interval` to the `Naive.Trader`'s state:
```{r, engine = 'elixir', eval = FALSE}
# /apps/naive/lib/naive/leader.ex
defp fetch_symbol_settings(symbol) do
...
%{
symbol: symbol,
chunks: 1,
# 0.01% for quick testing
buy_down_interval: "0.0001", # <= add this line
# -0.12% for quick testing
profit_interval: "-0.0012",
tick_size: tick_size
}
end
...
```
### IEx testing
That finishes the `buy_down_interval` implementation, we will jump into the IEx session to see how it works, but before that, for a moment we will change the logging level to `debug` to see current prices:
```{r, engine = 'elixir', eval = FALSE}
# config/config.exs
...
config :logger,
level: :debug # <= updated for our manual test
...
```
\newpage
After starting the streaming we should start seeing log messages with current prices. As we updated our implementation we should place our buy order below the current price as it's visible below:
```{r, engine = 'bash', eval = FALSE}
$ iex -S mix
...
iex(1)> Streamer.start_streaming("FLMUSDT")
{:ok, #PID<0.313.0>}
iex(2)> Naive.start_trading("FLMUSDT")
21:16:14.829 [info] Starting new supervision tree to trade on FLMUSDT
...
21:16:16.755 [info] Initializing new trader for FLMUSDT
...
21:16:20.000 [debug] Trade event received [email protected]
21:16:20.009 [info] Placing BUY order for FLMUSDT @ 0.1517, quantity: 100
```
As we can see our `Naive.Trader` process placed a buy order below the current price (based on the most recent trade event received)
[Note] Please remember to revert the change to logger level as otherwise there's too much noise in the logs.
[Note 2] Please remember to run the `mix format` to keep things nice and tidy.
The source code for this chapter can be found on [GitHub](https://github.com/Cinderella-Man/hands-on-elixir-and-otp-cryptocurrency-trading-bot-source-code/tree/chapter_06)