コード例 #1
0
 def test_replacement_is_first_bought(self):
     lots = lots_lib.Lots([
         self.first_gain, self.first_gain_earlier_sale,
         self.first_gain_later_sale
     ])
     self.assertSameLot(self.first_gain_earlier_sale,
                        wash.best_replacement_lot(self.loss, lots))
コード例 #2
0
 def test_parse_valid_csv_file(self):
     csv_data = [
         'Num Shares,Symbol,Description,Buy Date,Adjusted Buy Date,Basis,'
         'Adjusted Basis,Sell Date,Proceeds,Adjustment Code,Adjustment,'
         'Form Position,Buy Lot,Replacement For,Is Replacement,'
         'Loss Processed',
         '10,ABC,A,9/15/2014,9/14/2014,2000,2100,10/5/2014,1800,W,200,form1,'
         'lot1,lot3|lot4,true,true',
         '10,ABC,A,9/15/2014,,2000,,10/5/2014,1800,W,200,form2,lot2,,false,',
         '20,ABC,A,9/25/2014,,3000,,11/5/2014,1800,,,,,,'
     ]
     lots = lots_lib.Lots.create_from_csv_data(csv_data)
     expected_lots_rows = []
     expected_lots_rows.append(
         lots_lib.Lot(10, 'ABC', 'A', datetime.date(2014, 9, 15),
                      datetime.date(2014, 9, 14), 2000, 2100,
                      datetime.date(2014, 10, 5), 1800, 'W', 200, 'form1',
                      'lot1', ['lot3', 'lot4'], True, True))
     expected_lots_rows.append(
         lots_lib.Lot(10, 'ABC', 'A', datetime.date(2014, 9, 15),
                      datetime.date(2014, 9, 15), 2000, 2000,
                      datetime.date(2014, 10, 5), 1800, 'W', 200, 'form2',
                      'lot2', [], False, False))
     expected_lots_rows.append(
         lots_lib.Lot(20, 'ABC', 'A', datetime.date(2014, 9, 25),
                      datetime.date(2014, 9, 25), 3000, 3000,
                      datetime.date(2014, 11, 5), 1800, '', 0, '', '', [],
                      False, False))
     expected_lots = lots_lib.Lots(expected_lots_rows)
     self.assertSameLots(lots, expected_lots)
コード例 #3
0
 def test_two_similar_lots(self):
     # There are two lots that have the same values, but were bought
     # separately.
     lot1 = create_lot(10, 2012, 1, 1, 120, 2012, 1, 10, 110)
     lot2 = create_lot(10, 2012, 1, 1, 120, 2012, 1, 10, 110)
     lots = lots_lib.Lots([lot1, lot2])
     self.assertSameLot(lot2, wash.best_replacement_lot(lot1, lots))
コード例 #4
0
    def test_wash_against_small_replacement(self):
        lots = lots_lib.Lots([self.loss, self.small_first_gain])

        final_lots = copy.deepcopy(lots)
        disallowed_loss = final_lots.lots()[0]
        # Create the split lot.
        split_lot = copy.deepcopy(disallowed_loss)
        split_lot.num_shares = 4
        split_lot.basis *= 4. / 10.
        split_lot.adjusted_basis *= 4. / 10.
        split_lot.proceeds *= 4. / 10.
        final_lots.add(split_lot)

        disallowed_loss.num_shares = 6
        disallowed_loss.basis *= 6. / 10.
        disallowed_loss.adjusted_basis *= 6. / 10.
        disallowed_loss.proceeds *= 6. / 10.
        disallowed_loss.adjustment_code = 'W'
        disallowed_loss.adjustment = 6
        disallowed_loss.loss_processed = True
        replacement = final_lots.lots()[1]
        replacement.adjusted_basis = 106
        replacement.adjusted_buy_date -= (self.loss.sell_date -
                                          self.loss.buy_date)
        replacement.replacement_for = ['_1']
        replacement.is_replacement = True

        wash.wash_one_lot(self.loss, lots)
        self.assertSameLots(lots, final_lots)
コード例 #5
0
 def assertSameLot(self, a, b):
     self.assertIs(
         a,
         b,
         msg="{} is not {}: \n{}".format(id(a), id(b), lots_lib.Lots([a,
                                                                      b])),
     )
コード例 #6
0
 def assertLotIsNone(self, a):
     self.assertIsNone(
         a,
         msg="{} is not None: \n{}".format(
             id(a),
             lots_lib.Lots([a]) if a else None),
     )
