Time-weighted return calculator
A time-weighted return measures how an investment strategy performed without letting the size or timing of external contributions and withdrawals distort the result.
Run — free
Enter portfolio values for consecutive sub-periods, place each cash flow at the start or end of its period, and the calculator computes each period return before chaining them into one cumulative figure. This makes the result useful for comparing a portfolio manager, model, or strategy across accounts whose owners added or removed different amounts of money at different times.
Split the measurement window at external cash flows
Time-weighted return begins by dividing the full measurement window into sub-periods. A boundary should occur whenever money enters or leaves the portfolio for reasons unrelated to investment performance. Contributions, deposits, withdrawals, and distributions taken out of the account are external cash flows. Interest, dividends retained inside the portfolio, fees, and market gains or losses normally remain part of performance rather than being entered as cash flows. For every sub-period, provide the market value at its beginning and end. Then enter the external cash flow as a positive number for a contribution or a negative number for a withdrawal. Select start when that amount was available for investment throughout the sub-period. Select end when it arrived after the period's performance, or when a withdrawal was taken after performance occurred. Accurate boundary valuations matter: if a flow happened halfway through a long period, create a valuation boundary at that time instead of labeling the flow approximately. The rows must be supplied in chronological order, because the calculator chains their returns in that same order.
Understand the sub-period formulas and chaining
For a cash flow placed at the end, the calculator subtracts the flow from the ending value and divides the adjusted ending value by the beginning value. For a cash flow placed at the start, it adds the flow to the beginning value and divides the ending value by that invested amount. Subtracting one from either ratio gives the sub-period return. The calculator then converts every return to a growth factor, multiplies all growth factors, and subtracts one from the product. For example, a gain followed by a loss is compounded rather than averaged: equal positive and negative percentages do not cancel because the second percentage acts on a different capital base. The response includes decimal and percentage forms of every sub-period return, the final growth factor, and the cumulative time-weighted return. Rounding is applied only to reported values after each full-precision calculation has contributed to the chain. This preserves stable results while avoiding the drift that would arise from chaining visibly rounded intermediate percentages.
Interpret the result and recognize its limits
Use the final percentage to evaluate the investment process independently of an account owner's funding decisions. It is especially appropriate for comparing managers, benchmarks, model portfolios, or the same strategy across accounts with different contribution schedules. A positive result means one unit of capital exposed through all sub-periods grew; a negative result means it declined. The growth factor expresses the same outcome multiplicatively, so a factor above one indicates growth and a factor below one indicates loss. Time-weighted return is not the investor's personal earned return when large deposits or withdrawals occurred. For that question, a money-weighted return or internal rate of return gives cash flows weights based on when and how much the investor supplied. This calculator also does not annualize the answer, infer missing valuations, estimate intra-period flow dates, or compare against a benchmark. Its accuracy depends on correctly classifying external flows and having market values at suitable boundaries. Review the listed sub-period returns to catch an implausible row before relying on the chained result in reporting or analysis.
What you can do with it
Evaluate a portfolio manager
Remove client-controlled deposits and withdrawals so the reported return reflects investment decisions rather than funding timing.
Compare model portfolios
Chain consistent sub-period returns for strategies followed by accounts with different balances and contribution schedules.
Prepare performance reports
Produce an auditable cumulative return together with the component returns used in the chain.
FAQ
What does the calculator cost?
Each API request costs $0.002. The same deterministic calculation can also run in the browser.
Should contributions be positive or negative?
Enter contributions as positive cash flows and withdrawals as negative cash flows.
When should I choose start instead of end?
Choose start when the cash flow was invested for the whole sub-period. Choose end when it occurred after that sub-period's performance.
Is time-weighted return the same as internal rate of return?
No. Time-weighted return removes the effect of external cash-flow size and timing, while internal rate of return measures the investor's money-weighted experience.
Does the result annualize performance?
No. The result is the cumulative return across the entered sub-periods. Annualization requires the elapsed time, which this input does not request.
For developers — API access
Everything on this page is available programmatically. This section is for teams who want to wire it into their own systems; everyone else can just use the tool above.
API endpoint
Prefer to automate it? One authenticated POST creates the task; the result comes back by webhook or a signed link. The same capability also runs here on the web, by email and from Telegram — and soon from our app too.
Call it from your stack
curl -X POST https://api.kit.forhosting.com/finance/time-weighted-return \
-H "Authorization: Bearer $KIT_KEY" \
-H "Content-Type: application/json" \
-d '{"periods":[{"beginning_value":10000,"ending_value":10800,"cash_flow":500,"cash_flow_timing":"end"},{"beginning_value":11300,"ending_value":11752,"cash_flow":0,"cash_flow_timing":"end"}]}'const res = await fetch("https://api.kit.forhosting.com/finance/time-weighted-return", {
method: "POST",
headers: {
"Authorization": `Bearer ${process.env.KIT_KEY}`,
"Content-Type": "application/json"
},
body: JSON.stringify({
"periods": [
{
"beginning_value": 10000,
"ending_value": 10800,
"cash_flow": 500,
"cash_flow_timing": "end"
},
{
"beginning_value": 11300,
"ending_value": 11752,
"cash_flow": 0,
"cash_flow_timing": "end"
}
]
})
});
const { task_id } = await res.json();import os, requests
res = requests.post(
"https://api.kit.forhosting.com/finance/time-weighted-return",
headers={"Authorization": f"Bearer {os.environ['KIT_KEY']}"},
json={
"periods": [
{
"beginning_value": 10000,
"ending_value": 10800,
"cash_flow": 500,
"cash_flow_timing": "end"
},
{
"beginning_value": 11300,
"ending_value": 11752,
"cash_flow": 0,
"cash_flow_timing": "end"
}
]
},
)
task_id = res.json()["task_id"]<?php
$res = file_get_contents("https://api.kit.forhosting.com/finance/time-weighted-return", false, stream_context_create([
"http" => [
"method" => "POST",
"header" => "Authorization: Bearer " . getenv("KIT_KEY") . "\r\nContent-Type: application/json",
"content" => '{"periods":[{"beginning_value":10000,"ending_value":10800,"cash_flow":500,"cash_flow_timing":"end"},{"beginning_value":11300,"ending_value":11752,"cash_flow":0,"cash_flow_timing":"end"}]}',
],
]));
$task = json_decode($res, true);body := bytes.NewBufferString(`{"periods":[{"beginning_value":10000,"ending_value":10800,"cash_flow":500,"cash_flow_timing":"end"},{"beginning_value":11300,"ending_value":11752,"cash_flow":0,"cash_flow_timing":"end"}]}`)
req, _ := http.NewRequest("POST", "https://api.kit.forhosting.com/finance/time-weighted-return", body)
req.Header.Set("Authorization", "Bearer "+os.Getenv("KIT_KEY"))
req.Header.Set("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)Example request
{
"periods": [
{
"beginning_value": 10000,
"ending_value": 10800,
"cash_flow": 500,
"cash_flow_timing": "end"
},
{
"beginning_value": 11300,
"ending_value": 11752,
"cash_flow": 0,
"cash_flow_timing": "end"
}
]
}Example response
{
"task_id": "tsk_a1b2c3d4e5f6a1b2c3d4e5f6",
"type": "finance.time_weighted_return",
"status": "queued",
"_links": {
"result": "/tasks/tsk_…/result"
}
}The API is asynchronous: the call returns a task_id immediately and the result arrives by webhook. Polling is capped at 1 req/s per task.
Pricing
Published price — no tokens, no invented credits. A failed task is never charged.
Errors
| HTTP | Code | Meaning |
|---|---|---|
401 | unauthorized | Missing or invalid API key. |
402 | insufficient_balance | Your balance doesn't cover the task price. |
404 | unknown_type | That task type doesn't exist. |
429 | rate_limited | Too many requests. Use the webhook instead of polling. |