Владимиров Владимир
Владимиров Владимир личный блог
03 ноября 2025, 17:50

Выгрузка исторических данных

  Сегодня обсуждалась тема о том, что использование ряда зарубежных сайтов  для скачивания исторических данных стало недоступно. Добрый человек подсказал решение проблемы на питоне с использованием yfinance. При помощи DeepSeak создал программу для выгрузки данных. Возможно кому-то еще будет полезно.
   Для использования программы надо создать в текущей папке с кодом две папки: InData и OutData. В первой папке создайте текстовый файл со списком тикеров (активов), разделенных точкой с запятой (;) с расширением txt. Во второй папке будет сохранен файл «текущая дата.txt» с данными разделителем точка с запятой. Данные по всем заданным тикерам записываются в один файл последовательно. Формат вывода:
                              Тикер / Data  Open  High  Low  Close Volume /  данные по дням за период
   После запуска — выбрать период дат, выбрать файл с исходным списком активов, запустить скачивание данных.
     Сам код следующий:

import tkinter as tk
from tkinter import ttk, messagebox, filedialog
import yfinance as yf
import pandas as pd
import numpy as np
import os
from datetime import datetime

class YFinanceDownloader:
def __init__(self, root):
self.root = root
self.root.title(«YFinance Data Downloader»)
self.root.geometry(«600x400»)

self.create_widgets()

def create_widgets(self):
# Заголовок
title_label = ttk.Label(self.root, text=«Выгрузка данных торгов», font=(«Arial», 14, «bold»))
title_label.pack(pady=10)

# Фрейм для ввода дат
date_frame = ttk.Frame(self.root)
date_frame.pack(pady=10, padx=20, fill=«x»)

ttk.Label(date_frame, text=«Период дат (ДД.ММ.ГГГГ — ДД.ММ.ГГГГ):»).pack(anchor=«w»)

self.date_entry = ttk.Entry(date_frame, width=30)
self.date_entry.pack(pady=5, fill=«x»)
self.date_entry.insert(0, «01.10.2025 — 31.10.2025»)

# Фрейм для выбора файла
file_frame = ttk.Frame(self.root)
file_frame.pack(pady=10, padx=20, fill=«x»)

ttk.Label(file_frame, text=«Файл с тикерами:»).pack(anchor=«w»)

file_select_frame = ttk.Frame(file_frame)
file_select_frame.pack(pady=5, fill=«x»)

self.file_path = tk.StringVar()
self.file_entry = ttk.Entry(file_select_frame, textvariable=self.file_path, width=40)
self.file_entry.pack(side=«left», fill=«x», expand=True)

ttk.Button(file_select_frame, text=«Обзор», command=self.browse_file).pack(side=«left», padx=5)

# Кнопка выполнения
ttk.Button(self.root, text=«Выгрузить данные», command=self.download_data).pack(pady=20)

# Прогресс бар
self.progress = ttk.Progressbar(self.root, mode='indeterminate')
self.progress.pack(pady=10, padx=20, fill=«x»)

# Текстовое поле для логов
self.log_text = tk.Text(self.root, height=10, width=70)
self.log_text.pack(pady=10, padx=20, fill=«both», expand=True)

# Добавляем скроллбар для текстового поля
scrollbar = ttk.Scrollbar(self.log_text)
scrollbar.pack(side=«right», fill=«y»)
self.log_text.config(yscrollcommand=scrollbar.set)
scrollbar.config(command=self.log_text.yview)

def browse_file(self):
filename = filedialog.askopenfilename(
initialdir=«InData»,
title=«Выберите файл с тикерами»,
filetypes=((«Text files», "*.txt"), («All files», "*.*"))
)
if filename:
self.file_path.set(filename)

def log_message(self, message):
self.log_text.insert(«end», f"{datetime.now().strftime('%H:%M:%S')} — {message}\n")
self.log_text.see(«end»)
self.root.update()

def parse_dates(self, date_range):
""«Парсинг дат из формата ДД.ММ.ГГГГ»""
try:
start_str, end_str = date_range.split("-")
start_date = datetime.strptime(start_str.strip(), "%d.%m.%Y").strftime("%Y-%m-%d")
end_date = datetime.strptime(end_str.strip(), "%d.%m.%Y").strftime("%Y-%m-%d")
return start_date, end_date
except ValueError as e:
raise ValueError(«Неверный формат даты. Используйте: ДД.ММ.ГГГГ — ДД.ММ.ГГГГ»)

def read_tickers(self, filepath):
""«Чтение тикеров из файла»""
try:
with open(filepath, 'r', encoding='utf-8') as file:
content = file.read().strip()
tickers = [ticker.strip() for ticker in content.split(';') if ticker.strip()]
return tickers
except Exception as e:
raise Exception(f«Ошибка чтения файла: {e}»)

