MATLAB ir Python palyginimas algoritminėje prekyboje


Šiame darbe bus palygintas algoritminės prekybos uždavinių įgyvendinimas MATLAB ir Python programavimo kalbomis, vertinant analogiškų užduočių programinio kodo kompiliavimo laiką. Norint kad Python kodai būtų kuo artimesni MATLAB, naudosime papildomas Python bibliotekas grafikų brėžimui (Seaborn ir Matplotlib), vektorinėms operacijoms (Numpy), bei duomenų lentelių (struktūrų) apdorojimui (Pandas).

Pradžioje pažiūrėsime, kaip abiejomis kalbos užprogramuoti algoritmai veikia su paprastomis vektorinėmis operacijomis, paduodant didelės apimties vektorius, o po to palyginsime abiejų kalbų veikimą konkrečiuose prekybos indikatorių ir strategijų uždaviniuose.

Norėdami tiksliai išmatuoti laikus, naudosimės Python magic komanda %%timeit, ir analogiškos paskirties komanda MATLAB aplinkoje timeit.


Darbas atliktas ipython aplinkoje, naudojantis Jupyter Notebook, tad Python programinis kodas buvo kompiliuotas tiesiogiai pasirinktuose kodo langeliuose (cells), o laikas, pasinaudojant %%timeit, automatiškai generuojamas po kodo dalimi. MATLAB atveju, kadangi timeit funkcijai turime paduoti apsirašytą funkciją, programinį kodą teko išdalyti į atskiras funkcijas ir po to, kreipiantis į laiko skaičiavimo funkciją, gauti rezultatą.

Vektorinių ir iteracinių operacijų užduotėles atliksime su didelės apimties masyvais - kiekvieno jų ilgis bus 100000 elementų - ir tukstančių eilučių ir stulpelių matricas. Atliekant konkrečių finansinių strategijų palyginimus naudosime istorinius rinkos duomenis pasirinktiems finansiniams instrumentams.


Smulkių užduotėlių atlikimas

Visoms vektorizuotoms operacijoms atlikti įsikeliame Python biblioteka numpy.

In [1]:
import numpy as np

1) Dviejų vektorių suma

In [2]:
%%timeit
a = np.random.standard_normal(100000)
b = np.random.standard_normal(100000)
c = 0

for i in range(0, len(a)):
    c = c + a[i] + b[i]
10 loops, best of 3: 85.4 ms per loop

MATLAB aplinkoje:

function pirma()
    a = randn(1,100000);
    b = randn(1,100000);
    c = 0;
    for i = 1:length(a)
        c = c + a(i) + b(i);
    end
end

Skaičiuojame laiką:

time1 = timeit(@()pirma());

Gauname rezultatą: 0.005852603287941

Vektoriškai

In [3]:
%%timeit
a = np.random.standard_normal(100000)
b = np.random.standard_normal(100000)
sum_a = np.sum(a)
sum_b = np.sum(b)
suma = sum_a + sum_b
The slowest run took 4.16 times longer than the fastest. This could mean that an intermediate result is being cached.
100 loops, best of 3: 15.7 ms per loop

MATLAB

function pirmav()
    a = randn(1,100000);
    b = randn(1,100000);
    c = sum(a) + sum(b);
end

Skaičiuojame laiką:

time1v = timeit(@()pirmav());

0.005332458862336

Taigi net ir skaičiuojant be ciklų, dviejų vektorių sumą MATLAB paskaičiuoja apytiksliai 3 kartus greičiau.

2) Teigiamų elementų anuliavimas

In [4]:
%%timeit
a = np.random.standard_normal(100000)
for i in range(0, len(a)):
    if a[i] > 0:
        a[i] = 0
10 loops, best of 3: 61.6 ms per loop

MATLAB

function antra()
    a = randn(1,100000);
    for i = 1:length(a)
        if a(i) > 0 
            a(i) = 0;
        end
    end
end
time2 = timeit(@()antra());

0.005508749422692

Vektoriškai

In [5]:
%%timeit
a = np.random.standard_normal(100000)
a[a > 0] = 0
100 loops, best of 3: 8.84 ms per loop

MATLAB

function antrav()
    a = randn(1,100000);
    a(a < 0) = 0;
end
time2v = timeit(@()antrav());

0.003879545372827

3) Išmetimas didesnių nei 6 elementų iš masyvo

In [6]:
%%timeit
a = np.random.randint(1, 10, 100000)
b = np.array([], dtype=int)
for i in range(0, len(a)):
    if a[i] <= 6:
        b = np.append(b, a[i])
1 loop, best of 3: 2.29 s per loop

MATLAB

function trecia()
    a = randi(10,1,100000);
    b = [];
    for i = 1:length(a)
        if a(i) <= 6
            b = [b a(i)];
        end
    end
end
time3 = timeit(@()trecia());

0.143079270010462

Vektoriškai

In [7]:
%%timeit
a = np.random.randint(1, 10, 100000)
b = a[a < 6]
100 loops, best of 3: 4.08 ms per loop

MATLAB

function treciav()
    a = randi(10,1,100000);
    b = [];
    b = a(a <= 6);
end
time3v = timeit(@()treciav());

0.003605160461064

4) Dviejų vienodų šalia esančių elementų radimas

In [8]:
%%timeit
a = np.random.randint(1, 5, 100000)
A = np.array([])
j = 0
for i in range(1, len(a)):
    if a[i] == a[i - 1]:
        A = np.append(A, i) 
1 loop, best of 3: 714 ms per loop

MATLAB

function ketvirta()
    a = randi(5,1,100000);
    A = [];
    for i=2:length(a)
        if a(i) == a(i-1)
            A = [A i];
        end
    end
end
time4 = timeit(@()ketvirta());

0.050539233103017

Vektoriškai

