Language / ภาษา

Fix-Cash Grid Bot — deploy and operate

go-deribit-fix-cash-grid automates a grid of limit orders on Deribit. This guide covers what it does and — just as importantly — what it does not do, how to configure and deploy it, how to confirm it is genuinely trading, and how to stop it without leaving exposure behind.

1. What this bot does — and what it does not

The bot will NOT open your position for you

This is the single most common misunderstanding, and it is the reason this warning is the first thing in the guide. The bot automates placing the grid's limit orders. It does not place an entry for you. It never sends a market buy or market sell to establish a starting position. If you deploy it expecting it to enter the market on your behalf, you will wait for an entry that never comes.

On startup the bot calculates what position its formula implies, compares that to what your account actually holds, and then does nothing about the difference. It logs the gap and leaves the decision to you — deliberately, because automatic market orders on restart previously caused large unwanted trades.

But it does build exposure over time — know the mechanism

“It does not open a position” is true about entry, not about exposure. The bot's sell-side grid orders are ordinary limit sells that are not reduce-only, so as price rises through the grid those sells fill and a short position builds up, one level at a time. Its buy-side orders are always reduce-only and are only placed when a short already exists — so the bot can add to and reduce a short, but it can never open a long. Started on a flat account, it places sells only, and nothing at all happens until price rises into the grid.

Put plainly, the division of responsibility is this:

  • You are responsible for: funding the account, deciding the grid boundaries and leverage, and establishing or adjusting any starting position you want the bot to work around.
  • The bot is responsible for: placing limit orders at every grid level, cancelling and re-placing the grid after each fill, and pausing at the boundaries.
  • Nobody is responsible for closing your position automatically — with one exception: if price falls below the lower boundary the bot closes the whole position at market and pauses.

2. Prerequisites

  1. A Deribit account with API key and secret, with trade and read permissions. Use test.deribit.com first.
  2. A Redis instance the bot can reach. Redis holds the live configuration and the bot's order state, so it is not optional.
  3. A licence: either a SUBSCRIPTION_UUID or a TBS_ACTIVATION_TOKEN. Setting both is a fatal error at startup. Internal fund deployments use DEPLOYMENT_MODE=k8s_fund instead, which skips licence enforcement.
  4. Kubernetes access to the target namespace, if you are deploying to the cluster rather than running locally.
  5. A decision about the starting position — see section 1. Deploying without having decided this is the most common way to waste a day.

3. Configuration

Configuration comes from two places. Environment variables are fixed at startup and set identity and connections. The trading parameters live in Redis and are re-read while the bot runs, so they can be changed without a restart.

Environment variables — the ones that matter:

DERIBIT_API_KEYRequiredDeribit API key. The bot exits immediately at startup if this is empty.
DERIBIT_API_SECRETRequiredDeribit API secret. Also fatal if empty. Never commit this to git — supply it from a Kubernetes secret.
DERIBIT_TESTNETDefault: trueChooses testnet or live Deribit. The default is TRUE, meaning testnet. You must set it to false explicitly to trade live — check this value twice, in both directions.
BOT_IDDefault: grid-bot-1Identifies this bot. It selects the Redis config key, so two bots sharing a BOT_ID share a configuration — give every instance its own.
REDIS_URLDefault: redis://localhost:6379Redis connection string. Note that the bot reads REDIS_URL only — it ignores REDIS_HOST and REDIS_PORT, which some older manifests set instead. If you set only those, the bot silently falls back to localhost and will not find its config.
INSTRUMENTDefault: BTC-PERPETUALThe Deribit instrument to trade.
DEPLOYMENT_MODEDefault: localLicence mode. The value k8s_fund skips retail licence enforcement for internal fund deployments.
LOG_LEVELDefault: infodebug, info, warn or error. Use debug when you need to see individual order placements.

trade_enabled is not an environment variable

The master trading switch lives in Redis, not in the environment, and it defaults to off. Operators routinely look for a TRADE_ENABLED env var, do not find one, and conclude something is broken. Nothing is broken — the flag is set through fm-portal or the platform API, and until it is on the bot boots healthy and stays idle.

Trading parameters — stored in Redis under the key config:<BOT_ID>:grid:

trade_enabledDefault: falseMaster switch. False means the bot runs but never connects to Deribit and places no orders.
grid.upper_boundaryDefault: 100000Top of the grid in USD. Above it the bot pauses and holds the position — it does not close.
grid.lower_boundaryDefault: 80000Bottom of the grid in USD. Below it the bot closes the entire position at market, then pauses. This is the only automatic close in the bot — set it deliberately.
grid.base_spacing_usdDefault: 5000Spacing between levels in USD, for arithmetic mode. The bot rejects configurations producing more than 1000 levels or an order size below 10 USD.
position.leverageDefault: 10Valid range 1 to 50. See the safety section — leverage is what decides whether a rally can liquidate you.
position.base_contracts_per_gridDefault: 5How many contracts each grid level places. Must be greater than zero.
execution.post_onlyDefault: falseRejects an order rather than letting it fill as taker. Keeps fees at maker rate, at the cost of orders sometimes failing to be placed.
execution.max_open_ordersDefault: 00 means unlimited. A positive value enables a sliding window that cancels the farthest resting orders as price moves.
risk.max_position_contractsDefault: 5000Hard cap on position size. This guard is always active.

Two risk guards are OFF by default

max_drawdown and min_margin_ratio both ship disabled, by deliberate decision, so that manual withdrawals do not trip an automatic pause. Do not assume you are protected by them. The position cap and the boundary rules are the guards that are actually running.

4. Deploying

Staging first, always

Deploy and verify on staging before production, every time. Staging uses Deribit testnet, so a mistake there costs nothing.

