Example #1
0
    def test_shortSaleAndCover(self) -> None:
        ts = self.activityByDate[date(2018, 1, 2)]
        self.assertEqual(len(ts), 2)

        self.assertEqual(
            ts[0],
            Trade(
                date=ts[0].date,
                instrument=Stock("HD", Currency.USD),
                quantity=Decimal("-6"),
                amount=Cash(currency=Currency.USD, quantity=Decimal("1017.3")),
                fees=Cash(currency=Currency.USD, quantity=Decimal("4.96")),
                flags=TradeFlags.OPEN,
            ),
        )

        self.assertEqual(
            ts[1],
            Trade(
                date=ts[1].date,
                instrument=Stock("HD", Currency.USD),
                quantity=Decimal("6"),
                amount=Cash(currency=Currency.USD,
                            quantity=Decimal("-1033.12")),
                fees=Cash(currency=Currency.USD, quantity=Decimal("4.95")),
                flags=TradeFlags.CLOSE,
            ),
        )
def _parseVanguardTransaction(t: _VanguardTransaction) -> Optional[Activity]:
    if t.transactionType == "Dividend":
        return CashPayment(
            date=_parseVanguardTransactionDate(t.tradeDate),
            instrument=Stock(t.symbol if t.symbol else t.investmentName,
                             currency=Currency.USD),
            proceeds=Cash(currency=Currency.USD,
                          quantity=Decimal(t.netAmount)),
        )

    validTransactionTypes = set([
        "Buy",
        "Sell",
        "Reinvestment",
        "Corp Action (Redemption)",
        "Transfer (outgoing)",
    ])

    if t.transactionType not in validTransactionTypes:
        return None

    flagsByTransactionType = {
        "Buy": TradeFlags.OPEN,
        "Sell": TradeFlags.CLOSE,
        "Reinvestment": TradeFlags.OPEN | TradeFlags.DRIP,
        "Corp Action (Redemption)": TradeFlags.CLOSE,
        "Transfer (outgoing)": TradeFlags.CLOSE,
    }

    return _forceParseVanguardTransaction(
        t, flags=flagsByTransactionType[t.transactionType])
Example #3
0
def _parseFidelityTransaction(t: _FidelityTransaction) -> Optional[Activity]:
    if t.action == "DIVIDEND RECEIVED":
        return CashPayment(
            date=_parseFidelityTransactionDate(t.date),
            instrument=Stock(t.symbol, currency=Currency[t.currency]),
            proceeds=Cash(currency=Currency[t.currency],
                          quantity=Decimal(t.amount)),
        )
    elif t.action == "INTEREST EARNED":
        return CashPayment(
            date=_parseFidelityTransactionDate(t.date),
            instrument=None,
            proceeds=Cash(currency=Currency[t.currency],
                          quantity=Decimal(t.amount)),
        )

    flags = None
    # TODO: Handle 'OPENING TRANSACTION' and 'CLOSING TRANSACTION' text for options transactions
    if t.action.startswith("YOU BOUGHT"):
        flags = TradeFlags.OPEN
    elif t.action.startswith("YOU SOLD"):
        flags = TradeFlags.CLOSE
    elif t.action.startswith("REINVESTMENT"):
        flags = TradeFlags.OPEN | TradeFlags.DRIP

    if not flags:
        return None

    return _forceParseFidelityTransaction(t, flags=flags)
Example #4
0
def _guessInstrumentFromSymbol(symbol: str, currency: Currency) -> Instrument:
    if re.search(r"[0-9]+(C|P)[0-9]+$", symbol):
        return _parseOptionTransaction(symbol, currency)
    elif Bond.validBondSymbol(symbol):
        return Bond(symbol, currency=currency)
    else:
        return Stock(symbol, currency=currency)
