agentleFS
gravity-technologiesgravity-technologies/grvt-skills/skills/perpetual-trading/SKILL.md

perpetual-trading

Skill4 starsChanged 8 months agoMITView on GitHub

Trade perpetual futures on GRVT exchange using the grvt-pysdk. Use when the user wants to place, cancel, or manage perpetual orders.

  • Reads credentials
  • Installs packages

The file

We split this skill into the rules it gives an agent

6 rules. About 0 are common, 4 are distinctive, and 2 are very specific to this project.

  • ≈0

    Common · estimate

    Many files like this have a rule of this kind.

  • ≈4

    Distinctive · estimate

    This author's own choices. Worth a look.

  • 2

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Distinctive:

    Use this skill when the user wants to:

    line 10
  2. Distinctive:

    Place buy/sell orders on GRVT perpetual markets

    line 11
  3. Distinctive:

    Check order status or open orders

    line 14
  4. Very specific:

    All orders on perpetuals require EIP-712 signing — the SDK handles this automatically using GRVT_PRIVATE_KEY

    line 355
  5. Very specific:

    Use GrvtEnv.TESTNET for testing before moving to GrvtEnv.PRODUCTION

    line 357
  6. Distinctive:

    Always confirm the user wants to proceed before placing real orders on production

    line 361

≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

Perpetual Trading on GRVT

When to Use

Use this skill when the user wants to:

  • Place buy/sell orders on GRVT perpetual markets
  • Cancel orders (single or all)
  • Set take-profit / stop-loss orders
  • Check order status or open orders
  • View order history or fill history

Prerequisites

pip install grvt-pysdk

GRVT has two types of API keys. Set the environment variables for the ones you need:

# Required for this skill — Trading API Key credentials
export GRVT_TRADING_API_KEY="<Trading API Key from GRVT exchange UI>"
export GRVT_TRADING_PRIVATE_KEY="<private key associated with Trading API Key>"

# Optional — Funding API Key credentials (for funding account transfers)
export GRVT_FUNDING_API_KEY="<Funding API Key from GRVT exchange UI>"
export GRVT_FUNDING_PRIVATE_KEY="<private key associated with Funding API Key>"

export GRVT_ENV="testnet"  # or "prod"

Both key types can be created at:

This skill uses GRVT_TRADING_API_KEY and GRVT_TRADING_PRIVATE_KEY.

SDK Setup

Login to auto-detect trading_account_id and funding_account_address:

import os
import requests
from pathlib import Path
from pysdk.grvt_ccxt import GrvtCcxt
from pysdk.grvt_ccxt_env import GrvtEnv

# Load .env file if present
env_file = Path(".env")
if env_file.exists():
    for line in env_file.read_text().splitlines():
        line = line.strip()
        if line and not line.startswith("#") and "=" in line:
            key, _, value = line.partition("=")
            os.environ.setdefault(key.strip(), value.strip())

env = os.getenv("GRVT_ENV", "testnet")
base = "edge.testnet.grvt.io" if env == "testnet" else "edge.grvt.io"

# Login returns: sub_account_id, funding_account_address
login_resp = requests.post(
    f"https://{base}/auth/api_key/login",
    json={"api_key": os.getenv("GRVT_TRADING_API_KEY")},
).json()
trading_account_id = login_resp["sub_account_id"]
funding_account_address = login_resp["funding_account_address"]

api = GrvtCcxt(
    env=GrvtEnv.TESTNET if env == "testnet" else GrvtEnv.PRODUCTION,
    parameters={
        "api_key": os.getenv("GRVT_TRADING_API_KEY"),
        "trading_account_id": trading_account_id,
        "private_key": os.getenv("GRVT_TRADING_PRIVATE_KEY"),
    },
)

Symbol Format

Perpetual symbols follow the pattern: {BASE}_{QUOTE}_Perp

Examples: BTC_USDT_Perp, ETH_USDT_Perp, SOL_USDT_Perp

To discover available perpetuals:

markets = api.fetch_all_markets()
perps = [m for m in markets if m["kind"] == "PERPETUAL"]
for p in perps:
    print(p["instrument"])  # e.g. "BTC_USDT_Perp"

Placing Orders

Limit Order

order = api.create_order(
    symbol="BTC_USDT_Perp",
    order_type="limit",
    side="buy",        # "buy" for long, "sell" for short
    amount=0.01,       # Size in base currency (BTC)
    price=90000,       # Limit price in quote currency (USDT)
)