In [9]:
%%timeit
b = np.random.randint(1, 5, 100000)
b1 = np.append(0, b[1:len(b)])
b2 = np.append(1, b[0:(len(b) - 1)])
I = b1 == b2
A = I.nonzero()[0]
The slowest run took 14.36 times longer than the fastest. This could mean that an intermediate result is being cached.
1000 loops, best of 3: 1.94 ms per loop

MATLAB

function ketvirtav()
    a = randi(5,1,100000);
    b = [0 a(2:length(a))];
    c = [1 a(1:length(a)-1)];
    I = find(b == c);
end
time4v = timeit(@()ketvirtav());

0.005887764089270

5) Elementų iš vektoriaus a, didesnių už elementų iš vektoriaus b, radimas

In [10]:
%%timeit
a = np.random.standard_normal(100000)
b = np.random.standard_normal(100000)
A = np.array([])
for i in range(0, len(a)):
    if a[i] > b[i]:
        A = np.append(A, i)
1 loop, best of 3: 3.78 s per loop

MATLAB

function penkta()
    a = randn(1,100000);
    b = randn(1,100000);
    A = [];
    for i=1:length(a)
        if a(i) > b(i)
            A = [A i];
        end
    end
end
time5 = timeit(@()penkta());

0.119080873515373

Vektoriškai

In [11]:
%%timeit
a = np.random.standard_normal(100000)
b = np.random.standard_normal(100000)
ind = np.nonzero(a > b)[0]
100 loops, best of 3: 16.2 ms per loop

MATLAB

function penktav()
    a = randn(1,100000);
    b = randn(1,100000);
    I = find(a > b);
end
time5v = timeit(@()penktav());

0.006567787087173

6) Elementų perstumimas vektoriuje pakartojant paskutinįjį

In [12]:
%%timeit 
a = np.random.randint(1, 10, 100000)
for i in range(1, len(a)):
    a[i-1] = a[i]
10 loops, best of 3: 30.3 ms per loop

MATLAB

function sesta()
    a = randi(10,1,100000);
    for i=2:length(a)
        a(i-1) = a(i);
    end
end
time6 = timeit(@()sesta());

0.003351292270603

Vektoriškai

In [13]:
%%timeit
a = np.random.randint(1, 10, 100000)
a = np.append(a[1:len(a)], a[len(a)-1])
100 loops, best of 3: 3 ms per loop

MATLAB

function sestav()
    a = randi(10,1,100000);
    b = a(2:length(a));
    a_new = [b a(length(a))];
end
time6v = timeit(@()sestav());

0.003481152925233

7) Sukeitimas elementų tvarkos vektoriuje

In [14]:
%%timeit
a = np.random.randint(1, 10, 100000)
for i in range(0, len(a)/2):
    b = a[i]
    a[i] = a[len(a) - i - 1]
    a[len(a) - i - 1] = b
10 loops, best of 3: 40 ms per loop

MATLAB

function septinta()
    a = randi(10,1,100000);
    for i=1:length(a)/2
        b=a(i);
        a(i) = a(length(a) - i + 1);
        a(length(a) - i + 1) = b; 
    end    
end
time7 = timeit(@()septinta());

0.003337677847134

Vektoriškai

In [15]:
%%timeit 
a = np.random.randint(1, 10, 100000)
new_a = np.flip(a, 0)
100 loops, best of 3: 2.89 ms per loop

MATLAB

function septintav()
    a = randi(10,1,100000);
    new_a = fliplr(a);
end
time7v = timeit(@()septintav());

0.002688025486201

8) Kas antro elemento vektoriuje užnulinimas

In [16]:
%%timeit 
a = np.random.randint(1, 10, 100000)
for i in range(0, len(a), 2):
    a[i] = 0
100 loops, best of 3: 12.1 ms per loop

MATLAB

function astunta()
    a = randi(10,1,100000);
    for  i=1:2:length(a)
        a(i) = 0;
    end
end
time8 = timeit(@()astunta());

0.002787166416080

Vektoriškai

In [17]:
%%timeit
a = np.random.randint(1, 10, 100000)
a[0:len(a):2] = 0
100 loops, best of 3: 2.95 ms per loop

MATLAB

function astuntav()
    a = randi(10,1,100000);
    a(1:2:end) = 0;
end
time8v = timeit(@()astuntav());

0.002679298291669

9) Rasti matricos eilučių arba stulpelių vidurkius

In [18]:
%%timeit
a = np.random.standard_normal((1000, 2000))
A = np.array([])
for i in range(0, a.shape[0]):
    A = np.append(A, np.mean(a[i, :]))
1 loop, best of 3: 205 ms per loop

MATLAB

function devinta()
    a = randn(1000,2000);
    A = [];
    for i=1:size(a,1)
        A = [A mean(a(i,:))];
    end
end
time9 = timeit(@()devinta());

0.092300604375676

Vektoriškai

In [19]:
%%timeit
a = np.random.standard_normal((1000, 2000))
rows_mean = np.mean(a, 1)
10 loops, best of 3: 168 ms per loop

MATLAB

function devintav()
    a = randn(1000,2000);
    b = mean(a,2);
    c = mean(a,1);
end
time9v = timeit(@()devintav());

0.063612570511368

10) Gauti matricos diagonalinius elementus

In [20]:
%%timeit
a = np.random.randint(0, 10, (1000, 1000))
A = np.array([])
for i in range(0, np.size(a, 0)):
    A = np.append(A, a[i, i])
10 loops, best of 3: 40.5 ms per loop

MATLAB

function desimta()
    a = randi(1000,1000);
    A = [];
    for i=1:size(a,1)
        A = [A a(i,i)];
    end
end
time10 = timeit(@()desimta());

0.030851380415268

Vektoriškai

In [21]:
%%timeit
a = np.random.randint(0, 10, (1000, 1000))
i, j = np.indices(a.shape)
diagonal = a[i == j]
10 loops, best of 3: 37.3 ms per loop

MATLAB

function desimtav()
    a = randi(1000,1000);
    diagonal = a(1:size(a,1)+1:end);
