o2-cli 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- o2_cli/__init__.py +30 -0
- o2_cli/__main__.py +6 -0
- o2_cli/cli.py +94 -0
- o2_cli/client.py +307 -0
- o2_cli/commands/__init__.py +1 -0
- o2_cli/commands/_helpers.py +65 -0
- o2_cli/commands/account.py +46 -0
- o2_cli/commands/admin.py +147 -0
- o2_cli/commands/auth.py +140 -0
- o2_cli/commands/balance.py +64 -0
- o2_cli/commands/deposits.py +89 -0
- o2_cli/commands/fees.py +73 -0
- o2_cli/commands/markets.py +129 -0
- o2_cli/commands/mm.py +182 -0
- o2_cli/commands/notifications.py +136 -0
- o2_cli/commands/orders.py +331 -0
- o2_cli/commands/positions.py +158 -0
- o2_cli/commands/settings.py +129 -0
- o2_cli/commands/setup_cmd.py +78 -0
- o2_cli/commands/trades.py +86 -0
- o2_cli/commands/withdrawals.py +175 -0
- o2_cli/config.py +87 -0
- o2_cli/exceptions.py +31 -0
- o2_cli/output.py +224 -0
- o2_cli/setup.py +561 -0
- o2_cli-0.1.0.dist-info/METADATA +141 -0
- o2_cli-0.1.0.dist-info/RECORD +31 -0
- o2_cli-0.1.0.dist-info/WHEEL +5 -0
- o2_cli-0.1.0.dist-info/entry_points.txt +2 -0
- o2_cli-0.1.0.dist-info/licenses/LICENSE +21 -0
- o2_cli-0.1.0.dist-info/top_level.txt +1 -0
o2_cli/commands/mm.py
ADDED
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
"""o2 mm commands - Market maker control."""
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
|
|
5
|
+
import typer
|
|
6
|
+
|
|
7
|
+
from o2_cli.cli import get_state
|
|
8
|
+
from o2_cli.client import O2Client
|
|
9
|
+
from o2_cli.config import load_config, get_active_profile
|
|
10
|
+
from o2_cli.exceptions import APIError, ConnectionError
|
|
11
|
+
from o2_cli.output import OutputFormatter
|
|
12
|
+
|
|
13
|
+
app = typer.Typer(help="Market maker control")
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@app.command("status")
|
|
17
|
+
def status():
|
|
18
|
+
"""Show market maker status."""
|
|
19
|
+
asyncio.run(_status())
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
async def _status():
|
|
23
|
+
state = get_state()
|
|
24
|
+
formatter = OutputFormatter(json_mode=state["json_output"])
|
|
25
|
+
config = load_config()
|
|
26
|
+
profile = get_active_profile(config)
|
|
27
|
+
api_url = state.get("api_url") or profile.api_url
|
|
28
|
+
timeout = state.get("timeout") or profile.timeout
|
|
29
|
+
|
|
30
|
+
if not profile.token and profile.auth_type == "jwt":
|
|
31
|
+
formatter.print_error("Not authenticated. Run 'o2 auth test-login' first.")
|
|
32
|
+
raise typer.Exit(1)
|
|
33
|
+
|
|
34
|
+
async with O2Client(api_url, timeout) as client:
|
|
35
|
+
client.set_jwt(profile.token)
|
|
36
|
+
if profile.api_key_id:
|
|
37
|
+
client.set_api_key(profile.api_key_id, profile.api_secret)
|
|
38
|
+
try:
|
|
39
|
+
data = await client.mm_status()
|
|
40
|
+
formatter.print_raw(data)
|
|
41
|
+
except APIError as e:
|
|
42
|
+
formatter.print_error(str(e), e.code)
|
|
43
|
+
raise typer.Exit(1)
|
|
44
|
+
except ConnectionError as e:
|
|
45
|
+
formatter.print_error(str(e))
|
|
46
|
+
raise typer.Exit(1)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
@app.command("start")
|
|
50
|
+
def start():
|
|
51
|
+
"""Start the market maker."""
|
|
52
|
+
asyncio.run(_start())
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
async def _start():
|
|
56
|
+
state = get_state()
|
|
57
|
+
formatter = OutputFormatter(json_mode=state["json_output"])
|
|
58
|
+
config = load_config()
|
|
59
|
+
profile = get_active_profile(config)
|
|
60
|
+
api_url = state.get("api_url") or profile.api_url
|
|
61
|
+
timeout = state.get("timeout") or profile.timeout
|
|
62
|
+
|
|
63
|
+
if not profile.token and profile.auth_type == "jwt":
|
|
64
|
+
formatter.print_error("Not authenticated. Run 'o2 auth test-login' first.")
|
|
65
|
+
raise typer.Exit(1)
|
|
66
|
+
|
|
67
|
+
async with O2Client(api_url, timeout) as client:
|
|
68
|
+
client.set_jwt(profile.token)
|
|
69
|
+
if profile.api_key_id:
|
|
70
|
+
client.set_api_key(profile.api_key_id, profile.api_secret)
|
|
71
|
+
try:
|
|
72
|
+
data = await client.mm_start()
|
|
73
|
+
formatter.print_raw(data)
|
|
74
|
+
if not state["json_output"]:
|
|
75
|
+
formatter.print_success("Market maker started")
|
|
76
|
+
except APIError as e:
|
|
77
|
+
formatter.print_error(str(e), e.code)
|
|
78
|
+
raise typer.Exit(1)
|
|
79
|
+
except ConnectionError as e:
|
|
80
|
+
formatter.print_error(str(e))
|
|
81
|
+
raise typer.Exit(1)
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
@app.command("stop")
|
|
85
|
+
def stop():
|
|
86
|
+
"""Stop the market maker."""
|
|
87
|
+
asyncio.run(_stop())
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
async def _stop():
|
|
91
|
+
state = get_state()
|
|
92
|
+
formatter = OutputFormatter(json_mode=state["json_output"])
|
|
93
|
+
config = load_config()
|
|
94
|
+
profile = get_active_profile(config)
|
|
95
|
+
api_url = state.get("api_url") or profile.api_url
|
|
96
|
+
timeout = state.get("timeout") or profile.timeout
|
|
97
|
+
|
|
98
|
+
if not profile.token and profile.auth_type == "jwt":
|
|
99
|
+
formatter.print_error("Not authenticated. Run 'o2 auth test-login' first.")
|
|
100
|
+
raise typer.Exit(1)
|
|
101
|
+
|
|
102
|
+
async with O2Client(api_url, timeout) as client:
|
|
103
|
+
client.set_jwt(profile.token)
|
|
104
|
+
if profile.api_key_id:
|
|
105
|
+
client.set_api_key(profile.api_key_id, profile.api_secret)
|
|
106
|
+
try:
|
|
107
|
+
data = await client.mm_stop()
|
|
108
|
+
formatter.print_raw(data)
|
|
109
|
+
if not state["json_output"]:
|
|
110
|
+
formatter.print_success("Market maker stopped")
|
|
111
|
+
except APIError as e:
|
|
112
|
+
formatter.print_error(str(e), e.code)
|
|
113
|
+
raise typer.Exit(1)
|
|
114
|
+
except ConnectionError as e:
|
|
115
|
+
formatter.print_error(str(e))
|
|
116
|
+
raise typer.Exit(1)
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
@app.command("stats")
|
|
120
|
+
def stats():
|
|
121
|
+
"""Show market maker statistics."""
|
|
122
|
+
asyncio.run(_stats())
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
async def _stats():
|
|
126
|
+
state = get_state()
|
|
127
|
+
formatter = OutputFormatter(json_mode=state["json_output"])
|
|
128
|
+
config = load_config()
|
|
129
|
+
profile = get_active_profile(config)
|
|
130
|
+
api_url = state.get("api_url") or profile.api_url
|
|
131
|
+
timeout = state.get("timeout") or profile.timeout
|
|
132
|
+
|
|
133
|
+
if not profile.token and profile.auth_type == "jwt":
|
|
134
|
+
formatter.print_error("Not authenticated. Run 'o2 auth test-login' first.")
|
|
135
|
+
raise typer.Exit(1)
|
|
136
|
+
|
|
137
|
+
async with O2Client(api_url, timeout) as client:
|
|
138
|
+
client.set_jwt(profile.token)
|
|
139
|
+
if profile.api_key_id:
|
|
140
|
+
client.set_api_key(profile.api_key_id, profile.api_secret)
|
|
141
|
+
try:
|
|
142
|
+
data = await client.mm_stats()
|
|
143
|
+
formatter.print_raw(data)
|
|
144
|
+
except APIError as e:
|
|
145
|
+
formatter.print_error(str(e), e.code)
|
|
146
|
+
raise typer.Exit(1)
|
|
147
|
+
except ConnectionError as e:
|
|
148
|
+
formatter.print_error(str(e))
|
|
149
|
+
raise typer.Exit(1)
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
@app.command("orders")
|
|
153
|
+
def orders():
|
|
154
|
+
"""Show current market maker orders."""
|
|
155
|
+
asyncio.run(_orders())
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
async def _orders():
|
|
159
|
+
state = get_state()
|
|
160
|
+
formatter = OutputFormatter(json_mode=state["json_output"])
|
|
161
|
+
config = load_config()
|
|
162
|
+
profile = get_active_profile(config)
|
|
163
|
+
api_url = state.get("api_url") or profile.api_url
|
|
164
|
+
timeout = state.get("timeout") or profile.timeout
|
|
165
|
+
|
|
166
|
+
if not profile.token and profile.auth_type == "jwt":
|
|
167
|
+
formatter.print_error("Not authenticated. Run 'o2 auth test-login' first.")
|
|
168
|
+
raise typer.Exit(1)
|
|
169
|
+
|
|
170
|
+
async with O2Client(api_url, timeout) as client:
|
|
171
|
+
client.set_jwt(profile.token)
|
|
172
|
+
if profile.api_key_id:
|
|
173
|
+
client.set_api_key(profile.api_key_id, profile.api_secret)
|
|
174
|
+
try:
|
|
175
|
+
data = await client.mm_orders()
|
|
176
|
+
formatter.print_orders(data)
|
|
177
|
+
except APIError as e:
|
|
178
|
+
formatter.print_error(str(e), e.code)
|
|
179
|
+
raise typer.Exit(1)
|
|
180
|
+
except ConnectionError as e:
|
|
181
|
+
formatter.print_error(str(e))
|
|
182
|
+
raise typer.Exit(1)
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
"""o2 notifications commands - Notification management."""
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
from typing import Optional
|
|
5
|
+
|
|
6
|
+
import typer
|
|
7
|
+
|
|
8
|
+
from o2_cli.cli import get_state
|
|
9
|
+
from o2_cli.client import O2Client
|
|
10
|
+
from o2_cli.config import load_config, get_active_profile
|
|
11
|
+
from o2_cli.exceptions import APIError, ConnectionError
|
|
12
|
+
from o2_cli.output import OutputFormatter
|
|
13
|
+
|
|
14
|
+
app = typer.Typer(help="Notifications")
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@app.command("list")
|
|
18
|
+
def list_notifications(
|
|
19
|
+
unread_only: bool = typer.Option(
|
|
20
|
+
False, "--unread-only", "-u", help="Show only unread notifications"
|
|
21
|
+
),
|
|
22
|
+
type: Optional[str] = typer.Option(
|
|
23
|
+
None, "--type", "-t",
|
|
24
|
+
help="Filter by type (trade/deposit/withdrawal/system/security/market)"
|
|
25
|
+
),
|
|
26
|
+
):
|
|
27
|
+
"""List notifications."""
|
|
28
|
+
asyncio.run(_list_notifications(unread_only, type))
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
async def _list_notifications(unread_only: bool, type: Optional[str]):
|
|
32
|
+
state = get_state()
|
|
33
|
+
formatter = OutputFormatter(json_mode=state["json_output"])
|
|
34
|
+
config = load_config()
|
|
35
|
+
profile = get_active_profile(config)
|
|
36
|
+
api_url = state.get("api_url") or profile.api_url
|
|
37
|
+
timeout = state.get("timeout") or profile.timeout
|
|
38
|
+
|
|
39
|
+
if not profile.token and profile.auth_type == "jwt":
|
|
40
|
+
formatter.print_error("Not authenticated. Run 'o2 auth test-login' first.")
|
|
41
|
+
raise typer.Exit(1)
|
|
42
|
+
|
|
43
|
+
params = {}
|
|
44
|
+
if unread_only:
|
|
45
|
+
params["is_read"] = "false"
|
|
46
|
+
if type:
|
|
47
|
+
params["type"] = type
|
|
48
|
+
|
|
49
|
+
async with O2Client(api_url, timeout) as client:
|
|
50
|
+
client.set_jwt(profile.token)
|
|
51
|
+
if profile.api_key_id:
|
|
52
|
+
client.set_api_key(profile.api_key_id, profile.api_secret)
|
|
53
|
+
try:
|
|
54
|
+
data = await client.get_notifications(**params)
|
|
55
|
+
formatter.print_raw(data)
|
|
56
|
+
except APIError as e:
|
|
57
|
+
formatter.print_error(str(e), e.code)
|
|
58
|
+
raise typer.Exit(1)
|
|
59
|
+
except ConnectionError as e:
|
|
60
|
+
formatter.print_error(str(e))
|
|
61
|
+
raise typer.Exit(1)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
@app.command("unread")
|
|
65
|
+
def unread():
|
|
66
|
+
"""Show unread notification count."""
|
|
67
|
+
asyncio.run(_unread())
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
async def _unread():
|
|
71
|
+
state = get_state()
|
|
72
|
+
formatter = OutputFormatter(json_mode=state["json_output"])
|
|
73
|
+
config = load_config()
|
|
74
|
+
profile = get_active_profile(config)
|
|
75
|
+
api_url = state.get("api_url") or profile.api_url
|
|
76
|
+
timeout = state.get("timeout") or profile.timeout
|
|
77
|
+
|
|
78
|
+
if not profile.token and profile.auth_type == "jwt":
|
|
79
|
+
formatter.print_error("Not authenticated. Run 'o2 auth test-login' first.")
|
|
80
|
+
raise typer.Exit(1)
|
|
81
|
+
|
|
82
|
+
async with O2Client(api_url, timeout) as client:
|
|
83
|
+
client.set_jwt(profile.token)
|
|
84
|
+
if profile.api_key_id:
|
|
85
|
+
client.set_api_key(profile.api_key_id, profile.api_secret)
|
|
86
|
+
try:
|
|
87
|
+
data = await client.get_unread_count()
|
|
88
|
+
formatter.print_raw(data)
|
|
89
|
+
if not state["json_output"]:
|
|
90
|
+
count = data.get("unread_count", data.get("count", 0))
|
|
91
|
+
formatter.print_success(f"{count} unread notifications")
|
|
92
|
+
except APIError as e:
|
|
93
|
+
formatter.print_error(str(e), e.code)
|
|
94
|
+
raise typer.Exit(1)
|
|
95
|
+
except ConnectionError as e:
|
|
96
|
+
formatter.print_error(str(e))
|
|
97
|
+
raise typer.Exit(1)
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
@app.command("read")
|
|
101
|
+
def read(
|
|
102
|
+
id: str = typer.Option(
|
|
103
|
+
..., "--id", "-i", help="Notification ID to mark as read"
|
|
104
|
+
),
|
|
105
|
+
):
|
|
106
|
+
"""Mark a notification as read."""
|
|
107
|
+
asyncio.run(_read(id))
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
async def _read(notification_id: str):
|
|
111
|
+
state = get_state()
|
|
112
|
+
formatter = OutputFormatter(json_mode=state["json_output"])
|
|
113
|
+
config = load_config()
|
|
114
|
+
profile = get_active_profile(config)
|
|
115
|
+
api_url = state.get("api_url") or profile.api_url
|
|
116
|
+
timeout = state.get("timeout") or profile.timeout
|
|
117
|
+
|
|
118
|
+
if not profile.token and profile.auth_type == "jwt":
|
|
119
|
+
formatter.print_error("Not authenticated. Run 'o2 auth test-login' first.")
|
|
120
|
+
raise typer.Exit(1)
|
|
121
|
+
|
|
122
|
+
async with O2Client(api_url, timeout) as client:
|
|
123
|
+
client.set_jwt(profile.token)
|
|
124
|
+
if profile.api_key_id:
|
|
125
|
+
client.set_api_key(profile.api_key_id, profile.api_secret)
|
|
126
|
+
try:
|
|
127
|
+
data = await client.mark_notification_read(notification_id)
|
|
128
|
+
formatter.print_raw(data)
|
|
129
|
+
if not state["json_output"]:
|
|
130
|
+
formatter.print_success(f"Notification {notification_id} marked as read")
|
|
131
|
+
except APIError as e:
|
|
132
|
+
formatter.print_error(str(e), e.code)
|
|
133
|
+
raise typer.Exit(1)
|
|
134
|
+
except ConnectionError as e:
|
|
135
|
+
formatter.print_error(str(e))
|
|
136
|
+
raise typer.Exit(1)
|
|
@@ -0,0 +1,331 @@
|
|
|
1
|
+
"""o2 orders commands - Order management."""
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
import json
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import Optional
|
|
7
|
+
|
|
8
|
+
import typer
|
|
9
|
+
|
|
10
|
+
from o2_cli.cli import get_state
|
|
11
|
+
from o2_cli.client import O2Client
|
|
12
|
+
from o2_cli.config import load_config, get_active_profile
|
|
13
|
+
from o2_cli.exceptions import APIError, ConnectionError
|
|
14
|
+
from o2_cli.output import OutputFormatter
|
|
15
|
+
|
|
16
|
+
app = typer.Typer(help="Order management")
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@app.command("create")
|
|
20
|
+
def create(
|
|
21
|
+
market_id: int = typer.Option(..., "--market-id", "-m", help="Market ID"),
|
|
22
|
+
side: str = typer.Option(..., "--side", "-s", help="Order side (long/short)"),
|
|
23
|
+
order_type: str = typer.Option(
|
|
24
|
+
"limit", "--order-type", "-t", help="Order type (market/limit)"
|
|
25
|
+
),
|
|
26
|
+
base_amount: str = typer.Option(
|
|
27
|
+
..., "--base-amount", "-a", help="Order size in base units"
|
|
28
|
+
),
|
|
29
|
+
price: Optional[str] = typer.Option(
|
|
30
|
+
None, "--price", "-p", help="Limit price (required for limit orders)"
|
|
31
|
+
),
|
|
32
|
+
position_mode: str = typer.Option(
|
|
33
|
+
"open", "--position-mode", help="Position mode (open/close)"
|
|
34
|
+
),
|
|
35
|
+
leverage: Optional[int] = typer.Option(None, "--leverage", "-l", help="Leverage"),
|
|
36
|
+
margin_mode: str = typer.Option(
|
|
37
|
+
"cross", "--margin-mode", help="Margin mode (cross/isolated)"
|
|
38
|
+
),
|
|
39
|
+
reduce_only: bool = typer.Option(
|
|
40
|
+
False, "--reduce-only", help="Reduce-only order"
|
|
41
|
+
),
|
|
42
|
+
):
|
|
43
|
+
"""Create a new order."""
|
|
44
|
+
asyncio.run(
|
|
45
|
+
_create(
|
|
46
|
+
market_id, side, order_type, base_amount, price, position_mode,
|
|
47
|
+
leverage, margin_mode, reduce_only,
|
|
48
|
+
)
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
async def _create(
|
|
53
|
+
market_id: int,
|
|
54
|
+
side: str,
|
|
55
|
+
order_type: str,
|
|
56
|
+
base_amount: str,
|
|
57
|
+
price: Optional[str],
|
|
58
|
+
position_mode: str,
|
|
59
|
+
leverage: Optional[int],
|
|
60
|
+
margin_mode: str,
|
|
61
|
+
reduce_only: bool,
|
|
62
|
+
):
|
|
63
|
+
state = get_state()
|
|
64
|
+
formatter = OutputFormatter(json_mode=state["json_output"])
|
|
65
|
+
config = load_config()
|
|
66
|
+
profile = get_active_profile(config)
|
|
67
|
+
api_url = state.get("api_url") or profile.api_url
|
|
68
|
+
timeout = state.get("timeout") or profile.timeout
|
|
69
|
+
|
|
70
|
+
if not profile.token and profile.auth_type == "jwt":
|
|
71
|
+
formatter.print_error("Not authenticated. Run 'o2 auth test-login' first.")
|
|
72
|
+
raise typer.Exit(1)
|
|
73
|
+
|
|
74
|
+
if order_type == "limit" and not price:
|
|
75
|
+
formatter.print_error("Limit orders require --price.")
|
|
76
|
+
raise typer.Exit(1)
|
|
77
|
+
|
|
78
|
+
payload = {
|
|
79
|
+
"market_id": market_id,
|
|
80
|
+
"side": side,
|
|
81
|
+
"order_type": order_type,
|
|
82
|
+
"base_amount": base_amount,
|
|
83
|
+
"position_mode": position_mode,
|
|
84
|
+
"margin_mode": margin_mode,
|
|
85
|
+
"reduce_only": reduce_only,
|
|
86
|
+
}
|
|
87
|
+
if price is not None:
|
|
88
|
+
payload["price"] = price
|
|
89
|
+
if leverage is not None:
|
|
90
|
+
payload["leverage"] = leverage
|
|
91
|
+
|
|
92
|
+
async with O2Client(api_url, timeout) as client:
|
|
93
|
+
client.set_jwt(profile.token)
|
|
94
|
+
if profile.api_key_id:
|
|
95
|
+
client.set_api_key(profile.api_key_id, profile.api_secret)
|
|
96
|
+
try:
|
|
97
|
+
data = await client.create_order(**payload)
|
|
98
|
+
formatter.print_orders(data)
|
|
99
|
+
if not state["json_output"]:
|
|
100
|
+
formatter.print_success("Order created")
|
|
101
|
+
except APIError as e:
|
|
102
|
+
formatter.print_error(str(e), e.code)
|
|
103
|
+
raise typer.Exit(1)
|
|
104
|
+
except ConnectionError as e:
|
|
105
|
+
formatter.print_error(str(e))
|
|
106
|
+
raise typer.Exit(1)
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
@app.command("list")
|
|
110
|
+
def list_orders(
|
|
111
|
+
market_id: Optional[int] = typer.Option(
|
|
112
|
+
None, "--market-id", "-m", help="Filter by market ID"
|
|
113
|
+
),
|
|
114
|
+
status: Optional[str] = typer.Option(
|
|
115
|
+
None, "--status", "-s", help="Filter by status"
|
|
116
|
+
),
|
|
117
|
+
):
|
|
118
|
+
"""List orders."""
|
|
119
|
+
asyncio.run(_list_orders(market_id, status))
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
async def _list_orders(market_id: Optional[int], status: Optional[str]):
|
|
123
|
+
state = get_state()
|
|
124
|
+
formatter = OutputFormatter(json_mode=state["json_output"])
|
|
125
|
+
config = load_config()
|
|
126
|
+
profile = get_active_profile(config)
|
|
127
|
+
api_url = state.get("api_url") or profile.api_url
|
|
128
|
+
timeout = state.get("timeout") or profile.timeout
|
|
129
|
+
|
|
130
|
+
if not profile.token and profile.auth_type == "jwt":
|
|
131
|
+
formatter.print_error("Not authenticated. Run 'o2 auth test-login' first.")
|
|
132
|
+
raise typer.Exit(1)
|
|
133
|
+
|
|
134
|
+
async with O2Client(api_url, timeout) as client:
|
|
135
|
+
client.set_jwt(profile.token)
|
|
136
|
+
if profile.api_key_id:
|
|
137
|
+
client.set_api_key(profile.api_key_id, profile.api_secret)
|
|
138
|
+
try:
|
|
139
|
+
data = await client.list_orders(market_id=market_id, status_filter=status)
|
|
140
|
+
formatter.print_orders(data)
|
|
141
|
+
except APIError as e:
|
|
142
|
+
formatter.print_error(str(e), e.code)
|
|
143
|
+
raise typer.Exit(1)
|
|
144
|
+
except ConnectionError as e:
|
|
145
|
+
formatter.print_error(str(e))
|
|
146
|
+
raise typer.Exit(1)
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
@app.command("cancel")
|
|
150
|
+
def cancel(
|
|
151
|
+
order_id: str = typer.Option(..., "--order-id", "-i", help="Order ID to cancel"),
|
|
152
|
+
):
|
|
153
|
+
"""Cancel an order."""
|
|
154
|
+
asyncio.run(_cancel(order_id))
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
async def _cancel(order_id: str):
|
|
158
|
+
state = get_state()
|
|
159
|
+
formatter = OutputFormatter(json_mode=state["json_output"])
|
|
160
|
+
config = load_config()
|
|
161
|
+
profile = get_active_profile(config)
|
|
162
|
+
api_url = state.get("api_url") or profile.api_url
|
|
163
|
+
timeout = state.get("timeout") or profile.timeout
|
|
164
|
+
|
|
165
|
+
if not profile.token and profile.auth_type == "jwt":
|
|
166
|
+
formatter.print_error("Not authenticated. Run 'o2 auth test-login' first.")
|
|
167
|
+
raise typer.Exit(1)
|
|
168
|
+
|
|
169
|
+
async with O2Client(api_url, timeout) as client:
|
|
170
|
+
client.set_jwt(profile.token)
|
|
171
|
+
if profile.api_key_id:
|
|
172
|
+
client.set_api_key(profile.api_key_id, profile.api_secret)
|
|
173
|
+
try:
|
|
174
|
+
data = await client.cancel_order(order_id)
|
|
175
|
+
formatter.print_raw(data)
|
|
176
|
+
if not state["json_output"]:
|
|
177
|
+
formatter.print_success(f"Order {order_id} cancelled")
|
|
178
|
+
except APIError as e:
|
|
179
|
+
formatter.print_error(str(e), e.code)
|
|
180
|
+
raise typer.Exit(1)
|
|
181
|
+
except ConnectionError as e:
|
|
182
|
+
formatter.print_error(str(e))
|
|
183
|
+
raise typer.Exit(1)
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
@app.command("cancel-all")
|
|
187
|
+
def cancel_all(
|
|
188
|
+
market_id: Optional[int] = typer.Option(
|
|
189
|
+
None, "--market-id", "-m", help="Cancel orders for a specific market"
|
|
190
|
+
),
|
|
191
|
+
):
|
|
192
|
+
"""Cancel all open orders."""
|
|
193
|
+
asyncio.run(_cancel_all(market_id))
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
async def _cancel_all(market_id: Optional[int]):
|
|
197
|
+
state = get_state()
|
|
198
|
+
formatter = OutputFormatter(json_mode=state["json_output"])
|
|
199
|
+
config = load_config()
|
|
200
|
+
profile = get_active_profile(config)
|
|
201
|
+
api_url = state.get("api_url") or profile.api_url
|
|
202
|
+
timeout = state.get("timeout") or profile.timeout
|
|
203
|
+
|
|
204
|
+
if not profile.token and profile.auth_type == "jwt":
|
|
205
|
+
formatter.print_error("Not authenticated. Run 'o2 auth test-login' first.")
|
|
206
|
+
raise typer.Exit(1)
|
|
207
|
+
|
|
208
|
+
async with O2Client(api_url, timeout) as client:
|
|
209
|
+
client.set_jwt(profile.token)
|
|
210
|
+
if profile.api_key_id:
|
|
211
|
+
client.set_api_key(profile.api_key_id, profile.api_secret)
|
|
212
|
+
try:
|
|
213
|
+
data = await client.cancel_all_orders(market_id=market_id)
|
|
214
|
+
formatter.print_raw(data)
|
|
215
|
+
if not state["json_output"]:
|
|
216
|
+
label = f" for market {market_id}" if market_id else ""
|
|
217
|
+
formatter.print_success(f"All orders cancelled{label}")
|
|
218
|
+
except APIError as e:
|
|
219
|
+
formatter.print_error(str(e), e.code)
|
|
220
|
+
raise typer.Exit(1)
|
|
221
|
+
except ConnectionError as e:
|
|
222
|
+
formatter.print_error(str(e))
|
|
223
|
+
raise typer.Exit(1)
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
@app.command("modify")
|
|
227
|
+
def modify(
|
|
228
|
+
order_id: str = typer.Option(..., "--order-id", "-i", help="Order ID to modify"),
|
|
229
|
+
base_amount: Optional[str] = typer.Option(
|
|
230
|
+
None, "--base-amount", "-a", help="New order size"
|
|
231
|
+
),
|
|
232
|
+
price: Optional[str] = typer.Option(None, "--price", "-p", help="New price"),
|
|
233
|
+
):
|
|
234
|
+
"""Modify an existing order."""
|
|
235
|
+
asyncio.run(_modify(order_id, base_amount, price))
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
async def _modify(
|
|
239
|
+
order_id: str, base_amount: Optional[str], price: Optional[str]
|
|
240
|
+
):
|
|
241
|
+
state = get_state()
|
|
242
|
+
formatter = OutputFormatter(json_mode=state["json_output"])
|
|
243
|
+
config = load_config()
|
|
244
|
+
profile = get_active_profile(config)
|
|
245
|
+
api_url = state.get("api_url") or profile.api_url
|
|
246
|
+
timeout = state.get("timeout") or profile.timeout
|
|
247
|
+
|
|
248
|
+
if not profile.token and profile.auth_type == "jwt":
|
|
249
|
+
formatter.print_error("Not authenticated. Run 'o2 auth test-login' first.")
|
|
250
|
+
raise typer.Exit(1)
|
|
251
|
+
|
|
252
|
+
if base_amount is None and price is None:
|
|
253
|
+
formatter.print_error("Provide at least --base-amount or --price to modify.")
|
|
254
|
+
raise typer.Exit(1)
|
|
255
|
+
|
|
256
|
+
kwargs = {}
|
|
257
|
+
if base_amount is not None:
|
|
258
|
+
kwargs["base_amount"] = base_amount
|
|
259
|
+
if price is not None:
|
|
260
|
+
kwargs["price"] = price
|
|
261
|
+
|
|
262
|
+
async with O2Client(api_url, timeout) as client:
|
|
263
|
+
client.set_jwt(profile.token)
|
|
264
|
+
if profile.api_key_id:
|
|
265
|
+
client.set_api_key(profile.api_key_id, profile.api_secret)
|
|
266
|
+
try:
|
|
267
|
+
data = await client.modify_order(order_id, **kwargs)
|
|
268
|
+
formatter.print_raw(data)
|
|
269
|
+
if not state["json_output"]:
|
|
270
|
+
formatter.print_success(f"Order {order_id} modified")
|
|
271
|
+
except APIError as e:
|
|
272
|
+
formatter.print_error(str(e), e.code)
|
|
273
|
+
raise typer.Exit(1)
|
|
274
|
+
except ConnectionError as e:
|
|
275
|
+
formatter.print_error(str(e))
|
|
276
|
+
raise typer.Exit(1)
|
|
277
|
+
|
|
278
|
+
|
|
279
|
+
@app.command("batch")
|
|
280
|
+
def batch(
|
|
281
|
+
file: Path = typer.Option(
|
|
282
|
+
..., "--file", "-f", help="Path to JSON file with batch operations"
|
|
283
|
+
),
|
|
284
|
+
):
|
|
285
|
+
"""Submit batch order operations from a JSON file."""
|
|
286
|
+
asyncio.run(_batch(file))
|
|
287
|
+
|
|
288
|
+
|
|
289
|
+
async def _batch(file: Path):
|
|
290
|
+
state = get_state()
|
|
291
|
+
formatter = OutputFormatter(json_mode=state["json_output"])
|
|
292
|
+
config = load_config()
|
|
293
|
+
profile = get_active_profile(config)
|
|
294
|
+
api_url = state.get("api_url") or profile.api_url
|
|
295
|
+
timeout = state.get("timeout") or profile.timeout
|
|
296
|
+
|
|
297
|
+
if not profile.token and profile.auth_type == "jwt":
|
|
298
|
+
formatter.print_error("Not authenticated. Run 'o2 auth test-login' first.")
|
|
299
|
+
raise typer.Exit(1)
|
|
300
|
+
|
|
301
|
+
if not file.exists():
|
|
302
|
+
formatter.print_error(f"File not found: {file}")
|
|
303
|
+
raise typer.Exit(1)
|
|
304
|
+
|
|
305
|
+
try:
|
|
306
|
+
with open(file) as f:
|
|
307
|
+
payload = json.load(f)
|
|
308
|
+
except json.JSONDecodeError as e:
|
|
309
|
+
formatter.print_error(f"Invalid JSON in {file}: {e}")
|
|
310
|
+
raise typer.Exit(1)
|
|
311
|
+
|
|
312
|
+
operations = payload if isinstance(payload, list) else payload.get("operations", [])
|
|
313
|
+
if not operations:
|
|
314
|
+
formatter.print_error("No operations found in file.")
|
|
315
|
+
raise typer.Exit(1)
|
|
316
|
+
|
|
317
|
+
async with O2Client(api_url, timeout) as client:
|
|
318
|
+
client.set_jwt(profile.token)
|
|
319
|
+
if profile.api_key_id:
|
|
320
|
+
client.set_api_key(profile.api_key_id, profile.api_secret)
|
|
321
|
+
try:
|
|
322
|
+
data = await client.batch_orders(operations)
|
|
323
|
+
formatter.print_raw(data)
|
|
324
|
+
if not state["json_output"]:
|
|
325
|
+
formatter.print_success(f"Batch submitted ({len(operations)} operations)")
|
|
326
|
+
except APIError as e:
|
|
327
|
+
formatter.print_error(str(e), e.code)
|
|
328
|
+
raise typer.Exit(1)
|
|
329
|
+
except ConnectionError as e:
|
|
330
|
+
formatter.print_error(str(e))
|
|
331
|
+
raise typer.Exit(1)
|