Skip to main content

A robust portfolio tracker with multi-currency support and short selling

Project description

FinTrack - Advanced Portfolio Tracker

Tests Python License

A Python package for tracking and analyzing stock portfolios with multi-currency support, automatic dividend tracking, and short selling.

Important note

Tests, instructions and docstrings written using Claude, I tried to find any incorrect information but some may have slipped through the crack.

Features

  • Portfolio Management: Track multiple stock holdings with buy/sell transactions
  • Short Selling: Open and close short positions with mark-to-market daily valuation
  • Dynamic Price Tracking: Automatically fetch and store historical stock prices using yfinance
  • Multi-Currency Support: Handle stocks traded in different currencies with automatic conversion
  • Cash Management: Maintain accurate cash balances accounting for all transaction types and dividend payments
  • Dividend Tracking: Automatically capture and account for dividend payments (long positions only)
  • Historical Analysis: Query portfolio composition and value at any point in time
  • Stock Returns Analysis: Calculate individual stock performance including short positions
  • Index Comparison: Compare your portfolio returns against benchmark indices
  • Comprehensive Logging: Track all operations with detailed logging
  • Input Validation: Validate all transaction data before processing

Installation

Install the package using pip:

pip install FinTrack

For development:

git clone https://github.com/arofredriksson/FinTrack.git
cd FinTrack
pip install -e ".[dev]"
pytest tests/

Quick Start

1. Create a Transaction CSV

Create transactions.csv:

Date;Ticker;Type;Amount;Price
2023-01-15;AAPL;Buy;10;150.00
2023-02-20;MSFT;Buy;5;250.00
2023-03-10;AAPL;Sell;5;165.00
2023-04-05;TSLA;Buy;2;800.00
2023-06-01;TSLA;Short;3;290.00
2023-09-15;TSLA;Cover;3;240.00

2. Initialize and Query

from FinTrack import FinTrack
from datetime import date

# Create portfolio
portfolio = FinTrack(
    initial_cash=150000,
    currency="USD",
    csv_file="transactions.csv"
)

# Update with latest data
portfolio.update_portfolio()

# Get current holdings (short positions prefixed with "Short: ")
holdings = portfolio.get_current_holdings()
print(f"Holdings: {holdings}")

# Get portfolio value over time (shorts valued mark-to-market)
values = portfolio.get_portfolio_value(
    date(2023, 1, 1),
    date(2023, 12, 31)
)

# Get portfolio summary
summary = portfolio.get_portfolio_summary()
print(f"Total Value: {summary['total_value']:,.2f} {summary['currency']}")

CSV Format

Delimiter: Semicolon (;)

Required columns:

Column Type Description
Date YYYY-MM-DD Transaction date
Ticker String Stock ticker symbol
Type Buy/Sell/Short/Cover Transaction type
Amount Integer Number of shares
Price Number Price per share

Transaction type effects:

Type Shares Cash
Buy +shares −(shares × price)
Sell −shares +(shares × price)
Short −shares +(shares × price)
Cover +shares −(shares × price)

Example:

Date;Ticker;Type;Amount;Price
2023-01-15;AAPL;Buy;10;150.50
2023-02-20;MSFT;Buy;5;250.75
2023-03-10;AAPL;Sell;5;165.25
2023-04-05;TSLA;Buy;2;800.00
2023-06-01;NVDA;Short;4;420.00
2023-11-01;NVDA;Cover;4;460.00

Documentation

Core Methods

FinTrack.__init__(initial_cash, currency, csv_file, user_id=None)

Initialize a portfolio tracker.

Parameters:

  • initial_cash: Starting cash amount (must be non-negative)
  • currency: Base currency code (3-letter code, e.g., 'USD', 'EUR')
  • csv_file: Path to transactions CSV file
  • user_id: Optional identifier for multi-user setups (default: 'default')

Raises:

  • FileNotFoundError: If CSV file doesn't exist
  • ValidationError: If parameters are invalid

get_current_holdings() -> List[str]

Get list of current stock holdings with company names. Short positions are prefixed with "Short: ".

