# 12 in 24 February: Stock Trading Bot

Well here we go again. I'll be building a stock trading bot in Julia using Ally Bank to execute trades and Alpha Vantage for stocks. I started by implementing the Aroon Oscillator in Julia, to see if I could implement an actual technical indicator.

The Aroon Oscillator function looks like this:

\[oscillator = Aroon Up - Aroon Down\]

\[Aroon Up = 100 * \frac{(25 - PeriodsSince25PeriodHigh)}{25} \]

\[Aroon Down = 100 * \frac{(25 - PeriodsSince25PeriodLow)}{25} \]

To implement in Julia, I had to get the financial data first, then slide an Aroon Oscillator function over it.

```
function daily_df_yfinance(symbol, period)
data = DataFrame(yahoo(symbol, period))
rename!(data, [:timestamp, :open, :high, :low, :close, :adjclose, :volume])
data
end
function aroon(df)
if length(df.timestamp) < 25
return nothing
end
periods = first(df, 25)
aroon_up = 100 * ((25 - argmax(periods.high)) / 25)
aroon_down = 100 * ((25 - argmin(periods.low)) / 25)
aroon_up - aroon_down
end
```

Julia makes things simple, so that I can focus on the actual indicator that I'm building.

## Displaying the Aroon Oscillator

Let's actually look at it, and see what it looks like for a variety of stocks and ticker symbols. I used `Plots.jl`

to display the data, and generated an Aroon dataframe to eventually plot

```
sym = :TSLA
df = MyAlgoTrader.daily_df_yfinance(sym, YahooOpt(period1=DateTime(2023,1,1)))
# Pass a sliding window over the aroon window
aroon = DataFrame(MyAlgoTrader.aroon_series(df))
using Plots
p1 = plot(aroon.ts, aroon.A)
p2 = plot(aroon.ts, df.close)
plot(p1, p2, layout=(2,1))
```

Here's what the plot looked like:

The above line is the Aroon Oscillator for TSLA, the below line is the daily adjusted close for TSLA since 2023. You can see as the line oscillates around 0 whether an uptrend or a downtrend is indicated. Above 0 means that an uptrend is present, below 0 means a downtrend is present. We can use this to implement a trend-following strategy.

## Mean Reversion Strategy

The end goal of this strategy is to fit a line to any given stock, and then find the standard deviations up and down from that line. We trade based on values reverting to the mean.

\[y = mx + ny + b \]

`m`

and `n`

are the weights we are trying to learn. `x`

and `y`

are the features. `b`

is the bias. What I want to do is forecast the values of assets using a simple linear regression. Why linear regression? Because this is a great place to start due to it's simplicity and understandability.

We are trying to learn `m`

and `n`

, and we are trying to minimize error from the learned `m`

and `n`

by choosing values that minimize the squared error between the target and the predictions.

## Initial Analysis

We can begin the analysis using a "time dummy", which is just the time steps since the origin time `0`

.

Here's what that looks like in Julia:

```
julia> first(df, 10)
10×7 DataFrame
Row │ timestamp open high low close adjclose volume
│ Date Float64 Float64 Float64 Float64 Float64 Float64
─────┼─────────────────────────────────────────────────────────────────────
1 │ 2023-01-03 118.47 118.8 104.64 108.1 108.1 2.31403e8
2 │ 2023-01-04 109.11 114.59 107.52 113.64 113.64 1.80389e8
3 │ 2023-01-05 110.51 111.75 107.16 110.34 110.34 1.57986e8
4 │ 2023-01-06 103.0 114.39 101.81 113.06 113.06 2.20911e8
5 │ 2023-01-09 118.96 123.52 117.11 119.77 119.77 1.90284e8
6 │ 2023-01-10 121.07 122.76 114.92 118.85 118.85 1.67642e8
7 │ 2023-01-11 122.09 125.95 120.51 123.22 123.22 1.83811e8
8 │ 2023-01-12 122.56 124.13 117.0 123.56 123.56 1.69401e8
9 │ 2023-01-13 116.55 122.63 115.6 122.4 122.4 1.80714e8
10 │ 2023-01-17 125.7 131.7 125.02 131.49 131.49 1.86477e8
julia> df.dummy = 1:nrow(df)
1:282
julia> first(df, 1)
1×8 DataFrame
Row │ timestamp open high low close adjclose volume dummy
│ Date Float64 Float64 Float64 Float64 Float64 Float64 Int64
─────┼────────────────────────────────────────────────────────────────────────────
1 │ 2023-01-03 118.47 118.8 104.64 108.1 108.1 2.31403e8 1
```

Now we are trying to fit the following line

\[ y = time * weight + bias \]

We will fit that line using the package `GLM`

in Julia. This package mimics currently available R packages.

```
using GLM, StatsBase
ols = lm(@formula(adjclose ~ dummy), df)
b, m = coef(ols)
pred = @. m * (df.dummy) + b
plot(df.dummy, df.adjclose)
plot!(df.dummy, pred)
```

The `ols`

variable looks like this:

```
Coefficients:
────────────────────────────────────────────────────────────────────────────
Coef. Std. Error t Pr(>|t|) Lower 95% Upper 95%
────────────────────────────────────────────────────────────────────────────
(Intercept) 179.629 4.09776 43.84 <1e-99 171.563 187.695
dummy 0.258806 0.0251018 10.31 <1e-20 0.209394 0.308218
```

So what is the code above doing? `pred`

is the result of applying our learned linear regression function to the existing dummy data. When we plot the time dummy with the adjclose, we come up with this plot

