ืžืฉื™ืžื•ืช ืจืงืข ืขืœ ืคืื•ืกื˜, ื—ืœืง ื': ืžื‘ื•ื

ืžืฉื™ืžื•ืช ืจืงืข ืขืœ ืคืื•ืกื˜, ื—ืœืง ื': ืžื‘ื•ื

ืื™ืš ื”ื’ืขืชื™ ืœื—ื™ื•ืช ื›ื›ื”?

ืœื ืžื–ืžืŸ ื ืืœืฆืชื™ ืœืขื‘ื•ื“ ืขืœ ื”ืงืฆื” ื”ืื—ื•ืจื™ ืฉืœ ืคืจื•ื™ืงื˜ ืขืžื•ืก ืžืื•ื“, ื‘ื• ื”ื™ื” ืฆื•ืจืš ืœืืจื’ืŸ ื‘ื™ืฆื•ืข ืงื‘ื•ืข ืฉืœ ืžืกืคืจ ืจื‘ ืฉืœ ืžืฉื™ืžื•ืช ืจืงืข ืขื ื—ื™ืฉื•ื‘ื™ื ืžื•ืจื›ื‘ื™ื ื•ื‘ืงืฉื•ืช ืœืฉื™ืจื•ืชื™ ืฆื“ ืฉืœื™ืฉื™. ื”ืคืจื•ื™ื™ืงื˜ ื”ื•ื ืืกื™ื ื›ืจื•ื ื™ ื•ืœืคื ื™ ืฉื”ื’ืขืชื™, ื”ื™ื” ืœื• ืžื ื’ื ื•ืŸ ืคืฉื•ื˜ ืœืžืฉื™ืžื•ืช ื”ืคืขืœืช cron: ืœื•ืœืื” ื”ื‘ื•ื“ืงืช ืืช ื”ืฉืขื” ื”ื ื•ื›ื—ื™ืช ื•ื”ืฉืงืช ืงื‘ื•ืฆื•ืช ืฉืœ coroutines ื“ืจืš gather - ื”ื’ื™ืฉื” ื”ื–ื• ื”ืชื‘ืจืจื” ื›ืžืงื•ื‘ืœืช ืขื“ ืฉื”ื™ื• ืขืฉืจื•ืช ื•ืžืื•ืช ืฉืœ coroutines ื›ืืœื”. ืื•ืœื ื›ืืฉืจ ืžืกืคืจื ืขืœื” ืขืœ ืืœืคื™ื™ื, ื ืืœืฆืชื™ ืœื—ืฉื•ื‘ ืขืœ ืืจื’ื•ืŸ ืชื•ืจ ืžืฉื™ืžื•ืช ืจื’ื™ืœ ืขื ืžืชื•ื•ืš, ืžืกืคืจ ืขื•ื‘ื“ื™ื ื•ื›ื•'.

ืจืืฉื™ืช ื”ื—ืœื˜ืชื™ ืœื ืกื•ืช ืกืœืจื™, ืฉื”ืฉืชืžืฉืชื™ ื‘ื• ื‘ืขื‘ืจ. ื‘ื’ืœืœ ื”ืื•ืคื™ ื”ื-ืกื™ื ื›ืจื•ื ื™ ืฉืœ ื”ืคืจื•ื™ืงื˜, ืฆืœืœืชื™ ืœืชื•ืš ื”ืฉืืœื” ื•ืจืื™ืชื™ ืžืืžืจื›ืžื• ื’ื ืคืจื•ื™ืงื˜, ื ื•ืฆืจ ืขืœ ื™ื“ื™ ื›ื•ืชื‘ ื”ืžืืžืจ.

