Header menu logo Teaching

Script

Based on code from https://github.com/aexsalomao/YahooFinance/

#r "nuget: FSharp.Data, 5.0.2"

open System
open FSharp.Data

type Interval = 
    | Daily
    | Weekly
    | Monthly
    override this.ToString() = 
        match this with
        | Daily -> "1d"
        | Weekly -> "1wk"
        | Monthly -> "1mo"

type PriceObs = 
    { Symbol : string
      Date : DateTime
      Open : float
      High : float
      Low : float
      Close : float
      AdjustedClose : float
      Volume : float }

type private PriceObsCsv = CsvProvider<Sample="Date (date),Open (float),High (float),Low (float), Close (float),AdjClose (float),Volume (float)">
let private parseYahooPriceHistory symbol result = 
    PriceObsCsv.Parse(result).Rows
    |> Seq.map (fun x -> 
        { Symbol = symbol 
          Date = x.Date
          Open = x.Open
          High = x.High
          Low = x.Low
          Close = x.Close 
          AdjustedClose = x.AdjClose
          Volume = x.Volume })
    |> Seq.toArray


let private cc = System.Net.CookieContainer()
let private retryCount = 5
let private parallelSymbols = 5

type YahooFinance =
    static member PriceHistory(symbols: seq<string>,?startDate: DateTime,?endDate: DateTime,?interval: Interval) =
        let symbols = Seq.toList symbols
        let startDate = defaultArg startDate (DateTime.Now.AddYears(-1))
        let endDate = defaultArg endDate (DateTime.Now)
        let interval = defaultArg interval Interval.Monthly

        let generateYahooUrl (symbol: string) (startDate: DateTime) (endDate: DateTime) (interval: Interval) =
            let time dt = DateTimeOffset(dt).ToUnixTimeSeconds()
            $"https://query1.finance.yahoo.com/v7/finance/download/{symbol}?" +
            $"period1={time startDate}&period2={time endDate}&interval={interval}" +
            $"&events=history&includeAdjustedClose=true"
        
        let rec yahooRequest attempt symbol =
            async {
                let url = generateYahooUrl symbol startDate endDate interval
                try
                    let! result = 
                        Http.AsyncRequestString(
                            url = url, 
                            httpMethod = "GET",
                            query = ["format","csv"],
                            headers = [HttpRequestHeaders.Accept HttpContentTypes.Csv],
                            cookieContainer = cc)
                    return parseYahooPriceHistory symbol result
                with e ->
                    if attempt > 0 then
                        return! yahooRequest (attempt - 1) symbol
                    else return! failwith $"Failed to request {symbol}, Error: {e}"
            }
        let rec getSymbols (symbols: list<string>) output parallelSymbols =
            let download thisDownload =
                [| for symbol in thisDownload do 
                    yahooRequest retryCount symbol |]
                |> Async.Parallel
                |> Async.RunSynchronously
                |> Array.collect id
                |> Array.toList

            if symbols.Length > parallelSymbols then
                let thisDownload, remaining = symbols |> List.splitAt parallelSymbols
                let result = download thisDownload
                System.Threading.Thread.Sleep(1000) // Throttle 1 sec per batch of symbols
                getSymbols remaining (result @ output) parallelSymbols
            else
                let result = download symbols
                result @ output
        getSymbols symbols [] parallelSymbols                
    static member PriceHistory(symbol: string,?startDate: DateTime,?endDate: DateTime,?interval: Interval) =
        YahooFinance.PriceHistory(symbols=[symbol],?startDate=startDate,?endDate=endDate,?interval=interval)
namespace System
Multiple items
namespace FSharp

--------------------
namespace Microsoft.FSharp
Multiple items
namespace FSharp.Data

--------------------
namespace Microsoft.FSharp.Data
val this: Interval
union case Interval.Daily: Interval
union case Interval.Weekly: Interval
union case Interval.Monthly: Interval
type PriceObs = { Symbol: string Date: DateTime Open: float High: float Low: float Close: float AdjustedClose: float Volume: float }
Multiple items
val string: value: 'T -> string

