Οι εμπνευστές πίσω από την ανάπτυξη της στατιστικής υπηρεσίας για την Tinkoff Investments ήταν:
- άρθρο στο Habré «Τι δεν λέει η Tinkoff Investments»
- ανάλυση των επιθυμιών των χρηστών της πλατφόρμας
- ένα άρθρο για τον υπολογισμό των προμηθειών .
- Τι θα συζητηθεί;
- Ανάπτυξη μιας υπηρεσίας στατιστικής βήμα προς βήμα:
- Σύνδεση με το Tinkoff Invest API
- Σχεδίαση δεδομένων από το Tinkoff Invest API σε πρόγραμμα περιήγησης
- Λήψη αναφορών και συναλλαγών μεσιτείας
- GetBrokerReport
- Μέθοδος λήψης της ημερομηνίας, λαμβάνοντας υπόψη την αφαίρεση από την τρέχουσα ημερομηνία
- Αίτημα δημιουργίας αναφοράς
- Αποτέλεσμα:
- GetDividendsForeignIssuer
- GetOperationsByCursor
- Υπολογισμός και έξοδος πληροφοριών ενδιαφέροντος
- Δουλεύοντας με τιμές
- Το κόστος των συμβολαίων μελλοντικής εκπλήρωσης
- OTC αγορά
- Μαθηματικές πράξεις σε εργαλεία
- Το Microservice είναι έτοιμο!
- Συμπεράσματα και σχέδια για το μέλλον
- https://opexbot.info
Τι θα συζητηθεί;
- Μόνο το εφαρμοσμένο μέρος για την ανάπτυξη.
- Πραγματική γνώση και εμπειρία, τα οποία είναι πολύ σημαντικά στη συνεργασία με χρηματοοικονομικά μέσα.
- Επισκόπηση των θεμάτων που πρέπει να εργαστούν
Έτσι, θέλω να υπολογίσω τα στατιστικά στοιχεία του εμπορίου και να το κάνω με βολικό τρόπο.
Ανάπτυξη μιας υπηρεσίας στατιστικής βήμα προς βήμα:
- Σύνδεση με το Tinkoff Invest API
- Σχεδίαση δεδομένων από το Tinkoff Invest API σε πρόγραμμα περιήγησης
- Λήψη αναφορών και συναλλαγών μεσιτείας
- Υπολογισμός και έξοδος πληροφοριών ενδιαφέροντος
- Συμπεράσματα και σχέδια για το μέλλον
Σύνδεση με το Tinkoff Invest API
Για να συνδεθείτε στο API, μπορείτε να πάρετε οποιοδήποτε sdk από την τεκμηρίωση https://github.com/Tinkoff/investAPI#sdk . Ή πακέτο npm ` tinkoff-sdk-grpc-js` . Είναι σημαντικό το πακέτο να ενημερωθεί στην πιο πρόσφατη έκδοση από τους προγραμματιστές. Εγκαθιστώ
npm και tinkoff-sdk-grpc-js
Ελεγχος
const {createSdk } = require(‘tinkoff-sdk-grpc-js’); // Token που μπορεί να ληφθεί ως εξής const TOKEN = ‘YOURAPI’; // Το όνομα της εφαρμογής με την οποία μπορείτε να βρεθείτε στα αρχεία καταγραφής TCS. const appName = ‘tcsstat’; const sdk = createSdk(TOKEN, appName); (async () => { console.log(await sdk.users.getAccounts()); })();
Αποτέλεσμα: μια λίστα με τους λογαριασμούς σας θα εμφανιστεί στην κονσόλα. Για παράδειγμα, ας αναλύσουμε τις αποχρώσεις:
- Στη λίστα των λογαριασμών υπάρχει μια “Τράπεζα Επενδύσεων”, με την οποία δεν μπορείτε να εργαστείτε χρησιμοποιώντας το API
- Σημειώστε ότι τα πεδία έρχονται σε camelCase, ενώ στην τεκμηρίωση αυτά τα πεδία παρουσιάζονται σε under_score.
- Θα είναι έτσι παντού, επομένως δεν μπορείτε απλώς να πάρετε και να αντιγράψετε ένα πεδίο από την τεκμηρίωση.
Χρήσιμος:
- Μπορείτε να βρείτε αυτόν τον κωδικό στον κλάδο του έργου
https://github.com/pskucherov/tcsstat/tree/step1 https://github.com/pskucherov/tcsstat/compare/step1
Σχεδίαση δεδομένων από το Tinkoff Invest API σε πρόγραμμα περιήγησης
Πήρα το next.js και το socket.io. Αυτή δεν είναι μια ισχυρή σύσταση, επιλέξτε κατά την κρίση σας.
npx create-next-app@latest npm i socket.io socket.io-client
Προχωράμε αμέσως στο βήμα φιλίας next+socket+investapi και δείτε την ενότητα Χρήσιμα αυτού του βήματος για όλες τις λεπτομέρειες. Θα περιγράψω τις λεπτομέρειες:
- Στην πλευρά του nodejs (διακομιστής), υπάρχει ένα αρχείο pages/api/investapi.js. Εδώ δημιουργούμε τον διακομιστή socket.io και συνδεόμαστε στο investapi.
- Από την πλευρά του προγράμματος περιήγησης (πελάτη), συνδέουμε τον διακομιστή μέσω μιας υποδοχής και ζητάμε δεδομένα λογαριασμού από τον μεσίτη.
- Λαμβάνουμε δεδομένα από τον μεσίτη στον διακομιστή και, στη συνέχεια, τα στέλνουμε στον πελάτη. Όταν λαμβάνονται στον πελάτη, εμφανίζονται στο πρόγραμμα περιήγησης.
Αποτέλεσμα: στην κονσόλα του προγράμματος περιήγησης μπορούμε να δούμε πληροφορίες σχετικά με λογαριασμούς. Δηλαδή, στο τελευταίο βήμα, είδαμε πληροφορίες για λογαριασμούς στην κονσόλα διακομιστή (nodejs), στο τρέχον βήμα, μεταφέραμε αυτές τις πληροφορίες στον πελάτη (browser).
Τώρα ας το κάνουμε έτσι ώστε να μπορείτε να επιλέξετε έναν λογαριασμό από το πρόγραμμα περιήγησης και αν δεν υπάρχει διακριτικό, τότε αποστέλλεται ένα σφάλμα στην κονσόλα. Το έργο είναι απλό και τίποτα καινούργιο, γι’ αυτό δίνω μόνο συνδέσμους για δεσμεύσεις
- https://github.com/pskucherov/tcsstat/commit/7e1ac57061e5e971588479015b06d8814d6609a9
- https://github.com/pskucherov/tcsstat/commit/b28ac973a57494f5232589b4cb6b9fb13b8af759
Χρήσιμος:
- Το πώς να κάνετε φίλους next and socket περιγράφεται αναλυτικά εδώ .
- Κωδικός φιλίας next+socket+investapi:
https://github.com/pskucherov/tcsstat/commit/a443a4ac1bb4f0aa898f638128755fe7391ee381 Για όσους τα παραπάνω είναι δύσκολα, τότε μένουμε σε αυτό το στάδιο και ασχολούμαστε με τον κώδικα. Εάν έχετε ερωτήσεις – ρωτήστε. https://github.com/pskucherov/tcsstat/tree/step2 https://github.com/pskucherov/tcsstat/compare/step1…step2
Λήψη αναφορών και συναλλαγών μεσιτείας
Υπάρχουν τρεις μέθοδοι για να λαμβάνετε αναφορές και συναλλαγές μεσιτείας
Από την αρχή είναι σημαντικό να γνωρίζετε:
- Η αναφορά μεσιτείας δημιουργείται στη λειτουργία T-3, δηλ. Οι συναλλαγές εμφανίζονται εκεί μετά την πραγματική τους εκτέλεση.
- Αντίστοιχα, εάν ζητήσετε αυτήν την αναφορά για τις δύο τελευταίες ημέρες, θα είναι έτοιμη σε τρεις ημέρες.
- Για να κάνουμε συμφωνίες για τις τελευταίες ημέρες, χρησιμοποιούμε τη μέθοδο λήψης πράξεων, αλλά να θυμάστε ότι το αναγνωριστικό και το περιεχόμενό τους ενδέχεται να αλλάξουν μετά τη διαμόρφωση της αναφοράς μεσιτείας.
GetBrokerReport
Για να λάβετε μια αναφορά μεσιτείας, πρέπει να λάβετε το αναγνωριστικό λογαριασμού, την ημερομηνία έναρξης και την ημερομηνία λήξης της αναφοράς, αλλά όχι περισσότερο από 31 ημέρες. Στέλνουμε ένα αίτημα για τη δημιουργία αναφοράς στο API στο generate _broker_report_request , λάβετε ένα αναγνωριστικό εργασίας ως απόκριση. Μετά από αυτό, χρησιμοποιώντας αυτό το taskId, λαμβάνουμε δεδομένα από το get _broker_report_response.
- Πρέπει να αποθηκεύσετε το TaskID για πάντα ακριβώς για αυτές τις ημερομηνίες.
- Εφόσον εάν το χάσετε, τότε για τις ζητούμενες ημερομηνίες η αναφορά θα έρθει πρώτα ως απάντηση στο αίτημα δημιουργίας,
- Και μετά δεν θα έρθει καθόλου.
Μέθοδος λήψης της ημερομηνίας, λαμβάνοντας υπόψη την αφαίρεση από την τρέχουσα ημερομηνία
const getDateSubDay = (subDay = 5, start = true) => { const date = new Date(); date.setUTCDate(date.getUTCDate() – subDay); if (start) { date.setUTCHours(0, 0, 0, 0); } else { date.setUTCHours(23, 59, 59, 999); } ημερομηνία επιστροφής; };
Αίτημα δημιουργίας αναφοράς
const brokerReport = await (sdk.operations.getBrokerReport)({ genereBrokerReportRequest: { accountId, from, to, }, });
Αποτέλεσμα:
- Ως αποτέλεσμα της πρώτης εκτέλεσης της εντολής, παίρνουμε το taskId.
- Η αναφορά αρχίζει να δημιουργείται από την πλευρά του μεσίτη. Όταν είναι έτοιμο είναι άγνωστο, περιμένουμε και τραβάμε περιοδικά το taskId εν αναμονή της αναφοράς.
- Γιατί; Γιατί αν η αναφορά δεν είναι έτοιμη, βγάζει σφάλμα. Εάν η αναφορά δεν είναι έτοιμη από την πλευρά του μεσίτη, τότε αυτό είναι ένα σφάλμα στον κώδικά σας. Επεξεργαστείτε: 30058|INVALID_ARGUMENT|η εργασία δεν έχει ολοκληρωθεί ακόμα, δοκιμάστε ξανά αργότερα
Ο κωδικός αναμονής και λήψης αναφοράς μοιάζει κάπως έτσι.
const timer = async time => { return new Promise(resolve => setTimeout(resolve, time)); } const getBrokerResponseByTaskId = async (taskId, σελίδα = 0) => { try { return await (sdk.operations.getBrokerReport)({ getBrokerReportRequest: { taskId, page, }, }); } catch (e) { console.log(‘wait’, e); χρονόμετρο αναμονής (10000); επιστροφή σε αναμονή getBrokerResponseByTaskId(taskId, σελίδα); } };
Τότε συμβαίνει η ίδια μαγεία. Σταματάμε το σενάριό μας, το ξεκινάμε ξανά, δεν έχουμε taskId. Εκτελούμε τον κώδικα με το αίτημα taskId, αλλά δεν παίρνουμε πλέον το taskId, αλλά αμέσως την αναφορά. Μαγεία! Και όλα θα ήταν καλά αν ήταν πάντα έτσι. Αλλά σε ένα μήνα δεν θα υπάρχουν καθόλου δεδομένα. Χρήσιμο :
https://github.com/pskucherov/tcsstat/tree/step3.1 https://github.com/pskucherov/tcsstat/compare/step3.1
- Αν κάποιος το συναντήσει, τότε καλώς ήρθατε στο θέμα . Αφού επιδιορθώσουν αυτή τη μαγεία, θα χάσει τη δύναμή της και θα είναι κάπως διαφορετική. Αλλά αυτή τη στιγμή (21/03/2023) λειτουργεί ακριβώς έτσι.
GetDividendsForeignIssuer
Κάποιος μπορεί να πιστεύει ότι η μέθοδος είναι παρόμοια με την προηγούμενη και μπορείτε να χρησιμοποιήσετε μία μόνο μέθοδο στην οποία αλλάζετε μόνο το όνομα των πράξεων. Αλλά δεν μάντεψαν! Η ονομασία εκεί είναι πολύ διαφορετική τόσο στις μεθόδους όσο και στις επιστρεφόμενες πληροφορίες. Και ο αριθμός των σελίδων ξεκινά από το 0, μετά από το 1. Για να μην μπερδεύεστε σε όλα αυτά, είναι πιο εύκολο να γράψετε δύο διαφορετικές μεθόδους. Που είναι περίεργο, γιατί η λογική της δουλειάς είναι η ίδια. Έφτυσα για πολλή ώρα όταν προσπάθησα να φτιάξω μία μέθοδο και υπήρχε λιγότερος κώδικας. Εδώ δεν θα υπάρχουν παραδείγματα.
GetOperationsByCursor
Το αγαπημένο μου από τα τρία. Αν και όχι το πιο ακριβές, αλλά το πιο επαρκές. Κάνουμε αίτημα από την αρχή της δημιουργίας λογαριασμού μέχρι τη μέγιστη δυνατή ημερομηνία (κλείσιμο λογαριασμού ή την τρέχουσα). Λαμβάνουμε την απάντηση, παίρνουμε τον κέρσορα και ζητάμε ξανά όσο υπάρχουν δεδομένα. Και ο κώδικας είναι πιο συνοπτικός από ό,τι στα παραπάνω παραδείγματα.
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, withoutCommissions: false: χωρίς Συναλλαγές: ψευδής, χωρίς Διανυκτερεύσεις: ψευδής, δρομέας, }; επιστροφή σε αναμονή sdk.operations.getOperationsByCursor(reqData); } catch (e) { await timer(60000); επιστροφή σε αναμονή getOperationsByCursor(sdk, accountId, from, to, cursor = ”); } };
Το προσχέδιο για εκτέλεση είναι εδώ: https://github.com/pskucherov/tcsstat/tree/step3.3 https://github.com/pskucherov/tcsstat/compare/step3.3 Τώρα είμαστε έτοιμοι να προσθέσουμε λειτουργίες λήψης στο την αίτησή μας. Εάν γίνει σωστά, τότε θα πρέπει να λάβετε αναφορές μεσιτείας για ολόκληρη την ύπαρξη του λογαριασμού. Και για τα δεδομένα που λείπουν, τα ίδια T-3, επαναφόρτωση από τις επιχειρήσεις. Αλλά αυτό μπορεί να χωριστεί σε ξεχωριστό άρθρο. Από τις κύριες αποχρώσεις που θα συναντήσετε είναι να κολλήσετε λειτουργίες και μια έκθεση μεσιτείας.
- Εάν σήμερα λάβατε μια αναφορά μεσιτείας και συναλλαγές για τις απαιτούμενες ημερομηνίες, βάλτε τα όλα στη βάση δεδομένων, τότε δεν υπάρχουν προβλήματα.
- Θα αντιμετωπίσετε προβλήματα αύριο, όταν λάβετε το επόμενο τμήμα δεδομένων από την αναφορά και τις λειτουργίες και αποφασίσετε να τα συγχρονίσετε με την υπάρχουσα βάση δεδομένων.
- Πολλές αποχρώσεις σχετικά με την αναντιστοιχία ή την αλλαγή του αναγνωριστικού μετά την επεξεργασία
- Στη συνέχεια, για την αγορά OTC, τα αναγνωριστικά δεν ταιριάζουν καθόλου.
- Καθώς και οι αποχρώσεις των οργάνων συγχρονισμού, που και πάλι δεν συμπίπτουν, λόγω των ιδιαιτεροτήτων του API. Αλλά αυτό είναι μια άλλη ιστορία.
Ας προσθέσουμε στην εφαρμογή μας πληροφορίες σχετικά με τις λειτουργίες. Το κύριο ερώτημα θα είναι πού θα γίνει η επεξεργασία και η αποθήκευση των δεδομένων.
- Εάν το κάνετε για τον εαυτό σας, θα καταναλώσετε τα ίδια δεδομένα από διαφορετικές συσκευές. Στη συνέχεια, πρέπει να επεξεργαστείτε και να αποθηκεύσετε δεδομένα στον διακομιστή.
- Εάν έχετε πολλά διαφορετικά δεδομένα που καταναλώνονται από πολλούς διαφορετικούς χρήστες, τότε πρέπει να αποφασίσετε τι είναι πιο σημαντικό: η ταχύτητα των χρηστών ή η εξοικονόμηση σιδήρου από την πλευρά σας. Όποιος μπορεί να αντέξει οικονομικά άπειρο υλικό μετράει τα πάντα στον διακομιστή του και τον κάνει εξαιρετικά γρήγορο για τους χρήστες, εξοικονομώντας τους πόρους του χρήστη, όπως μπαταρία και κίνηση, κάτι που είναι πολύ σημαντικό στα τηλέφωνα.
Με τη σειρά του, η καταμέτρηση στο πρόγραμμα περιήγησης δεν είναι καταρχήν η βέλτιστη λύση. Επομένως, ό,τι δεν είναι ακριβό, το θεωρούμε στον διακομιστή μας. Τα υπόλοιπα τα αφήνουμε στον πελάτη. Θέλω πολύ να πάρω και να υπολογίσω την προμήθεια στον διακομιστή. Αλλά εδώ έρχεται η απόχρωση που ονομάζεται «διαδραστικότητα». Ας υποθέσουμε ότι έχετε χιλιάδες επεμβάσεις και χρειάζονται πέντε λεπτά για να τις λάβετε. Τι θα έχει ο χρήστης αυτή τη στιγμή; Κλώστης? Πρόοδος? Πληροφορίες για το πόσα ανέβηκαν; Είναι ιδανικό να χρησιμοποιείτε την “ενεργή αναμονή” όταν ο χρήστης στη διαδικασία μπορούσε ήδη να δει κάτι. Ιδού το Αποτέλεσμα:
- Φόρτωση σελίδας
- Ζητούνται όλα τα τιμολόγια
- Μετά από αυτό, όλες οι συναλλαγές με προμήθειες για εκτελούμενες συναλλαγές ζητούνται για όλους τους λογαριασμούς. Καθώς λαμβάνονται δεδομένα, αποδίδονται στο πρόγραμμα περιήγησης.
Για να μην φιλτράρουμε τα δεδομένα στα συμβάντα κάθε φορά, τραβάμε το δικό μας συμβάν για κάθε λογαριασμό. Σαν αυτό:
socket.emit(‘sdk:getOperationsCommissionResult_’ + accountId, { items: data?.items, inProgress: Boolean(nextCursor), });
Το προσχέδιο για κυκλοφορία είναι εδώ: https://github.com/pskucherov/tcsstat/tree/step3 https://github.com/pskucherov/tcsstat/compare/step2…step3 Προχωράμε. Είναι υπέροχο που διάβασες αυτή τη γραμμή!
Υπολογισμός και έξοδος πληροφοριών ενδιαφέροντος
Εξαρτάται από το ποιος χρειάζεται τι πληροφορίες. Ως εκ τούτου, σας λέω αμέσως τις κύριες αποχρώσεις που θα συναντήσετε.
Δουλεύοντας με τιμές
Όλοι όσοι ασχολούνται με τα οικονομικά γνωρίζουν ότι οι χρηματικές συναλλαγές πρέπει να εκτελούνται μόνο με ακέραιους αριθμούς. Λόγω της ανακρίβειας των τιμών μετά την υποδιαστολή και του αθροιστικού σφάλματος με μεγάλο αριθμό πράξεων. Γι’ αυτό όλες οι τιμές παρουσιάζονται στην παρακάτω μορφή MoneyValue
πεδίο | τύπος | Περιγραφή |
---|---|---|
νόμισμα | σειρά | Συμβολοσειρά κωδικού νομίσματος ISO |
μονάδες | int64 | Ακέραιο μέρος του αθροίσματος, μπορεί να είναι αρνητικός αριθμός |
νανο | int32 | Κλασματικό μέρος του ποσού, μπορεί να είναι αρνητικός αριθμός |
Τα επεξεργαζόμαστε ξεχωριστά και μετά τα φέρνουμε στην τιμή τιμής:
quotation.units + quotation.nano / 1e9
Το κόστος των συμβολαίων μελλοντικής εκπλήρωσης
Η τιμή των συμβολαίων μελλοντικής εκπλήρωσης παρουσιάζεται σε πόντους, όταν έχετε μελλοντικό νόμισμα, πρέπει να γνωρίζετε την ισοτιμία. Και φυσικά η τιμή σε πόντους και το βήμα τιμής. Όταν υπολογίζετε το κέρδος από τις συναλλαγές, αυτό μπορεί να πυροβολήσει, γιατί. αν υπολογίσετε το συνολικό ποσό πολλαπλασιάζοντας την τιμή με την ποσότητα. Εδώ πρέπει να είστε προσεκτικοί. Προς το παρόν, θα δούμε πώς θα πάει. Αυτό ισχύει για συμβόλαια μελλοντικής εκπλήρωσης νομισμάτων, σε άλλα μέρη όλα είναι εντάξει με αυτό.
OTC αγορά
Αυτή η αγορά έχει πολλές ιδιαιτερότητες, οπότε ας μελετήσουμε τις λειτουργίες σε αυτήν ξεχωριστά. Όταν ξεκινήσετε να συγχρονίζετε λειτουργίες, θα αποδειχθεί ότι πρέπει να φέρετε το figi / ticker στην ίδια μορφή για να ταιριάζει σωστά το όργανο. Όταν ξεκινήσετε να το συγχρονίζετε με την αναφορά μεσιτείας, θα αποδειχθεί ότι το tradeID της ίδιας συναλλαγής έχει γράμματα στην αρχή στις συναλλαγές και δεν υπάρχουν στην αναφορά μεσιτείας. Επομένως, δεν μπορούν να συγκριθούν … αχέμ-αχαμ … με σύγκριση! Ταίριαξα τον χρόνο συναλλαγής, το ticker και την αντιστοίχιση ότι ένα tradeId περιέχεται σε ένα άλλο. Σωστά, δεν ξέρω. Όποιος το συναντήσει και που νοιάζεται για αυτό, ελάτε στο θέμα ή ξεκινήστε ένα νέο.
Μαθηματικές πράξεις σε εργαλεία
Είναι αδύνατο, χωρίς να κοιτάξουμε, να εκτελέσουμε μαθηματικές πράξεις με ολόκληρη τη λίστα. Για να μην προσθέσουμε θερμό σε μαλακό, ελέγχουμε πάντα το νόμισμα και επεξεργαζόμαστε μόνο εάν είμαστε σίγουροι ότι το νόμισμα ταιριάζει και οι πόντοι μετατρέπονται στο επιθυμητό νόμισμα. Έχοντας γνώσεις σχετικά με την εργασία με αριθμούς τραπεζών, θα υπολογίσουμε την προμήθεια που δαπανάται για κάθε έναν από τους λογαριασμούς. Όπως αυτό: https://github.com/pskucherov/tcsstat/tree/step4 https://github.com/pskucherov/tcsstat/compare/step3…step4
Το Microservice είναι έτοιμο!
https://github.com/pskucherov/tcsstat Ως εργασία, μπορείτε να ελέγξετε εάν η υπηρεσία λειτουργεί με αργή σύνδεση, πότε διακόπτονται οι συνδέσεις, όταν αποσυνδέεται το Διαδίκτυο, όταν υπάρχουν σφάλματα ή ληγμένα όρια από την πλευρά του μεσίτη.
Συμπεράσματα και σχέδια για το μέλλον
- Έμαθε για τις βασικές λειτουργίες και τη συνεργασία με το Invest API
- Χρόνος που αφιερώθηκε ~ 10 ώρες
- Επίπεδο δυσκολίας ~ junior+ / χαμηλό μέσο
Εάν συνεχίσετε να βελτιώνετε το microservice, μπορεί να καταλήξετε με κάτι τέτοιο
https://opexbot.info
Αυτή είναι η εξέλιξή μου, για όσους είναι πολύ τεμπέληδες να καταλάβουν, να τρέξουν και να υπολογίζουν μόνοι τους. Σκοπεύω να προσθέσω αναλυτικά στοιχεία εκεί κατόπιν αιτήματος των χρηστών. Αν σας άρεσε το άρθρο, εγγραφείτε στο κανάλι μου στο τηλεγράφημα .
Полезная статья. Не могу представить, сколько усилий автора потребовалось, чтобы все описать. Благодарю.