コード例 #7
0
    def test_wash_against_large_replacement(self):
        lots = lots_lib.Lots([self.loss, self.large_first_gain])

        final_lots = copy.deepcopy(lots)
        disallowed_loss = final_lots.lots()[0]
        disallowed_loss.adjustment_code = 'W'
        disallowed_loss.adjustment = 10
        disallowed_loss.loss_processed = True
        replacement = final_lots.lots()[1]

        # Create the split lot.
        split_lot = copy.deepcopy(replacement)
        split_lot.num_shares = 8
        split_lot.basis = int(round(split_lot.basis * 8. / 18.))
        split_lot.adjusted_basis = int(
            round(split_lot.adjusted_basis * 8. / 18.))
        split_lot.proceeds = int(round(split_lot.proceeds * 8. / 18.))
        final_lots.add(split_lot)

        replacement.num_shares = 10
        replacement.basis = int(round(replacement.basis * 10. / 18.))
        replacement.adjusted_basis = int(
            round(replacement.adjusted_basis * 10. / 18.))
        replacement.proceeds = int(round(replacement.proceeds * 10. / 18.))
        replacement.adjusted_basis += 10
        replacement.adjusted_buy_date -= (self.loss.sell_date -
                                          self.loss.buy_date)
        replacement.replacement_for = ['_1']
        replacement.is_replacement = True

        wash.wash_one_lot(self.loss, lots)
        self.assertSameLots(lots, final_lots)
コード例 #8
0
ファイル: wash.py プロジェクト: kbloom/wash-sale-tracker
def merge_split_lots(lots):
    """Merge split lots back together, assuming lots is sorted with respect to
    original_form_position so only sequential records need to be merged."""

    orig = lots.lots()

    out = []
    # First lot in new sequence
    prev = copy.copy(orig[0])
    for lot in orig[1:]:
        assert (prev.original_form_position <= lot.original_form_position)
        if lot.original_form_position == prev.original_form_position:
            assert (lot.symbol == prev.symbol)
            prev.num_shares += lot.num_shares
            prev.basis += lot.basis
            prev.adjusted_basis += lot.adjusted_basis
            prev.proceeds += lot.proceeds
            prev.adjustment += lot.adjustment
            prev.buy_lot += '|' + lot.buy_lot
            if lot.adjustment_code:
                prev.adjustment_code = lot.adjustment_code
        else:
            # Loop has moved on to a different lot, finished with current
            out.append(prev)
            prev = copy.copy(lot)
    if prev:
        out.append(prev)
    return lots_lib.Lots(out)
コード例 #9
0
    def test_write_csv_data(self):
        lots_rows = []
        lots_rows.append(lots_lib.Lot(
            10, 'ABC', 'A', datetime.date(2014, 9, 15), datetime.date(
                2014, 9, 14), 2000, 2100, datetime.date(2014, 10, 5), 1800,
            'W', 200, 'form1', 'lot1', ['lot3', 'lot4'], True, True))
        lots_rows.append(lots_lib.Lot(10, 'ABC', 'A', datetime.date(
            2014, 9, 15), datetime.date(2014, 9, 15), 2000, 2000,
            datetime.date(2014, 10, 5), 1800, 'W', 200, 'form2', 'lot2', [],
            False, False))
        lots_rows.append(lots_lib.Lot(20, 'ABC', 'A', datetime.date(
            2014, 9, 25), datetime.date(2014, 9, 25), 3000, 3000,
            datetime.date(2014, 11, 5), 1800, '', 0, '', '', [], False, False))
        lots = lots_lib.Lots(lots_rows)

        actual_output = StringIO.StringIO()
        lots.write_csv_data(actual_output)

        expected_csv_data = [
            'Num Shares,Symbol,Description,Buy Date,Adjusted Buy Date,Basis,'
            'Adjusted Basis,Sell Date,Proceeds,Adjustment Code,Adjustment,'
            'Form Position,Buy Lot,Replacement For,Is Replacement,'
            'Loss Processed',
            '10,ABC,A,09/15/2014,09/14/2014,2000,2100,10/05/2014,1800,W,200,'
            'form1,lot1,lot3|lot4,True,True',
            '10,ABC,A,09/15/2014,,2000,,10/05/2014,1800,W,200,form2,lot2,,,',
            '20,ABC,A,09/25/2014,,3000,,11/05/2014,1800,,,,_1,,,'
        ]

        actual_output.seek(0)
        self.assertSequenceEqual(
            [line.rstrip()
             for line in actual_output.readlines()], expected_csv_data)