--------------------
type string = String
Multiple items
[<Struct>] type DateTime = new: year: int * month: int * day: int -> unit + 16 overloads member Add: value: TimeSpan -> DateTime member AddDays: value: float -> DateTime member AddHours: value: float -> DateTime member AddMicroseconds: value: float -> DateTime member AddMilliseconds: value: float -> DateTime member AddMinutes: value: float -> DateTime member AddMonths: months: int -> DateTime member AddSeconds: value: float -> DateTime member AddTicks: value: int64 -> DateTime ...
<summary>Represents an instant in time, typically expressed as a date and time of day.</summary>

--------------------
DateTime ()
   (+0 other overloads)
DateTime(ticks: int64) : DateTime
   (+0 other overloads)
DateTime(ticks: int64, kind: DateTimeKind) : DateTime
   (+0 other overloads)
DateTime(date: DateOnly, time: TimeOnly) : DateTime
   (+0 other overloads)
DateTime(year: int, month: int, day: int) : DateTime
   (+0 other overloads)
DateTime(date: DateOnly, time: TimeOnly, kind: DateTimeKind) : DateTime
   (+0 other overloads)
DateTime(year: int, month: int, day: int, calendar: Globalization.Calendar) : DateTime
   (+0 other overloads)
DateTime(year: int, month: int, day: int, hour: int, minute: int, second: int) : DateTime
   (+0 other overloads)
DateTime(year: int, month: int, day: int, hour: int, minute: int, second: int, kind: DateTimeKind) : DateTime
   (+0 other overloads)
DateTime(year: int, month: int, day: int, hour: int, minute: int, second: int, calendar: Globalization.Calendar) : DateTime
   (+0 other overloads)
Multiple items
val float: value: 'T -> float (requires member op_Explicit)

--------------------
type float = Double

