Exemple #1
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_tBill(self) -> None:
     self.assertEqual(self.positions[0].instrument,
                      Bond("942792RU5", Currency.USD))
     self.assertEqual(self.positions[0].quantity, 10000)
     self.assertEqual(
         self.positions[0].costBasis,
         Cash(currency=Currency.USD, quantity=Decimal("9800")),
     )
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
Exemple #5
0
 def test_tBill(self) -> None:
     self.assertEqual(
         self.positions[0].instrument,
         Bond(
             "U S TREASURY BILL CPN  0.00000 % MTD 2017-04-10 DTD 2017-08-14",
             currency=Currency.USD,
             validateSymbol=False,
         ),
     )
     self.assertEqual(self.positions[0].quantity, 5000)
Exemple #6
0
 def test_redeemBond(self) -> None:
     ts = self.activityByDate[date(2018, 6, 2)]
     self.assertEqual(len(ts), 1)
     self.assertEqual(
         ts[0],
         Trade(
             date=ts[0].date,
             instrument=Bond(symbol="912586AC5", currency=Currency.USD),
             quantity=Decimal("-10000"),
             amount=Cash(currency=Currency.USD, quantity=Decimal("10000")),
             fees=Cash(currency=Currency.USD, quantity=Decimal(0)),
             flags=TradeFlags.CLOSE | TradeFlags.EXPIRED,
         ),
     )
Exemple #7
0
 def test_buyBond(self) -> None:
     ts = self.activityByDate[date(2018, 3, 25)]
     self.assertEqual(len(ts), 1)
     self.assertEqual(
         ts[0],
         Trade(
             date=ts[0].date,
             instrument=Bond(symbol="912586AC5", currency=Currency.USD),
             quantity=Decimal("10000"),
             amount=Cash(currency=Currency.USD,
                         quantity=Decimal("-9956.80")),
             fees=Cash(currency=Currency.USD, quantity=Decimal(0)),
             flags=TradeFlags.OPEN,
         ),
     )
Exemple #8
0
    def test_bondInterest(self) -> None:
        ts = self.activityByDate[date(2019, 1, 17)]
        self.assertEqual(len(ts), 1)
        self.assertEqual(
            ts[0],
            CashPayment(
                date=ts[0].date,
                instrument=Bond("BA 3 3/4 02/17/19",
                                Currency.USD,
                                validateSymbol=False),
                proceeds=helpers.cashUSD(Decimal("18.75")),
            ),
        )

        self.assertNotIn(date(2019, 1, 15), self.activityByDate)
        self.assertNotIn(date(2019, 1, 14), self.activityByDate)
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}")
Exemple #10
0
 def test_securityOutgoingTransfer(self) -> None:
     ts = self.activityByDate[date(2017, 9, 12)]
     self.assertEqual(len(ts), 1)
     self.assertEqual(
         ts[0],
         Trade(
             date=ts[0].date,
             instrument=Bond(
                 "U S TREASURY BILL CPN  0.00000 % 2017-04-10 DTD 2017-08-14",
                 Currency.USD,
                 validateSymbol=False,
             ),
             quantity=Decimal("-10000"),
             amount=Cash(currency=Currency.USD, quantity=Decimal("0.00")),
             fees=Cash(currency=Currency.USD, quantity=Decimal("0.00")),
             flags=TradeFlags.CLOSE,
         ),
     )
Exemple #11
0
 def test_redeemTBill(self) -> None:
     ts = self.activityByDate[date(2017, 9, 23)]
     self.assertEqual(len(ts), 2)
     self.assertEqual(
         ts[0],
         Trade(
             date=ts[0].date,
             instrument=Bond(
                 "U S TREASURY BILL CPN  0.00000 % MTD 2017-03-10 DTD 2017-09-10",
                 Currency.USD,
                 validateSymbol=False,
             ),
             quantity=Decimal("-10000"),
             amount=Cash(currency=Currency.USD,
                         quantity=Decimal("9987.65")),
             fees=Cash(currency=Currency.USD, quantity=Decimal("0.00")),
             flags=TradeFlags.CLOSE,
         ),
     )
Exemple #12
0
 def test_buyBond(self) -> None:
     # IB doesn't export the CUSIP unless the data is paid for.
     symbol = "ALLY 3 3/4 11/18/19"
     ts = self.tradesBySymbol[symbol]
     self.assertEqual(len(ts), 1)
     self.assertEqual(ts[0].date.date(), date(2019, 3, 19))
     self.assertEqual(
         ts[0].instrument,
         Bond(symbol,
              Currency.USD,
              validateSymbol=False,
              exchange="UBSBOND"),
     )
     self.assertEqual(ts[0].quantity, Decimal("2000"))
     self.assertEqual(
         ts[0].amount,
         Cash(currency=Currency.USD, quantity=Decimal("-2035.13")))
     self.assertEqual(ts[0].fees,
                      Cash(currency=Currency.USD, quantity=Decimal("2")))
     self.assertEqual(ts[0].flags, TradeFlags.OPEN)
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)),
    )
Exemple #14
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
Exemple #15
0
 def test_tBill(self) -> None:
     self.assertEqual(self.positions[0].instrument,
                      Bond("193845XM2", Currency.USD))
     self.assertEqual(self.positions[0].quantity, 10000)
     self.assertEqual(self.positions[0].costBasis,
                      helpers.cashUSD(Decimal("9956.80")))
