Exemplo n.º 1
0
def DoRemovalOfOption(txn, balances):
    entry = CreateTransaction(txn, allow_fees=True)

    item = txn['transactionItem']
    inst = item['instrument']
    assert inst['assetType'] == 'OPTION'
    symbol = GetOptionName(inst, entry.date.year)
    account = config['asset_position'].format(symbol='Options')

    # Note: The contract size isn't present. If we find varying contract
    # size we could consider calling the API again to find out what it is.
    amt = DF(item['amount'], QO)
    amt *= CSIZE

    # Find the current amount in the given account to figure out the side of the
    # position and the appropriate side to remove. {492fa5292636}
    balance = balances[account]
    try:
        pos = balance[(symbol, None)]
        sign = -1 if pos.units.number > 0 else 1
    except KeyError:
        # Could not find the position. Go short.
        sign = -1

    cost = CostSpec(None, None, None, None, None, False)
    entry.postings.extend([
        Posting(account, Amount(sign * amt, symbol), cost, Amount(ZERO, USD)),
        Posting(config['pnl']),
    ])

    return entry
Exemplo n.º 2
0
def get_market_value(pos, price_map, date=None):
    """Get the market value of a Position.

    This differs from the convert.get_value function in Beancount by returning
    the cost value if no price can be found.

    Args:
        pos: A Position.
        price_map: A dict of prices, as built by prices.build_price_map().
        date: A datetime.date instance to evaluate the value at, or None.

    Returns:
        An Amount, with value converted or if the conversion failed just the
        cost value (or the units if the position has no cost).
    """
    units_ = pos.units
    cost_ = pos.cost
    value_currency = cost_.currency if cost_ else None

    if value_currency:
        base_quote = (units_.currency, value_currency)
        _, price_number = get_price(price_map, base_quote, date)
        if price_number is not None:
            return Amount(units_.number * price_number, value_currency)
        return Amount(units_.number * cost_.number, value_currency)
    return units_
Exemplo n.º 3
0
    def test_comparisons(self):
        amount1 = Amount(D('100'), 'USD')
        amount2 = Amount(D('100'), 'USD')
        self.assertEqual(amount1, amount2)

        amount3 = Amount(D('101'), 'USD')
        self.assertNotEqual(amount1, amount3)
Exemplo n.º 4
0
def load_fund_transactions(filename: str,
                           account: str) -> List[FundTransaction]:
    expected_field_names = [
        'Date', 'Fund', 'Category', 'Description', 'Price', 'Amount', 'Shares',
        'Total Shares', 'Total Value'
    ]
    transactions = []  # type: List[FundTransaction]
    filename = os.path.abspath(filename)
    with open(filename, 'r', encoding='utf-8', newline='') as csvfile:
        reader = csv.DictReader(csvfile)
        if reader.fieldnames != expected_field_names:
            raise RuntimeError(
                'Actual field names %r != expected field names %r' %
                (reader.fieldnames, expected_field_names))
        for line_i, row in enumerate(reader):
            transactions.append(
                FundTransaction(
                    account=account,
                    date=datetime.datetime.strptime(row['Date'],
                                                    date_format).date(),
                    description=row['Category'],
                    memo=row['Description'],
                    price=parse_amount(row['Price']),
                    amount=parse_amount(row['Amount']),
                    units=Amount(parse_number(row['Shares']), row['Fund']),
                    balance=Amount(parse_number(row['Total Shares']),
                                   row['Fund']),
                    filename=filename,
                    line=line_i + 1,
                ))
    return transactions
Exemplo n.º 5
0
 def test_add(self):
     self.assertEqual(Amount(D('117.02'), 'CAD'),
                      amount.add(Amount(D('100'), 'CAD'),
                                 Amount(D('17.02'), 'CAD')))
     with self.assertRaises(ValueError):
         amount.add(Amount(D('100'), 'USD'),
                    Amount(D('17.02'), 'CAD'))
