Any information generated by this software (its algorithms) does not constitute an individual investment recommendation. You make financial decisions on your own and at your own risk.
This software is provided "as is", without warranty of any kind, express or implied, including but not limited to the warranties of merchantability, fitness for a particular purpose, and noninfringement. In no event shall the authors or copyright holders be liable for any claim, damages, or other liability arising from, out of, or in connection with this software or its use.
This code was created with the help of an LLM (Large Language Model) assistant.
A system for analyzing the prices of financial assets: for each instrument, the system determines which of three price ranges its current price falls into, and generates a PDF report containing a summary table and charts. The configuration file (Config) lists the tickers and descriptions of assets traded on the Moscow Exchange.
The code can be used to analyze any financial asset by changing the ticker and its description. The txt folder must contain the data downloaded for that asset.
The "Three Prices" logic is simple:
-
Closing-price history is taken for the analysis period — from January 1 of the year that began
YEARSyears ago to the present day. -
Over this history the price minimum and maximum are computed; the
[min … max]range is split into three equal parts by the levelsmin + ⅓·rangeandmin + ⅔·range. -
The current price (the latest available date) is classified:
Condition Label Color in the table price ≤ ⅓ of the range низкая("low")green price ≤ ⅔ of the range средняя("medium")black price above ⅔ of the range высокая("high")red If the data contains no valid price at all, a
—label is set in gray.
The idea is a rough but vivid estimate of how "expensive" an instrument looks relative to its own multi-year history, with no reference to fundamentals.
Три цены/
├── main.py # entry point: orchestration only
├── config.py # all configurable parameters and the asset list
├── log_setup.py # logging setup and the startup banner
├── data_loader.py # reads quotes from txt/ → {ticker: DataFrame}
├── analysis.py # "three prices" levels and color labels (pure functions)
├── report.py # PDF generation: title page, table, charts
├── txt/ # INPUT data — quote files (required before the first run)
├── logs/ # run logs (created automatically)
└── reports/ # ready PDF reports (created automatically)
The project folder itself is named
Три цены("Three Prices" in Russian).
-
Python 3.10+ (uses the
str | None,dict[str, ...]syntax). -
Libraries:
pandas,matplotlib:pip install pandas matplotlib
Run from the project folder:
python3 main.py What happens on start:
- Run parameters are computed (dates, file names) — see
config.py. - Logging is configured (file + console) and the startup banner is printed.
- Quotes are read from the
txt/folder and filtered by the start date. - Levels and range labels are computed for each ticker.
- The PDF is written to
reports/; thelogs/andreports/folders are created automatically.
Return codes: 0 — the report was built; 1 — no ticker survived the
filtering (the log receives [ERROR] Нет данных для сохранения в PDF… —
"No data to save to PDF").
Console output example (the program prints its log messages in Russian; they are shown here as-is):
[INFO] Успешно прочитан файл: SBER-d-5y.txt (354 строк начиная с 2019-01-01)
[ERROR] В файле T-d-5y.txt не найдены колонки даты или цены. Доступные: ['foo', 'bar']
[INFO] Проанализировано: 3 тикеров
================================================
[2026-10-18 14:33:36] 🚀 Запуск системы "Три цены" 3price.
================================================
[2026-10-18 14:33:36] ✅ Готово: 2026-10-18-3price-7.pdf — сохранён в папке reports
(Log lines go to both the file and the console; the order in the example may differ due to stdout buffering when the output is redirected.)
Sequence of steps: build_run_params() → setup_logging() → write_banner()
→ load_data() → build_price_maps() → early exit with code 1 if the data is
empty → generate_pdf() → the final "Done" line written to the log and console.
The module is safe to import — no code runs on import main.
All project constants and the ASSETS_CONFIG asset dictionary. The
build_run_params() function assembles RunParams (a named set of run
parameters: years, today_str, pdf_filename, log_filename, start_date)
and has no other side effects. The period start date is computed as
January 1 (current year − YEARS).
setup_logging(logs_folder, log_filename)— creates the logs folder, sets up simultaneous output to a file (UTF-8) and the console in the[LEVEL] messageformat, and returns the path to the log file.write_banner(log_path)— the startup banner "🚀 Запуск системы "Три цены"" ("Three Prices system starting"), written to the log without the standard logging prefix.
load_data(txt_folder, assets_config, start_date)— the main function; returns{ticker: DataFrame}with thebegin(date) andclose(closing price) columns._extract_ticker(file_name)— the ticker from the file name: everything before the-d-5y.txtor-all-d.txtsuffix (e.g.SBER-d-5y.txt→SBER)._parse_price_file(file_path)— reads a single file:- the separator is detected automatically (
sep=None, engine='python'), so TAB, comma, and semicolon all work; - column names are lowercased and angle brackets are stripped
(
<DATE>→date); - the date column is looked up among the candidates
begin,date,timestamp,time; the price column — amongclose,last,price; - dates are parsed with
pd.to_datetime, prices withpd.to_numeric; rows with NaN are dropped, the data is sorted by date, prices are rounded to 2 decimal places.
- the separator is detected automatically (
Rules for handling problem files (the system never crashes on a single file):
| Situation | Reaction |
|---|---|
The ticker is not in ASSETS_CONFIG |
the file is silently skipped |
| Date/price columns not found | [ERROR] … не найдены колонки даты или цены. Доступные: [...] ("date or price columns not found. Available: [...]") |
| Other read/parse error | [ERROR] Ошибка обработки <файл>: <текст> ("error processing : ") |
| Empty after the date filter | [WARNING] Файл … не содержит данных после … ("file … contains no data after …") |
If both files exist for the same ticker (*-d-5y.txt and *-all-d.txt), both
will be read, and the last one read wins — i.e. the *-all-d.txt file
(the patterns are iterated in that order). Keep only one file per ticker in
txt/ so you don't have to guess which data made it into the report.
Pure functions without I/O, easy to cover with tests:
compute_levels(df)→(min, min+⅓·range, min+⅔·range, max)— the only levels formula in the project; it is used both by the labels and by the dashed chart lines.classify_price(current_price, levels)→(label, color)— the rule from the table above; the ⅓ and ⅔ boundaries belong to the lower/middle range.build_price_maps(data_dict)→(price_level_map, price_color_map)— labels and colors for all tickers at once.
generate_pdf(pdf_path, ...)— the public entry point: a title page, then one page per ticker strictly inASSETS_CONFIGorder._render_title_page(...)— a header with the date and period, the tableТикер / Наименование / Текущая цена / Диапазон цены("Ticker / Name / Current price / Price range"); the text in the last column is colored green/black/red/gray according to the analysis results._render_ticker_chart(...)— the price chart as a colored line; themin, ⅓, ⅔, maxlevels are shown as an orange dashed line (--), and the same values are highlighted in orange on the Y axis.plural_years(n)— Russian declension of "год/года/лет" ("year/years") with the 11–14 exceptions (one function for the whole project — used on the title page and in chart titles).assign_chart_colors(...)— line colors are handed to tickers round-robin fromBASE_COLORSinASSETS_CONFIGorder.
All parameters are collected in config.py (plus two presentation
constants in report.py). These are the only things you need to change.
| Parameter | Default | What it controls |
|---|---|---|
YEARS |
5 |
Analysis depth in years: the period "from January 1 (current year − YEARS)". Affects data filtering, the title-page subtitle, chart titles, and the PDF name (…-3price-N.pdf). |
TXT_FOLDER |
'txt' |
Folder with the input quote files. |
LOGS_FOLDER |
'logs' |
Folder for logs (created automatically). |
REPORTS_FOLDER |
'reports' |
Folder for PDF reports (created automatically). |
ASSETS_CONFIG |
19 tickers | The main asset list — see below. |
A dictionary of the form:
'SBER': {'name': 'Акции Сбербанка', 'currency': 'RUB'},
'NG': {'name': 'Фьючерсы на газ', 'currency': 'USD'},- The key is the ticker, which must exactly match the file-name prefix in
txt/(case matters:Si≠SI). name— a human-readable name for the table and chart titles.currency— the Y-axis label of the chart (Цена (RUB)/Цена (USD)— "Price (RUB/USD)"). Currency conversion is not performed: theNG,BR,GD,SV,PTfutures are shown in dollars, as they appear in the data.
How to add an asset: add a line to ASSETS_CONFIG and put a
TICKER-d-5y.txt or TICKER-all-d.txt file into txt/. To remove an asset
from the report — delete its line from the dictionary (the file in txt/ is
then simply ignored).
The order of the dictionary keys is the order of the table rows on the title page and the order of the chart pages.
| Parameter | What it controls |
|---|---|
LEVEL_COLOR = '#ff7f0e' |
The orange color of the dashed levels and of the level labels on the Y axis. |
BASE_COLORS = [...] |
A palette of 10 line colors; with more tickers the colors repeat cyclically. |
start_date— January 1 of the yearYEARSyears ago;- the PDF name —
YYYY-MM-DD-3price-N.pdf, by the run date; - the log name —
YYYY-MM-3price.log, by the run month; - the range-label colors (green/black/red/gray) — hardcoded in
analysis.classify_priceand_render_title_page.
Files are copied into the txt/ folder with the names TICKER-d-5y.txt or
TICKER-all-d.txt. The format is a text CSV with any separator; two columns
are required — the date and the closing price (from the candidates listed in
the data_loader.py description). A typical terminal export file:
<DATE> <CLOSE>
2020-01-06 100.00
2020-01-13 101.50
...
Content requirements:
- dates — in any format understood by
pandas.to_datetime(2020-01-06,06.01.2020,20200106, etc.); - prices — numbers; rows with empty/non-numeric values are dropped;
- enough data from
start_date— everything earlier is filtered out, but if not a single row survives the filter, the ticker will not appear in the report (a[WARNING]is logged); - data older than
start_datein the file does no harm — it is cut off at load time.
Ticker data files can be downloaded using the download_moex program. Then
copy them into the txt folder of this project.
- Page 1 — the title: a header with the date, the subtitle "Analysis for N years (from …)", and the table of all tickers with the current price and the colored range label.
- Then one page per ticker (in
ASSETS_CONFIGorder): the price chart for the period with orange dashedmin / ⅓ / ⅔ / maxlevels.
The full journal of runs for the month: startup banners, file-reading lines (how many rows were read and from which date), errors and warnings, and the final "✅ Готово" ("Done") line. The log is appended on every run.
| Symptom | Cause and what to do |
|---|---|
[ERROR] Нет данных для сохранения в PDF ("No data to save to PDF", return code 1) |
There are no files in txt/ with tickers from ASSETS_CONFIG, or the folders are not being created — check that you run from the project folder and that the file names match the tickers. |
…не найдены колонки даты или цены. Доступные: ['foo', 'bar'] ("date or price columns not found") |
The file has no column with an expected name — rename the column (<DATE>/<CLOSE>, etc.) or add your own candidate to the _parse_price_file list in data_loader.py. |
Файл … не содержит данных после ГГГГ-ММ-ДД ("file … contains no data after YYYY-MM-DD") |
The file has no quotes for the analysis period — download a longer history. |
The ticker is in txt/ but did not appear in the report |
The ticker was not added to ASSETS_CONFIG, or the name/case does not match (Si ≠ SI), or the ticker has a competing *-all-d.txt file that overrode the data. |
ModuleNotFoundError: pandas |
Run with the wrong interpreter: the libraries are installed into a specific Python (on the author's machine — /usr/local/bin/python3.12). |

