Skip Navigation
Telegram
💰 Сливаем весь депозит крипты
💰 Сливаем весь депозит крипты

Сегодня у нас на столе проект jhonwick547/trading_bot.
На первый взгляд — это серьёзная заявка на успех. Автоматический торговый бот для Binance, написанный на Python. А на деле — пособие по тому, как нельзя писать финансовый софт. Если вы запустите это на реальном счете, рынок вас сожрет.

В чем кошмар? 🤡

1️⃣Machine Learning Шредингера
В коде гордо импортируется sklearn и загружается модель:
self.model = joblib.load(model_path)

Но в методе generate_signals... она нигде не используется. 🤡
Сигналы генерируются через набор if-ов уровня 5-го класса: «Если RSI < 60 и MACD растет — покупай».
Зачем там Random Forest? Видимо, чтобы кулер на ноутбуке гудел убедительнее.

2️⃣ Смертельная гонка
Смотрим функцию execute_trade.
1. Бот отправляет рыночный ордер (create_market_buy_order).
2. Бот пытается поставить Stop Loss и Take Profit отдельными запросами.

Вопрос на миллион: что будет, если между пунктом 1 и 2 у вас отвалится интернет, упадет скрипт или Binance вернет ошибку API?
Ответ: Вы останетесь с открытой позицией без стоп-лосса. Одна свеча не в ту сторону — и привет, ликвидация.
В нормальных системах используют OCO-ордера (One Cancels the Other) или отправляют сетку ордеров батчем, чтобы вход и стоп были атомарны (ну или хотя бы максимально близки).

3️⃣ Математика, которая не работает
Функция расчета позиции calculate_position_size:
risk_amount = balance * self.balance_pct  # 10% от депо
position_size = risk_amount / (entry_price - stop_loss)

Автор путает риск на сделку и объем входа.
Если entry_price близка к stop_loss, знаменатель стремится к нулю, а размер позиции улетает в космос. Там стоит min(), но сама логика расчета объема от фиксированного риска реализована криво. В итоге вы либо рискуете копейками, либо всем депо, в зависимости от волатильности.

4️⃣ Двойная работа
В цикле start_trading бот скачивает свечи (fetch_data) и считает индикаторы, чтобы проверить сигнал.
Если сигнал есть, он вызывает execute_trade, где... снова скачивает свечи и снова считает индикаторы.
Видимо, чтобы Binance быстрее забанил по IP за лишние запросы.

5️⃣ Хардкод ключей
В if __name__ == "__main__": ключи предлагается вписать прямо в код.
api_key = 'YOUR_API_KEY'
Никогда. Слышите? Никогда не храните секреты в коде. Используйте .env.

👨🏻‍⚖️ Вердикт:
Внешне похоже на торгового бота, но по сути — генератор случайных убытков.

#жарим_код
Telegram
Зомби против здравого смысла в архитектуре 🧟‍♂️🌻


Нашел на просторах GitHub проект PythonPlantsVsZombies. Это клон легендарной «Растения против Зомби» на Pygame: с анимациями, разными типами зомби и уровнями через JSON. Но как только заглядываешь «под капот» — начинаешь сочувствовать зомби. Им хотя бы не нужно поддерживать этот код.

Давайте препарируем этот инженерный шедевр.

1️⃣ Ад из if-elif или «Фабрика на костылях»
В файле source/state/level.py живет метод addPlant. Когда вы сажаете растение на клетку, движок запускает допрос из 19 веток elif.

«Ты подсолнух? Нет? А может, горохострел? Тоже нет? Тогда, может, вишня?»


if self.plant_name == c.SUNFLOWER:
new_plant = plant.SunFlower(x, y, self.sun_group)
elif self.plant_name == c.PEASHOOTER:
new_plant = plant.PeaShooter(x, y, self.bullet_groups[map_y])
# ... и так еще 17 раз


Хочешь добавить новый вид подсолнуха? Иди в середину файла и дописывай очередное:
elif self.plant_name == c.SUNSHROOM:
new_plant = plant.SunShroom(x, y, self.sun_group)


Это классический антипаттерн. В нормальном мире мы используем реестр классов или маппинг. Один словарь — и этот позор превращается в две элегантные строки.

2️⃣ Синхронизация списков — путь к шизофрении
В source/component/menubar.py данные о растениях (имена, стоимость, кулдаун) разбросаны по четырем независимым спискам.
Все они должны быть строго одной длины и в строгом порядке.
Ошибся на один индекс в plant_sun_list? Поздравляю, теперь твой горохострел стоит как вишня, а вишня бесплатная.

