Возвращение блудного программиста (ч. 4). python.. python. вайбкодинг.. python. вайбкодинг. войтивайти.. python. вайбкодинг. войтивайти. ИИ.. python. вайбкодинг. войтивайти. ИИ. обучение программированию.. python. вайбкодинг. войтивайти. ИИ. обучение программированию. Программирование.

Эта часть про то, как я пилю бэкенд, учусь на этом и получаю эмоциональные качели.

Содержание

Для начала, напомню о себе: «у меня свой бизнес, а в IT, я так, для души». Шутка. В общем, после 12 лет отсутствия в сфере я решил вернуться к своему базовому образованию – инженер-программист. Что-то приходится «вспоминать с нуля», но я не люблю начинать изучение полностью с теории, я больше экспериментатор. Во-первых, так можно учиться до бесконечности, оттачивая свои знания, но никогда их не применить. Во-вторых, банально, но ты не узнаешь, что такое горячо пока не потрогаешь. Есть еще, в-третьих, теперь есть ИИ – интерактивная энциклопедия и помощник в одном лице (хотя тут столько своих НО).

Как я рассказывал раньше, одним из учебных проектов я решил сделать симулятор торговли на Московской бирже. Выбрал стек, даже сделал кое-какие приготовления на сервере.

Важное отступление: у меня самый дешевый виртуальный хостинг, не VPS. Почему? Потому что «уже был», под мелкие эксперименты

Так вот. Спустя почти 2 недели, попутно изучая что и зачем, я закончил бэкенд. Насколько я знаю, здесь не очень принято обсуждать опыт использования ИИ, но я вас спрашиваю, как это работает: «я никогда не сталкивался с IT, по образованию я преподаватель литературы, но решил всё изменить, и за один вечер с ИИ написал супер-пупер мега сервис». Как, Карл?

Вы видели, что он выдаёт? О, это вполне работоспособный код, но это «бомба замедленного действия». В первую очередь, меня напрягла куча фреймворков, кажется, буквально на всё. Ладно, может это сейчас так и делается. Потом, выдуманные параметры (например, для ендпоитнов ISS MOEX), или наоборот, заверения, что ничего не надо. Самое прикольное – это когда ИИ выдает полный листинг файла, а потом текстом примечание, типа «я тут использовал устаревший/неэффективный/еще какой-то подход, если хотите, чтоб всё работало как часы – надо сделать то-то и то-то». Блин, а когда ловишь его на ошибке/неточности «да, вы правы. Сейчас исправлю. И, кажется, всё равно кривой код попадает к контекст».

Отдельные перлы

Ты прав по обоим пунктам.

По первому — current_price * quantity абсолютно верно. Рыночная цена облигации уже отражает амортизацию — ISS её считает с учётом остаточного номинала. Нам не нужно ничего пересчитывать.

По второму — да, полный вариант бессмысленен. Мы всё равно берём цену из ISS, так что хранить номинал отдельно и пересчитывать — лишняя работа которая ни на что не влияет.

И это после того, как он мне это вставил в код, я столкнулся с “кривыми” данными, нашел почему и… да, я прав.

ISS хранит только 2 дня внутридневных данных — значит таймфреймы 1м и 5м будут доступны только за текущий и предыдущий торговый день.

Это совершенно не так, ISS отдаёт свечи, чуть ли не c 2008-10 годов по 7 разным интервалам, но там нет 5 минут. Я долго пытался понять почему я получаю пустой массив.

Именно, ты правильно мыслишь. Раз данные в MySQL обновляются раз в минуту — WebSocket вообще не даёт никакого преимущества. Смысла в нём ноль.

А тут он мне на shared-хостинге (да он знает мои ограничения) советует мне заморочиться с WebSocket (это вообще реально?) для данных, которые на сервере обновляются раз в минуту, а на клиенте, если чаще минуты вообще берутся из кэша (на фронте TanStack Query, при чем сам ИИ предложил).

Фуух, всё. В защиту скажу, что для изучения, в чём-то это даже хорошо. Как это работает для меня на конкретном примере. Я понимаю, что нужно сделать. Надо раз в сутки делать запрос к ISS, получать список бумаг и заливать их в MySQL на сервере в несколько таблиц. Затем, ежеминутно запрашивать с ISS котировки и тоже заливать в MySQL. Так сделано, чтобы не долбить ISS запросами от каждого клиента. Я лучше один раз заберу к себе, а потом раздам клиентам, в том числе из кэша. Но я не знаю всех прелестей python (мой стек был Delphi+ADO+MS SQL. Да, я баловался и PHP, и Python, даже Perl чуток, но это не в счёт). Я прошу ИИ написать код и изучаю его. Всё здорово, всё понятно и работает.

Я рад, но не долго. Начинаю делать ручные тесты (пока не в теме QA Automation). Вот тут и начинается обучение: я вижу, что скрипт работает не так как запланировано, разбираюсь с этим (были неправильные ендпоитны, не учитывал пагинацию, уверяя что её нет, брал вообще не те поля, брал названия полей не из модели данных и так далее). Начинаю фиксить сам, потом делаем это вместе с ИИ. Снова тесты. Снова сам, снова ИИ. И на одной из итераций победа! Локально. Всё работает, всё правильно берёт, правильно раскладывает. Деплой.

С первого взгляда всё отлично, но помните отступление про хостинг? А теперь эти скрипты «отжирают» 13% CPU, вместо разрешённых CPU. Разбираемся. Тут, конечно, я развёл руками. Отдался ИИ. Оказывается, мало того, что они долбят ISS каждый раз SSL церемониями на каждую бумагу, так еще и MySQL по несколько раз дергают, т.е. для 100 бумаг – это 100 соединений, и 200-300 обращений к базе через дополнительную прослойку (SQLAlchemy). Это только 1 скрипт, во втором всё еще хуже: на каждую бумагу несколько запросов к ISS (из-за пагинации ответов), и кратно записей в MySQL. Не удивительно, что каждый из них в момент работы занимал до 50-60% процессорного времени.