Exemplo n.º 6
0
    def extract(self, file, existing_entries=None):
        # Open the CSV file and create directives.
        entries = []
        index = 0
        with open(file.name, 'rb') as f:
            eml = parser.BytesParser().parse(fp=f)
            b = base64.b64decode(eml.get_payload()[0].get_payload())
            d = BeautifulSoup(b, "lxml")
            date_range = d.findAll(text=re.compile(
                '\d{4}\/\d{1,2}\/\d{1,2}-\d{4}\/\d{1,2}\/\d{1,2}'))[0]
            transaction_date = dateparse(
                date_range.split('-')[1].split('(')[0]).date()
            balance = '-' + d.find(src="https://pbdw.ebank.cmbchina.com/"
                                   "cbmresource/22/dyzd/jpkdyzd/xbgbdt/bqhkzz.jpg")\
                .parent.parent.find_next_sibling(
                'td').select('font')[0].text.replace('¥', '').replace(',', '').strip()
            txn_balance = data.Balance(account=self.account_name,
                                       amount=Amount(D(balance), 'CNY'),
                                       meta=data.new_metadata(".", 1000),
                                       tolerance=None,
                                       diff_amount=None,
                                       date=transaction_date)
            entries.append(txn_balance)

            bands = d.select('#fixBand29 #loopBand2>table>tr')
            for band in bands:
                tds = band.select('td #fixBand15 table table td')
                if len(tds) == 0:
                    continue
                trade_date = tds[1].text.strip()
                if trade_date == '':
                    trade_date = tds[2].text.strip()
                # date = datetime.strptime(trade_date,'%m%d').replace(year=transaction_date.year).date()
                date = datetime.strptime(trade_date, '%m%d')
                if date.month == 12 and transaction_date.month == 1:
                    date = date.replace(year=transaction_date.year - 1).date()
                else:
                    date = date.replace(year=transaction_date.year).date()
                full_descriptions = tds[3].text.strip().split('-')
                payee = full_descriptions[0]
                narration = '-'.join(full_descriptions[1:])
                real_currency = 'CNY'
                real_price = tds[4].text.replace('¥', '').replace('\xa0',
                                                                  '').strip()
                # print("Importing {} at {}".format(narration, date))
                flag = "*"
                amount = -Amount(D(real_price), real_currency)
                meta = data.new_metadata(file.name, index)
                txn = data.Transaction(
                    meta, date, self.FLAG, payee, narration, data.EMPTY_SET,
                    data.EMPTY_SET, [
                        data.Posting(self.account_name, amount, None, None,
                                     None, None),
                    ])

                entries.append(txn)

        # Insert a final balance check.

        return entries
Exemplo n.º 7
0
def load_balances(filename: str, date: datetime.date,
                  account: str) -> List[ImportedBalance]:
    expected_field_names = [
        'Fund', 'Name', 'Shares (#)', 'Closing Price', 'Closing Value'
    ]
    balances = []  # type: List[ImportedBalance]
    filename = os.path.abspath(filename)
    with open(filename, 'r', encoding='utf-8', newline='') as csvfile:
        reader = csv.DictReader(csvfile)
        if reader.fieldnames != expected_field_names:
            raise RuntimeError(
                'Actual field names %r != expected field names %r' %
                (reader.fieldnames, expected_field_names))
        for line_i, row in enumerate(reader):
            balances.append(
                ImportedBalance(
                    account=account,
                    date=date,
                    units=Amount(parse_number(row['Shares (#)']), row['Fund']),
                    price=Amount(parse_number(row['Closing Price']), 'USD'),
                    market_value=Amount(parse_number(row['Closing Value']),
                                        'USD'),
                    filename=filename,
                    line=line_i + 1,
                ))
        return balances
Exemplo n.º 8
0
    def average(self):
        """Average all lots of the same currency together..

        Returns:
          An instance of Inventory.
        """
        groups = collections.defaultdict(list)
        for position in self:
            lot = position.lot
            key = (lot.currency, lot.cost.currency if lot.cost else None)
            groups[key].append(position)

        average_inventory = Inventory()
        for (currency, cost_currency), positions in groups.items():
            total_units = sum(position.number for position in positions)
            units_amount = Amount(total_units, currency)

            if cost_currency:
                total_cost = sum(position.get_cost().number
                                 for position in positions)
                cost_amount = Amount(total_cost / total_units, cost_currency)
            else:
                cost_amount = None

            average_inventory.add_amount(units_amount, cost_amount)

        return average_inventory