ืื ื™ ืื’ื™ื“ ืืช ื–ื”, ื”ืคืจื•ื™ืงื˜ ืžืื•ื“ ืžืขื ื™ื™ืŸ ื•ืขื•ื‘ื“ ื‘ืฆื•ืจื” ื“ื™ ืžื•ืฆืœื—ืช ื‘ื™ื™ืฉื•ืžื™ื ืื—ืจื™ื ืฉืœ ื”ืฆื•ื•ืช ืฉืœื ื•, ื•ื”ืžื—ื‘ืจ ืขืฆืžื• ืื•ืžืจ ืฉื”ื•ื ื”ืฆืœื™ื— ืœื’ืœื’ืœ ืื•ืชื• ืœื™ื™ืฆื•ืจ ื‘ืืžืฆืขื•ืช ืžืื’ืจ ืืกื™ื ื›ืจื•ื ื™. ืื‘ืœ, ืœืฆืขืจื™, ื–ื” ืœื ืžืžืฉ ื”ืชืื™ื ืœื™, ื›ืคื™ ืฉื”ืชื‘ืจืจ ื‘ืขื™ื” ืขื ื”ืฉืงื” ืงื‘ื•ืฆืชื™ืช ืฉืœ ืžืฉื™ืžื•ืช (ืจืื”. ืงื‘ื•ืฆื”). ื‘ื–ืžืŸ ื”ื›ืชื™ื‘ื” ืกื•ื’ื™ื” ื”ื•ื ื›ื‘ืจ ืกื’ื•ืจ, ืขื ื–ืืช, ื”ืขื‘ื•ื“ื” ื ืžืฉื›ืช ื›ื‘ืจ ื—ื•ื“ืฉ. ื‘ื›ืœ ืžืงืจื”, ื‘ื”ืฆืœื—ื” ืœืžื—ื‘ืจ ื•ื›ืœ ื˜ื•ื‘, ื›ื™ ื™ืฉ ื›ื‘ืจ ื“ื‘ืจื™ื ืขื•ื‘ื“ื™ื ืขืœ lib... ื‘ื›ืœืœ, ื”ืขื™ืงืจ ื‘ื™ ื•ื”ื›ืœื™ ื”ืชื‘ืจืจ ืœื™ ืœื—. ื‘ื ื•ืกืฃ, ื‘ื—ืœืง ืžื”ืžืฉื™ืžื•ืช ื”ื™ื• 2-3 ื‘ืงืฉื•ืช http ืœืฉื™ืจื•ืชื™ื ืฉื•ื ื™ื, ื›ืš ืฉื’ื ื‘ืขืช ืื•ืคื˜ื™ืžื™ื–ืฆื™ื” ืฉืœ ืžืฉื™ืžื•ืช, ืื ื• ื™ื•ืฆืจื™ื 4 ื—ื™ื‘ื•ืจื™ TCP, ื‘ืขืจืš ื›ืœ ืฉืขืชื™ื™ื - ืœื ืžืžืฉ ื˜ื•ื‘... ืื ื™ ืจื•ืฆื” ืœื™ืฆื•ืจ ืกืฉืŸ ืœืกื•ื’ ืื—ื“ ืฉืœ ืžืฉื™ืžื” ื‘ืขืช ื”ืคืขืœืช ืขื•ื‘ื“ื™ื. ืงืฆืช ื™ื•ืชืจ ืขืœ ื”ืžืกืคืจ ื”ื’ื“ื•ืœ ืฉืœ ื‘ืงืฉื•ืช ื“ืจืš aiohttp ื›ืืŸ.

ื‘ื”ืงืฉืจ ื”ื–ื” ื”ืชื—ืœืชื™ ืœื—ืคืฉ ะฐะปัŒั‚ะตั€ะฝะฐั‚ะธะฒั‹ ื•ืžืฆื ืืช ื–ื”! ื™ื•ืฆืจื™ ื”ืกืœืจื™, ืกืคืฆื™ืคื™ืช, ืœืคื™ ื”ื‘ื ืชื™ ืชืฉืืœ ืืช ืกื•ืœื, ื ื•ืฆืจ ืคืื•ืกื˜, ื‘ืžืงื•ืจ ืขื‘ื•ืจ ื”ืคืจื•ื™ืงื˜ robinhood. ืคืื•ืกื˜ ืฉื•ืื‘ ื”ืฉืจืื” ืžืงืคืงื ืกื˜ืจื™ื ื•ืขื•ื‘ื“ ืขื ืงืคืงื ื›ืžืชื•ื•ืš, rocksdb ืžืฉืžืฉ ื’ื ืœืื—ืกื•ืŸ ืชื•ืฆืื•ืช ืžืขื‘ื•ื“ืช ืกื•ื›ื ื™ื, ื•ื”ื“ื‘ืจ ื”ื—ืฉื•ื‘ ื‘ื™ื•ืชืจ ื”ื•ื ืฉื”ืกืคืจื™ื™ื” ืชื”ื™ื” ืืกื™ื ื›ืจื•ื ื™ืช.

