A Python package for tracking and analyzing stock portfolios with multi-currency support, automatic dividend tracking, and short selling.
Tests, instructions and docstrings written using Claude, I tried to find any incorrect information but some may have slipped through the crack.
- 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
Install the package using pip:
pip install FinTrackFor development:
git clone https://github.com/arofredriksson/FinTrack.git
cd FinTrack
pip install -e ".[dev]"
pytest tests/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.00from 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']}")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.00Initialize 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 list of current stock holdings with company names. Short positions are prefixed with "Short: ".
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 cash balance on specific date. Cash includes proceeds received from short sales.
Get comprehensive portfolio summary. Holdings include an is_short boolean field. Short positions show negative shares and value.
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 a formatted table of stock returns. Open short positions are labelled with (Short).
Get daily returns for a benchmark index relative to start price.
Refresh portfolio with latest data from Yahoo Finance.
- 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
- Holdings for that ticker increase by the covered amount (back toward zero)
- Cash decreases by
shares × price(cost to buy back)
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 ✓
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
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.
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}")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)
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)
- 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
| Event | Cash effect |
|---|---|
| Buy stock | Decreases |
| Sell stock | Increases |
| Short stock | Increases |
| Cover short | Decreases |
| Dividend | Increases (long positions only) |
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.
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- Python >= 3.8
- pandas >= 1.3.0
- yfinance >= 0.2.0
pytest tests/
pytest tests/ --cov=src/FinTrack --cov-report=html
pytest tests/test_validation.pyblack src/
flake8 src/
mypy src/- 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
- Fork the repository
- Create a feature branch
- Add tests for new functionality
- Ensure all tests pass
- Submit a pull request
MIT License — see LICENSE for details.
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.
See CHANGELOG.md for detailed release notes.
- GitHub Issues
- Email: arofre903@gmail.com
- 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