├── .gitignore ├── README.md ├── SecretFormula.xlsx ├── StockEstimator.Charts ├── App.config ├── AssemblyInfo.fs ├── Charts.fsx ├── ChartsDrawing.fs ├── Program.fs ├── StockEstimator.Charts.fsproj ├── StockEstimator.Charts.v2.ncrunchproject └── packages.config ├── StockEstimator.ConsoleApp ├── App.config ├── Program.cs ├── Properties │ └── AssemblyInfo.cs ├── StockEstimator.ConsoleApp.csproj └── StockEstimator.ConsoleApp.v2.ncrunchproject ├── StockEstimator.Logic ├── App.config ├── AssemblyInfo.fs ├── StockData.fs ├── StockEstimator.Logic.fsproj ├── StockEstimator.Logic.v2.ncrunchproject └── packages.config ├── StockEstimator.Tests ├── App.config ├── AssemblyInfo.fs ├── StockDataTests.fs ├── StockEstimator.Tests.fsproj └── packages.config ├── StockEstimator.WebApi.Docker ├── App.fsx ├── Dockerfile ├── StockData.fsx ├── index.html ├── paket.bootstrapper.exe ├── paket.dependencies └── paket.exe ├── StockEstimator.WebApi ├── App.config ├── App.fs ├── AssemblyInfo.fs ├── StockEstimator.WebApi.fsproj ├── StockEstimator.WebApi.v2.ncrunchproject ├── index.html └── packages.config ├── StockEstimator.WebUI ├── Dockerfile ├── Program.cs ├── Startup.cs ├── StockEstimator.WebUI.csproj ├── app.yaml ├── gcloud-deploy.sh └── wwwroot │ ├── .editorconfig │ ├── .vscode │ └── settings.json │ ├── aurelia_project │ ├── aurelia.json │ ├── environments │ │ ├── dev.ts │ │ ├── prod.ts │ │ └── stage.ts │ ├── generators │ │ ├── attribute.json │ │ ├── attribute.ts │ │ ├── binding-behavior.json │ │ ├── binding-behavior.ts │ │ ├── component.json │ │ ├── component.ts │ │ ├── element.json │ │ ├── element.ts │ │ ├── generator.json │ │ ├── generator.ts │ │ ├── task.json │ │ ├── task.ts │ │ ├── value-converter.json │ │ └── value-converter.ts │ └── tasks │ │ ├── build.json │ │ ├── build.ts │ │ ├── copy-files.ts │ │ ├── process-css.ts │ │ ├── process-markup.ts │ │ ├── run.json │ │ ├── run.ts │ │ ├── test.json │ │ ├── test.ts │ │ └── transpile.ts │ ├── favicon.ico │ ├── images │ ├── aurelia.ico │ ├── aurelia.svg │ └── stock.ico │ ├── index.html │ ├── karma.conf.js │ ├── package.json │ ├── src │ ├── about.html │ ├── about.ts │ ├── aml.html │ ├── aml.ts │ ├── app.html │ ├── app.ts │ ├── environment.ts │ ├── home.html │ ├── home.ts │ ├── main.ts │ ├── nav-bar.html │ └── resources │ │ └── index.ts │ ├── styles │ └── styles.css │ ├── test │ ├── aurelia-karma.js │ └── unit │ │ ├── app.spec.ts │ │ └── setup.ts │ ├── tsconfig.json │ └── tslint.json ├── StockEstimator.sln └── StockEstimator.v2.ncrunchsolution /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by https://www.gitignore.io/api/visualstudio,f# 2 | 3 | ### VisualStudio ### 4 | ## Ignore Visual Studio temporary files, build results, and 5 | ## files generated by popular Visual Studio add-ons. 6 | 7 | # User-specific files 8 | *.suo 9 | *.user 10 | *.userosscache 11 | *.sln.docstates 12 | 13 | # User-specific files (MonoDevelop/Xamarin Studio) 14 | *.userprefs 15 | 16 | # Build results 17 | [Dd]ebug/ 18 | [Dd]ebugPublic/ 19 | [Rr]elease/ 20 | [Rr]eleases/ 21 | x64/ 22 | x86/ 23 | bld/ 24 | [Bb]in/ 25 | [Oo]bj/ 26 | 27 | # Visual Studio 2015 cache/options directory 28 | .vs/ 29 | # Uncomment if you have tasks that create the project's static files in wwwroot 30 | #wwwroot/ 31 | 32 | # MSTest test Results 33 | [Tt]est[Rr]esult*/ 34 | [Bb]uild[Ll]og.* 35 | 36 | # NUNIT 37 | *.VisualState.xml 38 | TestResult.xml 39 | 40 | # Build Results of an ATL Project 41 | [Dd]ebugPS/ 42 | [Rr]eleasePS/ 43 | dlldata.c 44 | 45 | # DNX 46 | project.lock.json 47 | artifacts/ 48 | 49 | *_i.c 50 | *_p.c 51 | *_i.h 52 | *.ilk 53 | *.meta 54 | *.obj 55 | *.pch 56 | *.pdb 57 | *.pgc 58 | *.pgd 59 | *.rsp 60 | *.sbr 61 | *.tlb 62 | *.tli 63 | *.tlh 64 | *.tmp 65 | *.tmp_proj 66 | *.log 67 | *.vspscc 68 | *.vssscc 69 | .builds 70 | *.pidb 71 | *.svclog 72 | *.scc 73 | 74 | # Chutzpah Test files 75 | _Chutzpah* 76 | 77 | # Visual C++ cache files 78 | ipch/ 79 | *.aps 80 | *.ncb 81 | *.opendb 82 | *.opensdf 83 | *.sdf 84 | *.cachefile 85 | 86 | # Visual Studio profiler 87 | *.psess 88 | *.vsp 89 | *.vspx 90 | *.sap 91 | 92 | # TFS 2012 Local Workspace 93 | $tf/ 94 | 95 | # Guidance Automation Toolkit 96 | *.gpState 97 | 98 | # ReSharper is a .NET coding add-in 99 | _ReSharper*/ 100 | *.[Rr]e[Ss]harper 101 | *.DotSettings.user 102 | 103 | # JustCode is a .NET coding add-in 104 | .JustCode 105 | 106 | # TeamCity is a build add-in 107 | _TeamCity* 108 | 109 | # DotCover is a Code Coverage Tool 110 | *.dotCover 111 | 112 | # NCrunch 113 | _NCrunch_* 114 | .*crunch*.local.xml 115 | nCrunchTemp_* 116 | 117 | # MightyMoose 118 | *.mm.* 119 | AutoTest.Net/ 120 | 121 | # Web workbench (sass) 122 | .sass-cache/ 123 | 124 | # Installshield output folder 125 | [Ee]xpress/ 126 | 127 | # DocProject is a documentation generator add-in 128 | DocProject/buildhelp/ 129 | DocProject/Help/*.HxT 130 | DocProject/Help/*.HxC 131 | DocProject/Help/*.hhc 132 | DocProject/Help/*.hhk 133 | DocProject/Help/*.hhp 134 | DocProject/Help/Html2 135 | DocProject/Help/html 136 | 137 | # Click-Once directory 138 | publish/ 139 | 140 | # Publish Web Output 141 | *.[Pp]ublish.xml 142 | *.azurePubxml 143 | # TODO: Comment the next line if you want to checkin your web deploy settings 144 | # but database connection strings (with potential passwords) will be unencrypted 145 | *.pubxml 146 | *.publishproj 147 | 148 | # NuGet Packages 149 | *.nupkg 150 | # The packages folder can be ignored because of Package Restore 151 | **/packages/* 152 | # except build/, which is used as an MSBuild target. 153 | !**/packages/build/ 154 | # Uncomment if necessary however generally it will be regenerated when needed 155 | #!**/packages/repositories.config 156 | # NuGet v3's project.json files produces more ignoreable files 157 | *.nuget.props 158 | *.nuget.targets 159 | 160 | # Microsoft Azure Build Output 161 | csx/ 162 | *.build.csdef 163 | 164 | # Microsoft Azure Emulator 165 | ecf/ 166 | rcf/ 167 | 168 | # Microsoft Azure ApplicationInsights config file 169 | ApplicationInsights.config 170 | 171 | # Windows Store app package directory 172 | AppPackages/ 173 | BundleArtifacts/ 174 | 175 | # Visual Studio cache files 176 | # files ending in .cache can be ignored 177 | *.[Cc]ache 178 | # but keep track of directories ending in .cache 179 | !*.[Cc]ache/ 180 | 181 | # Others 182 | ClientBin/ 183 | ~$* 184 | *~ 185 | *.dbmdl 186 | *.dbproj.schemaview 187 | *.pfx 188 | *.publishsettings 189 | node_modules/ 190 | orleans.codegen.cs 191 | 192 | # RIA/Silverlight projects 193 | Generated_Code/ 194 | 195 | # Backup & report files from converting an old project file 196 | # to a newer Visual Studio version. Backup files are not needed, 197 | # because we have git ;-) 198 | _UpgradeReport_Files/ 199 | Backup*/ 200 | UpgradeLog*.XML 201 | UpgradeLog*.htm 202 | 203 | # SQL Server files 204 | *.mdf 205 | *.ldf 206 | 207 | # Business Intelligence projects 208 | *.rdl.data 209 | *.bim.layout 210 | *.bim_*.settings 211 | 212 | # Microsoft Fakes 213 | FakesAssemblies/ 214 | 215 | # GhostDoc plugin setting file 216 | *.GhostDoc.xml 217 | 218 | # Node.js Tools for Visual Studio 219 | .ntvs_analysis.dat 220 | 221 | # Visual Studio 6 build log 222 | *.plg 223 | 224 | # Visual Studio 6 workspace options file 225 | *.opt 226 | 227 | # Visual Studio LightSwitch build output 228 | **/*.HTMLClient/GeneratedArtifacts 229 | **/*.DesktopClient/GeneratedArtifacts 230 | **/*.DesktopClient/ModelManifest.xml 231 | **/*.Server/GeneratedArtifacts 232 | **/*.Server/ModelManifest.xml 233 | _Pvt_Extensions 234 | 235 | # Paket dependency manager 236 | .paket/paket.exe 237 | 238 | # FAKE - F# Make 239 | .fake/ 240 | 241 | 242 | ### F# ### 243 | lib/debug 244 | lib/release 245 | Debug 246 | *.suo 247 | *.user 248 | obj 249 | bin 250 | *.exe 251 | *.lock 252 | 253 | # Aurelia 254 | 255 | .DS_STORE 256 | StockEstimator.WebUI/wwwroot/config.js 257 | StockEstimator.WebUI/wwwroot/scripts/ -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # StockEstimator 2 | 3 | F# library for estimating future stock prices 4 | 5 | ## About 6 | 7 | This library use linear regression for estimating future stock prices based on the historical data. 8 | 9 | ## Projects 10 | 11 | * **StockEstimator.Logic** - F# project that calculates the future stock prices 12 | * **StockEstimator.Tests** - F# unit test project written with xUnit, FsUnit, FsCheck and Unquote 13 | * **StockEstimator.WebApi** - F# RESTful API for get getting stock prices (using StockEstimator.Logic) written in F# with Suave Framework 14 | * **StockEstimator.Web** - ASP.NET Core web application built with Aurelia Framework that uses StockEstimator.WebApi for getting future stock prices 15 | * **StockEstimator.Charts** - F# WebForms app that display chart with future stock price estimates 16 | * **StockEstimator.ConsoleApp** - C# console app that get future stock prices using StockEstimator.Logic 17 | 18 | ## TODO 19 | 20 | * Refactor logic to perform one request per estimate range 21 | * Prettify UI 22 | * Add more advanced algorithms for estimating stock prices 23 | * Deploy to Azure (as Windows Container) 24 | * Deploy to Heroku 25 | * add real-time prices for current day (i.e. refresh result every 10(?) seconds to get latest price) - will be visible when estimating based on last 1-2 days) 26 | * add caching results on server-side 27 | * add caching results on client-side 28 | -------------------------------------------------------------------------------- /SecretFormula.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jj09/StockEstimator/e03d0ad0d6eea5db26e9f58b5a949974f292e9b7/SecretFormula.xlsx -------------------------------------------------------------------------------- /StockEstimator.Charts/App.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /StockEstimator.Charts/AssemblyInfo.fs: -------------------------------------------------------------------------------- 1 | namespace StockEstimator.Charts.AssemblyInfo 2 | 3 | open System.Reflection 4 | open System.Runtime.CompilerServices 5 | open System.Runtime.InteropServices 6 | 7 | // General Information about an assembly is controlled through the following 8 | // set of attributes. Change these attribute values to modify the information 9 | // associated with an assembly. 10 | [] 11 | [] 12 | [] 13 | [] 14 | [] 15 | [] 16 | [] 17 | [] 18 | 19 | // Setting ComVisible to false makes the types in this assembly not visible 20 | // to COM components. If you need to access a type in this assembly from 21 | // COM, set the ComVisible attribute to true on that type. 22 | [] 23 | 24 | // The following GUID is for the ID of the typelib if this project is exposed to COM 25 | [] 26 | 27 | // Version information for an assembly consists of the following four values: 28 | // 29 | // Major Version 30 | // Minor Version 31 | // Build Number 32 | // Revision 33 | // 34 | // You can specify all the values or you can default the Build and Revision Numbers 35 | // by using the '*' as shown below: 36 | // [] 37 | [] 38 | [] 39 | 40 | do 41 | () -------------------------------------------------------------------------------- /StockEstimator.Charts/Charts.fsx: -------------------------------------------------------------------------------- 1 | #load @"..\packages\FSharp.Charting.0.90.13\FSharp.Charting.fsx" 2 | #load @"..\packages\MathNet.Numerics.FSharp.3.11.0\MathNet.Numerics.fsx" 3 | #r @"..\packages\FSharp.Data.2.2.5\lib\net40\FSharp.Data.dll" 4 | #r @"..\StockEstimator.Logic\bin\Debug\StockEstimator.Logic.dll" 5 | 6 | open System 7 | open System.Drawing 8 | open FSharp.Charting 9 | open StockEstimator.Logic 10 | 11 | 12 | let stockData = StockData() 13 | 14 | let estimateBasedOnLastYearsCount = 2 15 | 16 | let data = stockData.GetStockDataForDateRange "msft" (DateTime.Now.AddYears(-estimateBasedOnLastYearsCount)) DateTime.Now 17 | 18 | let lst = [ for x in data -> (x.Key, x.Value)] @ [ for x in 1..365 -> (DateTime.Now.AddDays(float x), decimal (stockData.GetEstimatedPriceForDate ("msft", (DateTime.Now.AddDays(float x)), (DateTime.Now.AddYears(-estimateBasedOnLastYearsCount)))))] 19 | 20 | Chart.Line lst -------------------------------------------------------------------------------- /StockEstimator.Charts/ChartsDrawing.fs: -------------------------------------------------------------------------------- 1 | namespace StockEstimator.Charts 2 | 3 | open System 4 | open System.Net 5 | open FSharp.Charting 6 | open StockEstimator.Logic 7 | open System.Drawing 8 | 9 | type ChartsDrawing() = 10 | 11 | member this.CreatePriceLine (stockData: List) stock color = 12 | Chart.Line (stockData, Name = stock) 13 | |> Chart.WithSeries.Style(Color = color, BorderWidth = 2) 14 | 15 | member this.DrawEstimate ticker (lookBackTill: DateTime) (estimateTill: DateTime) = 16 | let data = StockData.GetStockDataForDateRange ticker lookBackTill DateTime.Now 17 | let past = [ for x in data -> (x.Key, x.Value)] 18 | let daysFromNowTillEstimateEnd = int (estimateTill - DateTime.Now).TotalDays 19 | let future = [ for x in 1..daysFromNowTillEstimateEnd -> (DateTime.Now.AddDays(float x), decimal (StockData.GetEstimatedPriceForDateWithRandom (ticker, DateTime.Now.AddDays(float x), lookBackTill)))] 20 | (Chart.Combine [ this.CreatePriceLine past "past" Color.SkyBlue 21 | this.CreatePriceLine future "future" Color.Red ]) -------------------------------------------------------------------------------- /StockEstimator.Charts/Program.fs: -------------------------------------------------------------------------------- 1 | module MainApp 2 | 3 | open System 4 | open System.Windows.Forms 5 | open FSharp.Charting 6 | open StockEstimator.Charts 7 | 8 | [] 9 | [] 10 | let main argv = 11 | 12 | Application.EnableVisualStyles() 13 | Application.SetCompatibleTextRenderingDefault false 14 | 15 | let estimateBasedOnLastYearsCount = 2 16 | let estimateFutureDaysCount = 365 17 | 18 | let chartsDrawing = ChartsDrawing() 19 | let estimateFrom = DateTime.Now.AddYears(-estimateBasedOnLastYearsCount) 20 | let estimateTill = DateTime.Now.AddDays(float estimateFutureDaysCount) 21 | let chart = chartsDrawing.DrawEstimate "msft" estimateFrom estimateTill 22 | 23 | Application.Run(chart.ShowChart()); 24 | 0 -------------------------------------------------------------------------------- /StockEstimator.Charts/StockEstimator.Charts.fsproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | 2.0 8 | 88b5bbcc-7e70-4d94-901e-dbd5839dddf3 9 | Exe 10 | StockEstimator.Charts 11 | StockEstimator.Charts 12 | v4.5 13 | true 14 | 4.4.0.0 15 | StockEstimator.Charts 16 | 17 | 18 | true 19 | full 20 | false 21 | false 22 | bin\Debug\ 23 | DEBUG;TRACE 24 | 3 25 | AnyCPU 26 | bin\Debug\StockEstimator.Charts.XML 27 | true 28 | 29 | 30 | pdbonly 31 | true 32 | true 33 | bin\Release\ 34 | TRACE 35 | 3 36 | AnyCPU 37 | bin\Release\StockEstimator.Charts.XML 38 | true 39 | 40 | 41 | 11 42 | 43 | 44 | 45 | 46 | $(MSBuildExtensionsPath32)\..\Microsoft SDKs\F#\3.0\Framework\v4.0\Microsoft.FSharp.Targets 47 | 48 | 49 | 50 | 51 | $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\FSharp\Microsoft.FSharp.Targets 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | ..\packages\FSharp.Charting.0.90.13\lib\net40\FSharp.Charting.dll 67 | True 68 | 69 | 70 | ..\packages\FSharp.Core.4.0.0.1\lib\net40\FSharp.Core.dll 71 | True 72 | 73 | 74 | ..\packages\MathNet.Numerics.3.11.0\lib\net40\MathNet.Numerics.dll 75 | True 76 | 77 | 78 | ..\packages\MathNet.Numerics.FSharp.3.11.0\lib\net40\MathNet.Numerics.FSharp.dll 79 | True 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | StockEstimator.Logic 92 | {f255f413-25da-44c6-92f2-6481cc8065eb} 93 | True 94 | 95 | 96 | 103 | -------------------------------------------------------------------------------- /StockEstimator.Charts/StockEstimator.Charts.v2.ncrunchproject: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jj09/StockEstimator/e03d0ad0d6eea5db26e9f58b5a949974f292e9b7/StockEstimator.Charts/StockEstimator.Charts.v2.ncrunchproject -------------------------------------------------------------------------------- /StockEstimator.Charts/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /StockEstimator.ConsoleApp/App.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /StockEstimator.ConsoleApp/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using StockEstimator.Logic; 7 | 8 | namespace StockEstimator.ConsoleApp 9 | { 10 | class Program 11 | { 12 | static void Main(string[] args) 13 | { 14 | // fetching stock data 15 | var msftRows = StockData.GetStockData("MSFT"); 16 | //foreach (var row in msftRows) 17 | //{ 18 | // Console.WriteLine($"{row.Key}: {string.Format("{0:.00}", float.Parse(row.Value))}"); 19 | //} 20 | 21 | // fetching stock data for date/time range 22 | var msftRowsForLastYear = StockData.GetStockDataForDateRange("MSFT", DateTime.Now.AddYears(-1), DateTime.Now); 23 | //foreach (var row in msftRowsForLastYear) 24 | //{ 25 | // Console.WriteLine($"{row.Key}: {string.Format("{0:.00}", float.Parse(row.Value))}"); 26 | //} 27 | 28 | // estimating future price 29 | var futureDate = DateTime.Now.AddDays(5); 30 | var estimatedPrice = StockData.GetEstimatedPriceForDate("MSFT", futureDate, DateTime.Now.AddMonths(-4)); 31 | Console.WriteLine($"Estimated price for {futureDate.ToShortDateString()} is {string.Format("{0:.00}", estimatedPrice)}"); 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /StockEstimator.ConsoleApp/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("StockEstimator.ConsoleApp")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("StockEstimator.ConsoleApp")] 13 | [assembly: AssemblyCopyright("Copyright © 2016")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("0d188e86-8a3a-4a6b-ae8a-f141bf690971")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /StockEstimator.ConsoleApp/StockEstimator.ConsoleApp.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {0D188E86-8A3A-4A6B-AE8A-F141BF690971} 8 | Exe 9 | Properties 10 | StockEstimator.ConsoleApp 11 | StockEstimator.ConsoleApp 12 | v4.5 13 | 512 14 | 15 | 16 | AnyCPU 17 | true 18 | full 19 | false 20 | bin\Debug\ 21 | DEBUG;TRACE 22 | prompt 23 | 4 24 | 25 | 26 | AnyCPU 27 | pdbonly 28 | true 29 | bin\Release\ 30 | TRACE 31 | prompt 32 | 4 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | {f255f413-25da-44c6-92f2-6481cc8065eb} 54 | StockEstimator.Logic 55 | 56 | 57 | 58 | 65 | -------------------------------------------------------------------------------- /StockEstimator.ConsoleApp/StockEstimator.ConsoleApp.v2.ncrunchproject: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jj09/StockEstimator/e03d0ad0d6eea5db26e9f58b5a949974f292e9b7/StockEstimator.ConsoleApp/StockEstimator.ConsoleApp.v2.ncrunchproject -------------------------------------------------------------------------------- /StockEstimator.Logic/App.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /StockEstimator.Logic/AssemblyInfo.fs: -------------------------------------------------------------------------------- 1 | namespace StockEstimator.Logic.AssemblyInfo 2 | 3 | open System.Reflection 4 | open System.Runtime.CompilerServices 5 | open System.Runtime.InteropServices 6 | 7 | // General Information about an assembly is controlled through the following 8 | // set of attributes. Change these attribute values to modify the information 9 | // associated with an assembly. 10 | [] 11 | [] 12 | [] 13 | [] 14 | [] 15 | [] 16 | [] 17 | [] 18 | 19 | // Setting ComVisible to false makes the types in this assembly not visible 20 | // to COM components. If you need to access a type in this assembly from 21 | // COM, set the ComVisible attribute to true on that type. 22 | [] 23 | 24 | // The following GUID is for the ID of the typelib if this project is exposed to COM 25 | [] 26 | 27 | // Version information for an assembly consists of the following four values: 28 | // 29 | // Major Version 30 | // Minor Version 31 | // Build Number 32 | // Revision 33 | // 34 | // You can specify all the values or you can default the Build and Revision Numbers 35 | // by using the '*' as shown below: 36 | // [] 37 | [] 38 | [] 39 | 40 | do 41 | () -------------------------------------------------------------------------------- /StockEstimator.Logic/StockData.fs: -------------------------------------------------------------------------------- 1 | module StockEstimator.Logic.StockData 2 | 3 | open System 4 | open FSharp.Data 5 | 6 | // this module use yahoo finance API: http://www.jarloo.com/yahoo_finance/ 7 | // for alternative API check: https://www.quandl.com/blog/api-for-stock-data 8 | 9 | type Stocks = CsvProvider<"http://ichart.finance.yahoo.com/table.csv?s=msft"> 10 | 11 | 12 | let getBaseStockUrl ticker = 13 | "http://ichart.finance.yahoo.com/table.csv?s=" + ticker 14 | 15 | let getUrlForDateTimeRange ticker (startDate:DateTime) (endDate:DateTime) = 16 | let root = getBaseStockUrl ticker 17 | sprintf "%s&a=%i&b=%i&c=%i&d=%i&e=%i&f=%i" 18 | root (startDate.Month - 1) startDate.Day startDate.Year 19 | (endDate.Month - 1) endDate.Day endDate.Year 20 | 21 | let GetStockData ticker = 22 | let stockData = Stocks.Load(getBaseStockUrl ticker) 23 | dict (stockData.Rows |> Seq.map (fun x -> x.Date, x.Close)) 24 | 25 | let GetStockDataForDateRange ticker (startDate:DateTime) (endDate:DateTime) = 26 | let url = getUrlForDateTimeRange ticker startDate endDate 27 | let stockData = Stocks.Load(url) 28 | dict (stockData.Rows |> Seq.map (fun x -> x.Date, x.Close)) 29 | 30 | let GetEstimatedPriceForDate (ticker, forDate: DateTime, fromDate: DateTime) = 31 | // TODO: cache results!!! 32 | let stockData = Stocks.Load(getUrlForDateTimeRange ticker fromDate (DateTime.Now)).Rows 33 | 34 | let firstDate = (stockData |> Seq.last).Date 35 | 36 | let xData, yData = 37 | stockData 38 | |> Seq.map (fun x -> (x.Date - firstDate).TotalDays, float x.Close) 39 | |> Seq.toArray 40 | |> Array.unzip 41 | 42 | let forDateInDays = (forDate - firstDate).TotalDays 43 | 44 | let intercept, slope = MathNet.Numerics.Fit.Line(xData, yData) 45 | 46 | let result = forDateInDays*slope + intercept 47 | result 48 | 49 | let GetEstimatedPriceForDateWithRandom (ticker, forDate: DateTime, fromDate: DateTime) = 50 | let price = GetEstimatedPriceForDate (ticker, forDate, fromDate) 51 | price - (price*0.05) + (price*0.1*Random().NextDouble()) 52 | 53 | let GetEstimatedPriceForDateRange (ticker, startDate: DateTime, endDate: DateTime, fromDate: DateTime) = 54 | let daysCount = int (endDate - startDate).TotalDays 55 | seq { 56 | for i in 0..daysCount do 57 | let dateTime = startDate.AddDays(float i) 58 | let price = GetEstimatedPriceForDate (ticker, dateTime, fromDate) 59 | yield (dateTime, price) 60 | } 61 | 62 | let GetEstimatedPriceForDateRangeWithRandom (ticker, startDate: DateTime, endDate: DateTime, fromDate: DateTime) = 63 | let daysCount = int (endDate - startDate).TotalDays 64 | seq { 65 | for i in 0..daysCount do 66 | let dateTime = startDate.AddDays(float i) 67 | let price = GetEstimatedPriceForDateWithRandom (ticker, dateTime, fromDate) 68 | yield (dateTime, price) 69 | } -------------------------------------------------------------------------------- /StockEstimator.Logic/StockEstimator.Logic.fsproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | 2.0 8 | f255f413-25da-44c6-92f2-6481cc8065eb 9 | Library 10 | StockEstimator.Logic 11 | StockEstimator.Logic 12 | v4.5 13 | 4.3.0.0 14 | true 15 | StockEstimator.Logic 16 | 17 | 18 | 19 | true 20 | full 21 | false 22 | false 23 | bin\Debug\ 24 | DEBUG;TRACE 25 | 3 26 | bin\Debug\StockEstimator.Logic.XML 27 | 28 | 29 | pdbonly 30 | true 31 | true 32 | bin\Release\ 33 | TRACE 34 | 3 35 | bin\Release\StockEstimator.Logic.XML 36 | 37 | 38 | 11 39 | 40 | 41 | 42 | 43 | $(MSBuildExtensionsPath32)\..\Microsoft SDKs\F#\3.0\Framework\v4.0\Microsoft.FSharp.Targets 44 | 45 | 46 | 47 | 48 | $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\FSharp\Microsoft.FSharp.Targets 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | ..\packages\FSharp.Core.4.0.0.1\lib\net40\FSharp.Core.dll 62 | True 63 | 64 | 65 | ..\packages\FSharp.Data.2.2.5\lib\net40\FSharp.Data.dll 66 | True 67 | 68 | 69 | ..\packages\MathNet.Numerics.3.11.0\lib\net40\MathNet.Numerics.dll 70 | True 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 85 | -------------------------------------------------------------------------------- /StockEstimator.Logic/StockEstimator.Logic.v2.ncrunchproject: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jj09/StockEstimator/e03d0ad0d6eea5db26e9f58b5a949974f292e9b7/StockEstimator.Logic/StockEstimator.Logic.v2.ncrunchproject -------------------------------------------------------------------------------- /StockEstimator.Logic/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /StockEstimator.Tests/App.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /StockEstimator.Tests/AssemblyInfo.fs: -------------------------------------------------------------------------------- 1 | namespace StockEstimator.Tests.AssemblyInfo 2 | 3 | open System.Reflection 4 | open System.Runtime.CompilerServices 5 | open System.Runtime.InteropServices 6 | 7 | // General Information about an assembly is controlled through the following 8 | // set of attributes. Change these attribute values to modify the information 9 | // associated with an assembly. 10 | [] 11 | [] 12 | [] 13 | [] 14 | [] 15 | [] 16 | [] 17 | [] 18 | 19 | // Setting ComVisible to false makes the types in this assembly not visible 20 | // to COM components. If you need to access a type in this assembly from 21 | // COM, set the ComVisible attribute to true on that type. 22 | [] 23 | 24 | // The following GUID is for the ID of the typelib if this project is exposed to COM 25 | [] 26 | 27 | // Version information for an assembly consists of the following four values: 28 | // 29 | // Major Version 30 | // Minor Version 31 | // Build Number 32 | // Revision 33 | // 34 | // You can specify all the values or you can default the Build and Revision Numbers 35 | // by using the '*' as shown below: 36 | // [] 37 | [] 38 | [] 39 | 40 | do 41 | () -------------------------------------------------------------------------------- /StockEstimator.Tests/StockDataTests.fs: -------------------------------------------------------------------------------- 1 | namespace StockDataEstimator.Tests 2 | 3 | open System 4 | open StockEstimator.Logic 5 | 6 | // fix for MissingMethod Exception: https://github.com/SwensenSoftware/unquote/issues/119 7 | 8 | module StockDataTests = 9 | open Xunit 10 | open FsUnit.Xunit 11 | open FsCheck 12 | open FsCheck.Xunit 13 | open System.Linq 14 | open Swensen.Unquote 15 | 16 | [] 17 | let ``returns stock data in correct format`` () = 18 | // Arrange 19 | 20 | // Act 21 | let data = StockData.GetStockData "msft" 22 | let firstKey = Enumerable.ToList(data.Keys).[0] 23 | let firstValue = data.Values.First() 24 | 25 | // Assert 26 | Assert.Equal(DateTime.Now.GetType(), firstKey.GetType()) // xunit 27 | Assert.Equal((decimal 1).GetType(), firstValue.GetType()) // xunit 28 | firstKey |> should be ofExactType // fsunit 29 | firstValue |> should be ofExactType // fsunit 30 | 31 | 32 | [] 33 | [] 34 | [] 35 | [] 36 | [] 37 | [] 38 | [] 39 | [] 40 | [] // Stock marked closed on New Year's Day and MLK Day 41 | [] // weekend 42 | let ``GetStockDataForDateRange returns correct number of results`` from till expectedRowCount = 43 | // Arrange 44 | 45 | // Act 46 | let data = StockData.GetStockDataForDateRange "msft" (DateTime.Parse from) (DateTime.Parse till) 47 | 48 | // Assert 49 | Assert.Equal(expectedRowCount, data.Count) // xunit 50 | data.Count |> should equal expectedRowCount // fsunit: https://fsprojects.github.io/FsUnit/#What-is-FsUnit 51 | test <@ expectedRowCount = data.Count @> // unquote 52 | 53 | [] 54 | [] 55 | [] 56 | [] 57 | [] 58 | [] 59 | [] 60 | let ``GetEstimatedPriceForDateWithRandom returns price greater or less by 5% from GetEstimatedPriceForDate`` (addDays: int) = 61 | // Arrange 62 | let targetDay = DateTime.Now.AddDays(float addDays) 63 | let lookBackTill = DateTime.Now.AddYears(-1) 64 | 65 | // Act 66 | let estimatedPrice = StockData.GetEstimatedPriceForDate("msft", targetDay, lookBackTill) 67 | let estimatedPriceWithRandom = StockData.GetEstimatedPriceForDateWithRandom("msft", targetDay, lookBackTill) 68 | 69 | // Assert 70 | estimatedPriceWithRandom |> should (equalWithin (estimatedPrice*0.05)) estimatedPrice // fsunit 71 | 72 | // property-based style 73 | type Days = 74 | static member Int32() = 75 | Arb.Default.Int32() 76 | |> Arb.filter (fun i -> i > 0 && i < 365) 77 | 78 | type DayPropertyAttribute() = 79 | inherit PropertyAttribute(Arbitrary = [| typeof |]) 80 | 81 | //[ |])>] // alternative for DayPropertyAttribute definition 82 | [] 83 | let ``GetEstimatedPriceForDateWithRandom returns price greater or less by 5% from GetEstimatedPriceForDate (property style)`` (addDays: Int32) = 84 | // Arrange 85 | let targetDay = DateTime.Now.AddDays(float addDays) 86 | let lookBackTill = DateTime.Now.AddYears(-1) 87 | 88 | // Act 89 | let estimatedPrice = StockData.GetEstimatedPriceForDate("msft", targetDay, lookBackTill) 90 | let estimatedPriceWithRandom = StockData.GetEstimatedPriceForDateWithRandom("msft", targetDay, lookBackTill) 91 | 92 | // Assert 93 | estimatedPriceWithRandom |> should (equalWithin (estimatedPrice*0.05)) estimatedPrice // fsunit 94 | 95 | [] 96 | [] 97 | [] 98 | [] 99 | let ``GetEstimatedPriceForDateRange returns correct number of results`` startDate endDate expectedRowCount = 100 | // Arrange 101 | let startDateTime = DateTime.Parse startDate 102 | let endDateTime = DateTime.Parse endDate 103 | let lookBackTill = DateTime.Now.AddYears(-1) 104 | 105 | // Act 106 | let data = StockData.GetEstimatedPriceForDateRange("msft", startDateTime, endDateTime, lookBackTill) 107 | 108 | // Assert 109 | Assert.Equal(expectedRowCount, data.Count()) // xunit 110 | data.Count() |> should equal expectedRowCount // fsunit: https://fsprojects.github.io/FsUnit/#What-is-FsUnit 111 | test <@ expectedRowCount = data.Count() @> // unquote 112 | 113 | [] 114 | [] 115 | [] 116 | [] 117 | let ``GetEstimatedPriceForDateRangeWithRandom returns correct number of results`` startDate endDate expectedRowCount = 118 | // Arrange 119 | let startDateTime = DateTime.Parse startDate 120 | let endDateTime = DateTime.Parse endDate 121 | let lookBackTill = DateTime.Now.AddYears(-1) 122 | 123 | // Act 124 | let data = StockData.GetEstimatedPriceForDateRangeWithRandom("msft", startDateTime, endDateTime, lookBackTill) 125 | 126 | // Assert 127 | Assert.Equal(expectedRowCount, data.Count()) // xunit 128 | data.Count() |> should equal expectedRowCount // fsunit: https://fsprojects.github.io/FsUnit/#What-is-FsUnit 129 | test <@ expectedRowCount = data.Count() @> // unquote -------------------------------------------------------------------------------- /StockEstimator.Tests/StockEstimator.Tests.fsproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | Debug 7 | AnyCPU 8 | 2.0 9 | 24d58cf7-73c8-4968-b637-af238fee2c57 10 | Library 11 | StockEstimator.Tests 12 | StockEstimator.Tests 13 | v4.5 14 | 4.3.0.0 15 | true 16 | StockEstimator.Tests 17 | 18 | 19 | 20 | 21 | 22 | true 23 | full 24 | false 25 | false 26 | bin\Debug\ 27 | DEBUG;TRACE 28 | 3 29 | bin\Debug\StockEstimator.Tests.XML 30 | 31 | 32 | pdbonly 33 | true 34 | true 35 | bin\Release\ 36 | TRACE 37 | 3 38 | bin\Release\StockEstimator.Tests.XML 39 | 40 | 41 | 11 42 | 43 | 44 | 45 | 46 | $(MSBuildExtensionsPath32)\..\Microsoft SDKs\F#\3.0\Framework\v4.0\Microsoft.FSharp.Targets 47 | 48 | 49 | 50 | 51 | $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\FSharp\Microsoft.FSharp.Targets 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | ..\packages\FsCheck.2.2.4\lib\net45\FsCheck.dll 65 | True 66 | 67 | 68 | ..\packages\FsCheck.Xunit.2.2.4\lib\net45\FsCheck.Xunit.dll 69 | True 70 | 71 | 72 | ..\packages\FSharp.Core.4.0.0.1\lib\net40\FSharp.Core.dll 73 | True 74 | 75 | 76 | ..\packages\FSharp.Data.2.3.0-beta2\lib\net40\FSharp.Data.dll 77 | True 78 | 79 | 80 | ..\packages\FsUnit.xUnit.1.4.1.0\lib\net45\FsUnit.Xunit.dll 81 | True 82 | 83 | 84 | 85 | ..\packages\FsUnit.xUnit.1.4.1.0\lib\net45\NHamcrest.dll 86 | True 87 | 88 | 89 | 90 | 91 | 92 | 93 | ..\packages\Unquote.3.1.1\lib\net45\Unquote.dll 94 | True 95 | 96 | 97 | ..\packages\xunit.abstractions.2.0.0\lib\net35\xunit.abstractions.dll 98 | True 99 | 100 | 101 | ..\packages\xunit.assert.2.1.0\lib\dotnet\xunit.assert.dll 102 | True 103 | 104 | 105 | ..\packages\xunit.extensibility.core.2.1.0\lib\dotnet\xunit.core.dll 106 | True 107 | 108 | 109 | ..\packages\xunit.extensibility.execution.2.1.0\lib\net45\xunit.execution.desktop.dll 110 | True 111 | 112 | 113 | 114 | 115 | StockEstimator.Logic 116 | {f255f413-25da-44c6-92f2-6481cc8065eb} 117 | True 118 | 119 | 120 | 121 | 122 | This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. 123 | 124 | 125 | 126 | 133 | -------------------------------------------------------------------------------- /StockEstimator.Tests/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /StockEstimator.WebApi.Docker/App.fsx: -------------------------------------------------------------------------------- 1 | #r "./packages/Suave/lib/net40/Suave.dll" 2 | #r "./packages/FSharp.Data/lib/net40/FSharp.Data.dll" 3 | #r "./packages/Newtonsoft.Json/lib/net40/Newtonsoft.Json.dll" 4 | #load "StockData.fsx" 5 | 6 | open System 7 | open System.Net 8 | open Suave 9 | open Suave.Http 10 | open Suave.Web 11 | open Suave.Successful 12 | open Suave.Filters 13 | open Suave.Operators 14 | open Suave.RequestErrors 15 | open StockEstimator.Logic 16 | open Newtonsoft.Json 17 | open Newtonsoft.Json.Serialization 18 | open Suave.Operators 19 | open Suave.Writers 20 | open Suave.Successful 21 | open FSharp.Data 22 | open FSharp.Data.HttpRequestHeaders 23 | 24 | let JSON v = 25 | let jsonSerializerSettings = JsonSerializerSettings() 26 | jsonSerializerSettings.ContractResolver <- CamelCasePropertyNamesContractResolver() 27 | 28 | JsonConvert.SerializeObject(v, jsonSerializerSettings) 29 | |> OK 30 | >=> Writers.setMimeType "application/json; charset=utf-8" 31 | 32 | let setCORSHeaders = 33 | setHeader "Access-Control-Allow-Origin" "*" 34 | >=> setHeader "Access-Control-Allow-Headers" "content-type" 35 | 36 | let getPrice = 37 | StockData.GetEstimatedPriceForDateWithRandom("msft", DateTime.Now.AddDays(float 5), DateTime.Now.AddYears(-1)) |> JSON 38 | 39 | let getPriceForDateRange ticker till since = 40 | let endDateTime = DateTime.Parse till 41 | let fromDate = DateTime.Parse since 42 | StockData.GetEstimatedPriceForDateRangeWithRandom(ticker, DateTime.Now, endDateTime, fromDate) |> JSON 43 | 44 | // THIS IS AWESOME :D 45 | let getPriceForDateRangeRequest = 46 | request (fun r -> 47 | match r.queryParam "ticker" with 48 | | Choice1Of2 ticker -> (request (fun r -> 49 | match r.queryParam "till" with 50 | | Choice1Of2 till -> (request (fun r -> 51 | match r.queryParam "since" with 52 | | Choice1Of2 since -> getPriceForDateRange ticker till since 53 | | Choice2Of2 msg -> BAD_REQUEST msg)) 54 | | Choice2Of2 msg -> BAD_REQUEST msg)) 55 | | Choice2Of2 msg -> BAD_REQUEST msg) 56 | 57 | let getPriceFromAzure = 58 | // from https://www.reddit.com/r/programming/comments/4ee3s7/getting_started_with_f/ 59 | let bind name nextStep = request (fun r -> 60 | match r.queryParam name with 61 | | Choice1Of2 value -> nextStep value 62 | | Choice2Of2 msg -> BAD_REQUEST msg) 63 | 64 | let experiments = dict[ 65 | "msft", ("9a21fffe27f34d6b90ab83a9a28af1f5", "9LqHbhrox5Bq05Eqwb4+0dg+7S9avXEsse03xUKhRBAc4Paz7I6KzB9/k9sXYnD1db61HzubiYuB4jp3IXd6ew=="); 66 | "msft(2015-11-04 to 2016-11-03)", ("08c4ee6a411145fca9973c2fd3a8fad6", "GHWmBkKaEXMVi5bifrYSi1h0BqIwD5ccopEm8E4RZqbTidwQAZVf1vQikDoITAR8LFVP9AH3ObxzN+0NpotXOw=="); 67 | "msft(2016-10-04 to 2016-11-03)", ("cdbf8d1e5f6d4aa396518a6bfe4bef52", "UbkxG8mVjv9Keyn2iKh+G2rzUGROrbfZuZ9dtQyTGH4Nrq/cZ10BUpm1OL348YsJJf9mLPJQXBi/JatUDiHEGw==")]; 68 | 69 | bind "date" <| fun date -> 70 | bind "experiment" <| fun experiment -> 71 | let service = fst (experiments.Item(experiment)) 72 | let token = snd (experiments.Item(experiment)) 73 | Http.RequestString(sprintf "https://ussouthcentral.services.azureml.net/workspaces/03f600fc9ca34cc9a692c2aeea610df0/services/%s/execute?api-version=2.0&format=swagger" service, 74 | headers = [ContentType HttpContentTypes.Json; Authorization (sprintf "Bearer %s" token) ], 75 | body = TextRequest (sprintf """ {"Inputs": { "input1": [{'Date': "%s"}]}, "GlobalParameters": {}} """ date)) |> JSON 76 | 77 | 78 | let webPart = 79 | choose [ 80 | GET >=> setCORSHeaders >=> choose [ 81 | path "/" >=> Files.file "index.html" 82 | 83 | path "/GetPrice" >=> getPrice 84 | path "/GetPriceForDateRange" >=> getPriceForDateRangeRequest 85 | 86 | path "/GetPriceFromAzure" >=> getPriceFromAzure 87 | 88 | //static files 89 | pathRegex "(.*)\.(css|png|gif)" >=> Files.browseHome 90 | ] 91 | ] 92 | 93 | let config = 94 | { defaultConfig with 95 | bindings = [HttpBinding.create HTTP (IPAddress.Parse "0.0.0.0") 8083us] 96 | } 97 | 98 | let resp = "Processed by " + System.Environment.MachineName 99 | startWebServer config webPart -------------------------------------------------------------------------------- /StockEstimator.WebApi.Docker/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ocelotuproar/docker-alpine-fsharp:4.0 2 | RUN mkdir -p /src 3 | WORKDIR /src 4 | 5 | COPY paket.bootstrapper.exe /src 6 | RUN mono paket.bootstrapper.exe 7 | 8 | COPY paket.dependencies /src 9 | RUN mono paket.exe install 10 | 11 | COPY . /src 12 | 13 | EXPOSE 8083 14 | CMD ["fsharpi", "App.fsx"] -------------------------------------------------------------------------------- /StockEstimator.WebApi.Docker/StockData.fsx: -------------------------------------------------------------------------------- 1 | module StockEstimator.Logic.StockData 2 | 3 | #r "./packages/Suave/lib/net40/Suave.dll" 4 | #r "./packages/FSharp.Data/lib/net40/FSharp.Data.dll" 5 | #r "./packages/MathNet.Numerics/lib/net40/MathNet.Numerics.dll" 6 | 7 | open System 8 | open System.Collections.Generic 9 | open FSharp.Data 10 | 11 | // this module use yahoo finance API: http://www.jarloo.com/yahoo_finance/ 12 | // for alternative API check: https://www.quandl.com/blog/api-for-stock-data 13 | 14 | type Stocks = CsvProvider<"http://ichart.finance.yahoo.com/table.csv?s=msft"> 15 | 16 | let stocksCache = new Dictionary() 17 | 18 | let getBaseStockUrl ticker = 19 | "http://ichart.finance.yahoo.com/table.csv?s=" + ticker 20 | 21 | let getUrlForDateTimeRange ticker (startDate:DateTime) (endDate:DateTime) = 22 | let root = getBaseStockUrl ticker 23 | sprintf "%s&a=%i&b=%i&c=%i&d=%i&e=%i&f=%i" 24 | root (startDate.Month - 1) startDate.Day startDate.Year 25 | (endDate.Month - 1) endDate.Day endDate.Year 26 | 27 | let getStockDataForUrl url = 28 | if not (stocksCache.ContainsKey(url)) then 29 | stocksCache.Add(url, Stocks.Load(url)) 30 | stocksCache.[url] 31 | 32 | let GetStockData ticker = 33 | let stockData = getBaseStockUrl ticker |> getStockDataForUrl 34 | dict (stockData.Rows |> Seq.map (fun x -> x.Date, x.Close)) 35 | 36 | let GetStockDataForDateRange ticker (startDate:DateTime) (endDate:DateTime) = 37 | let stockData = getUrlForDateTimeRange ticker startDate endDate |> getStockDataForUrl 38 | dict (stockData.Rows |> Seq.map (fun x -> x.Date, x.Close)) 39 | 40 | let GetEstimatedPriceForDate (ticker, forDate: DateTime, fromDate: DateTime) = 41 | let stockData = getUrlForDateTimeRange ticker fromDate (DateTime.Now) |> getStockDataForUrl 42 | let stockDataRows = stockData.Rows 43 | let firstDate = (stockDataRows |> Seq.last).Date 44 | 45 | let xData, yData = 46 | stockDataRows 47 | |> Seq.map (fun x -> (x.Date - firstDate).TotalDays, float x.Close) 48 | |> Seq.toArray 49 | |> Array.unzip 50 | 51 | let forDateInDays = (forDate - firstDate).TotalDays 52 | 53 | let intercept, slope = MathNet.Numerics.Fit.Line(xData, yData) 54 | 55 | let result = forDateInDays*slope + intercept 56 | result 57 | 58 | let GetEstimatedPriceForDateWithRandom (ticker, forDate: DateTime, fromDate: DateTime) = 59 | let price = GetEstimatedPriceForDate (ticker, forDate, fromDate) 60 | price - (price*0.05) + (price*0.1*Random().NextDouble()) 61 | 62 | let GetEstimatedPriceForDateRange (ticker, startDate: DateTime, endDate: DateTime, fromDate: DateTime) = 63 | let daysCount = int (endDate - startDate).TotalDays 64 | seq { 65 | for i in 0..daysCount do 66 | let dateTime = startDate.AddDays(float i) 67 | let price = GetEstimatedPriceForDate (ticker, dateTime, fromDate) 68 | yield (dateTime, price) 69 | } 70 | 71 | let GetEstimatedPriceForDateRangeWithRandom (ticker, startDate: DateTime, endDate: DateTime, fromDate: DateTime) = 72 | let daysCount = int (endDate - startDate).TotalDays 73 | seq { 74 | for i in 0..daysCount do 75 | let dateTime = startDate.AddDays(float i) 76 | let price = GetEstimatedPriceForDateWithRandom (ticker, dateTime, fromDate) 77 | yield (dateTime, price) 78 | } -------------------------------------------------------------------------------- /StockEstimator.WebApi.Docker/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Stock Estimator 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 |

