A robust portfolio tracker with multi-currency support and short selling
Project description
FinTrack - Advanced Portfolio Tracker
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 fileuser_id: Optional identifier for multi-user setups (default: 'default')
Raises:
FileNotFoundError: If CSV file doesn't existValidationError: 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:
- portfolio: Holdings per date (positive = long, negative = short)
- cash: Cash balance tracking (includes short sale proceeds)
- 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
- Fork the repository
- Create a feature branch
- Add tests for new functionality
- Ensure all tests pass
- 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
- GitHub Issues
- Email: arofre903@gmail.com
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
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file fintrack-1.2.1.tar.gz.
File metadata
- Download URL: fintrack-1.2.1.tar.gz
- Upload date:
- Size: 39.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.11.9
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
948841a7229e059fa71effab555d16ff4b3547abe1b1e54fcd8a9429438ca21c
|
|
| MD5 |
4c696494f4b299cf6e2b6980c8c353a7
|
|
| BLAKE2b-256 |
6eaf7793769b2875bc87d6da9b67c9b3d50609ed8ef689e21a738c8523871b9d
|
File details
Details for the file fintrack-1.2.1-py3-none-any.whl.
File metadata
- Download URL: fintrack-1.2.1-py3-none-any.whl
- Upload date:
- Size: 25.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.11.9
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
151c56e49df61db298c82ad7b6db00a1e216d166facfe972fe7ee5fd5335cf03
|
|
| MD5 |
a6969b26be515efeef135f20b0c9efe7
|
|
| BLAKE2b-256 |
ebf61b543679365be77adff7851045bfd8344c601560222508ecf646e2b37f9a
|