Beispiel #1
0
    def check_for_uncovered_positions(self, portfolio_positions):
        for symbol in portfolio_positions:
            call_count = max([
                0,
                count_short_option_positions(symbol, portfolio_positions, "C")
            ])
            stock_count = math.floor(
                sum([
                    p.position for p in portfolio_positions[symbol]
                    if isinstance(p.contract, Stock)
                ]))
            strike_limit = math.ceil(
                max([get_strike_limit(self.config, symbol, "C") or 0] + [
                    p.averageCost for p in portfolio_positions[symbol]
                    if isinstance(p.contract, Stock)
                ]))

            target_calls = max([0, stock_count // 100])

            maximum_new_contracts = self.config["target"][
                "maximum_new_contracts"]
            calls_to_write = max(
                [0, min([target_calls - call_count, maximum_new_contracts])])

            if calls_to_write > 0:
                click.secho(
                    f"Need to write {calls_to_write} for {symbol}, capped at {maximum_new_contracts}, at or above strike ${strike_limit} (target_calls={target_calls}, call_count={call_count})",
                    fg="green",
                )
                self.write_calls(
                    symbol,
                    self.get_primary_exchange(symbol),
                    calls_to_write,
                    strike_limit,
                )
    def check_for_uncovered_positions(self, account_summary,
                                      portfolio_positions):
        for symbol in portfolio_positions:
            call_count = max([
                0,
                count_short_option_positions(symbol, portfolio_positions, "C")
            ])
            stock_count = math.floor(
                sum([
                    p.position for p in portfolio_positions[symbol]
                    if isinstance(p.contract, Stock)
                ]))
            strike_limit = math.ceil(
                max([get_strike_limit(self.config, symbol, "C") or 0] + [
                    p.averageCost or 0 for p in portfolio_positions[symbol]
                    if isinstance(p.contract, Stock)
                ]))

            target_calls = max([0, stock_count // 100])
            new_contracts_needed = target_calls - call_count
            excess_calls = call_count - target_calls

            if excess_calls > 0:
                click.secho(
                    f"Warning: {symbol} has {excess_calls} excess covered calls stock_count={stock_count}, call_count={call_count}",
                    fg="yellow",
                )

            maximum_new_contracts = self.get_maximum_new_contracts_for(
                symbol,
                self.get_primary_exchange(symbol),
                account_summary,
            )
            calls_to_write = max(
                [0, min([new_contracts_needed, maximum_new_contracts])])

            if calls_to_write > 0:
                click.secho(
                    f"Will write {calls_to_write} calls, {new_contracts_needed} needed for {symbol}, capped at {maximum_new_contracts}, at or above strike ${strike_limit} (target_calls={target_calls}, call_count={call_count})",
                    fg="green",
                )
                try:
                    self.write_calls(
                        symbol,
                        self.get_primary_exchange(symbol),
                        calls_to_write,
                        strike_limit,
                    )
                except RuntimeError as e:
                    click.echo()
                    click.secho(str(e), fg="red")
                    click.secho(
                        f"Failed to write calls for {symbol}. Continuing anyway...",
                        fg="yellow",
                    )
Beispiel #3
0
def start(config):
    import toml

    import thetagang.config_defaults as config_defaults

    with open(config, "r") as f:
        config = toml.load(f)

    config = normalize_config(config)

    validate_config(config)

    click.secho(f"Config:", fg="green")
    click.echo()

    click.secho(f"  Account details:", fg="green")
    click.secho(
        f"    Number                   = {config['account']['number']}", fg="cyan"
    )
    click.secho(
        f"    Cancel existing orders   = {config['account']['cancel_orders']}",
        fg="cyan",
    )
    click.secho(
        f"    Margin usage             = {config['account']['margin_usage']} ({config['account']['margin_usage'] * 100}%)",
        fg="cyan",
    )
    click.secho(
        f"    Market data type         = {config['account']['market_data_type']}",
        fg="cyan",
    )
    click.echo()

    click.secho(f"  Roll options when either condition is true:", fg="green")
    click.secho(
        f"    Days to expiry          <= {config['roll_when']['dte']} and P&L >= {config['roll_when']['min_pnl']} ({config['roll_when']['min_pnl'] * 100}%)",
        fg="cyan",
    )
    click.secho(
        f"    P&L                     >= {config['roll_when']['pnl']} ({config['roll_when']['pnl'] * 100}%)",
        fg="cyan",
    )

    click.echo()
    click.secho(f"  When contracts are ITM:", fg="green")
    click.secho(
        f"    Roll puts               = {config['roll_when']['puts']['itm']}",
        fg="cyan",
    )
    click.secho(
        f"    Roll calls              = {config['roll_when']['calls']['itm']}",
        fg="cyan",
    )

    click.echo()
    click.secho(f"  Write options with targets of:", fg="green")
    click.secho(f"    Days to expiry          >= {config['target']['dte']}", fg="cyan")
    click.secho(
        f"    Default delta           <= {config['target']['delta']}", fg="cyan"
    )
    if "puts" in config["target"]:
        click.secho(
            f"    Delta for puts          <= {config['target']['puts']['delta']}",
            fg="cyan",
        )
    if "calls" in config["target"]:
        click.secho(
            f"    Delta for calls         <= {config['target']['calls']['delta']}",
            fg="cyan",
        )
    click.secho(
        f"    Maximum new contracts    = {config['target']['maximum_new_contracts']}",
        fg="cyan",
    )
    click.secho(
        f"    Minimum open interest    = {config['target']['minimum_open_interest']}",
        fg="cyan",
    )

    click.echo()
    click.secho(f"  Symbols:", fg="green")
    for s in config["symbols"].keys():
        c = config["symbols"][s]
        c_delta = f"{get_target_delta(config, s, 'C'):.2f}".rjust(4)
        p_delta = f"{get_target_delta(config, s, 'P'):.2f}".rjust(4)
        weight = f"{c['weight']:.2f}".rjust(4)
        weight_p = f"{(c['weight'] * 100):.1f}".rjust(4)
        strike_limits = ""
        c_limit = get_strike_limit(config, s, "C")
        p_limit = get_strike_limit(config, s, "P")
        if c_limit:
            strike_limits += f", call strike >= ${c_limit:.2f}"
        if p_limit:
            strike_limits += f", put strike <= ${p_limit:.2f}"
        click.secho(
            f"    {s.rjust(5)} weight = {weight} ({weight_p}%), delta = {p_delta}p, {c_delta}c{strike_limits}",
            fg="cyan",
        )
    assert (
        sum([config["symbols"][s]["weight"] for s in config["symbols"].keys()]) == 1.0
    )
    click.echo()

    if config.get("ib_insync", {}).get("logfile"):
        util.logToFile(config["ib_insync"]["logfile"])

    # TWS version is pinned to current stable
    ibc = IBC(981, **config["ibc"])

    def onConnected():
        portfolio_manager.manage()

    ib = IB()
    ib.connectedEvent += onConnected

    completion_future = asyncio.Future()
    portfolio_manager = PortfolioManager(config, ib, completion_future)

    probeContractConfig = config["watchdog"]["probeContract"]
    watchdogConfig = config.get("watchdog")
    del watchdogConfig["probeContract"]
    probeContract = Contract(
        secType=probeContractConfig["secType"],
        symbol=probeContractConfig["symbol"],
        currency=probeContractConfig["currency"],
        exchange=probeContractConfig["exchange"],
    )

    watchdog = Watchdog(ibc, ib, probeContract=probeContract, **watchdogConfig)

    watchdog.start()
    ib.run(completion_future)
    watchdog.stop()
    ibc.terminate()
Beispiel #4
0
    def roll_positions(self, positions, right, portfolio_positions={}):
        for position in positions:
            symbol = position.contract.symbol
            strike_limit = get_strike_limit(self.config, symbol, right)
            if right.startswith("C"):
                strike_limit = math.ceil(
                    max([strike_limit or 0] + [
                        p.averageCost for p in portfolio_positions[symbol]
                        if isinstance(p.contract, Stock)
                    ]))

            sell_ticker = self.find_eligible_contracts(
                symbol,
                self.get_primary_exchange(symbol),
                right,
                strike_limit,
                excluded_expirations=[
                    position.contract.lastTradeDateOrContractMonth
                ],
            )
            self.wait_for_midpoint_price(sell_ticker)

            quantity = abs(position.position)

            position.contract.exchange = "SMART"
            [buy_ticker] = self.ib.reqTickers(position.contract)
            self.wait_for_midpoint_price(buy_ticker)

            price = midpoint_or_market_price(
                buy_ticker) - midpoint_or_market_price(sell_ticker)

            # Create combo legs
            comboLegs = [
                ComboLeg(
                    conId=position.contract.conId,
                    ratio=1,
                    exchange="SMART",
                    action="BUY",
                ),
                ComboLeg(
                    conId=sell_ticker.contract.conId,
                    ratio=1,
                    exchange="SMART",
                    action="SELL",
                ),
            ]

            # Create contract
            combo = Contract(
                secType="BAG",
                symbol=symbol,
                currency="USD",
                exchange="SMART",
                comboLegs=comboLegs,
            )

            # Create order
            order = LimitOrder(
                "BUY",
                quantity,
                round(price, 2),
                algoStrategy="Adaptive",
                algoParams=[TagValue("adaptivePriority", "Patient")],
                tif="DAY",
            )

            # Submit order
            trade = self.ib.placeOrder(combo, order)
            self.orders.append(trade)
            click.echo()
            click.secho("Order submitted", fg="green")
            click.secho(f"{trade}", fg="green")
Beispiel #5
0
    def check_if_can_write_puts(self, account_summary, portfolio_positions):
        # Get stock positions
        stock_positions = [
            position for symbol in portfolio_positions
            for position in portfolio_positions[symbol]
            if isinstance(position.contract, Stock)
        ]

        total_buying_power = math.floor(
            float(account_summary["NetLiquidation"].value) *
            self.config["account"]["margin_usage"])

        click.echo()
        click.secho(
            f"Total buying power: ${total_buying_power:,.0f} at {round(self.config['account']['margin_usage'] * 100, 1)}% margin usage",
            fg="green",
        )
        click.echo()

        stock_symbols = dict()
        for stock in stock_positions:
            symbol = stock.contract.symbol
            stock_symbols[symbol] = stock

        targets = dict()
        target_additional_quantity = dict()

        # Determine target quantity of each stock
        for symbol in self.config["symbols"].keys():
            click.secho(f"  {symbol}", fg="green")

            ticker = self.get_ticker_for(symbol,
                                         self.get_primary_exchange(symbol))

            current_position = math.floor(stock_symbols[symbol].position
                                          if symbol in stock_symbols else 0)
            click.secho(
                f"    Current position quantity: {current_position} shares",
                fg="cyan",
            )

            targets[symbol] = round(
                self.config["symbols"][symbol]["weight"] * total_buying_power,
                2)
            click.secho(f"    Target value: ${targets[symbol]:,.0f}",
                        fg="cyan")
            target_quantity = math.floor(targets[symbol] /
                                         ticker.marketPrice())
            click.secho(f"    Target share quantity: {target_quantity:,d}",
                        fg="cyan")

            # Current number of short puts
            put_count = count_short_option_positions(symbol,
                                                     portfolio_positions, "P")

            target_additional_quantity[symbol] = math.floor(target_quantity -
                                                            current_position -
                                                            100 * put_count)

            click.secho(
                f"    Net quantity: {target_additional_quantity[symbol]:,d} shares, {target_additional_quantity[symbol] // 100} contracts",
                fg="cyan",
            )

        click.echo()

        # Figure out how many additional puts are needed, if they're needed
        for symbol in target_additional_quantity.keys():
            additional_quantity = target_additional_quantity[symbol] // 100
            # NOTE: it's possible there are non-standard option contract sizes,
            # like with futures, but we don't bother handling those cases.
            # Please don't use this code with futures.
            if additional_quantity >= 1:
                maximum_new_contracts = self.config["target"][
                    "maximum_new_contracts"]
                puts_to_write = min(
                    [additional_quantity, maximum_new_contracts])
                if puts_to_write > 0:
                    strike_limit = get_strike_limit(self.config, symbol, "P")
                    if strike_limit:
                        click.secho(
                            f"Preparing to write additional {puts_to_write} puts to purchase {symbol}, capped at {maximum_new_contracts}, at or below strike ${strike_limit}",
                            fg="cyan",
                        )
                    else:
                        click.secho(
                            f"Preparing to write additional {puts_to_write} puts to purchase {symbol}, capped at {maximum_new_contracts}",
                            fg="cyan",
                        )
                    self.write_puts(
                        symbol,
                        self.get_primary_exchange(symbol),
                        puts_to_write,
                        strike_limit,
                    )

        return
    def roll_positions(self,
                       positions,
                       right,
                       account_summary,
                       portfolio_positions={}):
        for position in positions:
            try:
                symbol = position.contract.symbol
                strike_limit = get_strike_limit(self.config, symbol, right)
                if right.startswith("C"):
                    strike_limit = math.ceil(
                        max([strike_limit or 0] + [
                            p.averageCost for p in portfolio_positions[symbol]
                            if isinstance(p.contract, Stock)
                        ]))

                sell_ticker = self.find_eligible_contracts(
                    symbol,
                    self.get_primary_exchange(symbol),
                    right,
                    strike_limit,
                    exclude_expirations_before=position.contract.
                    lastTradeDateOrContractMonth,
                    exclude_first_exp_strike=position.contract.strike,
                )

                qty_to_roll = abs(position.position)
                maximum_new_contracts = self.get_maximum_new_contracts_for(
                    symbol,
                    self.get_primary_exchange(symbol),
                    account_summary,
                )
                from_dte = option_dte(
                    position.contract.lastTradeDateOrContractMonth)
                roll_when_dte = self.config["roll_when"]["dte"]
                if from_dte > roll_when_dte:
                    qty_to_roll = min([qty_to_roll, maximum_new_contracts])

                position.contract.exchange = "SMART"
                buy_ticker = self.get_ticker_for(position.contract,
                                                 midpoint=True)

                price = midpoint_or_market_price(
                    buy_ticker) - midpoint_or_market_price(sell_ticker)

                # Create combo legs
                comboLegs = [
                    ComboLeg(
                        conId=position.contract.conId,
                        ratio=1,
                        exchange="SMART",
                        action="BUY",
                    ),
                    ComboLeg(
                        conId=sell_ticker.contract.conId,
                        ratio=1,
                        exchange="SMART",
                        action="SELL",
                    ),
                ]

                # Create contract
                combo = Contract(
                    secType="BAG",
                    symbol=symbol,
                    currency="USD",
                    exchange="SMART",
                    comboLegs=comboLegs,
                )

                # Create order
                order = LimitOrder(
                    "BUY",
                    qty_to_roll,
                    round(price, 2),
                    algoStrategy="Adaptive",
                    algoParams=[TagValue("adaptivePriority", "Patient")],
                    tif="DAY",
                )

                to_dte = option_dte(
                    sell_ticker.contract.lastTradeDateOrContractMonth)
                from_strike = position.contract.strike
                to_strike = sell_ticker.contract.strike

                # Submit order
                trade = self.ib.placeOrder(combo, order)
                self.orders.append(trade)
                click.echo()
                click.secho(
                    f"Order submitted, current position={abs(position.position)}, qty_to_roll={qty_to_roll}, from_dte={from_dte}, to_dte={to_dte}, from_strike={from_strike}, to_strike={to_strike}, price={round(price,2)}, trade={trade}",
                    fg="green",
                )
            except RuntimeError as e:
                click.echo()
                click.secho(str(e), fg="red")
                click.secho(
                    "Error occurred when trying to roll position. Continuing anyway...",
                    fg="yellow",
                )