ื›ืžื• ื›ืŸ, ืืชื” ื™ื›ื•ืœ ืœื”ืกืชื›ืœ ื”ืฉื•ื•ืื” ืžื”ื™ืจื” ืกืœืจื™ ื•ืคืื•ืกื˜ ืžื”ื™ื•ืฆืจื™ื ืฉืœ ื”ืื—ืจื•ื ื™ื: ื”ื”ื‘ื“ืœื™ื ื‘ื™ื ื™ื”ื, ื”ื‘ื“ืœื™ื ื‘ื™ืŸ ื‘ืจื•ืงืจื™ื, ื™ื™ืฉื•ื ืžืฉื™ืžื” ื™ืกื•ื“ื™ืช. ื”ื›ืœ ื“ื™ ืคืฉื•ื˜, ืขื ื–ืืช, ืชื›ื•ื ื” ื ื—ืžื“ื” ื‘-faust ืžื•ืฉื›ืช ืชืฉื•ืžืช ืœื‘ - ื ืชื•ื ื™ื ืžื•ืงืœื“ื™ื ืœืฉื™ื“ื•ืจ ืœื ื•ืฉื.

ืžื” ืื ื—ื ื• ืขื•ืฉื™ื?

ืœื›ืŸ, ื‘ืกื“ืจืช ืžืืžืจื™ื ืงืฆืจื”, ืื ื™ ืืจืื” ืœื›ื ื›ื™ืฆื“ ืœืืกื•ืฃ ื ืชื•ื ื™ื ืžืžืฉื™ืžื•ืช ืจืงืข ื‘ืืžืฆืขื•ืช ืคืื•ืกื˜. ื”ืžืงื•ืจ ืœืคืจื•ื™ืงื˜ ืœื“ื•ื’ืžื” ืฉืœื ื• ื™ื”ื™ื”, ื›ืคื™ ืฉื”ืฉื ืžืจืžื–, alphavantage.co. ืื ื™ ืื“ื’ื™ื ืื™ืš ื›ื•ืชื‘ื™ื ืกื•ื›ื ื™ื (ืกื™ื ืง, ื ื•ืฉืื™ื, ืžื—ื™ืฆื•ืช), ืื™ืš ืขื•ืฉื™ื ื‘ื™ืฆื•ืข ืจื’ื™ืœ (ืงืจื•ืŸ), ืคืงื•ื“ื•ืช faust cli ื”ื›ื™ ื ื•ื—ื•ืช (ืขื˜ื™ืคื” ืขืœ ืงืœื™ืง), ืงืœืกื™ื ื’ ืคืฉื•ื˜, ื•ื‘ืกื•ืฃ ื ืฆืจืฃ datadog ( ืขื•ื‘ื“ ืžื—ื•ืฅ ืœืงื•ืคืกื”) ื•ื ืกื” ืœืžืฆื•ื ืžืฉื”ื• ืœืจืื•ืช. ื›ื“ื™ ืœืื—ืกืŸ ืืช ื”ื ืชื•ื ื™ื ืฉื ืืกืคื• ื ืฉืชืžืฉ ื‘-mongodb ื•ื‘-motor ืœื—ื™ื‘ื•ืจ.

ื .ื‘ ืื ืœืฉืคื•ื˜ ืœืคื™ ื”ื‘ื™ื˜ื—ื•ืŸ ืฉื‘ื• ื ื›ืชื‘ื” ื”ื ืงื•ื“ื” ืœื’ื‘ื™ ื ื™ื˜ื•ืจ, ืื ื™ ื—ื•ืฉื‘ ืฉื”ืงื•ืจื ื‘ืกื•ืฃ ื”ืžืืžืจ ื”ืื—ืจื•ืŸ ืขื“ื™ื™ืŸ ื™ื™ืจืื” ื‘ืขืจืš ื›ืš:

ืžืฉื™ืžื•ืช ืจืงืข ืขืœ ืคืื•ืกื˜, ื—ืœืง ื': ืžื‘ื•ื

ื“ืจื™ืฉื•ืช ื”ืคืจื•ื™ืงื˜