def download_data(self):
try:
self.progress.start()
self.log_text.delete(1.0, «end»)

# Проверка введенных данных
if not self.file_path.get():
messagebox.showerror(«Ошибка», «Выберите файл с тикерами»)
return

if not self.date_entry.get():
messagebox.showerror(«Ошибка», «Введите период дат»)
return

# Парсинг дат
self.log_message(«Парсинг дат...»)
start_date, end_date = self.parse_dates(self.date_entry.get())
self.log_message(f«Период: {start_date} — {end_date}»)

# Чтение тикеров
self.log_message(«Чтение тикеров из файла...»)
tickers = self.read_tickers(self.file_path.get())
self.log_message(f«Найдено тикеров: {len(tickers)}»)

if not tickers:
messagebox.showerror(«Ошибка», «Файл не содержит тикеров»)
return

# Создание папки OutData если её нет
if not os.path.exists(«OutData»):
os.makedirs(«OutData»)
self.log_message(«Создана папка OutData»)

# Создание имени файла с текущей датой
current_date = datetime.now().strftime("%d-%m-%Y")
output_filename = f«OutData/{current_date}.txt»

# Открываем файл для записи
with open(output_filename, 'w', encoding='utf-8') as f:
total_tickers_saved = 0

for ticker in tickers:
try:
self.log_message(f«Обработка тикера {ticker}...»)

# Загружаем данные для текущего тикера
data = yf.download(ticker, start=start_date, end=end_date, progress=False)

if data.empty:
self.log_message(f«Предупреждение: нет данных для {ticker}»)
continue

self.log_message(f«Загружено {len(data)} строк для {ticker}»)

# Записываем заголовок тикера
f.write(f«Тикер {ticker}\n»)

# Записываем названия колонок
f.write(«Date;Open;High;Low;Close;Volume\n»)

# Подготовка и запись данных
rows_written = 0
for index, row in data.iterrows():
try:
# Преобразуем дату в формат ДД.ММ.ГГГГ
date_str = index.strftime('%d.%m.%Y')

# Получаем значения как числа
open_price = float(row['Open'])
high_price = float(row['High'])
low_price = float(row['Low'])
close_price = float(row['Close'])
volume = int(row['Volume'])

# Форматируем строку данных
data_line = f"{date_str};{open_price:.2f};{high_price:.2f};{low_price:.2f};{close_price:.2f};{volume}\n"
f.write(data_line)
rows_written += 1

except Exception as row_error:
continue

# Добавляем пустую строку между тикерами (кроме последнего)
if ticker != tickers[-1]:
f.write("\n")

total_tickers_saved += 1
self.log_message(f«Записано {rows_written} строк для {ticker}»)

except Exception as e:
self.log_message(f«Ошибка при обработке {ticker}: {str(e)}»)

self.progress.stop()

# Проверяем содержимое файла
if os.path.exists(output_filename):
with open(output_filename, 'r', encoding='utf-8') as check_file:
content = check_file.read()
file_size = len(content)

self.log_message(f«Файл создан: {output_filename}»)
self.log_message(f«Размер файла: {file_size} байт»)

if file_size > 0:
messagebox.showinfo(«Успех», f«Данные успешно выгружены для {total_tickers_saved} из {len(tickers)} тикеров\nФайл: {output_filename}»)
else:
messagebox.showwarning(«Предупреждение», f«Файл создан, но пустой: {output_filename}»)
else:
messagebox.showerror(«Ошибка», f«Файл не был создан: {output_filename}»)

except Exception as e:
self.progress.stop()
self.log_message(f«Ошибка: {str(e)}»)
messagebox.showerror(«Ошибка», str(e))

def main():
# Создаем папки если их нет
if not os.path.exists(«InData»):
os.makedirs(«InData»)
print(«Создана папка InData — поместите туда файлы с тикерами»)

if not os.path.exists(«OutData»):
os.makedirs(«OutData»)
print(«Создана папка OutData — туда будут сохраняться результаты»)

root = tk.Tk()
app = YFinanceDownloader(root)
root.mainloop()

if __name__ == "__main__":
main()




Данная публикация является личным мнением автора. Мнение владельца сайта может не совпадать с мнением автора.

