Skip to content

Commit 0c002fa

Browse files
committed
no args = help, add marks to live orders
1 parent e5f3836 commit 0c002fa

File tree

8 files changed

+75
-27
lines changed

8 files changed

+75
-27
lines changed

ttcli/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,5 @@
44

55
CUSTOM_CONFIG_PATH = ".config/ttcli/ttcli.cfg"
66
TOKEN_PATH = ".config/ttcli/.session"
7-
VERSION = "0.6.0"
7+
VERSION = "0.6.1"
88
__version__ = VERSION

ttcli/app.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
from ttcli.utils import config_path
1515
from ttcli.watchlist import watchlist
1616

17-
cli = Typer()
17+
cli = Typer(no_args_is_help=True)
1818

1919

2020
@cli.callback(invoke_without_command=True, no_args_is_help=True)

ttcli/option.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -113,12 +113,13 @@ def choose_futures_expiration(
113113
return exps[choice - 1]
114114

115115

116-
option = AsyncTyper(help="Buy, sell, and analyze options.")
116+
option = AsyncTyper(help="Buy, sell, and analyze options.", no_args_is_help=True)
117117

118118

119119
@option.command(
120120
help="Buy or sell calls with the given parameters.",
121121
context_settings={"ignore_unknown_options": True},
122+
no_args_is_help=True,
122123
)
123124
async def call(
124125
symbol: str,
@@ -353,6 +354,7 @@ async def call(
353354
@option.command(
354355
help="Buy or sell puts with the given parameters.",
355356
context_settings={"ignore_unknown_options": True},
357+
no_args_is_help=True,
356358
)
357359
async def put(
358360
symbol: str,
@@ -588,6 +590,7 @@ async def put(
588590
@option.command(
589591
help="Buy or sell strangles with the given parameters.",
590592
context_settings={"ignore_unknown_options": True},
593+
no_args_is_help=True,
591594
)
592595
async def strangle(
593596
symbol: str,
@@ -905,7 +908,7 @@ async def strangle(
905908
acc.place_order(sesh, order, dry_run=False)
906909

907910

908-
@option.command(help="Fetch and display an options chain.")
911+
@option.command(help="Fetch and display an options chain.", no_args_is_help=True)
909912
async def chain(
910913
symbol: str,
911914
strikes: Annotated[

ttcli/order.py

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
1+
from collections import defaultdict
12
from datetime import datetime
23
from decimal import Decimal
4+
from math import gcd
35
from typing import Annotated
46

57
from rich.console import Console
68
from rich.table import Table
9+
from tastytrade.market_data import get_market_data_by_type
710
from tastytrade.order import InstrumentType, NewOrder, OrderStatus
811
from tastytrade.utils import TastytradeError
912
from typer import Option, Typer
@@ -17,7 +20,7 @@
1720
print_error,
1821
)
1922

20-
order = Typer(help="List, adjust, or cancel orders.")
23+
order = Typer(help="List, adjust, or cancel orders.", no_args_is_help=True)
2124

2225

2326
@order.command(help="List, adjust, or cancel live orders.")
@@ -39,6 +42,19 @@ def live(
3942
for o in orders
4043
if o.status == OrderStatus.LIVE or o.status == OrderStatus.RECEIVED
4144
]
45+
instrument_dict: dict[InstrumentType, set[str]] = defaultdict(set)
46+
for o in orders:
47+
for leg in o.legs:
48+
instrument_dict[leg.instrument_type].add(leg.symbol)
49+
data = get_market_data_by_type(
50+
sesh,
51+
cryptocurrencies=list(instrument_dict[InstrumentType.CRYPTOCURRENCY]) or None,
52+
equities=list(instrument_dict[InstrumentType.EQUITY]) or None,
53+
futures=list(instrument_dict[InstrumentType.FUTURE]) or None,
54+
future_options=list(instrument_dict[InstrumentType.FUTURE_OPTION]) or None,
55+
options=list(instrument_dict[InstrumentType.EQUITY_OPTION]) or None,
56+
)
57+
marks = {d.symbol: d.mark for d in data}
4258
console = Console()
4359
table = Table(
4460
show_header=True,
@@ -55,11 +71,19 @@ def live(
5571
table.add_column("Type")
5672
table.add_column("TIF")
5773
table.add_column("Price", justify="right")
74+
table.add_column("Mark", justify="right")
5875
# leg info
5976
table.add_column("Qty", justify="right")
6077
table.add_column("Legs")
6178

6279
for i, order in enumerate(orders):
80+
total_price = ZERO
81+
# handle ratio spreads
82+
quantity = gcd(*[int(leg.quantity or 0) for leg in order.legs])
83+
for leg in order.legs:
84+
if leg.quantity:
85+
m = 1 if "Sell" in leg.action.value else -1
86+
total_price += m * marks[leg.symbol] * leg.quantity / quantity
6387
row = [
6488
str(i + 1),
6589
order.updated_at.strftime("%Y-%m-%d %H:%M"),
@@ -68,6 +92,7 @@ def live(
6892
order.order_type.value,
6993
order.time_in_force.value,
7094
conditional_color(order.price) if order.price else "--",
95+
conditional_color(total_price),
7196
conditional_quantity(order.legs[0].quantity or ZERO, order.legs[0].action),
7297
order.legs[0].symbol,
7398
]
@@ -83,6 +108,7 @@ def live(
83108
"",
84109
"",
85110
"",
111+
"",
86112
conditional_quantity(
87113
order.legs[i].quantity or ZERO, order.legs[i].action
88114
),

ttcli/plot.py

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import os
2+
import shutil
23
import tempfile
34
from datetime import datetime, time, timedelta
45
from enum import Enum
@@ -13,7 +14,7 @@
1314

1415
from ttcli.utils import AsyncTyper, RenewableSession, print_error
1516

16-
plot = AsyncTyper(help="Plot candle charts, portfolio P&L, or net liquidating value.")
17+
plot = AsyncTyper(help="Plot candle charts for any symbol.", no_args_is_help=True)
1718
fmt = "%Y-%m-%d %H:%M:%S"
1819

1920

@@ -49,6 +50,12 @@ def get_start_time(width: CandleType) -> datetime:
4950

5051

5152
def gnuplot(sesh: RenewableSession, symbol: str, candles: list[str]) -> None:
53+
if not shutil.which("gnuplot"):
54+
print_error(
55+
"Please install gnuplot on your system to use the plot module: "
56+
"[link=http://www.gnuplot.info]http://www.gnuplot.info[/link]"
57+
)
58+
return
5259
gnu = Gnuplot()
5360
tmp = tempfile.NamedTemporaryFile(delete=False)
5461
with open(tmp.name, "w") as f:
@@ -84,7 +91,7 @@ def gnuplot(sesh: RenewableSession, symbol: str, candles: list[str]) -> None:
8491
os.system("clear")
8592

8693

87-
@plot.command(help="Plot candle chart for the given symbol.")
94+
@plot.command(help="Plot candle chart for the given symbol.", no_args_is_help=True)
8895
async def stock(
8996
symbol: str,
9097
width: Annotated[
@@ -111,7 +118,7 @@ async def stock(
111118
gnuplot(sesh, symbol, candles)
112119

113120

114-
@plot.command(help="Plot candle chart for the given symbol.")
121+
@plot.command(help="Plot candle chart for the given symbol.", no_args_is_help=True)
115122
async def crypto(
116123
symbol: str,
117124
width: Annotated[
@@ -148,7 +155,7 @@ async def crypto(
148155
gnuplot(sesh, crypto.symbol, candles)
149156

150157

151-
@plot.command(help="Plot candle chart for the given symbol.")
158+
@plot.command(help="Plot candle chart for the given symbol.", no_args_is_help=True)
152159
async def future(
153160
symbol: str,
154161
width: Annotated[

ttcli/portfolio.py

Lines changed: 21 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,9 @@
4141
round_to_tick_size,
4242
)
4343

44-
portfolio = AsyncTyper(help="View positions and stats for your portfolio.")
44+
portfolio = AsyncTyper(
45+
help="View positions and stats for your portfolio.", no_args_is_help=True
46+
)
4547

4648

4749
def get_indicators(today: date, metrics: MarketMetricInfo) -> str:
@@ -186,7 +188,7 @@ async def positions(
186188
table.add_column("Net Liq", justify="right")
187189
table.add_column("Indicators", justify="center")
188190
sums = defaultdict(lambda: ZERO)
189-
closing: dict[int, TradeableTastytradeData] = {}
191+
closing: list[TradeableTastytradeData] = []
190192
for i, pos in enumerate(positions):
191193
row = [f"{i + 1}"]
192194
mark = pos.mark or ZERO
@@ -199,7 +201,7 @@ async def positions(
199201
# instrument-specific calculations
200202
if pos.instrument_type == InstrumentType.EQUITY_OPTION:
201203
o = options_dict[pos.symbol]
202-
closing[i + 1] = o
204+
closing.append(o)
203205
# BWD = beta * stock price * delta / index price
204206
delta = greeks_dict[o.streamer_symbol].delta * 100 * m
205207
theta = greeks_dict[o.streamer_symbol].theta * 100 * m
@@ -216,7 +218,7 @@ async def positions(
216218
pnl_day = day_change * pos.quantity * pos.multiplier
217219
elif pos.instrument_type == InstrumentType.FUTURE_OPTION:
218220
o = future_options_dict[pos.symbol]
219-
closing[i + 1] = o
221+
closing.append(o)
220222
delta = greeks_dict[o.streamer_symbol].delta * 100 * m
221223
theta = greeks_dict[o.streamer_symbol].theta * 100 * m
222224
gamma = greeks_dict[o.streamer_symbol].gamma * 100 * m
@@ -243,7 +245,7 @@ async def positions(
243245
metrics = metrics_dict[pos.symbol]
244246
e = equity_dict[pos.symbol]
245247
ticks = e.tick_sizes or []
246-
closing[i + 1] = e
248+
closing.append(e)
247249
beta = metrics.beta or 0
248250
indicators = get_indicators(today, metrics)
249251
bwd = beta * mark_price * delta / spy
@@ -258,7 +260,7 @@ async def positions(
258260
delta = pos.quantity * m * 100
259261
f = futures_dict[pos.symbol]
260262
ticks = f.tick_sizes or []
261-
closing[i + 1] = f
263+
closing.append(f)
262264
# BWD = beta * stock price * delta / index price
263265
metrics = metrics_dict[f.future_product.root_symbol] # type: ignore
264266
indicators = get_indicators(today, metrics)
@@ -281,7 +283,7 @@ async def positions(
281283
pos.quantity = round(pos.quantity, 2)
282284
c = crypto_dict[pos.symbol]
283285
ticks = [TickSize(value=c.tick_size)]
284-
closing[i + 1] = c
286+
closing.append(c)
285287
day_change = mark_price - prev_close(c.symbol)
286288
pnl_day = day_change * pos.quantity * pos.multiplier
287289
else:
@@ -350,14 +352,18 @@ async def positions(
350352
close = get_confirmation("Close out a position? y/N ", default=False)
351353
if not close:
352354
return
353-
# get the position(s) to close
354-
to_close = input(
355-
"Enter the number(s) of the leg(s) to include in closing order, separated by commas: "
356-
)
357-
if not to_close:
358-
return
359-
to_close = [int(i) for i in to_close.split(",")]
360-
close_objs = [closing[i] for i in to_close]
355+
if len(closing) > 1:
356+
# get the position(s) to close
357+
to_close = input(
358+
"Enter the number(s) of the leg(s) to include in closing order, separated by commas: "
359+
)
360+
if not to_close:
361+
return
362+
to_close = [int(i) for i in to_close.split(",")]
363+
close_objs = [closing[i - 1] for i in to_close]
364+
else:
365+
print("Auto-selected the only position available.")
366+
close_objs = [closing[0]]
361367
account_number = pos_dict[close_objs[0].symbol].account_number
362368
if any(pos_dict[o.symbol].account_number != account_number for o in close_objs):
363369
print("All legs must be in the same account!")

ttcli/trade.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,12 +26,15 @@
2626
round_to_width,
2727
)
2828

29-
trade = Typer(help="Buy or sell stocks/ETFs, crypto, and futures.")
29+
trade = Typer(
30+
help="Buy or sell stocks/ETFs, crypto, and futures.", no_args_is_help=True
31+
)
3032

3133

3234
@trade.command(
3335
help="Buy or sell stocks/ETFs.",
3436
context_settings={"ignore_unknown_options": True},
37+
no_args_is_help=True,
3538
)
3639
def stock(
3740
symbol: str,
@@ -125,7 +128,7 @@ def stock(
125128
acc.place_order(sesh, order, dry_run=False)
126129

127130

128-
@trade.command(help="Buy cryptocurrency.")
131+
@trade.command(help="Buy cryptocurrency.", no_args_is_help=True)
129132
def crypto(symbol: str, quantity: Annotated[Decimal, Argument(parser=decimalify)]):
130133
sesh = RenewableSession()
131134
symbol = symbol.upper()
@@ -219,6 +222,7 @@ def crypto(symbol: str, quantity: Annotated[Decimal, Argument(parser=decimalify)
219222
@trade.command(
220223
help="Buy or sell futures.",
221224
context_settings={"ignore_unknown_options": True},
225+
no_args_is_help=True,
222226
)
223227
def future(
224228
symbol: str,

ttcli/watchlist.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,9 @@
77

88

99
@watchlist.command(
10-
name="wl", help="Show prices and metrics for symbols in the given watchlist."
10+
name="wl",
11+
help="Show prices and metrics for symbols in the given watchlist.",
12+
no_args_is_help=True,
1113
)
1214
def filter(name: str):
1315
print_warning("This functionality hasn't been implemented yet!")

0 commit comments

Comments
 (0)