Ejemplo n.º 1
0
def main():
    parser = argparse.ArgumentParser()
    parser.add_argument("-o", "--out_file")
    parser.add_argument("-w", "--do_wash", metavar="in_file")
    parser.add_argument("-q", "--quiet", action="store_true")
    parser.add_argument(
        "-d",
        "--output-dollars",
        action="store_true",
        help="If set, outputs dollars instead of cents for money columns",
    )
    parsed = parser.parse_args()

    if parsed.quiet:
        logger = logger_lib.NullLogger()
    else:
        logger = logger_lib.TermLogger()
    if parsed.do_wash:
        lots = lots_lib.Lots([])
        with open(parsed.do_wash) as f:
            lots = lots_lib.Lots.create_from_csv_data(f)
        logger.print_lots("Start lots", lots)
        wash_all_lots(lots, logger)
        if parsed.out_file:
            with open(parsed.out_file, "w") as f:
                lots.write_csv_data(f, parsed.output_dollars)
        else:
            logger.print_lots("Final lots", lots)
Ejemplo n.º 2
0
def wash_all_lots(lots, logger=logger_lib.NullLogger()):
    """Performs wash sales of all the lots.

    Args:
        lots: A Lots object.
        logger: A logger_lib.Logger.
    """
    while True:
        loss_lot = earliest_loss_lot(lots)
        if not loss_lot:
            break
        logger.print_lots("Found loss", lots, loss_lots=[loss_lot])
        wash_one_lot(loss_lot, lots, logger)
Ejemplo n.º 3
0
def main():
    parser = argparse.ArgumentParser()
    parser.add_argument('-o', '--out_file')
    parser.add_argument('-w', '--do_wash', metavar='in_file')
    parser.add_argument('-q', '--quiet', action="store_true")
    parser.add_argument('-m',
                        '--merge_split_lots',
                        action="store_true",
                        help='''Any split lots are merged back together at end.
                       This makes it easier to match the output to the input
                       lots, but can cause the buy-dates to be slightly
                       incorrect since a lot can only have a single buy date.
                       In this mode, some wash sale lots may have a loss that
                       is greater than the adjustment amount, instead of being
                       identical, i.e., only part of the loss in the lot is
                       actually a wash sale. This is expected in this mode..'''
                        )
    parser.add_argument('-a',
                        '--always_show_adjusted',
                        action="store_true",
                        help='''Always fill in the adjusted buy date and basis.
			If no adjustments were made, then the unadjusted basis and
			buy date are used instead.''')
    parsed = parser.parse_args()

    if parsed.quiet:
        logger = logger_lib.NullLogger()
    else:
        logger = logger_lib.TermLogger()
    if parsed.do_wash:
        lots = lots_lib.Lots([])
        with open(parsed.do_wash) as f:
            lots = lots_lib.Lots.create_from_csv_data(f)
        logger.print_lots('Start lots', lots)
        wash_all_lots(lots, logger)
        if parsed.merge_split_lots:
            lots.sort(cmp=cmp_by_original_form_position)
            lots = merge_split_lots(lots)
        if parsed.out_file:
            with open(parsed.out_file, 'w') as f:
                lots.write_csv_data(parsed.always_show_adjusted, f)
        else:
            logger.print_lots('Final lots', lots)
Ejemplo n.º 4
0
def main():
    parser = argparse.ArgumentParser()
    parser.add_argument('-o', '--out_file')
    parser.add_argument('-w', '--do_wash', metavar='in_file')
    parser.add_argument('-q', '--quiet', action="store_true")
    parsed = parser.parse_args()

    if parsed.quiet:
        logger = logger_lib.NullLogger()
    else:
        logger = logger_lib.TermLogger()
    if parsed.do_wash:
        lots = lots_lib.Lots([])
        with open(parsed.do_wash) as f:
            lots = lots_lib.Lots.create_from_csv_data(f)
        logger.print_lots('Start lots', lots)
        wash_all_lots(lots, logger)
        if parsed.out_file:
            with open(parsed.out_file, 'w') as f:
                lots.write_csv_data(f)
        else:
            logger.print_lots('Final lots', lots)
Ejemplo n.º 5
0
def wash_one_lot(loss_lot, lots, logger=logger_lib.NullLogger()):
    """Performs a single wash.

    Given a single loss lot, finds replacement lot(s) and adjusts their basis
    and buy date in place.

    If the loss lot needs to be split into multiple parts (because the
    replacement lots are for fewer shares) then it will be split into two parts
    and the wash will be performed for only the first part. The second part can
    be taken care of by another call to this method with it passed in as the
    loss_lot.

    If the replacement lot needs to be split into multiple parts (because the
    replacement lot has more shares than the loss lot) then it will be split
    and the second part of the lot will be added to lots.

    A replacement lot is one that is purchased within 30 days of the loss_lot's
    sale, not already used as a replacement, and not part of the same lot as
    the loss_lot.

    Args:
        loss_lot: A Lot object, which is a loss that should be washed.
        lots: A Lots object, the full set of lots.
        logger: A logger_lib.Logger.
    """
    replacement_lot = best_replacement_lot(loss_lot, lots)
    if not replacement_lot:
        logger.print_lots("No replacement lot", lots, loss_lots=[loss_lot])
        loss_lot.loss_processed = True
        return

    logger.print_lots(
        "Found replacement lot",
        lots,
        loss_lots=[loss_lot],
        replacement_lots=[replacement_lot],
    )

    # There is a replacement lot. If it is not for the same number of shares as
    # the loss lot, split the larger one.
    if loss_lot.num_shares > replacement_lot.num_shares:
        _split_lot(
            replacement_lot.num_shares,
            loss_lot,
            lots,
            logger,
            "loss",
            existing_replacement_lot=replacement_lot,
        )
    elif replacement_lot.num_shares > loss_lot.num_shares:
        _split_lot(
            loss_lot.num_shares,
            replacement_lot,
            lots,
            logger,
            "replacement",
            existing_loss_lot=loss_lot,
        )

    # Now the loss_lot and replacement_lot have the same number of shares.
    loss_lot.loss_processed = True
    loss_lot.adjustment_code = "W"
    loss_lot.adjustment = loss_lot.adjusted_basis - loss_lot.proceeds
    replacement_lot.is_replacement = True
    replacement_lot.replacement_for.extend(loss_lot.replacement_for)
    replacement_lot.replacement_for.append(loss_lot.buy_lot)
    replacement_lot.adjusted_basis += loss_lot.adjustment
    replacement_lot.adjusted_buy_date -= (loss_lot.sell_date -
                                          loss_lot.adjusted_buy_date)

    logger.print_lots(
        "Adjusted basis and buy date",
        lots,
        loss_lots=[loss_lot],
        replacement_lots=[replacement_lot],
    )