Skip to content

Commit

Permalink
Optionally always roll ITM contracts
Browse files Browse the repository at this point in the history
We might want to avoid assignment, so we can always roll contracts once
they become ITM.

Setting `roll_when.calls/puts.always_when_itm=true` will roll short
contracts once they're ITM.
  • Loading branch information
brndnmtthws committed May 16, 2024
1 parent 63e4026 commit 6e37759
Show file tree
Hide file tree
Showing 5 changed files with 64 additions and 1 deletion.
17 changes: 17 additions & 0 deletions thetagang.toml
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,19 @@ min_pnl = 0.0
# true if not specified.
itm = true

# Always roll calls (short-circuit) when they're in the money, regardless of
# the P&L. This allows you to avoid assignment risk by eating the cost of
# rolling the calls. This is useful if you want to avoid assignment risk
# either because you don't own the underlying (i.e., with spreads) or because
# you don't want to realize a gain/loss yet. Note that if it's rolling early
# (i.e., before `roll_when.dte`) it may only roll a subset of the contracts
# based on the `target.maximum_new_contracts_percent` value.
#
# This can also be set for puts with `roll_when.puts.always_when_itm`. This
# option, when enabled, takes precedence over `roll_when.pnl` and
# `roll_when.min_pnl`.
always_when_itm = false

# Only roll when there's a suitable contract available that will result in a
# credit. Enabling this may result in the target delta value being ignored in
# circumstances where we can't find a contract that will result in both a
Expand Down Expand Up @@ -171,6 +184,10 @@ min_pnl = 0.0
# Roll puts if they're in the money. Defaults to false if not specified.
itm = false

# See comments above for `roll_when.calls.always_when_itm` for details on this
# option (which behaves the same for puts as with calls).
always_when_itm = false

# Only roll when there's a suitable contract available that will result in a
# credit. Enabling this may result in the target delta value being ignored in
# circumstances where we can't find a contract that will result in both a
Expand Down
2 changes: 2 additions & 0 deletions thetagang/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -114,12 +114,14 @@ def validate_config(config: Dict[str, Dict[str, Any]]) -> None:
Optional("max_dte"): And(int, lambda n: 1 <= n),
Optional("calls"): {
Optional("itm"): bool,
Optional("always_when_itm"): bool,
Optional("credit_only"): bool,
Optional("has_excess"): bool,
Optional("maintain_high_water_mark"): bool,
},
Optional("puts"): {
Optional("itm"): bool,
Optional("always_when_itm"): bool,
Optional("credit_only"): bool,
Optional("has_excess"): bool,
},
Expand Down
8 changes: 7 additions & 1 deletion thetagang/config_defaults.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,17 @@
"close_at_pnl": 1.0,
"calls": {
"itm": True,
"always_when_itm": False,
"credit_only": False,
"has_excess": True,
"maintain_high_water_mark": False,
},
"puts": {"itm": False, "credit_only": False, "has_excess": True},
"puts": {
"itm": False,
"always_when_itm": False,
"credit_only": False,
"has_excess": True,
},
},
"vix_call_hedge": {
"enabled": False,
Expand Down
26 changes: 26 additions & 0 deletions thetagang/portfolio_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -283,6 +283,19 @@ def put_can_be_rolled(self, put: PortfolioItem, table: Table) -> bool:
if put.position > 0:
return False

if (
isinstance(put.contract, Option)
and self.put_is_itm(put.contract)
and self.config["roll_when"]["puts"]["always_when_itm"]
):
table.add_row(
f"{put.contract.localSymbol}",
"[blue]Roll",
f"[blue]Will be rolled because put is ITM "
f"and always_when_itm={self.config['roll_when']['puts']['always_when_itm']}",
)
return True

# Check if this put is ITM, and if it's o.k. to roll
if (
not self.config["roll_when"]["puts"]["itm"]
Expand Down Expand Up @@ -368,6 +381,19 @@ def call_can_be_rolled(self, call: PortfolioItem, table: Table) -> bool:
if call.position > 0:
return False

if (
isinstance(call.contract, Option)
and self.call_is_itm(call.contract)
and self.config["roll_when"]["calls"]["always_when_itm"]
):
table.add_row(
f"{call.contract.localSymbol}",
"[blue]Roll",
f"[blue]Will be rolled because call is ITM "
f"and always_when_itm={self.config['roll_when']['calls']['always_when_itm']}",
)
return True

# Check if this call is ITM, and it's o.k. to roll
if (
not self.config["roll_when"]["calls"]["itm"]
Expand Down
12 changes: 12 additions & 0 deletions thetagang/thetagang.py
Original file line number Diff line number Diff line change
Expand Up @@ -221,12 +221,24 @@ def start(config_path: str, without_ibc: bool = False) -> None:
"=",
f"{config['roll_when']['puts']['itm']}",
)
config_table.add_row(
"",
"Roll puts always",
"=",
f"{config['roll_when']['puts']['always_when_itm']}",
)
config_table.add_row(
"",
"Roll calls",
"=",
f"{config['roll_when']['calls']['itm']}",
)
config_table.add_row(
"",
"Roll calls always",
"=",
f"{config['roll_when']['calls']['always_when_itm']}",
)

config_table.add_section()
config_table.add_row("[spring_green1]Write options with targets of")
Expand Down

0 comments on commit 6e37759

Please sign in to comment.