Exemplo n.º 9
0
 def test_sub(self):
     self.assertEqual(Amount(D('82.98'), 'CAD'),
                      amount.sub(Amount(D('100'), 'CAD'),
                                        Amount(D('17.02'), 'CAD')))
     with self.assertRaises(ValueError):
         amount.sub(Amount(D('100'), 'USD'),
                           Amount(D('17.02'), 'CAD'))
Exemplo n.º 10
0
    def test_hash(self):
        amount = Amount(D('100,034.027456'), 'USD')
        self.assertTrue({amount: True})
        self.assertTrue({amount})

        amount2 = Amount(D('100,034.027456'), 'CAD')
        self.assertEqual(2, len({amount: True, amount2: False}))
Exemplo n.º 11
0
def get_miles_expirations(accapi, options):
    """Show expiry of airline miles, rewards points"""

    exclude = ''
    exclude_option = options.get('exclude_currencies', '')
    if exclude_option:
        exclude = "AND not currency ~ '{currencies}'".format(
            currencies=exclude_option)

    sql = """
       SELECT
         account,
         sum(number) AS Balance,
         currency as Points,
         LAST(date) AS Latest_Transaction
       WHERE
         not currency ~ '{currencies}'
         AND account ~ '{accounts_pattern}'
         {exclude}
       GROUP BY account,Points ORDER BY LAST(date)
    """.format(
        currencies=accapi.get_operating_currencies_regex(),
        accounts_pattern=options.get('accounts_pattern', 'Assets'),
        exclude=exclude,
    )
    rtypes, rrows = accapi.query_func(sql)
    if not rtypes:
        return [], {}, [[]]

    # our output table is slightly different from our query table:
    retrow_types = rtypes[:-1] + [('value', int), ('expiry', datetime.date)]
    RetRow = collections.namedtuple('RetRow', [i[0] for i in retrow_types])

    commodities = accapi.get_commodity_directives()

    def get_miles_metadata(miles):
        try:
            return commodities[miles].meta
        except:
            return {}

    ret_rows = []
    for row in rrows:
        meta = get_miles_metadata(row.points)

        value = meta.get('points-value', Amount(Decimal(0), 'NONE'))
        converted_value = Amount(value.number * row.balance, value.currency)

        expiry_months = meta.get('expiry-months', 0)
        if expiry_months >= 0:
            expiry = row.latest_transaction + datetime.timedelta(
                int(expiry_months) * 365 / 12)
        else:
            expiry = datetime.date.max

        ret_rows.append(RetRow(*row[:-1], converted_value, expiry))

    ret_rows.sort(key=lambda r: r[-1])
    return retrow_types, ret_rows
Exemplo n.º 12
0
    def test_compare_zero_to_none(self):
        pos1 = Position(Amount(ZERO, "CAD"), None)
        pos_none = None
        self.assertEqual(pos1, pos_none)
        self.assertEqual(pos_none, pos1)

        pos2 = Position(Amount(ZERO, "USD"), None)
        self.assertNotEqual(pos1, pos2)
Exemplo n.º 13
0
def test_training_examples(tmpdir):
    check_source(
        tmpdir,
        source_spec={
            'module': 'beancount_import.source.mint',
            'filename': mint_filename,
        },
        journal_contents=r"""
        plugin "beancount.plugins.auto_accounts"

        1900-01-01 open Liabilities:Credit-Card  USD
          mint_id: "My Credit Card"

        1900-01-01 open Assets:Checking  USD
          mint_id: "My Checking"

        2013-11-27 * "CR CARD PAYMENT ALEXANDRIA VA"
          Liabilities:Credit-Card   66.88 USD
            date: 2013-11-27
            source_desc: "CR CARD PAYMENT ALEXANDRIA VA"
            cleared: TRUE
          Assets:Checking  -66.88 USD
            date: 2013-12-02
            source_desc: "NATIONAL FEDERAL DES:TRNSFR"
            cleared: TRUE

        2016-08-10 * "STARBUCKS STORE 12345"
          Liabilities:Credit-Card  -2.45 USD
            date: 2016-08-10
            source_desc: "STARBUCKS STORE 12345"
            cleared: TRUE
          Expenses:Coffee            2.45 USD
        """,
        accounts=frozenset([
            'Assets:Checking',
            'Liabilities:Credit-Card',
        ]),
        pending=[],
        training_examples=[
            (PredictionInput(source_account='Liabilities:Credit-Card',
                             amount=Amount(D('66.88'), 'USD'),
                             date=datetime.date(2013, 11, 27),
                             key_value_pairs={
                                 'desc': 'CR CARD PAYMENT ALEXANDRIA VA'
                             }), 'Assets:Checking'),
            (PredictionInput(source_account='Assets:Checking',
                             amount=Amount(D('-66.88'), 'USD'),
                             date=datetime.date(2013, 12, 2),
                             key_value_pairs={
                                 'desc': 'NATIONAL FEDERAL DES:TRNSFR'
                             }), 'Liabilities:Credit-Card'),
            (PredictionInput(source_account='Liabilities:Credit-Card',
                             amount=Amount(D('-2.45'), 'USD'),
                             date=datetime.date(2016, 8, 10),
                             key_value_pairs={'desc': 'STARBUCKS STORE 12345'
                                              }), 'Expenses:Coffee'),
        ],
    )