def _guessInstrumentFromSymbol(symbol: str) -> Instrument:
    if re.search(r"\s(C|P)$", symbol):
        return _parseOption(symbol)
    elif Bond.validBondSymbol(symbol):
        return Bond(symbol, currency=Currency.USD)
    else:
        return Stock(symbol, currency=Currency.USD)
 def test_aapl(self) -> None:
     self.assertEqual(self.positions[1].instrument,
                      Stock("AAPL", Currency.USD))
     self.assertEqual(self.positions[1].quantity, Decimal("100"))
     self.assertEqual(
         self.positions[1].costBasis,
         Cash(currency=Currency.USD, quantity=Decimal("14000")),
     )
 def test_robo(self) -> None:
     self.assertEqual(self.positions[2].instrument,
                      Stock("ROBO", Currency.USD))
     self.assertEqual(self.positions[2].quantity, Decimal("10"))
     self.assertEqual(
         self.positions[2].costBasis,
         Cash(currency=Currency.USD, quantity=Decimal("300")),
     )
 def test_v(self) -> None:
     self.assertEqual(self.positions[5].instrument,
                      Stock("V", Currency.USD))
     self.assertEqual(self.positions[5].quantity, Decimal("20"))
     self.assertEqual(
         self.positions[5].costBasis,
         Cash(currency=Currency.USD, quantity=Decimal("2600")),
     )
def _guessInstrumentForInvestmentName(name: str) -> Instrument:
    instrument: Instrument
    if re.match(r"^.+\s\%\s.+$", name):
        # TODO: Determine valid CUSIP for bonds
        instrument = Bond(name, currency=Currency.USD, validateSymbol=False)
    else:
        instrument = Stock(name, currency=Currency.USD)

    return instrument
Example #10
0
 def test_cashDividend(self) -> None:
     ts = self.activityByDate[date(2018, 3, 6)]
     self.assertEqual(len(ts), 1)
     self.assertEqual(
         ts[0],
         CashPayment(
             date=ts[0].date,
             instrument=Stock("VGLT", Currency.USD),
             proceeds=helpers.cashUSD(Decimal("12.85")),
         ),
     )
 def test_dividendPayment(self) -> None:
     ts = self.activityByDate[date(2017, 11, 9)]
     self.assertEqual(len(ts), 5)
     self.assertEqual(
         ts[2],
         CashPayment(
             date=ts[2].date,
             instrument=Stock("ROBO", Currency.USD),
             proceeds=helpers.cashUSD(Decimal("6.78")),
         ),
     )
Example #12
0
 def test_dividendReinvested(self) -> None:
     ts = self.activityByDate[date(2017, 3, 28)]
     self.assertEqual(len(ts), 1)
     self.assertEqual(
         ts[0],
         CashPayment(
             date=ts[0].date,
             instrument=Stock("VOO", Currency.USD),
             proceeds=helpers.cashUSD(Decimal("22.95")),
         ),
     )
Example #13
0
 def test_stockLoanInterest(self) -> None:
     ts = self.activityByDate[date(2019, 1, 1)]
     self.assertEqual(len(ts), 1)
     self.assertEqual(
         ts[0],
         CashPayment(
             date=ts[0].date,
             instrument=Stock("TSLA", Currency.USD, exchange="NASDAQ"),
             proceeds=helpers.cashUSD(Decimal("0.01")),
         ),
     )
 def test_reinvestShares(self) -> None:
     ts = self.activityByDate[date(2017, 11, 9)]
     self.assertEqual(
         ts[3],
         Trade(
             date=ts[3].date,
             instrument=Stock("ROBO", Currency.USD),
             quantity=Decimal("0.234"),
             amount=Cash(currency=Currency.USD, quantity=Decimal("-6.78")),
             fees=Cash(currency=Currency.USD, quantity=Decimal("0.00")),
             flags=TradeFlags.OPEN | TradeFlags.DRIP,
         ),
     )
Example #15
0
def normalizeInstrument(instrument: Instrument) -> Instrument:
    if isinstance(instrument, Stock):
        return Stock(
            symbol=normalizeSymbol(instrument.symbol),
            currency=instrument.currency,
            exchange=instrument.exchange,
        )
    elif isinstance(instrument, Option):
        # Handles the FutureOption subclass correctly as well.
        return replace(instrument,
                       underlying=normalizeSymbol(instrument.underlying))
    else:
        return instrument
