Skip to content

Commit

Permalink
Optionally only write calls on excess positions
Browse files Browse the repository at this point in the history
If you want to hold a stock/ETF, you may wish to avoid writing any calls
at all on it, except for the positions in excess of your desired
allocation. This may be preferable when you're bullish and want to avoid
missing out on upside.

For this case, you can just set either `write_when.calls.excess_only =
true` for the entire portfolio, or `symbols.<symbol>.calls.excess_only =
true` on a per-symbol basis.
  • Loading branch information
brndnmtthws committed Aug 21, 2024
1 parent 721c887 commit 3da9a8e
Show file tree
Hide file tree
Showing 5 changed files with 45 additions and 7 deletions.
18 changes: 18 additions & 0 deletions thetagang.toml
Original file line number Diff line number Diff line change
Expand Up @@ -267,6 +267,19 @@ calculate_net_contracts = false
# `symbols.<symbol>.calls.cap_target_floor`.
cap_target_floor = 0.0

# If set to true, calls are only written on the underlying when the underlying
# has an excess of shares. This is useful for covered calls, where you only
# want to write calls when you have more shares than you want to hold
# long-term. It also provides a way to rebalance your portfolio by writing
# calls and taking profits.
#
# This may also be set per-symbol with `symbols.<symbol>.calls.excess_only`,
# which takes precedence.
#
# When this is set to true, the `cap_factor` and `cap_target_floor` values are
# ignored.
excess_only = false

[write_when.puts]
# Optionally, only write puts when the underlying is red
green = false
Expand Down Expand Up @@ -420,6 +433,11 @@ minimum_open_interest = 10
cap_factor = 1.0
cap_target_floor = 0.0

# Optionally, only write calls when the underlying has an excess of shares
# when set to `true`. This overrides the `write_when.calls.excess_only`
# value.
excess_only = false

[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: Dict[str, Dict[str, Any]]) -> None:
Optional("red"): bool,
Optional("cap_factor"): And(float, lambda n: 0 <= n <= 1),
Optional("cap_target_floor"): And(float, lambda n: 0 <= n <= 1),
Optional("excess_only"): bool,
},
Optional("puts"): {
Optional("green"): bool,
Expand Down Expand Up @@ -174,6 +175,7 @@ def validate_config(config: Dict[str, Dict[str, Any]]) -> None:
Optional("maintain_high_water_mark"): bool,
Optional("cap_factor"): And(float, lambda n: 0 <= n <= 1),
Optional("cap_target_floor"): And(float, lambda n: 0 <= n <= 1),
Optional("excess_only"): bool,
Optional("write_when"): {
Optional("green"): bool,
Optional("red"): bool,
Expand Down
1 change: 1 addition & 0 deletions thetagang/config_defaults.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
"red": False,
"cap_factor": 1.0,
"cap_target_floor": 0.0,
"excess_only": False,
},
},
"roll_when": {
Expand Down
7 changes: 6 additions & 1 deletion thetagang/thetagang.py
Original file line number Diff line number Diff line change
Expand Up @@ -218,7 +218,12 @@ def start(config_path: str, without_ibc: bool = False) -> None:
"=",
f"{pfmt(config['write_when']['calls']['cap_target_floor'])}",
)

config_table.add_row(
"",
"Excess only",
"=",
f"{config['write_when']['calls']['excess_only']}",
)
config_table.add_section()
config_table.add_row("[spring_green1]When contracts are ITM")
config_table.add_row(
Expand Down
24 changes: 18 additions & 6 deletions thetagang/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -278,13 +278,16 @@ def get_strike_limit(
def get_target_calls(
config: Dict[str, Any], symbol: str, current_shares: int, target_shares: int
) -> int:
cap_factor = get_cap_factor(config, symbol)
cap_target_floor = get_cap_target_floor(config, symbol)
min_uncovered = (target_shares * cap_target_floor) // 100
max_covered = (current_shares * cap_factor) // 100
total_coverable = current_shares // 100
if write_excess_calls_only(config, symbol):
return max([0, (current_shares - target_shares) // 100])
else:
cap_factor = get_cap_factor(config, symbol)
cap_target_floor = get_cap_target_floor(config, symbol)
min_uncovered = (target_shares * cap_target_floor) // 100
max_covered = (current_shares * cap_factor) // 100
total_coverable = current_shares // 100

return max([0, math.floor(min([max_covered, total_coverable - min_uncovered]))])
return max([0, math.floor(min([max_covered, total_coverable - min_uncovered]))])


def get_write_threshold_sigma(
Expand Down Expand Up @@ -412,3 +415,12 @@ def trading_is_allowed(config: Dict[str, Any], symbol: str) -> bool:
"no_trading" not in config["symbols"][symbol]
or not config["symbols"][symbol]["no_trading"]
)


def write_excess_calls_only(config: Dict[str, Any], symbol: str) -> bool:
if (
"calls" in config["symbols"][symbol]
and "excess_only" in config["symbols"][symbol]["calls"]
):
return config["symbols"][symbol]["calls"]["excess_only"]
return config["write_when"]["calls"]["excess_only"]

0 comments on commit 3da9a8e

Please sign in to comment.