end
time10v = timeit(@()desimtav());

0.028721246773993

Laikų palyginimo lentelės

Užduotėlių su ciklais palyginimas:

Užduoties nr. Laikas Py (s) Laikas m (s) Santykis Py(s)/m(s)
1 0.0854 0.0058 14.7
2 0.0616 0.0055 11.2
3 2.29 0.14 16.4
4 0.714 0.051 14
5 3.78 0.119 31.8
6 0.0303 0.0034 8.9
7 0.040 0.0033 12.1
8 0.0121 0.00279 4.3
9 0.205 0.0923 2.221
10 0.0405 0.03085 1.313

Užduotėlių su vektoriais palyginimas:

Užduoties nr. Laikas Py (s) Laikas m (s) Santykis Py(s)/m(s)
1 0.0157 0.0053 2.962
2 0.00884 0.00388 2.278
3 0.00408 0.00360 1.133
4 0.00194 0.00589 0.329
5 0.0162 0.0066 2.455
6 0.003 0.0034 0.882
7 0.00289 0.002688 1.075
8 0.00295 0.00268 1.101
9 0.168 0.0636 2.641
10 0.0373 0.02872 1.299

Matome, kad beveik visose darbo su matricomis ir masyvais užduotyse MATLAB veikimo greitis pranoko Python. Šie skirtumai nėra itin ryškiai matomi lyginant vektorizuotą uždavinių variantą, kur daugumoje užduotėlių Python kompiliavo kodą 1-2 kartų lėčiau, tačiau daug labiau išryškėjo ciklo panaudojimo variantuose, kur MATLAB for ciklas suveikdavo vidutiniškai 10 kartų greičiau.


Sumuotų laikų grafikai

Toliau pateikiami Python ir MATLAB sumuotų laikų per for ciklų užduotėles ir vektorinį jų atvejį grafikai: mkvm jnfjd

Iš grafikų galime matyti, kad visoms 10 užduotėlių atlikti MATLAB aplenkė Python, bet vektorizuotu atveju šis pranašumas ne toks ryškus.

Visgi, ciklais užrašinėti vektorines operacijas nėra efektyvu, tad dabar palyginsime abiejų kalbų greitį realiuose algoritminės prekybos uždaviniuose.

Duomenų užkrovimas, dviejų skirtingų (akcijos ir forex) laiko eilučių sulyginimas papildant tuščias dienas arba jas ištrinant.

Duomenų užkrovimas į Python ir MATLAB

In [1]:
import matplotlib.dates as dates
import matplotlib.pyplot as plt
import pandas as pd
import numpy as np
import timeit
In [2]:
%%timeit
prad = pd.read_csv('C:\Users\Vartotojas\Desktop\Pitonas\AMZN 1min. NASDAQ Ilgesni.txt', chunksize=5000, low_memory=False, iterator=True, infer_datetime_format=True, parse_dates=[['Date','Time']])
data_min = pd.concat(prad, ignore_index=True)
1 loop, best of 3: 967 ms per loop
In [4]:
data_min.tail()
Out[4]:
Date_Time Open High Low Close Up Down
49786 2017-09-22 22:56:00 954.99 955.10 954.88 954.94 12600 13205
49787 2017-09-22 22:57:00 954.96 955.02 954.66 954.66 13137 8931
49788 2017-09-22 22:58:00 954.65 954.85 954.61 954.84 10395 5906
49789 2017-09-22 22:59:00 954.81 955.00 954.59 954.80 22380 24481
49790 2017-09-22 23:00:00 954.82 955.20 954.42 954.53 35749 30971

MATLAB

function [e] = AMZN()
    fid = fopen('AMZN.txt');
    b = textscan(fid,'%s %s %f %f %f %f %f %f','Delimiter',',','HeaderLines',1); %,'Delimiter',',' 
    d = []; 
    d.date = datenum(b{1},'mm/dd/yyyy')';
    d.time = datenum(b{2},'HH:MM')';
    d.date_time = d.date + d.time - datenum(2017,01,01);
    d.open = b{3}'; 
    d.high = b{4}'; 
    d.low = b{5}'; 
    d.close = b{6}'; 
    d.up = b{7}';
    d.down = b{8}';
    disp(d); 
    fclose(fid);
end

Skaičiuojame laiką

timeamzn = timeit(@()AMZN());

1.887481928995748

Kaip ir tikinių duomenų importavimo atveju, Python beveik du kartus gretesnis už MATLAB.

Kadangi šioje užduotyje turėsime panaudoti ne tik akcijų duomenis (Amazon), bet ir Forex valiutų kursą, įsikelsime EUR/USD kurso kitimo duomenis:

Python MATLAB
Laikai (s) 0.967 1.887

gg

In [5]:
%%timeit
prad = pd.read_csv('C:\Users\Vartotojas\Desktop\Pitonas\EURUSD 1min 6 menesiai.txt', chunksize=5000, low_memory=False, iterator=True, infer_datetime_format=True, parse_dates=[['Date','Time']])
eurusd = pd.concat(prad, ignore_index=True)
1 loop, best of 3: 3.58 s per loop
In [7]:
eurusd.tail()
Out[7]:
Date_Time Open High Low Close Up Down
184211 2017-09-22 23:55:00 1.19478 1.19478 1.19461 1.19468 0 0
184212 2017-09-22 23:56:00 1.19467 1.19476 1.19436 1.19449 0 0
184213 2017-09-22 23:57:00 1.19450 1.19473 1.19447 1.19468 0 0
184214 2017-09-22 23:58:00 1.19467 1.19468 1.19451 1.19460 0 0
184215 2017-09-22 23:59:00 1.19461 1.19467 1.19442 1.19466 0 0

MATLAB