get_portfolio_value(from_date, to_date) -> Dict[date, float]

Get portfolio value for each day in date range.

For short positions, value = cash (including short proceeds) + (negative_shares × current_price), which equals the unrealized P&L on the short automatically.

get_portfolio_cash(date) -> Optional[float]

Get cash balance on specific date. Cash includes proceeds received from short sales.

get_portfolio_summary() -> Dict

Get comprehensive portfolio summary. Holdings include an is_short boolean field. Short positions show negative shares and value.

get_stock_returns(from_date, to_date) -> Dict[str, float]

Calculate returns for each stock held during the period, including short positions.

  • Long positions: standard return on invested capital
  • Short positions: return = (proceeds − cover cost) / cover cost
  • Mixed activity: Modified Dietz-style approach

Returns: Dictionary mapping ticker symbols to returns (e.g., 0.062 = 6.2%, −0.05 = −5%)

print_stock_returns(from_date, to_date, sort_by='return')

Print a formatted table of stock returns. Open short positions are labelled with (Short).

get_index_returns(ticker, start_date, end_date) -> List[float]

Get daily returns for a benchmark index relative to start price.

update_portfolio()

Refresh portfolio with latest data from Yahoo Finance.

Short Selling — How It Works

Opening a short (Type=Short)

  • Holdings for that ticker decrease by the shorted amount (goes negative)
  • Cash increases by shares × price (simplified — proceeds credited immediately)
  • Prices are fetched for the ticker throughout the short period

Closing a short (Type=Cover)

  • Holdings for that ticker increase by the covered amount (back toward zero)
  • Cash decreases by shares × price (cost to buy back)

Daily valuation of open shorts

Because short proceeds were already credited to cash, the portfolio value calculation is:

value = cash + Σ(shares × price)

Since shorted shares are negative, their contribution is negative — i.e., the current cost-to-cover is subtracted from cash. The net result is the unrealized P&L:

unrealized P&L = proceeds_received − current_cost_to_cover

Example: Short 10 shares of TSLA at $300 → cash +$3,000. If today's price is $260:

contribution = -10 × $260 = -$2,600
net = $3,000 (cash) - $2,600 = +$400 unrealized gain ✓

Configuration

Data is stored in user's home directory:

~/.fintrack/
├── default/                    # Default user
│   └── data/
│       └── portfolio.db       # Portfolio database
├── user123/                    # Custom user
│   └── data/
│       └── portfolio.db
└── logs/
    └── fintrack.log           # Activity log

Logging

from FinTrack import setup_logger, get_logger
import logging

logger = setup_logger("my_app", level=logging.DEBUG)
logger.info("Portfolio initialized")

Logs are written to ~/.fintrack/logs/fintrack.log by default.

Error Handling

from FinTrack import (
    FinTrackError,
    ValidationError,
    DataFetchError,
    PriceError,
    DatabaseError,
    ConfigError,
)

try:
    portfolio = FinTrack(150000, "USD", "transactions.csv")
except ValidationError as e:
    print(f"Invalid input: {e}")
except FinTrackError as e:
    print(f"FinTrack error: {e}")

Input Validation

from FinTrack import TransactionValidator

df = pd.read_csv("transactions.csv", sep=";")
is_valid, errors = TransactionValidator.validate_dataframe(df)

if not is_valid:
    for error in errors:
        print(f"  - {error}")

Validation checks:

  • ✓ Date format (YYYY-MM-DD)
  • ✓ Ticker symbols (non-empty)
  • ✓ Transaction type (Buy, Sell, Short, or Cover)
  • ✓ Amount (positive integer)
  • ✓ Price (positive number)

How It Works

Database Structure

FinTrack uses SQLite with three main tables:

  1. portfolio: Holdings per date (positive = long, negative = short)
  2. cash: Cash balance tracking (includes short sale proceeds)
  3. prices: Daily stock prices in base currency (fetched for all open positions)