Deployment runs through Jenkins pipelines held in the repository, split by environment. CI builds and pushes the image; CD deploys it.

.kubernetes/
├── pipeline/
│   ├── staging/      Jenkinsfile.ci  Jenkinsfile.cd
│   └── production/   Jenkinsfile.ci  Jenkinsfile.cd
│                     Jenkinsfile.onboard  Jenkinsfile.rollback
├── overlays/         staging/  production/
└── templates/        deployment.yaml  service.yaml  redis.yaml
Pipeline and manifest layout in the bot repository.

The deployment is named grid-bot. Namespaces differ by environment and, in production, by client — confirm which namespace you are targeting before running anything.

# check what is actually running
kubectl get pods -n <namespace>
kubectl rollout status deployment/grid-bot -n <namespace>
kubectl logs -f deployment/grid-bot -n <namespace>
`get pods` shows effect — what is really running. A deployment object existing is only intent.

Never run more than one replica

The deployment is fixed at one replica with a Recreate strategy for a reason: two instances would drive the same Deribit account and the same Redis order state at the same time, duplicating orders and corrupting the bot's view of what it has placed. There is no horizontal scaling for this bot.

5. Confirming it actually started trading

“Grid bot is running” does not mean it is trading

The bot prints that line even when trading is disabled and it is sitting idle with no exchange connection. The health endpoints behave the same way: /health/ready returns green while idle, because being idle is a correct state. If you check only health, you cannot tell a trading bot from an idle one.

Check in this order. Each step rules out a different failure.

  1. The pod is running and not restarting — kubectl get pods.
  2. Trading is engaged — query /api/v1/status and read the engaged field. If it is false, not_engaged_reason tells you why: trade_disabled, exchange_auth_failed, or config_validation_failed.
  3. The grid was placed — look for the log line “Initial grid orders placement complete”, which reports how many short and long orders were placed. This is the single best confirmation that the grid is live.
  4. The orders exist on Deribit — confirm on the exchange or through fm-portal, not only in the logs. A log line says the bot tried; the order book says it succeeded.

Log lines that mean something is wrong, or at least worth understanding:

  • “Price below lower boundary, waiting for price to enter grid zone” — the bot placed no orders at all and is waiting. Not a crash, but nothing is happening.
  • “Skipping close orders — no existing short position to reduce” — expected on a flat account. The bot has placed sells only, which is exactly the behaviour described in section 1.
  • “AUTO-STOP: next short order would push projected liq below upper boundary” — the liquidation guard stopped adding shorts. Investigate before overriding.
  • “Position delta detected — manual adjustment may be needed” — the bot's target and your actual position disagree. It will not fix this for you.

6. Watching it run

Day-to-day observation happens in fm-portal, which shows the bot's status, config, grid orders and open position without needing cluster access. The portal is documented separately — see the fm-portal operator guide for the screen-by-screen walkthrough and for what each control does.

In a healthy bot the grid is not static. After each fill the bot cancels the grid and re-places it, logging “Grid refresh complete”. If the market has moved and the orders have not, that is your signal to investigate.

7. Stopping it safely

replicas=0 does NOT mean “not trading”

Scaling the deployment to zero stops the automation. It does not touch your exposure. Every resting grid order stays live on Deribit and any open position stays open, fully exposed to the market, with nothing now managing it. This has genuinely confused people here — a bot at replicas=0 was read as flat when it was holding a large short. Deployment state is not trading state. The only place that answers “what am I holding” is Deribit itself.

This is not an accident of configuration — the bot's shutdown path genuinely contains no order cancellation. On receiving a shutdown signal it stops its background work and disconnects the WebSocket. It never calls cancel-all, and Deribit's cancel-on-disconnect feature is not enabled. So stopping the process, deleting the pod, scaling to zero, or pressing stop in fm-portal all leave the same thing behind: live orders and live exposure.

To actually stop trading, do it in this order:

  1. Pause the bot, so it stops re-placing the grid while you work.
  2. Cancel the orders. Confirm on Deribit that the order book entries are gone — the request being accepted is not the same as the orders being cancelled.
  3. Decide about the position. Closing it realises the profit or loss immediately. Keeping it means you now hold unmanaged exposure — that may be exactly what you want, but it must be a decision, not an oversight.
  4. Only now stop or scale down the process.

Restarting rebuilds the grid from scratch

On start the bot cancels every open order on the instrument and re-places the grid around the current market price. So a restart does clean up old orders — but only when it starts, not when it stops. Between stopping and starting, those orders are live and unmanaged.

8. Safety

This bot trades real money

On production it is connected to a funded Deribit account. Configuration mistakes are not caught by a review step before they reach the market — the bot re-reads its config within seconds and acts on it.

A rally CAN liquidate a short — unless the position is fully collateralised

For an inverse contract held at exactly 1x — collateral matching position size — equity and maintenance margin both scale with 1/price, so the margin ratio does not change as price rises and no liquidation price exists. That invariance is the whole reason the structure is considered safe, and it holds ONLY at full collateralisation. At around 10x the position is roughly one tenth collateralised, the invariance is gone, and a liquidation price exists above the market. Never quote the “a rally cannot liquidate a short” rule without its precondition — that mistake has been made repeatedly here, and the default leverage in this bot is 10.

  • Check DERIBIT_TESTNET before every production deploy. It defaults to testnet, which is safe, but a copied config can flip it either way.
  • Set the lower boundary knowing it triggers a market close of the entire position, not a pause.
  • Do not raise leverage to make the numbers look better. Leverage is the variable that creates a liquidation price.
  • Never assume a stopped bot is a flat account. Confirm on Deribit.
  • For the live production fix-cash bot specifically: read-only inspection is fine, but any action requires explicit authorisation from the owner. Do not stop, scale, or reconfigure it on your own initiative.