Починили. В начале скрипта открываем сессию к ISS и запросы шлём в рамках этой сессии, собираем по результатам массив и на native SQL делаем bulk insert. Для меня пока что всё логично, и время CPU сократилось до 2-4%, т.е. имеем ожидаемый результат.

Для тех, кто любит пинать ногами

Первоначальный код
def fetch_coupons(ticker):
    while True:
        url = f'{ISS_BASE}/statistics/engines/stock/markets/bonds/bondization/{ticker}.json'
        try:
            r = requests.get(url, params=params, timeout=15)
            r.raise_for_status()
            data = r.json()
            # some more code

        except Exception as e:
            print(f' Ошибка при загрузке купонов {ticker} (start={start_coupons}): {e}')
            break
    
    while True:
        url = f'{ISS_BASE}/statistics/engines/stock/markets/bonds/bondization/{ticker}.json'
        try:
            r = requests.get(url, params=params, timeout=15)
            r.raise_for_status()
            data = r.json()
            # some more code

        except Exception as e:
            print(f' Ошибка при загрузке амортизаций {ticker} (start={start_amort}): {e}')
            break
    
    return sorted_coupons, sorted_amorts

with app.app_context():
    bonds = Security.query.filter_by(is_active=True, type='bond').all()
    for i, sec in enumerate(bonds, 1):
        rows, amort = fetch_coupons(sec.ticker)
        
        for c in rows:
            try:
                db.session.execute(“INSERT …. ON DUPLICATE KEY UPDATE”)
            except Exception as e:
                print(f'  Ошибка записи купона {sec.ticker}: {e}')
                continue

        for a in amort:
            try:
                db.session.execute(“INSERT …. ON DUPLICATE KEY UPDATE”)
            except Exception as e:
                print(f'  Ошибка записи амортизаций {sec.ticker}: {e}')
                continue                                   

    try:
        db.session.commit()
    except Exception as e:
        db.session.rollback()
        print(f'  Ошибка commit {sec.ticker}: {e}')
К чему пришли в итоге
def fetch_coupons(session, ticker):
    while True:
        url = f'{ISS_BASE}/statistics/engines/stock/markets/bonds/bondization/{ticker}.json'    
        try:
            r = session.get(url, params=params, timeout=15)
            r.raise_for_status()
            data = r.json()
            # some more code

        except Exception as e:
            print(f' Ошибка при загрузке купонов {ticker} (start={start_coupons}): {e}')
            break    

    while True:
        url = f'{ISS_BASE}/statistics/engines/stock/markets/bonds/bondization/{ticker}.json'
        try:
            r = session.get(url, params=params, timeout=15)
            r.raise_for_status()
            data = r.json()
            # some more code

        except Exception as e:
            print(f' Ошибка при загрузке амортизаций {ticker} (start={start_amort}): {e}')
            break 

    return sorted_coupons, sorted_amorts       

with app.app_context():
    with requests.Session() as session:
        bonds = Security.query.filter_by(is_active=True, type='bond').all()              
        for i, sec in enumerate(bonds, 1):
            rows, amort = fetch_coupons(session, sec.ticker)        
            for a in amort:
                bulk_amortizations.append({})
            for c in rows:
                bulk_coupons.append({})   

        if bulk_amortizations:
            try:
                db.session.execute(
                    db.text(),
                    bulk_amortizations
                )
                db.session.commit()
            except Exception as e:
                db.session.rollback()
                print(f'  Ошибка массовой записи амортизаций: {e}')  
                  
        if bulk_coupons:
            try:
                db.session.execute(
                    db.text(),
                    bulk_coupons
                )
                db.session.commit()
            except Exception as e:
                db.session.rollback()
                print(f'  Ошибка массовой записи амортизаций: {e}')                     

        db.session.remove()

Чему я научился за это время короткое:

  • доверяй ИИ, но проверяй. Тут как раз становятся актуальны статьи (здесь же, на Habr), почему нельзя вайбкодить если ты не разбираешься в программировании;

  • не все фреймворки одинаково полезны, вернее не всегда. Тот же SQLAlchemy в других местах мне пока очень нравится, там не должно быть таких затыков;

  • изучил ряд новых для себя конструкций, как например, декораторы функций в python;

  • ознакомился сразу с несколькими фреймворками. Не говорю, что изучил, ведь только опробовал часть функционала применительно к данной задаче;

  • ну и понял множество нюансов, вроде handshake в начале каждой сессии.

Смогу ли я повторить бэкенд без ИИ теперь. Вполне, но в любом случае мне потребуется документация на большинство инструментов, которые я применил. Здесь я рассказал только про два cron скрипта, которые мне показались наиболее интересными именно с точки зрения моего подхода к обучению. Кроме описанного у меня используется кэширование (Flask-Caching), аутентификация JWT (Flask-JWT-Extended), естественно сам Flask, ORM (SQLAlchemy), alembic для миграций.

В качестве вывода скажу, на данный момент мой подход имеет место быть, и может быть даже интересен. Особенно с точки зрения эмоционального подкрепления, т.е., учитывая минимальный уровень в python, я получаю достаточно быстрый результат и учусь на нём, что не может не радовать. Конечно, есть обратная сторона, это такой же «дешевый дофамин» как и в случае с социальными сетями. В итоге может пострадать глубина знаний, а глобально – и сама мотивация, это надо учитывать.

Автор: VAnderskaeV

Источник