Price Management

  • Prices automatically fetched from Yahoo Finance for all non-zero positions (long and short)
  • Multi-currency portfolios: prices converted to base currency
  • Forward-filling for missing trading days
  • Custom prices from CSV supported

Cash Flow Tracking

Event Cash effect
Buy stock Decreases
Sell stock Increases
Short stock Increases
Cover short Decreases
Dividend Increases (long positions only)

Stock Returns Calculation

Returns use a Modified Dietz approach treating all cash outflows (Buy, Cover) and inflows (Sell, Short) consistently, giving accurate performance metrics regardless of position type or how many times shares changed hands during the period.

Supported Currencies

portfolio = FinTrack(100000, "USD")  # US Dollar
portfolio = FinTrack(100000, "EUR")  # Euro
portfolio = FinTrack(100000, "GBP")  # British Pound
portfolio = FinTrack(100000, "JPY")  # Japanese Yen
portfolio = FinTrack(100000, "SEK")  # Swedish Krona

Requirements

  • Python >= 3.8
  • pandas >= 1.3.0
  • yfinance >= 0.2.0

Development

Running Tests

pytest tests/
pytest tests/ --cov=src/FinTrack --cov-report=html
pytest tests/test_validation.py

Code Quality

black src/
flake8 src/
mypy src/

Limitations

  • Prices fetched from Yahoo Finance — verify data quality
  • Daily resolution only (intra-day trading not supported)
  • Short selling uses a simplified cash model (proceeds credited immediately, no margin requirements)
  • Corporate actions (stock splits, mergers) must be manually adjusted
  • Past dividend data depends on Yahoo Finance records

Contributing

  1. Fork the repository
  2. Create a feature branch
  3. Add tests for new functionality
  4. Ensure all tests pass
  5. Submit a pull request

License

MIT License — see LICENSE for details.

Disclaimer

This software is provided as-is for educational and informational purposes. Always verify your portfolio calculations independently. The short selling implementation uses a simplified cash model and does not account for margin requirements, borrowing costs, or broker-specific rules. The author is not responsible for any financial losses resulting from use of this software.

Changelog

See CHANGELOG.md for detailed release notes.

Support

Version History

  • v1.2.0 (2026-02-18): Short selling support (Short/Cover transaction types, mark-to-market valuation)
  • v1.1.1 (2026-02-15): Added stock returns analysis methods and improved index returns handling
  • v1.1.0 (2026-02-14): Major refactoring with full test suite, proper error handling, logging, and pandas 2.0 compatibility
  • v1.0.0 (2026-02-13): Initial release

Built by: Aron Fredriksson
License: MIT
Last Updated: February 2026

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

fintrack-1.2.2.tar.gz (39.9 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

fintrack-1.2.2-py3-none-any.whl (25.5 kB view details)

Uploaded Python 3

File details

Details for the file fintrack-1.2.2.tar.gz.

File metadata

  • Download URL: fintrack-1.2.2.tar.gz
  • Upload date:
  • Size: 39.9 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.11.9

File hashes

Hashes for fintrack-1.2.2.tar.gz
Algorithm Hash digest
SHA256 f00c4ac874f8fd9dcd5e803408499a867cc989d8018b2123f5de7b8bd892c166
MD5 0d048f8690592501c42739858c32f1c8
BLAKE2b-256 bcec115f13470c455eea37e509ee60d913537a5eb2fb8764d2e7eb3d2a9cfccd

See more details on using hashes here.

File details

Details for the file fintrack-1.2.2-py3-none-any.whl.

File metadata

  • Download URL: fintrack-1.2.2-py3-none-any.whl
  • Upload date:
  • Size: 25.5 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.11.9

File hashes

Hashes for fintrack-1.2.2-py3-none-any.whl
Algorithm Hash digest
SHA256 04072ec6cee1a68b60c5fb57fd4a56d62a51f1d7942cc7902278d2d9443d6c98
MD5 9566b2b6c828fd6a10de30fa8fa08abd
BLAKE2b-256 4aabae428b7cba05a18dba1cc854e900d499f5d7926225cbe88142942a036d25

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page