Exemplo n.º 14
0
    def test_tostring(self):
        amount1 = Amount(D('100034.023'), 'USD')
        self.assertEqual('100034.023 USD', str(amount1))

        amount2 = Amount(D('0.00000001'), 'BTC')
        self.assertEqual('0.00000001 BTC', str(amount2))

        dcontext = display_context.DisplayContext()
        dformat = dcontext.build(commas=True)
        self.assertEqual('100,034.023 USD', amount1.to_string(dformat))
Exemplo n.º 15
0
    def test_constructor(self):
        amount = Amount(D('100,034.02'), 'USD')
        self.assertEqual(amount.number, D('100034.02'))

        # Ensure that it is possible to initialize the number to any object.
        # This is used when creating incomplete objects.
        class Dummy: pass
        amount = Amount(Dummy, Dummy)
        self.assertIs(amount.number, Dummy)
        self.assertIs(amount.currency, Dummy)
Exemplo n.º 16
0
 def test_merged_posting_is_cleared(self):
     postings = [
         Posting(account="a", units=Amount(D(10), "USD"), cost=None,
                 price=None, flag=None, meta=None),
         Posting(account="a", units=Amount(D(10), "USD"), cost=None,
                 price=None, flag=None, meta={"merge": True}),
     ]
     source = DescriptionBasedSource(lambda s: None)
     self.assertEqual(source.is_posting_cleared(postings[0]), False)
     self.assertEqual(source.is_posting_cleared(postings[1]), True)
Exemplo n.º 17
0
 def get_postings(self) -> List[Posting]:
     postings = [
         Posting(
             account=self.get_primary_account(),
             units=-Amount(Decimal(str(self.quantity)),
                           currency=self.symbol),
             # TODO handle cost basis by parsing cost-basis lots CSV, so we don't end
             # up getting beancount errors due to ambiguity
             cost=CostSpec(
                 number_per=MISSING,
                 number_total=None,
                 currency=MISSING,
                 date=None,
                 label=None,
                 merge=None,
             ),
             price=Amount(self.price, currency="USD"),
             flag=None,
             meta=self.get_meta(),
         ),
         Posting(
             account=self.get_other_account(),
             units=self.amount,
             cost=None,
             price=None,
             flag=None,
             meta=self.get_meta(),
         ),
     ]
     if self.action != SchwabAction.SELL_TO_OPEN:
         # too early to realize gains/losses when opening a short position
         postings.append(
             Posting(
                 account=self.get_cap_gains_account(),
                 units=MISSING,
                 cost=None,
                 price=None,
                 flag=None,
                 meta={},
             ))
     fees = self.fees
     if fees is not None:
         postings.append(
             Posting(
                 account=self.fees_account,
                 units=Amount(self.fees, currency="USD"),
                 cost=None,
                 price=None,
                 flag=None,
                 meta={},
             ))
     return postings