function [e] = eurusd()
    fid = fopen('EURUSD 1min 6 menesiai.txt');
    c = textscan(fid,'%s %s %f %f %f %f %f %f','Delimiter',',','HeaderLines',1); %,'Delimiter',',' 
    e = []; 
    e.date = datenum(c{1},'mm/dd/yyyy')';
    e.time = datenum(c{2},'HH:MM')';
    e.date_time = e.date + e.time - datenum(2017,01,01);
    e.open = c{3}'; 
    e.high = c{4}'; 
    e.low = c{5}'; 
    e.close = c{6}'; 
    e.up = c{7}';
    e.down = c{8}';
    disp(e); 
    fclose(fid);
end

Skaičiuojame laiką:

timeeur = timeit(@()eurusd());

6.939847207225888

Python MATLAB
Laikai (s) 3.58 6.94

Duomenų sulyginimas išmetant eilutes, kurios nesutampa duomenyse pagal datą

In [13]:
%%timeit 
data_min1 = data_min[data_min['Date_Time'].isin(eurusd['Date_Time'])]
eurusd1 = eurusd[eurusd['Date_Time'].isin(data_min['Date_Time'])]
10 loops, best of 3: 29.2 ms per loop

MATLAB

function [d,e] = sulyginimas(d,e)
    [bendros_datos, ie, id] = intersect(e.date_time, d.date_time);

    e.date_time = e.date_time(ie);
    e.open = e.open(ie);
    e.high = e.high(ie);
    e.low = e.low(ie);
    e.close = e.close(ie);

    d.date_time = d.date_time(id);
    d.open = d.open(id);
    d.high = d.high(id);
    d.low = d.low(id);
    d.close = d.close(id);
end

Skaičiuojame laiką:

timesul = timeit(@()sulyginimas(d,e))

0.021037213890021

Python MATLAB
Laikai (s) 0.0292 0.0210

Matome, kad šiuo atveju, lyginant dviejų duomenų eilutes Python suveikia lėčiau, tačiau skirtumas gana nežymus.

Brėžiame duomenų grafiką EUR ir USD kainomis

In [18]:
fig = plt.figure(figsize=(15,9))
fig.add_axes()
ax = fig.add_subplot(111)
ax.plot(data_min1['Date_Time'], data_min1['Close'], color='blue', label="AMZN USD", linewidth=0.5)
ax.plot(data_min1['Date_Time'], data_min1['Close'].values/eurusd1['Close'].values, color='red', label="AMZN EUR", linewidth=0.5)

# Nustatome asiu parametrus, datu periodiskuma
ax.xaxis.set_minor_locator(dates.DayLocator(interval=5))
ax.xaxis.grid(True, which='minor')
ax.xaxis.set_major_locator(dates.MonthLocator(interval=1))
ax.xaxis.set_major_formatter(dates.DateFormatter('%Y-%m-%d'))
plt.setp(ax.xaxis.get_majorticklabels(), rotation=90, fontsize=7)

plt.title("AMZN kursai USD ir EUR")
ax.set(title='AMZN kursai USD ir EUR', ylabel='Kaina', xlabel='Data')
fig.autofmt_xdate()
ax.autoscale_view()
plt.legend(loc='upper left')
plt.grid()
plt.show()

MATLAB

figure;
plot(e.date_time, d.close./e.close, 'b');
hold on;
plot(d.date_time, d.close, 'r');
hold off;
datetick('x','yyyy-mm-dd','keepticks','keeplimits');
legend('AMZN USD','AMZN EUR');
title('AMZN');
xlabel('Date');
ylabel('Close price'); 
grid on;

pav

Duomenų užkrovimas, atvaizdavimas, minutinių barų sudarymas iš tikinių duomenų, slankių vidurkių ir MACD algoritmai

Duomenų importavimas į Python ir MATLAB

Prieš pradedant skaičiavimus, įsikeliame reikalingus Python paketus:

In [1]:
from matplotlib.finance import candlestick_ohlc
import matplotlib.dates as dates
import matplotlib.pyplot as plt
import pandas as pd
import numpy as np
import timeit
from IPython.display import clear_output
In [2]:
%%timeit
prad = pd.read_csv('C:\Users\Vartotojas\Desktop\Pitonas\AAPL tikiniai 2 savaiciu.txt', chunksize=10000, low_memory=False, iterator=True, infer_datetime_format=True, parse_dates=[['Date', 'Time']])
data_tik = pd.concat(prad, ignore_index=True)
1 loop, best of 3: 32.5 s per loop
In [4]:
data_tik.tail()
Out[4]:
Date_Time Open High Low Close Up Down
1547924 2017-09-22 22:59:59 151.79 151.79 151.79 151.79 200 0
1547925 2017-09-22 22:59:59 151.79 151.79 151.79 151.79 200 0
1547926 2017-09-22 22:59:59 151.79 151.79 151.79 151.79 2300 0
1547927 2017-09-22 22:59:59 151.79 151.79 151.79 151.79 1336 0
1547928 2017-09-22 22:59:59 151.79 151.79 151.79 151.79 100 0

Matome, kad duomenų užkrovimas užima gana daug laiko, tačiau importuojame beveik pusantro milijonų eilučių ir kartu atliekame datos formatavimą datų stulpelyje.

MATLAB

Analogiškai paduodame nuskaityti duomenis MATLAB aplinkoje:

function d = AAPL()
    fid = fopen('AAPL tikiniai 2 savaiciu.txt');
    tikiniai = textscan(fid,'%s %s %f %f %f %f %f %f','Delimiter',',','HeaderLines',1); %,'Delimiter',',' 
    d = []; 
    d.date = datenum(tikiniai{1},'mm/dd/yyyy')';
    d.time = datenum(tikiniai{2},'HH:MM:SS')';
    d.date_time = d.date + d.time - datenum(2017,01,01);
    d.open = tikiniai{3}'; 
    d.high = tikiniai{4}'; 
    d.low = tikiniai{5}'; 
    d.close = tikiniai{6}'; 
    d.up = tikiniai{7}';
    d.down = tikiniai{8}';
    disp(d); 
    fclose(fid);
timeDuom = timeit(@()AAPL());