Example #16
0
    def test_postedAndPaid(self) -> None:
        ts = self.activityByDate[date(2019, 2, 14)]
        self.assertEqual(len(ts), 1)
        self.assertEqual(
            ts[0],
            CashPayment(
                date=ts[0].date,
                instrument=Stock("AAPL", Currency.USD, exchange="NASDAQ"),
                proceeds=helpers.cashUSD(Decimal("23.36")),
            ),
        )

        self.assertNotIn(date(2019, 2, 7), self.activityByDate)
        self.assertNotIn(date(2019, 2, 8), self.activityByDate)
Example #17
0
 def test_reinvestShares(self) -> None:
     ts = self.activityByDate[date(2017, 3, 29)]
     self.assertEqual(len(ts), 1)
     self.assertEqual(
         ts[0],
         Trade(
             date=ts[0].date,
             instrument=Stock("VOO", Currency.USD),
             quantity=Decimal("0.1062"),
             amount=Cash(currency=Currency.USD, quantity=Decimal("-22.95")),
             fees=Cash(currency=Currency.USD, quantity=Decimal(0)),
             flags=TradeFlags.OPEN | TradeFlags.DRIP,
         ),
     )
Example #18
0
 def test_buyUSDStock(self) -> None:
     symbol = "AAPL"
     ts = self.tradesBySymbol[symbol]
     self.assertEqual(len(ts), 1)
     self.assertEqual(ts[0].date.date(), date(2019, 2, 12))
     self.assertEqual(ts[0].instrument,
                      Stock(symbol, Currency.USD, exchange="ISLAND"))
     self.assertEqual(ts[0].quantity, Decimal("17"))
     self.assertEqual(
         ts[0].amount, Cash(currency=Currency.USD,
                            quantity=Decimal("-2890")))
     self.assertEqual(ts[0].fees,
                      Cash(currency=Currency.USD, quantity=Decimal("1")))
     self.assertEqual(ts[0].flags, TradeFlags.OPEN)
Example #19
0
 def test_buyGBPStock(self) -> None:
     symbol = "GAW"
     ts = self.tradesBySymbol[symbol]
     self.assertEqual(len(ts), 1)
     self.assertEqual(ts[0].date.date(), date(2019, 2, 12))
     self.assertEqual(ts[0].instrument,
                      Stock(symbol, Currency.GBP, exchange="LSE"))
     self.assertEqual(ts[0].quantity, Decimal("100"))
     self.assertEqual(
         ts[0].amount, Cash(currency=Currency.GBP,
                            quantity=Decimal("-3050")))
     self.assertEqual(
         ts[0].fees, Cash(currency=Currency.GBP, quantity=Decimal("21.25")))
     self.assertEqual(ts[0].flags, TradeFlags.OPEN)
Example #20
0
 def test_securityTransferSale(self) -> None:
     ts = self.activityByDate[date(2018, 1, 4)]
     self.assertEqual(len(ts), 1)
     self.assertEqual(
         ts[0],
         Trade(
             date=ts[0].date,
             instrument=Stock("MSFT", Currency.USD),
             quantity=Decimal("-10"),
             amount=Cash(currency=Currency.USD, quantity=Decimal("920.78")),
             fees=Cash(currency=Currency.USD, quantity=Decimal("13.65")),
             flags=TradeFlags.CLOSE,
         ),
     )
Example #21
0
 def test_buyStock(self) -> None:
     ts = self.activityByDate[date(2017, 2, 22)]
     self.assertEqual(len(ts), 1)
     self.assertEqual(
         ts[0],
         Trade(
             date=ts[0].date,
             instrument=Stock("VOO", Currency.USD),
             quantity=Decimal("23"),
             amount=Cash(currency=Currency.USD,
                         quantity=Decimal("-4981.11")),
             fees=Cash(currency=Currency.USD, quantity=Decimal("6.95")),
             flags=TradeFlags.OPEN,
         ),
     )
 def test_buySecurity(self) -> None:
     ts = self.activityByDate[date(2017, 9, 23)]
     self.assertEqual(len(ts), 1)
     self.assertEqual(
         ts[0],
         Trade(
             date=ts[0].date,
             instrument=Stock("USFD", Currency.USD),
             quantity=Decimal("178"),
             amount=Cash(currency=Currency.USD,
                         quantity=Decimal("-5427.15")),
             fees=Cash(currency=Currency.USD, quantity=Decimal("4.95")),
             flags=TradeFlags.OPEN,
         ),
     )