Exemplo n.º 18
0
    def posting(self, filename, lineno, account, units, cost, price, istotal,
                flag):
        """Process a posting grammar rule.

        Args:
          filename: the current filename.
          lineno: the current line number.
          account: A string, the account of the posting.
          position: An instance of Position from the grammar rule.
          price: Either None, or an instance of Amount that is the cost of the position.
          istotal: A bool, True if the price is for the total amount being parsed, or
                   False if the price is for each lot of the position.
          flag: A string, one-character, the flag associated with this posting.
        Returns:
          A new Posting object, with no parent entry.
        """
        # Prices may not be negative.
        if not __allow_negative_prices__:
            if price and isinstance(price.number,
                                    Decimal) and price.number < ZERO:
                meta = new_metadata(filename, lineno)
                self.errors.append(
                    ParserError(meta, (
                        "Negative prices are not allowed: {} "
                        "(see http://furius.ca/beancount/doc/bug-negative-prices "
                        "for workaround)").format(price), None))
                # Fix it and continue.
                price = Amount(abs(price.number), price.currency)

        # If the price is specified for the entire amount, compute the effective
        # price here and forget about that detail of the input syntax.
        if istotal:
            if units.number == ZERO:
                number = ZERO
            else:
                if __allow_negative_prices__:
                    number = price.number / units.number
                else:
                    number = price.number / abs(units.number)
            price = Amount(number, price.currency)

        # Note: Allow zero prices because we need them for round-trips for
        # conversion entries.
        #
        # if price is not None and price.number == ZERO:
        #     self.errors.append(
        #         ParserError(meta, "Price is zero: {}".format(price), None))

        meta = new_metadata(filename, lineno)
        return Posting(account, units, cost, price,
                       chr(flag) if flag else None, meta)
Exemplo n.º 19
0
def get_key_from_posting(entry: Transaction, posting: Posting,
                         source_postings: List[Posting], source_desc: str,
                         posting_date: datetime.date) -> MatchKey:
    del entry
    del source_postings
    transaction_type = posting.meta and posting.meta.get(TRANSACTION_TYPE_KEY)
    if isinstance(posting.cost, CostSpec):
        cost = Amount(posting.cost.number_per, posting.cost.currency)
    elif isinstance(posting.cost, Cost):
        cost = Amount(posting.cost.number, posting.cost.currency)
    else:
        cost = None
    return MatchKey(posting.account, posting_date, source_desc,
                    transaction_type, cost, posting.units)
Exemplo n.º 20
0
 def get_postings(self) -> List[Posting]:
     postings = [
         Posting(
             account=self.get_primary_account(),
             units=Amount(Decimal(str(self.quantity)),
                          currency=self.symbol),
             cost=CostSpec(
                 number_per=self.price,
                 number_total=None,
                 currency="USD",
                 date=None,
                 label=None,
                 merge=None,
             ),
             price=None,
             flag=None,
             meta=self.get_meta(),
         ),
         Posting(
             account=self.get_other_account(),
             units=self.amount,
             cost=None,
             price=None,
             flag=None,
             meta=self.get_meta(),
         ),
     ]
     if self.action == SchwabAction.BUY_TO_CLOSE:
         # need to record gains when closing a short position
         postings.append(
             Posting(
                 account=self.get_cap_gains_account(),
                 units=MISSING,
                 cost=None,
                 price=None,
                 flag=None,
                 meta={},
             ))
     fees = self.fees
     if fees is not None:
         postings.append(
             Posting(
                 account=self.fees_account,
                 units=Amount(self.fees, currency="USD"),
                 cost=None,
                 price=None,
                 flag=None,
                 meta={},
             ))
     return postings
Exemplo n.º 21
0
def CreateFeesPostings(txn) -> List[data.Posting]:
    """Get postings for fees."""
    postings = []
    fees = txn['fees']
    commission = fees.pop('commission')
    if commission:
        postings.append(
            Posting(config['commission'], Amount(DF(commission), USD)))
    fees.pop('optRegFee', None)
    fees.pop('secFee', None)
    for unused_name, number in sorted(fees.items()):
        if not number:
            continue
        postings.append(Posting(config['fees'], Amount(DF(number), USD)))
    return postings
Exemplo n.º 22
0
    def test_fromstring(self):
        amount1 = Amount(D('100'), 'USD')
        amount2 = Amount.from_string('100 USD')
        self.assertEqual(amount1, amount2)

        Amount.from_string('  100.00 USD  ')

        with self.assertRaises(ValueError):
            Amount.from_string('100')

        with self.assertRaises(ValueError):
            Amount.from_string('USD')

        with self.assertRaises(ValueError):
            Amount.from_string('100.00 U')