Market Order

order = api.create_order(
    symbol="BTC_USDT_Perp",
    order_type="market",
    side="buy",
    amount=0.01,
    price=0,  # Price is ignored for market orders
)

Order Parameters

Additional parameters can be passed via params:

order = api.create_order(
    symbol="BTC_USDT_Perp",
    order_type="limit",
    side="buy",
    amount=0.01,
    price=90000,
    params={
        "client_order_id": 12345,          # Optional custom ID
        "time_in_force": "GTC",            # GTC (default), IOC, FOK, AON
        "post_only": True,                 # Maker-only order
        "reduce_only": True,               # Only reduce existing position
    },
)

Time-in-force options:

  • GTC — Good Till Cancel (default)
  • IOC — Immediate or Cancel (fill what you can, cancel rest)
  • FOK — Fill or Kill (all or nothing)
  • AON — All or None

Take-Profit / Stop-Loss (Trigger Orders)

The SDK's create_order does not support trigger orders natively. Use this helper to place real TP/SL trigger orders that only activate when the trigger price is reached:

from pysdk.grvt_ccxt_utils import get_grvt_order, get_order_payload
from pysdk.grvt_ccxt_env import get_grvt_endpoint

def create_trigger_order(api, symbol, side, amount, limit_price, trigger_type, trigger_price, trigger_by="MARK", reduce_only=True, private_key=None):
    """Place a trigger order (TP/SL).

    Args:
        api: GrvtCcxt instance
        symbol: e.g. "BTC_USDT_Perp"
        side: "buy" or "sell"
        amount: order size
        limit_price: execution price after trigger
        trigger_type: "TAKE_PROFIT" or "STOP_LOSS"
        trigger_price: price that activates the order
        trigger_by: "MARK" (default), "INDEX", "LAST", or "MID"
        reduce_only: True to only reduce position (default)
        private_key: hex private key string
    """
    order = get_grvt_order(
        sub_account_id=api.get_trading_account_id(),
        symbol=symbol,
        order_type="limit",
        side=side,
        amount=amount,
        limit_price=limit_price,
        order_duration_secs=24 * 60 * 60,
        params={"reduce_only": reduce_only},
    )
    payload = get_order_payload(order, private_key=private_key, env=api.env, instruments=api.markets)

    # Inject trigger metadata (unsigned — not part of EIP-712 signature)
    payload["order"]["metadata"]["trigger"] = {
        "trigger_type": trigger_type,
        "tpsl": {
            "trigger_by": trigger_by,
            "trigger_price": str(trigger_price),
            "close_position": False,
        },
    }

    path = get_grvt_endpoint(api.env, "CREATE_ORDER")
    return api._auth_and_post(path, payload)


# Example: Long BTC position, set SL at 68000 (trigger) / 67900 (limit)
result = create_trigger_order(
    api, "BTC_USDT_Perp", side="sell", amount=0.01,
    limit_price=67900, trigger_type="STOP_LOSS", trigger_price=68000,
    private_key=os.getenv("GRVT_TRADING_PRIVATE_KEY"),
)

# Example: Long BTC position, set TP at 75000 (trigger) / 74900 (limit)
result = create_trigger_order(
    api, "BTC_USDT_Perp", side="sell", amount=0.01,
    limit_price=74900, trigger_type="TAKE_PROFIT", trigger_price=75000,
    private_key=os.getenv("GRVT_TRADING_PRIVATE_KEY"),
)

Trigger types: TAKE_PROFIT, STOP_LOSS Trigger by: MARK (default), INDEX, LAST, MID

The order stays dormant until the trigger price is reached, then becomes a limit order at limit_price.

Cancelling Orders

# Cancel by client_order_id (preferred — works immediately after create_order)
api.cancel_order(id=None, params={"client_order_id": "4224496967"})

# Cancel by order_id (get from fetch_open_orders first)
api.cancel_order(id="0x01010105033f70be000000000515217b")

# Cancel all open orders
api.cancel_all_orders()

# Cancel all perpetual orders only
api.cancel_all_orders(params={"kind": "PERPETUAL"})

Querying Orders

# Get a specific order
order = api.fetch_order(id="<order_id>")

# Get all open orders
open_orders = api.fetch_open_orders(symbol="BTC_USDT_Perp")

# Get order history
history = api.fetch_order_history()

# Get fill/trade history
fills = api.fetch_my_trades(symbol="BTC_USDT_Perp", limit=50)

Order Response