This is Stock Estimator Web API!

20 | 21 | 22 | 24 | 25 | 27 | 28 | -------------------------------------------------------------------------------- /StockEstimator.WebApi.Docker/paket.bootstrapper.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jj09/StockEstimator/e03d0ad0d6eea5db26e9f58b5a949974f292e9b7/StockEstimator.WebApi.Docker/paket.bootstrapper.exe -------------------------------------------------------------------------------- /StockEstimator.WebApi.Docker/paket.dependencies: -------------------------------------------------------------------------------- 1 | source https://nuget.org/api/v2 2 | 3 | nuget Suave 4 | nuget FSharp.Data 5 | nuget MathNet.Numerics 6 | nuget Newtonsoft.Json -------------------------------------------------------------------------------- /StockEstimator.WebApi.Docker/paket.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jj09/StockEstimator/e03d0ad0d6eea5db26e9f58b5a949974f292e9b7/StockEstimator.WebApi.Docker/paket.exe -------------------------------------------------------------------------------- /StockEstimator.WebApi/App.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /StockEstimator.WebApi/App.fs: -------------------------------------------------------------------------------- 1 | //module StockEstimator.Web 2 | 3 | open System 4 | open Suave 5 | open Suave.Successful 6 | open Suave.Filters 7 | open Suave.Operators 8 | open Suave.RequestErrors 9 | open StockEstimator.Logic 10 | open Newtonsoft.Json 11 | open Newtonsoft.Json.Serialization 12 | open Suave.Operators 13 | open Suave.Writers 14 | open Suave.Successful 15 | open FSharp.Data 16 | open FSharp.Data.HttpRequestHeaders 17 | 18 | let JSON v = 19 | let jsonSerializerSettings = new JsonSerializerSettings() 20 | jsonSerializerSettings.ContractResolver <- new CamelCasePropertyNamesContractResolver() 21 | 22 | JsonConvert.SerializeObject(v, jsonSerializerSettings) 23 | |> OK 24 | >=> Writers.setMimeType "application/json; charset=utf-8" 25 | 26 | let getPrice = 27 | StockData.GetEstimatedPriceForDateWithRandom("msft", DateTime.Now.AddDays(float 5), DateTime.Now.AddYears(-1)) |> JSON 28 | 29 | let getPriceForDateRange ticker till since = 30 | let endDateTime = DateTime.Parse till 31 | let fromDate = DateTime.Parse since 32 | StockData.GetEstimatedPriceForDateRangeWithRandom(ticker, DateTime.Now, endDateTime, fromDate) |> JSON 33 | 34 | // THIS IS AWESOME :D 35 | let getPriceForDateRangeRequest = 36 | request (fun r -> 37 | match r.queryParam "ticker" with 38 | | Choice1Of2 ticker -> (request (fun r -> 39 | match r.queryParam "till" with 40 | | Choice1Of2 till -> (request (fun r -> 41 | match r.queryParam "since" with 42 | | Choice1Of2 since -> getPriceForDateRange ticker till since 43 | | Choice2Of2 msg -> BAD_REQUEST msg)) 44 | | Choice2Of2 msg -> BAD_REQUEST msg)) 45 | | Choice2Of2 msg -> BAD_REQUEST msg) 46 | 47 | let getPriceFromAzure = 48 | // from https://www.reddit.com/r/programming/comments/4ee3s7/getting_started_with_f/ 49 | let bind name nextStep = request (fun r -> 50 | match r.queryParam name with 51 | | Choice1Of2 value -> nextStep value 52 | | Choice2Of2 msg -> BAD_REQUEST msg) 53 | 54 | let experiments = dict[ 55 | "msft", ("9a21fffe27f34d6b90ab83a9a28af1f5", "9LqHbhrox5Bq05Eqwb4+0dg+7S9avXEsse03xUKhRBAc4Paz7I6KzB9/k9sXYnD1db61HzubiYuB4jp3IXd6ew=="); 56 | "msft(2015-11-04 to 2016-11-03)", ("08c4ee6a411145fca9973c2fd3a8fad6", "GHWmBkKaEXMVi5bifrYSi1h0BqIwD5ccopEm8E4RZqbTidwQAZVf1vQikDoITAR8LFVP9AH3ObxzN+0NpotXOw=="); 57 | "msft(2016-10-04 to 2016-11-03)", ("cdbf8d1e5f6d4aa396518a6bfe4bef52", "UbkxG8mVjv9Keyn2iKh+G2rzUGROrbfZuZ9dtQyTGH4Nrq/cZ10BUpm1OL348YsJJf9mLPJQXBi/JatUDiHEGw==")]; 58 | 59 | bind "date" <| fun date -> 60 | bind "experiment" <| fun experiment -> 61 | let service = fst (experiments.Item(experiment)) 62 | let token = snd (experiments.Item(experiment)) 63 | Http.RequestString(sprintf "https://ussouthcentral.services.azureml.net/workspaces/03f600fc9ca34cc9a692c2aeea610df0/services/%s/execute?api-version=2.0&format=swagger" service, 64 | headers = [ContentType HttpContentTypes.Json; Authorization (sprintf "Bearer %s" token) ], 65 | body = TextRequest (sprintf """ {"Inputs": { "input1": [{'Date': "%s"}]}, "GlobalParameters": {}} """ date)) |> JSON 66 | 67 | 68 | let setCORSHeaders = 69 | setHeader "Access-Control-Allow-Origin" "*" 70 | >=> setHeader "Access-Control-Allow-Headers" "content-type" 71 | 72 | let webPart = 73 | choose [ 74 | GET >=> setCORSHeaders >=> choose [ 75 | path "/" >=> Files.file "index.html" 76 | 77 | path "/GetPrice" >=> getPrice 78 | path "/GetPriceForDateRange" >=> getPriceForDateRangeRequest 79 | 80 | path "/GetPriceFromAzure" >=> getPriceFromAzure 81 | 82 | //static files 83 | pathRegex "(.*)\.(css|png|gif)" >=> Files.browseHome 84 | ] 85 | 86 | ] 87 | 88 | startWebServer defaultConfig webPart 89 | -------------------------------------------------------------------------------- /StockEstimator.WebApi/AssemblyInfo.fs: -------------------------------------------------------------------------------- 1 | namespace StockEstimator.Suave.AssemblyInfo 2 | 3 | open System.Reflection 4 | open System.Runtime.CompilerServices 5 | open System.Runtime.InteropServices 6 | 7 | // General Information about an assembly is controlled through the following 8 | // set of attributes. Change these attribute values to modify the information 9 | // associated with an assembly. 10 | [] 11 | [] 12 | [] 13 | [] 14 | [] 15 | [] 16 | [] 17 | [] 18 | 19 | // Setting ComVisible to false makes the types in this assembly not visible 20 | // to COM components. If you need to access a type in this assembly from 21 | // COM, set the ComVisible attribute to true on that type. 22 | [] 23 | 24 | // The following GUID is for the ID of the typelib if this project is exposed to COM 25 | [] 26 | 27 | // Version information for an assembly consists of the following four values: 28 | // 29 | // Major Version 30 | // Minor Version 31 | // Build Number 32 | // Revision 33 | // 34 | // You can specify all the values or you can default the Build and Revision Numbers 35 | // by using the '*' as shown below: 36 | // [] 37 | [] 38 | [] 39 | 40 | do 41 | () -------------------------------------------------------------------------------- /StockEstimator.WebApi/StockEstimator.WebApi.fsproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | 2.0 8 | ed9ac7a7-62e3-4bf8-97b9-e76cdd3d6eae 9 | Exe 10 | StockEstimator.Suave 11 | StockEstimator.Suave 12 | v4.5 13 | true 14 | 4.4.0.0 15 | StockEstimator.WebApi 16 | 17 | 18 | true 19 | full 20 | false 21 | false 22 | bin\Debug\ 23 | DEBUG;TRACE 24 | 3 25 | AnyCPU 26 | bin\Debug\StockEstimator.Suave.XML 27 | true 28 | 29 | 30 | pdbonly 31 | true 32 | true 33 | bin\Release\ 34 | TRACE 35 | 3 36 | AnyCPU 37 | bin\Release\StockEstimator.Suave.XML 38 | true 39 | 40 | 41 | 11 42 | 43 | 44 | 45 | 46 | $(MSBuildExtensionsPath32)\..\Microsoft SDKs\F#\3.0\Framework\v4.0\Microsoft.FSharp.Targets 47 | 48 | 49 | 50 | 51 | $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\FSharp\Microsoft.FSharp.Targets 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | PreserveNewest 63 | 64 | 65 | 66 | 67 | ..\packages\FSharp.Core.4.0.0.1\lib\net40\FSharp.Core.dll 68 | True 69 | 70 | 71 | ..\packages\FSharp.Data.2.3.2\lib\net40\FSharp.Data.dll 72 | True 73 | 74 | 75 | 76 | ..\packages\Newtonsoft.Json.8.0.2\lib\net45\Newtonsoft.Json.dll 77 | True 78 | 79 | 80 | ..\packages\Suave.1.1.1\lib\net40\Suave.dll 81 | True 82 | 83 | 84 | ..\packages\Suave.Experimental.1.1.1\lib\net40\Suave.Experimental.dll 85 | True 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | StockEstimator.Logic 95 | {f255f413-25da-44c6-92f2-6481cc8065eb} 96 | True 97 | 98 | 99 | 106 | -------------------------------------------------------------------------------- /StockEstimator.WebApi/StockEstimator.WebApi.v2.ncrunchproject: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jj09/StockEstimator/e03d0ad0d6eea5db26e9f58b5a949974f292e9b7/StockEstimator.WebApi/StockEstimator.WebApi.v2.ncrunchproject -------------------------------------------------------------------------------- /StockEstimator.WebApi/index.html: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | Stock Estimator 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 |