Exemplo n.º 23
0
    def test_mutation(self):
        amount1 = Amount(D('100'), 'USD')

        # Test how changing existing attributes should fail.
        with self.assertRaises(AttributeError) as ctx:
            amount1.currency = 'CAD'
        self.assertRegex("can't set attribute", str(ctx.exception))

        with self.assertRaises(AttributeError) as ctx:
            amount1.number = D('200')
        self.assertRegex("can't set attribute", str(ctx.exception))

        # Try setting a new attribute.
        with self.assertRaises(AttributeError):
            amount1.something = 42
Exemplo n.º 24
0
def create_simple_posting_with_cost_or_price(entry,
                                             account,
                                             number,
                                             currency,
                                             price_number=None,
                                             price_currency=None,
                                             cost_number=None,
                                             cost_currency=None,
                                             costspec=None):
    """Create a simple posting on the entry, with a cost (for purchases) or price (for sell transactions).

    Args:
      entry: The entry instance to add the posting to.
      account: A string, the account to use on the posting.
      number: A Decimal number or string to use in the posting's Amount.
      currency: A string, the currency for the Amount.
      price_number: A Decimal number or string to use for the posting's price Amount.
      price_currency: a string, the currency for the price Amount.
      cost_number: A Decimal number or string to use for the posting's cost Amount.
      cost_currency: a string, the currency for the cost Amount.
    Returns:
      An instance of Posting, and as a side-effect the entry has had its list of
      postings modified with the new Posting instance.
    """
    if isinstance(account, str):
        pass
    if not isinstance(number, Decimal):
        number = D(number)
    units = Amount(number, currency)

    if not (price_number or cost_number):
        print("Either price ({}) or cost ({}) must be specified ({})".format(
            price_number, cost_number, entry))
        import pdb
        pdb.set_trace()
        raise Exception(
            "Either price ({}) or cost ({}) must be specified".format(
                price_number, cost_number))

    price = Amount(price_number, price_currency) if price_number else None
    cost = Cost(cost_number, cost_currency, None,
                None) if cost_number else None
    cost = costspec if costspec else cost
    posting = data.Posting(account, units, cost, price, None, None)

    if entry is not None:
        entry.postings.append(posting)
    return posting
Exemplo n.º 25
0
def load_balances(filename: str) -> List[RawBalance]:
    expected_field_names = [
        'Name', 'Currency', 'Balance', 'Last Updated', 'State',
        'Last Transaction'
    ]
    balances = []
    filename = os.path.abspath(filename)
    with open(filename, 'r', encoding='utf-8', newline='') as csvfile:
        reader = csv.DictReader(csvfile)
        if reader.fieldnames != expected_field_names:
            raise RuntimeError(
                'Actual field names %r != expected field names %r' %
                (reader.fieldnames, expected_field_names))
        for line_i, row in enumerate(reader):
            date_str = row['Last Transaction'].strip()
            if not date_str:
                continue
            date = datetime.datetime.strptime(date_str, mint_date_format).date()
            balances.append(
                RawBalance(
                    account=row['Name'],
                    date=date,
                    amount=Amount(D(row['Balance']), row['Currency']),
                    filename=filename,
                    line=line_i + 1))
        return balances
Exemplo n.º 26
0
def new_filtered_entries(tx, params, get_amounts, selected_postings, config):
    """
    Beancount plugin: Dublicates all transaction's postings over time.

    Args:
      tx: A transaction instance.
      params: A parser options dict.
      get_amounts: A function, i.e. distribute_over_period.
      selected_postings: A list of postings.
      config: A configuration string in JSON format given in source file.
    Returns:
      An array of transaction entries.
    """

    all_pairs = []

    for _, new_account, params, posting in selected_postings:
        dates, amounts = get_amounts(params, tx.date, posting.units.number, config)
        all_pairs.append( (dates, amounts, posting, new_account) )

    map_of_dates = {}

    for dates, amounts, posting, new_account in all_pairs:

        for i in range( min(len(dates), len(amounts)) ):
            if(not dates[i] in map_of_dates):
                map_of_dates[dates[i]] = []

            amount = Amount(amounts[i], posting.units.currency)
            # Income/Expense to be spread
            map_of_dates[dates[i]].append(Posting(account=new_account,
                              units=amount,
                              cost=None,
                              price=None,
                              flag=posting.flag,
                              meta=new_metadata(tx.meta['filename'], tx.meta['lineno'])))

            # Asset/Liability that buffers the difference
            map_of_dates[dates[i]].append(Posting(account=posting.account,
                              units=mul(amount, D(-1)),
                              cost=None,
                              price=None,
                              flag=posting.flag,
                              meta=new_metadata(tx.meta['filename'], tx.meta['lineno'])))

    new_transactions = []
    for i, (date, postings) in enumerate(sorted(map_of_dates.items())):
        if len(postings) > 0:
            e = Transaction(
                date=date,
                meta=tx.meta,
                flag=tx.flag,
                payee=tx.payee,
                narration=tx.narration + config['suffix']%(i+1, len(dates)),
                tags={config['tag']},
                links=tx.links,
                postings=postings)
            new_transactions.append(e)

    return new_transactions
