Skip to content

Commit

Permalink
Merge pull request #53 from shayandavoodii/EGM
Browse files Browse the repository at this point in the history
Add EGM 🆕
  • Loading branch information
shayandavoodii authored Nov 24, 2023
2 parents da86ccb + 187247a commit f9527f4
Show file tree
Hide file tree
Showing 9 changed files with 400 additions and 4 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ This package presents a selection of proposed Online Portfolio Selection (OPS) a
| 8 | [Anticor](https://shayandavoodii.github.io/OnlinePortfolioSelection.jl/dev/FL/#Anti-Correlation-(Anticor)) | Follow the Loser | 19 | [CAEG](https://shayandavoodii.github.io/OnlinePortfolioSelection.jl/dev/ML/#Continuous-Aggregating-Exponential-Gradient-(CAEG)) | Meta-Learning |
| 9 | [1/N](https://shayandavoodii.github.io/OnlinePortfolioSelection.jl/dev/benchmark/#/N) | Benchmark (Market) | 20 | [OLDEM](https://shayandavoodii.github.io/OnlinePortfolioSelection.jl/dev/PM/#Online-Low-Dimension-Ensemble-Method-(OLDEM)) | Pattern-Matching |
|10 | [OLMAR](https://shayandavoodii.github.io/OnlinePortfolioSelection.jl/dev/FL/#Online-Moving-Average-Reversion-(OLMAR)) | Follow the Loser | 21 | [AICTR](https://shayandavoodii.github.io/OnlinePortfolioSelection.jl/dev/FW/#Adaptive-Input-and-Composite-Trend-Representation-(AICTR)) | Follow the Winner |
|11 | [Bᴷ](https://shayandavoodii.github.io/OnlinePortfolioSelection.jl/dev/PM/#Bᴷ) | Pattern-Matching |
|11 | [Bᴷ](https://shayandavoodii.github.io/OnlinePortfolioSelection.jl/dev/PM/#Bᴷ) | Pattern-Matching | 22 | [EGM](https://shayandavoodii.github.io/OnlinePortfolioSelection.jl/dev/FW/#Exponential-Gradient-with-Momentum-(EGM)) | Follow the Winner |
</div>

<p align="right">🆙<a href="#top">Table of cotents</a></p>
Expand Down
73 changes: 73 additions & 0 deletions docs/src/FW.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ The Follow the Winner (FW) strategies operate on the principle that assets that
1. Exponential Gradient (EG)
2. Price Peak Tracking (PPT)
3. Adaptive Input and Composite Trend Representation (AICTR)
4. Exponential Gradient with Momentum (EGM)

## Exponential Gradient

Expand Down Expand Up @@ -195,6 +196,78 @@ julia> sn(model.b, rel_price)

The outcome suggests that if we had invested during the given period, we would have incurred a loss of approximately 2.8% of our wealth. It's important to note that [`sn`](@ref) automatically considers the last 5 relative prices in this case. Other metrics can be found in the [Performance evaluation](@ref) section.

## Exponential Gradient with Momentum (EGM)

[LI2022115889](@citet) proposed a novel online portfolio selection algorithm, named Exponential Gradient with Momentum (EGM), which is an extension of the Exponential Gradient (EG) algorithm. The EGM algorithm integrates the EG algorithm with the momentum technique, which can effectively use the historical information to improve the performance of the EG algorithm. Through the study, three variants of the EGM algorithm are proposed, namely, EGE, EGR, and EGA, which each use a different formula to update the weights of the portfolio. The EGE algorithm adopts the Exponential Moving Average (EMA) method to update the weights of the portfolio. EGR algorithm employs the RMSProp method to update the weights of the portfolio, and EGA algorithm uses a combination of EGE and EGR to update the weights of the portfolio. **It is worth mentioning that all three variants of the EGM algorithm are implemented in this package.** See [`egm`](@ref).

Let's run the algorithm on the real market data.

```julia
julia> using OnlinePortfolioSelection, YFinance

julia> tickers = ["AAPL", "MSFT", "GOOG"];

julia> querry = [get_prices(ticker, startdt="2019-01-01", enddt="2019-01-12")["adjclose"] for ticker in tickers];

julia> prices = stack(querry) |> permutedims;

julia> rel_pr = prices[:, 2:end]./prices[:, 1:end-1]
3×7 Matrix{Float64}:
0.900393 1.04269 0.997774 1.01906 1.01698 1.0032 0.990182
0.963212 1.04651 1.00128 1.00725 1.0143 0.993574 0.992278
0.971516 1.05379 0.997833 1.00738 0.998495 0.995971 0.987723

julia> # EGE variant
julia> variant = EGE(0.5)

julia> model = egm(rel_pr, variant)

julia> model.b
3×7 Matrix{Float64}:
0.333333 0.33294 0.332704 0.332576 0.332576 0.332634 0.332711
0.333333 0.333493 0.333564 0.333619 0.333614 0.333647 0.33363
0.333333 0.333567 0.333732 0.333805 0.33381 0.333719 0.333659

julia> # EGR variant
julia> variant = EGR(0.)

julia> model = egm(rel_pr, variant)

julia> model.b
3×7 Matrix{Float64}:
0.333333 0.348653 0.350187 0.350575 0.348072 0.345816 0.344006
0.333333 0.327 0.327299 0.326573 0.327852 0.326546 0.327824
0.333333 0.324347 0.322514 0.322853 0.324077 0.327638 0.328169

julia> # EGA variant
julia> variant = EGA(0.5, 0.)

julia> model = egm(rel_pr, variant)

julia> model.b
3×7 Matrix{Float64}:
0.333333 0.349056 0.350429 0.350706 0.348071 0.345757 0.343929
0.333333 0.326833 0.327223 0.326516 0.327857 0.326514 0.327842
0.333333 0.324111 0.322348 0.322779 0.324072 0.327729 0.328229
```

One can calculate the cumulative wealth during the investment period by using the [`sn`](@ref) function. For example, this metric can be calculated for the EGE variant as follows:

```julia
julia> sn(model.b, rel_pr)
8-element Vector{Float64}:
1.0
0.945040143852776
0.990085264306328
0.9890572598499234
1.0001585616811146
1.0100760127054376
1.0076262117924928
0.9976113744470753
```

The outcome suggests that if we had invested during the given period, we would have incurred a loss of approximately 2.8% of our wealth. It's important to note that [`sn`](@ref) automatically considers the last 7 relative prices in this case. Other metrics can be found in the [Performance evaluation](@ref) section.

## References

```@bibliography
Expand Down
4 changes: 2 additions & 2 deletions docs/src/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ end
# Introduction

Online Portfolio Selection (OPS) strategies represent trading algorithms that sequentially allocate capital among a pool of assets with the aim of maximizing investment returns. This forms a fundamental issue in computational finance, extensively explored across various research domains, including finance, statistics, artificial intelligence, machine learning, and data mining. Framed within an online machine learning context, OPS is defined as a sequential decision problem, providing a range of advanced approaches to tackle this challenge. These approaches categorize into benchmarks, “Follow-the-Winner” and “Follow-the-Loser” strategies, “Pattern-Matching” based methodologies, and "Meta-Learning" Algorithms [li2013online](@cite).
This package offers an efficient implementation of OPS algorithms in Julia, ensuring complete type stability. All algorithms yield an [`OPSAlgorithm`](@ref) object, permitting inquiries into portfolio weights, asset count, and algorithm names. Presently, 21 algorithms are incorporated, with ongoing plans for further additions. The existing algorithms are as follows:
This package offers an efficient implementation of OPS algorithms in Julia, ensuring complete type stability. All algorithms yield an [`OPSAlgorithm`](@ref) object, permitting inquiries into portfolio weights, asset count, and algorithm names. Presently, 22 algorithms are incorporated, with ongoing plans for further additions. The existing algorithms are as follows:

!!! note
In the following table, the abbreviations **PM**, **ML**, **FL**, and **FW** stand for **Pattern-Matching**, **Meta-Learning**, **Follow the Loser**, and **Follow the Winner**, respectively.
Expand All @@ -24,7 +24,7 @@ This package offers an efficient implementation of OPS algorithms in Julia, ensu
| 8 | [Anticor](@ref Anti-Correlation-(Anticor)) | FL | 2003 | 19 | [CAEG](@ref Continuous-Aggregating-Exponential-Gradient-(CAEG)) | ML | 2020 |
| 9 | [1/N](@ref) | Market | - | 20 | [OLDEM](@ref Online-Low-Dimension-Ensemble-Method-(OLDEM)) | PM | 2023 |
| 10 | [OLMAR](@ref Online-Moving-Average-Reversion-(OLMAR)) | FL | 2012 | 21 | [AICTR](@ref Adaptive-Input-and-Composite-Trend-Representation-(AICTR)) | FW | 2018 |
| 11 | [Bᴷ](@ref) | PM | 2006 |
| 11 | [Bᴷ](@ref) | PM | 2006 | 22 | [EGM](@ref Exponential-Gradient-with-Momentum-(EGM)) | FW | 2021 |

The available methods can be viewed by calling the [`opsmethods`](@ref) function.

Expand Down
13 changes: 13 additions & 0 deletions docs/src/refs.bib
Original file line number Diff line number Diff line change
Expand Up @@ -278,3 +278,16 @@ @ARTICLE{8356708
pages={6214-6226},
doi={10.1109/TNNLS.2018.2827952}
}

@article{LI2022115889,
title = {Exponential Gradient with Momentum for Online Portfolio Selection},
journal = {Expert Systems with Applications},
volume = {187},
pages = {115889},
year = {2022},
issn = {0957-4174},
doi = {10.1016/j.eswa.2021.115889},
author = {Yuyuan Li and Xiaolin Zheng and Chaochao Chen and Jiawei Wang and Shuai Xu},
keywords = {Online portfolio selection, High-frequency trading, Algorithmic trading, Online learning},
abstract = {Online portfolio selection is a fundamental research problem, which has drawn extensive investigations in both machine learning and computational finance communities. The evolution of electronic trading has contributed to the growing prevalence of High-Frequency Trading (HFT) in recent years. Generally, HFT requires trading strategies to be fast in execution. However, the existing online portfolio selection strategies fail to either satisfy the demand for high execution speed or make effective utilization of historical data. In response, we propose a framework named Exponential Gradient with Momentum (EGM) which integrates EG with an acknowledged optimization method in stochastic learning, i.e., momentum. Specifically, momentum boosts the performance of EG by making full use of historical information. Most essentially, EGM can execute with only constant memory and running time in the number of assets per trading period, thus overcoming the drawback of most online strategies. The theoretical analysis reveals that EGM bounds the regret sublinearly. The extensive experiments conducted on four real-world datasets demonstrate that EGM outperforms relevant strategies with respect to comprehensive evaluation metrics.}
}
199 changes: 199 additions & 0 deletions src/Algos/EGM.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,199 @@
"""
wᵢᵗ⁺¹func(
model::EGE,
η::AbstractFloat,
vᵗ::AbstractVector,
_::AbstractVector,
wᵗ::AbstractVector,
xᵗ::AbstractVector
)
wᵢᵗ⁺¹func(
model::EGR,
η::AbstractFloat,
_::AbstractVector,
mᵗ::AbstractVector,
wᵗ::AbstractVector,
xᵗ::AbstractVector
)
wᵢᵗ⁺¹func(
model::EGA,
η::AbstractFloat,
vᵗ::AbstractVector,
mᵗ::AbstractVector,
wᵗ::AbstractVector,
xᵗ::AbstractVector
)
Calculate the weights for the next period using the EGM framework.
# Method 1: EGE
## Arguments
- `model::EGE`: EGM framework with `gamma1` parameter. See [`EGE`](@ref) type.
- `η::AbstractFloat`: learning rate.
- `vᵗ::AbstractVector`: vector of size `n_assets`.
- `_::AbstractVector`: dummy vector of size `n_assets`.
- `wᵗ::AbstractVector`: vector of size `n_assets` containing the weights for the current period.
- `xᵗ::AbstractVector`: vector of size `n_assets` containing the relative prices for the current period.
## Returns
- `::AbstractVector`: vector of size `n_assets` containing the weights for the next period.
# Method 2: EGR
## Arguments
- `model::EGR`: EGM framework with `gamma2` parameter. See [`EGR`](@ref) type.
- `η::AbstractFloat`: learning rate.
- `_::AbstractVector`: dummy vector of size `n_assets`.
- `mᵗ::AbstractVector`: vector of size `n_assets`.
- `wᵗ::AbstractVector`: vector of size `n_assets` containing the weights for the current period.
- `xᵗ::AbstractVector`: vector of size `n_assets` containing the relative prices for the current period.
## Returns
- `::AbstractVector`: vector of size `n_assets` containing the weights for the next period.
# Method 3: EGA
## Arguments
- `model::EGA`: EGM framework with `gamma1` and `gamma2` parameters. See [`EGA`](@ref) type.
- `η::AbstractFloat`: learning rate.
- `vᵗ::AbstractVector`: vector of size `n_assets`.
- `mᵗ::AbstractVector`: vector of size `n_assets`.
- `wᵗ::AbstractVector`: vector of size `n_assets` containing the weights for the current period.
- `xᵗ::AbstractVector`: vector of size `n_assets` containing the relative prices for the current period.
## Returns
- `::AbstractVector`: vector of size `n_assets` containing the weights for the next period.
"""
function wᵢᵗ⁺¹func(
model::EGE,
η::AbstractFloat,
vᵗ::AbstractVector,
_::AbstractVector,
wᵗ::AbstractVector,
xᵗ::AbstractVector
)
γ₁ = model.gamma1
gᵗ = xᵗ./sum(xᵗ.*wᵗ)
vᵗ⁺¹ = vᵗ .= γ₁*vᵗ.+(1-γ₁).*gᵗ
numerator_ = wᵗ.*exp.(η.*vᵗ⁺¹)
return numerator_/sum(numerator_)
end

function wᵢᵗ⁺¹func(
model::EGR,
η::AbstractFloat,
_::AbstractVector,
mᵗ::AbstractVector,
wᵗ::AbstractVector,
xᵗ::AbstractVector
)
γ₂ = model.gamma2
gᵗ = xᵗ./sum(xᵗ.*wᵗ)
mᵗ⁺¹ = mᵗ .= γ₂*mᵗ.+(1-γ₂).*(gᵗ.^2)
numerator_ = ((wᵗ.*exp.(η.*gᵗ))./(sqrt.(mᵗ⁺¹).+1e-8))
return numerator_/sum(numerator_)
end

algname(::EGE) = "EGE"
algname(::EGR) = "EGR"
algname(::EGA) = "EGA"

function wᵢᵗ⁺¹func(
model::EGA,
η::AbstractFloat,
vᵗ::AbstractVector,
mᵗ::AbstractVector,
wᵗ::AbstractVector,
xᵗ::AbstractVector
)
γ₁ = model.gamma1
γ₂ = model.gamma2
gᵗ = xᵗ./sum(xᵗ.*wᵗ)
vᵗ⁺¹ = vᵗ .= γ₁*vᵗ.+(1-γ₁).*gᵗ
mᵗ⁺¹ = mᵗ .= γ₂*mᵗ.+(1-γ₂).*(gᵗ.^2)
numerator_ = (wᵗ.*exp.(η.*vᵗ⁺¹))./(sqrt.(mᵗ⁺¹).+1e-8)
return numerator_/sum(numerator_)
end

"""
egm(rel_pr::AbstractMatrix, model::EGMFramework, η::AbstractFloat=0.05)
Run the Exponential Gradient with Momentum (EGM) algorithm. This framework contains three \
variants: [`EGE`](@ref), [`EGR`](@ref) and [`EGA`](@ref).
# Arguments
- `rel_pr::AbstractMatrix`: matrix of size `n_assets` by `n_periods` containing the \
relative prices.
- `model::EGMFramework`: EGM framework. [`EGE`](@ref), [`EGR`](@ref) or [`EGA`](@ref) can \
be used.
- `η::AbstractFloat=0.05`: learning rate.
# Returns
- `::OPSAlgorithm`: An [`OPSAlgorithm`](@ref) object.
# Example
```julia
julia> using OnlinePortfolioSelection, YFinance
julia> tickers = ["AAPL", "MSFT", "GOOG"];
julia> querry = [get_prices(ticker, startdt="2019-01-01", enddt="2019-01-12")["adjclose"] for ticker in tickers];
julia> prices = stack(querry) |> permutedims;
julia> rel_pr = prices[:, 2:end]./prices[:, 1:end-1]
3×7 Matrix{Float64}:
0.900393 1.04269 0.997774 1.01906 1.01698 1.0032 0.990182
0.963212 1.04651 1.00128 1.00725 1.0143 0.993574 0.992278
0.971516 1.05379 0.997833 1.00738 0.998495 0.995971 0.987723
julia> # EGE variant
julia> variant = EGE(0.5)
julia> model = egm(rel_pr, variant);
julia> model.b
3×7 Matrix{Float64}:
0.333333 0.33294 0.332704 0.332576 0.332576 0.332634 0.332711
0.333333 0.333493 0.333564 0.333619 0.333614 0.333647 0.33363
0.333333 0.333567 0.333732 0.333805 0.33381 0.333719 0.333659
julia> # EGR variant
julia> variant = EGR(0.)
julia> model = egm(rel_pr, variant);
julia> model.b
3×7 Matrix{Float64}:
0.333333 0.348653 0.350187 0.350575 0.348072 0.345816 0.344006
0.333333 0.327 0.327299 0.326573 0.327852 0.326546 0.327824
0.333333 0.324347 0.322514 0.322853 0.324077 0.327638 0.328169
julia> # EGA variant
julia> variant = EGA(0.5, 0.)
julia> model = egm(rel_pr, variant);
julia> model.b
3×7 Matrix{Float64}:
0.333333 0.349056 0.350429 0.350706 0.348071 0.345757 0.343929
0.333333 0.326833 0.327223 0.326516 0.327857 0.326514 0.327842
0.333333 0.324111 0.322348 0.322779 0.324072 0.327729 0.328229
```
# References
> [Exponential Gradient with Momentum for Online Portfolio Selection](https://doi.org/10.1016/j.eswa.2021.115889)
"""
function egm(rel_pr::AbstractMatrix, variant::EGMFramework, η::AbstractFloat=0.05)
η>0 || ArgumentError("η must be positive") |> throw
n_assets, n_periods = size(rel_pr)
b = ones(n_assets, n_periods)/n_assets
vₜ = zeros(n_assets)
mₜ = zeros(n_assets)

for t 1:n_periods-1
b[:, t+1] = wᵢᵗ⁺¹func(variant, η, vₜ, mₜ, b[:, t], rel_pr[:, t])
end

return OPSAlgorithm(n_assets, b, algname(variant))
end
6 changes: 5 additions & 1 deletion src/OnlinePortfolioSelection.jl
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ include("Types/Clustering.jl")
include("Types/PAMR.jl")
include("Types/CWMR.jl")
include("Types/AICTR.jl")
include("Types/EGM.jl")
include("Algos/CRP.jl")
include("Algos/CW-OGD.jl")
include("Algos/EG.jl")
Expand All @@ -34,17 +35,19 @@ include("Algos/CWMR.jl")
include("Algos/CAEG.jl")
include("Algos/OLDEM.jl")
include("Algos/AICTR.jl")
include("Algos/EGM.jl")
include("Tools/metrics.jl")
include("Tools/show.jl")
include("Tools/tools.jl")
include("Tools/cornfam.jl")

export up, eg, cornu, cornk, dricornk, crp, bs, rprt, anticor, olmar, bk, load, mrvol, cwogd
export uniform, cluslog, pamr, ppt, cwmr, caeg, oldem, aictr
export uniform, cluslog, pamr, ppt, cwmr, caeg, oldem, aictr, egm
export OPSMetrics, sn, mer, apy, ann_std, ann_sharpe, mdd, calmar
export OPSAlgorithm, KMNLOG, KMDLOG, PAMR, PAMR1, PAMR2
export CWMRD, CWMRS, Var, Stdev
export SMA, EMA, PP
export EGE, EGR, EGA
export opsmethods

@setup_workload begin
Expand Down Expand Up @@ -106,6 +109,7 @@ function opsmethods()
println(" CAEG: Continuous Aggregating Exponential Gradient - Call `caeg`")
println(" OLDEM: Online Low Dimension Ensemble Method - Call `oldem`")
println(" AICTR: Adaptive Input and Composite Trend Representation - Call `aictr`")
println(" EGM: Exponential Gradient with Momentum - Call `egm`")
end
# COV_EXCL_STOP

Expand Down
Loading

0 comments on commit f9527f4

Please sign in to comment.