コード例 #10
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)
コード例 #11
0
 def test_two_lots_from_same_buy_lot(self):
     # There are two lots that have the same values, and were bought
     # together.
     lot1 = create_lot(10, 2012, 1, 1, 120, 2012, 1, 10, 110)
     lot1.buy_lot = '1'
     lot2 = create_lot(10, 2012, 1, 1, 120, 2012, 1, 10, 110)
     lot2.buy_lot = '1'
     lots = lots_lib.Lots([lot1, lot2])
     self.assertLotIsNone(wash.best_replacement_lot(lot1, lots))
コード例 #12
0
    def test_no_wash_if_no_replacement_shares(self):
        lots = lots_lib.Lots(
            [self.loss, self.very_early_gain, self.very_late_gain])

        final_lots = copy.deepcopy(lots)
        loss = final_lots.lots()[0]
        loss.loss_processed = True

        wash.wash_one_lot(self.loss, lots)
        self.assertSameLots(lots, final_lots)
コード例 #13
0
    def test_replacement_for_large_loss(self):
        lots = lots_lib.Lots([self.unsold, self.first_gain, self.large_loss])
        final_lots = copy.deepcopy(lots)
        wash_lot = wash.best_replacement_lot(self.large_loss, lots)
        self.assertSameLot(self.first_gain, wash_lot)
        self.assertEqual(10, wash_lot.num_shares)
        self.assertEqual(3, lots.size())

        lots.sort(key=cmp_to_key(lots_lib.Lot.cmp_by_original_buy_date))
        final_lots.sort(key=cmp_to_key(lots_lib.Lot.cmp_by_original_buy_date))
        self.assertSameLots(lots, final_lots)
コード例 #14
0
    def test_contents_not_equal(self):
        lots = lots_lib.Lots([])
        lots.add(lots_lib.Lot(1, '', '', datetime.date(2014, 9, 2),
            datetime.date(2014, 9, 2), 0, 0, datetime.date(2014, 11, 5), 0, '',
            0, 'form2', '', [], False, False))
        lots.add(lots_lib.Lot(5, '', '', datetime.date(2014, 9, 1),
            datetime.date(2014, 9, 1), 0, 0, datetime.date(2014, 10, 5), 0, '',
            0, 'form1', '', [], False, False))
        lots.add(lots_lib.Lot(3, '', '', datetime.date(2014, 9, 2),
            datetime.date(2014, 9, 2), 0, 0, datetime.date(2014, 11, 5), 0, '',
            0, 'form1', '', [], False, False))

        other_lots = copy.deepcopy(lots)
        other_lots.lots()[0].num_shares = 2
        self.assertFalse(lots.contents_equal(other_lots))
コード例 #15
0
    def test_wash_against_subsequent_loss(self):
        lots = lots_lib.Lots([self.loss, self.later_loss])

        final_lots = copy.deepcopy(lots)
        disallowed_loss = final_lots.lots()[0]
        disallowed_loss.adjustment_code = 'W'
        disallowed_loss.adjustment = 10
        disallowed_loss.loss_processed = True
        replacement = final_lots.lots()[1]
        replacement.adjusted_basis = 150
        replacement.adjusted_buy_date -= (self.loss.sell_date -
                                          self.loss.buy_date)
        replacement.replacement_for = ['_1']
        replacement.is_replacement = True

        wash.wash_one_lot(self.loss, lots)
        self.assertSameLots(lots, final_lots)
コード例 #16
0
    def test_wash_against_unsold(self):
        lots = lots_lib.Lots([self.loss, self.unsold])

        final_lots = copy.deepcopy(lots)
        disallowed_loss = final_lots.lots()[0]
        disallowed_loss.adjustment_code = "W"
        disallowed_loss.adjustment = 10
        disallowed_loss.loss_processed = True
        replacement = final_lots.lots()[1]
        replacement.adjusted_basis = 140
        replacement.adjusted_buy_date -= (self.loss.sell_date -
                                          self.loss.buy_date)
        replacement.replacement_for = ["_1"]
        replacement.is_replacement = True

        wash.wash_one_lot(self.loss, lots)
        self.assertSameLots(lots, final_lots)