62.952276957169374

Matome, kad šiuo atveju Python duomenų importavimas, pasinaudojant paketo pandas funkcija read_csv, duomenis nuskaitant gabaliukais ir automatiškai konvertuojant datas yra du kartus greitesnis, nepaisant to, kad kodas gerokai trumpesnis.

Python MATLAB
Laikai (s) 32.5 62.9

h

Grafikų brėžimas

Atvaizduosime gautus tikinių duomenų grafikus:

In [5]:
fig = plt.figure(figsize=(15,9))
fig.add_axes()
ax = fig.add_subplot(111)
ax.plot(data_tik['Date_Time'], data_tik['Close'], color='green', label="AAPL", linewidth=0.8)
ax.xaxis.set_minor_locator(dates.MinuteLocator(interval=360))
ax.xaxis.set_minor_formatter(dates.DateFormatter('%H:%M'))
plt.setp(ax.xaxis.get_minorticklabels(), rotation=90, fontsize=5)
ax.xaxis.grid(True, which="minor")
ax.xaxis.set_major_locator(dates.DayLocator(interval=2))
ax.xaxis.set_major_formatter(dates.DateFormatter('\n\n%Y/%m/%d'))
plt.title("AAPL")
ax.set(title='AAPL', ylabel='Kaina', xlabel='Data')
plt.legend(loc='upper left')
plt.grid()
plt.show()

MATLAB

function grafikas(d)
    figure;
    plot(d.date_time, d.close);
    datetick('x','mm/dd','keepticks','keeplimits');
    title('AAPL tikiniai duomenys');
    xlabel('Date');
    ylabel('Close price'); 
    grid on;

pav

Minutinių barų formavimas:

Python aplinkoje, norėdami formuoti minutinius barus, pasinaudosime resample() funkcija, kartu paduodami conversion žodyną (dictionary), pagal kurį bus konvertuojami atitinkami open, high, low, close stulpeliai.

In [36]:
%%timeit 
data_tik_new = data_tik.set_index('Date_Time')

conversion = {'Open': 'first', 'High': 'max', 'Low': 'min', 'Close': 'last', 'Up': 'sum', 'Down': 'sum'}
data_min = data_tik_new.resample('Min').agg(conversion)
data_hour = data_tik_new.resample('H').agg(conversion)

data_min = data_min[-130:]
1 loop, best of 3: 442 ms per loop
In [37]:
data_min.head()
Out[37]:
Down Up High Low Close Open
Date_Time
2017-09-22 20:50:00 91281.0 53792.0 151.38 151.26 151.33 151.38
2017-09-22 20:51:00 52873.0 98982.0 151.51 151.33 151.48 151.33
2017-09-22 20:52:00 47050.0 63296.0 151.58 151.48 151.58 151.48
2017-09-22 20:53:00 39812.0 22697.0 151.58 151.48 151.52 151.56
2017-09-22 20:54:00 18568.0 25763.0 151.58 151.49 151.55 151.52

Analogiška konversija MATLAB aplinkoje:

function e = konversija(d)
    e = [];
    d.date_min = floor(1440*d.date_time + 0.0001)/1440;
    e.minutes = unique(d.date_min);
    e.minutes = e.minutes(end-129:end);
    e.open = zeros(1, length(e.minutes));
    e.high = zeros(1, length(e.minutes));
    e.low = zeros(1, length(e.minutes));
    e.close = zeros(1, length(e.minutes));
    for i = 1:length(e.minutes)
        e.open(i) = d.close(find(d.date_min == e.minutes(i),1,'first'));
        e.high(i) = max(d.close(d.date_min == e.minutes(i)));
        e.low(i) = min(d.close(d.date_min == e.minutes(i)));
        e.close(i) = d.close(find(d.date_min == e.minutes(i),1,'last'));
    end    
end

Paskaičiuojame laiką:

timek = timeit(@()konversija(d));

3.340964379307540

Matome, kad šiuo atveju, Python funkcionalumas Pandas pakete gerokai pranoksta MATLAB barų konvertavimo procedūrą. Visgi, šiuo atveju naudojamės specifine Python pandas paketo funkcija, kuri leidžia efektyviai transformuoti duomenų lentelę, o MATLAB aplinkoje kliaunamės ciklu, pereidami per visas pradinės duomenų struktūros eilutes.

Python MATLAB
Laikai (s) 0.447 3.341

g

Minutinių barų grafikai

Kadangi pradiniai tikiniai duomenys jau performuoti į minutinius barus, galime pavaizduoti, minutinių ''žvakių" (candles) grafikus:

Python aplinkoje tam panaudosime specialią paketo matplotlib.finance funkciją candlestick_ohlc, bet prieš tai dar turime pakeisti datų formatą iš pradinio YYYY-MM-DD hh-mm-ss į šiai funkcijai tinkantį skaitinį datenum formatą:

In [38]:
data_min.index = dates.date2num(data_min.index.to_pydatetime())

# Ismetame po konversijos atsiradusius nepilnus duomenis is dateframe'o
#data_min = data_min.dropna()

# "Resetiname" indeksus, graziname Date_Time stulpeli i pirmykste vieta
data_min = data_min.reset_index()
data_min = data_min.rename(columns={"index": "Date_Time"})
In [40]:
# Breziame minutiniu baru grafika
fig = plt.figure(figsize=(15,9))
fig.add_axes()
ax = fig.add_subplot(111)

# Taikome candles funkcija
candlestick_ohlc(ax, data_min[['Date_Time', 'Open', 'High', 'Low', 'Close']].values,
                 width=0.0005, colorup='#008000', colordown='#FF0000')

# Nustatome asiu parametrus, datu periodiskuma 
ax.xaxis.set_major_locator(dates.MinuteLocator(interval=5))
ax.xaxis.set_major_formatter(dates.DateFormatter('%H:%M'))
plt.setp(ax.xaxis.get_majorticklabels(), rotation=90, fontsize=7)
plt.title("AAPL min. candles")
ax.set(title='AAPL min. candles', ylabel='Kaina', xlabel='Data')
plt.grid()
plt.show()