Example #23
0
 def test_sellSecurity(self) -> None:
     ts = self.activityByDate[date(2016, 10, 13)]
     self.assertEqual(len(ts), 1)
     self.assertEqual(
         ts[0],
         Trade(
             date=ts[0].date,
             instrument=Stock("VWO", Currency.USD),
             quantity=Decimal("-4"),
             amount=Cash(currency=Currency.USD,
                         quantity=Decimal("1234.56")),
             fees=Cash(currency=Currency.USD, quantity=Decimal("0.00")),
             flags=TradeFlags.CLOSE,
         ),
     )
Example #24
0
 def test_buySecurity(self) -> None:
     ts = self.activityByDate[date(2016, 4, 20)]
     self.assertEqual(len(ts), 1)
     self.assertEqual(
         ts[0],
         Trade(
             date=ts[0].date,
             instrument=Stock("VTI", Currency.USD),
             quantity=Decimal("12"),
             amount=Cash(currency=Currency.USD,
                         quantity=Decimal("-3456.78")),
             fees=Cash(currency=Currency.USD, quantity=Decimal("0.00")),
             flags=TradeFlags.OPEN,
         ),
     )
Example #25
0
    def test_reinvestShares(self) -> None:
        ts = self.activityByDate[date(2017, 2, 4)]
        self.assertEqual(len(ts), 4)

        self.assertEqual(
            ts[0],
            CashPayment(
                date=ts[0].date,
                instrument=Stock("VWO", Currency.USD),
                proceeds=helpers.cashUSD(Decimal("29.35")),
            ),
        )

        self.assertEqual(
            ts[1],
            Trade(
                date=ts[1].date,
                instrument=Stock("VWO", Currency.USD),
                quantity=Decimal("0.123"),
                amount=Cash(currency=Currency.USD, quantity=Decimal("-20.15")),
                fees=Cash(currency=Currency.USD, quantity=Decimal("0.00")),
                flags=TradeFlags.OPEN | TradeFlags.DRIP,
            ),
        )

        self.assertEqual(
            ts[3],
            Trade(
                date=ts[3].date,
                instrument=Stock("VOO", Currency.USD),
                quantity=Decimal("0.321"),
                amount=Cash(currency=Currency.USD, quantity=Decimal("-17.48")),
                fees=Cash(currency=Currency.USD, quantity=Decimal("0.00")),
                flags=TradeFlags.OPEN | TradeFlags.DRIP,
            ),
        )
def _parseInstrument(entry: _instrumentEntryTypes) -> Instrument:
    symbol = entry.symbol
    if not symbol:
        raise ValueError(f"Missing symbol in entry: {entry}")

    if entry.currency not in Currency.__members__:
        raise ValueError(f"Unrecognized currency in entry: {entry}")

    currency = Currency[entry.currency]

    if isinstance(entry, _IBChangeInDividendAccrual):
        exchange = None
    else:
        exchange = entry.exchange

    exchange = exchange or entry.listingExchange or None

    tag = entry.assetCategory
    if tag == "STK":
        return Stock(symbol=symbol, currency=currency, exchange=exchange)
    elif tag == "BILL" or tag == "BOND":
        return Bond(symbol=symbol,
                    currency=currency,
                    validateSymbol=False,
                    exchange=exchange)
    elif tag == "OPT":
        return _parseOption(
            symbol=symbol,
            currency=currency,
            multiplier=_parseFiniteDecimal(entry.multiplier),
            exchange=exchange,
        )
    elif tag == "FUT":
        return Future(
            symbol=symbol,
            currency=currency,
            multiplier=_parseFiniteDecimal(entry.multiplier),
            expiration=_parseIBDate(entry.expiry).date(),
            exchange=exchange,
        )
    elif tag == "CASH":
        return _parseForex(symbol=symbol, currency=currency, exchange=exchange)
    elif tag == "FOP":
        return _parseFutureOption(entry, exchange=exchange)
    else:
        raise ValueError(
            f"Unrecognized/unsupported security type in entry: {entry}")