コード例 #17
0
ファイル: wash.py プロジェクト: kbloom/wash-sale-tracker
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)
コード例 #18
0
ファイル: wash.py プロジェクト: tjablin/wash-sale-tracker
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)
コード例 #19
0
    def test_write_csv_data_dollars(self):
        lots_rows = []
        lots_rows.append(
            lots_lib.Lot(
                20,
                "ABC",
                "A",
                datetime.date(2014, 9, 25),
                datetime.date(2014, 9, 25),
                3010,
                3010,
                datetime.date(2014, 11, 5),
                1850,
                "",
                0,
                "",
                "",
                [],
                False,
                False,
            ))
        lots = lots_lib.Lots(lots_rows)

        actual_output = StringIO()
        lots.write_csv_data(actual_output, True)

        expected_csv_data = [
            "Num Shares,Symbol,Description,Buy Date,Adjusted Buy Date,Basis,"
            "Adjusted Basis,Sell Date,Proceeds,Adjustment Code,Adjustment,"
            "Form Position,Buy Lot,Replacement For,Is Replacement,"
            "Loss Processed",
            "20,ABC,A,09/25/2014,,$30,,11/05/2014,$19,,,,_1,,,",
        ]

        actual_output.seek(0)
        self.assertSequenceEqual(
            [line.rstrip() for line in actual_output.readlines()],
            expected_csv_data,
        )
コード例 #20
0
 def test_only_loss_lot_exists(self):
     lots = lots_lib.Lots([self.loss])
     self.assertLotIsNone(wash.best_replacement_lot(self.loss, lots))
コード例 #21
0
 def test_replacement_for_small_loss(self):
     lots = lots_lib.Lots([self.unsold, self.first_gain, self.small_loss])
     wash_lot = wash.best_replacement_lot(self.small_loss, lots)
     self.assertSameLot(self.first_gain, wash_lot)
コード例 #22
0
 def test_unsold(self):
     lots = lots_lib.Lots([self.loss1, self.unsold, self.loss2])
     self.assertSameLot(self.loss1, wash.earliest_loss_lot(lots))
コード例 #23
0
 def test_gain(self):
     lots = lots_lib.Lots([self.loss1, self.gain, self.loss2])
     self.assertSameLot(self.loss1, wash.earliest_loss_lot(lots))
コード例 #24
0
 def test_replacement_checks_sell_date(self):
     # If there are multiple possible replacements that were bought on the
     # same day, the one with the earlier sell date is chosen.
     lots = lots_lib.Lots([self.unsold, self.first_gain, self.loss])
     self.assertSameLot(self.first_gain,
                        wash.best_replacement_lot(self.loss, lots))
コード例 #25
0
 def test_two_losses(self):
     lots = lots_lib.Lots([self.loss1, self.loss2, self.loss3])
     self.assertSameLot(self.loss1, wash.earliest_loss_lot(lots))
コード例 #26
0
 def test_loss_not_in_lots(self):
     lots = lots_lib.Lots([self.unsold, self.first_gain])
     self.assertSameLot(self.first_gain,
                        wash.best_replacement_lot(self.loss, lots))
コード例 #27
0
 def test_replacement_is_unsold(self):
     lots = lots_lib.Lots([self.unsold, self.loss])
     self.assertSameLot(self.unsold,
                        wash.best_replacement_lot(self.loss, lots))
コード例 #28
0
 def test_replacement_for_loss_multiple_options(self):
     lots = lots_lib.Lots(
         [self.loss, self.small_first_gain, self.large_first_gain])
     wash_lot = wash.best_replacement_lot(self.loss, lots)
     self.assertSameLot(self.small_first_gain, wash_lot)
コード例 #29
0
 def test_lot_sold_before_loss_is_not_replacement(self):
     lots = lots_lib.Lots([self.loss, self.gain_just_before_loss])
     self.assertLotIsNone(wash.best_replacement_lot(self.loss, lots))
コード例 #30
0
 def test_already_used_replacement_is_not_used_again(self):
     lot1 = create_lot(10, 2012, 1, 1, 120, 2012, 1, 10, 110)
     lot2 = create_lot(10, 2012, 1, 1, 120, 2012, 1, 10, 110)
     lot2.is_replacement = True
     lots = lots_lib.Lots([lot1, lot2])
     self.assertLotIsNone(wash.best_replacement_lot(lot1, lots))