This is Stock Estimator Web API!

20 | 21 | 22 | 24 | 25 | 27 | 28 | -------------------------------------------------------------------------------- /StockEstimator.WebApi/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /StockEstimator.WebUI/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM microsoft/dotnet:1.1.1-sdk 2 | COPY . /app 3 | WORKDIR /app 4 | 5 | RUN ["dotnet", "restore"] 6 | RUN ["dotnet", "build"] 7 | 8 | EXPOSE 5000/tcp 9 | ENV ASPNETCORE_URLS http://*:5000 10 | 11 | ENTRYPOINT ["dotnet", "run"] -------------------------------------------------------------------------------- /StockEstimator.WebUI/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | using Microsoft.AspNetCore.Hosting; 7 | 8 | namespace StockEstimator.WebUI 9 | { 10 | public class Program 11 | { 12 | public static void Main(string[] args) 13 | { 14 | var host = new WebHostBuilder() 15 | .UseKestrel() 16 | .UseContentRoot(Directory.GetCurrentDirectory()) 17 | .UseIISIntegration() 18 | .UseStartup() 19 | .Build(); 20 | 21 | host.Run(); 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /StockEstimator.WebUI/Startup.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using Microsoft.AspNetCore.Builder; 6 | using Microsoft.AspNetCore.Hosting; 7 | using Microsoft.AspNetCore.Http; 8 | using Microsoft.Extensions.DependencyInjection; 9 | using Microsoft.Extensions.Logging; 10 | 11 | namespace StockEstimator.WebUI 12 | { 13 | public class Startup 14 | { 15 | // This method gets called by the runtime. Use this method to add services to the container. 16 | // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940 17 | public void ConfigureServices(IServiceCollection services) 18 | { 19 | } 20 | 21 | // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. 22 | public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) 23 | { 24 | app.UseDefaultFiles(); 25 | app.UseStaticFiles(); 26 | app.UseFileServer(); 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /StockEstimator.WebUI/StockEstimator.WebUI.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp1.1 5 | 6 | 7 | 1.0.3 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /StockEstimator.WebUI/app.yaml: -------------------------------------------------------------------------------- 1 | # this is for gcloud deployment 2 | runtime: aspnetcore 3 | env: flex -------------------------------------------------------------------------------- /StockEstimator.WebUI/gcloud-deploy.sh: -------------------------------------------------------------------------------- 1 | dotnet restore 2 | cd wwwroot 3 | npm i 4 | cd .. 5 | dotnet publish -c Release 6 | gcloud beta app deploy bin/Release/netcoreapp1.1/publish/app.yaml -------------------------------------------------------------------------------- /StockEstimator.WebUI/wwwroot/.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig is awesome: http://EditorConfig.org 2 | 3 | # top-most EditorConfig file 4 | root = true 5 | 6 | # Unix-style newlines with a newline ending every file 7 | [*] 8 | end_of_line = lf 9 | insert_final_newline = true 10 | # 2 space indentation 11 | indent_style = space 12 | indent_size = 2 13 | -------------------------------------------------------------------------------- /StockEstimator.WebUI/wwwroot/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | // Place your settings in this file to overwrite default and user settings. 2 | { 3 | "typescript.tsdk": "node_modules/typescript/lib", 4 | "html.suggest.angular1": false, 5 | "html.suggest.ionic": false 6 | } 7 | -------------------------------------------------------------------------------- /StockEstimator.WebUI/wwwroot/aurelia_project/aurelia.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "stock-estimator", 3 | "type": "project:application", 4 | "platform": { 5 | "id": "web", 6 | "displayName": "Web", 7 | "output": "scripts", 8 | "index": "index.html", 9 | "baseDir": "." 10 | }, 11 | "transpiler": { 12 | "id": "typescript", 13 | "displayName": "TypeScript", 14 | "fileExtension": ".ts", 15 | "dtsSource": [ 16 | "./custom_typings/**/*.d.ts" 17 | ], 18 | "source": "src/**/*.ts" 19 | }, 20 | "markupProcessor": { 21 | "id": "minimum", 22 | "displayName": "Minimal Minification", 23 | "fileExtension": ".html", 24 | "source": "src/**/*.html" 25 | }, 26 | "cssProcessor": { 27 | "id": "none", 28 | "displayName": "None", 29 | "fileExtension": ".css", 30 | "source": "src/**/*.css" 31 | }, 32 | "editor": { 33 | "id": "vscode", 34 | "displayName": "Visual Studio Code" 35 | }, 36 | "unitTestRunner": { 37 | "id": "karma", 38 | "displayName": "Karma", 39 | "source": "test/unit/**/*.ts" 40 | }, 41 | "paths": { 42 | "root": "src", 43 | "resources": "resources", 44 | "elements": "resources/elements", 45 | "attributes": "resources/attributes", 46 | "valueConverters": "resources/value-converters", 47 | "bindingBehaviors": "resources/binding-behaviors" 48 | }, 49 | "testFramework": { 50 | "id": "jasmine", 51 | "displayName": "Jasmine" 52 | }, 53 | "build": { 54 | "targets": [ 55 | { 56 | "id": "web", 57 | "displayName": "Web", 58 | "output": "scripts", 59 | "index": "index.html", 60 | "baseDir": "." 61 | } 62 | ], 63 | "loader": { 64 | "type": "require", 65 | "configTarget": "vendor-bundle.js", 66 | "includeBundleMetadataInConfig": "auto", 67 | "plugins": [ 68 | { 69 | "name": "text", 70 | "extensions": [ 71 | ".html", 72 | ".css" 73 | ], 74 | "stub": true 75 | } 76 | ] 77 | }, 78 | "options": { 79 | "minify": "stage & prod", 80 | "sourcemaps": "dev & stage" 81 | }, 82 | "bundles": [ 83 | { 84 | "name": "app-bundle.js", 85 | "source": [ 86 | "[**/*.js]", 87 | "**/*.{css,html}" 88 | ] 89 | }, 90 | { 91 | "name": "vendor-bundle.js", 92 | "prepend": [ 93 | "node_modules/bluebird/js/browser/bluebird.core.js", 94 | "node_modules/aurelia-cli/lib/resources/scripts/configure-bluebird.js", 95 | "node_modules/requirejs/require.js" 96 | ], 97 | "dependencies": [ 98 | "aurelia-binding", 99 | "aurelia-bootstrapper", 100 | "aurelia-dependency-injection", 101 | "aurelia-event-aggregator", 102 | "aurelia-framework", 103 | "aurelia-history", 104 | "aurelia-history-browser", 105 | "aurelia-loader", 106 | "aurelia-loader-default", 107 | "aurelia-logging", 108 | "aurelia-logging-console", 109 | "aurelia-metadata", 110 | "aurelia-pal", 111 | "aurelia-pal-browser", 112 | "aurelia-path", 113 | "aurelia-polyfills", 114 | "aurelia-route-recognizer", 115 | "aurelia-router", 116 | "aurelia-task-queue", 117 | "aurelia-templating", 118 | "aurelia-templating-binding", 119 | "text", 120 | { 121 | "name": "aurelia-templating-resources", 122 | "path": "../node_modules/aurelia-templating-resources/dist/amd", 123 | "main": "aurelia-templating-resources" 124 | }, 125 | { 126 | "name": "aurelia-templating-router", 127 | "path": "../node_modules/aurelia-templating-router/dist/amd", 128 | "main": "aurelia-templating-router" 129 | }, 130 | { 131 | "name": "aurelia-testing", 132 | "path": "../node_modules/aurelia-testing/dist/amd", 133 | "main": "aurelia-testing", 134 | "env": "dev" 135 | }, 136 | { 137 | "name": "aurelia-fetch-client", 138 | "main": "aurelia-fetch-client", 139 | "path": "../node_modules/aurelia-fetch-client/dist/amd" 140 | }, 141 | "jquery", 142 | { 143 | "name": "d3", 144 | "main": "d3", 145 | "path": "../node_modules/d3", 146 | "resources": [] 147 | }, 148 | { 149 | "name": "jquery-ui-dist", 150 | "path": "../node_modules/jquery-ui-dist", 151 | "main": "jquery-ui", 152 | "deps": ["jquery"], 153 | "resources": [ 154 | "jquery-ui.css" 155 | ] 156 | } 157 | ] 158 | } 159 | ] 160 | } 161 | } -------------------------------------------------------------------------------- /StockEstimator.WebUI/wwwroot/aurelia_project/environments/dev.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | debug: true, 3 | testing: true 4 | }; 5 | -------------------------------------------------------------------------------- /StockEstimator.WebUI/wwwroot/aurelia_project/environments/prod.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | debug: false, 3 | testing: false 4 | }; 5 | -------------------------------------------------------------------------------- /StockEstimator.WebUI/wwwroot/aurelia_project/environments/stage.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | debug: true, 3 | testing: false 4 | }; 5 | -------------------------------------------------------------------------------- /StockEstimator.WebUI/wwwroot/aurelia_project/generators/attribute.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "attribute", 3 | "description": "Creates a custom attribute class and places it in the project resources." 4 | } 5 | -------------------------------------------------------------------------------- /StockEstimator.WebUI/wwwroot/aurelia_project/generators/attribute.ts: -------------------------------------------------------------------------------- 1 | import {inject} from 'aurelia-dependency-injection'; 2 | import {Project, ProjectItem, CLIOptions, UI} from 'aurelia-cli'; 3 | 4 | @inject(Project, CLIOptions, UI) 5 | export default class AttributeGenerator { 6 | constructor(private project: Project, private options: CLIOptions, private ui: UI) { } 7 | 8 | execute() { 9 | return this.ui 10 | .ensureAnswer(this.options.args[0], 'What would you like to call the custom attribute?') 11 | .then(name => { 12 | let fileName = this.project.makeFileName(name); 13 | let className = this.project.makeClassName(name); 14 | 15 | this.project.attributes.add( 16 | ProjectItem.text(`${fileName}.ts`, this.generateSource(className)) 17 | ); 18 | 19 | return this.project.commitChanges() 20 | .then(() => this.ui.log(`Created ${fileName}.`)); 21 | }); 22 | } 23 | 24 | generateSource(className) { 25 | return `import {autoinject} from 'aurelia-framework'; 26 | 27 | @autoinject() 28 | export class ${className}CustomAttribute { 29 | constructor(private element: Element) { } 30 | 31 | valueChanged(newValue, oldValue) { 32 | 33 | } 34 | } 35 | 36 | ` 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /StockEstimator.WebUI/wwwroot/aurelia_project/generators/binding-behavior.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "binding-behavior", 3 | "description": "Creates a binding behavior class and places it in the project resources." 4 | } 5 | -------------------------------------------------------------------------------- /StockEstimator.WebUI/wwwroot/aurelia_project/generators/binding-behavior.ts: -------------------------------------------------------------------------------- 1 | import {inject} from 'aurelia-dependency-injection'; 2 | import {Project, ProjectItem, CLIOptions, UI} from 'aurelia-cli'; 3 | 4 | @inject(Project, CLIOptions, UI) 5 | export default class BindingBehaviorGenerator { 6 | constructor(private project: Project, private options: CLIOptions, private ui: UI) { } 7 | 8 | execute() { 9 | return this.ui 10 | .ensureAnswer(this.options.args[0], 'What would you like to call the binding behavior?') 11 | .then(name => { 12 | let fileName = this.project.makeFileName(name); 13 | let className = this.project.makeClassName(name); 14 | 15 | this.project.bindingBehaviors.add( 16 | ProjectItem.text(`${fileName}.ts`, this.generateSource(className)) 17 | ); 18 | 19 | return this.project.commitChanges() 20 | .then(() => this.ui.log(`Created ${fileName}.`)); 21 | }); 22 | } 23 | 24 | generateSource(className) { 25 | return `export class ${className}BindingBehavior { 26 | bind(binding, source) { 27 | 28 | } 29 | 30 | unbind(binding, source) { 31 | 32 | } 33 | } 34 | 35 | ` 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /StockEstimator.WebUI/wwwroot/aurelia_project/generators/component.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "component", 3 | "description": "Creates a custom component class and template (view model and view), placing them in the project source folder (or optionally in sub folders)." 4 | } -------------------------------------------------------------------------------- /StockEstimator.WebUI/wwwroot/aurelia_project/generators/component.ts: -------------------------------------------------------------------------------- 1 | import { inject } from 'aurelia-dependency-injection'; 2 | import { Project, ProjectItem, CLIOptions, UI } from 'aurelia-cli'; 3 | 4 | var path = require('path'); 5 | 6 | @inject(Project, CLIOptions, UI) 7 | export default class ElementGenerator { 8 | constructor(private project: Project, private options: CLIOptions, private ui: UI) { } 9 | 10 | execute() { 11 | let self = this; 12 | 13 | return this.ui 14 | .ensureAnswer(this.options.args[0], 'What would you like to call the component?') 15 | .then(name => { 16 | 17 | return self.ui.ensureAnswer(this.options.args[1], 'What sub-folder would you like to add it to?\nIf it doesn\'t exist it will be created for you.\n\nDefault folder is the source folder (src).', ".") 18 | .then(subFolders => { 19 | 20 | let fileName = this.project.makeFileName(name); 21 | let className = this.project.makeClassName(name); 22 | 23 | self.project.root.add( 24 | ProjectItem.text(path.join(subFolders, fileName + ".ts"), this.generateJSSource(className)), 25 | ProjectItem.text(path.join(subFolders, fileName + ".html"), this.generateHTMLSource(className)) 26 | ); 27 | 28 | return this.project.commitChanges() 29 | .then(() => this.ui.log(`Created ${name} in the '${path.join(self.project.root.name, subFolders)}' folder`)); 30 | }); 31 | }); 32 | } 33 | 34 | generateJSSource(className) { 35 | return `export class ${className} { 36 | message: string; 37 | 38 | constructor() { 39 | this.message = 'Hello world'; 40 | } 41 | }` 42 | } 43 | 44 | generateHTMLSource(className) { 45 | return `` 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /StockEstimator.WebUI/wwwroot/aurelia_project/generators/element.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "element", 3 | "description": "Creates a custom element class and template, placing them in the project resources." 4 | } 5 | -------------------------------------------------------------------------------- /StockEstimator.WebUI/wwwroot/aurelia_project/generators/element.ts: -------------------------------------------------------------------------------- 1 | import {inject} from 'aurelia-dependency-injection'; 2 | import {Project, ProjectItem, CLIOptions, UI} from 'aurelia-cli'; 3 | 4 | @inject(Project, CLIOptions, UI) 5 | export default class ElementGenerator { 6 | constructor(private project: Project, private options: CLIOptions, private ui: UI) { } 7 | 8 | execute() { 9 | return this.ui 10 | .ensureAnswer(this.options.args[0], 'What would you like to call the custom element?') 11 | .then(name => { 12 | let fileName = this.project.makeFileName(name); 13 | let className = this.project.makeClassName(name); 14 | 15 | this.project.elements.add( 16 | ProjectItem.text(`${fileName}.ts`, this.generateJSSource(className)), 17 | ProjectItem.text(`${fileName}.html`, this.generateHTMLSource(className)) 18 | ); 19 | 20 | return this.project.commitChanges() 21 | .then(() => this.ui.log(`Created ${fileName}.`)); 22 | }); 23 | } 24 | 25 | generateJSSource(className) { 26 | return `import {bindable} from 'aurelia-framework'; 27 | 28 | export class ${className} { 29 | @bindable value; 30 | 31 | valueChanged(newValue, oldValue) { 32 | 33 | } 34 | } 35 | 36 | ` 37 | } 38 | 39 | generateHTMLSource(className) { 40 | return `` 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /StockEstimator.WebUI/wwwroot/aurelia_project/generators/generator.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "generator", 3 | "description": "Creates a generator class and places it in the project generators folder." 4 | } 5 | -------------------------------------------------------------------------------- /StockEstimator.WebUI/wwwroot/aurelia_project/generators/generator.ts: -------------------------------------------------------------------------------- 1 | import {inject} from 'aurelia-dependency-injection'; 2 | import {Project, ProjectItem, CLIOptions, UI} from 'aurelia-cli'; 3 | 4 | @inject(Project, CLIOptions, UI) 5 | export default class GeneratorGenerator { 6 | constructor(private project: Project, private options: CLIOptions, private ui: UI) { } 7 | 8 | execute() { 9 | return this.ui 10 | .ensureAnswer(this.options.args[0], 'What would you like to call the generator?') 11 | .then(name => { 12 | let fileName = this.project.makeFileName(name); 13 | let className = this.project.makeClassName(name); 14 | 15 | this.project.generators.add( 16 | ProjectItem.text(`${fileName}.ts`, this.generateSource(className)) 17 | ); 18 | 19 | return this.project.commitChanges() 20 | .then(() => this.ui.log(`Created ${fileName}.`)); 21 | }); 22 | } 23 | 24 | generateSource(className) { 25 | return `import {autoinject} from 'aurelia-dependency-injection'; 26 | import {Project, ProjectItem, CLIOptions, UI} from 'aurelia-cli'; 27 | 28 | @autoinject() 29 | export default class ${className}Generator { 30 | constructor(private project: Project, private options: CLIOptions, private ui: UI) { } 31 | 32 | execute() { 33 | return this.ui 34 | .ensureAnswer(this.options.args[0], 'What would you like to call the new item?') 35 | .then(name => { 36 | let fileName = this.project.makeFileName(name); 37 | let className = this.project.makeClassName(name); 38 | 39 | this.project.elements.add( 40 | ProjectItem.text(\`\${fileName}.js\`, this.generateSource(className)) 41 | ); 42 | 43 | return this.project.commitChanges() 44 | .then(() => this.ui.log(\`Created \${fileName}.\`)); 45 | }); 46 | } 47 | 48 | generateSource(className) { 49 | return \`import {bindable} from 'aurelia-framework'; 50 | 51 | export class \${className} { 52 | @bindable value; 53 | 54 | valueChanged(newValue, oldValue) { 55 | 56 | } 57 | } 58 | 59 | \` 60 | } 61 | } 62 | 63 | ` 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /StockEstimator.WebUI/wwwroot/aurelia_project/generators/task.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "task", 3 | "description": "Creates a task and places it in the project tasks folder." 4 | } 5 | -------------------------------------------------------------------------------- /StockEstimator.WebUI/wwwroot/aurelia_project/generators/task.ts: -------------------------------------------------------------------------------- 1 | import {inject} from 'aurelia-dependency-injection'; 2 | import {Project, ProjectItem, CLIOptions, UI} from 'aurelia-cli'; 3 | 4 | @inject(Project, CLIOptions, UI) 5 | export default class TaskGenerator { 6 | constructor(private project: Project, private options: CLIOptions, private ui: UI) { } 7 | 8 | execute() { 9 | return this.ui 10 | .ensureAnswer(this.options.args[0], 'What would you like to call the task?') 11 | .then(name => { 12 | let fileName = this.project.makeFileName(name); 13 | let functionName = this.project.makeFunctionName(name); 14 | 15 | this.project.tasks.add( 16 | ProjectItem.text(`${fileName}.ts`, this.generateSource(functionName)) 17 | ); 18 | 19 | return this.project.commitChanges() 20 | .then(() => this.ui.log(`Created ${fileName}.`)); 21 | }); 22 | } 23 | 24 | generateSource(functionName) { 25 | return `import * as gulp from 'gulp'; 26 | import * as changed from 'gulp-changed'; 27 | import * as project from '../aurelia.json'; 28 | 29 | export default function ${functionName}() { 30 | return gulp.src(project.paths.???) 31 | .pipe(changed(project.paths.output, {extension: '.???'})) 32 | .pipe(gulp.dest(project.paths.output)); 33 | } 34 | 35 | ` 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /StockEstimator.WebUI/wwwroot/aurelia_project/generators/value-converter.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "value-converter", 3 | "description": "Creates a value converter class and places it in the project resources." 4 | } 5 | -------------------------------------------------------------------------------- /StockEstimator.WebUI/wwwroot/aurelia_project/generators/value-converter.ts: -------------------------------------------------------------------------------- 1 | import {inject} from 'aurelia-dependency-injection'; 2 | import {Project, ProjectItem, CLIOptions, UI} from 'aurelia-cli'; 3 | 4 | @inject(Project, CLIOptions, UI) 5 | export default class ValueConverterGenerator { 6 | constructor(private project: Project, private options: CLIOptions, private ui: UI) { } 7 | 8 | execute() { 9 | return this.ui 10 | .ensureAnswer(this.options.args[0], 'What would you like to call the value converter?') 11 | .then(name => { 12 | let fileName = this.project.makeFileName(name); 13 | let className = this.project.makeClassName(name); 14 | 15 | this.project.valueConverters.add( 16 | ProjectItem.text(`${fileName}.ts`, this.generateSource(className)) 17 | ); 18 | 19 | return this.project.commitChanges() 20 | .then(() => this.ui.log(`Created ${fileName}.`)); 21 | }); 22 | } 23 | 24 | generateSource(className) { 25 | return `export class ${className}ValueConverter { 26 | toView(value) { 27 | 28 | } 29 | 30 | fromView(value) { 31 | 32 | } 33 | } 34 | 35 | ` 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /StockEstimator.WebUI/wwwroot/aurelia_project/tasks/build.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "build", 3 | "description": "Builds and processes all application assets.", 4 | "flags": [ 5 | { 6 | "name": "env", 7 | "description": "Sets the build environment.", 8 | "type": "string" 9 | } 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /StockEstimator.WebUI/wwwroot/aurelia_project/tasks/build.ts: -------------------------------------------------------------------------------- 1 | import * as gulp from 'gulp'; 2 | import transpile from './transpile'; 3 | import processMarkup from './process-markup'; 4 | import processCSS from './process-css'; 5 | import copyFiles from './copy-files'; 6 | import {build} from 'aurelia-cli'; 7 | import * as project from '../aurelia.json'; 8 | 9 | export default gulp.series( 10 | readProjectConfiguration, 11 | gulp.parallel( 12 | transpile, 13 | processMarkup, 14 | processCSS, 15 | copyFiles 16 | ), 17 | writeBundles 18 | ); 19 | 20 | function readProjectConfiguration() { 21 | return build.src(project); 22 | } 23 | 24 | function writeBundles() { 25 | return build.dest(); 26 | } 27 | -------------------------------------------------------------------------------- /StockEstimator.WebUI/wwwroot/aurelia_project/tasks/copy-files.ts: -------------------------------------------------------------------------------- 1 | import * as gulp from 'gulp'; 2 | import * as path from 'path'; 3 | import * as minimatch from 'minimatch'; 4 | import * as changedInPlace from 'gulp-changed-in-place'; 5 | import * as project from '../aurelia.json'; 6 | 7 | export default function copyFiles(done) { 8 | if (typeof project.build.copyFiles !== 'object') { 9 | done(); 10 | return; 11 | } 12 | 13 | const instruction = getNormalizedInstruction(); 14 | const files = Object.keys(instruction); 15 | 16 | return gulp.src(files) 17 | .pipe(changedInPlace({ firstPass: true })) 18 | .pipe(gulp.dest(x => { 19 | const filePath = prepareFilePath(x.path); 20 | const key = files.find(f => minimatch(filePath, f)); 21 | return instruction[key]; 22 | })); 23 | } 24 | 25 | function getNormalizedInstruction() { 26 | const files = project.build.copyFiles; 27 | let normalizedInstruction = {}; 28 | 29 | for (let key in files) { 30 | normalizedInstruction[path.posix.normalize(key)] = files[key]; 31 | } 32 | 33 | return normalizedInstruction; 34 | } 35 | 36 | function prepareFilePath(filePath) { 37 | let preparedPath = filePath.replace(process.cwd(), '').substring(1); 38 | 39 | //if we are running on windows we have to fix the path 40 | if (/^win/.test(process.platform)) { 41 | preparedPath = preparedPath.replace(/\\/g, '/'); 42 | } 43 | 44 | return preparedPath; 45 | } 46 | -------------------------------------------------------------------------------- /StockEstimator.WebUI/wwwroot/aurelia_project/tasks/process-css.ts: -------------------------------------------------------------------------------- 1 | import * as gulp from 'gulp'; 2 | import * as changedInPlace from 'gulp-changed-in-place'; 3 | import * as project from '../aurelia.json'; 4 | import {build} from 'aurelia-cli'; 5 | 6 | export default function processCSS() { 7 | return gulp.src(project.cssProcessor.source) 8 | .pipe(changedInPlace({firstPass:true})) 9 | .pipe(build.bundle()); 10 | }; 11 | -------------------------------------------------------------------------------- /StockEstimator.WebUI/wwwroot/aurelia_project/tasks/process-markup.ts: -------------------------------------------------------------------------------- 1 | import * as gulp from 'gulp'; 2 | import * as htmlmin from 'gulp-htmlmin'; 3 | import * as changedInPlace from 'gulp-changed-in-place'; 4 | import * as project from '../aurelia.json'; 5 | import {build} from 'aurelia-cli'; 6 | 7 | export default function processMarkup() { 8 | return gulp.src(project.markupProcessor.source) 9 | .pipe(changedInPlace({firstPass:true})) 10 | .pipe(htmlmin({ 11 | removeComments: true, 12 | collapseWhitespace: true, 13 | minifyCSS: true, 14 | minifyJS: true 15 | })) 16 | .pipe(build.bundle()); 17 | } -------------------------------------------------------------------------------- /StockEstimator.WebUI/wwwroot/aurelia_project/tasks/run.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "run", 3 | "description": "Builds the application and serves up the assets via a local web server, watching files for changes as you work.", 4 | "flags": [ 5 | { 6 | "name": "env", 7 | "description": "Sets the build environment.", 8 | "type": "string" 9 | }, 10 | { 11 | "name": "watch", 12 | "description": "Watches source files for changes and refreshes the app automatically.", 13 | "type": "boolean" 14 | } 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /StockEstimator.WebUI/wwwroot/aurelia_project/tasks/run.ts: -------------------------------------------------------------------------------- 1 | import * as gulp from 'gulp'; 2 | import * as browserSync from 'browser-sync'; 3 | import * as historyApiFallback from 'connect-history-api-fallback/lib'; 4 | import * as project from '../aurelia.json'; 5 | import build from './build'; 6 | import {CLIOptions} from 'aurelia-cli'; 7 | 8 | function onChange(path) { 9 | console.log(`File Changed: ${path}`); 10 | } 11 | 12 | function reload(done) { 13 | browserSync.reload(); 14 | done(); 15 | } 16 | 17 | let serve = gulp.series( 18 | build, 19 | done => { 20 | browserSync({ 21 | online: false, 22 | open: false, 23 | port: 9000, 24 | logLevel: 'silent', 25 | server: { 26 | baseDir: [project.platform.baseDir], 27 | middleware: [historyApiFallback(), function(req, res, next) { 28 | res.setHeader('Access-Control-Allow-Origin', '*'); 29 | next(); 30 | }] 31 | } 32 | }, function (err, bs) { 33 | if (err) return done(err); 34 | let urls = bs.options.get('urls').toJS(); 35 | console.log(`Application Available At: ${urls.local}`); 36 | console.log(`BrowserSync Available At: ${urls.ui}`); 37 | done(); 38 | }); 39 | } 40 | ); 41 | 42 | let refresh = gulp.series( 43 | build, 44 | reload 45 | ); 46 | 47 | let watch = function(refreshCb, onChangeCb) { 48 | return function(done) { 49 | gulp.watch(project.transpiler.source, refreshCb).on('change', onChangeCb); 50 | gulp.watch(project.markupProcessor.source, refreshCb).on('change', onChangeCb); 51 | gulp.watch(project.cssProcessor.source, refreshCb).on('change', onChangeCb); 52 | 53 | //see if there are static files to be watched 54 | if (typeof project.build.copyFiles === 'object') { 55 | const files = Object.keys(project.build.copyFiles); 56 | gulp.watch(files, refreshCb).on('change', onChangeCb); 57 | } 58 | }; 59 | }; 60 | 61 | let run; 62 | 63 | if (CLIOptions.hasFlag('watch')) { 64 | run = gulp.series( 65 | serve, 66 | watch(refresh, onChange) 67 | ); 68 | } else { 69 | run = serve; 70 | } 71 | 72 | export { run as default, watch }; 73 | 74 | -------------------------------------------------------------------------------- /StockEstimator.WebUI/wwwroot/aurelia_project/tasks/test.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "test", 3 | "description": "Runs all unit tests and reports the results.", 4 | "flags": [ 5 | { 6 | "name": "env", 7 | "description": "Sets the build environment.", 8 | "type": "string" 9 | }, 10 | { 11 | "name": "watch", 12 | "description": "Watches test files for changes and re-runs the tests automatically.", 13 | "type": "boolean" 14 | } 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /StockEstimator.WebUI/wwwroot/aurelia_project/tasks/test.ts: -------------------------------------------------------------------------------- 1 | import * as gulp from 'gulp'; 2 | import {Server as Karma} from 'karma'; 3 | import {CLIOptions} from 'aurelia-cli'; 4 | import build from './build'; 5 | import {watch} from './run'; 6 | import * as path from 'path'; 7 | 8 | function log(message) { 9 | console.log(message); //eslint-disable-line no-console 10 | } 11 | 12 | function onChange(path) { 13 | log(`File Changed: ${path}`); 14 | } 15 | 16 | let karma = done => { 17 | new Karma({ 18 | configFile: path.join(__dirname, '/../../karma.conf.js'), 19 | singleRun: !CLIOptions.hasFlag('watch') 20 | }, done).start(); 21 | }; 22 | 23 | let unit; 24 | 25 | if (CLIOptions.hasFlag('watch')) { 26 | unit = gulp.series( 27 | build, 28 | gulp.parallel( 29 | watch(build, onChange), 30 | karma 31 | ) 32 | ); 33 | } else { 34 | unit = gulp.series( 35 | build, 36 | karma 37 | ); 38 | } 39 | 40 | export default unit; 41 | -------------------------------------------------------------------------------- /StockEstimator.WebUI/wwwroot/aurelia_project/tasks/transpile.ts: -------------------------------------------------------------------------------- 1 | import * as gulp from 'gulp'; 2 | import * as changedInPlace from 'gulp-changed-in-place'; 3 | import * as plumber from 'gulp-plumber'; 4 | import * as sourcemaps from 'gulp-sourcemaps'; 5 | import * as notify from 'gulp-notify'; 6 | import * as rename from 'gulp-rename'; 7 | import * as ts from 'gulp-typescript'; 8 | import * as project from '../aurelia.json'; 9 | import {CLIOptions, build} from 'aurelia-cli'; 10 | import * as eventStream from 'event-stream'; 11 | 12 | function configureEnvironment() { 13 | let env = CLIOptions.getEnvironment(); 14 | 15 | return gulp.src(`aurelia_project/environments/${env}.ts`) 16 | .pipe(changedInPlace({firstPass:true})) 17 | .pipe(rename('environment.ts')) 18 | .pipe(gulp.dest(project.paths.root)); 19 | } 20 | 21 | var typescriptCompiler = typescriptCompiler || null; 22 | 23 | function buildTypeScript() { 24 | typescriptCompiler = ts.createProject('tsconfig.json', { 25 | "typescript": require('typescript') 26 | }); 27 | 28 | let dts = gulp.src(project.transpiler.dtsSource); 29 | 30 | let src = gulp.src(project.transpiler.source) 31 | .pipe(changedInPlace({firstPass: true})); 32 | 33 | return eventStream.merge(dts, src) 34 | .pipe(plumber({ errorHandler: notify.onError('Error: <%= error.message %>') })) 35 | .pipe(sourcemaps.init()) 36 | .pipe(typescriptCompiler()) 37 | .pipe(sourcemaps.write({ sourceRoot: 'src' })) 38 | .pipe(build.bundle()); 39 | } 40 | 41 | export default gulp.series( 42 | configureEnvironment, 43 | buildTypeScript 44 | ); 45 | -------------------------------------------------------------------------------- /StockEstimator.WebUI/wwwroot/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jj09/StockEstimator/e03d0ad0d6eea5db26e9f58b5a949974f292e9b7/StockEstimator.WebUI/wwwroot/favicon.ico -------------------------------------------------------------------------------- /StockEstimator.WebUI/wwwroot/images/aurelia.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jj09/StockEstimator/e03d0ad0d6eea5db26e9f58b5a949974f292e9b7/StockEstimator.WebUI/wwwroot/images/aurelia.ico -------------------------------------------------------------------------------- /StockEstimator.WebUI/wwwroot/images/aurelia.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | logo 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 145 | 146 | 147 | 148 | -------------------------------------------------------------------------------- /StockEstimator.WebUI/wwwroot/images/stock.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jj09/StockEstimator/e03d0ad0d6eea5db26e9f58b5a949974f292e9b7/StockEstimator.WebUI/wwwroot/images/stock.ico -------------------------------------------------------------------------------- /StockEstimator.WebUI/wwwroot/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Aurelia 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /StockEstimator.WebUI/wwwroot/karma.conf.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const path = require('path'); 3 | const project = require('./aurelia_project/aurelia.json'); 4 | const tsconfig = require('./tsconfig.json'); 5 | 6 | let testSrc = [ 7 | { pattern: project.unitTestRunner.source, included: false }, 8 | 'test/aurelia-karma.js' 9 | ]; 10 | 11 | let output = project.platform.output; 12 | let appSrc = project.build.bundles.map(x => path.join(output, x.name)); 13 | let entryIndex = appSrc.indexOf(path.join(output, project.build.loader.configTarget)); 14 | let entryBundle = appSrc.splice(entryIndex, 1)[0]; 15 | let files = [entryBundle].concat(testSrc).concat(appSrc); 16 | 17 | module.exports = function(config) { 18 | config.set({ 19 | basePath: '', 20 | frameworks: [project.testFramework.id], 21 | files: files, 22 | exclude: [], 23 | preprocessors: { 24 | [project.unitTestRunner.source]: [project.transpiler.id] 25 | }, 26 | typescriptPreprocessor: { 27 | typescript: require('typescript'), 28 | options: tsconfig.compilerOptions 29 | }, 30 | reporters: ['progress'], 31 | port: 9876, 32 | colors: true, 33 | logLevel: config.LOG_INFO, 34 | autoWatch: true, 35 | browsers: ['Chrome'], 36 | singleRun: false, 37 | // client.args must be a array of string. 38 | // Leave 'aurelia-root', project.paths.root in this order so we can find 39 | // the root of the aurelia project. 40 | client: { 41 | args: ['aurelia-root', project.paths.root] 42 | } 43 | }); 44 | }; 45 | -------------------------------------------------------------------------------- /StockEstimator.WebUI/wwwroot/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "stock-estimator", 3 | "description": "Stock Estimator - Predicting Future Stock Prices.", 4 | "version": "0.1.0", 5 | "repository": { 6 | "type": "git", 7 | "url": "http://github.com/jj09/StockEstimator" 8 | }, 9 | "scripts": { 10 | "postinstall": "au build" 11 | }, 12 | "license": "MIT", 13 | "dependencies": { 14 | "aurelia-animator-css": "^1.0.1", 15 | "aurelia-bootstrapper": "^2.1.0", 16 | "aurelia-fetch-client": "^1.1.2", 17 | "bluebird": "^3.4.1", 18 | "d3": "^3.5.16", 19 | "jquery": "^3.2.1", 20 | "jquery-ui-dist": "^1.12.1", 21 | "requirejs": "^2.3.2", 22 | "text": "github:requirejs/text#latest" 23 | }, 24 | "peerDependencies": {}, 25 | "devDependencies": { 26 | "aurelia-cli": "^0.27.0", 27 | "aurelia-testing": "^1.0.0-beta.2.0.1", 28 | "aurelia-tools": "^1.0.0", 29 | "browser-sync": "^2.13.0", 30 | "connect-history-api-fallback": "^1.2.0", 31 | "gulp": "github:gulpjs/gulp#4.0", 32 | "gulp-changed-in-place": "^2.0.3", 33 | "gulp-plumber": "^1.1.0", 34 | "gulp-rename": "^1.2.2", 35 | "gulp-sourcemaps": "^2.0.0-alpha", 36 | "gulp-notify": "^2.2.0", 37 | "minimatch": "^3.0.2", 38 | "through2": "^2.0.1", 39 | "uglify-js": "^2.6.3", 40 | "vinyl-fs": "^2.4.3", 41 | "event-stream": "^3.3.3", 42 | "gulp-typescript": "^3.1.4", 43 | "gulp-tslint": "^5.0.0", 44 | "tslint": "^3.11.0", 45 | "typescript": ">=1.9.0-dev || ^2.0.0", 46 | "@types/node": "^6.0.45", 47 | "gulp-htmlmin": "^3.0.0", 48 | "html-minifier": "^3.2.3", 49 | "jasmine-core": "^2.4.1", 50 | "karma": "^0.13.22", 51 | "karma-chrome-launcher": "^1.0.1", 52 | "karma-jasmine": "^1.0.2", 53 | "karma-typescript-preprocessor": "^0.2.1", 54 | "@types/jasmine": "^2.2.0" 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /StockEstimator.WebUI/wwwroot/src/about.html: -------------------------------------------------------------------------------- 1 | 8 | -------------------------------------------------------------------------------- /StockEstimator.WebUI/wwwroot/src/about.ts: -------------------------------------------------------------------------------- 1 | export class Welcome { 2 | heading = 'About Stock Estimator'; 3 | } -------------------------------------------------------------------------------- /StockEstimator.WebUI/wwwroot/src/aml.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /StockEstimator.WebUI/wwwroot/src/aml.ts: -------------------------------------------------------------------------------- 1 | import {inject} from 'aurelia-framework'; 2 | import {HttpClient, json} from 'aurelia-fetch-client'; 3 | import * as $ from 'jquery'; 4 | 5 | @inject(HttpClient) 6 | export class Aml { 7 | till: any; 8 | http: HttpClient; 9 | estimateFor = this.daysFromNow(7); //'2016-12-12'; 10 | estimatedPrice = '?'; 11 | experiment = 'msft'; 12 | 13 | experiments = ['msft', 'msft(2015-11-04 to 2016-11-03)', 'msft(2016-10-04 to 2016-11-03)']; 14 | 15 | constructor(http) { 16 | http.configure(config => { 17 | config 18 | .useStandardConfiguration() 19 | .withBaseUrl('http://stockestimator.westus.cloudapp.azure.com/'); 20 | // .withBaseUrl('http://localhost:8083/'); 21 | }); 22 | 23 | this.http = http; 24 | } 25 | 26 | getPrice() { 27 | let body = { 28 | Inputs: { 29 | input1: [{Date: this.estimateFor}] 30 | } 31 | }; 32 | 33 | this.http.fetch(`GetPriceFromAzure?date=${this.estimateFor}&experiment=${this.experiment}`) 34 | .then(response => response.json()) 35 | .then((data: any) => { 36 | this.estimatedPrice = JSON.parse(data).Results.output1[0]['Scored Labels']; 37 | }); 38 | } 39 | 40 | attached() { 41 | $("#estimate-for").datepicker({ 42 | onSelect: (selected, evt) => { 43 | this.till = selected.toString(); 44 | } 45 | }); 46 | } 47 | 48 | daysFromNow(days) { 49 | return new Date(Date.now()+days*24*60*60*1000).toISOString().substring(0,10); 50 | } 51 | } 52 | 53 | export class CurrencyValueConverter { 54 | toView(value) { 55 | if (value === '?') { 56 | return value; 57 | } 58 | 59 | return value && '$' + (Math.round(value * 100) / 100).toFixed(2); 60 | } 61 | } -------------------------------------------------------------------------------- /StockEstimator.WebUI/wwwroot/src/app.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /StockEstimator.WebUI/wwwroot/src/app.ts: -------------------------------------------------------------------------------- 1 | export class App { 2 | router: any; 3 | 4 | configureRouter(config, router) { 5 | config.title = 'Stock Estimator'; 6 | config.map([ 7 | { route: ['', 'home'], name: 'home', moduleId: 'home', nav: true, title: 'Home' }, 8 | { route: 'aml', name: 'aml', moduleId: 'aml', nav: true, title: 'Azure Machine Learning' }, 9 | { route: 'about', name: 'about', moduleId: 'about', nav: true, title: 'About' }, 10 | ]); 11 | 12 | this.router = router; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /StockEstimator.WebUI/wwwroot/src/environment.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | debug: true, 3 | testing: true 4 | }; 5 | -------------------------------------------------------------------------------- /StockEstimator.WebUI/wwwroot/src/home.html: -------------------------------------------------------------------------------- 1 | 44 | -------------------------------------------------------------------------------- /StockEstimator.WebUI/wwwroot/src/home.ts: -------------------------------------------------------------------------------- 1 | import {inject} from 'aurelia-framework'; 2 | import {HttpClient} from 'aurelia-fetch-client'; 3 | import {bindable} from 'aurelia-framework'; 4 | import * as d3 from'd3'; 5 | import * as $ from 'jquery'; 6 | import 'jquery-ui-dist'; 7 | 8 | @inject(HttpClient) 9 | export class Home { 10 | estimatedPrice: number; 11 | prices: any; 12 | http: HttpClient; 13 | heading = 'Estimating future stock prices'; 14 | baseUrl = 'http://localhost:8083'; 15 | 16 | @bindable ticker = "msft"; 17 | @bindable till = this.daysFromNow(7); 18 | @bindable since = "2014-09-09"; 19 | tickers = ["msft", "googl", "amzn", "aapl"]; 20 | 21 | constructor(http: HttpClient) { 22 | this.http = http; 23 | 24 | this.isApiAvailable('http://localhost:8083') 25 | .then(isAvailable => { 26 | if (isAvailable) { 27 | throw new Error('available api found'); 28 | } 29 | 30 | this.isApiAvailable('http://stockestimator.westus.cloudapp.azure.com') 31 | .then(isAvailable => { 32 | if (isAvailable) { 33 | this.baseUrl = 'http://stockestimator.westus.cloudapp.azure.com'; 34 | throw new Error('available api found'); 35 | } 36 | 37 | this.isApiAvailable('http://104.196.239.135') 38 | .then(isAvailable => { 39 | if (isAvailable) { 40 | this.baseUrl = 'http://104.196.239.135'; 41 | throw new Error('available api found'); 42 | } 43 | }) 44 | .catch(() => this.configureHttp()) 45 | }) 46 | .catch(() => this.configureHttp()) 47 | }) 48 | .catch(() => this.configureHttp()); 49 | } 50 | 51 | configureHttp() { 52 | if (this.baseUrl[this.baseUrl.length-1] !== '/') { 53 | this.baseUrl += '/'; 54 | } 55 | 56 | this.http.configure(config => { 57 | config 58 | .useStandardConfiguration() 59 | .withBaseUrl(this.baseUrl); 60 | }); 61 | 62 | this.getData(); 63 | } 64 | 65 | isApiAvailable(url) { 66 | return this.http.fetch(url) 67 | .then(result => { 68 | console.info('API url:', url); 69 | return true; 70 | }) 71 | .catch(err => { 72 | console.error('API not available:', url); 73 | return false; 74 | }); 75 | } 76 | 77 | propertyChanged(propertyName, newValue, oldValue) { 78 | // for now - do not refresh with every prop changed, rather wait for 'Get Data' to be clicked 79 | if (["ticker", "till", "since"].indexOf(propertyName) !== -1) { 80 | //this.getData(); 81 | } 82 | } 83 | 84 | getData() { 85 | console.log('getting future', this.ticker, 'prices till', this.till, 'based on data since', this.since); 86 | this.isLoading(true); 87 | return this.http.fetch(`GetPriceForDateRange?ticker=${this.ticker}&since=${this.since}&till=${this.till}`) 88 | .then(response => { 89 | return response.json(); 90 | }) 91 | .then((prices: any) => { 92 | this.prices = prices; 93 | this.estimatedPrice = prices.length > 0 ? prices[prices.length - 1].item2 : "?"; 94 | }) 95 | .then(() => { 96 | this.drawChart(); 97 | this.isLoading(false); 98 | console.log('done'); 99 | }); 100 | } 101 | 102 | isLoading(isLoading) { 103 | $('#getDataButton').prop('disabled', isLoading); 104 | 105 | let $formControls = $('.form-control'); 106 | 107 | if (isLoading) { 108 | $formControls.attr('disabled', 'disabled'); 109 | } else { 110 | $formControls.removeAttr('disabled'); 111 | } 112 | 113 | } 114 | 115 | activate() { 116 | // run before view rendering 117 | } 118 | 119 | // the component is attached to the DOM (in document). 120 | // If the view-model has an attached callback, it will be invoked at this time. 121 | // http://aurelia.io/docs.html#/aurelia/framework/1.0.0-beta.1.1.3/doc/article/cheat-sheet 122 | attached() { 123 | $("#date-till").datepicker({ 124 | onSelect: (selected, evt) => { 125 | this.till = selected.toString(); 126 | } 127 | }); 128 | $("#date-till").datepicker("setDate", new Date(this.till)); 129 | 130 | $("#date-since").datepicker({ 131 | onSelect: (selected, evt) => { 132 | this.since = selected.toString(); 133 | } 134 | }); 135 | $("#date-since").datepicker("setDate", new Date(this.since)); 136 | 137 | //this.getData(); 138 | } 139 | 140 | daysFromNow(days) { 141 | return new Date(Date.now()+days*24*60*60*1000).toISOString().substring(0,10); 142 | } 143 | 144 | drawChart() { 145 | let data = this.prices.map(p => { 146 | return { 147 | price: p.item2, 148 | date: new Date(p.item1) 149 | }; 150 | }); 151 | 152 | $("#visualisation").empty(); 153 | let vis = d3.select("#visualisation"); 154 | const WIDTH = 600; 155 | const HEIGHT = 500; 156 | const MARGINS = { 157 | top: 20, 158 | right: 20, 159 | bottom: 20, 160 | left: 50 161 | }; 162 | let xScale = d3.time.scale() 163 | .range([MARGINS.left, WIDTH - MARGINS.right]) 164 | .domain(d3.extent(data, function(d) { return d.date; })); 165 | 166 | let yScale = d3.scale.linear() 167 | .range([HEIGHT - MARGINS.top, MARGINS.bottom]) 168 | .domain(d3.extent(data, function(d) { return d.price; })); 169 | 170 | let xAxis = d3.svg.axis() 171 | .scale(xScale) 172 | .orient("bottom") 173 | .innerTickSize(-HEIGHT) 174 | .outerTickSize(0) 175 | .ticks(5) 176 | .tickPadding(10); 177 | 178 | let yAxis = d3.svg.axis() 179 | .scale(yScale) 180 | .orient("left") 181 | .innerTickSize(-WIDTH) 182 | .outerTickSize(0) 183 | .tickPadding(10); 184 | 185 | let yGrid = d3.svg.axis() 186 | .scale(yScale) 187 | .orient("left") 188 | .tickSize(-(WIDTH-MARGINS.left-MARGINS.right), 0, 0) 189 | .tickFormat("") 190 | .tickPadding(50) 191 | .ticks(5); 192 | 193 | vis.append("svg:g") 194 | .attr("class", "x axis") 195 | .attr("transform", "translate(0," + (HEIGHT - MARGINS.bottom) + ")") 196 | .style('fill', '#888888') 197 | .call(xAxis); 198 | 199 | vis.append("svg:g") 200 | .attr("class", "y axis") 201 | .attr("transform", "translate(" + (MARGINS.left) + ",0)") 202 | .style('fill', '#888888') 203 | .call(yAxis); 204 | 205 | vis.append("svg:g") 206 | .attr("class", "y axis") 207 | .attr("class", "grid") 208 | .attr("transform", "translate(" + (MARGINS.left) + ",0)") 209 | .style('fill', '#888888') 210 | .call(yGrid); 211 | 212 | let lineGen = d3.svg.line() 213 | .x(function(d) { 214 | return xScale(d.date); 215 | }) 216 | .y(function(d) { 217 | return yScale(d.price); 218 | }) 219 | .interpolate("none"); 220 | 221 | vis.append('svg:path') 222 | .datum(data) 223 | .attr("class", "line") 224 | .attr("d", lineGen) 225 | .attr('stroke', '#2A9FD6') 226 | .attr('stroke-width', 1) 227 | .attr('fill', 'none'); 228 | } 229 | } 230 | 231 | export class CurrencyValueConverter { 232 | toView(value) { 233 | return value && (Math.round(value * 100) / 100).toFixed(2); 234 | } 235 | } -------------------------------------------------------------------------------- /StockEstimator.WebUI/wwwroot/src/main.ts: -------------------------------------------------------------------------------- 1 | import {Aurelia} from 'aurelia-framework'; 2 | import environment from './environment'; 3 | 4 | export function configure(aurelia: Aurelia) { 5 | aurelia.use 6 | .standardConfiguration() 7 | .feature('resources'); 8 | 9 | if (environment.debug) { 10 | aurelia.use.developmentLogging(); 11 | } 12 | 13 | if (environment.testing) { 14 | aurelia.use.plugin('aurelia-testing'); 15 | } 16 | 17 | aurelia.start().then(() => aurelia.setRoot()); 18 | } 19 | -------------------------------------------------------------------------------- /StockEstimator.WebUI/wwwroot/src/nav-bar.html: -------------------------------------------------------------------------------- 1 | 31 | -------------------------------------------------------------------------------- /StockEstimator.WebUI/wwwroot/src/resources/index.ts: -------------------------------------------------------------------------------- 1 | import {FrameworkConfiguration} from 'aurelia-framework'; 2 | 3 | export function configure(config: FrameworkConfiguration) { 4 | //config.globalResources([]); 5 | } 6 | -------------------------------------------------------------------------------- /StockEstimator.WebUI/wwwroot/styles/styles.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | } 4 | 5 | .splash { 6 | text-align: center; 7 | margin: 10% 0 0 0; 8 | box-sizing: border-box; 9 | } 10 | 11 | .splash .message { 12 | font-size: 72px; 13 | line-height: 72px; 14 | text-shadow: rgba(0, 0, 0, 0.5) 0 0 15px; 15 | text-transform: uppercase; 16 | font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; 17 | } 18 | 19 | .splash .fa-spinner { 20 | text-align: center; 21 | display: inline-block; 22 | font-size: 72px; 23 | margin-top: 50px; 24 | } 25 | 26 | .page-host { 27 | position: absolute; 28 | left: 0; 29 | right: 0; 30 | top: 50px; 31 | bottom: 0; 32 | overflow-x: hidden; 33 | overflow-y: auto; 34 | } 35 | 36 | @media print { 37 | .page-host { 38 | position: absolute; 39 | left: 10px; 40 | right: 0; 41 | top: 50px; 42 | bottom: 0; 43 | overflow-y: inherit; 44 | overflow-x: inherit; 45 | } 46 | } 47 | 48 | section { 49 | margin: 0 20px; 50 | } 51 | 52 | .navbar-nav li.loader { 53 | margin: 12px 24px 0 6px; 54 | } 55 | 56 | .pictureDetail { 57 | max-width: 425px; 58 | } 59 | 60 | /* animate page transitions */ 61 | section.au-enter-active { 62 | -webkit-animation: fadeInRight 1s; 63 | animation: fadeInRight 1s; 64 | } 65 | 66 | div.au-stagger { 67 | /* 50ms will be applied between each successive enter operation */ 68 | -webkit-animation-delay: 50ms; 69 | animation-delay: 50ms; 70 | } 71 | 72 | .card-container.au-enter { 73 | opacity: 0; 74 | } 75 | 76 | .card-container.au-enter-active { 77 | -webkit-animation: fadeIn 2s; 78 | animation: fadeIn 2s; 79 | } 80 | 81 | .card { 82 | overflow: hidden; 83 | position: relative; 84 | border: 1px solid #CCC; 85 | border-radius: 8px; 86 | text-align: center; 87 | padding: 0; 88 | background-color: #337ab7; 89 | color: rgb(136, 172, 217); 90 | margin-bottom: 32px; 91 | box-shadow: 0 0 5px rgba(0, 0, 0, .5); 92 | } 93 | 94 | .card .content { 95 | margin-top: 10px; 96 | } 97 | 98 | .card .content .name { 99 | color: white; 100 | text-shadow: 0 0 6px rgba(0, 0, 0, .5); 101 | font-size: 18px; 102 | } 103 | 104 | .card .header-bg { 105 | /* This stretches the canvas across the entire hero unit */ 106 | position: absolute; 107 | top: 0; 108 | left: 0; 109 | width: 100%; 110 | height: 70px; 111 | border-bottom: 1px #FFF solid; 112 | border-radius: 6px 6px 0 0; 113 | } 114 | 115 | .card .avatar { 116 | position: relative; 117 | margin-top: 15px; 118 | z-index: 100; 119 | } 120 | 121 | .card .avatar img { 122 | width: 100px; 123 | height: 100px; 124 | -webkit-border-radius: 50%; 125 | -moz-border-radius: 50%; 126 | border-radius: 50%; 127 | border: 2px #FFF solid; 128 | } 129 | 130 | /* animation definitions */ 131 | @-webkit-keyframes fadeInRight { 132 | 0% { 133 | opacity: 0; 134 | -webkit-transform: translate3d(100%, 0, 0); 135 | transform: translate3d(100%, 0, 0) 136 | } 137 | 100% { 138 | opacity: 1; 139 | -webkit-transform: none; 140 | transform: none 141 | } 142 | } 143 | 144 | @keyframes fadeInRight { 145 | 0% { 146 | opacity: 0; 147 | -webkit-transform: translate3d(100%, 0, 0); 148 | -ms-transform: translate3d(100%, 0, 0); 149 | transform: translate3d(100%, 0, 0) 150 | } 151 | 100% { 152 | opacity: 1; 153 | -webkit-transform: none; 154 | -ms-transform: none; 155 | transform: none 156 | } 157 | } 158 | 159 | @-webkit-keyframes fadeIn { 160 | 0% { 161 | opacity: 0; 162 | } 163 | 100% { 164 | opacity: 1; 165 | } 166 | } 167 | 168 | @keyframes fadeIn { 169 | 0% { 170 | opacity: 0; 171 | } 172 | 100% { 173 | opacity: 1; 174 | } 175 | } 176 | 177 | /* D3 */ 178 | .axis path { 179 | fill: none; 180 | stroke: #777; 181 | shape-rendering: crispEdges; 182 | } 183 | 184 | .grid .tick { 185 | stroke: #555555; 186 | opacity: 0.7; 187 | } 188 | .grid path { 189 | stroke-width: 0; 190 | } -------------------------------------------------------------------------------- /StockEstimator.WebUI/wwwroot/test/aurelia-karma.js: -------------------------------------------------------------------------------- 1 | (function(global) { 2 | var karma = global.__karma__; 3 | var requirejs = global.requirejs 4 | var locationPathname = global.location.pathname; 5 | var root = 'src'; 6 | karma.config.args.forEach(function(value, index) { 7 | if (value === 'aurelia-root') { 8 | root = karma.config.args[index + 1]; 9 | } 10 | }); 11 | 12 | if (!karma || !requirejs) { 13 | return; 14 | } 15 | 16 | function normalizePath(path) { 17 | var normalized = [] 18 | var parts = path 19 | .split('?')[0] // cut off GET params, used by noext requirejs plugin 20 | .split('/') 21 | 22 | for (var i = 0; i < parts.length; i++) { 23 | if (parts[i] === '.') { 24 | continue 25 | } 26 | 27 | if (parts[i] === '..' && normalized.length && normalized[normalized.length - 1] !== '..') { 28 | normalized.pop() 29 | continue 30 | } 31 | 32 | normalized.push(parts[i]) 33 | } 34 | 35 | // Use case of testing source code. RequireJS doesn't add .js extension to files asked via sibling selector 36 | // If normalized path doesn't include some type of extension, add the .js to it 37 | if (normalized.length > 0 && normalized[normalized.length - 1].indexOf('.') < 0) { 38 | normalized[normalized.length - 1] = normalized[normalized.length - 1] + '.js' 39 | } 40 | 41 | return normalized.join('/') 42 | } 43 | 44 | function patchRequireJS(files, originalLoadFn, locationPathname) { 45 | var IS_DEBUG = /debug\.html$/.test(locationPathname) 46 | 47 | requirejs.load = function (context, moduleName, url) { 48 | url = normalizePath(url) 49 | 50 | if (files.hasOwnProperty(url) && !IS_DEBUG) { 51 | url = url + '?' + files[url] 52 | } 53 | 54 | if (url.indexOf('/base') !== 0) { 55 | url = '/base/' + url; 56 | } 57 | 58 | return originalLoadFn.call(this, context, moduleName, url) 59 | } 60 | 61 | var originalDefine = global.define; 62 | global.define = function(name, deps, m) { 63 | if (typeof name === 'string') { 64 | originalDefine('/base/' + root + '/' + name, [name], function (result) { return result; }); 65 | } 66 | 67 | return originalDefine(name, deps, m); 68 | } 69 | } 70 | 71 | function requireTests() { 72 | var TEST_REGEXP = /(spec)\.js$/i; 73 | var allTestFiles = ['/base/test/unit/setup.js']; 74 | 75 | Object.keys(window.__karma__.files).forEach(function(file) { 76 | if (TEST_REGEXP.test(file)) { 77 | allTestFiles.push(file); 78 | } 79 | }); 80 | 81 | require(allTestFiles, window.__karma__.start); 82 | } 83 | 84 | karma.loaded = function() {}; // make it async 85 | patchRequireJS(karma.files, requirejs.load, locationPathname); 86 | requireTests(); 87 | })(window); 88 | -------------------------------------------------------------------------------- /StockEstimator.WebUI/wwwroot/test/unit/app.spec.ts: -------------------------------------------------------------------------------- 1 | import {App} from '../../src/app'; 2 | 3 | describe('the app', () => { 4 | it('says hello', () => { 5 | expect(new App().message).toBe('Hello World!'); 6 | }); 7 | }); 8 | -------------------------------------------------------------------------------- /StockEstimator.WebUI/wwwroot/test/unit/setup.ts: -------------------------------------------------------------------------------- 1 | import 'aurelia-polyfills'; 2 | import {initialize} from 'aurelia-pal-browser'; 3 | initialize(); 4 | -------------------------------------------------------------------------------- /StockEstimator.WebUI/wwwroot/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compileOnSave": false, 3 | "compilerOptions": { 4 | "sourceMap": true, 5 | "target": "es5", 6 | "module": "amd", 7 | "declaration": false, 8 | "noImplicitAny": false, 9 | "removeComments": true, 10 | "emitDecoratorMetadata": true, 11 | "experimentalDecorators": true, 12 | "moduleResolution": "node", 13 | "lib": ["es2017", "dom"] 14 | }, 15 | "exclude": [ 16 | "node_modules", 17 | "aurelia_project" 18 | ], 19 | "filesGlob": [ 20 | "./src/**/*.ts", 21 | "./test/**/*.ts", 22 | "./custom_typings/**/*.d.ts" 23 | ], 24 | "atom": { 25 | "rewriteTsconfig": false 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /StockEstimator.WebUI/wwwroot/tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "rules": { 3 | 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /StockEstimator.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.26228.4 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "StockEstimator.Logic", "StockEstimator.Logic\StockEstimator.Logic.fsproj", "{F255F413-25DA-44C6-92F2-6481CC8065EB}" 7 | EndProject 8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StockEstimator.ConsoleApp", "StockEstimator.ConsoleApp\StockEstimator.ConsoleApp.csproj", "{0D188E86-8A3A-4A6B-AE8A-F141BF690971}" 9 | EndProject 10 | Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "StockEstimator.Charts", "StockEstimator.Charts\StockEstimator.Charts.fsproj", "{88B5BBCC-7E70-4D94-901E-DBD5839DDDF3}" 11 | EndProject 12 | Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "StockEstimator.Tests", "StockEstimator.Tests\StockEstimator.Tests.fsproj", "{24D58CF7-73C8-4968-B637-AF238FEE2C57}" 13 | EndProject 14 | Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "StockEstimator.WebApi", "StockEstimator.WebApi\StockEstimator.WebApi.fsproj", "{ED9AC7A7-62E3-4BF8-97B9-E76CDD3D6EAE}" 15 | EndProject 16 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "StockEstimator.WebUI", "StockEstimator.WebUI\StockEstimator.WebUI.csproj", "{69668F2F-980C-4259-B158-D0196C7555D6}" 17 | EndProject 18 | Global 19 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 20 | Debug|Any CPU = Debug|Any CPU 21 | Release|Any CPU = Release|Any CPU 22 | EndGlobalSection 23 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 24 | {F255F413-25DA-44C6-92F2-6481CC8065EB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 25 | {F255F413-25DA-44C6-92F2-6481CC8065EB}.Debug|Any CPU.Build.0 = Debug|Any CPU 26 | {F255F413-25DA-44C6-92F2-6481CC8065EB}.Release|Any CPU.ActiveCfg = Release|Any CPU 27 | {F255F413-25DA-44C6-92F2-6481CC8065EB}.Release|Any CPU.Build.0 = Release|Any CPU 28 | {0D188E86-8A3A-4A6B-AE8A-F141BF690971}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 29 | {0D188E86-8A3A-4A6B-AE8A-F141BF690971}.Debug|Any CPU.Build.0 = Debug|Any CPU 30 | {0D188E86-8A3A-4A6B-AE8A-F141BF690971}.Release|Any CPU.ActiveCfg = Release|Any CPU 31 | {0D188E86-8A3A-4A6B-AE8A-F141BF690971}.Release|Any CPU.Build.0 = Release|Any CPU 32 | {88B5BBCC-7E70-4D94-901E-DBD5839DDDF3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 33 | {88B5BBCC-7E70-4D94-901E-DBD5839DDDF3}.Debug|Any CPU.Build.0 = Debug|Any CPU 34 | {88B5BBCC-7E70-4D94-901E-DBD5839DDDF3}.Release|Any CPU.ActiveCfg = Release|Any CPU 35 | {88B5BBCC-7E70-4D94-901E-DBD5839DDDF3}.Release|Any CPU.Build.0 = Release|Any CPU 36 | {24D58CF7-73C8-4968-B637-AF238FEE2C57}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 37 | {24D58CF7-73C8-4968-B637-AF238FEE2C57}.Debug|Any CPU.Build.0 = Debug|Any CPU 38 | {24D58CF7-73C8-4968-B637-AF238FEE2C57}.Release|Any CPU.ActiveCfg = Release|Any CPU 39 | {24D58CF7-73C8-4968-B637-AF238FEE2C57}.Release|Any CPU.Build.0 = Release|Any CPU 40 | {ED9AC7A7-62E3-4BF8-97B9-E76CDD3D6EAE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 41 | {ED9AC7A7-62E3-4BF8-97B9-E76CDD3D6EAE}.Debug|Any CPU.Build.0 = Debug|Any CPU 42 | {ED9AC7A7-62E3-4BF8-97B9-E76CDD3D6EAE}.Release|Any CPU.ActiveCfg = Release|Any CPU 43 | {ED9AC7A7-62E3-4BF8-97B9-E76CDD3D6EAE}.Release|Any CPU.Build.0 = Release|Any CPU 44 | {69668F2F-980C-4259-B158-D0196C7555D6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 45 | {69668F2F-980C-4259-B158-D0196C7555D6}.Debug|Any CPU.Build.0 = Debug|Any CPU 46 | {69668F2F-980C-4259-B158-D0196C7555D6}.Release|Any CPU.ActiveCfg = Release|Any CPU 47 | {69668F2F-980C-4259-B158-D0196C7555D6}.Release|Any CPU.Build.0 = Release|Any CPU 48 | EndGlobalSection 49 | GlobalSection(SolutionProperties) = preSolution 50 | HideSolutionNode = FALSE 51 | EndGlobalSection 52 | EndGlobal 53 | -------------------------------------------------------------------------------- /StockEstimator.v2.ncrunchsolution: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jj09/StockEstimator/e03d0ad0d6eea5db26e9f58b5a949974f292e9b7/StockEstimator.v2.ncrunchsolution --------------------------------------------------------------------------------