Exemplo n.º 27
0
def create_simple_posting_with_cost(entry, account, number, currency,
                                    cost_number, cost_currency):
    """Create a simple posting on the entry, with just a number and currency (no cost).

    Args:
      entry: The entry instance to add the posting to.
      account: A string, the account to use on the posting.
      number: A Decimal number or string to use in the posting's Amount.
      currency: A string, the currency for the Amount.
      cost_number: A Decimal number or string to use for the posting's cost Amount.
      cost_currency: a string, the currency for the cost Amount.
    Returns:
      An instance of Posting, and as a side-effect the entry has had its list of
      postings modified with the new Posting instance.
    """
    if isinstance(account, str):
        pass
    if not isinstance(number, Decimal):
        number = D(number)
    if cost_number and not isinstance(cost_number, Decimal):
        cost_number = D(cost_number)
    units = Amount(number, currency)
    cost = Cost(cost_number, cost_currency, None, None)
    posting = Posting(account, units, cost, None, None, None)
    if entry is not None:
        entry.postings.append(posting)
    return posting
Exemplo n.º 28
0
def _booking_method_xifo(entry, posting, matches, reverse_order):
    """FIFO and LIFO booking method implementations."""
    booked_reductions = []
    booked_matches = []
    errors = []
    insufficient = False

    # Each up the positions.
    sign = -1 if posting.units.number < ZERO else 1
    remaining = abs(posting.units.number)
    for match in sorted(matches, key=lambda p: p.cost and p.cost.date,
                        reverse=reverse_order):
        if remaining <= ZERO:
            break

        # If the inventory somehow ended up with mixed lots, skip this one.
        if match.units.number * sign > ZERO:
            continue

        # Compute the amount of units we can reduce from this leg.
        size = min(abs(match.units.number), remaining)
        booked_reductions.append(
            posting._replace(units=Amount(size * sign, match.units.currency),
                             cost=match.cost))
        booked_matches.append(match)
        remaining -= size

    # If we couldn't eat up all the requested reduction, return an error.
    insufficient = (remaining > ZERO)

    return booked_reductions, booked_matches, errors, insufficient
Exemplo n.º 29
0
def deserialise(json_entry):
    """Parse JSON to a Beancount entry.

    Args:
        json_entry: The entry.

    Raises:
        KeyError: if one of the required entry fields is missing.
        FavaAPIException: if the type of the given entry is not supported.
    """
    if json_entry['type'] == 'Transaction':
        date = util.date.parse_date(json_entry['date'])[0]
        narration, tags, links = extract_tags_links(json_entry['narration'])
        postings = [deserialise_posting(pos) for pos in json_entry['postings']]
        return data.Transaction(json_entry['meta'], date, json_entry['flag'],
                                json_entry['payee'], narration, tags, links,
                                postings)
    if json_entry['type'] == 'Balance':
        date = util.date.parse_date(json_entry['date'])[0]
        number = parse_number(json_entry['number'])
        amount = Amount(number, json_entry['currency'])

        return data.Balance(json_entry['meta'], date, json_entry['account'],
                            amount, None, None)
    if json_entry['type'] == 'Note':
        date = util.date.parse_date(json_entry['date'])[0]
        comment = json_entry['comment'].replace('"', '')
        return data.Note(json_entry['meta'], date, json_entry['account'],
                         comment)
    raise FavaAPIException('Unsupported entry type.')
Exemplo n.º 30
0
def test_add_ignored(tmpdir):
    journal_path = create_journal(
        tmpdir, """
2015-01-01 * "Test transaction 1"
  Assets:Account-A  100 USD
  Assets:Account-B
""")
    ignored_path = create_journal(
        tmpdir, """
2015-03-01 * "Test transaction 2"
  Assets:Account-A  100 USD
  Assets:Account-B
""",
        name='ignored.beancount')
    editor = journal_editor.JournalEditor(journal_path, ignored_path)
    stage = editor.stage_changes()
    new_transaction = Transaction(
        meta=None,
        date=datetime.date(2015, 4, 1),
        flag='*',
        payee=None,
        narration='New transaction',
        tags=EMPTY_SET,
        links=EMPTY_SET,
        postings=[
            Posting(
                account='Assets:Account-A',
                units=Amount(Decimal(3), 'USD'),
                cost=None,
                price=None,
                flag=None,
                meta=None),
            Posting(
                account='Assets:Account-B',
                units=MISSING,
                cost=None,
                price=None,
                flag=None,
                meta=None),
        ],
    )
    stage.add_entry(new_transaction, ignored_path)
    result = stage.apply()
    check_file_contents(
        journal_path, """
2015-01-01 * "Test transaction 1"
  Assets:Account-A  100 USD
  Assets:Account-B
""")
    check_file_contents(
        ignored_path, """
2015-03-01 * "Test transaction 2"
  Assets:Account-A  100 USD
  Assets:Account-B

2015-04-01 * "New transaction"
  Assets:Account-A  3 USD
  Assets:Account-B
""")
    check_journal_entries(editor)
Exemplo n.º 31
0
def oneliner(entries, options_map, config):
  """Parse note oneliners into valid transactions. For example,
  1999-12-31 note Assets:Cash "Income:Test -16.18 EUR * Description goes here *" """

  errors = []

  new_entries = []

  for entry in entries:
    if(isinstance(entry, data.Note) and entry.comment[-1:] == "*"):
      comment = entry.comment
      try:
        k = None
        maybe_cost = RE_COST.findall(comment)
        if len(maybe_cost) > 0:
          amount = maybe_cost[0].split()[0]
          currency = maybe_cost[0].split()[1]
          cost = Cost(D(amount), currency, None, None)
          k = mul(cost, D(-1))
          comment = RE_COST.sub('', comment)
        else:
          cost = None

        maybe_price = RE_PRICE.findall(comment)
        if len(maybe_price) > 0:
          price = Amount.from_string(maybe_price[0])
          k = k or mul(price, D(-1))
          comment = RE_PRICE.sub('', comment)
        else:
          price = None

        comment_tuple = comment.split()
        other_account = comment_tuple[0]
        units = Amount.from_string(' '.join(comment_tuple[1:3]))
        flag = comment_tuple[3]
        narration_tmp = ' '.join(comment_tuple[4:-1])
        tags = {'NoteToTx'}
        for tag in RE_TAG.findall(narration_tmp):
          tags.add( tag[1] )
        narration = RE_TAG.sub('', narration_tmp).rstrip()

        k = k or Amount(D(-1), units.currency)

        # print(type(cost), cost, type(price), price, type(units), units, k, comment)
        p1 = data.Posting(account=other_account,
                  units=units,
                  cost=cost,
                  price=price,
                  flag=None,
                  meta={'filename': entry.meta['filename'], 'lineno': entry.meta['lineno']})
        p2 = data.Posting(account=entry.account,
                  units=mul(k, units.number),
                  cost=cost,
                  price=None,
                  flag=None,
                  meta={'filename': entry.meta['filename'], 'lineno': entry.meta['lineno']})
        e = data.Transaction(date=entry.date,
                   flag=flag,
                   payee=None,  # TODO
                   narration=narration,
                   tags=tags,  # TODO
                   links=EMPTY_SET,  # TODO
                   postings=[p1, p2],
                   meta=entry.meta)
        new_entries.append(e)
        # print(e)
      except:
        print('beancount-oneliner error:', entry, sys.exc_info())
    else:
      new_entries.append(entry)

  return new_entries, errors