ื‘ืฉืœ ื”ืขื•ื‘ื“ื” ืฉื›ื‘ืจ ื”ื‘ื˜ื—ืชื™, ื‘ื•ืื• ื ืขืฉื” ืจืฉื™ืžื” ืงื˜ื ื” ืฉืœ ืžื” ืฉื”ืฉื™ืจื•ืช ืืžื•ืจ ืœื”ื™ื•ืช ืžืกื•ื’ืœ ืœืขืฉื•ืช:

  1. ื”ืขืœื” ื ื™ื™ืจื•ืช ืขืจืš ื•ืกืงื™ืจื” ื›ืœืœื™ืช ืฉืœื”ื (ื›ื•ืœืœ ืจื•ื•ื—ื™ื ื•ื”ืคืกื“ื™ื, ืžืื–ืŸ, ืชื–ืจื™ื ืžื–ื•ืžื ื™ื - ืœืฉื ื” ื”ืื—ืจื•ื ื”) - ื‘ืื•ืคืŸ ืงื‘ื•ืข
  2. ื”ืขืœื” ื ืชื•ื ื™ื ื”ื™ืกื˜ื•ืจื™ื™ื (ืœื›ืœ ืฉื ืช ืžืกื—ืจ, ืžืฆื ืขืจื›ื™ื ืงื™ืฆื•ื ื™ื™ื ืฉืœ ืžื—ื™ืจ ื”ืกื’ื™ืจื” ืฉืœ ื”ืžืกื—ืจ) - ื‘ืื•ืคืŸ ืงื‘ื•ืข
  3. ื”ืขืœื” ื ืชื•ื ื™ ืžืกื—ืจ ืขื“ื›ื ื™ื™ื - ื‘ืื•ืคืŸ ืงื‘ื•ืข
  4. ื”ืขืœื” ืจืฉื™ืžื” ืžื•ืชืืžืช ืื™ืฉื™ืช ืฉืœ ืื™ื ื“ื™ืงื˜ื•ืจื™ื ืขื‘ื•ืจ ื›ืœ ื ื™ื™ืจ ืขืจืš - ื‘ืื•ืคืŸ ืงื‘ื•ืข

ื›ืฆืคื•ื™, ืื ื• ื‘ื•ื—ืจื™ื ืฉื ืœืคืจื•ื™ืงื˜ ืžืืคืก: ื”ื•ืจื˜ื•ืŸ

ืื ื—ื ื• ืžื›ื™ื ื™ื ืืช ื”ืชืฉืชื™ืช

