コード例 #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,
            ),
        )
コード例 #2
0
 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,
         ),
     )
コード例 #3
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,
         ),
     )
コード例 #4
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,
         ),
     )
コード例 #5
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,
         ),
     )
コード例 #6
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,
            ),
        )
コード例 #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,
         ),
     )
コード例 #8
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,
         ),
     )
コード例 #9
0
 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,
         ),
     )
コード例 #10
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,
         ),
     )
コード例 #11
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,
         ),
     )
コード例 #12
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,
         ),
     )
コード例 #13
0
 def test_sellToCloseOption(self) -> None:
     ts = self.activityByDate[date(2017, 11, 9)]
     self.assertEqual(
         ts[4],
         Trade(
             date=ts[4].date,
             instrument=Option(
                 underlying="SPY",
                 currency=Currency.USD,
                 optionType=OptionType.CALL,
                 expiration=date(2018, 1, 25),
                 strike=Decimal("260"),
             ),
             quantity=Decimal("-4"),
             amount=Cash(currency=Currency.USD, quantity=Decimal("94.04")),
             fees=Cash(currency=Currency.USD, quantity=Decimal("5.03")),
             flags=TradeFlags.CLOSE,
         ),
     )
コード例 #14
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,
         ),
     )
コード例 #15
0
 def test_sellToCloseOption(self) -> None:
     ts = self.activityByDate[date(2018, 11, 9)]
     self.assertEqual(len(ts), 1)
     self.assertEqual(
         ts[0],
         Trade(
             date=ts[0].date,
             instrument=Option(
                 underlying="INTC",
                 currency=Currency.USD,
                 optionType=OptionType.PUT,
                 expiration=date(2018, 12, 7),
                 strike=Decimal("48.50"),
             ),
             quantity=Decimal("-1"),
             amount=Cash(currency=Currency.USD, quantity=Decimal("140")),
             fees=Cash(currency=Currency.USD, quantity=Decimal("5.60")),
             flags=TradeFlags.CLOSE,
         ),
     )
コード例 #16
0
 def test_assignedOption(self) -> None:
     ts = self.activityByDate[date(2018, 2, 4)]
     self.assertEqual(len(ts), 4)
     self.assertEqual(
         ts[3],
         Trade(
             date=ts[3].date,
             instrument=Option(
                 underlying="QQQ",
                 currency=Currency.USD,
                 optionType=OptionType.CALL,
                 expiration=date(2018, 2, 1),
                 strike=Decimal("130"),
             ),
             quantity=Decimal("1"),
             amount=Cash(currency=Currency.USD, quantity=Decimal(0)),
             fees=Cash(currency=Currency.USD, quantity=Decimal(0)),
             flags=TradeFlags.CLOSE | TradeFlags.ASSIGNED_OR_EXERCISED,
         ),
     )
コード例 #17
0
 def test_expiredShortOption(self) -> None:
     ts = self.activityByDate[date(2018, 12, 3)]
     self.assertEqual(len(ts), 1)
     self.assertEqual(
         ts[0],
         Trade(
             date=ts[0].date,
             instrument=Option(
                 underlying="CSCO",
                 currency=Currency.USD,
                 optionType=OptionType.PUT,
                 expiration=date(2018, 11, 30),
                 strike=Decimal("44.50"),
             ),
             quantity=Decimal("1"),
             amount=Cash(currency=Currency.USD, quantity=Decimal(0)),
             fees=Cash(currency=Currency.USD, quantity=Decimal(0)),
             flags=TradeFlags.CLOSE | TradeFlags.EXPIRED,
         ),
     )
コード例 #18
0
 def test_sellToOpenOption(self) -> None:
     ts = self.activityByDate[date(2018, 12, 12)]
     self.assertEqual(len(ts), 2)
     self.assertEqual(
         ts[1],
         Trade(
             date=ts[1].date,
             instrument=Option(
                 underlying="MAR",
                 currency=Currency.USD,
                 optionType=OptionType.CALL,
                 expiration=date(2018, 12, 28),
                 strike=Decimal("112"),
             ),
             quantity=Decimal("-1"),
             amount=Cash(currency=Currency.USD, quantity=Decimal("190")),
             fees=Cash(currency=Currency.USD, quantity=Decimal("5.60")),
             flags=TradeFlags.OPEN,
         ),
     )
コード例 #19
0
 def test_buyToOpenOption(self) -> None:
     ts = self.activityByDate[date(2017, 8, 26)]
     self.assertEqual(len(ts), 1)
     self.assertEqual(
         ts[0],
         Trade(
             date=ts[0].date,
             instrument=Option(
                 underlying="SPY",
                 currency=Currency.USD,
                 optionType=OptionType.PUT,
                 expiration=date(2018, 3, 22),
                 strike=Decimal("198"),
             ),
             quantity=Decimal("32"),
             amount=Cash(currency=Currency.USD,
                         quantity=Decimal("-3185.67")),
             fees=Cash(currency=Currency.USD, quantity=Decimal("25.31")),
             flags=TradeFlags.OPEN,
         ),
     )
