Отпуск завершен, доехали до дома. Итого, 17 дней, около 5 тыс. километров, 10 мест ночёвки. У нас это первая такая масштабная поездка с 2017 года.
Основной итог: дети доросли до возраста, когда путешествовать с ними на машине на дальние расстояния в удовольствие, а машина для путешествий у нас близка к идеальной.
За все путешествие было несколько происшествий с машиной:
1. Сегодня с утра под Питером машина перестала реагировать на нажатие педали газа. Перезагрузка помогла, думаю программный сбой.
2. Пару раз мы похоже заправилисьослиной мочой плохим бензином: загорался check engine, гас после следущий заправки.
3. Где-то по пути туда у нас начало трескаться лобовое стекло, видимо неудачно отлетел камень.
А так - болота, горы, бороды, бесконечные подъёмы - все препятствия машина преодолела на ура.
Теперь пора возвращаться к оседлой жизни. Если кому-то интересно что-то ещё про отпуск или машину - пишите в личку, подготовлю пост.
#отпуск
Основной итог: дети доросли до возраста, когда путешествовать с ними на машине на дальние расстояния в удовольствие, а машина для путешествий у нас близка к идеальной.
За все путешествие было несколько происшествий с машиной:
1. Сегодня с утра под Питером машина перестала реагировать на нажатие педали газа. Перезагрузка помогла, думаю программный сбой.
2. Пару раз мы похоже заправились
3. Где-то по пути туда у нас начало трескаться лобовое стекло, видимо неудачно отлетел камень.
А так - болота, горы, бороды, бесконечные подъёмы - все препятствия машина преодолела на ура.
Теперь пора возвращаться к оседлой жизни. Если кому-то интересно что-то ещё про отпуск или машину - пишите в личку, подготовлю пост.
#отпуск
👍9
Последние двое выходных я провёл на Волге чуть выше Твери. Там уже нет судоходства, количество лодочников приемлемое, более менее адекватные подъезды к воде и должна быть рыба.
Но далась рыба далеко не сразу: прошлые раза четыре я благополучно пролетел. В этот раз дети очень хотели на рыбалку, а сил организовывать что-то увлекательное с лодкой у меня не было. Потому поехали просто посидеть с фидером на берегу, первый раз с детьми, второй - с другом.
К выбору точки ловли я впервые за долгие годы подошёл "по науке" от рыболовных блогеров: эхолотом нашел в воде место с перепадом глубин и обе рыбалки бомбил чисто его. Результат на фото: с каждой рыбалки привезли по три килограмма подлещиков.
#рыбалка
#волга
Но далась рыба далеко не сразу: прошлые раза четыре я благополучно пролетел. В этот раз дети очень хотели на рыбалку, а сил организовывать что-то увлекательное с лодкой у меня не было. Потому поехали просто посидеть с фидером на берегу, первый раз с детьми, второй - с другом.
К выбору точки ловли я впервые за долгие годы подошёл "по науке" от рыболовных блогеров: эхолотом нашел в воде место с перепадом глубин и обе рыбалки бомбил чисто его. Результат на фото: с каждой рыбалки привезли по три килограмма подлещиков.
#рыбалка
#волга
👏8👍4🔥3❤1
#klhztrader. Часть -1. Метрики.
Небольшая заметка на полях о прометеусе. Оставлю тут, чтобы не утратить обретенные знания.
Раньше я многократно пользовался Прометеусом для отображения метрик приложения. Рантайм шарпа по дефолту экспортирует достаточно информативный набор метрик, в т.ч. потребляемая приложением память, в т.ч. с разбивкой по поколениям сборки мусора. Добавлять кастомные метрики, позволяющие мониторить различные аспекты разрабатываемого приложения - более менее стандартная практика. И тут я задался вопросом, а как разделить метрики на группы (смапленные на разные пути: /metrics, /metrics/critical и т.д.), чтобы можно было опрашивать более критичные метрики с большей периодичностью.
Промучавшись некоторое время, я пришёл к следующему решению:
1. Основную группу метрик маппить стандартным способом, дефолтным .MapMetrics() в Program.cs
2. Для своих метрик создать свой CollectorRegistry, дальше методом Metrics.WithCustomRegistry создать экзмепляр фабрики MetricFactory, который заинжектить в приложение.
3. В местах, где требуются нестандартные метрики забирать MetricFactory и с его помощью создавать экземпляры метрик в нужных местах.
4. Отдачу своих метрик в прометеус осуществлять через самописный контроллер, в который инжектится CollectorRegistry. Дальше метрики в качестве text/plain отдаются контроллером.
#prometheus
Небольшая заметка на полях о прометеусе. Оставлю тут, чтобы не утратить обретенные знания.
Раньше я многократно пользовался Прометеусом для отображения метрик приложения. Рантайм шарпа по дефолту экспортирует достаточно информативный набор метрик, в т.ч. потребляемая приложением память, в т.ч. с разбивкой по поколениям сборки мусора. Добавлять кастомные метрики, позволяющие мониторить различные аспекты разрабатываемого приложения - более менее стандартная практика. И тут я задался вопросом, а как разделить метрики на группы (смапленные на разные пути: /metrics, /metrics/critical и т.д.), чтобы можно было опрашивать более критичные метрики с большей периодичностью.
Промучавшись некоторое время, я пришёл к следующему решению:
1. Основную группу метрик маппить стандартным способом, дефолтным .MapMetrics() в Program.cs
2. Для своих метрик создать свой CollectorRegistry, дальше методом Metrics.WithCustomRegistry создать экзмепляр фабрики MetricFactory, который заинжектить в приложение.
3. В местах, где требуются нестандартные метрики забирать MetricFactory и с его помощью создавать экземпляры метрик в нужных местах.
4. Отдачу своих метрик в прометеус осуществлять через самописный контроллер, в который инжектится CollectorRegistry. Дальше метрики в качестве text/plain отдаются контроллером.
#prometheus
👍3❤1
#klhztrader. Часть 0. Автодеплой проекта.
Запишу инструкцию по настройке основы самого простого автодеплоя с помощью gitea actions. Она основана на документации, но обходит несколько подводных камней на которые я наткнулся в процессе.
Цель: исполнять bash - команды на удаленном сервере по коммиту в репозиторий, раннер крутится как демон, не в контейнере. При этом, для деплоя нам скорее всего потребуется установленный на сервер докер. Работу без докера я не проверял.
1. Качаем бинарник gitea actions runner с сайта на сервер (для совместимости с доступной мне версией gitea я взял раннер двухлетней давности).
2. Переименовываем скачанный файл в act_runner и разрешаем ему исполняться:
3. Добавляем пользователя act_runner, я выдал ему sudo привилегии.
4. Запускаем ./act_runner register и идём по шагам, вставляя по запросу url gitea, registration token из настроек gitea (settings => actions => runners => Create new Runner), как-то называем раннер, затем нас спрашивают добавить labels для активации. Добавляем
Это указывает runner-у выполнять джобы, помеченные как
5. Убеждаемся в интерфейсе gitea - появился ли там созданный раннер.
6. Настраиваем запуск раннера в качестве демона как указано в документации. Ниже приведу мой вариант .service файла, в котором убрано использование конфига и перебиты пути на домашнюю директорию моего пользователя:
Дальше как в инструкции:
```shell
sudo systemctl enable act_runner --now
on:
- push
jobs:
test:
runs-on: my_project_deploy_cmd
name: test action
steps:
- name: test
run: echo "Hello from Gitea Action11s!" && docker ps -a
Важно, чтобы в runs-on был указан label, который мы задали в конце шага 4. Будет выполнен вывод текста в командную строку, а затем - запрос вывода статусов всех докер-контейнеров на сервере.
9. Имея в распоряжении командную строку, мы можем проделать любые манипуляции с сервером и завести деплой в том виде, в котором нам удобно.
#gitea
#devops
Запишу инструкцию по настройке основы самого простого автодеплоя с помощью gitea actions. Она основана на документации, но обходит несколько подводных камней на которые я наткнулся в процессе.
Цель: исполнять bash - команды на удаленном сервере по коммиту в репозиторий, раннер крутится как демон, не в контейнере. При этом, для деплоя нам скорее всего потребуется установленный на сервер докер. Работу без докера я не проверял.
1. Качаем бинарник gitea actions runner с сайта на сервер (для совместимости с доступной мне версией gitea я взял раннер двухлетней давности).
wget https://dl.gitea.com/act_runner/0.2.3/act_runner-0.2.3-linux-amd64
2. Переименовываем скачанный файл в act_runner и разрешаем ему исполняться:
chmod +x act_runner
3. Добавляем пользователя act_runner, я выдал ему sudo привилегии.
4. Запускаем ./act_runner register и идём по шагам, вставляя по запросу url gitea, registration token из настроек gitea (settings => actions => runners => Create new Runner), как-то называем раннер, затем нас спрашивают добавить labels для активации. Добавляем
my_project_deploy_cmd:host
Это указывает runner-у выполнять джобы, помеченные как
my_project_deploy_cmd
непосредственно на сервере, а не внутри контейнера.5. Убеждаемся в интерфейсе gitea - появился ли там созданный раннер.
6. Настраиваем запуск раннера в качестве демона как указано в документации. Ниже приведу мой вариант .service файла, в котором убрано использование конфига и перебиты пути на домашнюю директорию моего пользователя:
[Unit]
Description=Gitea Actions runner
Documentation=https://gitea.com/gitea/act_runner
After=docker.service
[Service]
ExecStart=/home/my_user/act_runner daemon
ExecReload=/bin/kill -s HUP $MAINPID
WorkingDirectory=/home/my_user
TimeoutSec=0
RestartSec=10
Restart=always
User=act_runner
[Install]
WantedBy=multi-user.target
Дальше как в инструкции:
sudo systemctl daemon-reload
```shell
sudo systemctl enable act_runner --now
name: test
7. Включаем enable actions в настройках репозитория.
8. Добавляем в корень репозитория папку .gitea, в неё - папку workflows, в нее - файл test.yaml со следующим содержимым:
on:
- push
jobs:
test:
runs-on: my_project_deploy_cmd
name: test action
steps:
- name: test
run: echo "Hello from Gitea Action11s!" && docker ps -a
`
Важно, чтобы в runs-on был указан label, который мы задали в конце шага 4. Будет выполнен вывод текста в командную строку, а затем - запрос вывода статусов всех докер-контейнеров на сервере.
9. Имея в распоряжении командную строку, мы можем проделать любые манипуляции с сервером и завести деплой в том виде, в котором нам удобно.
#gitea
#devops
Gitea: Git with a cup of tea
act_runner
A runner for Gitea based on act.
👍6
#klhztrader, Часть 1. Анонс.
Последние два месяца я все свободное от работы время занимался новым проектом: ботом для торговли на Мосбирже. Предыдущие два поста про метрики и gitea были написаны на этапе подготовки к деплою mvp бота. Все дальнейшие посты, касающиеся торгового бота будут под тегом #klhztrader.
Как родился проект. Я давно хотел познакомиться с темой инвестиций, торговли на бирже и т.д., но не хватало когнитивных ресурсов. И тут за время отпуска я внезапно отдохнул так, что снова захотелось думать. В доступе есть доменный эксперт - друг много лет успешно торгует на Мосбирже. Было принято решение изучить возможности, представляемые биржей.
Немного поторговав руками я понял, что слишком азартен, надо много работать над собой. Зато при просмотре графиков котировок у меня возникают флешбеки из универа и с первой работы в IT: там мне довольно много приходилось иметь дело с временными рядами.
Обычно такие вещи пишут на питоне, но я в гробу его видал, а под c# нашлось отличное api от T-Bank-а, поставляемое в качестве пакета, подключаемого в проект. Указываешь токен доступа - и можно торговать.
Дальше будет длинный конспект процесса разработки и результаты экспериментов. Работы все ещё активно ведутся, не переключайтесь. Код пока не выкладываю, возможно выложу потом.
Последние два месяца я все свободное от работы время занимался новым проектом: ботом для торговли на Мосбирже. Предыдущие два поста про метрики и gitea были написаны на этапе подготовки к деплою mvp бота. Все дальнейшие посты, касающиеся торгового бота будут под тегом #klhztrader.
Как родился проект. Я давно хотел познакомиться с темой инвестиций, торговли на бирже и т.д., но не хватало когнитивных ресурсов. И тут за время отпуска я внезапно отдохнул так, что снова захотелось думать. В доступе есть доменный эксперт - друг много лет успешно торгует на Мосбирже. Было принято решение изучить возможности, представляемые биржей.
Немного поторговав руками я понял, что слишком азартен, надо много работать над собой. Зато при просмотре графиков котировок у меня возникают флешбеки из универа и с первой работы в IT: там мне довольно много приходилось иметь дело с временными рядами.
Обычно такие вещи пишут на питоне, но я в гробу его видал, а под c# нашлось отличное api от T-Bank-а, поставляемое в качестве пакета, подключаемого в проект. Указываешь токен доступа - и можно торговать.
Дальше будет длинный конспект процесса разработки и результаты экспериментов. Работы все ещё активно ведутся, не переключайтесь. Код пока не выкладываю, возможно выложу потом.
🔥13❤2
#klhztrader. Часть 2. Визуализация данных.
При работе с временными рядами требуется периодически визуализировать то, с чем ты работаешь, с нанесенными на график промежуточными результатами. В питоне для этих целей есть прекрасна библиотека matplotlib, с её помощью можно в пару строк сделать график.
В шарпе все несколько сложнее. Изначально я хотел вкрячить какую-то визуализацию прямо в бота по принципу server-side rendering. Я потыкал несколько библиотек для построения графиков. Все они предназначены для серьезной разработки, но ведь моя цель - торговый бот, а не классный визуализатор данных!
Сунулся в React.js, с которым я капельку знаком - за два года там чего-то навертели в гайдах по быстрому старту, старт нифига не быстрый. Vue.js оказался сильно дружелюбнее. Но добрый человек @vekhden_speak посоветовал попробовать для визуализации графану (при попытке сложить данные в прометеус, так, чтобы было удобно, и родился один из прошлых постов). В графане я и остался.
В итоге я пришел к тому, что локально на ноуте кручу данные, укладываю результаты экспериментов в таблицу в постгресе и отсматриваю результаты работы на нескольких дашбордах в графане.
При работе с временными рядами требуется периодически визуализировать то, с чем ты работаешь, с нанесенными на график промежуточными результатами. В питоне для этих целей есть прекрасна библиотека matplotlib, с её помощью можно в пару строк сделать график.
В шарпе все несколько сложнее. Изначально я хотел вкрячить какую-то визуализацию прямо в бота по принципу server-side rendering. Я потыкал несколько библиотек для построения графиков. Все они предназначены для серьезной разработки, но ведь моя цель - торговый бот, а не классный визуализатор данных!
Сунулся в React.js, с которым я капельку знаком - за два года там чего-то навертели в гайдах по быстрому старту, старт нифига не быстрый. Vue.js оказался сильно дружелюбнее. Но добрый человек @vekhden_speak посоветовал попробовать для визуализации графану (при попытке сложить данные в прометеус, так, чтобы было удобно, и родился один из прошлых постов). В графане я и остался.
В итоге я пришел к тому, что локально на ноуте кручу данные, укладываю результаты экспериментов в таблицу в постгресе и отсматриваю результаты работы на нескольких дашбордах в графане.
🔥4
#klhztrader. Часть 3. Инфраструктура.
Бот хостится на виртуалке, 4 х 2.2ГГц, 4Гб оперативки и HDD на 80 Гб, хостер - ruvds. Пока таких ресурсов хватает с головой.
На сервере в докер композе живут шарповый сервис бота, Prometheus, Loki, Graphana, и PostgreSQL. Для мониторинга состояния сервера в качестве обычного демона установлен прометеусовский node_exporter.
Для автодеплоя используется сервер gitea, на которой меня любезно пустил @ssleg. О моих сражениях с настройкой раннера я писал ранее.
Так как проектом занимаюсь я один, было решено в плане мониторинга и обработки логов остановиться на прометеусовском стеке. Я не очень люблю Loki, но для небольшого проекта он оказался идеальным: диск и оперативку потребляет по минимуму, без проблем стыкуется с графаной. Логов у меня не много: 4-5 записей в секунду, во основном меня интересуют ошибки, при том сортировать/фильтровать их не нужно. А тот же ELK съел бы у меня все ресурсы сервера, не дав ничего нового.
В итоге я собрал симпатичный дашборд для мониторинга состояния сервера. На нем четыре графика:
1. Оперативная память. Два графика - доступная память и потребление моего сервиса.
2. Утилизация CPU. По графику на каждое из ядер + отдельно график доли моего сервиса в общем потреблении.
3. Свободное место на диске.
4. Нагрузка на сеть.
Для экстренного управления сделан тетеграм бот. Он позволяет ребутнуть бота (Environment.Exit), бот вырубается, а затем средствами докера автоматически запускается. Ещё можно включить/выключить реальные покупки и продажи, а также докупить или сбросить активы на бирже.
Бот хостится на виртуалке, 4 х 2.2ГГц, 4Гб оперативки и HDD на 80 Гб, хостер - ruvds. Пока таких ресурсов хватает с головой.
На сервере в докер композе живут шарповый сервис бота, Prometheus, Loki, Graphana, и PostgreSQL. Для мониторинга состояния сервера в качестве обычного демона установлен прометеусовский node_exporter.
Для автодеплоя используется сервер gitea, на которой меня любезно пустил @ssleg. О моих сражениях с настройкой раннера я писал ранее.
Так как проектом занимаюсь я один, было решено в плане мониторинга и обработки логов остановиться на прометеусовском стеке. Я не очень люблю Loki, но для небольшого проекта он оказался идеальным: диск и оперативку потребляет по минимуму, без проблем стыкуется с графаной. Логов у меня не много: 4-5 записей в секунду, во основном меня интересуют ошибки, при том сортировать/фильтровать их не нужно. А тот же ELK съел бы у меня все ресурсы сервера, не дав ничего нового.
В итоге я собрал симпатичный дашборд для мониторинга состояния сервера. На нем четыре графика:
1. Оперативная память. Два графика - доступная память и потребление моего сервиса.
2. Утилизация CPU. По графику на каждое из ядер + отдельно график доли моего сервиса в общем потреблении.
3. Свободное место на диске.
4. Нагрузка на сеть.
Для экстренного управления сделан тетеграм бот. Он позволяет ребутнуть бота (Environment.Exit), бот вырубается, а затем средствами докера автоматически запускается. Ещё можно включить/выключить реальные покупки и продажи, а также докупить или сбросить активы на бирже.
👍3🔥3
#klhztrader. Часть 4. Архитектура приложения, некоторые особенности реализации.
Я подписываюсь на поток данных биржи (стрим grpc), после чего распространяю эти данные по своему приложению через каналы (Channel<T>), обернутые в абстракцию, чтобы при желании можно было без боли воткнуть туда брокер сообщений и разорвать бота на любое количество процессов.
История торгов за последние несколько часов нужна достаточно часто, тягать ее каждый раз из базы - не вариант. Значит - нужно кеширование. Redis/Tarantool я тащить не стал, сделал самописный кэш. Первые итерации были на основе массивов, но в итоге я пришел к кэшу на базе LinkedList (первый раз использовал его на практике).
Он оказался идеальным решением, чтобы держать в памяти данные за определенный промежуток времени. Добавляем новый элемент с помощью AddLast, а дальше в цикле while удаляем с помощью RemoveFirst всё вылезшее за пределы заданного временного интервала. Доступ к листу идёт через обычный lock. Для вычислений данные копируются в массив, который отдаётся запросившему.
Все используемые мной в проекте модели для хранения данных имеют поля с доступами {get;init;}, что защищает меня от чудес, которые могут проявиться при изменении объектов из нескольких потоков. Можно было бы упороться в readonly struct, но это уже для высоких нагрузок, которых у меня не предвидится.
Я подписываюсь на поток данных биржи (стрим grpc), после чего распространяю эти данные по своему приложению через каналы (Channel<T>), обернутые в абстракцию, чтобы при желании можно было без боли воткнуть туда брокер сообщений и разорвать бота на любое количество процессов.
История торгов за последние несколько часов нужна достаточно часто, тягать ее каждый раз из базы - не вариант. Значит - нужно кеширование. Redis/Tarantool я тащить не стал, сделал самописный кэш. Первые итерации были на основе массивов, но в итоге я пришел к кэшу на базе LinkedList (первый раз использовал его на практике).
Он оказался идеальным решением, чтобы держать в памяти данные за определенный промежуток времени. Добавляем новый элемент с помощью AddLast, а дальше в цикле while удаляем с помощью RemoveFirst всё вылезшее за пределы заданного временного интервала. Доступ к листу идёт через обычный lock. Для вычислений данные копируются в массив, который отдаётся запросившему.
Все используемые мной в проекте модели для хранения данных имеют поля с доступами {get;init;}, что защищает меня от чудес, которые могут проявиться при изменении объектов из нескольких потоков. Можно было бы упороться в readonly struct, но это уже для высоких нагрузок, которых у меня не предвидится.
👍2🔥1
#klhztrader. Часть 5. Некоторые биржевые понятия.
В дальнейшем при описании стратегий торговли я буду оперировать некоторыми специфическими понятиями. Вообще, они легко гуглятся, но думаю не лишним будет вкратце описать их тут.
Позиция - любой актив, который есть у нас в наличии. Мы можем открыть (войти в) позицию - приобрести некоторое количество активов. Или закрыть (выйти из) - избавиться от нее, получив деньги.
Лонг - позиция, открытая в надежде на рост цены актива.
Шорт - позиция, открытая в надежде на падение цены актива.
Волатильность - нестабильность цены актива.
Брокер - прокладка между частным лицом и Мосбиржей, в моем случае - T-инвестиции.
Маржинальная торговля (маржиналка) - торговля с привлечением заёмных средств брокера. Прибыль или убыток начисляется два раза в рабочий день во время клиринга.
Клиринг - в районе 14 и 19 часов по будням биржа останавливает торги и проводит различные расчёты, в т.ч. начисляет или списывает маржу, которую заработал пользователь на фьючерсах с прошлого клиринга.
Фьючерс - производная от реально существующей ценной бумаги, по дефолту торгуется маржинально. По сути, торговля ведётся ожиданиями на цену.
Свеча - формат представления данных о торгах за временной интервал, включает в себя цену на начало, цену на конец, максимальную и минимальную цены.
Стопы - механизмы автоматического выхода из позиций при выполнении определенных условий. Это отдельная тема, о ней будет рассказано потом.
Заявки - заявки на покупку или продажу актива. Лимитная заявка - заявка на покупку/продажу не более чем N ценных бумаг по цене M.
Совокупность лимитных заявок образует стакан. Назван он так из-за одной из самых популярных форм отображения, при случае приложу скрин. Заявки можно бесплатно выставлять и отменять сколько угодно раз, комиссия взимается только за исполнение.
Самый быстрый способ что-то купить/продать - сделать рыночную заявку. Если хватает лимитных заявок в стакане, она съедает их оттуда. Соответственно, чтобы сместить цену в какую-то сторону, нужно чтобы часть стакана была съедена рыночными заявками (а часть будет отменена пользователями и роботами). Если лимитных заявок в стакане не хватает на удовлетворение рыночной заявки - будет выброшена ошибка.
В дальнейшем при описании стратегий торговли я буду оперировать некоторыми специфическими понятиями. Вообще, они легко гуглятся, но думаю не лишним будет вкратце описать их тут.
Позиция - любой актив, который есть у нас в наличии. Мы можем открыть (войти в) позицию - приобрести некоторое количество активов. Или закрыть (выйти из) - избавиться от нее, получив деньги.
Лонг - позиция, открытая в надежде на рост цены актива.
Шорт - позиция, открытая в надежде на падение цены актива.
Волатильность - нестабильность цены актива.
Брокер - прокладка между частным лицом и Мосбиржей, в моем случае - T-инвестиции.
Маржинальная торговля (маржиналка) - торговля с привлечением заёмных средств брокера. Прибыль или убыток начисляется два раза в рабочий день во время клиринга.
Клиринг - в районе 14 и 19 часов по будням биржа останавливает торги и проводит различные расчёты, в т.ч. начисляет или списывает маржу, которую заработал пользователь на фьючерсах с прошлого клиринга.
Фьючерс - производная от реально существующей ценной бумаги, по дефолту торгуется маржинально. По сути, торговля ведётся ожиданиями на цену.
Свеча - формат представления данных о торгах за временной интервал, включает в себя цену на начало, цену на конец, максимальную и минимальную цены.
Стопы - механизмы автоматического выхода из позиций при выполнении определенных условий. Это отдельная тема, о ней будет рассказано потом.
Заявки - заявки на покупку или продажу актива. Лимитная заявка - заявка на покупку/продажу не более чем N ценных бумаг по цене M.
Совокупность лимитных заявок образует стакан. Назван он так из-за одной из самых популярных форм отображения, при случае приложу скрин. Заявки можно бесплатно выставлять и отменять сколько угодно раз, комиссия взимается только за исполнение.
Самый быстрый способ что-то купить/продать - сделать рыночную заявку. Если хватает лимитных заявок в стакане, она съедает их оттуда. Соответственно, чтобы сместить цену в какую-то сторону, нужно чтобы часть стакана была съедена рыночными заявками (а часть будет отменена пользователями и роботами). Если лимитных заявок в стакане не хватает на удовлетворение рыночной заявки - будет выброшена ошибка.
❤5👍3