Amazon Cloud Services und Analyse des Investmentportfolios

    In letzter Zeit ist an den Aktienmärkten eine hohe Volatilität zu beobachten, wenn zum Beispiel ein stabiles Papier eines bekannten Unternehmens aufgrund von Sanktionen gegen seine Führung mehrere Prozentpunkte auf einmal verlieren kann oder im Gegenteil aufgrund eines positiven Berichts in die Luft fliegt und die Erwartungen der Anleger in Bezug auf Super-Profit-Dividenden.

    Wie lässt sich feststellen, ob der Besitz dieses Wertpapiers zu Einkommen oder nur Verlust und Enttäuschung geführt hat?

    (Quelle)

    In diesem Artikel erkläre ich Ihnen, wie Sie das angepasste Finanzergebnis für Wertpapiere ermitteln und visualisieren können.

    Am Beispiel von Client Reporting Opening Broker ziehen wir die Analyse und Konsolidierung von Brokerage-Berichten für den Aktienmarkt in Betracht. Dabei erstellen wir eine Cloud-Reporting-Systemarchitektur, gefolgt von einer einfachen und bequemen Analyse in AWS Quicksight.

    Aufgabenbeschreibung


    In vielen Schulungen und Schulungsstunden erfahren Sie, dass ein Handelsjournal geführt werden muss, das alle Parameter der Transaktion zur weiteren Analyse und Zusammenfassung der Arbeit der Handelsstrategie aufzeichnet. Ich stimme zu, dass ein solcher Ansatz für die Arbeit an der Börse es Ihnen ermöglicht, einen Händler zu disziplinieren, um sein Bewusstsein zu erhöhen, aber es kann auch großartig sein, den langwierigen Prozess zu ermüden.

    Ich gebe zu, ich habe zuerst sorgfältig versucht, den Ratschlägen des Journalings zu folgen, schrieb jede Transaktion mit ihren Parametern in einer Excel-Tabelle sorgfältig auf, baute einige Berichte, zusammenfassende Diagramme, geplante zukünftige Geschäfte auf, aber ... ich war schnell müde davon.

    Warum ist es unbequem, ein manuelles Journal eines Händlers zu führen?
    • Manuelles Befüllen des Magazins (auch bei teilweiser Automatisierung, in Form von Entladevorgängen vom Handelsterminal für den Tag), schnell ermüdet;
    • Bei manueller Eingabe besteht ein hohes Fehler- oder Tippfehlerrisiko.
    • es kann vorkommen, dass der aktive Händler ein passiver Investor wird und er immer weniger zu dieser Zeitschrift zurückkehrt und ihn dann vergisst (mein Fall); Nun endlich
    • мы умеем программировать, почему бы этим не воспользоваться и не автоматизировать весь процесс? Итак, погнали!

    Häufig sind Maklerunternehmen High-Tech-Organisationen, die ihren Kunden ziemlich hochwertige Analysen zu praktisch allen relevanten Themen zur Verfügung stellen. Man kann sagen, dass diese Berichterstattung mit jedem Update immer besser wird, aber selbst die fortgeschrittensten von ihnen haben möglicherweise nicht die Anpassungs- und Konsolidierungsmöglichkeiten, die anspruchsvolle und neugierige Kunden wünschen.

    Mit Opening Broker können Sie beispielsweise Brokerage-Berichte im XML-Format in Ihrem Konto empfangen. Wenn Sie jedoch ein IIS- und ein normales Brokerage-Konto an der Moskauer Börse (MOEX) haben, handelt es sich hierbei um zwei verschiedene Berichte. Wenn Sie noch ein Konto in St. Petersburg haben Der Petersburger Börse (SPB), wird den ersten beiden eine weitere hinzugefügt.

    Um ein konsolidiertes Anlegerprotokoll zu erhalten, müssen Sie drei Dateien im XML-Format verarbeiten.

    Die oben genannten Berichte zu MOEX und SPB unterscheiden sich geringfügig in ihren Formaten, die bei der Implementierung der Datenzuordnung berücksichtigt werden müssen.

    Die Architektur des entwickelten Systems


    Das folgende Diagramm zeigt das Architekturmodell des zu entwickelnden Systems:


    Parser-Implementierung


    Wir werden Berichte über alle drei Konten in "Mein Konto" für den maximal möglichen Zeitraum erhalten (kann in mehrere Berichte für jedes Jahr unterteilt werden), speichern sie im XML-Format und legen sie in einem Ordner ab. Als Testdaten für die Studie werden wir das fiktive Kundenportfolio verwenden, jedoch mit den grössten Parametern für die Marktrealität.


    Angenommen, der Anleger, Herr X, den wir in Betracht ziehen, verfügt über ein kleines Portfolio von fünf Wertpapieren:

    1. Der Bericht über die Börse SPB wird zwei Papiere enthalten: Apple und Microsoft;
    2. Im Börsenbericht MOEX (Brokerage) ein Papier: FGC UES;
    3. Der Bericht an der Börse MOEX (IIS) zwei Papiere: CMI und OFZ 24019;

    Laut unseren fünf Veröffentlichungen kann es zu Kauf- / Verkaufstransaktionen, Dividenden und Kuponzahlungen kommen, die Preise können sich ändern usw. Wir wollen die aktuelle Situation sehen, nämlich das Finanzergebnis unter Berücksichtigung aller Zahlungen, Transaktionen und des aktuellen Marktwerts.

    Und hier kommt Python, wir lesen Informationen aus allen Berichten in ein Array:

    my_files_list = [join('Data/', f) for f in listdir('Data/') if isfile(join('Data/', f))]
    my_xml_data = []
    # Считывание отчетов из каталогаfor f in my_files_list:
        tree = ET.parse(f)
        root = tree.getroot()
        my_xml_data.append(root)

    Für die Analyse benötigen wir aus Berichten mehrere Entitäten, nämlich:

    • Positionen der Wertpapiere im Portfolio;
    • Abgeschlossene Transaktionen;
    • Nichthandelsgeschäfte und andere Bewegungen auf dem Konto;
    • Durchschnittspreise der offenen Positionen
    Um das Beispiel vorzubereiten, verwenden wir vier Wörterbücher, um die obigen Sätze zu beschreiben.

    dict_stocks = {'stock_name': [], 'account': [], 'currency': [], 'current_cost': [], 'current_cost_rub': [], 'saldo' : []}
    dict_deals  = {'stock_name': [], 'account': [], 'date_oper': [], 'type_oper': [], 'quantity': [], 'price': [], 'currency': [], 'brokerage': [], 'result': []}
    dict_flows  = {'stock_name': [], 'account': [], 'date_oper': [], 'type_oper': [], 'result': [], 'currency': []}
    dict_avg_price = {'stock_name': [], 'account': [], 'avg_open_price' : []}

    Ein paar Worte zu diesen Wörterbüchern.

    Wörterbuch dict_stocks
    Словарь dict_stocks необходим для хранения общей информации по портфелю:

    • Наименование бумаги (stock_name);
    • Наименование счета (SPB, MOEX BROK, MOEX IIS) (account);
    • Валюта, используемая для расчетов по данной бумаге (currency);
    • Текущая стоимость (на момент формирования отчета в Личном кабинете Открытие Брокер) (current_cost). Здесь хочу заметить, что для сверхтребовательных клиентов, можно в будущем внести дополнительную доработку и использовать динамическое получение котировки ценной бумаги из торгового терминала или с сайта соответствующей биржи;
    • Текущая стоимость позиции ценной бумаги на момент формирования отчета (current_cost_rub)
      Аналогично вышеупомянутому пункту, здесь можно так же получать курс ЦБ на текущий момент или биржевой курс, кому как нравится.
    • Текущий остаток ценных бумаг (saldo)

    Dict_deals Wörterbuch
    Словарь dict_deals необходим для хранения следующей информации по совершенным сделкам:

    • Наименование бумаги (stock_name);
    • Наименование счета (SPB, MOEX BROK, MOEX IIS) (account);
    • Дата совершения сделки, т.е. Т0 (date_oper);
    • Тип операции (type_oper);
    • Объем бумаг, участвующих в сделке (quantity);
    • Цена, по которой была исполнена сделка (price);
    • Валюта, в которой была совершена операция (currency);
    • Брокерская комиссия за сделку (brokerage);
    • Финансовый результат по сделке (result)

    Dict_flows Wörterbuch
    Словарь dict_flows отражает движение средств по клиентскому счету и используется для хранения следующей информации:

    • Наименование бумаги (stock_name);
    • Наименование счета (SPB, MOEX BROK, MOEX IIS) (account);
    • Дата совершения сделки, т.е. Т0 (date_oper);
    • Тип операции (type_oper). Может принимать несколько значений: div, NKD, tax;
    • Валюта, в которой была совершена операция (currency);
    • Финансовый результат операции (result)

    Wörterbuch dict_avg_price
    Словарь dict_avg_price необходим для учета информации по средней цене покупки по каждой бумаге:

    • Наименование бумаги (stock_name);
    • Наименование счета (SPB, MOEX BROK, MOEX IIS) (account);
    • Средняя цена открытой позиции (avg_open_price)

    Wir verarbeiten ein Array von XML-Dokumenten und füllen diese Wörterbücher mit den entsprechenden Daten:

    # Сбор данных из соответствующих частей отчетовfor XMLdata in my_xml_data:
        # Информация о Бирже и счете
        exchange_name = 'SPB'if XMLdata.get('board_list') == 'ФБ СПБ'else'MOEX' 
        client_code =  XMLdata.get('client_code')
        account_name = get_account_name(exchange_name, client_code)
        # Маппинг тегов
        current_position, deals, flows, stock_name, \
        saldo, ticketdate, price, brokerage, \
        operationdate, currency, \
        current_cost, current_cost_rub, \
        stock_name_deal, payment_currency, currency_flows = get_allias(exchange_name)
        # Информация о состоянии клиентского портфеля
        get_briefcase(XMLdata)
        df_stocks = pd.DataFrame(dict_stocks)
        df_stocks.set_index("stock_name", drop = False, inplace = True)
        # Информация о сделках
        get_deals(XMLdata)
        df_deals = pd.DataFrame(dict_deals)
        df_avg = pd.DataFrame(dict_avg_price)
        # Информация о неторговых операциях по счету
        get_nontrade_operation(XMLdata)
        df_flows = pd.DataFrame(dict_flows)

    Die gesamte Verarbeitung wird durch alle XML-Daten aus den Berichten geleitet. Die Informationen über die Handelsplattform und den Client-Code sind in allen Berichten gleich. Sie können sie also ohne Zuweisung aus denselben Tags extrahieren.

    Dann müssen Sie jedoch eine spezielle Konstruktion anwenden, die den erforderlichen Alias ​​für das Tag auf der Grundlage des Berichts (SPB oder MOEX) bereitstellt, da Die gleichen Daten in diesen Berichten werden unterschiedlich bezeichnet.

    Diskrepanzen in Tags
    • Комиссия брокера по сделке в отчете SBP лежит в теге brokerage, а в отчете MOEX — broker_commission;
    • Дата неторговой операции по счету в отчете SPB – это operationdate, а в MOEX — operation_date и т.д.

    Beispiel-Tag-Zuordnung
    tags_mapping = {
        'SPB': {
            'current_position': 'briefcase_position',
            'deals': 'closed_deal',
            'flows': 'nontrade_money_operation',
    ...
            'stock_name_deal': 'issuername',
            'paymentcurrency': 'paymentcurrency',
            'currency_flows': 'currencycode'
        },
        'MOEX': {
            'current_position': 'spot_assets',
            'deals': 'spot_main_deals_conclusion',
            'flows': 'spot_non_trade_money_operations',
    ...
            'stock_name_deal': 'security_name',
            'paymentcurrency': 'price_currency_code',
            'currency_flows': 'currency_code'
        }
    }
    

    Die Funktion get_allias gibt den Namen des für die Verarbeitung erforderlichen Tags zurück, wobei der Name der Handelsplattform als Eingabe verwendet wird:

    Get_allias-Funktion
    defget_allias(exchange_name):return(
                tags_mapping[exchange_name]['current_position'],
                tags_mapping[exchange_name]['deals'],
                tags_mapping[exchange_name]['flows'],
    ...
                tags_mapping[exchange_name]['stock_name_deal'],
                tags_mapping[exchange_name]['paymentcurrency'],
                tags_mapping[exchange_name]['currency_flows']
              )
    

    Die Funktion get_briefcase ist für die Verarbeitung von Informationen über den Zustand des Kundenportfolios verantwortlich:

    Get_briefcase-Funktion
    defget_briefcase(XMLdata):# В отчете ФБ СПБ портфель находится под тегом briefcase_position
        briefcase_position = XMLdata.find(current_position)
        ifnot briefcase_position:
            returntry:
            for child in briefcase_position:
                stock_name_reduce = child.get(stock_name).upper()
                stock_name_reduce = re.sub('[,\.]|(\s?INC)|(\s+$)|([-\s]?АО)', '', stock_name_reduce)
                dict_stocks['stock_name'].append(stock_name_reduce)
                dict_stocks['account'].append(account_name)
                dict_stocks['currency'].append(child.get(currency))
                dict_stocks['current_cost'].append(float(child.get(current_cost)))
                dict_stocks['current_cost_rub'].append(float(child.get(current_cost_rub)))
                dict_stocks['saldo'].append(float(child.get(saldo)))
        except Exception as e:
            print('get_briefcase --> Oops! It seems we have a BUG!', e) 

    Mit der Funktion get_deals werden außerdem Informationen zu Transaktionen extrahiert:

    Get_deals-Funktion
    defget_deals(XMLdata):
        stock_name_proc = ''    
        closed_deal = XMLdata.find(deals)
        ifnot closed_deal:
            return# Отчет по SPB имеет иную сортировку - только по дате сделки,# тогда как отчеты MOEX: по бумаге, а потом по дате сделки# Отсортируем сделки по бумаге:if exchange_name == 'SPB':
            sortchildrenby(closed_deal, stock_name_deal)
            for child in closed_deal:
                sortchildrenby(child, stock_name_deal)
        try:        
            for child in closed_deal:
                stock_name_reduce = child.get(stock_name_deal).upper()
                stock_name_reduce = re.sub('[,\.]|(\s?INC)|(\s+$)|([-\s]?АО)', '', stock_name_reduce)
                dict_deals['stock_name'].append(stock_name_reduce)
                dict_deals['account'].append(account_name)
                dict_deals['date_oper'].append(to_dt(child.get(ticketdate)).strftime('%Y-%m-%d'))
                current_cost = get_current_cost(stock_name_reduce)
                # В отчете по SPB один тег на количество - quantity,# а на MOEX целых два: buy_qnty и sell_qntyif exchange_name == 'MOEX':
                    if child.get('buy_qnty'):
                        quantity = float(child.get('buy_qnty'))
                    else:
                        quantity = - float(child.get('sell_qnty'))
                else:    
                    quantity = float(child.get('quantity'))
                dict_deals['quantity'].append(quantity)    
                dict_deals['price'].append(float(child.get('price')))
                dict_deals['type_oper'].append('deal')
                dict_deals['currency'].append(child.get(payment_currency))
                brok_comm = child.get(brokerage)
                if brok_comm isNone: 
                    brok_comm = 0else:
                    brok_comm = float(brok_comm)
                dict_deals['brokerage'].append(float(brok_comm))
                # Доходность по каждой сделке и средняя цена позицииif stock_name_proc != stock_name_reduce:
                    if stock_name_proc != '':
                        put_avr_price_in_df(account_name, stock_name_proc, \
                                            pnl.m_net_position, pnl.m_avg_open_price)
                        current_cost = get_current_cost(stock_name_proc)
                        pnl.update_by_marketdata(current_cost)
                        if len(dict_deals['result']) > 0: 
                            if exchange_name != 'SPB':
                                dict_deals['result'][-1] = pnl.m_unrealized_pnl * 0.87 -dict_deals['brokerage'][-2]
                            else:
                                dict_deals['result'][-1] = pnl.m_unrealized_pnl - dict_deals['brokerage'][-2]
                    stock_name_proc = stock_name_reduce
                    pnl = PnlSnapshot(stock_name_proc, float(child.get('price')), quantity)
                    dict_deals['result'].append(-1 * brok_comm)
                else:
                    pnl.update_by_tradefeed(float(child.get('price')), quantity)
                    # Продажа бумаг, фиксация результатаif quantity < 0:
                        if pnl.m_realized_pnl > 0and exchange_name != 'SPB':
                            pnl_sum = pnl.m_realized_pnl * 0.87 - brok_comm
                        else:
                            pnl_sum = pnl.m_realized_pnl - brok_comm
                        dict_deals['result'].append(float(pnl_sum))
                    else:
                        pnl.update_by_marketdata(current_cost)
                        dict_deals['result'].append(-1 * brok_comm)
            put_avr_price_in_df(account_name, stock_name_proc, \
                                pnl.m_net_position, pnl.m_avg_open_price)
            current_cost = get_current_cost(stock_name_proc)
            pnl.update_by_marketdata(current_cost)
            if len(dict_deals['result']) > 0: 
                if exchange_name != 'SPB':
                    dict_deals['result'][-1] = pnl.m_unrealized_pnl * 0.87 -dict_deals['brokerage'][-2]
                else:
                    dict_deals['result'][-1] = pnl.m_unrealized_pnl - dict_deals['brokerage'][-2]
        except Exception as e:
            print('get_deals --> Oops! It seems we have a BUG!', e)  

    Neben der Verarbeitung eines Arrays mit Informationen zu den Parametern der Transaktion berechnet es auch den Durchschnittspreis einer offenen Position und die durch das FIFO-Verfahren realisierte PNL. Die PnlSnapshot-Klasse ist für diese Berechnung verantwortlich. Sie erstellt, auf deren Basis der hier vorgestellte Code mit geringfügigen Modifikationen als Grundlage genommen wurde: P & L-Berechnung.

    Am schwierigsten zu implementieren ist die Funktion, Informationen über Nichthandelsoperationen zu erhalten - get_nontrade_operation . Ihre Komplexität liegt in der Tatsache, dass in dem für Nichthandelsgeschäfte verwendeten Berichtsblock keine klaren Informationen über die Art des Vorgangs und die Sicherheit, mit der dieser Vorgang verknüpft ist, vorhanden sind.

    Beispiel für Zahlungszwecke für Nichthandelsgeschäfte
    Выплата дивидендов или накопленного купонного дохода может быть указана так:

    1. Выплата дохода клиент <777777> дивиденды <APPLE INC-ао> --> выплата дивидендов из отчета SPB;
    2. Выплата дохода клиент <777777> дивиденды <MICROSOFT COM-ао>
    3. Выплата дохода клиент 777777i (НКД 2 ОФЗ 24019) налог к удержанию 0.00 рублей --> выплата купона из отчета MOEX;
    4. Выплата дохода клиент 777777 дивиденды ФСК ЕЭС-ао налог к удержанию XX.XX рублей --> выплата дивидендов из отчета MOEX. и т.д.

    Dementsprechend wird es schwierig sein, ohne reguläre Ausdrücke auszukommen, deshalb werden wir sie voll nutzen. Die andere Seite der Frage ist, dass der Name des Unternehmens nicht immer mit dem Namen im Portfolio oder in den Transaktionen übereinstimmt. Daher muss der aus dem Zweck der Zahlung erhaltene Name des Ausstellers zusätzlich mit dem Wörterbuch korreliert werden. Als Wörterbuch verwenden wir seitdem eine Reihe von Transaktionen Es gibt die umfassendste Liste von Unternehmen.

    Die Funktion get_company_from_str extrahiert den Namen des Ausstellers aus dem Kommentar:

    Get_company_from_str-Funktion
    defget_company_from_str(comment):
        company_name = ''# Шаблоны для случаев дивиденды/купон
        flows_pattern = [
            '^.+дивиденды\s<(\w+)?.+-ао>$',
            '^.+дивиденды\s(.+)-а.+$',
            '^.+\(НКД\s\d?\s(.+)\).+$',
            '^.+дивидендам\s(.+)-.+$'
        ]
        for pattern in flows_pattern:
            match = re.search(pattern, comment)
            if match:
                return match.group(1).upper()
        return company_name

    Die Funktion get_company_from_briefcase bringt den Namen des Unternehmens in das Wörterbuch, wenn es eine Übereinstimmung zwischen den an den Transaktionen beteiligten Unternehmen findet:

    Funktion Get_company_from_briefcase
    defget_company_from_briefcase(company_name):
        company_name_full = None
        value_from_dic = df_deals[df_deals['stock_name'].str.contains(company_name)]
        company_arr  = value_from_dic['stock_name'].unique()
        if len(company_arr) == 1:
            company_name_full = company_arr[0]
        return company_name_full


    Die letzte Datenerfassungsfunktion für Nichthandelsoperationen ist schließlich get_nontrade_operation :

    Funktion "Get_nontrade_operation"
    defget_nontrade_operation(XMLdata):
        nontrade_money_operation = XMLdata.find(flows)
        ifnot nontrade_money_operation:
            returntry:
            for child in nontrade_money_operation:
                comment = child.get('comment')
                type_oper_match = re.search('дивиденды|НКД|^.+налог.+дивидендам.+$', comment) 
                if type_oper_match:
                    company_name = get_company_from_str(comment)
                    type_oper = get_type_oper(comment)
                    dict_flows['stock_name'].append(company_name)
                    dict_flows['account'].append(account_name)
                    dict_flows['date_oper'].append(to_dt(child.get(operationdate)).strftime('%Y-%m-%d'))
                    dict_flows['type_oper'].append(type_oper)
                    dict_flows['result'].append(float(child.get('amount')))
                    dict_flows['currency'].append(child.get(currency_flows))
        except Exception as e:
            print('get_nontrade_operation --> Oops! It seems we have a BUG!', e) 

    Das Ergebnis der Datenerfassung aus den Berichten besteht aus drei DataFrame-Daten. Dies sind ungefähr die folgenden:

    1. DataFrame mit Informationen zu Durchschnittspreisen von offenen Positionen:
    2. Transaktionsinformationen DataFrame:
    3. DataFrame mit Informationen zu Nichthandelsgeschäften:


    Wir müssen also nur die externe Transaktion der Tabelle der Transaktionen mit der Tabelle der Informationen über das Portfolio durchführen:

    df_result = pd.merge(df_deals, df_stocks_avg, how='outer', on=['stock_name', 'account', 'currency']).fillna(0)
    df_result.sample(10)


    Der letzte Teil der Datenarrayverarbeitung ist schließlich das Zusammenführen des im vorherigen Schritt erhaltenen Datenarrays mit dem DataFrame für nicht handelbare Transaktionen.
    Das Ergebnis der geleisteten Arbeit ist ein großer flacher Tisch mit allen notwendigen Informationen für die Analyse:

    df_result_full = df_result.append(df_flows, ignore_index=True).fillna(0)
    df_result_full.sample(10).head()


    Der resultierende Datensatz (Abschlussbericht) aus dem DataFrame kann einfach in CSV heruntergeladen werden und kann dann zur detaillierten Analyse in jedem BI-System verwendet werden.

    ifnot exists('OUTPUT'): makedirs('OUTPUT')
    report_name = 'OUTPUT\my_trader_diary.csv'
    df_result_full.to_csv(report_name, index = False, encoding='utf-8-sig')


    Laden und Verarbeiten von Daten in AWS


    Fortschritt steht nicht still, und mittlerweile erfreuen sich Cloud-Services und Serverless-Computing-Modelle bei der Verarbeitung und Speicherung von Daten großer Beliebtheit. Dies liegt vor allem an der Einfachheit und den geringen Kosten eines solchen Ansatzes. Wenn Sie eine Architektur für komplexe Berechnungen erstellen oder große Datenmengen verarbeiten, müssen Sie keine teuren Geräte kaufen. Sie mieten nur Strom für die Cloud, die Sie benötigen, und stellen die erforderlichen Ressourcen für eine relativ geringe Gebühr schnell bereit. .

    Einer der größten und bekanntesten Anbieter von Cloud-Technologien auf dem Markt ist Amazon. Ein Beispiel ist die Umgebung, in der Amazon Web Services (AWS) ein Analysesystem für die Verarbeitung von Daten in unserem Anlageportfolio aufbaut.

    AWS verfügt über eine umfangreiche Auswahl an Tools. Wir werden jedoch Folgendes verwenden:

    • Amazon S3 - Objektspeicher, mit dem Sie praktisch unbegrenzt viele Informationen speichern können;
    • AWS Glue ist der leistungsfähigste ETL-Cloud-Service, der die Struktur selbst bestimmen und den ETL-Code anhand der angegebenen Quelldaten generieren kann.
    • Amazon Athena ist ein serverloser Online-SQL-Abfragedienst, mit dem Sie Daten von S3 ohne viel Vorbereitung schnell analysieren können. Er hat auch Zugriff auf die Metadaten, die AWS Glue vorbereitet, wodurch der Zugriff auf die Daten unmittelbar nach dem Bestehen der ETL möglich ist.
    • Amazon QuickSight ist ein serverloser BI-Dienst, mit dem Sie beliebige Visualisierungen, Analyseberichte usw. erstellen können.

    Mit der Amazon-Dokumentation ist alles in Ordnung, insbesondere gibt es einen guten Artikel Best Practices bei der Verwendung von Athen mit AWS Glue , in dem beschrieben wird, wie Tabellen und Daten mit AWS Glue erstellt und verwendet werden. Lassen Sie uns und wir werden die Hauptideen dieses Artikels verwenden und sie verwenden, um unsere eigene Architektur des analytischen Berichtssystems zu erstellen.

    CSV-Dateien, die von unserem Berichtsparser vorbereitet wurden, werden dem S3-Bucket hinzugefügt. Es ist geplant, dass der entsprechende Ordner auf S3 an jedem Samstag - am Ende der Handelswoche - aufgefüllt wird. Daher können wir zum Zeitpunkt der Erstellung und Bearbeitung des Berichts nicht auf eine Datenpartitionierung verzichten.
    Neben der Optimierung der Arbeit von SQL-Abfragen für solche Daten können wir mit diesem Ansatz zusätzliche Analysen durchführen, z. B. um die Dynamik der Änderungen des Finanzergebnisses für jedes Dokument zu ermitteln usw.

    Arbeiten Sie mit Amazon S3
    • Создадим бакет на S3, назовем его «report-parser»;
    • В этом бакете «report-parser» создадим папку под названием «my_trader_diary»;
    • В каталоге «my_trader_diary» создадим каталог с датой текущего отчета, например, «date_report=2018-10-01» и поместим в него CSV-файл;
    • Только ради эксперимента и лучшего понимания секционирования создадим еще два каталога: «date_report=2018-09-27» и «date_report=2018-10-08». В них положим тот же CSV-файл;
    • Итоговый S3 бакет «report-parser» должен иметь вид как показано на картинки ниже:


    Arbeiten Sie mit AWS Glue
    По большому счету, можно обойтись лишь Amazon Athena чтобы создать внешнюю таблицу из данных, лежащих на S3, но AWS Glue – более гибкий и удобный для этого инструмент.

    • Заходим в AWS Glue и создаем новый Crawler, который будет из разрозненных по отчетным датам CSV-файлов собирать одну таблицу:
      • Задаем имя нового Crawler;
      • Указываем хранилище, откуда брать данные (s3://report-parser/my_trader_diary/)
      • Выбираем или создаем новую IAM роль, которая будет иметь доступ к запуску Crawler и доступ к указанному ресурсу на S3;
      • Далее, необходимо задать частоту запуска. Пока ставим по требованию, но в дальнейшем, думаю, это изменится и запуск станет еженедельным;
      • Сохраняем и ждем, когда Crawler создастся.
    • Когда Crawler перейдет в состояние Ready, запускаем его!

    • Как только он отработает, в закладке AWS Glue: Database -> Tables появится новая таблица my_trader_diary:


    Betrachten Sie die generierte Tabelle genauer.
    Wenn Sie auf den Namen der erstellten Tabelle klicken, gelangen Sie zu der Seite mit der Beschreibung der Metadaten. Am unteren Rand befindet sich ein Tabellendiagramm, und die aktuellste ist eine Spalte, die sich nicht in der ursprünglichen CSV-Datei befand - date_report. Glue erstellt diese AWS-Spalte automatisch basierend auf der Definition der Quelldatenabschnitte (im S3-Stapel haben wir speziell die Ordner namens date_report = YYYY-MM-DD benannt, wodurch sie als nach Datum getrennte Abschnitte verwendet werden konnten).

    Partitionierung der Tabelle

    На той же странице в верхнем правом углу есть кнопка View partitions, нажав на которую, мы можем увидеть из каких секций состоит наша сформированная таблица:

    Datenanalyse


    Nachdem wir verarbeitete Daten heruntergeladen haben, können wir deren Analyse problemlos durchführen. Lassen Sie uns zunächst die Funktionen von Amazon Athena als die einfachste und schnellste Möglichkeit zum Analysieren von Abfragen betrachten. Wechseln Sie dazu zum Amazon Athena-Dienst, wählen Sie die benötigte Datenbank (finanziell) aus und schreiben Sie den folgenden SQL-Code:

    select 
    	d.date_report, d.account,
    	d.stock_name,  d.currency,
    	sum(d.quantity) as quantity,
    	round(sum(d.result), 2) asresultfrom my_trader_diary d
    groupby 
    	d.date_report, d.account,
    	d.stock_name,  d.currency
    orderby 
    	d.account, d.stock_name,
    	d.date_report;

    Diese Abfrage gibt uns zu jedem Berichtstermin ein Nettofinanzergebnis für jedes Papier. Weil Wir haben den gleichen Bericht dreimal zu unterschiedlichen Terminen heruntergeladen, und das Ergebnis wird sich nicht ändern, was natürlich unter den tatsächlichen Marktbedingungen anders sein wird:


    Was aber, wenn wir die Daten in Form flexibler Tabellen oder Diagramme visualisieren möchten? Hier kommt Amazon QuickSight zur Hilfe, mit dem Sie flexible Analysen fast so schnell einrichten können wie das Schreiben einer SQL-Abfrage. Gehen wir zu Amazon QuickSight (falls Sie sich noch nicht dort registriert haben, ist eine Registrierung erforderlich).

    Klicken Sie auf die Schaltfläche Neue Analysen -> Neuer Datensatz, und klicken Sie im angezeigten Fenster zur Auswahl der Quelle für den Datensatz auf Athena:



    Wir erfinden einen Namen für unsere Datenquelle, beispielsweise "PNL_Analyse", und klicken Sie auf die Schaltfläche "Datenquelle erstellen".

    Als nächstes wird das Fenster Choose your table (Tabelle auswählen) geöffnet, in dem Sie eine Datenbank und eine Datenquellentabelle auswählen müssen. Wählen Sie eine Finanzdatenbank und eine Tabelle aus: my_traider_diary. Standardmäßig wird die gesamte Tabelle verwendet. Wenn jedoch "Benutzerdefiniertes SQL verwenden" ausgewählt ist, können Sie das benötigte Datenmuster anpassen und optimieren. Verwenden Sie beispielsweise die gesamte Tabelle und klicken Sie auf die Schaltfläche Daten bearbeiten / Datenvorschau.

    Eine neue Seite wird geöffnet, auf der Sie weitere Einstellungen vornehmen und die verfügbaren Daten verarbeiten können.

    Jetzt müssen zusätzliche berechnete Felder zu unserem Datensatz hinzugefügt werden: Quartal und Jahr des Vorgangs. Der aufmerksame Leser wird möglicherweise feststellen, dass solche Manipulationen auf der Seite des Parsers einfacher waren, bevor der Abschlussbericht in CSV gespeichert wurde. Zweifellos ist es jetzt mein Ziel, die Fähigkeiten und die Flexibilität bei der Einrichtung eines BI-Systems im laufenden Betrieb zu demonstrieren. Fahren Sie mit der Erstellung der berechneten Felder fort, indem Sie auf die Schaltfläche "Neues Feld" klicken.

    Ein neues Feld erstellen

    Um das Jahr der Operation und das Quartal auszuwählen, werden einfache Formeln verwendet:


    Formeln für das neue Feld ausfüllen

    Wenn die berechneten Felder erfolgreich erstellt und zum Beispiel hinzugefügt wurden, geben wir unserem Datensatz den Namen, beispielsweise "my_pnl_analyze", und klicken Sie auf die Schaltfläche "Speichern und visualisieren".

    Danach werden wir auf die Amazon QuickSight-Hauptplatine übertragen, und als Erstes müssen Sie einen Filter für das Berichtsdatum einrichten (unter Berücksichtigung der Tatsache, dass in drei Abschnitten dieselben Daten gesammelt wurden). Wählen Sie den Stichtag 2018-10-01 aus und klicken Sie auf die Schaltfläche Übernehmen, und wechseln Sie zur Registerkarte Visualisieren.

    Filterinstallation

    Jetzt können wir das Ergebnis für ein Portfolio in jeder Ebene visualisieren, zum Beispiel für jedes Wertpapier innerhalb eines Handelskontos, und dies wiederum unterteilt nach Währungen (da das Ergebnis in verschiedenen Währungen nicht vergleichbar ist) und nach Transaktionstypen. Beginnen wir mit dem leistungsfähigsten Werkzeug aller BI-Pivot-Tabellen. Um Platz zu sparen und die Flexibilität der Anzeige zu verbessern, habe ich Währungen in einem separaten Steuerelement dargestellt (analoges Segment in MS Excel).

    Die obige Tabelle zeigt, dass, wenn ein Investor beschließt, alle Aktien von FGC UES jetzt zu verkaufen, er den Verlust dadurch beheben wird Dividendenzahlungen in Höhe von 1 509,91 p. decken nicht ihre Kosten (1 763,36 S. - negative Währungsdifferenz und 174 S. - NDFL für Dividenden). Es ist sinnvoll, auf die besten Zeiten an der Börse zu warten.

    Das folgende Diagramm ist ein Balkendiagramm:


    Und jetzt erstellen wir eine Tabelle, die uns zeigt, wie viel wir in jedes Papier investiert haben, wie viele Tage es in unserem Portfolio gibt und wie hoch die Rendite für die gesamte Eigentumsdauer ist. Fügen Sie dazu zwei neue berechnete Felder hinzu: sum_investment und count_days.

    Feld sum_investment
    Вычисляемое поле sum_investment (сумма инвестиций) будем определять так:

    ifelse({stock_name} = 'ОФЗ 24019',{avg_open_price} * quantity * 10,{avg_open_price} * quantity)

    Такой подход к обработке расчета суммы вложений по облигациям обусловлен тем, что по ним всегда указывается цена – как процент от номинала (номинал в данном случае – 1000р).

    Count_days-Feld
    Вычисляемое поле count_day (количество дней владения бумагой) мы определим как разницу между датой операции и отчетной датой и в сводной таблице возьмем максимум:

    dateDiff(parseDate({date_oper}),parseDate({date_report}))

    Die endgültige Tabelle ist im folgenden Screenshot dargestellt:



    Schlussfolgerungen und Zusammenfassung


    Wir haben mit Ihnen die Implementierung des Berichtsparsers und die Analyse der von ihm aufbereiteten Daten mithilfe von Amazon-Diensten überprüft. Berührte auch einige geschäftliche und grundlegende Aspekte der Analyse des Anlageportfolios, weil Dieses Thema ist fast immens und es ist ziemlich schwierig, es in einen Artikel zu integrieren. Ich denke, es ist sinnvoll, es in einer separaten Publikation oder sogar in einem Publikationszyklus zu veröffentlichen.

    Die Verwendung des Berichtsverarbeitungswerkzeugs des Brokers und der damit verbundenen Ansätze und Algorithmen kann (mit einer entsprechenden Modifikation) für die Verarbeitung von Berichten anderer Brokers verwendet werden. Wenn Sie darüber nachdenken, den Code an Ihre Bedürfnisse anzupassen, bin ich bereit, einige Ratschläge zu geben. Zögern Sie nicht, Fragen zu stellen - ich werde auf jeden Fall versuchen, sie zu beantworten.

    Ich bin sicher, dass dieses System seine Anwendung finden und weiterentwickelt werden wird. Es ist beispielsweise geplant, die Berechnung der gesamten PNL für das Portfolio, die Bilanzierung von Verwahrstellen und anderen Provisionen (z. B. Auszahlungen von Geldern) sowie die Rücknahme von Anleihen usw. zu ergänzen. Die berechneten Felder auf der Quicksight-Seite wurden zu Demonstrationszwecken in der nächsten Version des Parsers verwendet. Alle diese zusätzlichen Spalten werden in Python übertragen und auf der Seite des Parsers berechnet.

    Als Architekt und Hauptgeschäftskunde dieser Lösung sehe ich eine weitere Modernisierung wie folgt: Nun, ich möchte diese XML-Berichte nicht jedes Mal manuell manuell anfordern! Natürlich gibt es bisher keine andere Möglichkeit, aber die Broker-API mit der Übertragung eines Tokens und eines Abtastbereichs wäre idealerweise für den Empfang von wöchentlichen Rohberichten geeignet. Die anschließende vollautomatische Verarbeitung auf Amazon-Seite: Von der Auslösung eines ETL-Jobs in AWS Glue bis hin zu fertigen Ergebnissen in Form von Diagrammen und Tabellen in Amazon QuickSight können Sie den Prozess vollständig automatisieren.

    Den vollständigen Quellcode finden Sie in meinem GitHub-Repository.

    Jetzt auch beliebt: