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:
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:
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:
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: