In [1]:
get_ipython().ast_node_interactivity = 'all'
import os
import matplotlib.pyplot as plt
import numpy as np
import matplotlib
import math
from PIL import Image
import pandas as pd
import random
from collections import defaultdict
In [2]:
targets = (
    #'MATRIKS.AKSA',
    #'MATRIKS.AKSEN',
    #'MATRIKS.AKBNK',
    'MATRIKS.ADEL',
    #'MATRIKS.ARCLK',
    #'MATRIKS.EREGL',
    #'MATRIKS.VESTL',
    'MATRIKS.VESBE',
    #'MATRIKS.BIMAS',
    #'MATRIKS.THYAO',
    #'MATRIKS.TAVHL',
    #'MATRIKS.MTRKS',
    #'MATRIKS.MGROS',
    'MATRIKS.SOKM',
    'MATRIKS.TCELL',
    'MATRIKS.GARAN',
    'MATRIKS.ENJSA',
    #'MATRIKS.KCHOL',
    #'MATRIKS.SAHOL',
    #'MATRIKS.MAVI',
    #'MATRIKS.PGSUS',
)
In [3]:
# Fetch data
import miniclickhouse

ch = miniclickhouse.connect(database="algotrade")

qry = """
select * from daily_price_pivot
where `date` > today() - 365
and `MATRIKS.CCOLA` is not null
order by date asc
"""

symbol_data = defaultdict(lambda: [])

for row in ch.execute(qry):
    for target in targets:
        symbol_data[target].append(row[target])
In [4]:
def start_from_zero(vals):
    return [x - vals[0] for x in vals]
In [5]:
_ = plt.figure(dpi=200)

for target in targets:
    _ = plt.plot(start_from_zero(symbol_data[target]), label=target)

_ = plt.legend()
Out:
<Figure size 1200x800 with 1 Axes>
In [6]:
def portfolio_to_prices(portfolio):
    assert len(portfolio) == len(targets)
    prices = []
    for row in zip(*[symbol_data[target] for target in targets]):
        p = 0
        for v, w in zip(portfolio, row):
            p += v * w
        prices.append(p)
    return prices
In [7]:
_ = plt.figure(dpi=200)
_ = plt.plot(portfolio_to_prices([1 / len(targets)] * len(targets)))
Out:
<Figure size 1200x800 with 1 Axes>
In [8]:
def random_weights(N):
    w = [random.uniform(0, 1) for _ in range(N)]
    s = sum(w)
    return [w / s for w in w]

def random_portfolio():
    return random_weights(len(targets))

_ = plt.figure(dpi=200)
for _ in range(200):
    _ = plt.plot(start_from_zero(portfolio_to_prices(random_portfolio())), linewidth=0.2)
Out:
<Figure size 1200x800 with 1 Axes>

As you can see, there is a quite a bit of variance on the final results.

In [9]:
def returns(values):
    values = list(values)
    prev = values[0]

    for val in values[1:]:
        yield (val - prev) / prev
        prev = val

Let’s try to plot random portfolios to see if we can see the expected shape.

In [10]:
Xs = []
Ys = []
Cs = []

minvol_at_target = {}

def record_portfolio(params):
    values = portfolio_to_prices(params)
    rets = list(returns(values))
    std = np.std(rets)
    # std += np.std(params) * std
    mean = np.mean(rets)
    pct = (values[-1] - values[0]) / values[0] * 100
    rpct = int(round(pct))
    
    if "min" not in minvol_at_target:
        minvol_at_target["min"] = (std, params)
    else:
        mvs, mvp = minvol_at_target["min"]
        if std < mvs:
            minvol_at_target["min"] = (std, params)
    
    if rpct not in minvol_at_target:
        minvol_at_target[rpct] = (std, params)
    else:
        mvs, mvp = minvol_at_target[rpct]
        if std < mvs:
            minvol_at_target[rpct] = (std, params)
    Xs.append(std)
    Ys.append(pct)
    Cs.append(mean / std * np.sqrt(249))
    return values, std, mean, pct


for _ in range(512):
    _ = record_portfolio(random_portfolio())

# Lowest volatility
from scipy.optimize import *

def fitness(params):
    params = params / np.sum(params)
    values, std, mean, pct = record_portfolio(params)
    return std

_ = dual_annealing(fitness, [(0, 1)] * len(targets), maxiter=1000)

# Min vol at target

def fitness(params, target):
    params = params / np.sum(params)
    values, std, mean, pct = record_portfolio(params)
    if pct < target:
        return std + abs(pct - target) ** 2
    return std

for target in (5, 10, 15, 20):
    values, std, mean, pct = record_portfolio(minvol_at_target["min"][1])
    _ = dual_annealing(fitness,
                       [(0, 1)] * len(targets),
                       args=(pct + target, ),
                       maxiter=1000)
    print(int(pct + target))

_ = plt.figure(dpi=200)
_ = plt.scatter(Xs, Ys, s=3, c=Cs, cmap="Wistia")
_ = plt.colorbar()


values, std, mean, pct = record_portfolio(minvol_at_target["min"][1])
_ = plt.scatter(std, pct, marker="*", label=f"Min volatility ({pct:.2f}%)")

values, std, mean, pct = record_portfolio(minvol_at_target["min"][1])
pct = int(round(pct))
for target in (pct + 5, pct + 10, pct + 15, pct + 20):
    values, std, mean, pct = record_portfolio(minvol_at_target[target][1])
    _ = plt.scatter(std, pct, marker="*", label=f"Min volatility ({target}%)")
_ = plt.legend()
Out:
54
59
64
69
Out: