Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 42 additions & 0 deletions .github/workflows/staging-deploy.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
name: Deploy to Staging (home server)
on:
pull_request:
types: [opened, synchronize, reopened, ready_for_review]
workflow_dispatch:

env:
ACTIONS_RUNNER_DEBUG: false

jobs:
full-stack-deploy:
runs-on: ubuntu-latest
continue-on-error: true
timeout-minutes: 20

steps:
- name: Connect to Tailscale
continue-on-error: true
uses: tailscale/github-action@v4
with:
oauth-client-id: ${{ secrets.TAILSCALE_CLIENT_ID }}
oauth-secret: ${{ secrets.TAILSCALE_CLIENT_SECRET }}
tags: tag:ci

- name: Install SSH key
continue-on-error: true
run: |
mkdir -p ~/.ssh
chmod 700 ~/.ssh

echo "${{ secrets.STAGING_DEPLOY }}" > ~/.ssh/home_server_key
chmod 600 ~/.ssh/home_server_key

ssh-keyscan -p 2222 100.64.83.67 >> ~/.ssh/known_hosts

- name: Deploy stack
continue-on-error: true
run: |
ssh -i ~/.ssh/home_server_key \
-p 2222 \
"carkod@100.64.83.67" \
'cd /var/www/terminal.binbot.in && docker compose up --pull always -d && docker image prune -f'
6 changes: 3 additions & 3 deletions api/exchange_apis/kucoin/futures/position_deal.py
Original file line number Diff line number Diff line change
Expand Up @@ -801,10 +801,10 @@ def exit(self, close_price: float, _: float | None = None) -> BotModel:
and (int(time() * 1000) - self.active_bot.deal.opening_timestamp)
>= 3 * 24 * 60 * 60 * 1000
)
# Panic close only low-profit positions after 3 days.
if 0 < bot_profit < 1 and is_3_days:
# Panic close stale low-conviction positions after 3 days.
if -1 <= bot_profit < 1 and is_3_days:
self.controller.update_logs(
f"Panic close triggered for {position_name} due to {'3 days elapsed' if is_3_days else 'unprofitable position'} with profit {bot_profit}. Closing position immediately.",
f"Panic close triggered for stale {position_name} position after 3 days with profit {bot_profit}. Closing position immediately.",
self.active_bot,
)
self.close_all()
Expand Down
68 changes: 67 additions & 1 deletion api/tests/test_kucoin_futures_stop_loss.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from bots.models import BotModel, DealModel, OrderModel
from exchange_apis.kucoin.futures.futures_deal import KucoinPositionDeal
from exchange_apis.kucoin.futures.position_deal import PositionDeal
from pybinbot import OrderBase, OrderStatus, DealType, Position
from pybinbot import MarketType, OrderBase, OrderStatus, DealType, Position
from kucoin_universal_sdk.generate.futures.order.model_add_order_req import (
AddOrderReq,
)
Expand Down Expand Up @@ -263,6 +263,72 @@ def test_reconcile_exchange_sl_places_when_exchange_missing():
assert calls == ["cancel", "place"]


def test_exit_panic_closes_stale_mild_loser_after_three_days(monkeypatch):
deal = cast(Any, PositionDeal.__new__(PositionDeal))
deal.price_precision = 2
deal.active_bot = BotModel(
pair="BEATUSDTM",
market_type=MarketType.FUTURES,
position=Position.long,
stop_loss=0,
trailing=False,
take_profit=0,
deal=DealModel(
opening_price=100.0,
opening_timestamp=1_000,
),
)
deal.active_bot.position = Position.long
deal.controller = types.SimpleNamespace(
save=lambda bot: None,
update_logs=lambda *args, **kwargs: None,
)
closed: list[bool] = []
deal.close_all = lambda: closed.append(True)

monkeypatch.setattr(
"exchange_apis.kucoin.futures.position_deal.time",
lambda: (1_000 + (4 * 24 * 60 * 60 * 1000)) / 1000,
)

PositionDeal.exit(deal, 99.5)

assert closed == [True]


def test_exit_keeps_stale_loser_below_panic_close_band(monkeypatch):
deal = cast(Any, PositionDeal.__new__(PositionDeal))
deal.price_precision = 2
deal.active_bot = BotModel(
pair="BEATUSDTM",
market_type=MarketType.FUTURES,
position=Position.long,
stop_loss=0,
trailing=False,
take_profit=0,
deal=DealModel(
opening_price=100.0,
opening_timestamp=1_000,
),
)
deal.active_bot.position = Position.long
deal.controller = types.SimpleNamespace(
save=lambda bot: None,
update_logs=lambda *args, **kwargs: None,
)
closed: list[bool] = []
deal.close_all = lambda: closed.append(True)

monkeypatch.setattr(
"exchange_apis.kucoin.futures.position_deal.time",
lambda: (1_000 + (4 * 24 * 60 * 60 * 1000)) / 1000,
)

PositionDeal.exit(deal, 98.9)

assert closed == []


def test_reconcile_exchange_sl_skips_on_api_failure():
"""API blip must not cancel/replace a possibly-still-valid SL."""
calls: list[str] = []
Expand Down
5 changes: 0 additions & 5 deletions api/tools/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,6 @@ def _validate_required_vars(self):
"TZ",
"SECRET_KEY",
"ENV",
"BACKEND_DIRECTORY",
"BACKEND_DOMAIN",
"FRONTEND_DOMAIN",
"POSTGRES_USER",
Expand Down Expand Up @@ -97,10 +96,6 @@ def secret_key(self) -> str:
def env(self) -> str:
return self._get_required("ENV")

@property
def backend_directory(self) -> str:
return self._get_required("BACKEND_DIRECTORY")

@property
def backend_domain(self) -> str:
return self._get_required("BACKEND_DOMAIN")
Expand Down
Loading