create_order returns the order payload. Note: order_id in the create response is a placeholder (0x00). To get the real order ID, use fetch_open_orders or track by client_order_id.

Key fields in the response:

  • order_id — Exchange-assigned order ID (use from fetch_open_orders, not create_order)
  • metadata.client_order_id — Your custom ID (auto-generated if not provided)
  • sub_account_id — Trading account
  • is_market — Whether it's a market order
  • time_in_force — GOOD_TILL_TIME, IMMEDIATE_OR_CANCEL, FILL_OR_KILL, ALL_OR_NONE
  • legs[0].instrument — Symbol (e.g. BTC_USDT_Perp)
  • legs[0].size — Order size
  • legs[0].limit_price — Limit price
  • legs[0].is_buying_asset — true for buy, false for sell

Position Config (Margin Type & Leverage)

The SDK does not natively support set_position_config. Use this helper to switch between CROSS and ISOLATED margin, and to set leverage per instrument.

import time, random
from pysdk.grvt_ccxt_utils import get_EIP712_domain_data
from eth_account import Account
from eth_account.messages import encode_typed_data

def set_position_config(api, instrument, margin_type, leverage, private_key):
    """Set margin type and leverage for a perpetual instrument.

    Args:
        api: GrvtCcxt instance (authenticated)
        instrument: e.g. "BTC_USDT_Perp"
        margin_type: "ISOLATED" or "CROSS"
        leverage: integer 1-50
        private_key: hex private key string
    """
    MARGIN_TYPE_MAP = {"ISOLATED": 1, "CROSS": 2}
    LEVERAGE_MULTIPLIER = 1_000_000

    market = api.fetch_market(instrument)
    instrument_hash = market["instrument_hash"]
    sub_account_id = int(api._trading_account_id)
    expiration_ns = int((time.time() + 86400) * 1_000_000_000)
    nonce = random.randint(1, 2**32 - 1)

    domain_data = get_EIP712_domain_data(api.env)
    types = {
        "SetSubAccountPositionMarginConfig": [
            {"name": "subAccountID", "type": "uint64"},
            {"name": "asset", "type": "uint256"},
            {"name": "marginType", "type": "uint8"},
            {"name": "leverage", "type": "int32"},
            {"name": "nonce", "type": "uint32"},
            {"name": "expiration", "type": "int64"},
        ]
    }
    message_data = {
        "subAccountID": sub_account_id,
        "asset": instrument_hash,
        "marginType": MARGIN_TYPE_MAP[margin_type],
        "leverage": leverage * LEVERAGE_MULTIPLIER,
        "nonce": nonce,
        "expiration": expiration_ns,
    }

    message = encode_typed_data(domain_data, types, message_data)
    signed = Account.sign_message(message, private_key)
    signer = Account.from_key(private_key)

    payload = {
        "sub_account_id": str(sub_account_id),
        "instrument": instrument,
        "margin_type": margin_type,
        "leverage": str(leverage),
        "signature": {
            "signer": signer.address,
            "r": hex(signed.r),
            "s": hex(signed.s),
            "v": signed.v,
            "expiration": str(expiration_ns),
            "nonce": nonce,
        },
    }

    env_name = api.env.value  # "testnet" or "prod"
    base = "trades.testnet.grvt.io" if "testnet" in env_name else "trades.grvt.io"
    url = f"https://{base}/full/v1/set_position_config"
    return api._auth_and_post(url, payload)


# Usage:
# set_position_config(api, "BTC_USDT_Perp", "ISOLATED", 20, os.getenv("GRVT_PRIVATE_KEY"))
# set_position_config(api, "BTC_USDT_Perp", "CROSS", 10, os.getenv("GRVT_PRIVATE_KEY"))

Constraints:

  • Leverage must be a whole number between 1 and 50
  • Cannot change margin type while you have an open position for that instrument
  • Close the position first, then switch margin type

Important Notes

  • All orders on perpetuals require EIP-712 signing — the SDK handles this automatically using GRVT_PRIVATE_KEY
  • The SDK auto-manages authentication sessions and refreshes cookies before expiry
  • Use GrvtEnv.TESTNET for testing before moving to GrvtEnv.PRODUCTION
  • Prices use 9 decimal precision internally; the SDK handles conversion
  • Minimum notional is 100 USDT — ensure price * amount >= 100
  • create_order returns order_id: "0x00" — use metadata.client_order_id to track/cancel
  • Always confirm the user wants to proceed before placing real orders on production

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.