-
Notifications
You must be signed in to change notification settings - Fork 3
/
PAMR.jl
135 lines (103 loc) · 4.72 KB
/
PAMR.jl
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
"""
pamr(rel_pr::AbstractMatrix, ϵ::AbstractFloat, C::AbstractFloat, model::PAMRModel)
Run the PAMR algorithm on the matrix of relative prices `rel_pr`.
# Arguments
- `rel_pr::AbstractMatrix`: matrix of relative prices.
- `ϵ::AbstractFloat`: Sensitivity parameter.
- `C::AbstractFloat`: Aggressiveness parameter.
- `model::PAMRModel`: PAMR model to use. All three variants, namely, `PAMR()`, `PAMR1()`, and `PAMR2()` are supported.
!!! warning "Beware!"
`rel_price` should be a matrix of size `n_assets` × `n_periods`.
# Output
- `::OPSAlgorithm`: An object of type [`OPSAlgorithm`](@ref).
# Example
```julia
julia> using OnlinePortfolioSelection, YFinance
julia> tickers = ["AAPL", "MSFT", "AMZN", "META", "GOOG"]
julia> startdt, enddt = "2019-01-01", "2020-01-01"
julia> querry = [get_prices(ticker, startdt=startdt, enddt=enddt)["adjclose"] for ticker in tickers]
julia> prices = stack(querry) |> permutedims
julia> rel_pr = prices[:, 2:end]./prices[:, 1:end-1]
julia> model = PAMR()
julia> eps = 0.01
julia> result = pamr(rel_pr, eps, model)
julia> result.b
5×251 Matrix{Float64}:
0.2 0.224672 0.22704 0.230855 0.229743 … 0.0966823 0.0966057 0.0900667
0.2 0.196884 0.197561 0.199825 0.203945 0.172787 0.171734 0.171626
0.2 0.191777 0.190879 0.178504 0.178478 0.290126 0.289638 0.291135
0.2 0.193456 0.193855 0.196363 0.189322 0.182514 0.181609 0.185527
0.2 0.193211 0.190665 0.194453 0.198513 0.25789 0.260414 0.261645
julia> sum(result.b, dims=1) .|> isapprox(1.) |> all
true
```
In the same way, you can use `PAMR1()` and `PAMR2()`:
```julia
julia> model = PAMR1(C=0.02)
julia> eps = 0.01
julia> result = pamr(rel_pr, eps, model)
julia> result.b
5×251 Matrix{Float64}:
0.2 0.200892 0.200978 0.201116 … 0.196264 0.19626 0.196257 0.19602
0.2 0.199887 0.199912 0.199994 0.198835 0.199017 0.198979 0.198975
0.2 0.199703 0.19967 0.199223 0.203659 0.203261 0.203243 0.203297
0.2 0.199763 0.199778 0.199868 0.199246 0.199351 0.199319 0.19946
0.2 0.199754 0.199662 0.199799 0.201997 0.20211 0.202202 0.202246
julia> model = PAMR2(C=1.)
julia> eps = 0.01
julia> result = pamr(rel_pr, eps, model)
julia> result.b
5×251 Matrix{Float64}:
0.2 0.219093 0.220963 0.223948 … 0.119093 0.119013 0.118953 0.11385
0.2 0.197589 0.198123 0.199895 0.175224 0.179199 0.178376 0.178291
0.2 0.193636 0.192928 0.183242 0.279176 0.27052 0.270138 0.271307
0.2 0.194936 0.19525 0.197214 0.183626 0.185922 0.185215 0.188272
0.2 0.194746 0.192736 0.195701 0.242882 0.245346 0.247319 0.248279
```
# References
> [PAMR: Passive aggressive mean reversion strategy for portfolio selection](https://www.doi.org/10.1007/s10994-012-5281-z)
"""
function pamr(rel_pr::AbstractMatrix, ϵ::AbstractFloat, model::PAMRModel)
ϵ > 0 || ArgumentError("ϵ must be positive.") |> throw
n_assets, n_obs = size(rel_pr)
b = ones(n_assets, n_obs)/n_assets
for t in 1:n_obs-1
rel_prₜ = @view rel_pr[:,t]
bₜ = @view b[:,t]
x̄ₜ = rel_prₜ./length(rel_prₜ)
ℓᵗ = ℓᵗfunc(bₜ, rel_prₜ, ϵ)
τₜ = τₜfunc(model, rel_prₜ, x̄ₜ, ℓᵗ)
bₜ₊₁ = updateptf(bₜ, rel_prₜ, x̄ₜ, τₜ)
b[:,t+1] = normptf(bₜ₊₁)
end
return OPSAlgorithm(n_assets, b, pamralgname(model))
end
function ℓᵗfunc(𝐛ₜ::AbstractVector, rel_prₜ::AbstractVector, ϵ::AbstractFloat)
return max(0., sum(𝐛ₜ .* rel_prₜ) - ϵ)
end
function τₜfunc(::PAMR, rel_prₜ::AbstractVector, x̄ₜ::AbstractVector, ℓᵗ::AbstractFloat)
return ℓᵗ/norm(rel_prₜ-x̄ₜ)
end
function τₜfunc(m::PAMR1, rel_prₜ::AbstractVector, x̄ₜ::AbstractVector, ℓᵗ::AbstractFloat)
m.C > 0 || ArgumentError("C must be positive. Example: PAMR1(C=1.)") |> throw
return min(m.C, ℓᵗ/norm(rel_prₜ-x̄ₜ))
end
function τₜfunc(m::PAMR2, rel_prₜ::AbstractVector, x̄ₜ::AbstractVector, ℓᵗ::AbstractFloat)
m.C > 0 || ArgumentError("C must be positive. Example: PAMR2(C=1.)") |> throw
return ℓᵗ/(norm(rel_prₜ-x̄ₜ)+(1/(2m.C)))
end
function updateptf(𝐛ₜ::AbstractVector, rel_prₜ::AbstractVector, x̄ₜ::AbstractVector, τₜ::AbstractFloat)
return 𝐛ₜ .- τₜ * (rel_prₜ .- x̄ₜ)
end
function normptf(bₜ₊₁::AbstractVector)
n_assets = length(bₜ₊₁)
model = Model(optimizer_with_attributes(Optimizer, "print_level" => 0))
@variable(model, 0. ≤ b[i=1:n_assets] ≤ 1.)
@constraint(model, sum(b) == 1.)
@NLobjective(model, Min, sum((b[i] - bₜ₊₁[i])^2 for i=1:n_assets))
optimize!(model)
return value.(b)
end
pamralgname(::PAMR) = "PAMR"
pamralgname(::PAMR1) = "PAMR1"
pamralgname(::PAMR2) = "PAMR2"