Задача написать бота на питоне для полуавтоматической системы.
Бот будет брать данные из таблице где будет множество инструментов*, на каждом из которых будет срабатывать сигнал на разных таймфреймах* (временных интервалах)

*не зависящие друг от друга (сигнал в таблице не взаимоисключающие)

Сигнал может быть одновременно на разных таймфреймах на одном уровне.


Параметры сделки
  • Direction: Направление сделки (L - лонг, S - шорт)
  • Symbol: Тикер инструмента (SiZ3, RIM3 и т.д.)
  • Level: Ценовой уровень для входа
  • Lots: Количество лотов
  • Tick: Минимальный шаг цены (в идеале чтобы бот сам рассчитывал минимльный шаг цены)
  • Timeframe: Временной промежуток где мы выставляем на каком таймфрейме будет работать бот 5m 15m 30m h1 h4 d
Пояснение У нас есть один инструмент SI и у него написан уровень и система смотрит если на минутном он не дал позицию он отбрасывает этот таймфрей но продалжает 5 минутный тайфрейм и если он тоже не дал то переходит на другой более большой (взятый из таблице как и предидущии) и тот срабатывает (закрывается по патерну) и тогда бот открывает сделку
Условия выставления заявки

Для каждого инструмента в стратегии система проверяет на выбранном таймфрейме(который берет из таблице) закрытия свечей:

Для лонга (L):
  • Цена открытия > Level
  • Минимум свечи < Level
  • Цена закрытия > Level
Для шорта (S):
  • Цена открытия < Level
  • Максимум свечи > Level
  • Цена закрытия < Level
При закрытии свечи (заданного таймфрейма из таблице) и условия прошли что были описаны выше то выставляется заявка



Вид заявки

У нас есть три вида заявки которые так же будут браться из таблицы

Рыночная (цена выставляется по рыночной цене)
Лимитная ( цена высталяется по цене взятой из таблице)
Люфтовая (

для шорта, заявка выставляется по ( цена уровня минус люфт), при выполнении условий срабатывания заявки
Для лонга соответсвенно наоборот

Стоп лосс

шорт
стоп выставляется после исполнения заявки на покупку/продажу хай пробойной свечи плюс один пункт.

условия срабатывания стоп заявки
при достижении цены стоп заявки, начинается отсчет времени , через которое сработает заявка (время указано в окне защитное время в эксель таблице и считается оно в секундах) по истечении заданного защитного времени, выставляется рыночная заявка на покупку/продажу на тот же объем которым была совершенна сделка ( в таблице лоты)


рекомендация от нейронки по выбору брокера
Финам — если ты уже с ним
Подходит, если:
  • Готов работать через QUIK или Transaq
  • Уже есть счет
  • Не нужен удобный REST API

Alor (Алор Брокер) — 💎 лучший выбор
  • Предоставляет API Alor OpenAPI v2
  • ✅ Полный доступ к фьючерсам и опционам MOEX
  • ✅ Работает без QUIK
  • ✅ Поддержка WebSocket + REST
  • ✅ Имеется Python-обертка: https://github.com/alor-dev
  • ✅ Демо-режим для тестов
Этапы
Создание бота
на питоне с синхронизацией с эксель таблицей и погрузкой с айпишников в нашем случае с Финама
Загрузка бота на сервер
загрузка бота на арендованный сервер для дистанционного управления и доступа 24/7
создание Телеграм бота
в Телеграм бот должен управлять ботом на сервере запуск/остановка/выставление всех данных что мы писали в эксель таблице/ добавление счета и токене для работы с одного бота на разные счетах
в боте мы управляем данными для робота теми же что и в эксель таблице и при совершении ботом сделки должно приходить уведомление

Структура проекта:
/project
│
├── strategy.xlsx
├── bot.py  ← (этот файл — основной код)
Вот полный пример кода торгового бота на Python, который:
  1. Загружает торговую стратегию из Excel (strategy.xlsx)
  2. Загружает свечные данные с Finam через finam-export
  3. Проверяет сигналы по уровням и условиям
  4. Расчитывает цену заявки (рыночная, лимитная, люфтовая)
  5. Рассчитывает стоп-лосс и защитное время
  6. Выводит решение о сделке (заглушка для отправки в реальную торговую систему)
import pandas as pd
from datetime import date
from finam.export import Exporter, Market, Timeframe
import time
import threading

# === Загрузка стратегии ===

def load_strategy(filename='strategy.xlsx'):
    df = pd.read_excel(filename)
    df = df.dropna()
    return df.to_dict(orient='records')

# === Загрузка свечей с Finam ===

exporter = Exporter()

TIMEFRAME_MAP = {
    '1m': Timeframe.MINUTES1,
    '5m': Timeframe.MINUTES5,
    '15m': Timeframe.MINUTES15,
    '30m': Timeframe.MINUTES30,
    '1h': Timeframe.HOUR,
    'h1': Timeframe.HOUR,
    'h4': Timeframe.HOUR4,
    'd': Timeframe.DAY,
}

def get_last_candle(symbol, timeframe):
    try:
        candles = exporter.download(
            symbol=symbol,
            market=Market.FUTURES,  # Или SHARES для акций
            timeframe=TIMEFRAME_MAP[timeframe],
            start_date=date.today(),
            end_date=date.today()
        )
        if candles.empty:
            return None
        return candles.iloc[-1]
    except Exception as e:
        print(f"Ошибка при получении свечи для {symbol} — {e}")
        return None

# === Проверка сигнала ===

def is_signal_valid(candle, level, direction):
    o, h, l, c = candle['OPEN'], candle['HIGH'], candle['LOW'], candle['CLOSE']

    if direction == 'L':
        return o > level and l < level and c > level
    elif direction == 'S':
        return o < level and h > level and c < level
    return False

# === Цена заявки ===

def get_order_price(level, order_type, offset, direction):
    if str(order_type).lower() == 'рыночная':
        return 'market'
    elif str(order_type).lower() == 'лимитная':
        return level
    elif str(order_type).lower() == 'люфтовая':
        return level - offset if direction == 'S' else level + offset
    return level

# === Стоп-лосс ===

def calculate_stop_loss(candle, tick, direction):
    if direction == 'L':
        return candle['LOW'] - tick
    else:
        return candle['HIGH'] + tick

# === Заявка и защитное время ===

def place_order(symbol, direction, lots, price):
    print(f"\n➡️  Открытие позиции: {symbol} | {direction} | {lots} лотов | цена: {price}")

def delayed_stop_order(symbol, direction, stop_price, lots, delay_seconds):
    def job():
        print(f"⏳ Защитное время {delay_seconds} сек до стоп-заявки по {symbol}...")
        time.sleep(delay_seconds)
        close_side = 'buy' if direction == 'S' else 'sell'
        print(f"❗ СТОП: {symbol} | Закрытие позиции {close_side} по рынку на {lots} лотов по цене: {stop_price}")
    threading.Thread(target=job).start()

# === Главный цикл ===

def main():
    strategy = load_strategy()

    for row in strategy:
        symbol = row['Symbol']
        direction = row['Direction']
        level = float(row['Level'])
        lots = int(row['Lots'])
        tick = float(row['Tick'])
        timeframe = row['Timeframe']
        offset = float(row.get('Offset', 0))
        order_type = row['OrderType']
        protect_time = int(row.get('ProtectTime', 0))

        print(f"\n🔍 Проверка сигнала: {symbol}, {timeframe}")

        candle = get_last_candle(symbol, timeframe)
        if candle is None:
            print(f"❌ Нет данных по {symbol}")
            continue

        if is_signal_valid(candle, level, direction):
            print(f"✅ Сигнал подтвержден на {symbol} ({timeframe})")

            price = get_order_price(level, order_type, offset, direction)
            place_order(symbol, direction, lots, price)

            stop_price = calculate_stop_loss(candle, tick, direction)
            delayed_stop_order(symbol, direction, stop_price, lots, protect_time)

        else:
            print(f"🟡 Условия входа не выполнены для {symbol} ({timeframe})")

if __name__ == "__main__":
    main()