--------------------
type float<'Measure> = float
type private PriceObsCsv = CsvProvider<...>
type CsvProvider
<summary>Typed representation of a CSV file.</summary> <param name='Sample'>Location of a CSV sample file or a string containing a sample CSV document.</param> <param name='Separators'>Column delimiter(s). Defaults to <c>,</c>.</param> <param name='InferRows'>Number of rows to use for inference. Defaults to <c>1000</c>. If this is zero, all rows are used.</param> <param name='Schema'>Optional column types, in a comma separated list. Valid types are <c>int</c>, <c>int64</c>, <c>bool</c>, <c>float</c>, <c>decimal</c>, <c>date</c>, <c>datetimeoffset</c>, <c>timespan</c>, <c>guid</c>, <c>string</c>, <c>int?</c>, <c>int64?</c>, <c>bool?</c>, <c>float?</c>, <c>decimal?</c>, <c>date?</c>, <c>datetimeoffset?</c>, <c>timespan?</c>, <c>guid?</c>, <c>int option</c>, <c>int64 option</c>, <c>bool option</c>, <c>float option</c>, <c>decimal option</c>, <c>date option</c>, <c>datetimeoffset option</c>, <c>timespan option</c>, <c>guid option</c> and <c>string option</c>. You can also specify a unit and the name of the column like this: <c>Name (type&lt;unit&gt;)</c>, or you can override only the name. If you don't want to specify all the columns, you can reference the columns by name like this: <c>ColumnName=type</c>.</param> <param name='HasHeaders'>Whether the sample contains the names of the columns as its first line.</param> <param name='IgnoreErrors'>Whether to ignore rows that have the wrong number of columns or which can't be parsed using the inferred or specified schema. Otherwise an exception is thrown when these rows are encountered.</param> <param name='SkipRows'>Skips the first n rows of the CSV file.</param> <param name='AssumeMissingValues'>When set to true, the type provider will assume all columns can have missing values, even if in the provided sample all values are present. Defaults to false.</param> <param name='PreferOptionals'>When set to true, inference will prefer to use the option type instead of nullable types, <c>double.NaN</c> or <c>""</c> for missing values. Defaults to false.</param> <param name='Quote'>The quotation mark (for surrounding values containing the delimiter). Defaults to <c>"</c>.</param> <param name='MissingValues'>The set of strings recognized as missing values specified as a comma-separated string (e.g., "NA,N/A"). Defaults to <c>NaN,NA,N/A,#N/A,:,-,TBA,TBD</c>.</param> <param name='CacheRows'>Whether the rows should be caches so they can be iterated multiple times. Defaults to true. Disable for large datasets.</param> <param name='Culture'>The culture used for parsing numbers and dates. Defaults to the invariant culture.</param> <param name='Encoding'>The encoding used to read the sample. You can specify either the character set name or the codepage number. Defaults to UTF8 for files, and to ISO-8859-1 the for HTTP requests, unless <c>charset</c> is specified in the <c>Content-Type</c> response header.</param> <param name='ResolutionFolder'>A directory that is used when resolving relative file references (at design time and in hosted execution).</param> <param name='EmbeddedResource'>When specified, the type provider first attempts to load the sample from the specified resource (e.g. 'MyCompany.MyAssembly, resource_name.csv'). This is useful when exposing types generated by the type provider.</param>
val private parseYahooPriceHistory: symbol: string -> result: string -> PriceObs array
val symbol: string
val result: string
CsvProvider<...>.Parse(text: string) : CsvProvider<...>
Parses the specified CSV string
module Seq from Microsoft.FSharp.Collections
val map: mapping: ('T -> 'U) -> source: 'T seq -> 'U seq
val x: CsvProvider<...>.Row
property CsvProvider<...>.Row.Date: DateTime with get
property CsvProvider<...>.Row.Open: float with get
property CsvProvider<...>.Row.High: float with get
property CsvProvider<...>.Row.Low: float with get
property CsvProvider<...>.Row.Close: float with get
property CsvProvider<...>.Row.AdjClose: float with get
property CsvProvider<...>.Row.Volume: float with get
val toArray: source: 'T seq -> 'T array
val private cc: Net.CookieContainer
namespace System.Net
Multiple items
type CookieContainer = new: unit -> unit + 2 overloads member Add: cookie: Cookie -> unit + 3 overloads member GetAllCookies: unit -> CookieCollection member GetCookieHeader: uri: Uri -> string member GetCookies: uri: Uri -> CookieCollection member SetCookies: uri: Uri * cookieHeader: string -> unit static val DefaultCookieLengthLimit: int static val DefaultCookieLimit: int static val DefaultPerDomainCookieLimit: int member Capacity: int ...
<summary>Provides a container for a collection of <see cref="T:System.Net.CookieCollection" /> objects.</summary>

--------------------
Net.CookieContainer() : Net.CookieContainer
Net.CookieContainer(capacity: int) : Net.CookieContainer
Net.CookieContainer(capacity: int, perDomainCapacity: int, maxCookieSize: int) : Net.CookieContainer
val private retryCount: int
val private parallelSymbols: int
val symbols: string seq
Multiple items
val seq: sequence: 'T seq -> 'T seq

--------------------
type 'T seq = Collections.Generic.IEnumerable<'T>
val startDate: DateTime option
val endDate: DateTime option
val interval: Interval option
type Interval = | Daily | Weekly | Monthly override ToString: unit -> string
val symbols: string list
val toList: source: 'T seq -> 'T list
val startDate: DateTime
val defaultArg: arg: 'T option -> defaultValue: 'T -> 'T
property DateTime.Now: DateTime with get
<summary>Gets a <see cref="T:System.DateTime" /> object that is set to the current date and time on this computer, expressed as the local time.</summary>
<returns>An object whose value is the current local date and time.</returns>
DateTime.AddYears(value: int) : DateTime
val endDate: DateTime
val interval: Interval
val generateYahooUrl: symbol: string -> startDate: DateTime -> endDate: DateTime -> interval: Interval -> string
val time: dt: DateTime -> int64
val dt: DateTime
Multiple items
[<Struct>] type DateTimeOffset = new: dateTime: DateTime -> unit + 8 overloads member Add: timeSpan: TimeSpan -> DateTimeOffset member AddDays: days: float -> DateTimeOffset member AddHours: hours: float -> DateTimeOffset member AddMicroseconds: microseconds: float -> DateTimeOffset member AddMilliseconds: milliseconds: float -> DateTimeOffset member AddMinutes: minutes: float -> DateTimeOffset member AddMonths: months: int -> DateTimeOffset member AddSeconds: seconds: float -> DateTimeOffset member AddTicks: ticks: int64 -> DateTimeOffset ...
<summary>Represents a point in time, typically expressed as a date and time of day, relative to Coordinated Universal Time (UTC).</summary>