У нас есть dataclasses, есть словари, есть ООП, в конце концов. Группируйте связанные данные в объекты, иначе дебаг превратится в ад.

3️⃣ Глобальные побочные эффекты.
В source/tool.py инициализация Pygame и создание окна (SCREEN) происходит прямо на уровне модуля.
В чем проблема: Вы не можете просто импортировать константу или вспомогательную функцию из этого файла в тесты, не инициализировав всё графическое ядро. Это убивает возможность модульного тестирования. Логика должна быть отделена от «железа».

4️⃣ Умный «мозг» при глупых объектах
Вместо того чтобы использовать полиморфизм (где каждое растение само знает, как ему атаковать), основной класс Level вручную проверяет имена строк: if plant.name == c.THREEPEASHOOTER, и сам решает, куда стрелять. Это делает классы растений просто декорациями с картинками, а логику игры — неподъемным монолитом.

Вердикт:
Проект крутой как демка и способ потыкать Pygame. Но если вы придете с таким архитектурным подходом на нормальный проект — вас съедят быстрее, чем зомби съедают орех на первой линии.

🎓 Чему учимся:
1. Не делайте гигантские if-else цепочки там, где работает полиморфизм.
2. Группируйте связанные данные в объекты или словари.
3. Если у вас в коде есть фраза «индекс в этом списке соответствует индексу в том списке» — удаляйте всё и переписывайте.
4. Ресурсы (графика/звук) должны грузиться лениво (Lazy Loading), а не «всё и сразу» при импорте модуля.

#жарим_код
Telegram
Тетрис на Python: классика жанра или архитектурный хоррор?


Тетрис (ну и Змейка) — это «Hello World» в мире геймдева. Казалось бы, испортить его сложно, но автор этого репозитория очень старался. Разбираем проект, который преподносится как «обучающий материал для начинающих». Да, код рабочий, проект законченный, даже видео на YouTube есть. Но на деле там учат вредным привычкам.

1️⃣ Отсутствие точки входа
В main.py код просто навален в корень файла. Никакого if __name__ == "__main__":. Если вы попробуете импортировать что-то из этого файла (хотя зачем?), у вас сразу инициализируется Pygame и откроется окно.

2️⃣ Беда с пространством имен
В game.py мы видим прекрасное: from blocks import *.
Запомните: каждый раз, когда вы используете import *, вы забиваете пространство имен мусором. Какие классы прилетели? Откуда? Никто не знает.

3️⃣ Класс-оркестр
Класс Game — это и швец, и жнец. Он управляет логикой, считает очки, загружает звуки, проигрывает музыку и... отрисовывает блоки.
Нарушение SRP (Single Responsibility Principle) на лицо. Логика игры не должна знать о существовании pygame.mixer или о том, как рисовать прямоугольники.

# В недрах Game.__init__
self.rotate_sound = pygame.mixer.Sound("Sounds/rotate.ogg")
pygame.mixer.music.load("Sounds/music.ogg")

Хотите поменять звуковую библиотеку? Удачи переписывать всё ядро игры.

4️⃣ ООП головного мозга: Наследование ради... ничего
В blocks.py мы видим классическую ошибку: создание семи разных классов (LBlock, JBlock и т.д.), которые наследуются от Block только ради того, чтобы в __init__ вписать словарик с координатами.

Это классический оверинжиниринг. Все эти классы не имеют уникального поведения, только разные данные.

Как надо: Один класс Block, который принимает тип фигуры или конфиг при инициализации. Данные отдельно, логика отдельно.

5️⃣ Класс Position — зачем?
class Position:
def __init__(self, row, column):
self.row = row
self.column = column

Создавать целый класс для хранения двух целых чисел — это избыточно. В Python есть namedtuple, dataclasses или, в конце концов, просто кортежи (row, col).

6️⃣ Магические числа и хардкод
if self.next_block.id == 3:
self.next_block.draw(screen, 255, 290)
elif self.next_block.id == 4:
self.next_block.draw(screen, 255, 280)

Это «костыльный» UI в чистом виде. Вместо того чтобы вычислить центр области предпросмотра, автор просто подогнал координаты под конкретные ID блоков. Добавите новый блок — и вся верстка поплывет.

7️⃣ Обработка счета из эпохи мамонтов
В game.py мы видим это:

def update_score(self, lines_cleared, move_down_points):
if lines_cleared == 1:
self.score += 100
elif lines_cleared == 2:
self.score += 300
# ... и так далее


