Los inspiradores detrás del desarrollo del servicio de estadísticas para Tinkoff Investments fueron:
- artículo sobre Habré “Lo que Tinkoff Investments no dice”
- análisis de los deseos de los usuarios de la plataforma
- un artículo sobre el cálculo de comisiones .
- ¿Qué se discutirá?
- Desarrollando un servicio de estadísticas paso a paso:
- Conexión a la API de Tinkoff Invest
- Dibujar datos de Tinkoff Invest API en un navegador
- Recepción de informes y transacciones de corretaje
- Obtener informe de intermediario
- Método para obtener la fecha, teniendo en cuenta la resta de la fecha actual
- Solicitud de generación de informes
- Resultado:
- ObtenerDividendosEmisor Extranjero
- ObtenerOperacionesPorCursor
- Cálculo y salida de información de interés
- Trabajando con precios
- El costo de los contratos de futuros
- mercado OTC
- Operaciones matemáticas sobre herramientas.
- ¡El microservicio está listo!
- Conclusiones y planes para el futuro.
- https://opexbot.info
¿Qué se discutirá?
- Sólo la parte aplicada sobre el desarrollo.
- Conocimiento y experiencia reales, que son muy importantes en el trabajo con instrumentos financieros.
- Descripción general de los problemas a trabajar
Entonces, quiero calcular las estadísticas comerciales y hacerlo de una manera conveniente.
Desarrollando un servicio de estadísticas paso a paso:
- Conexión a la API de Tinkoff Invest
- Dibujar datos de Tinkoff Invest API en un navegador
- Recepción de informes y transacciones de corretaje
- Cálculo y salida de información de interés
- Conclusiones y planes para el futuro.
Conexión a la API de Tinkoff Invest
Para conectarse a la API, puede tomar cualquier SDK de la documentación https://github.com/Tinkoff/investAPI#sdk . O el paquete npm ` tinkoff-sdk-grpc-js `. Es importante que los desarrolladores actualicen el paquete a la última versión. Instalar
Comprobación
const { createSdk } = require(‘tinkoff-sdk-grpc-js’); // Token que se puede obtener así const TOKEN = ‘TUPI’; // El nombre de la aplicación por la que se le puede encontrar en los registros de TCS. const appName = ‘tcsstat’; const sdk = createSdk(TOKEN, appName); (async () => { console.log(await sdk.users.getAccounts()); })();
Resultado: se mostrará una lista de sus cuentas en la consola. Por ejemplo, analicemos los matices:
- En el listado de cuentas hay un “Banco de inversión”, con el que no se puede trabajar usando la API
- Tenga en cuenta que los campos vienen en camelCase, mientras que en la documentación estos campos se presentan en under_score.
- Será así en todas partes, por lo que no puede simplemente tomar y copiar un campo de la documentación.
Útil:
- Puedes encontrar este código en la rama del proyecto.
https://github.com/pskucherov/tcsstat/tree/step1 https://github.com/pskucherov/tcsstat/compare/step1
Dibujar datos de Tinkoff Invest API en un navegador
Tomé next.js y socket.io. Esta no es una recomendación fuerte, elija a su discreción.
npx create-next-app@latest npm i socket.io socket.io-client
Inmediatamente pasamos al paso de amistad next+socket+investapi, y vemos la sección Útil de este paso para conocer todos los detalles. Voy a describir los detalles:
- En el lado de nodejs (servidor), hay un archivo pages/api/investapi.js. Aquí es donde creamos el servidor socket.io y nos conectamos a investapi.
- En el lado del navegador (cliente), nos conectamos al servidor a través de un socket y solicitamos los datos de la cuenta al corredor.
- Recibimos datos del corredor en el servidor y luego los enviamos al cliente. Cuando se reciben en el cliente, se muestran en el navegador.
Resultado: en la consola del navegador podemos ver información sobre las cuentas. Es decir, en el último paso, vimos información sobre las cuentas en la consola del servidor (nodejs), en el paso actual, transferimos esta información al cliente (navegador).
Ahora hagamos que pueda seleccionar una cuenta desde el navegador, y si no hay token, se envía un error a la consola. El trabajo es simple y nada nuevo, por lo que solo doy enlaces a confirmaciones.
- https://github.com/pskucherov/tcsstat/commit/7e1ac57061e5e971588479015b06d8814d6609a9
- https://github.com/pskucherov/tcsstat/commit/b28ac973a57494f5232589b4cb6b9fb13b8af759
Útil:
- Cómo hacer amigos a continuación y socket se describe en detalle aquí .
- Código de amistad siguiente+socket+investapi:
https://github.com/pskucherov/tcsstat/commit/a443a4ac1bb4f0aa898f638128755fe7391ee381 Para quienes lo anterior es difícil, permanecemos en esta etapa y nos ocupamos del código. Si tiene alguna pregunta, pregunte. https://github.com/pskucherov/tcsstat/tree/step2 https://github.com/pskucherov/tcsstat/compare/step1…step2
Recepción de informes y transacciones de corretaje
Hay tres métodos para recibir informes y transacciones de corretaje
Desde el principio es importante saber:
- El informe de corretaje se genera en el modo T-3, es decir las transacciones se muestran allí después de su ejecución real.
- Por lo tanto, si solicita este informe de los últimos dos días, estará listo en tres días.
- Para obtener transacciones de los últimos días, usamos el método para recibir transacciones, pero recuerde que su id y contenido pueden cambiar después de generar el informe de corretaje.
Obtener informe de intermediario
Para obtener un informe de corretaje, debe tomar la identificación de la cuenta, la fecha de inicio y la fecha de finalización del informe, pero no más de 31 días. Enviamos una solicitud para generar un informe a la API en generar _broker_report_request , obtenemos un ID de tarea en respuesta. Después de eso, usando este ID de tarea, obtenemos datos de get _broker_report_response.
- Debe guardar el TaskID para siempre exactamente para estas fechas.
- Dado que si lo pierde, entonces para las fechas solicitadas, el informe vendrá primero en respuesta a la solicitud de generación,
- Y entonces no vendrá en absoluto.
Método para obtener la fecha, teniendo en cuenta la resta de la fecha actual
const getDateSubDay = (subDay = 5, start = true) => { const date = new Date(); fecha.setUTCDate(fecha.getUTCDate() – subDía); if (inicio) { fecha.setUTCHours(0, 0, 0, 0); } else { fecha.setUTCHours(23, 59, 59, 999); } fecha de regreso; };
Solicitud de generación de informes
const brokerReport = await (sdk.operations.getBrokerReport)({ generarBrokerReportRequest: { accountId, from, to, }, });
Resultado:
- Como resultado de la primera ejecución del comando, obtenemos el taskId.
- El informe comienza a generarse en el lado del corredor. Cuando está listo es desconocido, esperamos y extraemos periódicamente el taskId antes del informe.
- ¿Por qué? Porque si el informe no está listo, arroja un error. Si el informe no está listo por parte del corredor, entonces se trata de un error en su código. Procese: 30058|INVALID_ARGUMENT|tarea aún no completada, inténtelo de nuevo más tarde
El código para esperar y recibir un informe se parece a esto.
const timer = async time => { return new Promise(resolve => setTimeout(resolve, time)); } const getBrokerResponseByTaskId = asíncrono (taskId, página = 0) => { try { return await (sdk.operations.getBrokerReport)({ getBrokerReportRequest: { taskId, página, }, }); } catch (e) { console.log(‘esperar’, e); esperar temporizador (10000); volver esperar getBrokerResponseByTaskId(taskId, página); } };
Entonces ocurre la misma magia. Detenemos nuestro script, lo iniciamos de nuevo, no tenemos un ID de tarea. Ejecutamos el código con la solicitud taskId, pero ya no obtenemos el taskId, sino inmediatamente el informe. ¡Magia! Y todo estaría bien si siempre fuera así. Pero en un mes no habrá datos en absoluto. útil :
https://github.com/pskucherov/tcsstat/tree/step3.1 https://github.com/pskucherov/tcsstat/compare/step3.1
- Si alguien se encuentra con esto, entonces bienvenido al problema . Después de que reparen esta magia, perderá su poder y será de alguna manera diferente. Pero en el momento actual (21/03/2023) funciona así.
ObtenerDividendosEmisor Extranjero
Alguien podría pensar que el método es similar al anterior y puedes usar un único método en el que solo cambias el nombre de las operaciones. ¡Pero no lo adivinaron! La denominación allí es muy diferente tanto en los métodos como en la información devuelta. Y el recuento de páginas comienza desde 0, luego desde 1. Para no confundirse con todo esto, es más fácil escribir dos métodos diferentes. Lo cual es extraño, porque la lógica de trabajo es la misma. Escupí durante mucho tiempo cuando traté de hacer un método y había menos código. No habrá ejemplos aquí.
ObtenerOperacionesPorCursor
Mi favorito de los tres. Aunque no la más precisa, pero sí la más adecuada. Hacemos una solicitud desde el inicio de la creación de una cuenta hasta la fecha máxima posible (cierre de una cuenta o la actual). Obtenemos la respuesta, tomamos el cursor y volvemos a solicitar siempre que haya datos. Y el código es más conciso que en los ejemplos anteriores.
const timer = async time => { return new Promise(resolve => setTimeout(resolve, time)); } const getOperationsByCursor = async (sdk, accountId, from, to, cursor = ») => { try { const reqData = { accountId, from, to, limit: 1000, state: sdk.OperationState.OPERATION_STATE_EXECUTED, sin Comisiones: falso, sinTrades: falso, sinAlojamientos: falso, cursor, }; volver esperar sdk.operations.getOperationsByCursor(reqData); } catch (e) { temporizador de espera (60000); return await getOperationsByCursor(sdk, accountId, from, to, cursor = »); } };
El borrador para ejecutar está aquí: https://github.com/pskucherov/tcsstat/tree/step3.3 https://github.com/pskucherov/tcsstat/compare/step3.3 Ahora estamos listos para agregar operaciones de recepción a nuestra aplicación. Si se hace correctamente, debe obtener informes de corretaje para toda la existencia de la cuenta. Y para los datos faltantes, esos mismos T-3, recargar desde operaciones. Pero esto se puede separar en un artículo separado. De los principales matices que encontrará es pegar operaciones y un informe de corretaje.
- Si hoy recibió un informe de corretaje y transacciones para las fechas requeridas, póngalo todo en la base de datos, entonces no hay problemas.
- Tendrá problemas mañana cuando reciba la siguiente porción de datos del informe y las operaciones y decida sincronizarlos con la base de datos existente.
- Muchos matices sobre la identificación no coincidente o cambiante después del procesamiento
- Luego, para el mercado OTC, las identificaciones no coinciden en absoluto.
- Así como los matices de sincronizar instrumentos, que de nuevo no coinciden, por las peculiaridades de la API. Pero esa es otra historia.
Agreguemos obtener información sobre operaciones a nuestra aplicación. La pregunta principal será dónde se procesarán y almacenarán los datos.
- Si lo haces por ti mismo, consumirás los mismos datos desde diferentes dispositivos. Luego necesita procesar y almacenar datos en el servidor.
- Si tiene una gran cantidad de datos diferentes consumidos por muchos usuarios diferentes, entonces debe decidir qué es más importante: la velocidad de los usuarios o el ahorro de hierro de su lado. Quien puede permitirse una cantidad infinita de hardware cuenta todo en su servidor y lo hace súper rápido para los usuarios, ahorrando recursos al usuario, como la batería y el tráfico, que es muy importante en los teléfonos.
A su vez, contar en el navegador no es la solución más óptima en principio. Por lo tanto, lo que no es caro, lo consideramos en nuestro servidor. El resto lo dejamos al cliente. Tengo muchas ganas de tomar y calcular la comisión en el servidor. Pero aquí viene el matiz llamado “interactividad”. Digamos que tienes miles de operaciones y tardas cinco minutos en recibirlas. ¿Qué tendrá el usuario en este momento? ¿Hilandero? ¿Progreso? Infa acerca de cuánto se subió? Es ideal usar “espera activa” cuando el usuario en el proceso ya pudo ver algo. Aquí está el resultado:
- Cargando página
- Se solicitan todas las facturas
- Después de eso, todas las transacciones con comisiones por transacciones ejecutadas se solicitan para todas las cuentas. A medida que se reciben los datos, se procesan en el navegador.
Para no filtrar los datos en los eventos cada vez, extraemos nuestro propio evento para cada cuenta. Como esto:
socket.emit(‘sdk:getOperationsCommissionResult_’ + accountId, { items: data?.items, inProgress: Boolean(nextCursor), });
El borrador para lanzar está aquí: https://github.com/pskucherov/tcsstat/tree/step3 https://github.com/pskucherov/tcsstat/compare/step2…step3 Continuando. ¡Qué bueno que hayas leído esta línea!
Cálculo y salida de información de interés
Depende de quién necesita qué información. Por eso, inmediatamente te cuento los principales matices que encontrarás.
Trabajando con precios
Todos los que trabajan con finanzas saben que las transacciones de dinero solo deben realizarse con números enteros. Debido a la imprecisión de los valores posteriores al punto decimal y al error acumulativo con un gran número de operaciones. Es por eso que todos los precios se presentan en el siguiente formato MoneyValue
campo | tipo | Descripción |
---|---|---|
divisa | cadena | Código de moneda ISO de cadena |
unidades | int64 | Parte entera de la suma, puede ser un número negativo |
nano | int32 | Parte fraccionaria de la cantidad, puede ser un número negativo |
Los procesamos por separado, luego los llevamos al valor del precio:
cotización.unidades + cotización.nano / 1e9
El costo de los contratos de futuros
El precio de los futuros se presenta en puntos, cuando tiene un futuro de divisas, necesita saber la tasa. Y por supuesto el precio en puntos y el escalón de precio. Cuando calcula el beneficio de las transacciones, esto puede dispararse, porque. si calcula la cantidad total multiplicando el precio por la cantidad. Aquí hay que tener cuidado. Por ahora, veremos cómo va. Esto se aplica a los futuros de divisas, en otros lugares todo está bien con esto.
mercado OTC
Este mercado tiene muchas peculiaridades, así que estudiemos las operaciones en él por separado.Cuando comience a sincronizar las operaciones, resultará que necesita llevar figi / ticker al mismo formulario para que coincida correctamente con el instrumento. Cuando comience a sincronizar esto con el informe de corretaje, resultará que el tradeID de la misma transacción tiene letras al principio de las transacciones y no están en el informe de corretaje. Por lo tanto, no se pueden comparar… ejem-ejem… ¡por comparación! Hice coincidir el tiempo de negociación, el ticker y la coincidencia de que un tradeId está contenido en otro. Correcto, no sé. Quien se encuentre con esto y a quien le importe, venga al problema o comience uno nuevo.
Operaciones matemáticas sobre herramientas.
Es imposible, sin mirar, realizar operaciones matemáticas con toda la lista. Para no agregar cálido a suave, siempre verificamos la moneda y procesamos solo si estamos seguros de que la moneda coincide y los puntos se convierten a la moneda deseada. Armados con el conocimiento sobre cómo trabajar con números bancarios, calcularemos la comisión gastada en cada una de las cuentas. Así: https://github.com/pskucherov/tcsstat/tree/step4 https://github.com/pskucherov/tcsstat/compare/step3…step4
¡El microservicio está listo!
https://github.com/pskucherov/tcsstat Como tarea, puede verificar si el servicio funciona con una conexión lenta, cuando las conexiones están rotas, cuando Internet está desconectado, cuando hay errores o límites vencidos por parte del corredor.
Conclusiones y planes para el futuro.
- Aprendió sobre las operaciones básicas y cómo trabajar con la API de Invest
- Tiempo empleado ~ 10 horas
- Nivel de dificultad ~ junior+ / medio bajo
Si continúa refinando el microservicio, podría terminar con algo como esto
https://opexbot.info
Este es mi desarrollo, para aquellos que son demasiado perezosos para comprender, correr y contar por sí mismos. Planeo agregar análisis allí a pedido de los usuarios. Si te gustó el artículo, suscríbete a mi canal de Telegram .
Полезная статья. Не могу представить, сколько усилий автора потребовалось, чтобы все описать. Благодарю.