So let's create a linear regression mean reversion strategy on time and price. This will be used as a simple means to trade based on our mathematical formula here.

The green bands provide us with the standard deviation above and below the linear regression line. I calculated the standard deviation from the time period of the data.

My strategy, is going to be to buy when I cross the bottom standard deviation going up, stop loss at 10% less than the bottom standard deviation, exit half the position at mean, and the rest of the position at the top standard deviation.

This can be broken down into a simple set of rules. Here is my trading strategy in it's entirety.

- On a new day, calculate the linear regression, standard deviation for the last 200 days.
- Does the last close price now fall below the linear regression - standard deviation? Hold
- Is the last close price now above the lower standard deviation line? Buy
- Did the last close price cross the linear regression going upward? Sell half.
- Did the last close price cross the upper standard deviation line? Sell all.

Now, how can we implement this in Julia?

## Backtesting

To start, I'd like to implement my own backtester. Backtesting in stock trading means to test your strategy against historical data, and see what returns the strategy can generate. It's a crucial part to evaluating the effectiveness of the strategy.

I'll borrow ideas from the excellent package: https://kernc.github.io/backtesting.py/. I need to initialize a strategy then run it over a time period.

I can create custom types for my strategies that allow me to use Julia's multiple dispatch functionality.

I'll define an abstract type for my strategy, that all my other strategies can derive from. Then I'll create a concrete type that derives from `Strategy`

to represent my mean reversion strategy.

```
abstract type Strategy end
# Custom strategies
mutable struct MeanReversionStrategy <: Strategy
data::DataFrame
end
```

With these custom strategies I need to define backtest methods that give me the ability to test. Julia's multiple dispatch is of use here, because I can use dispatch to create a default method that warns us if the strategy backtest method has not been created.

```
function run(s::Strategy, money::Float64)
@warn "Initializing a strategy without an implementation"
end
function run(s::MeanReversionStrategy, money::Float64)
#...
end
```

This means that if Julia cannot find an implementation of `run`

for a concrete strategy type, it will warn instead of doing anything. This will tell developers that they need to provide the implementation.

Finally, I put my strategy to use in a basic backtest. I don't have any useful indicators yet that I can trade on other than my half-baked linear regression, and I don't calculate anything like a Sharpe ratio yet (I'm an absolute beginner). However, I was able to put together a basic backtester that provided me with a good idea of how the strategy actually worked!

```
function run(s::MeanReversionStrategy, money::Float64)
# Calculate the standard deviation.
basis = money
σ = std(s.data.adjclose)
share_count = 0
# Get the time dummy for fitting the linreg.
s.data.dummy = 1:nrow(s.data)
# Get the linear regression
linreg = lm(@formula(adjclose ~ dummy), s.data)
# With the linear regression, we can get our functions that necessitate buy or sell.
b, m = coef(linreg)
mean = @. m * (s.data.dummy) + b
# High and low functions
high = mean .+ σ
low = mean .- σ
@info "Starting backtest with cost basis: $(basis)"
for i ∈ 2:nrow(s.data)
last_close = s.data[i,:].adjclose
previous = s.data[i-1,:].adjclose
low_bar = low[i]
high_bar = high[i]
mean_bar = mean[i]
# If we were greater than the low bar, and the previous was less.
if last_close >= low_bar && previous < low_bar
@info "Buy @ $(last_close)"
share_count += 1
money -= last_close
# If we were greater than the mean bar and the previous was less
elseif last_close >= mean_bar && previous < mean_bar && share_count > 0
@info "Sell half @ $(last_close)"
share_count -= (share_count / 2)
money += (last_close * (share_count / 2))
elseif last_close >= high_bar && previous < high_bar && share_count > 0
@info "Sell remaining @ $(last_close)"
money += last_close * share_count
share_count = 0
# If we've crossed a stop-loss threshold
elseif last_close < low_bar - (low_bar * .2) && share_count > 0
@info "Stop-loss, sell @ $(last_close)"
money += (last_close * share_count)
share_count = 0
end
end
@info "Ended with $(money), profit percentage: $((money - basis) / 100)"
end
```

This puts together all the previous ideas in a way that I can use to see how the strategy actually works on historical data. The strategy performs decently well (it doesn't lose money!)

I can call the backtest now like this

```
julia> MyAlgoTrader.backtest(now() - Dates.Month(30), 30000.)
[ Info: Starting backtest with cost basis: 30000.0
[ Info: Buy @ 243.636673
[ Info: Sell half @ 298.0
[ Info: Sell remaining @ 359.013336
[ Info: Buy @ 219.600006
[ Info: Sell half @ 271.706665
[ Info: Sell half @ 274.820007
[ Info: Buy @ 190.720001
[ Info: Buy @ 194.699997
[ Info: Stop-loss, sell @ 109.099998
[ Info: Buy @ 181.410004
[ Info: Buy @ 183.259995
[ Info: Buy @ 170.059998
[ Info: Buy @ 173.860001
[ Info: Sell half @ 224.570007
[ Info: Sell remaining @ 282.480011
[ Info: Ended with 29834.044184624996, profit percentage: -1.6595581537500401
```

Doesn't look great, but it's a great first step for me in the world of algo-trading

## Next Steps

- Further refine the backtest algorithm to be more parameterizable. Can I make stop-loss orders have a configurable threshold?
- Better trade sizing, are there trade sizing algorithms out there that I can use?
- Fix backtester bugs. I can't sell fractional shares 😄