gimme my money
xkcd.com
Michał "Khorne" Lowas-Rzechonek
krzysztoF "spooky" szczęsny
gimme my money
TO NIE JEST PORADA FINANSOWA
NIE JESTEM PRAWNIKIEM ANI DORADCĄ FINANSOWYM, PODATKOWYM ITP
INWESTOWANIE WIĄŻE SIĘ Z RYZYKIEM
gimme my money
TO NIE JEST też PORADA techniczna
giełda
Giełda Papierów Wartościowych
Krajowy Depozyt Papierów Wartościowych
giełda: POjęcia
KUPIĘ | SPRZEDAM |
---|---|
45,00 | 41,30 |
42,40 41,50 |
42,50 43,80 |
arkusz
sesja
makler
transakcja i prowizja
tick: 41,30
KURS
rachunek
giełda: spekulacjA
handlować różnymi rzeczami
kupić tanio, poczekać, sprzedać drogo
jak zbyt stanieje, sprzedać po bieżącej cenie
podążać za strategią
projekt
niech komputer mi powie co kupić a co sprzedać i za ile
zlecenia złożę sobie sam
żeby miało jakieś pokrętła do zabawy
żeby jakoś wyglądało
żeby nie liczyło przez tydzień
żeby nie trzeba było się urobić przy implementacji
projekt: narzędzia
django admin (bo nie umiem w ui)
postgres (bo nie umiem w excel i nie wierzę w mongo)
websockety i knockout.js (żeby web był fancy)
zeromq (bo książkę czytałem)
projekt: Dane
problem: split/resplit
problem: wstrzymanie obrotu
ile tego właściwie jest?
dywidendy
projekt: Dane
~7.5K sesji
~250 spółek
~1.8M rekordów
AKA "nie ma sensu się przejmować"
Dane
plik csv z danymi od początku świata
import do tymczasowej tabeli
wypełnianie dziur
przekładanie do docelowych tabel
Dane: odczyt
import do tymczasowej tabeli
@contextmanager
def TemporaryTable(cursor, model, *args):
columns = ",".join(filter(partial(str.format, '"{}"'), args))
db_table = model._meta.db_table
cursor.execute(f"""
CREATE TEMPORARY TABLE "{db_table}_import"
ON COMMIT DROP
AS SELECT {columns}
FROM "{db_table}"
WHERE NULL"""
)
with TemporaryTable(
cursor, Quote,
'NULL::text AS short_name', 'NULL::date AS date',
'open', 'high', 'low', 'close', 'volume') as quote_import
Dane: odczyt
import do tymczasowej tabeli
quotes = StringIO()
for q in data:
date = datetime.strptime(q.date, '%Y%m%d').strftime('%Y-%m-%d')
volume = q.volume.replace('.', '')
quotes.write("\t".join([q.short_name, date,
q.open, q.high, q.low, q.close,
volume]) + "\n")
quotes.seek(0)
cursor.copy_from(
quotes, quote_import,
columns=['short_name', 'date',
'open', 'high', 'low', 'close',
'volume'])
Dane: obróbka
wypełnianie dziur
cursor.execute("SELECT count(*) FROM quotes_quote_fill_gaps()")
cursor.fetchone()
create or replace function quotes_quote_fill_gaps()
returns setof integer as $$
with
/* SQL window query */
returning id;
$$ language sql;
Dane: import
przekładanie do docelowej tabeli
cursor.execute(f"""
INSERT INTO "{db_table}"(
"stock_id", "session_id",
"open", "high", "low", "close", "volume"
)
SELECT
"stock"."id", "session"."id",
"open", "high", "low", "close", "volume"
FROM "{db_table}_import"
LEFT JOIN "{stock_table}" AS "stock"
ON "stock"."short_name" = "{db_table}_import"."short_name"
LEFT JOIN "{session_table}" AS "session"
ON "session"."date" = "{db_table}_import"."date"
ON CONFLICT("stock_id", "session_id") DO NOTHING
""")
wizualizacja: highcharts
let ohlc = [
{% for quote in original.quote_set.all %}
[
{{ quote.session.timestamp }} * 1000,
{{ quote.open }},
{{ quote.high }},
{{ quote.low }},
{{ quote.close }}
]{% if not forloop.last %},{% endif %}
{% endfor %}
],
volume = [
{% for quote in original.quote_set.all %}
[
{{ quote.session.timestamp }} * 1000,
{{ quote.volume }},
]{% if not forloop.last %},{% endif %}
{% endfor %}
],
wizualizacja: django admin
algorytm
xkcd.com
algorytm
anomalia
Trendy średnioterminowe zwykle się utrzymują
trendy długoterminowe zwykle się odwracają
algorytm: strategia
raz na czas, wybierz kilka spółek o najlepszej stopie zwrotu
kup mniej więcej po równo
nie handluj rzeczami które trudno sprzedać
jeśli cena spadnie za bardzo, sprzedaj od razu
unikaj płacenia nadmiernych prowizji
algorytm: wskaźniki
wskaźnik: stopa zwrotu
def rate_of_return(period):
current = F('close')
previous = F('close%i' % period)
return Round((current - previous) / previous, 4)
qs = Quote.objects.annotate(
# add columns with prices shifted by period
**{'close%i' % i: lag(F('close'), i)
for i in return_periods}
).annotate(
# to build JSON on the db side,
# both keys and values must be strings
returns=JSONObject(
keys=Array(*(Text(Value(i))
for i in return_periods)),
values=Array(*(Text(rate_of_return(i))
for i in return_periods)),
),
)
algorytm: przepływ danych
sesje
maszynka
stan
rekomendacje
transakcje
bilans
status
implementacja: zeromq
Socket
protokół
Analogicznie jak w TCP, socket to "końcówka" połączenia
ZeroMQ automagicznie wznawia połączenia
Różne transporty: inproc, ipc, tcp
Różne rodzaje: REQ/REP, PUB/SUB
Wiadomości składają się z jednej lub więcej ramek
Znaczenie ramek zależy od rodzaju socketa
bardzo niskopoziomowe
implementacja: zeromq
req-rep
Serwer wystawia socket REP pod jakimś znanym adresem
Klient tworzy socket REQ i łączy się na znany adres
Tylko jeden klient na raz - pozostali czekają
req-router
Jeden serwer, wielu klientów
DEALER-REP
Jeden klient, wiele serwerów
implementacja: zeromq
pub-sub
Serwer wystawia socket PUB pod jakimś znanym adresem
Klienci tworzą sockety SUB i łączą się na znany adres
Klienci subskrybują klucze
Serwer wysyła klucz jako pierwszą ramkę wiadomości
implementacja: sesje
PUB
SERWER
cli
SUB
REP
REQ
opublikuj sesje
od 2022-11-01
do 2022-11-04
klienT
klienT
ok, 3 sesje
implementacja: klonowanie
PUB
SERWER
SUB
ROUTER
klienT
nowe bilanse
REQ
poprzednie, poproszę
stare bilanse
połącz
pokaż
implementacja: websocket
websocket
django admin
zeromq
JSMQ Dealer
websocket
<TYPE>
ws://mymoney/
<NAME>/<TYPE>
ZWS
<NAME>
ZeroMQ
wizualizacja: jsmq
function CloneClient(state, updates, callback) {
let queued = [],
subscriber = new JSMQ.Subscriber(),
dealer = new JSMQ.Dealer();
dealer.sendReady = () => dealer.send(new JSMQ.Message(state.req));
dealer.onMessage = (items) => {
items.forEach(callback);
queued.forEach(callback);
dealer.disconnect();
subscriber.onMessage = callback; // update dynamically
};
// request state AFTER subscribe
subscriber.sendReady = () => dealer.connect(state.url);
subscriber.onMessage = queued.push;
subscriber.connect(updates.url);
subscriber.subscribe(updates.key);
};
wizualizacja: knockout.js
<script type="text/html" id="summary-widget">
<div class="brief">
<h1>
<span title="returns" data-bind="text: ror"/>
<small title="growth" data-bind="text: cagr"/>
</h1>
<span title="total" data-bind="text: total"/>
<span title="stock" data-bind="text: stock"/>
<span title="cash" data-bind="text: cash"/>
</div>
</script>
<!--
ko template:
{ name: 'summary-widget', data: summary }
-->
<!-- /ko -->
wizualizacja: knockout.js
function PortfolioSummaryViewModel() {
this.ror = ko.observable('');
this.cagr = ko.observable('');
this.total = ko.observable('');
this.stock = ko.observable('');
this.cash = ko.observable('');
}
var summary = new PortfolioSummaryViewModel();
function onMessage(status) {
summary.ror(status.ror);
...
}
wizualizacja: knockout.js
(function(ko) {
ko.applyBindings(
new PortfolioSummaryViewModel(),
$('#suit-center')[0]
);
}(ko));
DEMO TIME
gimme my money
xkcd.com
Michał "Khorne" Lowas-Rzechonek
krzysztoF "spooky" szczęsny
Gimme my money
By Michał Lowas-Rzechonek
Gimme my money
- 107