def _parseSchwabTransaction(
    t: _SchwabTransaction, otherTransactionsThisDate: Iterable[_SchwabTransaction]
) -> Optional[Activity]:
    dividendActions = {"Cash Dividend", "Reinvest Dividend", "Non-Qualified Div"}

    if t.action in dividendActions:
        return CashPayment(
            date=_parseSchwabTransactionDate(t.date),
            instrument=Stock(t.symbol, currency=Currency.USD),
            proceeds=Cash(currency=Currency.USD, quantity=_schwabDecimal(t.amount)),
        )

    interestActions = {"Credit Interest", "Margin Interest"}

    if t.action in interestActions:
        return CashPayment(
            date=_parseSchwabTransactionDate(t.date),
            instrument=None,
            proceeds=Cash(currency=Currency.USD, quantity=_schwabDecimal(t.amount)),
        )

    # Bond redemptions are split into two entries, for some reason.
    if t.action == "Full Redemption Adj":
        redemption = next(
            (
                r
                for r in otherTransactionsThisDate
                if r.symbol == t.symbol and r.action == "Full Redemption"
            ),
            None,
        )
        if not redemption:
            raise ValueError(
                f'Expected to find "Full Redemption" action on same date as {t}'
            )

        quantity = Decimal(redemption.quantity)
        amount = _schwabDecimal(t.amount)

        return Trade(
            date=_parseSchwabTransactionDate(t.date),
            instrument=Bond(t.symbol, currency=Currency.USD),
            quantity=quantity,
            amount=Cash(currency=Currency.USD, quantity=amount),
            fees=Cash(currency=Currency.USD, quantity=Decimal(0)),
            # TODO: Do we want a new TradeFlag?
            flags=TradeFlags.CLOSE | TradeFlags.EXPIRED,
        )

    if t.action == "Full Redemption":
        adj = next(
            (
                r
                for r in otherTransactionsThisDate
                if r.symbol == t.symbol and r.action == "Full Redemption Adj"
            ),
            None,
        )
        if not adj:
            raise ValueError(
                f'Expected to find "Full Redemption Adj" action on same date as {t}'
            )

        # Will process on the adjustment entry
        return None

    ignoredActions = {
        "Wire Funds",
        "Wire Funds Received",
        "MoneyLink Transfer",
        "MoneyLink Deposit",
        "Long Term Cap Gain Reinvest",
        "ATM Withdrawal",
        "Schwab ATM Rebate",
        "Service Fee",
        "Journal",
        "Misc Cash Entry",
        "Security Transfer",
    }

    if t.action in ignoredActions:
        return None

    flagsByAction = {
        "Buy": TradeFlags.OPEN,
        "Sell Short": TradeFlags.OPEN,
        "Buy to Open": TradeFlags.OPEN,
        "Sell to Open": TradeFlags.OPEN,
        "Reinvest Shares": TradeFlags.OPEN | TradeFlags.DRIP,
        "Sell": TradeFlags.CLOSE,
        "Buy to Close": TradeFlags.CLOSE,
        "Sell to Close": TradeFlags.CLOSE,
        "Assigned": TradeFlags.CLOSE | TradeFlags.ASSIGNED_OR_EXERCISED,
        "Exchange or Exercise": TradeFlags.CLOSE | TradeFlags.ASSIGNED_OR_EXERCISED,
        "Expired": TradeFlags.CLOSE | TradeFlags.EXPIRED,
    }

    if not t.action in flagsByAction:
        raise ValueError(f'Unexpected Schwab action "{t.action}" in transaction {t}')

    return _forceParseSchwabTransaction(t, flags=flagsByAction[t.action])
def _extractPosition(p: IB.Position) -> Position:
    tag = p.contract.secType
    symbol = p.contract.localSymbol

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

    currency = Currency[p.contract.currency]
    exchange = p.contract.exchange or None

    try:
        instrument: Instrument
        if tag == "STK":
            instrument = Stock(symbol=symbol,
                               currency=currency,
                               exchange=exchange)
        elif tag == "BILL" or tag == "BOND":
            instrument = Bond(
                symbol=symbol,
                currency=currency,
                validateSymbol=False,
                exchange=exchange,
            )
        elif tag == "OPT":
            instrument = _parseOption(
                symbol=symbol,
                currency=currency,
                multiplier=_parseFiniteDecimal(p.contract.multiplier),
                exchange=exchange,
            )
        elif tag == "FUT":
            instrument = Future(
                symbol=symbol,
                currency=currency,
                multiplier=_parseFiniteDecimal(p.contract.multiplier),
                expiration=_parseIBDate(
                    p.contract.lastTradeDateOrContractMonth).date(),
                exchange=exchange,
            )
        elif tag == "FOP":
            instrument = _parseFutureOptionContract(p.contract,
                                                    currency=currency,
                                                    exchange=exchange)
        elif tag == "CASH":
            instrument = _parseForex(symbol=symbol,
                                     currency=currency,
                                     exchange=exchange)
        else:
            raise ValueError(
                f"Unrecognized/unsupported security type in position: {p}")

        qty = _parseFiniteDecimal(p.position)
        costBasis = _parseFiniteDecimal(p.avgCost) * qty

        return Position(
            instrument=instrument,
            quantity=qty,
            costBasis=Cash(currency=Currency[p.contract.currency],
                           quantity=costBasis),
        )
    except InvalidOperation:
        raise ValueError(
            f"One of the numeric position or contract values is out of range: {p}"
        )