Build a GPT-4 crypto trading assistant with Purple Flea
Complete, runnable examples for OpenAI function calling — open positions, execute swaps, and run casino operations directly from a GPT-4o chat loop.
View API docs →GPT-4 and GPT-4o tool use with Purple Flea
OpenAI's tools parameter (formerly functions) lets GPT-4, GPT-4o, and GPT-4o-mini decide autonomously when to call your Purple Flea integrations. The model returns a structured tool_calls array; your code dispatches to the correct Purple Flea endpoint and feeds the result back as a tool role message.
GPT-4o
Best reasoning + fastest. Recommended for trading decisions and multi-step agent loops.
GPT-4o-mini
10× cheaper. Use for high-frequency polling, balance checks, and simple swaps.
Parallel calls
GPT-4o can invoke multiple tools simultaneously in a single turn — check balance + check market + open position in one round-trip.
Streaming
Stream tool call deltas for real-time feedback as the model constructs its arguments.
A GPT-4o trading assistant — full working code
This is a complete, runnable trading assistant. It accepts natural-language commands and dispatches to Purple Flea Trading (open/close positions), Purple Flea Wallet (swaps), and Purple Flea Casino (coin flips).
import json
import requests
from openai import OpenAI
client = OpenAI() # reads OPENAI_API_KEY from env
PURPLE_FLEA_KEY = "sk_live_..."
HEADERS = {"Authorization": f"Bearer {PURPLE_FLEA_KEY}"}
# ── Tool definitions ────────────────────────────────────────────
TOOLS = [
{
"type": "function",
"function": {
"name": "get_balance",
"description": "Get the agent wallet balance for a specific chain and token.",
"parameters": {
"type": "object",
"properties": {
"chain": {"type": "string", "enum": ["ethereum", "base", "arbitrum", "solana"]},
"token": {"type": "string", "description": "Token symbol, e.g. USDC, ETH"}
},
"required": ["chain", "token"]
}
}
},
{
"type": "function",
"function": {
"name": "get_market_price",
"description": "Get the current mid price and 24h change for a perp market.",
"parameters": {
"type": "object",
"properties": {
"market": {"type": "string", "description": "Market ID, e.g. ETH-PERP"}
},
"required": ["market"]
}
}
},
{
"type": "function",
"function": {
"name": "trading_open_position",
"description": "Open a perpetual futures position on Purple Flea Trading.",
"parameters": {
"type": "object",
"properties": {
"market": {"type": "string"},
"side": {"type": "string", "enum": ["long", "short"]},
"size": {"type": "number", "description": "USD notional"},
"leverage": {"type": "integer", "minimum": 1, "maximum": 50},
"take_profit": {"type": "number", "description": "TP price (optional)"},
"stop_loss": {"type": "number", "description": "SL price (optional)"}
},
"required": ["market", "side", "size"]
}
}
},
{
"type": "function",
"function": {
"name": "trading_close_position",
"description": "Close an open position by position ID.",
"parameters": {
"type": "object",
"properties": {
"position_id": {"type": "string"}
},
"required": ["position_id"]
}
}
},
{
"type": "function",
"function": {
"name": "wallet_swap",
"description": "Execute a best-rate cross-chain token swap via Purple Flea Wallet.",
"parameters": {
"type": "object",
"properties": {
"from_chain": {"type": "string"},
"to_chain": {"type": "string"},
"from_token": {"type": "string"},
"to_token": {"type": "string"},
"amount": {"type": "string"}
},
"required": ["from_chain", "to_chain", "from_token", "to_token", "amount"]
}
}
},
{
"type": "function",
"function": {
"name": "casino_flip",
"description": "Flip a provably fair coin on Purple Flea Casino.",
"parameters": {
"type": "object",
"properties": {
"amount": {"type": "number"},
"side": {"type": "string", "enum": ["heads", "tails"]}
},
"required": ["amount", "side"]
}
}
}
]
# ── Tool dispatch ────────────────────────────────────────────────
def dispatch(name, args):
if name == "get_balance":
r = requests.get(f"https://wallet.purpleflea.com/v1/balance",
headers=HEADERS, params=args)
return r.json()
elif name == "get_market_price":
r = requests.get(f"https://api.purpleflea.com/v1/markets/{args['market']}/price",
headers=HEADERS)
return r.json()
elif name == "trading_open_position":
r = requests.post("https://api.purpleflea.com/v1/trading/open",
headers=HEADERS, json=args)
return r.json()
elif name == "trading_close_position":
r = requests.post("https://api.purpleflea.com/v1/trading/close",
headers=HEADERS, json=args)
return r.json()
elif name == "wallet_swap":
r = requests.post("https://wallet.purpleflea.com/v1/wallet/swap",
headers=HEADERS, json=args)
return r.json()
elif name == "casino_flip":
r = requests.post("https://api.purpleflea.com/api/v1/flip",
headers=HEADERS, json=args)
return r.json()
return {"error": "unknown tool"}
# ── Agent loop ───────────────────────────────────────────────────
def run_agent(user_message: str):
messages = [
{"role": "system", "content": "You are a crypto trading assistant powered by Purple Flea. Execute operations accurately and confirm before placing large trades."},
{"role": "user", "content": user_message}
]
while True:
response = client.chat.completions.create(
model="gpt-4o",
messages=messages,
tools=TOOLS,
tool_choice="auto"
)
msg = response.choices[0].message
if not msg.tool_calls:
return msg.content # final text response
messages.append(msg)
for tc in msg.tool_calls:
args = json.loads(tc.function.arguments)
result = dispatch(tc.function.name, args)
messages.append({
"role": "tool",
"tool_call_id": tc.id,
"content": json.dumps(result)
})
# Usage
print(run_agent("Check my USDC balance on Base, get the ETH-PERP price, and open a $500 long at 5x leverage."))
Parallel tool calls — do three things at once
GPT-4o supports parallel function calling: when the model determines that multiple tools can be invoked without depending on each other's results, it returns all of them in the same turn. This is a significant latency win for agent operations.
The prompt "Check my USDC balance on Base, get the ETH-PERP price, and open a $500 long" will cause GPT-4o to emit three tool_calls simultaneously — get_balance, get_market_price, and (after receiving those results) trading_open_position.
# GPT-4o may return multiple tool_calls in one message
import concurrent.futures
def handle_parallel_tool_calls(msg, messages):
tool_calls = msg.tool_calls
if not tool_calls:
return messages
# Run all tool calls concurrently
def execute_one(tc):
args = json.loads(tc.function.arguments)
result = dispatch(tc.function.name, args)
return (tc.id, result)
with concurrent.futures.ThreadPoolExecutor() as ex:
futures = [ex.submit(execute_one, tc) for tc in tool_calls]
results = [f.result() for f in concurrent.futures.as_completed(futures)]
messages.append(msg)
for tool_call_id, result in results:
messages.append({
"role": "tool",
"tool_call_id": tool_call_id,
"content": json.dumps(result)
})
return messages
tool_choice: auto vs forced
The tool_choice parameter controls how aggressively the model uses tools. Choose the right mode for your use case.
# Model decides when to call tools
# Best for conversational agents
response = client.chat.completions.create(
model="gpt-4o",
messages=messages,
tools=TOOLS,
tool_choice="auto"
)
# Force the model to call one tool
# Best for deterministic pipelines
response = client.chat.completions.create(
model="gpt-4o",
messages=messages,
tools=TOOLS,
tool_choice={
"type": "function",
"function": {
"name": "trading_open_position"
}
}
)
- Use
"auto"for conversational trading assistants that need to answer questions and execute trades - Use forced tool choice for deterministic pipelines where you know exactly which operation to run
- Use
"none"to disable tools temporarily, e.g. when generating a summary after all trades are done - Use
"required"to force the model to call at least one tool — useful for structured data extraction
Streaming tool calls for real-time feedback
With streaming enabled, you receive tool call argument deltas as the model builds them. This lets you show users what the agent is doing before the arguments are complete — useful for large position sizes or complex swap routes where users want confidence before execution.
from openai import OpenAI
client = OpenAI()
stream = client.chat.completions.create(
model="gpt-4o",
messages=[{"role": "user", "content": "Open a $1000 ETH-PERP long at 10x leverage."}],
tools=TOOLS,
stream=True
)
tool_call_args = ""
tool_call_name = ""
tool_call_id = None
for chunk in stream:
delta = chunk.choices[0].delta
if delta.tool_calls:
tc_delta = delta.tool_calls[0]
if tc_delta.id:
tool_call_id = tc_delta.id
if tc_delta.function.name:
tool_call_name = tc_delta.function.name
print(f"[calling {tool_call_name}...]", flush=True)
if tc_delta.function.arguments:
tool_call_args += tc_delta.function.arguments
# Stream argument construction to UI in real time
print(tc_delta.function.arguments, end="", flush=True)
# Once streaming is done, dispatch
args = json.loads(tool_call_args)
result = dispatch(tool_call_name, args)
print(f"\nResult: {result}")
Error handling patterns
Financial operations require robust error handling. Below are the three layers you need: API-level errors from Purple Flea, network errors, and model-level argument validation failures.
import logging
from typing import Any
logger = logging.getLogger("trading_agent")
class PurpleFleaError(Exception):
def __init__(self, code: str, message: str):
self.code = code
self.message = message
super().__init__(message)
def safe_dispatch(name: str, args: dict) -> dict[str, Any]:
try:
result = dispatch(name, args)
# Purple Flea returns {"error": {...}} on API errors
if "error" in result:
err = result["error"]
logger.warning(f"Purple Flea API error: {err}")
return {
"success": False,
"error_code": err.get("code", "unknown"),
"message": err.get("message", "Operation failed")
}
return {"success": True, "data": result}
except requests.exceptions.Timeout:
logger.error(f"Timeout calling {name}")
return {"success": False, "error_code": "timeout",
"message": "Request timed out. The operation may or may not have executed."}
except requests.exceptions.ConnectionError:
logger.error(f"Connection error calling {name}")
return {"success": False, "error_code": "connection_error",
"message": "Could not reach Purple Flea API."}
except Exception as e:
logger.exception(f"Unexpected error calling {name}: {e}")
return {"success": False, "error_code": "internal",
"message": "An unexpected error occurred."}
# The model receives structured errors and can explain them to the user
# e.g.: "I tried to open the position but got: insufficient_margin"
Idempotency warning: If a network timeout occurs after sending a trading request, the position may have been opened even though you received no confirmation. Always check open positions before retrying. Use Purple Flea's GET /v1/trading/positions endpoint to verify state before re-sending.
- Return structured error objects to the model — it will explain errors to the user in plain language
- Log every tool call with its arguments, result, and latency for audit trails
- Handle timeouts conservatively: assume the operation may have executed
- Set a per-tool request timeout (recommended: 10s for trading, 30s for cross-chain swaps)
- Implement a maximum retry count to prevent runaway agents from hammering the API