Skip to content

Commit c2fec04

Browse files
authored
Merge pull request #42 from Reya-Labs/feat/new-spot-markets-1
Add new example and small fixes
2 parents c6efe4f + 899e338 commit c2fec04

15 files changed

Lines changed: 449 additions & 10 deletions
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
#!/usr/bin/env python3
2+
"""
3+
Cancel Order by ID - Cancel a specific order on the SPOT order book.
4+
5+
This script demonstrates how to cancel a specific order by its order ID.
6+
Simply update the configuration variables below with your order details.
7+
8+
Requirements:
9+
- CHAIN_ID: The chain ID (1729 for mainnet, 89346162 for testnet)
10+
- PRIVATE_KEY: Your Ethereum private key
11+
12+
Usage:
13+
1. Update ORDER_ID, SYMBOL, and ACCOUNT_ID below
14+
2. Run: python -m examples.rest_api.spot.cancel_order_by_id
15+
"""
16+
17+
import asyncio
18+
import os
19+
20+
from dotenv import load_dotenv
21+
from eth_account import Account
22+
23+
from sdk.reya_rest_api import ReyaTradingClient
24+
from sdk.reya_rest_api.config import MAINNET_CHAIN_ID, TradingConfig
25+
26+
ORDER_ID = "1856060584567504896" # The order ID to cancel
27+
SYMBOL = "WETHRUSD" # The trading symbol (WETHRUSD, WBTCRUSD)
28+
ACCOUNT_ID = 10000000002 # Your Reya account ID
29+
30+
31+
async def main() -> None:
32+
"""Cancel an order by ID."""
33+
load_dotenv()
34+
35+
# Get credentials from environment
36+
private_key = os.getenv("PRIVATE_KEY", "")
37+
chain_id = int(os.getenv("CHAIN_ID", str(MAINNET_CHAIN_ID)))
38+
39+
if not private_key:
40+
print("❌ PRIVATE_KEY environment variable is required.")
41+
return
42+
43+
if not ORDER_ID:
44+
print("❌ ORDER_ID is required. Update the ORDER_ID variable in the script.")
45+
return
46+
47+
# Derive wallet address from private key
48+
wallet = Account.from_key(private_key)
49+
wallet_address = wallet.address
50+
51+
# Determine API URL based on chain
52+
api_url = "https://api.reya.xyz/v2" if chain_id == MAINNET_CHAIN_ID else "https://api-cronos.reya.xyz/v2"
53+
54+
print("=" * 60)
55+
print("CANCEL ORDER BY ID")
56+
print("=" * 60)
57+
print(f"Order ID: {ORDER_ID}")
58+
print(f"Symbol: {SYMBOL}")
59+
print(f"Account ID: {ACCOUNT_ID}")
60+
print("=" * 60)
61+
62+
# Create config and client
63+
config = TradingConfig(
64+
api_url=api_url,
65+
chain_id=chain_id,
66+
owner_wallet_address=wallet_address,
67+
private_key=private_key,
68+
account_id=ACCOUNT_ID,
69+
)
70+
71+
client = ReyaTradingClient(config)
72+
await client.start()
73+
74+
try:
75+
print(f"\n📤 Sending cancel request for order {ORDER_ID}...")
76+
result = await client.cancel_order(order_id=ORDER_ID, symbol=SYMBOL, account_id=ACCOUNT_ID)
77+
print(f"✅ Cancel response: {result}")
78+
except Exception as e: # pylint: disable=broad-exception-caught
79+
print(f"❌ Error cancelling order: {e}")
80+
finally:
81+
await client.close()
82+
83+
print("=" * 60)
84+
print("Done!")
85+
86+
87+
if __name__ == "__main__":
88+
asyncio.run(main())
Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
#!/usr/bin/env python3
2+
"""
3+
Create Orders - Creates a buy and sell order at extreme prices.
4+
5+
This script demonstrates how to place GTC limit orders that will sit in the order book:
6+
1. BUY order at $10 (far below market - will not fill immediately)
7+
2. SELL order at $1,000,000 (far above market - will not fill immediately)
8+
9+
Both orders are for 0.001 ETH.
10+
11+
Requirements:
12+
- CHAIN_ID: The chain ID (1729 for mainnet, 89346162 for testnet)
13+
- SPOT_ACCOUNT_ID_1: Your Reya account ID
14+
- SPOT_PRIVATE_KEY_1: Your Ethereum private key
15+
16+
Usage:
17+
python -m examples.rest_api.spot.create_orders
18+
"""
19+
20+
import asyncio
21+
import logging
22+
import sys
23+
24+
from dotenv import load_dotenv
25+
26+
from sdk.open_api.models import TimeInForce
27+
from sdk.reya_rest_api import ReyaTradingClient, get_spot_config
28+
from sdk.reya_rest_api.models.orders import LimitOrderParameters
29+
30+
# =============================================================================
31+
# CONFIGURATION
32+
# =============================================================================
33+
34+
SPOT_SYMBOL = "WETHRUSD"
35+
TRADE_QTY = "0.0001"
36+
BUY_PRICE = "10"
37+
SELL_PRICE = "1000000"
38+
39+
# =============================================================================
40+
# LOGGING SETUP
41+
# =============================================================================
42+
43+
logging.basicConfig(level=logging.WARNING, format="%(asctime)s - %(levelname)s - %(message)s")
44+
logger = logging.getLogger("create_orders")
45+
logger.setLevel(logging.INFO)
46+
logger.handlers = []
47+
handler = logging.StreamHandler()
48+
handler.setFormatter(logging.Formatter("%(asctime)s - %(levelname)s - %(message)s"))
49+
logger.addHandler(handler)
50+
logger.propagate = False
51+
52+
53+
# =============================================================================
54+
# MAIN LOGIC
55+
# =============================================================================
56+
57+
58+
async def main() -> None:
59+
"""Main entry point for the create orders example."""
60+
load_dotenv()
61+
62+
# Get config from environment
63+
try:
64+
config = get_spot_config(account_number=1)
65+
except ValueError as e:
66+
logger.error(f"❌ Configuration error: {e}")
67+
sys.exit(1)
68+
69+
if config.account_id is None:
70+
logger.error("❌ SPOT_ACCOUNT_ID_1 environment variable is required")
71+
sys.exit(1)
72+
73+
if config.private_key is None:
74+
logger.error("❌ SPOT_PRIVATE_KEY_1 environment variable is required")
75+
sys.exit(1)
76+
77+
account_id = config.account_id
78+
79+
logger.info("=" * 60)
80+
logger.info("CREATE ORDERS EXAMPLE")
81+
logger.info("=" * 60)
82+
logger.info(f"Symbol: {SPOT_SYMBOL}")
83+
logger.info(f"Quantity: {TRADE_QTY} ETH")
84+
logger.info(f"Buy Price: ${BUY_PRICE}")
85+
logger.info(f"Sell Price: ${SELL_PRICE}")
86+
logger.info(f"Account ID: {account_id}")
87+
logger.info("=" * 60)
88+
89+
# Create trading client
90+
client = ReyaTradingClient(config)
91+
92+
try:
93+
# Initialize client
94+
await client.start()
95+
logger.info("✅ Client initialized")
96+
97+
# Place GTC BUY order at $10
98+
logger.info("-" * 60)
99+
logger.info(f"📈 Placing GTC BUY order: {TRADE_QTY} ETH @ ${BUY_PRICE}")
100+
101+
buy_params = LimitOrderParameters(
102+
symbol=SPOT_SYMBOL,
103+
is_buy=True,
104+
qty=TRADE_QTY,
105+
limit_px=BUY_PRICE,
106+
time_in_force=TimeInForce.GTC,
107+
)
108+
109+
buy_response = await client.create_limit_order(buy_params)
110+
logger.info(f"✅ BUY order placed: Order ID = {buy_response.order_id}")
111+
112+
# Place GTC SELL order at $1,000,000
113+
logger.info("-" * 60)
114+
logger.info(f"📉 Placing GTC SELL order: {TRADE_QTY} ETH @ ${SELL_PRICE}")
115+
116+
sell_params = LimitOrderParameters(
117+
symbol=SPOT_SYMBOL,
118+
is_buy=False,
119+
qty=TRADE_QTY,
120+
limit_px=SELL_PRICE,
121+
time_in_force=TimeInForce.GTC,
122+
)
123+
124+
sell_response = await client.create_limit_order(sell_params)
125+
logger.info(f"✅ SELL order placed: Order ID = {sell_response.order_id}")
126+
127+
# Summary
128+
logger.info("=" * 60)
129+
logger.info("✅ ORDERS PLACED SUCCESSFULLY")
130+
logger.info(f" Buy Order ID: {buy_response.order_id} @ ${BUY_PRICE}")
131+
logger.info(f" Sell Order ID: {sell_response.order_id} @ ${SELL_PRICE}")
132+
logger.info("=" * 60)
133+
logger.info("ℹ️ These orders will sit in the order book until cancelled or filled.")
134+
135+
finally:
136+
await client.close()
137+
138+
139+
if __name__ == "__main__":
140+
try:
141+
asyncio.run(main())
142+
except KeyboardInterrupt:
143+
print("\nProgram interrupted by user. Exiting...")
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
#!/usr/bin/env python3
2+
"""
3+
Mass Cancel Orders - Cancel all open orders for configured SPOT accounts.
4+
5+
This script demonstrates how to mass cancel all open orders across multiple
6+
spot markets (WETHRUSD, WBTCRUSD) for one or more accounts.
7+
8+
Requirements:
9+
- CHAIN_ID: The chain ID (1729 for mainnet, 89346162 for testnet)
10+
- SPOT_ACCOUNT_ID_1: First Reya account ID (Maker)
11+
- SPOT_PRIVATE_KEY_1: Private key for first account
12+
- SPOT_ACCOUNT_ID_2: Second Reya account ID (Taker) - optional
13+
- SPOT_PRIVATE_KEY_2: Private key for second account - optional
14+
15+
Usage:
16+
python -m examples.rest_api.spot.mass_cancel_orders
17+
"""
18+
19+
import asyncio
20+
import logging
21+
import sys
22+
23+
from dotenv import load_dotenv
24+
25+
from sdk.reya_rest_api import ReyaTradingClient, get_spot_config
26+
27+
SYMBOLS = ["WETHRUSD", "WBTCRUSD"]
28+
29+
logging.basicConfig(level=logging.WARNING, format="%(asctime)s - %(levelname)s - %(message)s")
30+
logger = logging.getLogger("mass_cancel_orders")
31+
logger.setLevel(logging.INFO)
32+
logger.handlers = []
33+
handler = logging.StreamHandler()
34+
handler.setFormatter(logging.Formatter("%(asctime)s - %(levelname)s - %(message)s"))
35+
logger.addHandler(handler)
36+
logger.propagate = False
37+
38+
39+
async def mass_cancel_account(account_number: int, name: str) -> bool:
40+
"""Mass cancel all orders for an account.
41+
42+
Args:
43+
account_number: The account number (1 or 2) for config lookup
44+
name: Display name for logging (e.g., "Maker", "Taker")
45+
46+
Returns:
47+
True if account was processed, False if config was missing
48+
"""
49+
try:
50+
config = get_spot_config(account_number=account_number)
51+
except ValueError:
52+
logger.debug(f"Account {account_number} not configured, skipping")
53+
return False
54+
55+
if config.account_id is None:
56+
logger.warning(f"SPOT_ACCOUNT_ID_{account_number} not set, skipping")
57+
return False
58+
59+
if config.private_key is None:
60+
logger.warning(f"SPOT_PRIVATE_KEY_{account_number} not set, skipping")
61+
return False
62+
63+
client = ReyaTradingClient(config)
64+
await client.start()
65+
66+
try:
67+
for symbol in SYMBOLS:
68+
try:
69+
result = await client.mass_cancel(symbol=symbol, account_id=config.account_id)
70+
logger.info(f"✅ {name} ({config.account_id}): Cancelled {result.cancelled_count} orders for {symbol}")
71+
except Exception as e: # pylint: disable=broad-exception-caught
72+
if "No orders" in str(e) or "no active" in str(e).lower():
73+
logger.info(f"ℹ️ {name} ({config.account_id}): No orders to cancel for {symbol}")
74+
else:
75+
logger.error(f"❌ {name} ({config.account_id}): Error cancelling {symbol}: {e}")
76+
finally:
77+
await client.close()
78+
79+
return True
80+
81+
82+
async def main() -> None:
83+
"""Main entry point for the mass cancel orders example."""
84+
load_dotenv()
85+
86+
logger.info("=" * 60)
87+
logger.info("MASS CANCEL ALL SPOT ORDERS")
88+
logger.info("=" * 60)
89+
90+
accounts_processed = 0
91+
92+
# Process Account 1 (Maker)
93+
if await mass_cancel_account(1, "Maker"):
94+
accounts_processed += 1
95+
96+
# Process Account 2 (Taker)
97+
if await mass_cancel_account(2, "Taker"):
98+
accounts_processed += 1
99+
100+
if accounts_processed == 0:
101+
logger.error("❌ No accounts configured. Set SPOT_ACCOUNT_ID_1 and SPOT_PRIVATE_KEY_1.")
102+
sys.exit(1)
103+
104+
logger.info("=" * 60)
105+
logger.info(f"✅ Done! Processed {accounts_processed} account(s).")
106+
107+
108+
if __name__ == "__main__":
109+
try:
110+
asyncio.run(main())
111+
except KeyboardInterrupt:
112+
print("\nProgram interrupted by user. Exiting...")

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[project]
22
name = "reya-python-sdk"
3-
version = "2.1.3.1"
3+
version = "2.1.3.4"
44
description = "SDK for interacting with Reya Labs APIs"
55
authors = [
66
{name = "Reya Labs"}

tests/test_spot/test_balance_verification.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,11 @@ async def test_spot_balance_update_after_buy(
159159
), f"Taker RUSD change should be exactly -{expected_rusd_change}, got: {taker_rusd_change}"
160160

161161
logger.info("✅ EXACT balance changes verified (zero fees confirmed)")
162+
163+
# Verify no open orders remain
164+
await maker_tester.check.no_open_orders()
165+
await taker_tester.check.no_open_orders()
166+
162167
logger.info("✅ SPOT BALANCE UPDATE AFTER BUY TEST COMPLETED")
163168

164169

@@ -287,6 +292,11 @@ async def test_spot_balance_update_after_sell(
287292
), f"Taker RUSD change should be exactly -{expected_rusd_change}, got: {taker_rusd_change}"
288293

289294
logger.info("✅ EXACT balance changes verified (zero fees confirmed)")
295+
296+
# Verify no open orders remain
297+
await maker_tester.check.no_open_orders()
298+
await taker_tester.check.no_open_orders()
299+
290300
logger.info("✅ SPOT BALANCE UPDATE AFTER SELL TEST COMPLETED")
291301

292302

@@ -384,4 +394,8 @@ async def test_spot_balance_maker_taker_consistency(
384394
assert rusd_diff == Decimal(0), f"RUSD not exactly conserved (zero fees expected): diff={rusd_diff}"
385395
logger.info("✅ RUSD exactly conserved (zero fees)")
386396

397+
# Verify no open orders remain
398+
await maker_tester.check.no_open_orders()
399+
await taker_tester.check.no_open_orders()
400+
387401
logger.info("✅ SPOT BALANCE MAKER/TAKER CONSISTENCY TEST COMPLETED")

0 commit comments

Comments
 (0)