Closed vpintea closed 3 months ago
oops, sorry about that. The import shouldn't be there because it's for something I'm still testing locally and the import was committed by mistake.
For now just delete the from . import agent
line and I'll do a cleanup commit to remove it soon.
Thank you! Looks like there are a few import issues but that one is resolved with your suggestion.
Also, is there a way to configure the .env.icli file to account for multiple accounts? Say you want to put orders through more than 1 account.
I added a new commit with those import/paren/startup bugs fixed. Thanks for pointing those out! I've not really seen anybody try to use this before other than myself, so I hadn't seen it break like that before.
As for multiple accounts, you would just have to run multiple clients each with unique ICLI_IBKR_ACCOUNT_ID
values in different terminal windows. There's not currently support for multiple accounts or all the other non-individual IBKR account types: https://www.interactivebrokers.com/en/accounts/account-guide.php
I'll try to watch out for partial commits doing bad things in the future (also the entire README needs a rewrite, but current commands can be found with ?
then per-command docs can be shown with usage like buy?
and the commit history is the official changelog as features are updated/changed/added over time.)
Thanks Matt. This is outstanding work!
I am still trying to figure out how to use the CLI to enter in bracket orders. For example, buy TSLA240315C00130000 when TSLA stock price triggers 130. I couldn't find the command in the lang.py file. As well how do you enter stop loss/trailing stop orders with an initial trade. Like:
buy TSLA240315C00130000 when TSLA stock price triggers 130, and stop loss at 10%. Do I have to manually enter in 3 orders? Is this even possible?
You are correct about the missing bracket orders. There is technically a little logic for it here but it's not attached to any commands anymore because I stopped using it: https://github.com/mattsta/icli/blob/main/icli/cli.py#L1148-L1225
Short version: there's currently no way to enter bracket orders.
I have thought about adding a manual event ability where you could just have your client manage trigger conditions (e.g. "when price of SYMBOL is OVER $X for N seconds -> execute commands"), but I haven't gotten around to it yet.
For entering orders, I use either buy
or limit
to create the order (Buy/Sell to Open) then exit with either limit
(for the Sell/Buy to Close) or evict
or use buy
again (the buy
command supports negative quantity or price to trigger a sell (or short) condition).
Also, if you are ordering for a visible quote, you can use :N
shorthand like buy :25 $10_000 AF preview
or even limit :25
to pre-load the symbol into the menu. The :N
syntax also works with adding spread quotes like: add "buy 1 :25 sell 1 :26"
for regular option spreads or even add "buy 100 :19 sell 1 :30"
for stock buy+write spreads (since there's 100 units of stock for every 1 option). Spread quotes can be executed using the spread
command since spreads operate differently from single leg orders.
Long version: Brackets with options are unstable since most options can bounce ±20% normally (unless you bought directly at the start of a very strong directional trend). Things with low prices and low liquidity like options means it doesn't take much price movement to equal huge percentage swings (plus all the wide spreads unless you are on AAPL or SPY essentially).
If you trigger a stop with options, you'll probably be very unhappy because option depth is mostly dynamic and often thin where you may fill the top bid then get sunk into 75% lower prices immediately before new bids fill in the liquidity gap (also, stops on options mean you end up paying the entire spread and the spread itself can be 5% to 25% off the ask depending on liquidity).
Here are some book depth
command examples from my logs showing the different resting limit prices at book depth waiting for something mispriced to reach them (which they would then immediately turn around and sell for a profit):
2023-11-21 11:09:18.661 | INFO | icli.lang:run:845 - NVDA :: NVDA 231208P00400000 Grouped by Price :: Row count: 16
2023-11-21 11:09:18.663 | FRAME | icli.lang:run:845 -
Bids Asks
price size marketMaker price size marketMaker
0 1.00 142 [AMEX, CBOE, ISE, MIAX, NASDAQOM] 1.05 403 [AMEX]
1 0.99 32 [AMEX, ISE, NASDAQOM] 1.06 102 [AMEX, BATS, CBOE, CBOE2, EDGX, EMERALD, ISE, MIAX, NASDAQBX, NASDAQOM, PEARL, PSE]
2 0.98 39 [AMEX, NASDAQOM] 1.10 1 [AMEX, ISE, NASDAQOM, PHLX]
3 0.94 5 [BATS, CBOE2, EDGX, EMERALD, NASDAQBX, NASDAQOM, PEARL, PHLX, PSE] 1.36 1 [AMEX, NASDAQOM]
4 0.92 5 [ISE] 1.50 1 [AMEX, NASDAQOM]
5 0.88 1 [ISE] 1.54 6 [ISE]
6 0.85 1 [ISE, NASDAQOM, PSE] 1.55 33 [ISE]
7 0.66 1 [PSE] 2.08 1 [ISE]
8 0.49 42 [PSE] 6.00 1 [NASDAQOM]
9 0.20 2 [PSE] 8.25 1 [NASDAQOM]
10 -1 -1 -1 9.55 1 [NASDAQOM]
11 -1 -1 -1 9.65 1 [PSE]
12 -1 -1 -1 16.25 1 [PSE]
13 -1 -1 -1 19.40 20 [PSE]
14 -1 -1 -1 20.00 4 [PSE]
2024-02-16 11:21:46.022 | INFO | icli.lang:run:888 - SMCI :: SMCI 240315C01000000 Grouped by Price :: Row count: 25
2024-02-16 11:21:46.025 | FRAME | icli.lang:run:888 -
Bids Asks
price size marketMaker price size marketMaker
0 56.60 1 [AMEX] 58.90 860 [AMEX]
1 55.00 14 [AMEX, BATS, NASDAQBX, NASDAQOM, PEARL] 59.80 1 [AMEX]
2 54.60 8 [AMEX, CBOE, EDGX, EMERALD, ISE, MIAX, NASDAQOM, PHLX] 60.00 1 [AMEX, BATS, CBOE, CBOE2, EDGX, EMERALD, ISE, MIAX, NASDAQBX, NASDAQOM, PEARL, PHLX, PSE]
3 54.50 1 [AMEX, PSE] 61.20 1 [AMEX, ISE, PSE]
4 53.20 1 [AMEX, PSE] 62.00 3 [AMEX, PSE]
5 52.80 1 [CBOE2] 62.30 1 [ISE]
6 52.50 2 [ISE] 62.50 4 [ISE]
7 52.40 1 [ISE] 63.60 6 [ISE, NASDAQOM]
8 52.30 1 [ISE] 63.70 1 [NASDAQOM]
9 52.10 1 [ISE, NASDAQOM] 64.00 1 [NASDAQOM]
10 52.00 4 [NASDAQOM] 64.10 1 [NASDAQOM]
11 51.90 1 [NASDAQOM] 64.80 1 [NASDAQOM]
12 51.30 8 [NASDAQOM] 65.60 1 [NASDAQOM]
13 51.20 1 [NASDAQOM] 65.90 1 [NASDAQOM]
14 50.50 1 [NASDAQOM] 66.00 1 [NASDAQOM]
15 50.00 28 [NASDAQOM] 67.10 1 [NASDAQOM]
16 48.80 1 [NASDAQOM] 67.40 1 [NASDAQOM]
17 48.60 1 [NASDAQOM] 67.70 1 [NASDAQOM]
18 47.70 1 [NASDAQOM] 68.80 1 [NASDAQOM]
19 47.40 1 [NASDAQOM] 77.70 1 [NASDAQOM]
20 47.10 1 [NASDAQOM] 120.00 1 [PSE]
21 46.00 1 [PSE] 168.00 27 [PSE]
22 45.90 1 [PSE] -1 -1 -1
23 37.70 1 [PSE] -1 -1 -1
sum -1 82 -1 -1 917 -1
2024-02-20 11:50:43.381 | INFO | icli.lang:run:888 - AMD :: AMD 240315C00200000 Grouped by Price :: Row count: 20
2024-02-20 11:50:43.384 | FRAME | icli.lang:run:888 -
Bids Asks
price size marketMaker price size marketMaker
0 0.97 810 [AMEX] 0.99 121 [AMEX]
1 0.96 81 [AMEX] 1.00 158 [AMEX]
2 0.85 7 [AMEX] 1.15 2 [AMEX, BATS, CBOE, CBOE2, EDGX, EMERALD, ISE, MEMX, MIAX, NASDAQBX, NASDAQOM, PEARL, PSE]
3 0.83 4 [AMEX, BATS, CBOE, CBOE2, EMERALD, ISE, MEMX, MIAX, NASDAQBX, NASDAQOM, PEARL, PSE] 1.38 10 [AMEX, ISE, NASDAQOM, PHLX, PSE]
4 0.78 1 [AMEX, EDGX, ISE, PHLX] 1.49 10 [AMEX, NASDAQOM]
5 0.65 1 [ISE] 1.50 16 [ISE]
6 0.47 18 [ISE] 1.75 1 [ISE, NASDAQOM]
7 0.46 10 [ISE, NASDAQOM] 1.98 1 [ISE, NASDAQOM]
8 0.45 1 [NASDAQOM] 2.00 1 [NASDAQOM]
9 0.23 1 [NASDAQOM] 2.23 1 [NASDAQOM]
10 0.18 2 [NASDAQOM] 2.40 2 [NASDAQOM]
11 0.17 1 [NASDAQOM, PSE] 2.50 1 [NASDAQOM]
12 0.15 1 [NASDAQOM, PSE] 4.15 2 [NASDAQOM]
13 0.05 2 [PSE] 4.20 2 [NASDAQOM]
14 0.01 1 [PSE] 4.25 1 [NASDAQOM]
sum -1 941 -1 -1 363 -1
15 -1 -1 -1 4.80 25 [NASDAQOM]
16 -1 -1 -1 5.00 6 [PSE]
17 -1 -1 -1 10.00 1 [PSE]
18 -1 -1 -1 12.25 2 [PSE]
2024-02-22 09:52:28.282 | INFO | icli.lang:run:888 - NDX :: NDXP 240222C18000000 Grouped by Price :: Row count: 7
2024-02-22 09:52:28.284 | FRAME | icli.lang:run:888 -
Bids Asks
price size marketMaker price size marketMaker
0 9.80 1 [ISE] 10.90 51 [ISE]
1 6.60 3 [ISE] 14.30 1 [ISE]
2 4.40 9 [ISE] 17.90 1 [ISE]
3 3.20 1 [ISE] 18.20 9 [ISE]
4 1.70 1 [ISE] 19.80 2 [ISE, PHLX]
5 0.60 1 [PHLX] -1 -1 -1
sum -1 16 -1 -1 64 -1
Imagine you had 10 of those NDXP240222C18000000
then some stop condition triggers and you end up selling most of your position for $4 instead of $9 because there was nobody else behind the current bid at the exact second your order executed.
Especially with very thin things like index options, a stop is just a way to give some other market participant a 50% to 90% discount off the current value if a stop of any size is triggered.
As a more concrete example, one time I bought some worthless $0.01 calls expecting a squeeze, it did start to squeeze, then I set my limit sell price to $15 (highly unreasonable since these were like 50% to 80% OTM) — then, somebody's market order swept the liquidity in my call strike and my worthless options sold for $15 each. I imagine the buyer was very unhappy, but this is why we don't use market orders with options.
But then what's the answer? well, that's where the bad imports you originally saw should work better. We don't actually want static bracket "take profit / stop loss," but rather we want to hold a position while price is confirmed going up then sell when price is confirmed going down. The (unreleased) integrated price+algo system should be more capable than static bracket orders because it follows actual trend changes instead of trying to predict the future price ranges (since we also know the market loves to "stop hunt" by falling just under a support level so people think it's selling off (where people placed their stops!) then everything reverses higher immediately after the false breakdown).
For now I'm still just following the traditional pattern of "create order, panic at ±5% from breakeven, see profit, sell" until the auto-price-algo-level system is more reliable.
Thanks Matt for in depth reply/explanation. I did see the bracket order function but it wasn't being used anywhere so wanted to check with you first.
What about conditional orders. Say NVDA crosses 900 and I want it to trigger an automatic buy or the ATM call option - I see your code can pull up the nearest ATM option) - and forget the stop order and take profit orders. How could I do this in the cli? Would I need to incorporate a tradingview webhook to send a trigger to the cli? Even if it's clunky it would be incredibly useful.
Say NVDA crosses 900 and I want it to trigger an automatic buy or the ATM call option
That's a clever idea, but it heavily depends on what "crosses" means and how we confirm "good" vs "false bounce" (again, we fear the dreaded "bull trap then immediately collapse" scenario).
It's one of those things where it either works great (like having a "buy if goes above $1 trigger" on days where index options go from $0.50 to $50 in 30 minutes), but other days something will bounce right at a trigger price, go up two more ticks, then collapse and never recover again.
Here are some fun (frustrating, annoying-because-not-taken) backtest results where it would work though for "buy above $X" 0dte from a week ago if taking a $10k position at the trigger price (output shows strike, algo duration, algo, final profit, max profit, max loss):
111 SPXW240308P05135000 18 total-since-0.50 $162,171.60 $162,171.60 $0.00
112 SPXW240308P05140000 18 total-since-1.00 $123,385.60 $123,385.60 $0.00
113 SPXW240308P05140000 15 total-since-1.00 $123,385.60 $123,385.60 $0.00
114 SPXW240308P05145000 18 total-since-1.00 $181,730.60 $181,730.60 $0.00
115 SPXW240308P05145000 15 total-since-1.00 $190,090.60 $190,090.60 $0.00
116 SPXW240308P05150000 180 grab-and-go-vwap-2.00 $101,702.40 $101,702.40 $0.00
117 SPXW240308P05150000 180 grab-and-go-vwap-2.25 $101,702.40 $101,702.40 $0.00
118 SPXW240308P05155000 18 total-since-2.25 $104,668.60 $104,668.60 $0.00
119 SPXW240308P05155000 15 total-since-2.25 $107,431.60 $107,431.60 $0.00
I'm still too afraid of fixed-definition triggers to run them for auto-trading yet. I do have a weird alert system for manual notification throughout the day of price anomalies though just to wake me up if prices suddenly all start moving at once (still all manual decisions on if it matters though):
2024-03-15 07:05:35.964 | INFO | awwdio.awwdio:speakerRunner:120 - [batch 49 :: 724 :: 5 :: 5 :: Samantha]: call VOLUME HIGH OF DAY 5 1 5 zero SPXW, BAR 18 (repeated 5)
2024-03-15 07:05:39.365 | INFO | awwdio.awwdio:speakerRunner:120 - [batch 50 :: 725 :: 1 :: 100 :: Alex]: LOW PRICE OF DAY, ASML @ 942.54
2024-03-15 07:05:41.357 | INFO | awwdio.awwdio:speakerRunner:120 - [batch 51 :: 726 :: 1 :: 100 :: Alex]: LOW PRICE OF DAY, NQ @ 18056.09
2024-03-15 07:05:43.479 | INFO | awwdio.awwdio:speakerRunner:120 - [batch 52 :: 727 :: 1 :: 100 :: Alex]: LOW PRICE OF DAY, MSFT @ 416.94
2024-03-15 07:05:45.562 | INFO | awwdio.awwdio:speakerRunner:120 - [batch 53 :: 728 :: 1 :: 100 :: Alex]: LOW volume OF DAY, SMCI @ 1093.34
2024-03-15 07:05:47.969 | INFO | awwdio.awwdio:speakerRunner:120 - [batch 54 :: 729 :: 1 :: 10 :: Allison (Enhanced)]: call DISTRIBUTION 5 1 35 SPXW, BAR 18
2024-03-15 07:05:50.595 | INFO | awwdio.awwdio:speakerRunner:120 - [batch 55 :: 730 :: 2 :: 10 :: Allison (Enhanced)]: call DISTRIBUTION 5 1 4 zero SPXW, BAR 18 (repeated 2)
Here's the opening from Thursday which went from "yay" to "oh no" quickly which keeps me afraid of trusting things like "over 900 go leveraged long"):
the 892
line is my current arbitrary LIS flip condition which keeps getting falsely breached too (4 false runs above then collapses on friday! great for "short the rip" decisions but less good for "buy the dip" mode):
It looks like you did find some of the option chain crawlers where fast
command sweeps a range of strikes which does work if you want to trust the current directions (and it can also go arbitrarily ITM with negative offsets or OTM with positive offsets). The fast
command has an annoying number of arguments (there's always tradeoffs between using positional args vs building a "real language" out of things), but it works like fast META c 5000 0 0 0.00 preview
to preview what would happen if buying $5,000 of ATM options (the last 0.00
parameter is percentage of underlying price to capture strikes, so if you have fast META c 5000 0 0 0.10 preview
it would use strikes from ATM up to 10% OTM but it spends more towards ATM than OTM to try and be somewhat responsible) — the fast
command with preview
is also just a simple way of bulk-adding quotes too.
Would I need to incorporate a tradingview webhook to send a trigger to the cli? Even if it's clunky it would be incredibly useful.
That could be technically be done since the icli
system is all just text commands, you could create a network listener to receive external commands (websockets is usually nice) and just run the input through the single runop()
command with (command, args)
format basically:
await self.dispatch.runop("add", "AAPL MSFT SPY QQQ", self.opstate)
I think the better answer to your question is in the unreleased algo trigger trading features, but they aren't public yet because the system is currently too trusting of bad external algo decisions and it can just get stuck in "buy high, sell low" too many times if the external algo decisions aren't calibrated properly.
Here's what the current working-but-unreleased auto-trading client looks like:
> asub?
asubscribe [symbolTrade] [symbolQuote] [evictByGlob] [accountPercent] [moneyPool] [algoBarDuration] [algo] [asset] [side] [count] [*preview]
> asub MSFT MSFT no $40_000 exact 35 volstop-1.75 direct both -1 preview
Overall, the algo system reads a custom external data feed having a format like:
{
"RTY": {
"15": {
"bar": {
"o": 1989.4,
"h": 1989.4,
"l": 1989.3,
"c": 1989.3,
"v": 5,
"vw": 1989.36,
"t": 1706045850
},
"algos": {
"volstop-6.75": {
"stopped": false,
"be": "long",
"stops": 1988.811901
}
},
"35": {
"bar": {
"o": 1989.5,
"h": 1989.5,
"l": 1989.1,
"c": 1989.3,
"v": 29,
"vw": 1989.255172,
"t": 1706045845
},
"algos": {
"volstop-1.00": {
"stopped": false,
"be": "short",
"stops": 1989.599494
}
}
}
}
So every symbol has multiple bar durations and each bar duration tracks its own algos and price history. Overall there's about 1,000 algos per symbol after combining all bar durations and algo settings (and you can see above: they often disagree depending on the timeframe and error bounds!).
The algo system checks the configured settings for all (quoteSymbol, duration, algo)
combinations then when stopped
becomes true
on any of them, it takes the new be
direction for the tradeSymbol
configured.
What all that means is you give it:
symbolTrade
and symbolQuote
: a symbol to trade and a symbol to quote against (example: maybe you want to trade /ES but using quotes from SPY triggers if you don't have /ES quotes, so you can give different trade-vs-trigger symbols)accountPerecent
: can either have it trade a dynamic percent of your account in each trade or a fixed dollar amountmoneyPool
: determines whether accountPercent
is a percent of the cash balance, buying power balance, or an exact unchanging dollar amountalgoBarDuration
: an update duration to use for the buy/sell checks (using 15 second bars? 90 second bars? 5 minute bars?)algo
: there are hundreds of possible combinations of algos and stop conditions, but we have to pick one to use for trade triggers. The worst outcome here is if your algo error bounds are perfectly offset from asset volatility and your algo repeatedly buys high thinking there's a new uptrend, but price collapses, it sells low, but price recovers for only 90 seconds, then it repeats "buy high, sell low" over and over again. The system doesn't have guards for "i'm having a bad day and should really quit" anywhere yet.asset
: whether to trade shares or ATM options on symbolTrade
when a buy/sell signal is triggered (since the algo system is aware of options, it can track options by flipping from long calls to long puts on direction changes).side
: whether to only allow long+exit or short+exit or always keep a position open by flipping long/short trades.count
: just if the algo should execute a fixed number of trades or unlimited trades (maybe you only want to use the algo condition to exit a current position, so you would set the algo to only short and only trigger once)(the command format should maybe even just allow loading from external configuration files too since having 11 positional arguments gets difficult quickly)
Overall, after all those configured parameters, the system just launches either a buy
command if the asset
is direct
or it uses fast {symbol} {P,C} {cash} 0 0 0 0
if the asset
is option
.
General conclusion: there's no external automated trigger system and improving the algo-formatted system and its various levels of trust and decision making is probably the next step towards automatic execution based on price movement or confirmed level breaches (auto scale-in to test level trust, add more as price is confirmed, quickly abandon if wrong, etc).
Thanks for indulging my verbose explanations. Sometimes just extra responses gives me a chance to think through more features and future directions and missing doc opportunities.
Sent you email (from gmail) to continue discussion as this thread was getting long and figured it might be easier :)
Having issues running this from terminal and from Pycharm. Installed poetry and created the venv. Any ideas?
❯ poetry run icli Traceback (most recent call last): File "", line 1, in
File "/usr/local/Cellar/python@3.12/3.12.2_1/Frameworks/Python.framework/Versions/3.12/lib/python3.12/importlib/init.py", line 90, in import_module
return _bootstrap._gcd_import(name[level:], package, level)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "", line 1387, in _gcd_import
File "", line 1360, in _find_and_load
File "", line 1331, in _find_and_load_unlocked
File "", line 935, in _load_unlocked
File "", line 995, in exec_module
File "", line 488, in _call_with_frames_removed
File "/Users/xxxx/xxxx/xxxx/icli/icli/main.py", line 7, in
import icli.cli as cli
File "/Users/xxxx/xxxx/xxxx/icli/icli/cli.py", line 46, in
from . import agent
ImportError: cannot import name 'agent' from 'icli' (/Users/xxxx/xxxx/xxxx/icli/icli/init.py)