MATLAB aplinkoje, tam pasinaudosime financial toolbox funkcija candle:

figure;
candle(e.high', e.low', e.close', e.open', 'b', e.minutes');
grid on;
title('AAPL min candles')
xlabel('Data');
ylabel('Kaina');

pav

Slenkančių vidurkių algoritmai:

Toliau palyginsime trijų slenkančių vidurkių algoritmų veikimą - paprastąjį (simple moving average), eksponentinį ( exponential moving average) ir svertinį (weighted moving average):

Paprastasis slankus vidurkis (simple moving average)

In [41]:
%%timeit 
def Simple_moving_average(data, period):
    sumdf = data['Close'].cumsum(axis=0)
    average = (sumdf.values[period - 1:] - sumdf.values[: -period + 1] + data['Close'].values[: -period + 1])/period
    return average

averages1 = Simple_moving_average(data_min, 10)
averages2 = Simple_moving_average(data_min, 2)
1000 loops, best of 3: 553 µs per loop

MATLAB kodas:

function average = Simple_moving_average(data, period)
    sumdf = cumsum(data);
    average = (sumdf(period:end) - sumdf(1:end - period + 1) + data(1:end - period + 1))/period;
end

function [average1,average2] = SMA(e)
    average1 = Simple_moving_average(e.close, 10);
    average2 = Simple_moving_average(e.close, 2);
end

Skaičiuojame laiką:

timeAV = timeit(@()SMA(e));

0.00002241849557302785

Matome, kad šiuo atveju, dirbant su vektorizuotomis operacijomis, MATLAB suveikia gerokai greičiau.

Python MATLAB
Laikai (s) 0.000553 0.000024

g

Eksponentinis slankus vidurkis (exponential moving average)

In [43]:
%%timeit 
def Exponential_moving_average(data, period):
    ema = np.array([])
    mult = 2/((period + 1)*1.0)
    ema = np.append(ema, sum(data['Close'].values[:period])/period)
    for i in range(period, len(data)):
        ema = np.append(ema, (data['Close'].values[i] - ema[i - period])*mult + ema[i - period])
    return ema    
    
ema1 = Exponential_moving_average(data_min, 10)
ema2 = Exponential_moving_average(data_min, 2) 
100 loops, best of 3: 6.48 ms per loop

MATLAB kodas:

function ema = Exponential_moving_average(data, period)
    mult = 2/(period + 1);
    ema = [sum(data(1:period))/period];
    for i=(period+1):length(data)
        ema = [ema (data(i) - ema(i - period)).*mult + ema(i - period)];
    end
end

function [ema1, ema2] = EMA(e)
    ema1 = Exponential_moving_average(e.close, 10);
    ema2 = Exponential_moving_average(e.close, 2);
end

Skaičiuojame laiką:

timeEV = timeit(@()EMA(e));

0.0005907353873962813

Python MATLAB
Laikai (s) 0.00648 0.00059

hhg

Svertinis slankus vidurkis (weighted moving average)

In [45]:
%%timeit 
def Weighted_moving_average(data, period):
    b = np.arange(1, period + 1)
    wma = np.array([])
    for i in range(period, len(data) + 1):
        wma = np.append(wma, sum(np.multiply(data['Close'].values[(i - period):i], b))/sum(b))
    return wma

wma1 = Weighted_moving_average(data_min, 10)
wma2 = Weighted_moving_average(data_min, 2)
100 loops, best of 3: 10.3 ms per loop

MATLAB kodas:

function wma = Weighted_moving_average(data, period)
    b = 1:period;
    wma = [];
    for i=period:length(data)
        wma = [wma sum(data((i - period + 1):i).*b)/sum(b)];
    end
end

function [wma1,wma2] = WMA(e)
    wma1 = Weighted_moving_average(e.close, 10);
    wma2 = Weighted_moving_average(e.close, 2);
end

Skaičiuojame laiką:

timeWV = timeit(@()WMA(e));

0.001561551896094

Matome, kad ir svertinio slankaus vidurkio atveju, MATLAB pranoko Python.

Python MATLAB
Laikai (s) 0.0103 0.0016

g

Slenkančių vidurkių grafikai:

Gautus slenkančių vidurkių grafikus galime atvaizduoti:

Paprastojo slenkančio vidurkio grafikas

In [47]:
fig = plt.figure(figsize=(15,9))
fig.add_axes()
ax = fig.add_subplot(111)

plt.plot(data_min['Date_Time'], data_min['Close'], color='red', label="Minutiniai duomenys", linewidth=1)
plt.plot(data_min['Date_Time'].values[9:], averages1, color='blue', label="10min. MA", linewidth=1)
plt.plot(data_min['Date_Time'].values[1:], averages2, color='black', label="2min. MA", linewidth=1)
ax.xaxis.set_major_locator(dates.MinuteLocator(interval=5))
ax.xaxis.set_major_formatter(dates.DateFormatter('%H:%M'))
plt.setp(ax.xaxis.get_majorticklabels(), rotation=90, fontsize=7)
plt.title("AAPL min. moving averages")
ax.set(title='AAPL min. moving averages', ylabel='Kaina', xlabel='Data')
plt.legend(loc='upper left')
plt.grid()
plt.show()

MATLAB

figure;
plot(e.minutes, e.close, 'b');
hold on;
plot(e.minutes(length(e.minutes)- length(average1) + 1:end), average1, 'm');
hold on;
plot(e.minutes(length(e.minutes)- length(average2) + 1:end), average2, 'r');
hold off;
datetick('x','HH-MM:SS','keepticks','keeplimits');
legend('AAPL close','AAPL 10 MA','AAPL 2 MA')
title('AAPL MA');
xlabel('Date');
ylabel('Close price'); 
grid on;

pav

Eksponentinio slankaus vidurkio grafikas

In [48]:
fig = plt.figure(figsize=(15,9))
fig.add_axes()
ax = fig.add_subplot(111)

plt.plot(data_min['Date_Time'], data_min['Close'], color='red', label="Minutiniai duomenys", linewidth=1)
plt.plot(data_min['Date_Time'].values[9:], ema1, color='blue', label="10min. EMA", linewidth=1)
plt.plot(data_min['Date_Time'].values[1:], ema2, color='black', label="2min. EMA", linewidth=1)
ax.xaxis.set_major_locator(dates.MinuteLocator(interval=5))
ax.xaxis.set_major_formatter(dates.DateFormatter('%H:%M'))
plt.setp(ax.xaxis.get_majorticklabels(), rotation=90, fontsize=7)
plt.title("AAPL min. exponential moving averages")
ax.set(title='AAPL min. exponential moving averages', ylabel='Kaina', xlabel='Data')
plt.legend(loc='upper left')
plt.grid()
plt.show()

MATLAB

figure;
plot(e.minutes, e.close, 'b');
hold on;
plot(e.minutes(length(e.minutes)- length(ema1) + 1:end), ema1, 'r');
hold on;
plot(e.minutes(length(e.minutes)- length(ema2) + 1:end), ema2, 'm');
hold off;
datetick('x','HH-MM:SS','keepticks','keeplimits');
legend('AAPL close','AAPL 10 EMA','AAPL 2 EMA')
title('AAPL EMA');
xlabel('Date');
ylabel('Close price'); 
grid on;

pav

Svertinio slankaus vidurkio grafikas

In [49]:
fig = plt.figure(figsize=(15,9))
fig.add_axes()
ax = fig.add_subplot(111)

plt.plot(data_min['Date_Time'], data_min['Close'], color='red', label="Minutiniai duomenys", linewidth=1)
plt.plot(data_min['Date_Time'].values[9:], wma1, color='blue', label="10min. WMA", linewidth=1)
plt.plot(data_min['Date_Time'].values[1:], wma2, color='black', label="2min. WMA", linewidth=1)
ax.xaxis.set_major_locator(dates.MinuteLocator(interval=5))
ax.xaxis.set_major_formatter(dates.DateFormatter('%H:%M'))
plt.setp(ax.xaxis.get_majorticklabels(), rotation=90, fontsize=7)
plt.title("AAPL min. weighted moving averages")
ax.set(title='AAPL min. weighted moving averages', ylabel='Kaina', xlabel='Data')
plt.legend(loc='upper left')
plt.grid()
plt.show()

MATLAB

figure;
plot(e.minutes, e.close, 'b');
hold on;
plot(e.minutes(length(e.minutes)- length(wma1) + 1:end), wma1, 'r');
hold on;
plot(e.minutes(length(e.minutes)- length(wma2) + 1:end), wma2, 'm');
hold off;
datetick('x','HH-MM:SS','keepticks','keeplimits');
legend('AAPL close','AAPL 10 WMA','AAPL 2 WMA')
title('AAPL WMA');
xlabel('Date');
ylabel('Close price'); 
grid on;

pav

Hull slankus vidurkis

Kaip matome, svertinis ir eksponentinis slankūs vidurkiai sumažina vėlavimą, gaunamą paprastojo slankaus vidurkio atveju. Visgi, norint dar labiau sumažinti vėlavimą atsirandantį didelių periodų vidurkių grafikuose, naudosime Hull slankų vidurkį - svertinių slankių vidurkių kombinaciją - gerokai sumažinančią anksčiau aprašytųjų vidurkių vėlavimo problemą.

In [50]:
%%timeit 
WMA1 = Weighted_moving_average(data_min, 5)
WMA2 = Weighted_moving_average(data_min, 10)
WMA3 = WMA1[(10-5):]*2 - WMA2
a = 10**(.5)
n = int(10**(.5))

b = np.arange(1,n+1)
hma = np.array([])
for i in range(n, len(WMA3)+1):
    hma = np.append(hma, sum(np.multiply(WMA3[(i - n):i], b))/sum(b)) 
100 loops, best of 3: 13.4 ms per loop

MATLAB

function [hma] = HMA(e)
    WMA1 = Weighted_moving_average(e.close, 5);
    WMA2 = Weighted_moving_average(e.close, 10);
    WMA3 = WMA1((10 - 5 + 1):end).*2 - WMA2;
    a = 10^0.5;
    n = round(10^0.5);

    b = 1:n;
    hma = [];
    for i = n:length(WMA3)
       hma = [hma, sum(WMA3(i - n + 1:i).*b)/sum(b)]; 
    end
end

Skaičiuosime laika:

timeHA = timeit(@()HMA(e));

0.002284785531188

Ir šiuo atveju MATLAB greičiu beveik 7-8 kartus pranoko Python.

Python MATLAB
Laikai (s) 0.0134 0.0023

h

Hull slankaus vidurkio grafikas

In [52]:
fig = plt.figure(figsize=(15,9))
fig.add_axes()
ax = fig.add_subplot(111)
plt.plot(data_min['Date_Time'], data_min['Close'], color='red', label="Minutiniai duomenys", linewidth=1)
plt.plot(data_min['Date_Time'].values[9:], WMA2, color='blue', label="10min. WMA", linewidth=1)
plt.plot(data_min['Date_Time'].values[4:], WMA1, color='green', label="5min. WMA", linewidth=1)
plt.plot(data_min['Date_Time'].values[11:], hma, color='black', label="HMA", linewidth=1)
ax.xaxis.set_major_locator(dates.MinuteLocator(interval=5))
ax.xaxis.set_major_formatter(dates.DateFormatter('%H:%M'))
plt.setp(ax.xaxis.get_majorticklabels(), rotation=90, fontsize=7)
plt.title("AAPL min. Hull moving average")
ax.set(title='AAPL min. Hull moving averages', ylabel='Kaina', xlabel='Data')
plt.legend(loc='upper left')
plt.grid()
plt.show()

MATLAB

figure;
plot(e.minutes, e.close, 'b');
hold on;
plot(e.minutes(length(e.minutes)- length(WMA2) + 1:end), WMA2, 'r');
hold on;
plot(e.minutes(length(e.minutes)- length(WMA1) + 1:end), WMA1, 'm');
hold on;
plot(e.minutes(length(e.minutes)- length(hma) + 1:end), hma, 'k');
hold off;
datetick('x','HH-MM:SS','keepticks','keeplimits');
legend('AAPL close prices','AAPL WMA2','AAPL WMA1', 'AAPL HMA')
title('AAPL Hull moving average');
xlabel('Date');
ylabel('Close price'); 
grid on;

pav

Galime pastebėti, kad juoda Hull slankaus vidurkio kreivė gerokai mažiau atsilieka nuo pradinio close kainų grafiko.

Moving average convergence-divergence (MACD) indikatorius

In [53]:
%%timeit
macd_line = Exponential_moving_average(data_min, 12)[26-12:] - Exponential_moving_average(data_min, 26)
signal_line = np.array([])
mult = 2/((9 + 1)*1.0)
signal_line = np.append(signal_line, sum(macd_line[:9])/9)
for i in range(9, len(macd_line)):
    signal_line = np.append(signal_line, (macd_line[i] - signal_line[i - 9])*mult + signal_line[i - 9])

macd_hist = macd_line[len(macd_line)-len(signal_line):] - signal_line 
100 loops, best of 3: 7.13 ms per loop

MATLAB

function [signal_line, macd_line] = MACD(e)
    exp1 = Exponential_moving_average(e.close, 12);
    exp2 = Exponential_moving_average(e.close, 26);
    macd_line = exp1(26 - 12 + 1:end) - exp2;
    signal_line = [];
    mult = 2/(9 + 1);
    signal_line = [signal_line sum(macd_line(1:9))/9];

    for i = 9 + 1:length(macd_line)
        signal_line = [signal_line (macd_line(i) - signal_line(i - 9))*mult + signal_line(i - 9)];
    end

    macd_hist = macd_line(length(macd_line) - length(signal_line) + 1:end) - signal_line;
end

Skaičiuojame laiką:

timeMACD = timeit(@()MACD(e));

0.0007267958859019566

Matome, kad MATLAB beveik 10 kartų greičiau paskaičiuoja MACD indikatoriaus vektorius.

Python MATLAB
Laikai (s) 0.00713 0.00073

g

MACD indikatoriaus grafikas

In [56]:
f, axes = plt.subplots(2, 1, sharex=True, gridspec_kw = {'height_ratios':[3, 1]}, figsize=(15,9))

candlestick_ohlc(axes[0], data_min[['Date_Time', 'Open', 'High', 'Low', 'Close']].values,
                 width=0.0005, colorup='#008000', colordown='#FF0000')
axes[1].plot(data_min['Date_Time'].values[len(data_min)-len(macd_line):], macd_line, color='blue', label="MACD linija", linewidth=1)
axes[1].plot(data_min['Date_Time'].values[len(data_min)-len(signal_line):], signal_line, color='red', label="Signalo linija", linewidth=1)
axes[1].bar(data_min['Date_Time'].values[len(data_min)-len(macd_hist):], macd_hist, width = 0.0005, color='magenta', align='center')
 
axes[0].xaxis.set_minor_locator(dates.MinuteLocator(interval=1))
axes[0].xaxis.set_major_locator(dates.MinuteLocator(interval=5))
axes[0].xaxis.set_major_formatter(dates.DateFormatter('%H:%M'))
axes[0].xaxis.grid(True, which="minor")
axes[0].yaxis.grid(True)
axes[1].xaxis.grid(True, which="minor")
axes[1].yaxis.grid(True)
plt.setp(axes[1].xaxis.get_majorticklabels(), rotation=90, fontsize=7)
axes[0].set(title='AAPL Moving average convergence divergence', ylabel='Kaina')
axes[1].set(ylabel='Skirtumas', xlabel='Data')

axes[0].set_xlim(data_min['Date_Time'].values[len(data_min)-len(macd_hist)-1], data_min['Date_Time'].values[-1])

plt.legend(loc='upper left')
plt.show()    

MATLAB

figure;
ax1 = subplot(6,1,1:4);
plot(ax1, e.minutes, e.close, 'k');
xlim = ([e.minutes(length(e.minutes) - length(macd_line) + 1), e.minutes(end)]);
title(ax1, 'MACD indikatorius AAPL');
ylabel(ax1, 'Close kainos');
datetick('x','HH-MM:SS','keepticks','keeplimits');
grid on; 

ax2 = subplot(6,1,5:6);
bar(ax2, e.minutes(length(e.minutes) - length(macd_hist) + 1:end), macd_hist, 'y');
hold on;
plot(ax2, e.minutes(length(e.minutes) - length(macd_line) + 1:end), macd_line, 'b');
plot(ax2, e.minutes(length(e.minutes) - length(signal_line) + 1:end), signal_line, 'r');
hold off;
xlim = ([e.minutes(length(e.minutes) - length(macd_line) + 1), e.minutes(end)]);
ylabel(ax2, 'Skirtumas');
datetick('x','HH-MM:SS','keepticks','keeplimits');
grid on;
linkaxes([ax1, ax2], 'x');

pav


Taigi, realaus algoritminės prekybos uždavinio atveju išryškėjo tam tikri abiejų kalbų greičio skirtumai priklausomai nuo kodo dalies: MATLAB gerokai lenkia Python iteraciniuose skaičiavimuose, taip pat vektoriniuose uždaviniuose, kur Python lėtumu galėjome įsitikinti ankstesniame smulkių uždavinukų pavyzdyje, tačiau Python duomenų importavimas naudojantis pandas paketo funkcijomis šiuo atveju pranoko MATLAB.