def _parseVanguardPosition(p: _VanguardPosition,
                           activity: List[Activity]) -> Position:
    instrument: Instrument
    if len(p.symbol) > 0:
        instrument = Stock(p.symbol, currency=Currency.USD)
    else:
        instrument = _guessInstrumentForInvestmentName(p.investmentName)

    qty = Decimal(p.shares)

    realizedBasis = _realizedBasisForSymbol(instrument.symbol, activity)
    assert realizedBasis, "Invalid realizedBasis: %s for %s" % (
        realizedBasis,
        instrument,
    )

    return Position(instrument=instrument,
                    quantity=qty,
                    costBasis=realizedBasis)
def _parseSchwabPosition(p: _SchwabPosition) -> Optional[Position]:
    if re.match(r"Futures |Cash & Money Market|Account Total", p.symbol):
        return None

    instrument: Instrument
    if re.match(r"Equity|ETFs", p.securityType):
        instrument = Stock(p.symbol, currency=Currency.USD)
    elif re.match(r"Option", p.securityType):
        instrument = _parseOption(p.symbol)
    elif re.match(r"Fixed Income", p.securityType):
        instrument = Bond(p.symbol, currency=Currency.USD)
    else:
        raise ValueError(f"Unrecognized security type: {p.securityType}")

    return Position(
        instrument=instrument,
        quantity=_schwabDecimal(p.quantity),
        costBasis=Cash(currency=Currency.USD,
                       quantity=_schwabDecimal(p.costBasis)),
    )
Example #29
0
def _parsePositions(path: Path, lenient: bool = False) -> List[Position]:
    with open(path, newline="") as csvfile:
        stocksCriterion = csvsectionslicer.CSVSectionCriterion(
            startSectionRowMatch=["Stocks"],
            endSectionRowMatch=[""],
            rowFilter=lambda r: r[0:7],
        )
        bondsCriterion = csvsectionslicer.CSVSectionCriterion(
            startSectionRowMatch=["Bonds"],
            endSectionRowMatch=[""],
            rowFilter=lambda r: r[0:7],
        )
        optionsCriterion = csvsectionslicer.CSVSectionCriterion(
            startSectionRowMatch=["Options"],
            endSectionRowMatch=["", ""],
            rowFilter=lambda r: r[0:7],
        )

        instrumentBySection: Dict[
            csvsectionslicer.CSVSectionCriterion, _InstrumentFactory] = {
                stocksCriterion:
                lambda p: Stock(p.symbol, currency=Currency.USD),
                bondsCriterion:
                lambda p: Bond(p.symbol, currency=Currency.USD),
                optionsCriterion:
                lambda p: _parseOptionsPosition(p.description),
            }

        sections = csvsectionslicer.parseSectionsForCSV(
            csvfile, [stocksCriterion, bondsCriterion, optionsCriterion])

        positions: List[Position] = []

        for sec in sections:
            for r in sec.rows:
                pos = _parseFidelityPosition(
                    _FidelityPosition._make(r),
                    instrumentBySection[sec.criterion])
                positions.append(pos)

        return positions
def _forceParseVanguardTransaction(t: _VanguardTransaction,
                                   flags: TradeFlags) -> Optional[Trade]:
    instrument: Instrument
    if len(t.symbol) > 0:
        instrument = Stock(t.symbol, currency=Currency.USD)
    else:
        instrument = _guessInstrumentForInvestmentName(t.investmentName)

    totalFees = Decimal(t.commissionFees)
    amount = Decimal(t.principalAmount)

    if t.transactionDescription == "Redemption":
        shares = Decimal(t.shares) * (-1)
    else:
        shares = Decimal(t.shares)

    return Trade(
        date=_parseVanguardTransactionDate(t.tradeDate),
        instrument=instrument,
        quantity=shares,
        amount=Cash(currency=Currency.USD, quantity=amount),
        fees=Cash(currency=Currency.USD, quantity=totalFees),
        flags=flags,
    )