Customer-side copy trading execution agent.
This repo is intentionally small. It does not contain alpha logic, trading-system database schemas, portfolio engines, or internal risk engines. It contains only the public-safe contracts and runtime pieces needed by a customer VPS agent:
copy.event.v1normalization.copy.command.v1execution command builders.- local idempotency.
- local guardrails.
- data_layer REST client for warmup/recovery/freshness checks.
- dry-run broker execution.
- Binance USDM request mapping.
- CLI diagnostics.
- SaaS command polling over outbound HTTPS.
- command signature verification.
- AI/operator support workflow in
AGENT_SKILL.md.
Development install:
pip install -e .Docker smoke:
docker build -f docker/Dockerfile -t bobby-copy-agent:local .
docker run --rm bobby-copy-agent:local copy-agent doctor --agent-id demo-agentDoctor:
copy-agent doctor --agent-id demo-agent --verboseDoctor with config:
copy-agent doctor --config examples/agent_config.example.json --verboseDry-run one command:
copy-agent dry-run-command examples/place_order_command.json \
--adapter dry-run \
--allowed-symbol ETHUSDT \
--allowed-venue BINANCE \
--max-notional 100Map to Binance USDM request without submitting:
copy-agent dry-run-command examples/place_order_command.json \
--adapter binance-usdm \
--allowed-symbol ETHUSDT \
--allowed-venue BINANCESign a local fixture command for smoke testing:
copy-agent sign-command examples/place_order_command.json \
--hmac-secret local-fixture-secretVerify a signed command:
copy-agent verify-command signed_command.json \
--hmac-secret local-fixture-secretPoll one batch from the SaaS command API and report results:
copy-agent run-once \
--config examples/agent_config.example.jsonRun continuously:
copy-agent run \
--config examples/agent_config.example.jsonSend one heartbeat:
copy-agent heartbeat \
--config examples/agent_config.example.jsonDiagnose a log file:
copy-agent diagnose-log ~/.copy_agent/agent.log --tail-lines 500Check data_layer contract:
copy-agent data-layer-check \
--base-url http://data_layer:8100 \
--symbol BTCUSDT \
--market crypto \
--provider binance \
--binance-market usdm \
--interval 15m \
--limit 5The customer agent should connect outbound to the SaaS server by HTTPS polling or WebSocket. Redis stream access is private-lab only and should not be exposed to customer VPS.
The SaaS server should:
- Consume
copy.event.v1from trading-system. - Apply investor subscription sizing and risk.
- Emit signed
copy.command.v1commands to the customer agent. - Store result reports from the agent.
The customer agent should:
- Verify signature and expiry.
- Deduplicate by
copy_command_id/idempotency_key. - Apply local guardrails.
- Execute through the customer's broker credentials.
- Report broker result back to SaaS.
The production transport is outbound HTTPS from the customer VPS to the SaaS server. The default contract is:
GET /api/agent/v1/commands?agent_id=...&limit=...POST /api/agent/v1/resultsPOST /api/agent/v1/heartbeat
GET /commands may return either:
{"commands": []}or a direct JSON array:
[]Authentication is separate from command signing. The agent sends a programmatic client API key to
the SaaS server. A typical key is generated by the SaaS client portal with an lk_live_... prefix,
shown to the investor once, and stored server-side only as a SHA-256 hash. The agent stores the raw
key locally because it must authenticate future outbound requests.
Default header:
Authorization: Bearer lk_live_...
Alternative X-API-Key style:
COPY_AGENT_AUTH_HEADER=X-API-Key
COPY_AGENT_AUTH_SCHEME=Core env vars:
COPY_AGENT_ID=customer-vps-agent-001
COPY_AGENT_SAAS_BASE_URL=https://copy-api.example.com
COPY_AGENT_API_KEY=lk_live_replace_with_key_shown_once
COPY_AGENT_REQUIRE_SAAS_AUTH=true
COPY_AGENT_STATE_PATH=~/.copy_agent/idempotency.json
COPY_AGENT_ADAPTER=dry-run
COPY_AGENT_BROKER_MODE=dry-run
COPY_AGENT_LIVE_SUBMIT=false
COPY_AGENT_POLL_LIMIT=10
COPY_AGENT_POLL_INTERVAL_SECONDS=2
COPY_AGENT_REQUIRE_SIGNATURE=true
COPY_AGENT_HMAC_SECRET=replace_with_command_signature_secret
COPY_AGENT_ALLOWED_SYMBOLS=BTCUSDT,ETHUSDT
COPY_AGENT_ALLOWED_VENUES=BINANCE
COPY_AGENT_MAX_NOTIONAL=100
COPY_AGENT_CAPITAL=1000
COPY_AGENT_ALLOCATION_PCT=0.1
COPY_AGENT_LEVERAGE=1
COPY_AGENT_QUANTITY_MULTIPLIER=1
COPY_AGENT_MARGIN_MODE=ISOLATEDThe SaaS server should already transform source trading-system events into investor-specific,
sized copy.command.v1 commands. The customer agent still applies a final local policy before the
broker call:
COPY_AGENT_CAPITALCOPY_AGENT_ALLOCATION_PCTCOPY_AGENT_LEVERAGECOPY_AGENT_MAX_NOTIONALCOPY_AGENT_MAX_QUANTITYCOPY_AGENT_QUANTITY_MULTIPLIERCOPY_AGENT_MARGIN_MODE
This local policy is an extra safety overlay, not a replacement for SaaS-side subscription sizing.
It can scale quantities and cap command max_notional. It does not fabricate prices for MARKET
orders; if a strategy needs strict notional control, SaaS should send a reference price or a limit
order and the agent should keep COPY_AGENT_MAX_NOTIONAL conservative.
The API key authenticates the agent to the SaaS API. The command signature protects each command payload from tampering or replay. Do not reuse the API key as the command signing secret.
First release signature algorithm:
signature_algorithm=hmac-sha256- canonical payload excludes
signatureandsignature_algorithm - JSON is sorted and compact before signing
- digest is URL-safe base64 without padding
When COPY_AGENT_REQUIRE_SIGNATURE=true, the agent rejects a command before broker execution if:
signature_algorithmis missing or unsupported;signatureis missing;COPY_AGENT_HMAC_SECRETis missing;- payload verification fails;
- command expiry fails local guard validation.
The HMAC path is intentionally simple for the first release. A future Ed25519 mode can be added without changing the command transport shape: SaaS signs with a private key and the public agent verifies with a public verify key.
The agent includes a small direct Binance USD-M Futures REST client so testnet/live submission does
not depend on python-binance.
Required env for testnet:
COPY_AGENT_ADAPTER=binance-usdm
COPY_AGENT_BROKER_MODE=testnet
COPY_AGENT_LIVE_SUBMIT=true
COPY_AGENT_BINANCE_API_KEY=...
COPY_AGENT_BINANCE_API_SECRET=...
COPY_AGENT_ALLOWED_SYMBOLS=BTCUSDT,ETHUSDT
COPY_AGENT_MAX_NOTIONAL=25
COPY_AGENT_REQUIRE_SIGNATURE=true
COPY_AGENT_HMAC_SECRET=...Connectivity/account check:
copy-agent test-broker \
--config examples/agent_config.example.json \
--broker binance-usdm \
--testnet \
--query-accountPlace and immediately cancel a tiny far-from-market LIMIT order:
copy-agent test-broker \
--config examples/agent_config.example.json \
--broker binance-usdm \
--testnet \
--place-cancel-smoke \
--confirm TESTNET_ORDER \
--symbol BTCUSDT \
--quantity 0.001 \
--price 1Automated smoke tests should use LIMIT orders far from market by default. Do not use MARKET orders for automated broker smoke unless a human explicitly approves that specific run.
Live is blocked by default. To enable live Binance USDM, all of these must be true:
COPY_AGENT_ADAPTER=binance-usdmCOPY_AGENT_BROKER_MODE=liveCOPY_AGENT_LIVE_SUBMIT=trueCOPY_AGENT_LIVE_CONFIRM=I_UNDERSTAND_THIS_SUBMITS_LIVE_ORDERSCOPY_AGENT_REQUIRE_SIGNATURE=trueCOPY_AGENT_ALLOWED_SYMBOLSis non-emptyCOPY_AGENT_MAX_NOTIONALis set- Binance API key/secret are present
- local
HALTfile does not exist
Preflight:
copy-agent live-preflight \
--config examples/agent_config.example.jsonPreflight with broker account/open-order check:
copy-agent live-preflight \
--config examples/agent_config.example.json \
--check-brokerEmergency halt:
mkdir -p ~/.copy_agent
touch ~/.copy_agent/HALTWhen the halt file exists, the agent rejects new commands before broker execution. Remove it only after the operator understands the current broker state.
- A technician should never ask for or store customer broker secrets outside the customer's VPS.
- Prefer screen share or guided terminal commands where the customer enters credentials locally.
- Use testnet/sandbox first, then a tiny live smoke only after explicit customer approval.
- Do not grant withdrawal permission to exchange API keys.
- Start with symbol allowlists and low notional caps.
- Keep logs useful but redact API keys, secrets, signatures, and raw account identifiers where possible.
- SaaS API keys (
lk_live_...) should be revocable from the client portal and stored server-side only as SHA-256 hashes. - Command signing secrets/public keys should be rotated separately from API keys.
The agent uses the same market-data integration standard as the rest of the Bobby stack:
- REST for warmup, latest-state recovery, diagnostics, and freshness checks.
- Redis Pub/Sub only as an optional/private-lab streaming transport.
- No direct Binance/DNSE/vnstock/OKX market-data connections when running inside the stack.
Client module:
from copy_agent.data_layer import DataLayerClient
client = DataLayerClient(base_url="http://data_layer:8100")
health = client.health()
warmup = client.warmup_ohlcv("crypto", "BTCUSDT", interval="15m", limit=500, provider="binance")
latest = client.latest_trade("binance", "BTCUSDT", market="usdm", allow_last_snapshot=True)
freshness = client.validate_freshness(latest, max_age_seconds=5)VN example:
warmup = client.warmup_ohlcv("vn_stock", "FPT", interval="15m", limit=500)
quote = client.latest_vn_quote("FPT", allow_last_snapshot=True)- Dry-run is the default.
- Binance adapter does not submit unless constructed with
live_submit=True. - DNSE adapter is a stub until endpoint semantics are confirmed.
- Local idempotency persists to
~/.copy_agent/idempotency.jsonby default. - Result-report failure does not replay broker execution; the local idempotency key remains marked.
AGENT_SKILL.md is the short operational playbook for an AI support agent or technician. It covers:
- safe setup rules;
- doctor/diagnose commands;
- Binance testnet smoke;
- live preflight;
- emergency HALT file;
- common error meanings without exposing secrets.