ื”ื›ื•ืชืจืช ื‘ื”ื—ืœื˜ ื—ื–ืงื”, ืขื ื–ืืช, ื›ืœ ืžื” ืฉืืชื” ืฆืจื™ืš ืœืขืฉื•ืช ื”ื•ื ืœื›ืชื•ื‘ ืชืฆื•ืจื” ืงื˜ื ื” ืขื‘ื•ืจ docker-compose ืขื kafka (ื•-zookeeper - ื‘ืžื™ื›ืœ ืื—ื“), kafdrop (ืื ืื ื—ื ื• ืจื•ืฆื™ื ืœื”ืกืชื›ืœ ืขืœ ื”ื•ื“ืขื•ืช ื‘ื ื•ืฉืื™ื), mongodb. ืื ื—ื ื• ืžืงื‘ืœื™ื [docer-compose.yml](https://github.com/Egnod/horton/blob/562fa5ec14df952cd74760acf76e141707d2ef58/docker-compose.yml) ืžื”ื˜ื•ืคืก ื”ื‘ื:

version: '3'

services:
  db:
    container_name: horton-mongodb-local
    image: mongo:4.2-bionic
    command: mongod --port 20017
    restart: always
    ports:
      - 20017:20017
    environment:
      - MONGO_INITDB_DATABASE=horton
      - MONGO_INITDB_ROOT_USERNAME=admin
      - MONGO_INITDB_ROOT_PASSWORD=admin_password

  kafka-service:
    container_name: horton-kafka-local
    image: obsidiandynamics/kafka
    restart: always
    ports:
      - "2181:2181"
      - "9092:9092"
    environment:
      KAFKA_LISTENERS: "INTERNAL://:29092,EXTERNAL://:9092"
      KAFKA_ADVERTISED_LISTENERS: "INTERNAL://kafka-service:29092,EXTERNAL://localhost:9092"
      KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: "INTERNAL:PLAINTEXT,EXTERNAL:PLAINTEXT"
      KAFKA_INTER_BROKER_LISTENER_NAME: "INTERNAL"
      KAFKA_ZOOKEEPER_SESSION_TIMEOUT: "6000"
      KAFKA_RESTART_ATTEMPTS: "10"
      KAFKA_RESTART_DELAY: "5"
      ZOOKEEPER_AUTOPURGE_PURGE_INTERVAL: "0"

  kafdrop:
    container_name: horton-kafdrop-local
    image: 'obsidiandynamics/kafdrop:latest'
    restart: always
    ports:
      - '9000:9000'
    environment:
      KAFKA_BROKERCONNECT: kafka-service:29092
    depends_on:
      - kafka-service

ืื™ืŸ ื›ืืŸ ืฉื•ื ื“ื‘ืจ ืžืกื•ื‘ืš ื‘ื›ืœืœ. ืฉื ื™ ืžืื–ื™ื ื™ื ื”ื•ื›ืจื–ื• ืœืงืคืงื: ืื—ื“ (ืคื ื™ืžื™) ืœืฉื™ืžื•ืฉ ื‘ืชื•ืš ื”ืจืฉืช ื”ืžืจื•ื›ื‘ืช, ื•ื”ืฉื ื™ (ื—ื™ืฆื•ื ื™) ืœื‘ืงืฉื•ืช ืžื‘ื—ื•ืฅ, ื•ืœื›ืŸ ื”ืขื‘ื™ืจื• ืื•ืชื• ื”ื—ื•ืฆื”. 2181 - ื ืžืœ ืฉื•ืžืจ ื’ืŸ ื”ื—ื™ื•ืช. ื”ืฉืืจ, ืœื“ืขืชื™, ื‘ืจื•ืจ.

ื”ื›ื ืช ืฉืœื“ ื”ืคืจื•ื™ืงื˜

ื‘ื’ืจืกื” ื”ื‘ืกื™ืกื™ืช, ื”ืžื‘ื ื” ืฉืœ ื”ืคืจื•ื™ืงื˜ ืฉืœื ื• ืฆืจื™ืš ืœื”ื™ืจืื•ืช ื›ืš:

horton
โ”œโ”€โ”€ docker-compose.yml
โ””โ”€โ”€ horton
    โ”œโ”€โ”€ agents.py *
    โ”œโ”€โ”€ alphavantage.py *
    โ”œโ”€โ”€ app.py *
    โ”œโ”€โ”€ config.py
    โ”œโ”€โ”€ database
    โ”‚   โ”œโ”€โ”€ connect.py
    โ”‚   โ”œโ”€โ”€ cruds
    โ”‚   โ”‚   โ”œโ”€โ”€ base.py
    โ”‚   โ”‚   โ”œโ”€โ”€ __init__.py
    โ”‚   โ”‚   โ””โ”€โ”€ security.py *
    โ”‚   โ””โ”€โ”€ __init__.py
    โ”œโ”€โ”€ __init__.py
    โ”œโ”€โ”€ records.py *
    โ””โ”€โ”€ tasks.py *

*ื›ืœ ืžื” ืฉืฆื™ื™ื ืชื™ ืื ื—ื ื• ืขื“ื™ื™ืŸ ืœื ื ื•ื’ืขื™ื ื‘ื–ื”, ืื ื—ื ื• ืจืง ื™ื•ืฆืจื™ื ืงื‘ืฆื™ื ืจื™ืงื™ื.**

ื™ืฆืจื ื• ืžื‘ื ื”. ืขื›ืฉื™ื• ื‘ื•ืื• ื ื•ืกื™ืฃ ืืช ื”ืชืœื•ืช ื”ื“ืจื•ืฉื•ืช, ื ื›ืชื•ื‘ ืืช ื”ืชืฆื•ืจื” ื•ื ืชื—ื‘ืจ ืœ-mongodb. ืœื ืืžืกื•ืจ ืืช ื”ื˜ืงืกื˜ ื”ืžืœื ืฉืœ ื”ืงื‘ืฆื™ื ื‘ืžืืžืจ, ื›ื“ื™ ืœื ืœืขื›ื‘ ื–ืืช, ืืš ืื‘ื™ื ืงื™ืฉื•ืจื™ื ืœื’ืจืกืื•ืช ื”ื“ืจื•ืฉื•ืช.

ื‘ื•ืื• ื ืชื—ื™ืœ ืขื ืชืœื•ืช ื•ืžื˜ื ืœื’ื‘ื™ ื”ืคืจื•ื™ืงื˜ - pyproject.toml

ืœืื—ืจ ืžื›ืŸ, ืื ื• ืžืชื—ื™ืœื™ื ืœื”ืชืงื™ืŸ ืชืœื•ืช ื•ืœื™ืฆื•ืจ virtualenv (ืื• ืฉืืชื” ื™ื›ื•ืœ ืœื™ืฆื•ืจ ืืช ืชื™ืงื™ื™ืช venv ื‘ืขืฆืžืš ื•ืœื”ืคืขื™ืœ ืืช ื”ืกื‘ื™ื‘ื”):

pip3 install poetry (ะตัะปะธ ะตั‰ั‘ ะฝะต ัƒัั‚ะฐะฝะพะฒะปะตะฝะพ)
poetry install

ืขื›ืฉื™ื• ื‘ื•ืื• ื ื™ืฆื•ืจ config.yml - ืื™ืฉื•ืจื™ื ื•ืื™ืคื” ืœื“ืคื•ืง. ืืชื” ื™ื›ื•ืœ ืžื™ื“ ืœืžืงื ืฉื ื ืชื•ื ื™ื ืขื‘ื•ืจ alphavantage. ื•ื‘ื›ืŸ, ื‘ื•ืื• ื ืขื‘ื•ืจ ืœ config.py - ืœื—ืœืฅ ื ืชื•ื ื™ื ืขื‘ื•ืจ ื”ืืคืœื™ืงืฆื™ื” ืžื”ืชืฆื•ืจื” ืฉืœื ื•. ื›ืŸ, ืื ื™ ืžื•ื“ื”, ื”ืฉืชืžืฉืชื™ ื‘ืœื™ื‘ ืฉืœื™ - ืกื™ื˜ืจื™.

ื›ืฉืžืชื—ื‘ืจื™ื ืœืžื•ื ื’ื• ื”ื›ืœ ื“ื™ ืคืฉื•ื˜. ื”ื›ืจื™ื– ื›ื™ืชืช ืœืงื•ื—ื•ืช ืœื”ืชื—ื‘ืจ ื• ืžืขืžื“ ื‘ืกื™ืก ืขื‘ื•ืจ cruds, ื›ื“ื™ ืœื”ืงืœ ืขืœ ื‘ื™ืฆื•ืข ืฉืื™ืœืชื•ืช ืขืœ ืื•ืกืคื™ื.

ืžื” ื™ืงืจื” ืื—ืจ ื›ืš?

ื”ืžืืžืจ ืœื ืืจื•ืš ื‘ืžื™ื•ื—ื“, ืžื›ื™ื•ื•ืŸ ืฉื›ืืŸ ืื ื™ ืžื“ื‘ืจ ืจืง ืขืœ ืžื•ื˜ื™ื‘ืฆื™ื” ื•ื”ื›ื ื”, ืื– ืืœ ืชืืฉื™ืžื• ืื•ืชื™ - ืื ื™ ืžื‘ื˜ื™ื— ืฉื‘ื—ืœืง ื”ื‘ื ื™ื”ื™ื• ืืงืฉืŸ ื•ื’ืจืคื™ืงื”.

ืื–, ื‘ื—ืœืง ื”ื‘ื ืžืžืฉ ืื ื—ื ื•:

  1. ื‘ื•ืื• ื ื›ืชื•ื‘ ืœืงื•ื— ืงื˜ืŸ ืขื‘ื•ืจ alphavantage ื‘-aiohttp ืขื ื‘ืงืฉื•ืช ืœื ืงื•ื“ื•ืช ื”ืงืฆื” ืฉืื ื—ื ื• ืฆืจื™ื›ื™ื.
  2. ื‘ื•ืื• ื ื™ืฆื•ืจ ืกื•ื›ืŸ ืฉื™ืืกื•ืฃ ืขื‘ื•ืจื ื ืชื•ื ื™ื ืขืœ ื ื™ื™ืจื•ืช ืขืจืš ื•ืžื—ื™ืจื™ื ื”ื™ืกื˜ื•ืจื™ื™ื.

ืงื•ื“ ืคืจื•ื™ืงื˜

ืงื•ื“ ืขื‘ื•ืจ ื—ืœืง ื–ื”

ืžืงื•ืจ: www.habr.com

ื”ื•ืกืคืช ืชื’ื•ื‘ื”