Как надо: Обычный словарь или список коэффициентов сделал бы этот код в одну строку. elif-цепочки для простых соответствий — верный признак того, что автор не умеет в структуры данных.

🧑‍⚖️ Вердикт:
Как учебный проект — пойдет. Если учитесь по таким туториалам, помните: их цель — показать результат за 20 минут видео, а не научить вас писать нормальный код. Не тащите эти паттерны в продакшен.

#жарим_код
Telegram
@shadow_0771 попросил дать обратную связь по его коду в рамках изучения ООП
@shadow_0771 попросил дать обратную связь по его коду в рамках изучения ООП.

Ну чтож... #жарим_код!

RPG в консоли — это база. Код выполняет свою задачу, но написан так, что любая попытка его масштабировать вызовет БООООООООЛЬ. Разберем самые важные проблемы 👇

1️⃣ Ctrl+C, Ctrl+V наследование
Смотрим на иерархию брони:
class Armor(Item):
def __init__(self, name, category, strength=None, value_strength=None...): # и еще 100500 аргументов
super().__init__(name, category)
# ...

class Helmet(Armor):
def __init__(self, ...):
super().__init__(...)

class Chestplate(Armor):
# Копия Helmet

class Greaves(Armor):
# Копия Chestplate

Все эти классы (Helmet, Chestplate, Greaves, Boots) — абсолютно идентичны. Они не добавляют ни новых атрибутов, ни нового поведения. Они не делают ничего, кроме вызова super().__init__.

ООП создано не для того, чтобы описывать каждый физический предмет в мире отдельным классом. Если сущности отличаются только названием категории — это должен быть один класс Armor, у которого есть атрибут slot_type (в идеале через Enum).

2️⃣ Конструктор Франкенштейна
Посмотрите, как создается предмет:
crown = Helmet('Шлем Господства', 'Шлем', 'Сила', 5, 'Ловкость', 7, 'Интеллект', 3)

Никогда не хардкодьте названия статов в сигнатуру метода. Используйте словари.
Вместо этой простыни параметров, предмет должен принимать stats={'strength': 5, 'agility': 7, 'intellect': 3}.

3️⃣ Инцест классов
Класс Characteristic принимает в себя hero, а потом делает вот это:
for item in self._hero.slots_equipment.values():
if item:
if hasattr(item, 'value_strength') and item.value_strength:
self.attributes['strength'] += item.value_strength

Это называется "Tight Coupling" (жесткая связность). Класс характеристик лезет грязными руками в инвентарь героя, проверяет, есть ли там предметы, а потом через hasattr (что само по себе костыль в 99% случаев) пытается вытащить из них статы.

Герой должен сам опрашивать свою экипировку и передавать итоговые модификаторы в систему характеристик. А сейчас хвост виляет собакой.

4️⃣ Использование Exceptions для логики
В методе equip_armor видим такое:
try:
if key not in self.slots_equipment:
print('Нет такого слота.')
# ... логика ...
except KeyError:
print(f'Предмет не найден')

Во-первых, перехват широкого KeyError замаскирует вам реальные баги в коде (например, опечатку в словаре внутри try). Во-вторых, исключения — для исключительных ситуаций, а не для проверки наличия предмета в инвентаре. Для этого есть метод словаря .get().

В общем, для начала вместо 10 бесполезных классов брони и монструозных конструкторов можно сделать хотя бы так:

from dataclasses import dataclass
from enum import Enum

class EquipmentSlot(Enum):
HEAD = "Шлем"
CHEST = "Нагрудник"
WEAPON = "Оружие"

@dataclass
class Equipment:
name: str
slot: EquipmentSlot
stats_bonus: dict[str, int]

# Создание предмета:
crown = Equipment(
name='Шлем Господства',
slot=EquipmentSlot.HEAD,
stats_bonus={'strength': 5, 'agility': 7, 'intellect': 3}
)

И всё. Никаких дублей кода, защита от опечаток в слотах через Enum, и расширяемая система статов, куда можно завтра добавить хоть "Удачу", не переписывая __init__ у десятка классов.

ООП — это не когда у вас на каждую сущность во вселенной заведен отдельный класс. ООП — это про управление сложностью и состоянием.


Но для одного месяца обучения — это абсолютно нормальный этап эволюции.

📖 Читаем:
- Когда класс в Python — это зло: 6 случаев, когда вы усложняете себе жизнь
- Принципы SOLID в ООП с примерами на Python