--------------------
DateTimeOffset ()
DateTimeOffset(dateTime: DateTime) : DateTimeOffset
DateTimeOffset(dateTime: DateTime, offset: TimeSpan) : DateTimeOffset
DateTimeOffset(ticks: int64, offset: TimeSpan) : DateTimeOffset
DateTimeOffset(date: DateOnly, time: TimeOnly, offset: TimeSpan) : DateTimeOffset
DateTimeOffset(year: int, month: int, day: int, hour: int, minute: int, second: int, offset: TimeSpan) : DateTimeOffset
DateTimeOffset(year: int, month: int, day: int, hour: int, minute: int, second: int, millisecond: int, offset: TimeSpan) : DateTimeOffset
DateTimeOffset(year: int, month: int, day: int, hour: int, minute: int, second: int, millisecond: int, calendar: Globalization.Calendar, offset: TimeSpan) : DateTimeOffset
DateTimeOffset(year: int, month: int, day: int, hour: int, minute: int, second: int, millisecond: int, microsecond: int, offset: TimeSpan) : DateTimeOffset
DateTimeOffset(year: int, month: int, day: int, hour: int, minute: int, second: int, millisecond: int, microsecond: int, calendar: Globalization.Calendar, offset: TimeSpan) : DateTimeOffset
val yahooRequest: attempt: int -> symbol: string -> Async<PriceObs array>
val attempt: int
val async: AsyncBuilder
val url: string
type Http = static member AsyncRequest: url: string * [<Optional>] ?query: (string * string) list * [<Optional>] ?headers: (string * string) seq * [<Optional>] ?httpMethod: string * [<Optional>] ?body: HttpRequestBody * [<Optional>] ?cookies: (string * string) seq * [<Optional>] ?cookieContainer: CookieContainer * [<Optional>] ?silentHttpErrors: bool * [<Optional>] ?silentCookieErrors: bool * [<Optional>] ?responseEncodingOverride: string * [<Optional>] ?customizeHttpRequest: (HttpWebRequest -> HttpWebRequest) * [<Optional>] ?timeout: int -> Async<HttpResponse> static member AsyncRequestStream: url: string * [<Optional>] ?query: (string * string) list * [<Optional>] ?headers: (string * string) seq * [<Optional>] ?httpMethod: string * [<Optional>] ?body: HttpRequestBody * [<Optional>] ?cookies: (string * string) seq * [<Optional>] ?cookieContainer: CookieContainer * [<Optional>] ?silentHttpErrors: bool * [<Optional>] ?silentCookieErrors: bool * [<Optional>] ?customizeHttpRequest: (HttpWebRequest -> HttpWebRequest) * [<Optional>] ?timeout: int -> Async<HttpResponseWithStream> static member AsyncRequestString: url: string * [<Optional>] ?query: (string * string) list * [<Optional>] ?headers: (string * string) seq * [<Optional>] ?httpMethod: string * [<Optional>] ?body: HttpRequestBody * [<Optional>] ?cookies: (string * string) seq * [<Optional>] ?cookieContainer: CookieContainer * [<Optional>] ?silentHttpErrors: bool * [<Optional>] ?silentCookieErrors: bool * [<Optional>] ?responseEncodingOverride: string * [<Optional>] ?customizeHttpRequest: (HttpWebRequest -> HttpWebRequest) * [<Optional>] ?timeout: int -> Async<string> static member Request: url: string * [<Optional>] ?query: (string * string) list * [<Optional>] ?headers: (string * string) seq * [<Optional>] ?httpMethod: string * [<Optional>] ?body: HttpRequestBody * [<Optional>] ?cookies: (string * string) seq * [<Optional>] ?cookieContainer: CookieContainer * [<Optional>] ?silentHttpErrors: bool * [<Optional>] ?silentCookieErrors: bool * [<Optional>] ?responseEncodingOverride: string * [<Optional>] ?customizeHttpRequest: (HttpWebRequest -> HttpWebRequest) * [<Optional>] ?timeout: int -> HttpResponse static member RequestStream: url: string * [<Optional>] ?query: (string * string) list * [<Optional>] ?headers: (string * string) seq * [<Optional>] ?httpMethod: string * [<Optional>] ?body: HttpRequestBody * [<Optional>] ?cookies: (string * string) seq * [<Optional>] ?cookieContainer: CookieContainer * [<Optional>] ?silentHttpErrors: bool * [<Optional>] ?silentCookieErrors: bool * [<Optional>] ?customizeHttpRequest: (HttpWebRequest -> HttpWebRequest) * [<Optional>] ?timeout: int -> HttpResponseWithStream static member RequestString: url: string * [<Optional>] ?query: (string * string) list * [<Optional>] ?headers: (string * string) seq * [<Optional>] ?httpMethod: string * [<Optional>] ?body: HttpRequestBody * [<Optional>] ?cookies: (string * string) seq * [<Optional>] ?cookieContainer: CookieContainer * [<Optional>] ?silentHttpErrors: bool * [<Optional>] ?silentCookieErrors: bool * [<Optional>] ?responseEncodingOverride: string * [<Optional>] ?customizeHttpRequest: (HttpWebRequest -> HttpWebRequest) * [<Optional>] ?timeout: int -> string
<summary> Utilities for working with network via HTTP. Includes methods for downloading resources with specified headers, query parameters and HTTP body </summary>
static member Http.AsyncRequestString: url: string * [<Runtime.InteropServices.Optional>] ?query: (string * string) list * [<Runtime.InteropServices.Optional>] ?headers: (string * string) seq * [<Runtime.InteropServices.Optional>] ?httpMethod: string * [<Runtime.InteropServices.Optional>] ?body: HttpRequestBody * [<Runtime.InteropServices.Optional>] ?cookies: (string * string) seq * [<Runtime.InteropServices.Optional>] ?cookieContainer: Net.CookieContainer * [<Runtime.InteropServices.Optional>] ?silentHttpErrors: bool * [<Runtime.InteropServices.Optional>] ?silentCookieErrors: bool * [<Runtime.InteropServices.Optional>] ?responseEncodingOverride: string * [<Runtime.InteropServices.Optional>] ?customizeHttpRequest: (Net.HttpWebRequest -> Net.HttpWebRequest) * [<Runtime.InteropServices.Optional>] ?timeout: int -> Async<string>
val query: Linq.QueryBuilder
module HttpRequestHeaders from FSharp.Data
<summary> Headers that can be sent in an HTTP request </summary>
val Accept: contentType: string -> string * string
<summary> Content-Types that are acceptable for the response </summary>
module HttpContentTypes from FSharp.Data
<summary> Constants for common HTTP content types </summary>
[<Literal>] val Csv: string = "text/csv"
<summary> text/csv </summary>
val e: exn
val failwith: message: string -> 'T
val getSymbols: symbols: string list -> output: PriceObs list -> parallelSymbols: int -> PriceObs list
type 'T list = List<'T>
val output: PriceObs list
val parallelSymbols: int
val download: thisDownload: string seq -> PriceObs list
val thisDownload: string seq
Multiple items
type Async = static member AsBeginEnd: computation: ('Arg -> Async<'T>) -> ('Arg * AsyncCallback * obj -> IAsyncResult) * (IAsyncResult -> 'T) * (IAsyncResult -> unit) static member AwaitEvent: event: IEvent<'Del,'T> * ?cancelAction: (unit -> unit) -> Async<'T> (requires delegate and 'Del :> Delegate) static member AwaitIAsyncResult: iar: IAsyncResult * ?millisecondsTimeout: int -> Async<bool> static member AwaitTask: task: Task<'T> -> Async<'T> + 1 overload static member AwaitWaitHandle: waitHandle: WaitHandle * ?millisecondsTimeout: int -> Async<bool> static member CancelDefaultToken: unit -> unit static member Catch: computation: Async<'T> -> Async<Choice<'T,exn>> static member Choice: computations: Async<'T option> seq -> Async<'T option> static member FromBeginEnd: beginAction: (AsyncCallback * obj -> IAsyncResult) * endAction: (IAsyncResult -> 'T) * ?cancelAction: (unit -> unit) -> Async<'T> + 3 overloads static member FromContinuations: callback: (('T -> unit) * (exn -> unit) * (OperationCanceledException -> unit) -> unit) -> Async<'T> ...

--------------------
type Async<'T>
static member Async.Parallel: computations: Async<'T> seq -> Async<'T array>
static member Async.Parallel: computations: Async<'T> seq * ?maxDegreeOfParallelism: int -> Async<'T array>
static member Async.RunSynchronously: computation: Async<'T> * ?timeout: int * ?cancellationToken: Threading.CancellationToken -> 'T
type Array = interface ICollection interface IEnumerable interface IList interface IStructuralComparable interface IStructuralEquatable interface ICloneable member Clone: unit -> obj member CopyTo: array: Array * index: int -> unit + 1 overload member GetEnumerator: unit -> IEnumerator member GetLength: dimension: int -> int ...
<summary>Provides methods for creating, manipulating, searching, and sorting arrays, thereby serving as the base class for all arrays in the common language runtime.</summary>
val collect: mapping: ('T -> 'U array) -> array: 'T array -> 'U array
val id: x: 'T -> 'T
val toList: array: 'T array -> 'T list
property List.Length: int with get
val thisDownload: string list
val remaining: string list
Multiple items
module List from Microsoft.FSharp.Collections

--------------------
type List<'T> = | op_Nil | op_ColonColon of Head: 'T * Tail: 'T list interface IReadOnlyList<'T> interface IReadOnlyCollection<'T> interface IEnumerable interface IEnumerable<'T> member GetReverseIndex: rank: int * offset: int -> int member GetSlice: startIndex: int option * endIndex: int option -> 'T list static member Cons: head: 'T * tail: 'T list -> 'T list member Head: 'T member IsEmpty: bool member Item: index: int -> 'T with get ...
val splitAt: index: int -> list: 'T list -> 'T list * 'T list
val result: PriceObs list
namespace System.Threading
Multiple items
type Thread = inherit CriticalFinalizerObject new: start: ParameterizedThreadStart -> unit + 3 overloads member Abort: unit -> unit + 1 overload member DisableComObjectEagerCleanup: unit -> unit member GetApartmentState: unit -> ApartmentState member GetCompressedStack: unit -> CompressedStack member GetHashCode: unit -> int member Interrupt: unit -> unit member Join: unit -> unit + 2 overloads member Resume: unit -> unit ...
<summary>Creates and controls a thread, sets its priority, and gets its status.</summary>

--------------------
Threading.Thread(start: Threading.ParameterizedThreadStart) : Threading.Thread
Threading.Thread(start: Threading.ThreadStart) : Threading.Thread
Threading.Thread(start: Threading.ParameterizedThreadStart, maxStackSize: int) : Threading.Thread
Threading.Thread(start: Threading.ThreadStart, maxStackSize: int) : Threading.Thread
Threading.Thread.Sleep(timeout: TimeSpan) : unit
Threading.Thread.Sleep(millisecondsTimeout: int) : unit
type YahooFinance = static member PriceHistory: symbol: string * ?startDate: DateTime * ?endDate: DateTime * ?interval: Interval -> PriceObs list + 1 overload
static member YahooFinance.PriceHistory: symbols: string seq * ?startDate: DateTime * ?endDate: DateTime * ?interval: Interval -> PriceObs list
static member YahooFinance.PriceHistory: symbol: string * ?startDate: DateTime * ?endDate: DateTime * ?interval: Interval -> PriceObs list

Type something to start searching.