A two-part crypto trading workspace:
app/— Python analysis backend. Read-only. Indicators, patterns, strategies, backtesting, risk validation, market/account reads. Never submits orders.bybit-trader/— TypeScript control layer. The sole execution surface. Order submission is gated behind mode, kill-switch, risk re-validation, and a one-shot confirmation token.
Everything previously capable of auto-execution (scheduler, execution_service,
telegram_polling, reconciliation_service, live CLI, MCP trade tools) has been
moved to legacy/ and is not installed with the active package.
┌──────────────────────────────────────────────────────────────┐
│ Claude / CLI │
└───────────────┬──────────────────────────┬───────────────────┘
│ │
│ analysis │ plan → confirm → execute
▼ ▼
┌──────────────────────────────┐ ┌──────────────────────────────┐
│ Python app/ (read-only) │ │ bybit-trader/ (TypeScript) │
│ │ │ │
│ market_data_service │ │ mode_manager │
│ portfolio_service │ │ risk_engine │
│ signal_service (strategies) │ │ order_planner │
│ risk_service (validator) │ │ confirmation_gate │
│ backtesting │ │ executor ◄── only submitOrder│
│ exchanges/ (fetch_* only) │ │ bybit_client (read-only) │
└──────────────┬───────────────┘ └──────────────┬───────────────┘
│ CCXT read │ bybit-api write
▼ ▼
Bybit exchange
No scheduler, no admin socket, no Telegram approvals, no MCP trade tools.
| Mode | Allowed operations |
|---|---|
READ_ONLY |
READ_MARKET, READ_ACCOUNT |
ANALYSIS |
+ ANALYZE |
ORDER_PREP |
+ PREPARE_ORDER |
CONFIRM_EXEC |
+ CONFIRM_ORDER, EXECUTE_ORDER (token required) |
AUTO_LIMITED |
+ EXECUTE_ORDER without per-trade prompt (Phase 5; unused) |
Default mode in bybit-trader/system_config.json is READ_ONLY
with trading_enabled=false and kill_switch=true.
bash scripts/setup.sh # creates .venv, installs deps, copies .env
source .venv/bin/activate
pytest tests/ -v # run analysis + guard testsEdit .env with your Bybit API key/secret. Phase 1 keys should be
Read-only (no Trade, no Withdraw).
There is no long-running Python process to start. The backend is a library called from tests, notebooks, or the TS control layer.
cd bybit-trader
npm install
cp .env.example .env # fill BYBIT_API_KEY / SECRET / TESTNET
npm run typecheck
npm run dev # HTTP status on http://127.0.0.1:7420/
npm run cli -- status # print mode + config summaryExecution flow (Phase 2+):
npm run cli -- plan '{"symbol":"BTCUSDT","side":"Buy",...}' # builds OrderPlan + risk validation
npm run cli -- confirm <plan_hash> # issues one-shot token (60s TTL)
npm run cli -- execute --token <uuid> # submits order; fires only if all invariants holdexecutor.ts is the only file in the repo that calls
RestClientV5.submitOrder. The guard test
tests/test_no_exec_surface.py locks this in.
trading/
├── app/ # Python analysis backend (READ-ONLY)
│ ├── indicators/ # trend, momentum, volatility, volume
│ ├── patterns/ # candle, breakout, divergence, market structure
│ ├── strategies/ # ema_rsi_macd, breakout_confirmation, mean_reversion
│ ├── exchanges/
│ │ ├── base.py # ExchangeBase (no place/cancel)
│ │ ├── ccxt_client.py # fetch_* only
│ │ └── bybit_adapter.py
│ ├── services/
│ │ ├── market_data_service.py
│ │ ├── portfolio_service.py
│ │ ├── signal_service.py # runs strategies → signals; no execution
│ │ ├── risk_service.py # validator library
│ │ └── telemetry_service.py
│ ├── backtesting/ # engine, metrics, loaders
│ ├── models/ # pydantic schemas
│ ├── storage/ # SQLAlchemy + repositories
│ ├── utils/ # time, ids, math, validations
│ └── settings.py # execution_mode is pinned to "readonly"
├── configs/
│ ├── base.yaml # defaults
│ ├── paper.yaml # analysis-only override
│ ├── testnet.yaml
│ ├── risk.yaml # validator caps
│ └── symbols.yaml # allowed trading pairs
├── scripts/
│ ├── setup.sh # env bootstrap
│ └── run_backtest.sh # backtests
├── bybit-trader/ # TypeScript control layer (EXECUTION)
│ ├── risk_config.json
│ ├── system_config.json # READ_ONLY / trading_enabled=false / kill_switch=true
│ ├── .env.example # BYBIT_API_KEY / SECRET / TESTNET
│ ├── package.json
│ └── src/
│ ├── types.ts
│ ├── config_loader.ts
│ ├── mode_manager.ts # enforces stage ladder
│ ├── risk_engine.ts # validateTrade()
│ ├── bybit_client.ts # read-only RestClientV5 wrapper
│ ├── order_planner.ts # buildPlan() + plan_hash
│ ├── confirmation_gate.ts # one-shot, 60s tokens
│ ├── executor.ts # ONLY file that submits orders
│ ├── cli.ts # status | plan | confirm | execute --token
│ └── index.ts # status HTTP server on :7420
├── tests/
│ ├── test_indicators.py
│ ├── test_patterns.py
│ ├── test_backtesting.py
│ ├── test_risk.py
│ └── test_no_exec_surface.py # locks in the refactor
├── state/ # local runtime files (HALT, sqlite)
├── logs/
├── .env.example # EXCHANGE_API_KEY / SECRET / TESTNET
├── .mcp.json # {"mcpServers": {}} — no MCP trade tools
└── legacy/ # archived pre-refactor; excluded from install
Two independent layers:
-
Python
risk_service.py— used by tests and analysis. ValidatesOrderRequestagainst kill-switch, symbol allow-list, stop-loss, position cap, notional cap, daily-loss cap, cooldown. Fail-closed on DB error. Not wired to any executor. -
TS
risk_engine.ts—validateTrade()runs insideorder_planner.tsand again insideexecutor.tsimmediately beforesubmitOrder. Rejects on kill-switch, trading-disabled, pair allow-list, leverage cap, stop-loss validity, position cap, per-trade risk cap, daily-loss cap, hard-stop drawdown.
Kill switches:
# Python side — existence of this file is checked by risk_service
touch state/HALT
# TS side — flip system_config.json
jq '.kill_switch=true' bybit-trader/system_config.json > tmp && mv tmp bybit-trader/system_config.jsonExecution also refuses if bybit-trader/system_config.json:trading_enabled=false.
source .venv/bin/activate
pytest tests/ -v --tb=short| File | Covers |
|---|---|
test_indicators.py |
EMA, SMA, RSI, MACD, ATR, Bollinger, VWAP |
test_patterns.py |
Doji, hammer, engulfing, inside bar, market structure |
test_backtesting.py |
Engine mechanics, stop/TP hits, metrics |
test_risk.py |
All rejection cases + position sizing |
test_no_exec_surface.py |
Locks in removal of order methods and MCP trade tools |
TypeScript:
cd bybit-trader && npm run typecheckThe Telegram execution bot provides 14 commands for order placement, position management, and order management.
/order SYMBOL buy|sell type=market|limit qty= price= leverage= sl= tp= reduce_only=true|false post_only=true|false
- Create a pending order (requires /confirm)
- Market orders must not include price
- Limit orders must include price
- qty is mandatory (no risk-based sizing)
- reduce_only and post_only are optional (default false)
- Example:
/order BTCUSDT buy type=market qty=0.01 leverage=2 sl=50000 tp=55000
/confirm
- Execute the pending order
- Only works if mode=CONFIRM_EXEC, trading_enabled=true, kill_switch=false
- Pending order expires after 120 seconds
/cancel
- Cancel the pending order
- Does not cancel open orders on the exchange
/close SYMBOL qty=
- Close a position (full or partial)
- If qty is not provided, closes the full position
- If qty is provided, closes partial position
- Executes immediately (no /confirm required)
- Example:
/close BTCUSDT(full close) - Example:
/close BTCUSDT qty=0.005(partial close)
/close_all SYMBOL=
- Close all positions
- If symbol is not provided, closes all positions for all symbols
- If symbol is provided, closes all positions for that symbol
- Executes immediately
- Example:
/close_all(close all positions) - Example:
/close_all BTCUSDT(close all BTCUSDT positions)
/update_sl SYMBOL sl=
- Update stop loss for an open position
- Executes immediately
- Example:
/update_sl BTCUSDT sl=49000
/update_tp SYMBOL tp=
- Update take profit for an open position
- Executes immediately
- Example:
/update_tp BTCUSDT tp=56000
/cancel_order SYMBOL order_id=
- Cancel a specific open order by ID
- Executes immediately
- Use /open_orders to get order IDs
- Example:
/cancel_order BTCUSDT order_id=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
/cancel_all SYMBOL=
- Cancel all open orders
- If symbol is not provided, cancels all orders for all symbols
- If symbol is provided, cancels all orders for that symbol
- Executes immediately
- Example:
/cancel_all(cancel all orders) - Example:
/cancel_all BTCUSDT(cancel all BTCUSDT orders)
/open_orders SYMBOL=
- Show open orders
- Displays symbol, side, qty, price, and order_id for each order
- Example:
/open_orders(all orders) - Example:
/open_orders BTCUSDT(BTCUSDT orders only)
/set_leverage SYMBOL leverage=
- Set leverage for a symbol
- Validates against max_leverage in risk_config.json
- Executes immediately
- Example:
/set_leverage BTCUSDT leverage=5
/positions
- Show current positions with PnL
/status
- Show bot status and pending order
/preflight
- Check if execution is currently allowed
- Reports mode, trading_enabled, kill_switch, allowed symbols, max leverage
- Lists block reasons if execution is not allowed
- Only
/orderrequires/confirm - All management commands execute immediately
- New order placement is gated for safety
- Position management actions are immediate for speed
- Only TELEGRAM_ALLOWED_CHAT_ID can execute commands
- Respects system_config mode, trading_enabled, kill_switch
- All actions are logged to audit trail
- No natural language parsing
- One pending order at a time (120s TTL)
The Telegram execution bot provides controlled live trading with explicit safety gates.
Step 1: Verify risk configuration
cd bybit-trader
npm run check:configReview risk_config.json:
allowed_pairs: symbols you want to trademax_risk_per_trade: typically 0.01-0.02 (1-2%)max_leverage: typically 1-10max_daily_loss: typically 0.05 (5%)hard_stop_loss: typically 0.10 (10%)
Step 2: Set system mode to CONFIRM_EXEC
jq '.mode="CONFIRM_EXEC"' system_config.json > tmp && mv tmp system_config.jsonStep 3: Enable trading
jq '.trading_enabled=true' system_config.json > tmp && mv tmp system_config.jsonStep 4: Disable kill switch
jq '.kill_switch=false' system_config.json > tmp && mv tmp system_config.jsonStep 5: Verify configuration
npm run check:configStep 6: Start Telegram bot
npm run telegramStep 7: Verify execution is allowed
Send /preflight command in Telegram. Expected output:
Execution Status: ✅ ALLOWED
Emergency Kill (instant):
jq '.kill_switch=true' system_config.json > tmp && mv tmp system_config.jsonThis blocks ALL executions immediately, even for pending orders.
Safe Disable (allows pending orders to complete):
jq '.trading_enabled=false' system_config.json > tmp && mv tmp system_config.jsonThis blocks new orders but allows pending confirmations.
Mode Downgrade (restricts operations):
jq '.mode="ORDER_PREP"' system_config.json > tmp && mv tmp system_config.jsonThis allows planning but blocks execution.
{
"mode": "CONFIRM_EXEC",
"trading_enabled": true,
"require_confirmation": true,
"kill_switch": false
}Always run /preflight before attempting execution. It reports:
- System mode
- Trading enabled status
- Kill switch status
- Allowed symbols
- Max leverage
- Max risk per trade
- Whether execution is currently possible
- Block reasons if any
Live execution requires all of:
- Phases 1–3 stable locally for ≥ 30 days with zero accidental orders
-
pytest tests/ -vgreen -
npm run typecheckgreen inbybit-trader/ - Risk parameters reviewed in configs/risk.yaml and bybit-trader/risk_config.json
- Bybit API key is Trade + Read only — never enable withdrawal
- API key is IP-restricted to the machine that will execute
-
bybit-trader/system_config.jsonflipped toCONFIRM_EXEC+trading_enabled=true+kill_switch=false -
BYBIT_TESTNET=falsetested with a minimum-size test order
VPS deployment becomes necessary only at Phase 5 (unattended stop management). Phase 1–4 is local-only.
The trading system can be deployed to a VPS for manual trading workflow (analysis + trade plan generation only). Execution commands are disabled.
- Ubuntu 20.04+ or similar Linux distribution
- Node.js 18+ and npm
- Git
- PM2 (process manager)
NEVER commit these to Git:
.envfiles (use.env.exampleas template)- API keys or secrets
logs/directorystate/directory.tokens.jsonfilesnode_modules/directorydist/directory
ALWAYS:
- Copy
.env.exampleto.envand fill in values - Set file permissions:
chmod 600 .env - Use environment variables for all secrets
/home/ubuntu/cryptotrading/
├── .env.example # Template for secrets
├── .gitignore # Ignores secrets, logs, state
├── deploy.sh # Deployment script
├── README.md # This file
├── app/ # Python analysis backend
├── bybit-trader/ # TypeScript control layer
│ ├── .env # Secrets (NEVER commit)
│ ├── .env.example # Template
│ ├── .gitignore # Ignores logs, state, .tokens.json
│ ├── node_modules/ # Ignored
│ ├── logs/ # Ignored
│ ├── package.json
│ └── src/
└── logs/ # Ignored
- Clone repository:
git clone https://github.com/BOJO1992/cryptotrading.git
cd cryptotrading- Configure environment:
# Copy example env file
cp bybit-trader/.env.example bybit-trader/.env
# Edit with your API keys (READ-ONLY for manual trading)
nano bybit-trader/.env- Run deployment script:
chmod +x deploy.sh
./deploy.shThe deploy script:
- Pulls latest changes from GitHub
- Runs
npm ciin bybit-trader/ - Runs type check
- Builds the project
- PM2 Setup (Manual Trading Mode):
Create PM2 ecosystem file:
nano ecosystem.config.jsAdd this content:
module.exports = {
apps: [{
name: 'bybit-trader',
script: './bybit-trader/src/cli.ts',
interpreter: 'node',
interpreter_args: '--loader tsx',
env: {
NODE_ENV: 'production',
BYBIT_TESTNET: 'true',
},
error_file: './logs/pm2-error.log',
out_file: './logs/pm2-out.log',
log_date_format: 'YYYY-MM-DD HH:mm:ss Z',
autorestart: true,
max_restarts: 10,
min_uptime: '10s',
}]
};Start with PM2:
pm2 start ecosystem.config.js
pm2 save
pm2 startupThe system operates in READ_ONLY mode for manual trading:
# Generate trade signal
cd bybit-trader
npm run cli -- decision --symbol BTCUSDT
# Output includes:
# - Direction (LONG/SHORT)
# - Entry price
# - Stop Loss
# - Take Profit
# - Position size
# - Risk %
# - MANUAL ORDER INPUT section for copying to exchangeExecution commands (confirm, execute) are disabled for manual trading workflow.
# Check PM2 status
pm2 status
# View logs
pm2 logs bybit-trader
# Restart
pm2 restart bybit-trader
# Stop
pm2 stop bybit-trader- System remains in READ_ONLY mode
- No automatic execution
- Trading must be done manually using the generated signals
- All API keys are READ-ONLY (no trade permissions)
- Logs and state files are excluded from Git
- Python 3.11+, type hints on public functions
- Pydantic v2 models at service boundaries
Decimalfor prices/quantities,np.ndarray[float64]for indicator arrays- UTC-aware datetimes via
app.utils.time_utils.utcnow()— neverdatetime.utcnow() - Structlog JSON logging,
service=<name>bound on every service - No bare
except:— catch specific exception types - New strategies extend
app.strategies.base.Strategy