16 Комментариев
  • Serg_Mich
    16 апреля 2026, 10:09
    Меня зациклило на одной задаче, не могу подобрать длину окна истории, которое требуется для обучения модели. Перепробовал много вариантов, но ни один не работает точно. Ищу как эту задачу решают другие, но по теме почти пусто, даже в книгах… Вот думаю, почему бы не спросить у более подкованных в математике и физике, например у тебя :) Наверняка же приходилось сталкиваться с похожей задачей, как считаешь, есть ли хорошее аналитическое решение у сей проблемы, может какую-то теорию сюда можно притянуть? 
  • Serg_Mich
    16 апреля 2026, 12:09

    В правильно заданом вопросе есть уже половина ответа и это более-менее понятно:) Дело тут даже не в оценке, ведь чтобы ее получить — надо:

    a) сперва что-то с чем-то сравнить. И это, на мой взгляд,  больше похоже на эмпирический метод.

    b) потратить время на перебор вариантов. 

    Это всё допустимо, но наверное можно попытаться вычислить шаг до получения этой оценки — как-то аналитически и «заранее» посчитать длину этой ретроспективы опираясь на характеристики самих данных 

     

  • Serg_Mich
    16 апреля 2026, 18:47

    Можно и паттернами мерять, можно твоими величинами, думаю принцип должен быть похожим. Если паттернами, тогда, что если на некой глубине ретроспективы встречается N-повторений одного паттерна, допустим 20 или 120 и статистика вроде как набрана. Вот тут, чтобы не получить переобучение или наоборот шум на выходе, как понять, а в идеале вычислить заранее и без перебора, этих N уже хватит или еще недостаточно? (если отталкиваться от того, что глубина задается N-повторениями одного паттерна, но мне эта идея не очень). Но в целом твоя идея понятна, она +- похожа на мою. Ладно, буду думать, это единственно нерешенная задача осталась в этой чертовщине :)

  • Serg_Mich
    17 апреля 2026, 09:09

    «следует иметь статистику иное сочетание параметров -> такое же движение цены» — а это уже другой паттерн и задача свелась к ранжированию разных паттернов на одной выборке. Если допустим ты предполагаешь что к некому паттерну можно добавить скрытый параметр, вроде показания объема или индикатора, то по сути получаем новый патерн, с теми же проблемами статистики недобора или перебора и вопрос с длиной ретроспективы так же остатся открытым.

    В твоем методе проще только потому, что модель не учитывает динамику паттерна, а только лишь форму патерна (взаимосвязи величин), попробуй добавь поведение и все тут же поменяется, столкнешься с точно такой же проблемой. 

  • Serg_Mich
    17 апреля 2026, 11:10
    Дело в том, что когда выборка разбивается на паттерны то не остается никакого «любого иного сочетания параметров» которое бы не сожержалось в паттернах на данной выборке. Предположим что разнообразие паттернов покрывает всю выборку, больше нет места для маневров. И понятное дело что у движения есть градиенты силы и тд, пример упрощенный был, для демонстрации. Или ты иммеешь ввиду разбивку на тренировочные и тестовые данные? Но и тут все равно вопрос об оптимальной глубине тренировочных данных никуда не делся.
  • Serg_Mich
    20 апреля 2026, 14:21
    Предыдущее сообщение удалил, чтобы не «палить поляну» раньше времени, там была зацепка:) В общем получилось. Пока не самым оптимальным способом и не «на лету», зато точно. Метод «плато» называется. Я им и раньше пользовался, но в других оценках. А тут подумал, почему бы не применить и для оценки ретроспективы...
    Метод заключается в анализе сходимости функции ошибки при вариации длины ретроспективы. Проводятся итерационные замеры на скользящих окнах с возрастающим шагом. Оптимальным значением n_bars считается начало устойчивого плато, где производная функции ошибки по длине окна стремится к нулю. Это точка, после которой включение дополнительных исторических данных уже не приносит новой информации (информационное насыщение), а лишь увеличивает вычислительную нагрузку или размывает актуальные закономерности.
  • Serg_Mich
    21 апреля 2026, 11:06

    В твоей модели глубина n_bars, вероятно, — константа, подобранная как некая универсальная величина. Это популярный метод, и он подходит большинству стратегий, но универсальность удобна до поры до времени. Ты сам подтверждаешь, что модель не умеет отрабатывать резкую смену волатильности — и это прямое следствие неоптимальной ретроспективы. Из-за фиксированного окна модели банально не хватает данных для адаптации к аномалиям и попытка компенсировать это системой из трех окон выглядит скорее как тактический обход проблемы, чем ее фундаментальное решение.

    «при таком определении оптимальной глубины ретроспективы есть риск получить подгонку модели под текущий-прошедший характер движения цены» 

    Ну, подгонкой это назвать можно, но только в самом широком смысле.
    Это ближе к структурной калибровке, адаптации, а не к оптимизации под доходность. Не ищется n_back, который максимизирует прибыль или точность на тестовой выборке. Ищется минимальное окно, при котором модель стабильно идентифицирует свою же внутреннюю структуру. Это ближе к вопросу «сколько данных нужно модели, чтобы работать корректно», чем к «на каких данных модель давала лучшие сигналы».
    Пока ничего лучшего не нашел, приходится «мутить Франкенштейна»

Активные форумы
Что сейчас обсуждают

Старый дизайн
Старый
дизайн