Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Optionally maintain a high water mark w/ CCs #341

Merged
merged 1 commit into from
Dec 16, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions thetagang.toml
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,25 @@ credit_only = false
# rolling calls regardless of the total quantity.
has_excess = true

# We can optionally maintain the high water mark for covered calls by never
# rolling CCs down, only out. We do this because it's not uncommon for a sharp
# bounce up after a stock/index falls enough to make the contract eligible for
# rolling. If we roll the CC down and out, we might be capping our gains when it
# eventually does bounce back. The trade off here is that you end up with
# slightly less downside protection.
#
# This only applies to rolling calls, and is essentially equivalent to setting
# the minimum strike of the next contract to the previous strike and only
# allowing the value to ratchet upwards.
#
# While this is off by default, it would be sensible to turn it on if you're
# trying to capture upside, rather than protect downside.
#
# This value can also be set per-symbol, with
# `symbols.<symbol>.calls.maintain_high_water_mark` (see `symbols.QQQ` example
# below).
maintain_high_water_mark = false

[roll_when.puts]
# Roll puts if they're in the money. Defaults to false if not specified.
itm = false
Expand Down Expand Up @@ -289,6 +308,8 @@ write_threshold = 0.01 # 1%, absolute value
[symbols.QQQ.calls]
strike_limit = 100.0 # never write a call with a strike below $100

maintain_high_water_mark = true # maintain the high water mark when rolling calls

[symbols.TLT]
weight = 0.2
# parts = 20
Expand Down
2 changes: 2 additions & 0 deletions thetagang/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@ def validate_config(config):
Optional("itm"): bool,
Optional("credit_only"): bool,
Optional("has_excess"): bool,
Optional("maintain_high_water_mark"): bool,
},
Optional("puts"): {
Optional("itm"): bool,
Expand Down Expand Up @@ -147,6 +148,7 @@ def validate_config(config):
Optional("write_threshold"): And(float, lambda n: 0 <= n <= 1),
Optional("write_threshold_sigma"): And(float, lambda n: n > 0),
Optional("strike_limit"): And(float, lambda n: n > 0),
Optional("maintain_high_water_mark"): bool,
},
Optional("puts"): {
Optional("delta"): And(float, lambda n: 0 <= n <= 1),
Expand Down
7 changes: 6 additions & 1 deletion thetagang/config_defaults.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,12 @@
"roll_when": {
"min_pnl": 0.0,
"close_at_pnl": 1.0,
"calls": {"itm": True, "credit_only": False, "has_excess": True},
"calls": {
"itm": True,
"credit_only": False,
"has_excess": True,
"maintain_high_water_mark": False,
},
"puts": {"itm": False, "credit_only": False, "has_excess": True},
},
"vix_call_hedge": {
Expand Down
3 changes: 3 additions & 0 deletions thetagang/portfolio_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
get_target_delta,
get_write_threshold_perc,
get_write_threshold_sigma,
maintain_high_water_mark,
midpoint_or_market_price,
net_option_positions,
portfolio_positions_to_dict,
Expand Down Expand Up @@ -1194,6 +1195,8 @@ def roll_positions(
max([strike_limit or 0] + average_cost),
2,
)
if maintain_high_water_mark(self.config, symbol):
strike_limit = max([strike_limit, position.contract.strike])

elif right.startswith("P"):
strike_limit = round(
Expand Down
10 changes: 9 additions & 1 deletion thetagang/thetagang.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
get_target_delta,
get_write_threshold_perc,
get_write_threshold_sigma,
maintain_high_water_mark,
)

from .portfolio_manager import PortfolioManager
Expand Down Expand Up @@ -163,7 +164,12 @@ def start(config_path, without_ibc=False):
"=",
f"{config['roll_when']['calls']['has_excess']}",
)

config_table.add_row(
"",
"Calls: maintain high water mark",
"=",
f"{config['roll_when']['calls']['maintain_high_water_mark']}",
)
config_table.add_section()
config_table.add_row("[spring_green1]When writing new contracts")
config_table.add_row(
Expand Down Expand Up @@ -247,6 +253,7 @@ def start(config_path, without_ibc=False):
symbols_table.add_column("Call delta", justify="right")
symbols_table.add_column("Call strike limit", justify="right")
symbols_table.add_column("Call threshold", justify="right")
symbols_table.add_column("HWM", justify="right")
symbols_table.add_column("Put delta", justify="right")
symbols_table.add_column("Put strike limit", justify="right")
symbols_table.add_column("Put threshold", justify="right")
Expand All @@ -259,6 +266,7 @@ def start(config_path, without_ibc=False):
f"{ffmt(get_write_threshold_sigma(config, symbol, 'C'))}σ"
if get_write_threshold_sigma(config, symbol, "C")
else pfmt(get_write_threshold_perc(config, symbol, "C")),
str(maintain_high_water_mark(config, symbol)),
ffmt(get_target_delta(config, symbol, "P")),
dfmt(get_strike_limit(config, symbol, "P")),
f"{ffmt(get_write_threshold_sigma(config, symbol, 'P'))}σ"
Expand Down
9 changes: 9 additions & 0 deletions thetagang/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -227,3 +227,12 @@ def algo_params_from(params):

def get_minimum_credit(config: dict) -> float:
return config["orders"].get("minimum_credit", 0.0)


def maintain_high_water_mark(config: dict, symbol: str) -> bool:
if (
"calls" in config["symbols"][symbol]
and "maintain_high_water_mark" in config["symbols"][symbol]["calls"]
):
return config["symbols"][symbol]["calls"]["maintain_high_water_mark"]
return config["roll_when"]["calls"]["maintain_high_water_mark"]