Recientemente, ha habido una alta volatilidad en los mercados bursátiles, cuando, por ejemplo, un documento estable de una empresa conocida puede perder varios por ciento a la vez por las noticias de sanciones contra su administración o viceversa, volar a ciegas en un informe positivo y las expectativas de los inversores sobre dividendos súper rentables.
¿Cómo determinar si la propiedad de un valor dado ha generado ingresos o solo pérdidas y decepciones?
(Fuente)En este artículo, le diré cómo identificar y visualizar el resultado financiero ajustado para valores.
Utilizando el ejemplo de informes de clientes de Opening Broker, consideraremos el análisis y la consolidación de informes de corretaje para el mercado de valores, construyendo la arquitectura de un sistema de informes en la nube con el posterior análisis simple y conveniente en AWS Quicksight.
Descripción de la tarea
Muchas capacitaciones y lecciones educativas nos informan sobre la necesidad de un diario del operador, donde todos los parámetros de transacción se registran para un análisis posterior y para resumir la estrategia comercial. Estoy de acuerdo en que este enfoque para trabajar en el intercambio le permite disciplinar a un comerciante, aumentar su conciencia, pero también puede cansarlo de un proceso tedioso.
Admito que al principio intenté seguir cuidadosamente los consejos del diario, escribí meticulosamente cada transacción con sus parámetros en una tabla de Excel, construí algunos informes, cuadros resumen, planifiqué transacciones futuras, pero ... rápidamente me cansé de todo.
¿Por qué es inconveniente mantener el diario de un comerciante manualmente?- el llenado manual del diario (incluso usando automatización parcial, en la forma de descargar transacciones diarias desde la terminal de negociación) se cansa rápidamente;
- existe un alto riesgo de error o error tipográfico con la entrada manual;
- Puede suceder que un comerciante activo se convierta en un inversor pasivo y regrese cada vez menos a esta revista, y luego se olvide por completo (mi caso); bien y finalmente
- podemos programar, ¿por qué no aprovechar esto y automatizar todo el proceso? ¡Entonces vamos!
A menudo, las compañías de corretaje son organizaciones de alta tecnología que brindan a sus clientes análisis de bastante alta calidad sobre casi todos los temas de interés. Es justo decir que este informe está mejorando cada vez más con cada actualización, pero incluso los más avanzados pueden no tener la personalización y consolidación que los clientes exigentes y curiosos quieren ver.
Por ejemplo, Opening Broker le permite recibir informes de corretaje en formato XML en su cuenta personal, pero si tiene un IIA y una cuenta de corretaje regular en la Bolsa de Moscú (MOEX), estos serán dos informes diferentes, y si tiene otra cuenta en St. Petersburg Stock Exchange (SPB), luego los dos primeros agregarán uno más.
En total, para obtener un diario consolidado del inversor, será necesario procesar tres archivos en formato XML.
Los informes antes mencionados sobre MOEX y SPB difieren ligeramente en sus formatos, que deberán tenerse en cuenta en el proceso de implementación del mapeo de datos.
Arquitectura del sistema en desarrollo.
El siguiente diagrama muestra el modelo de arquitectura del sistema en desarrollo:
Implementación del analizador
Recibiremos informes de las tres cuentas de la Cuenta personal durante el período máximo posible (se puede dividir en varios informes por año), los guardaremos en formato XML y los pondremos en una carpeta. Como datos de prueba para el estudio, utilizaremos una cartera de clientes ficticia, pero con parámetros lo más cercanos posible a las realidades del mercado.
Suponga que el inversionista Sr. X bajo consideración tiene una pequeña cartera de cinco valores:
- El informe sobre el intercambio SPB tendrá dos documentos: Apple y Microsoft;
- El informe sobre el intercambio MOEX (corretaje) contiene un documento: FGC UES;
- El informe sobre el MOEX Exchange (IIS) contiene dos valores: MMK y OFZ 24019;
Según nuestros cinco valores, puede haber transacciones en la compra / venta, pago de dividendos y un cupón, el precio puede cambiar, etc. Queremos ver la situación en el momento actual, a saber: el resultado financiero, teniendo en cuenta todos los pagos, transacciones y el valor de mercado actual.
Y aquí entra en juego Python, leemos la información de todos los informes en una matriz:
my_files_list = [join('Data/', f) for f in listdir('Data/') if isfile(join('Data/', f))] my_xml_data = []
Para el análisis, de los informes necesitamos varias entidades, a saber:
- Posiciones de valores en una cartera;
- Acuerdos concluidos;
- Operaciones no comerciales y otros movimientos de cuenta;
- Precios promedio de las posiciones abiertas
Para preparar la muestra, utilizaremos cuatro diccionarios para describir los conjuntos anteriores.
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' : []}
Algunas palabras sobre de qué se tratan estos diccionarios.
Diccionario Dict_stocksEl diccionario dict_stocks es necesario para almacenar información general sobre la cartera:
- Nombre del papel (stock_name);
- Nombre de la cuenta (SPB, MOEX BROK, MOEX IIS) (cuenta);
- Moneda utilizada para liquidaciones en este papel (moneda);
- Valor actual (en el momento de generar el informe en el Agente de apertura de cuenta personal) (current_cost). Aquí quiero señalar que para clientes demasiado exigentes, es posible realizar mejoras adicionales en el futuro y utilizar el recibo dinámico de una cotización de seguridad de un terminal comercial o del sitio web del intercambio correspondiente;
- El valor actual de la posición de seguridad en el momento en que se generó el informe (current_cost_rub)
De manera similar al artículo anterior, aquí también puede obtener la tasa del Banco Central en el momento actual o la tasa de cambio, como lo desee. - Saldo actual de valores (saldo)
Diccionario dict_dealsSe requiere el diccionario dict_deals para almacenar la siguiente información sobre transacciones completadas:
- Nombre del papel (stock_name);
- Nombre de la cuenta (SPB, MOEX BROK, MOEX IIS) (cuenta);
- Fecha de transacción, es decir T0 (date_oper);
- Tipo de operación (type_oper);
- El volumen de valores que participan en la transacción (cantidad);
- El precio al que se ejecutó la transacción (precio);
- Moneda en la que se realizó la transacción (moneda);
- Comisión de corretaje para una transacción (corretaje);
- El resultado financiero de la transacción (resultado)
Diccionario Dict_flowsEl diccionario dict_flows refleja el movimiento de fondos en la cuenta del cliente y se utiliza para almacenar la siguiente información:
- Nombre del papel (stock_name);
- Nombre de la cuenta (SPB, MOEX BROK, MOEX IIS) (cuenta);
- Fecha de transacción, es decir T0 (date_oper);
- Tipo de operación (type_oper). Puede tomar varios valores: div, NKD, tax;
- Moneda en la que se realizó la transacción (moneda);
- El resultado financiero de la operación (resultado)
Diccionario dict_avg_priceEl diccionario dict_avg_price es necesario para la información contable al precio promedio de compra de cada artículo:
- Nombre del papel (stock_name);
- Nombre de la cuenta (SPB, MOEX BROK, MOEX IIS) (cuenta);
- Precio promedio de una posición abierta (avg_open_price)
Procesamos una variedad de documentos XML y completamos estos diccionarios con los datos apropiados:
Todo el procesamiento pasa por el ciclo sobre todos los datos XML de los informes. La información sobre la plataforma de negociación, el código del cliente es el mismo en todos los informes, por lo que puede extraerlo de manera segura de las mismas etiquetas sin utilizar el mapeo.
Pero luego tenemos que usar un diseño especial que proporcionará el alias necesario para la etiqueta basada en el informe (SPB o MOEX), porque Los datos de naturaleza idéntica en estos informes se denominan de manera diferente.
Discrepancias de etiquetas- La comisión del agente de transacciones en el informe SBP se encuentra en la etiqueta de corretaje y en el informe MOEX : broker_commission ;
- La fecha de transacción de la cuenta no comercial en el informe SPB es fecha de operación , y en MOEX, es fecha de operación , etc.
Ejemplo de mapeo de etiquetas 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' } }
La función get_allias devuelve el nombre de la etiqueta necesaria para el procesamiento, tomando el nombre de la plataforma de negociación como entrada:
Función Get_allias def get_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'] )
La función get_briefcase es responsable de procesar la información sobre el estado de la cartera de clientes:
Función Get_briefcase def get_briefcase(XMLdata):
A continuación, la función get_deals recupera información sobre transacciones:
Función Get_deals def get_deals(XMLdata): stock_name_proc = '' closed_deal = XMLdata.find(deals) if not closed_deal: return
Además de procesar una matriz con información sobre los parámetros de la transacción, el precio promedio de una posición abierta y realizado por PNL utilizando el método FIFO también se calcula aquí. La clase PnlSnapshot es responsable de este cálculo, cuya creación con las pequeñas modificaciones se tomó como base el código presentado aquí:
Cálculo de pérdidas y ganancias
Y, por último, lo más difícil de implementar es la función de obtener información sobre operaciones no comerciales:
get_nontrade_operation . Su complejidad radica en el hecho de que en el bloque de informe utilizado para operaciones no comerciales, no hay información clara sobre el tipo de transacción y la seguridad a la que está vinculada esta operación.
Ejemplo de destinos de pago para operaciones no comercialesEl pago de dividendos o ingresos por cupones acumulados puede indicarse de la siguiente manera:
- Pago de ingresos del cliente <777777> dividendos < APPLE INC-ao> -> pago de dividendos del informe SPB;
- Pago de ingresos del cliente <777777> dividendos < MICROSOFT COM->
- Pago de rentas del cliente 777777i (NKD 2 OFZ 24019 ) retención de impuestos 0,00 rublos -> pago de cupón del informe MOEX;
- Pago de ingresos al cliente 777777 dividendos de FGC UES -ao retención de impuestos XX.XX rublos -> pago de dividendos del informe MOEX. etc.
En consecuencia, será difícil prescindir de expresiones regulares, por lo que las utilizaremos al máximo. El otro lado del problema es que el nombre de la empresa no siempre coincide con el nombre en la cartera o en las transacciones en el propósito del pago. Por lo tanto, el nombre recibido del emisor del propósito del pago debe correlacionarse adicionalmente con el diccionario. Como diccionario usaremos una variedad de ofertas, porque Existe la lista más completa de empresas.
La función
get_company_from_str recupera el nombre del emisor del comentario:
Función Get_company_from_str def get_company_from_str(comment): company_name = ''
La función
get_company_from_briefcase lleva el nombre de la compañía al diccionario si encuentra una coincidencia entre las compañías que participaron en las transacciones:
Función Get_company_from_briefcase def get_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
Y finalmente, la función final de recopilar datos sobre operaciones no comerciales es
get_nontrade_operation :
Función Get_nontrade_operation def get_nontrade_operation(XMLdata): nontrade_money_operation = XMLdata.find(flows) if not nontrade_money_operation: return try: 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)
El resultado de la recopilación de datos de los informes serán tres marcos de datos, que son aproximadamente los siguientes:
- DataFrame con información sobre los precios promedio de las posiciones abiertas:
- Deal DataFrame:
- DataFrame con información sobre operaciones no comerciales:
Entonces, todo lo que nos queda por hacer es realizar una unión externa de la tabla de transacciones con la tabla de información de cartera:
df_result = pd.merge(df_deals, df_stocks_avg, how='outer', on=['stock_name', 'account', 'currency']).fillna(0) df_result.sample(10)
Y finalmente, la parte final del procesamiento de la matriz de datos es la fusión de la matriz de datos obtenida en el paso anterior con el DataFrame para transacciones no comerciales.
El resultado del trabajo realizado es una gran mesa plana con toda la información necesaria para el análisis:
df_result_full = df_result.append(df_flows, ignore_index=True).fillna(0) df_result_full.sample(10).head()
El conjunto de datos resultante (Informe final) del DataFrame se carga fácilmente en el CSV y luego se puede utilizar para un análisis detallado en cualquier sistema de BI.
if not exists('OUTPUT'): makedirs('OUTPUT') report_name = 'OUTPUT\my_trader_diary.csv' df_result_full.to_csv(report_name, index = False, encoding='utf-8-sig')
Cargar y procesar datos en AWS
El progreso no se detiene y ahora los servicios en la nube y los modelos informáticos sin servidor están ganando gran popularidad en el procesamiento y almacenamiento de datos. Esto se debe en gran parte a la simplicidad y al bajo costo de este enfoque, cuando no necesita comprar equipos costosos para construir una arquitectura de sistema para computación compleja o procesar grandes datos, sino que simplemente alquila la energía en la nube por el tiempo que necesita e implementa los recursos necesarios lo suficientemente rápido por una tarifa relativamente pequeña .
Uno de los proveedores de nube más grandes y conocidos del mercado es Amazon. Veamos el ejemplo del entorno de Amazon Web Services (AWS) para crear un sistema analítico para procesar datos en nuestra cartera de inversiones.
AWS tiene una amplia selección de herramientas, pero utilizaremos lo siguiente:
- Amazon S3 : almacenamiento de objetos, que le permite almacenar cantidades casi ilimitadas de información;
- AWS Glue : el servicio ETL en la nube más potente que puede determinar la estructura y generar el código ETL a partir de los datos de origen dados;
- Amazon Athena , un servicio de consulta SQL en línea sin servidor, le permite analizar rápidamente los datos de S3 sin mucha preparación. También tiene acceso a los metadatos que prepara AWS Glue, que le permite acceder a los datos inmediatamente después de pasar el ETL;
- Amazon QuickSight : servicio de BI sin servidor, puede crear cualquier visualización, informes analíticos "sobre la marcha", etc.
La documentación de Amazon está bien, en particular, hay un buen artículo
Mejores prácticas al usar Athena con AWS Glue , que describe cómo crear y usar tablas y datos usando AWS Glue. Aprovechemos las ideas principales de este artículo y aplíquelas para crear nuestra propia arquitectura de un sistema de informes analíticos.
Los archivos CSV preparados por nuestro analizador de informes se agregarán al depósito S3. Está previsto que la carpeta correspondiente en S3 se reponga todos los sábados, al final de la semana de negociación, por lo que no puede prescindir de la partición de datos en la fecha de formación y procesamiento del informe.
Además de optimizar el funcionamiento de las consultas SQL a dichos datos, este enfoque nos permitirá realizar análisis adicionales, por ejemplo, para obtener la dinámica de los cambios en el resultado financiero de cada artículo, etc.
Trabaja con Amazon S3- Cree un depósito en S3, llámelo "informe-analizador";
- En este bucket "report-parser" crea una carpeta llamada "my_trader_diary";
- En el directorio "my_trader_diary", cree un directorio con la fecha del informe actual, por ejemplo, "date_report = 2018-10-01" y coloque el archivo CSV en él;
- Solo en aras del experimento y una mejor comprensión de la partición, crearemos dos directorios más: “date_report = 2018-09-27” y “date_report = 2018-10-08”. Ponemos el mismo archivo CSV en ellos;
- El último "informe-analizador" del cubo S3 debería parecerse al que se muestra en las imágenes a continuación:
Trabajar con AWS GlueEn general, solo puede hacer Amazon Athena para crear una tabla externa a partir de los datos que se encuentran en S3, pero AWS Glue es una herramienta más flexible y conveniente para esto.
- Entramos en AWS Glue y creamos un nuevo Crawler, que recopilará una tabla de archivos CSV separados informando las fechas:
- Establecer el nombre del nuevo rastreador;
- Indicamos el repositorio de dónde obtener los datos (s3: // report-parser / my_trader_diary /)
- Seleccionamos o creamos un nuevo rol de IAM que tendrá acceso para iniciar Crawler y acceder al recurso especificado en S3;
- A continuación, debe establecer la frecuencia de inicio. Lo ponemos a la demanda por ahora, pero en el futuro, creo que esto cambiará y el lanzamiento será semanal;
- Guarde y espere a que se cree el rastreador.
- Cuando el Crawler entre en el estado Listo, ¡inícielo!
- Una vez que funcione, aparecerá una nueva tabla my_trader_diary en AWS Glue: Base de datos -> pestaña Tablas:
Considere la tabla generada con más detalle.
Si hace clic en el nombre de la tabla creada, iremos a la página con la descripción de los metadatos. En la parte inferior hay un diseño de tabla y el más reciente es una columna que no estaba en el archivo CSV de origen: date_report. Esta columna AWS Glue se crea automáticamente en función de la definición de secciones de los datos de origen (en el Bucket S3, nombramos especialmente las carpetas date_report = AAAA-MM-DD, lo que nos permitió usarlas como secciones separadas por fecha).
Particionamiento de tablaEn la misma página en la esquina superior derecha hay un botón Ver particiones, al hacer clic en el que podemos ver en qué secciones se compone nuestra tabla generada:
Análisis de datos
Teniendo a nuestra disposición datos procesados cargados, podemos comenzar a analizarlos fácilmente. Para comenzar, considere las capacidades de Amazon Athena como la forma más fácil y rápida de realizar consultas analíticas. Para hacer esto, vaya al servicio Amazon Athena, seleccione la base de datos que necesitamos (financiera) y escriba el siguiente código SQL:
select d.date_report, d.account, d.stock_name, d.currency, sum(d.quantity) as quantity, round(sum(d.result), 2) as result from my_trader_diary d group by d.date_report, d.account, d.stock_name, d.currency order by d.account, d.stock_name, d.date_report;
Esta solicitud nos mostrará un resultado financiero neto para cada valor para todas las fechas de informes. Porque descargamos el mismo informe tres veces para diferentes fechas, el resultado no cambiará, lo que, por supuesto, en un mercado real será diferente:
Pero, ¿qué sucede si queremos visualizar los datos recibidos en forma de tablas o diagramas flexibles? Aquí Amazon QuickSight viene al rescate, con la ayuda de la cual puede configurar análisis flexibles casi tan rápido como escribir una consulta SQL. Iremos al servicio Amazon QuickSight (si no se ha registrado allí, entonces es necesario registrarse).
Haga clic en el botón Nuevos análisis -> Nuevo conjunto de datos y en la ventana que aparece seleccionar las fuentes para el conjunto de datos, haga clic en Athena:
Vamos a encontrar un nombre para nuestra fuente de datos, por ejemplo, "PNL_analysis" y hacer clic en el botón "Crear fuente de datos".
A continuación, se abre la ventana Elija su tabla, donde debe seleccionar la base de datos y la tabla de origen de datos. Seleccionamos la base de datos financiera y la tabla que contiene: my_traider_diary. De manera predeterminada, se utiliza toda la tabla, pero si selecciona "Usar SQL personalizado", puede personalizar y ajustar la muestra de datos que necesita. Por ejemplo, usamos toda la tabla y hacemos clic en el botón Editar / Vista previa de datos.
Se abrirá una nueva página donde puede realizar configuraciones adicionales y procesar datos existentes.
Ahora necesitamos agregar campos calculados adicionales a nuestro conjunto de datos: trimestre y año de operación. Un lector atento puede notar que tales manipulaciones fueron más fáciles de realizar en el analizador antes de guardar el Informe Final en CSV. Sin lugar a dudas, mi objetivo ahora es demostrar las capacidades y la flexibilidad de la configuración del sistema de BI sobre la marcha. Continuamos creando campos calculados haciendo clic en el botón "Nuevo campo".
Para resaltar el año de la operación y el trimestre, se utilizan fórmulas simples:
Completar fórmulas para un nuevo campo Cuando los campos calculados se hayan creado y agregado correctamente a la selección, asigne un nombre a nuestro conjunto de datos, por ejemplo, "my_pnl_analyze" y haga clic en el botón "Guardar y visualizar".
Después de eso, nos transferimos a la placa principal de Amazon QuickSight y lo primero que debemos hacer es configurar un filtro para la fecha del informe (teniendo en cuenta que se recopilaron los mismos datos de tres secciones). Seleccione la fecha del informe 2018-10-01 y haga clic en el botón Aplicar y vaya a la pestaña Visualizar.
, , , (.. ) . BI – . , ( MS Excel)
, , , .. 1 509.91 . (1 763.36 . – 174 . – ). .
– :
, , , . : sum_investment count_days.
sum_investmentsum_investment ( ) :
ifelse({stock_name} = ' 24019',{avg_open_price} * quantity * 10,{avg_open_price} * quantity)
, – ( – 1000).
count_dayscount_day ( ) :
dateDiff(parseDate({date_oper}),parseDate({date_report}))
:
« » Amazon. , .. , , , .
, ( ) . , , , – .
, . , PNL (, ), .… Quicksight , , Python .
- , : XML-! , , API . Amazon: ETL-job AWS Glue Amazon QuickSight .
GitHub