コード例 #20
0
def _forceParseSchwabTransaction(t: _SchwabTransaction, flags: TradeFlags) -> Trade:
    quantity = Decimal(t.quantity)
    if re.match(r"^Sell", t.action):
        quantity = -quantity

    fees = Decimal(0)
    if t.fees:
        fees = _schwabDecimal(t.fees)

    amount = Decimal(0)
    if t.amount:
        # Schwab automatically deducts the fees, but we need to add them back in for consistency with other brokers
        # (where the denominating currency of these two things may differ)
        amount = _schwabDecimal(t.amount) + fees

    return Trade(
        date=_parseSchwabTransactionDate(t.date),
        instrument=_guessInstrumentFromSymbol(t.symbol),
        quantity=quantity,
        amount=Cash(currency=Currency.USD, quantity=amount),
        fees=Cash(currency=Currency.USD, quantity=fees),
        flags=flags,
    )
コード例 #21
0
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,
    )
コード例 #22
0
def _forceParseFidelityTransaction(t: _FidelityTransaction,
                                   flags: TradeFlags) -> Trade:
    quantity = Decimal(t.quantity)

    totalFees = Decimal(0)
    # Fidelity's total fees include commision and fees
    if t.commission:
        totalFees += Decimal(t.commission)
    if t.fees:
        totalFees += Decimal(t.fees)

    amount = Decimal(0)
    if t.amount:
        amount = Decimal(t.amount) + totalFees

    currency = Currency[t.currency]
    return Trade(
        date=_parseFidelityTransactionDate(t.date),
        instrument=_guessInstrumentFromSymbol(t.symbol, currency),
        quantity=quantity,
        amount=Cash(currency=currency, quantity=amount),
        fees=Cash(currency=currency, quantity=totalFees),
        flags=flags,
    )
コード例 #23
0
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])
コード例 #24
0
def _parseTradeConfirm(trade: _IBTradeConfirm) -> Trade:
    try:
        instrument = _parseInstrument(trade)

        flagsByCode = {
            # Codes referenced from:
            # https://www.interactivebrokers.com/en/software/reportguide/reportguide.htm#reportguide/codestradeconfirm.htm
            "A": TradeFlags.ASSIGNED_OR_EXERCISED,  # Assignment
            "C": TradeFlags.CLOSE,  # Closing Trade
            "Ep": TradeFlags.EXPIRED,  # Resulted from an Expired Position
            "Ex": TradeFlags.ASSIGNED_OR_EXERCISED,  # Exercise
            "L": TradeFlags.
            LIQUIDATED,  # Ordered by IB (Margin Violation, Forced Futures Sell)
            "O": TradeFlags.OPEN,  # Opening Trade
            "R": TradeFlags.DRIP,  # Dividend Reinvestment
            "T": TradeFlags.OPEN,  # Transfer
            # Ignored
            "D": TradeFlags.NONE,  # IB acted as Dual Agent
            "P": TradeFlags.NONE,  # Partial Execution
            # Currently Unsupported
            # 'B': TradeFlags.NONE, # Automatic Buy-in
            # 'Ca': TradeFlags.NONE, # Cancelled
            # 'Co': TradeFlags.NONE, # Corrected Trade
            # 'G': TradeFlags.NONE, # Trade in Guaranteed Account Segment
            # 'M': TradeFlags.NONE, # Entered manually by IB
            # 'Si': TradeFlags.NONE, # Solicited Trade (This order was solicited by Interactive Brokers).
        }

        codes = trade.code.split(";")
        flags = TradeFlags.NONE
        for c in codes:
            if c == "":
                continue

            if c not in flagsByCode:
                raise ValueError(f"Unrecognized code {c} in trade: {trade}")

            flags |= flagsByCode[c]

        # Codes are not always populated with open/close, not sure why
        if flags & (TradeFlags.OPEN | TradeFlags.CLOSE) == TradeFlags.NONE:
            if trade.buySell == "BUY":
                flags |= TradeFlags.OPEN
            else:
                flags |= TradeFlags.CLOSE

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

        # We could choose to account for accrued interest payments as part of
        # the trade price or as a separate cash payment; the former seems
        # marginally cleaner and more sensible for a trade log.
        proceeds = Cash(
            currency=Currency[trade.currency],
            quantity=_parseFiniteDecimal(trade.proceeds) +
            _parseFiniteDecimal(trade.accruedInt),
        )

        return Trade(
            date=_parseIBDate(trade.tradeDate),
            instrument=instrument,
            quantity=_parseFiniteDecimal(trade.quantity),
            amount=proceeds,
            fees=Cash(
                currency=Currency[trade.commissionCurrency],
                quantity=-(_parseFiniteDecimal(trade.commission) +
                           _parseFiniteDecimal(trade.tax)),
            ),
            flags=flags,
        )
    except InvalidOperation:
        raise ValueError(
            f"One of the numeric trade values is out of range: {trade}")