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)
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)
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)
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)
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], )