├── plotting ├── requirements.txt ├── Makefile ├── plot_stocks.py ├── plot_stocks_multipanel.py ├── plot_volatility.py └── plot_crossover.py ├── .gitignore ├── .travis.yml ├── Makefile ├── data └── get_data.sh ├── src ├── Makefile ├── mod_alloc.f90 ├── stock_gain.f90 ├── stock_crossover.f90 ├── stock_volatility.f90 ├── mod_io.f90 └── mod_arrays.f90 ├── LICENSE └── README.md /plotting/requirements.txt: -------------------------------------------------------------------------------- 1 | matplotlib 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.o 2 | *.mod 3 | *.txt 4 | stock_gain 5 | stock_volatility 6 | stock_crossover 7 | -------------------------------------------------------------------------------- /plotting/Makefile: -------------------------------------------------------------------------------- 1 | all: volatility crossover 2 | 3 | volatility: 4 | python plot_volatility.py 5 | 6 | crossover: 7 | python plot_crossover.py 8 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: none 2 | fast_finish: true 3 | 4 | os: 5 | - linux 6 | 7 | env: FC=gfortran-7 8 | 9 | dist: trusty 10 | group: edge 11 | 12 | notifications: 13 | email: false 14 | 15 | git: 16 | depth: 3 17 | 18 | addons: 19 | apt: 20 | sources: 21 | - ubuntu-toolchain-r-test 22 | packages: 23 | - gfortran-7 24 | 25 | install: 26 | - make 27 | 28 | script: 29 | - ./stock_gain 30 | - ./stock_volatility 31 | - ./stock_crossover 32 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # stock-prices Makefile 2 | 3 | .PHONY: all clean 4 | 5 | all: stock_gain stock_volatility stock_crossover 6 | 7 | stock_gain: src/*.f90 8 | $(MAKE) --directory=src $@ 9 | cp src/$@ . 10 | 11 | stock_volatility: src/*.f90 12 | $(MAKE) --directory=src $@ 13 | cp src/$@ . 14 | 15 | stock_crossover: src/*.f90 16 | $(MAKE) --directory=src $@ 17 | cp src/$@ . 18 | 19 | figures: stock_volatility stock_crossover 20 | ./stock_volatility 21 | ./stock_crossover 22 | $(MAKE) --directory=plotting 23 | 24 | clean: 25 | $(MAKE) --directory=src $@ 26 | $(RM) *.txt stock_gain stock_volatility stock_crossover 27 | -------------------------------------------------------------------------------- /data/get_data.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # This is a bash script that can be run on an operating system that 4 | # supports it, e.g. Linux, macOS, or Windows Subsystem for Linux on 5 | # Windows. 6 | # 7 | # Downloads the end of day stock data from the alphavantage.co API. 8 | # The ALPHAVANTAGE_KEY environment variable must be set. 9 | 10 | symbols="AMZN MSFT AAPL HPQ IBM CRAY NVDA ORCL INTC CSCO" 11 | 12 | for symbol in $symbols; do 13 | echo $symbol 14 | url=https://www.alphavantage.co/query?function=TIME_SERIES_DAILY_ADJUSTED\&symbol=${symbol}\&outputsize=full\&apikey=${ALPHAVANTAGE_KEY}\&datatype=csv 15 | curl $url > ${symbol}.csv 16 | done 17 | -------------------------------------------------------------------------------- /src/Makefile: -------------------------------------------------------------------------------- 1 | # stock-prices Makefile 2 | 3 | OBJS = mod_alloc.o mod_arrays.o mod_io.o 4 | 5 | all: stock_gain 6 | 7 | .SUFFIXES: .f90 .o 8 | 9 | # general rule 10 | .f90.o: 11 | $(FC) -c $< 12 | 13 | # programs 14 | stock_gain: stock_gain.f90 $(OBJS) 15 | $(FC) $< $(OBJS) -o $@ 16 | 17 | stock_volatility: stock_volatility.f90 $(OBJS) 18 | $(FC) $< $(OBJS) -o $@ 19 | 20 | stock_crossover: stock_crossover.f90 $(OBJS) 21 | $(FC) $< $(OBJS) -o $@ 22 | 23 | # modules 24 | mod_alloc.o: mod_alloc.f90 25 | mod_arrays.o: mod_arrays.f90 26 | mod_io.o: mod_io.f90 mod_alloc.o 27 | 28 | .PHONY: 29 | clean: 30 | $(RM) *.o *.mod stock_gain stock_volatility stock_crossover 31 | -------------------------------------------------------------------------------- /src/mod_alloc.f90: -------------------------------------------------------------------------------- 1 | module mod_alloc 2 | 3 | implicit none 4 | 5 | private 6 | public :: alloc, free 7 | 8 | contains 9 | 10 | subroutine alloc(a, n) 11 | real, allocatable, intent(in out) :: a(:) 12 | integer, intent(in) :: n 13 | integer :: stat 14 | character(len=100) :: errmsg 15 | if (allocated(a)) call free(a) 16 | allocate(a(n), stat=stat, errmsg=errmsg) 17 | if (stat > 0) error stop errmsg 18 | end subroutine alloc 19 | 20 | subroutine free(a) 21 | real, allocatable, intent(in out) :: a(:) 22 | integer :: stat 23 | character(len=100) :: errmsg 24 | if (.not. allocated(a)) return 25 | deallocate(a, stat=stat, errmsg=errmsg) 26 | if (stat > 0) error stop errmsg 27 | end subroutine free 28 | 29 | end module mod_alloc 30 | -------------------------------------------------------------------------------- /src/stock_gain.f90: -------------------------------------------------------------------------------- 1 | program stock_gain 2 | 3 | use mod_arrays, only: reverse 4 | use mod_io, only: read_stock 5 | 6 | implicit none 7 | 8 | character(len=4), allocatable :: symbols(:) 9 | character(len=:), allocatable :: time(:) 10 | real, allocatable :: open(:), high(:), low(:), close(:), adjclose(:), volume(:) 11 | integer :: i, im, n 12 | real :: gain 13 | 14 | symbols = ['AAPL', 'AMZN', 'CRAY', 'CSCO', 'HPQ ',& 15 | 'IBM ', 'INTC', 'MSFT', 'NVDA', 'ORCL'] 16 | 17 | do n = 1, size(symbols) 18 | 19 | call read_stock('data/' // trim(symbols(n)) // '.csv', time,& 20 | open, high, low, close, adjclose, volume) 21 | 22 | adjclose = reverse(adjclose) 23 | gain = (adjclose(size(adjclose)) - adjclose(1)) 24 | 25 | if (n == 1) then 26 | print *, time(size(time)) // ' through ' // time(1) 27 | print *, 'Symbol, Gain (USD), Relative gain (%)' 28 | print *, '-------------------------------------' 29 | end if 30 | 31 | print *, symbols(n), gain, nint(gain / adjclose(1) * 100) 32 | 33 | end do 34 | 35 | end program stock_gain 36 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Milan Curcic 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/stock_crossover.f90: -------------------------------------------------------------------------------- 1 | program stock_crossover 2 | 3 | use mod_arrays, only: crossneg, crosspos, reverse 4 | use mod_io, only: read_stock 5 | 6 | implicit none 7 | 8 | character(len=4), allocatable :: symbols(:) 9 | character(len=:), allocatable :: time(:) 10 | real, allocatable :: open(:), high(:), low(:), close(:), adjclose(:), volume(:) 11 | integer :: fileunit, i, im, n 12 | integer, allocatable :: buy(:), sell(:) 13 | 14 | symbols = ['AAPL', 'AMZN', 'CRAY', 'CSCO', 'HPQ ',& 15 | 'IBM ', 'INTC', 'MSFT', 'NVDA', 'ORCL'] 16 | 17 | do n = 1, size(symbols) 18 | 19 | print *, 'Processing moving average crossover for ' // symbols(n) 20 | 21 | call read_stock('data/' // trim(symbols(n)) // '.csv', time,& 22 | open, high, low, close, adjclose, volume) 23 | 24 | time = time(size(time):1:-1) 25 | adjclose = reverse(adjclose) 26 | 27 | buy = crosspos(adjclose, 30) 28 | sell = crossneg(adjclose, 30) 29 | 30 | open(newunit=fileunit, file=trim(symbols(n)) // '_crossover.txt') 31 | do i = 1, size(buy) 32 | write(fileunit, fmt=*) 'Buy ', time(buy(i)) 33 | end do 34 | do i = 1, size(sell) 35 | write(fileunit, fmt=*) 'Sell ', time(sell(i)) 36 | end do 37 | close(fileunit) 38 | 39 | end do 40 | 41 | end program stock_crossover 42 | -------------------------------------------------------------------------------- /src/stock_volatility.f90: -------------------------------------------------------------------------------- 1 | program stock_volatility 2 | 3 | use mod_arrays, only: average, std, moving_average, moving_std, reverse 4 | use mod_io, only: read_stock, write_stock 5 | 6 | implicit none 7 | 8 | character(len=4), allocatable :: symbols(:) 9 | character(len=:), allocatable :: time(:) 10 | real, allocatable :: open(:), high(:), low(:), close(:), adjclose(:), volume(:) 11 | integer :: i, im, n 12 | 13 | symbols = ['AAPL', 'AMZN', 'CRAY', 'CSCO', 'HPQ ',& 14 | 'IBM ', 'INTC', 'MSFT', 'NVDA', 'ORCL'] 15 | 16 | do n = 1, size(symbols) 17 | 18 | call read_stock('data/' // trim(symbols(n)) // '.csv', time,& 19 | open, high, low, close, adjclose, volume) 20 | 21 | im = size(time) 22 | adjclose = reverse(adjclose) 23 | 24 | if (n == 1) then 25 | print *, time(im) // ' through ' // time(1) 26 | print *, 'Symbol, Average (USD), Volatility (USD), Relative Volatility (%)' 27 | print *, '----------------------------------------------------------------' 28 | end if 29 | 30 | print *, symbols(n), average(adjclose), std(adjclose),& 31 | nint(std(adjclose) / average(adjclose) * 100) 32 | 33 | time = time(im:1:-1) 34 | 35 | call write_stock(trim(symbols(n)) // '_volatility.txt', time, adjclose,& 36 | moving_average(adjclose, 30), moving_std(adjclose, 30)) 37 | 38 | end do 39 | 40 | end program stock_volatility 41 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # stock-prices 2 | 3 | [![Build Status](https://travis-ci.org/modern-fortran/listings.svg?branch=master)](https://travis-ci.org/modern-fortran/stock-prices) 4 | [![GitHub issues](https://img.shields.io/github/issues/modern-fortran/stock-prices.svg)](https://github.com/modern-fortran/stock-prices/issues) 5 | 6 | Analyzing stock prices with Fortran arrays. 7 | Companion code for Chapter 4 of [Modern Fortran: Building Efficient Parallel Applications](https://www.manning.com/books/modern-fortran?a_aid=modernfortran&a_bid=2dc4d442) 8 | 9 | ![](https://github.com/modern-fortran/stock-prices/blob/master/plotting/adjclose_multipanel.svg) 10 | 11 | ## Getting started 12 | 13 | ### Getting the code and data 14 | 15 | ``` 16 | git clone https://github.com/modern-fortran/stock-prices 17 | cd stock-prices 18 | FC=gfortran make 19 | ``` 20 | 21 | This will build three small apps: `stock_gain`, `stock_volatility`, and `stock_crossover`. 22 | 23 | ### Running the programs 24 | 25 | ``` 26 | ./stock_gain 27 | ./stock_volatility 28 | ./stock_crossover 29 | ``` 30 | 31 | ### Plotting the results 32 | 33 | You can use the included Python scripts to read and plot the data 34 | produced by the Fortran apps. 35 | 36 | First, set up a fresh Python virtual environment: 37 | 38 | ``` 39 | python3 -m venv venv 40 | source venv/bin/activate 41 | pip install -U pip 42 | pip install -r plotting/requirements.txt 43 | ``` 44 | 45 | Then type: 46 | 47 | ``` 48 | make figures 49 | ``` 50 | -------------------------------------------------------------------------------- /plotting/plot_stocks.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import matplotlib 4 | matplotlib.use('Agg') 5 | 6 | from datetime import datetime 7 | import matplotlib.pyplot as plt 8 | 9 | class Stock(): 10 | def __init__(self, filename): 11 | self.time = [] 12 | self.open = [] 13 | self.high = [] 14 | self.low = [] 15 | self.close = [] 16 | self.adjclose = [] 17 | data = [line.strip() for line in open(filename, 'r').readlines()][1:] 18 | for line in data[::-1]: 19 | line = line.split(',') 20 | self.time.append(datetime.strptime(line[0], '%Y-%m-%d')) 21 | self.open.append(float(line[1])) 22 | self.high.append(float(line[2])) 23 | self.low.append(float(line[3])) 24 | self.close.append(float(line[4])) 25 | self.adjclose.append(float(line[5])) 26 | 27 | 28 | datapath = '../data' 29 | 30 | stocks = ['AAPL', 'AMZN', 'CRAY', 'CSCO', 'HPQ', 31 | 'IBM', 'INTC', 'MSFT', 'NVDA', 'ORCL'] 32 | 33 | for stock in stocks: 34 | s = Stock(datapath + '/'+stock+'.csv') 35 | fig = plt.figure(figsize=(12, 6)) 36 | ax = fig.add_subplot(111, ylim=(0, max(s.adjclose))) 37 | ax.tick_params(axis='both', labelsize=16) 38 | plt.plot(s.time, s.adjclose, 'k-', lw=1) 39 | plt.title(stock, fontsize=16) 40 | plt.ylabel('Adj. close [USD]', fontsize=16) 41 | plt.grid(True) 42 | plt.savefig(stock + '.png', dpi=100) 43 | plt.savefig(stock + '.svg') 44 | plt.close(fig) 45 | -------------------------------------------------------------------------------- /plotting/plot_stocks_multipanel.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import matplotlib 4 | matplotlib.use('Agg') 5 | 6 | from datetime import datetime 7 | import matplotlib.pyplot as plt 8 | 9 | class Stock(): 10 | def __init__(self, filename): 11 | self.time = [] 12 | self.open = [] 13 | self.high = [] 14 | self.low = [] 15 | self.close = [] 16 | self.adjclose = [] 17 | data = [line.strip() for line in open(filename, 'r').readlines()][1:] 18 | for line in data[::-1]: 19 | line = line.split(',') 20 | self.time.append(datetime.strptime(line[0], '%Y-%m-%d')) 21 | self.open.append(float(line[1])) 22 | self.high.append(float(line[2])) 23 | self.low.append(float(line[3])) 24 | self.close.append(float(line[4])) 25 | self.adjclose.append(float(line[5])) 26 | 27 | 28 | datapath = '../data' 29 | 30 | stocks = ['AAPL', 'AMZN', 'CRAY', 'CSCO', 'HPQ', 'IBM', 'INTC', 'MSFT', 'NVDA', 'ORCL'] 31 | 32 | fig = plt.figure(figsize=(12, 8)) 33 | ind = 0 34 | for row in range(5): 35 | for col in range(2): 36 | ax = plt.subplot2grid((5, 2),(row, col), colspan=1, rowspan=1) 37 | stock = stocks[ind] 38 | s = Stock(datapath + '/' + stock + '.csv') 39 | ax.tick_params(axis='both', labelsize=10) 40 | plt.plot(s.time, s.adjclose, 'k-', lw=1) 41 | plt.text(0.5, 0.8, stock, ha='center', va='bottom', 42 | transform=ax.transAxes, fontsize=12) 43 | if not row == 4: 44 | ax.set_xticklabels([]) 45 | plt.grid(True) 46 | ind += 1 47 | 48 | fig.tight_layout() 49 | plt.savefig('adjclose_multipanel.svg') 50 | plt.close(fig) 51 | -------------------------------------------------------------------------------- /plotting/plot_volatility.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import matplotlib 4 | matplotlib.use('Agg') 5 | 6 | from datetime import datetime 7 | import matplotlib.pyplot as plt 8 | import numpy as np 9 | 10 | class Stock(): 11 | def __init__(self, filename): 12 | self.time = [] 13 | self.adjclose = [] 14 | self.mvavg = [] 15 | self.mvstd = [] 16 | data = [line.strip() for line in open(filename, 'r').readlines()] 17 | for line in data: 18 | line = line.split() 19 | self.time.append(datetime.strptime(line[0], '%Y-%m-%d')) 20 | self.adjclose.append(float(line[1])) 21 | self.mvavg.append(float(line[2])) 22 | self.mvstd.append(float(line[3])) 23 | 24 | 25 | datapath = '..' 26 | 27 | stocks = ['AAPL', 'AMZN', 'CRAY', 'CSCO', 'HPQ', 28 | 'IBM', 'INTC', 'MSFT', 'NVDA', 'ORCL'] 29 | 30 | startdate = datetime(2017, 1, 1) 31 | enddate = datetime(2018, 1, 1) 32 | 33 | for stock in stocks: 34 | s = Stock(datapath + '/' + stock + '_volatility.txt') 35 | mask = (np.array(s.time) >= startdate) & (np.array(s.time) <= enddate) 36 | 37 | fig = plt.figure(figsize=(12, 8)) 38 | ax1 = plt.subplot2grid((2, 1), (0, 0), colspan=1, rowspan=1) 39 | plt.ylim(min(np.array(s.adjclose)[mask]), max(np.array(s.adjclose)[mask])) 40 | plt.plot(s.time, s.adjclose, 'k-', lw=2) 41 | plt.plot(s.time, s.mvavg, 'k-', lw=4, alpha=0.4) 42 | plt.title(stock, fontsize=16) 43 | plt.ylabel('Adj. close [USD]', fontsize=16) 44 | ax2 = plt.subplot2grid((2, 1), (1, 0), colspan=1, rowspan=1) 45 | plt.ylim(0, 15) 46 | plt.plot(s.time, 100 * np.array(s.mvstd) / np.array(s.mvavg), 'k-', lw=2) 47 | plt.title(stock, fontsize=16) 48 | plt.ylabel('Volatility [%]', fontsize=16) 49 | for ax in [ax1, ax2]: 50 | ax.set_xlim(startdate, enddate) 51 | ax.tick_params(axis='both', labelsize=16) 52 | ax.grid(True) 53 | plt.savefig(stock + '_volatility.svg') 54 | plt.savefig(stock + '_volatility.png') 55 | plt.close(fig) 56 | 57 | -------------------------------------------------------------------------------- /src/mod_io.f90: -------------------------------------------------------------------------------- 1 | module mod_io 2 | 3 | ! A helper module for parsing stock price data in csv format. 4 | 5 | use mod_alloc, only: alloc 6 | 7 | implicit none 8 | 9 | private 10 | public :: read_stock, write_stock 11 | 12 | contains 13 | 14 | integer function num_records(filename) 15 | ! Return the number of records (lines) of a text file. 16 | character(len=*), intent(in) :: filename 17 | integer :: fileunit 18 | open(newunit=fileunit, file=filename) 19 | num_records = 0 20 | do 21 | read(unit=fileunit, fmt=*, end=1) 22 | num_records = num_records + 1 23 | end do 24 | 1 continue 25 | close(unit=fileunit) 26 | end function num_records 27 | 28 | subroutine read_stock(filename, time, open, high, low, close, adjclose, volume) 29 | ! Read daily stock prices from a csv file. 30 | character(len=*), intent(in) :: filename 31 | character(len=:), allocatable, intent(in out) :: time(:) 32 | real, allocatable, intent(in out) :: open(:), high(:), low(:),& 33 | close(:), adjclose(:), volume(:) 34 | integer :: fileunit, n, nm 35 | nm = num_records(filename) - 1 36 | if (allocated(time)) deallocate(time) 37 | allocate(character(len=10) :: time(nm)) 38 | call alloc(open, nm) 39 | call alloc(high, nm) 40 | call alloc(low, nm) 41 | call alloc(close, nm) 42 | call alloc(adjclose, nm) 43 | call alloc(volume, nm) 44 | open(newunit=fileunit, file=filename) 45 | read(fileunit, fmt=*, end=1) 46 | do n = 1, nm 47 | read(fileunit, fmt=*, end=1) time(n), open(n),& 48 | high(n), low(n), close(n), adjclose(n), volume(n) 49 | end do 50 | 1 close(fileunit) 51 | end subroutine read_stock 52 | 53 | subroutine write_stock(filename, time, price, mvavg, mvstd) 54 | ! Write derived stock data to file. 55 | character(len=*), intent(in) :: filename 56 | character(len=:), allocatable, intent(in) :: time(:) 57 | real, intent(in) :: price(:), mvavg(:), mvstd(:) 58 | integer :: fileunit, n 59 | open(newunit=fileunit, file=filename) 60 | do n = 1, size(time) 61 | write(fileunit, fmt=*) time(n), price(n), mvavg(n), mvstd(n) 62 | end do 63 | close(fileunit) 64 | end subroutine write_stock 65 | 66 | end module mod_io 67 | -------------------------------------------------------------------------------- /plotting/plot_crossover.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import matplotlib 4 | matplotlib.use('Agg') 5 | 6 | from datetime import datetime 7 | from matplotlib.dates import date2num 8 | import matplotlib.pyplot as plt 9 | import numpy as np 10 | 11 | class Stock(): 12 | def __init__(self, filename): 13 | self.time = [] 14 | self.adjclose = [] 15 | self.mvavg = [] 16 | self.mvstd = [] 17 | data = [line.strip() for line in open(filename, 'r').readlines()] 18 | for line in data: 19 | line = line.split() 20 | self.time.append(datetime.strptime(line[0], '%Y-%m-%d')) 21 | self.adjclose.append(float(line[1])) 22 | self.mvavg.append(float(line[2])) 23 | self.mvstd.append(float(line[3])) 24 | 25 | def read_crossover(filename): 26 | data = [line.strip() for line in open(filename, 'r').readlines()] 27 | buy = [] 28 | sell = [] 29 | for line in data: 30 | line = line.split() 31 | if line[0] == 'Buy': 32 | buy.append(datetime.strptime(line[1], '%Y-%m-%d')) 33 | else: 34 | sell.append(datetime.strptime(line[1], '%Y-%m-%d')) 35 | return buy, sell 36 | 37 | 38 | datapath = '..' 39 | 40 | stocks = ['AAPL', 'AMZN', 'CRAY', 'CSCO', 'HPQ', 41 | 'IBM', 'INTC', 'MSFT', 'NVDA', 'ORCL'] 42 | 43 | startdate = datetime(2017, 1, 1) 44 | enddate = datetime(2018, 1, 1) 45 | 46 | up_arrow, down_arrow = u'$\u2191$', u'$\u2193$' 47 | 48 | for stock in stocks: 49 | 50 | print(stock) 51 | s = Stock(datapath + '/' + stock + '_volatility.txt') 52 | buy, sell = read_crossover(datapath + '/' + stock + '_crossover.txt') 53 | 54 | mask = (np.array(s.time) >= startdate) & (np.array(s.time) <= enddate) 55 | 56 | fig = plt.figure(figsize=(12, 6)) 57 | ax = fig.add_subplot(111, xlim=(startdate, enddate)) 58 | ax.tick_params(axis='both', labelsize=16) 59 | plt.ylim(min(np.array(s.adjclose)[mask]), max(np.array(s.adjclose)[mask])) 60 | plt.plot(s.time, s.adjclose, 'k-', lw=2) 61 | plt.plot(s.time, s.mvavg, 'k-', lw=4, alpha=0.4) 62 | datenum = date2num(s.time) 63 | for t in sell: 64 | n = np.argmin((datenum - date2num(t))**2) 65 | plt.plot(s.time[n], s.adjclose[n], linestyle=None, 66 | marker=down_arrow, color='r', ms=18) 67 | for t in buy: 68 | n = np.argmin((datenum - date2num(t))**2) 69 | plt.plot(s.time[n], s.adjclose[n], linestyle=None, 70 | marker=up_arrow, color='g', ms=18) 71 | plt.title(stock, fontsize=16) 72 | plt.ylabel('Adj. close [USD]', fontsize=16) 73 | plt.title(stock, fontsize=16) 74 | plt.grid(True) 75 | plt.savefig(stock + '_crossover.svg') 76 | plt.savefig(stock + '_crossover.png') 77 | plt.close(fig) 78 | 79 | -------------------------------------------------------------------------------- /src/mod_arrays.f90: -------------------------------------------------------------------------------- 1 | module mod_arrays 2 | 3 | ! Utility functions that operate on arrays. 4 | 5 | implicit none 6 | 7 | private 8 | public :: argsort, average, crossneg, crosspos, intdate, moving_average,& 9 | moving_std, reverse, std 10 | 11 | contains 12 | 13 | pure function argsort(x) result(a) 14 | ! Returns indices that sort x from low to high. 15 | real, intent(in):: x(:) 16 | integer :: a(size(x)) 17 | integer :: i, i0, tmp1 18 | real :: tmp2 19 | real :: xwork(size(x)) 20 | a = [(real(i), i = 1, size(x))] 21 | xwork = x 22 | do i = 1, size(x) - 1 23 | i0 = minloc(xwork(i:), 1) + i - 1 24 | if (i0 /= i) then 25 | tmp2 = xwork(i) 26 | xwork(i) = xwork(i0) 27 | xwork(i0) = tmp2 28 | tmp1 = a(i) 29 | a(i) = a(i0) 30 | a(i0) = tmp1 31 | end if 32 | end do 33 | end function argsort 34 | 35 | pure real function average(x) 36 | ! Returns a average of x. 37 | real, intent(in) :: x(:) 38 | average = sum(x) / size(x) 39 | end function average 40 | 41 | pure function crossneg(x, w) result(res) 42 | ! Returns indices where input array x crosses its 43 | ! moving average with window w from positive to negative. 44 | real, intent(in) :: x(:) 45 | integer, intent(in) :: w 46 | integer, allocatable :: res(:) 47 | real, allocatable :: xavg(:) 48 | logical, allocatable :: greater(:), smaller(:) 49 | integer :: i 50 | res = [(i, i = 2, size(x))] 51 | xavg = moving_average(x, w) 52 | greater = x > xavg 53 | smaller = x < xavg 54 | res = pack(res, smaller(2:) .and. greater(:size(x)-1)) 55 | end function crossneg 56 | 57 | pure function crosspos(x, w) result(res) 58 | ! Returns indices where input array x crosses its 59 | ! moving average with window w from negative to positive. 60 | real, intent(in) :: x(:) 61 | integer, intent(in) :: w 62 | integer, allocatable :: res(:) 63 | real, allocatable :: xavg(:) 64 | logical, allocatable :: greater(:), smaller(:) 65 | integer :: i 66 | res = [(i, i = 2, size(x))] 67 | xavg = moving_average(x, w) 68 | greater = x > xavg 69 | smaller = x < xavg 70 | res = pack(res, greater(2:) .and. smaller(:size(x)-1)) 71 | end function crosspos 72 | 73 | pure elemental integer function intdate(t) 74 | ! Converts a time stamp in format YYYY-mm-dd to integer. 75 | character(len=10), intent(in) :: t 76 | character(len=8) :: str 77 | str = t(1:4) // t(6:7) // t(9:10) 78 | read(str, *) intdate 79 | end function intdate 80 | 81 | pure function moving_average(x, w) result(res) 82 | ! Returns the moving average of x with one-sided window w. 83 | real, intent(in) :: x(:) 84 | integer, intent(in) :: w 85 | real :: res(size(x)) 86 | integer :: i, i1 87 | do i = 1, size(x) 88 | i1 = max(i-w, 1) 89 | res(i) = average(x(i1:i)) 90 | end do 91 | end function moving_average 92 | 93 | pure function moving_std(x, w) result(res) 94 | ! Returns the moving standard deviation of x with one-sided window w. 95 | real, intent(in) :: x(:) 96 | integer, intent(in) :: w 97 | real :: res(size(x)) 98 | integer :: i, i1 99 | do i = 1, size(x) 100 | i1 = max(i-w, 1) 101 | res(i) = std(x(i1:i)) 102 | end do 103 | end function moving_std 104 | 105 | pure function reverse(x) 106 | ! Reverses the order of elements of x. 107 | real, intent(in) :: x(:) 108 | real :: reverse(size(x)) 109 | reverse = x(size(x):1:-1) 110 | end function reverse 111 | 112 | pure real function std(x) 113 | ! Returns the standard deviation of x. 114 | real, intent(in) :: x(:) 115 | std = sqrt(average((x - average(x))**2)) 116 | end function std 117 | 118 | end module mod_arrays 119 | --------------------------------------------------------------------------------