sqa 0.0.2 → 0.0.4

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: cc25d07a5b5662c4f07ce7cfc1446ce7de13182f100a054d799d399fbf90c87f
4
- data.tar.gz: d42fecd00d56d568011c4173ec2954bc5baa9525319e8344e4bf6c86fe7a25ca
3
+ metadata.gz: 0f6d2b8e3a8092a5c423c86ebb5a2058d3d1ce9b1f617084009dcb63600fdc88
4
+ data.tar.gz: 1b26d1cd8cc77d50567e20cd939c99d103b877681c65dd0cfa72a08feec55ae4
5
5
  SHA512:
6
- metadata.gz: f609dbfdaf96ded7fc3a973fe6e51f663fe033e0ea30d6fffefa383145b90edac5b7f7164fd0896d2c33634304b5cd780f81fbe2b1ef93888a5c2db2750f6042
7
- data.tar.gz: 9b9f08cf5adf6c998d9c3fcc1df68441aef596190365218db77498b7f3f63ffca1364d49d8cf37d5407ca066f32cd16a988b0bb1a4ff4d4139358a3ad88a7b0a
6
+ metadata.gz: f6f3691cfe84cab52cd2001151e8fdcd2b23951466349b1285c7120b3a170906aa197194d08312f032090659748667393d182fe1e7c69e65a124361d48dc4b44
7
+ data.tar.gz: 3a9229b9a64d3306efe90b9bc364c39ef1042ff549709447888df6db9c6c9fbe469a767d1dce667b91edca6b0b80f2df609effe2b3ce7be009d8db14415d28ed
@@ -0,0 +1 @@
1
+ ad94810d7678366fc822ec4169e38e67542ebfe442ad64bd03faa6af781854e5c5943eddde65cc5f9251d7d3f7a0242f41b0da423cdc62cb9df9c9581b3b987a
@@ -0,0 +1 @@
1
+ 0bf41b69d8f760412a87aa119292b29f8d23c1a09daf059643314cb00fc251a5bd88a4843d9ed9e2493a9b177389a4bba2b7aff998d90bc7b50b81fb9fd81e47
@@ -0,0 +1 @@
1
+ e63dcb48ad13d1d8397b5445e25d15e58ede7191e6b9f5c5a106d701a01020036eb37cc1b45954d7fb7aa819527452df7b16b82678f439b138833c8ad0377dec
data/docs/requirements.md CHANGED
@@ -38,3 +38,25 @@ Most reliable way of getting data is the scrape the website. The gem financial_
38
38
  ## Extract Indicators
39
39
 
40
40
  After sleeping on it, I think the original plan with the fin_tech gem is a better idea for how to package the indicators. I'm going to keep the name FinTech for now while I think of something better. These are indicators; but I want them to be class-level methods with established contracts in their API.
41
+
42
+ The indicators in lib/sqa/indicator are stand-alone class methods; but, its so handy to have them in this repo. I will keep them here for a while
43
+
44
+ ## Configuration
45
+
46
+ SQA::Config is managed by the gem "mixlib-config" See the gem for full documentation.
47
+
48
+ The TL;DR is:
49
+
50
+ ```ruby
51
+ require 'sqa'
52
+ # read a configuration file in yaml, toml, json, ruby
53
+ # or just accept the defaults
54
+ SQA::Config.from_file(path_to_file)
55
+
56
+ # Initialize the environment
57
+ SQA.init
58
+ ```
59
+
60
+ ## Strategy Framework
61
+
62
+ Got the first ideas for handling strategies. in place.
data/docs/strategy.md ADDED
@@ -0,0 +1,5 @@
1
+ # Strategy
2
+
3
+ A strategy is a recipe that cooks all the indicators together to make a decision on a potential trade. The SQA::Strategy class provides the framework for executing multiple strategies.
4
+
5
+ You can also think of a strategy as a set of rules like in the old days of rule-based forward/backward chaining engines. The rules are evaluated to determine whether a specific decision to trade is good or bad.
@@ -4,8 +4,13 @@
4
4
  require_relative 'data_frame/yahoo_finance'
5
5
 
6
6
  class SQA::DataFrame < Daru::DataFrame
7
+ def self.path(filename)
8
+ SQA::Config.data_dir + filename
9
+ end
10
+
7
11
  def self.from_csv(ticker)
8
- path_to_csv = SQA::Config.data_dir + "#{ticker.downcase}.csv"
9
- super(path_to_csv)
12
+ df = super(path("#{ticker.downcase}.csv"))
13
+ df[:ticker] = ticker
14
+ df
10
15
  end
11
16
  end
data/lib/sqa/errors.rb CHANGED
@@ -1,5 +1,6 @@
1
1
  # lib/sqa/errors.rb
2
2
 
3
3
  module SQA
4
+ # raised when an API contract is broken
4
5
  class BadParameterError < ArgumentError; end
5
6
  end
@@ -20,7 +20,7 @@ class SQA::Indicator; class << self
20
20
 
21
21
  {
22
22
  upper_band: upper_band, # Array
23
- pwer_band: lower_band # Array
23
+ lower_band: lower_band # Array
24
24
  }
25
25
  end
26
26
  alias_method :bb, :bollinger_bands
@@ -33,7 +33,7 @@ class SQA::Indicator; class << self
33
33
  waves.each do |wave|
34
34
  analysis << {
35
35
  wave: wave,
36
- oattern: ewt_identify_pattern(wave)
36
+ pattern: ewt_identify_pattern(wave)
37
37
  }
38
38
  end
39
39
 
@@ -11,8 +11,12 @@ class SQA::Indicator; class << self
11
11
 
12
12
  short_ma = simple_moving_average(prices, short_period)
13
13
  long_ma = simple_moving_average(prices, long_period)
14
- macd_line = short_ma.last - long_ma.last
15
- signal_line = simple_moving_average(short_ma, signal_period).last
14
+ signal_line = simple_moving_average(short_ma, signal_period)
15
+ macd_line = []
16
+
17
+ prices.size.times do |x|
18
+ macd_line << short_ma[x] - long_ma[x]
19
+ end
16
20
 
17
21
  {
18
22
  macd: macd_line, # Array
@@ -0,0 +1,38 @@
1
+ # lib/sqa/strategry/common.rb
2
+
3
+ # This module needs to be extend'ed within
4
+ # a strategy class so that these common class
5
+ # methods are available in every trading strategy.
6
+
7
+ class SQA::Strategy
8
+ module Common
9
+ def trade_against(vector)
10
+ return :hold unless respond_to? :trade
11
+
12
+ recommendation = trade(vector)
13
+
14
+ if :sell == recommendation
15
+ :buy
16
+ elsif :buy == recommendation
17
+ :sell
18
+ else
19
+ :hold
20
+ end
21
+ end
22
+
23
+ def desc
24
+ doc_filename = self.name.split('::').last.downcase + ".md"
25
+ doc_path = Pathname.new(__dir__) + doc_filename
26
+
27
+ debug_me{[ :doc_path ]}
28
+
29
+ if doc_path.exist?
30
+ doc = doc_path.read
31
+ else
32
+ doc = "A description of #{self.name} is not available"
33
+ end
34
+
35
+ puts doc
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,43 @@
1
+ # lib/sqa/strategry/consensus.rb
2
+
3
+ require_relative 'common'
4
+
5
+ class SQA::Strategy::Consensus
6
+ extend SQA::Strategy::Common
7
+
8
+ def self.trade(vector)
9
+ new(vector).my_fancy_trader
10
+ end
11
+
12
+ def initialize(vector)
13
+ @vector = vector
14
+ @results = []
15
+ end
16
+
17
+ def my_fancy_trader
18
+ strat_one
19
+ strat_two
20
+ strat_three
21
+ strat_four
22
+ strat_five
23
+ consensus
24
+ end
25
+
26
+ def consensus
27
+ count = @results.group_by(&:itself).transform_values(&:count)
28
+
29
+ if count[:buy] > count[:sell]
30
+ :buy
31
+ elsif count[:sell] > count[:buy]
32
+ :sell
33
+ else
34
+ :hold
35
+ end
36
+ end
37
+
38
+ def strat_one = @results << (0==rand(2) ? :buy : :sell)
39
+ def strat_two = @results << (0==rand(2) ? :buy : :sell)
40
+ def strat_three = @results << (0==rand(2) ? :buy : :sell)
41
+ def strat_four = @results << (0==rand(2) ? :buy : :sell)
42
+ def strat_five = @results << :hold
43
+ end
@@ -0,0 +1,19 @@
1
+ # lib/sqa/strategry/ema.rb
2
+
3
+ require_relative 'common'
4
+
5
+ class SQA::Strategy::EMA
6
+ extend SQA::Strategy::Common
7
+
8
+ def self.trade(vector)
9
+ ema_trend = vector.ema[:trend]
10
+
11
+ if :up == ema_trend
12
+ :buy
13
+ elsif :down == ema_trend
14
+ :sell
15
+ else
16
+ :hold
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,19 @@
1
+ # lib/sqa/strategry/mp.rb
2
+
3
+ require_relative 'common'
4
+
5
+ class SQA::Strategy::MP
6
+ extend SQA::Strategy::Common
7
+
8
+ def self.trade(vector)
9
+ mp = vector.market_profile=:mixed,
10
+
11
+ if :resistance == mp
12
+ :sell
13
+ elsif :support == mp
14
+ :buy
15
+ else
16
+ :hold
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,15 @@
1
+ # lib/sqa/strategry/mr.rb
2
+
3
+ require_relative 'common'
4
+
5
+ class SQA::Strategy::MR
6
+ extend SQA::Strategy::Common
7
+
8
+ def self.trade(vector)
9
+ if vector.mr
10
+ :sell
11
+ else
12
+ :hold
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,5 @@
1
+ # SQA::Strategy::Random
2
+
3
+ A random flip of a three headed coin.
4
+
5
+ Responds with a :buy, :sell or :hold recommendation based upon a random number. Out of 9 possible random numbers, the lower third, middle third and upper third of the number set are used to determine the recommendation.
@@ -0,0 +1,18 @@
1
+ # lib/sqa/strategry/random.rb
2
+
3
+ require_relative 'common'
4
+
5
+ class SQA::Strategy::Random
6
+ extend SQA::Strategy::Common
7
+
8
+ def self.trade(vector)
9
+ case rand(9)
10
+ when (0..2)
11
+ :buy
12
+ when (3..5)
13
+ :sell
14
+ else
15
+ :hold
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,19 @@
1
+ # lib/sqa/strategry/rsi.rb
2
+
3
+ require_relative 'common'
4
+
5
+ class SQA::Strategy::RSI
6
+ extend SQA::Strategy::Common
7
+
8
+ def self.trade(vector)
9
+ rsi_trend = vector.rsi[:trend]
10
+
11
+ if :over_bought == rsi_trend
12
+ :sell
13
+ elsif :over_sold == rsi_trend
14
+ :buy
15
+ else
16
+ :hold
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,19 @@
1
+ # lib/sqa/strategry/sma.rb
2
+
3
+ require_relative 'common'
4
+
5
+ class SQA::Strategy::SMA
6
+ extend SQA::Strategy::Common
7
+
8
+ def self.trade(vector)
9
+ sma_trend = vector.rsi[:trend]
10
+
11
+ if :up == sma_trend
12
+ :buy
13
+ elsif :down == sma_trend
14
+ :sell
15
+ else
16
+ :hold
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,57 @@
1
+ # lib/sqa/strategy.rb
2
+
3
+ class SQA::Strategy
4
+ attr_accessor :strategies
5
+
6
+ def initialize
7
+ @strategies = []
8
+ end
9
+
10
+ def add(a_strategy)
11
+ raise SQA::BadParameterError unless [Class, Method].include? a_strategy.class
12
+
13
+ a_proc = if Class == a_strategy.class
14
+ a_strategy.method(:trade)
15
+ else
16
+ a_strategy
17
+ end
18
+
19
+ @strategies << a_proc
20
+ end
21
+
22
+ def execute(v)
23
+ result = []
24
+ # TODO: Can do this in parallel ...
25
+ @strategies.each { |signal| result << signal.call(v) }
26
+ result
27
+ end
28
+
29
+ def auto_load(except: [:common], only: [])
30
+ dir_path = Pathname.new(__dir__) + "strategy"
31
+ except = Array(except).map{|f| f.to_s.downcase}
32
+ only = Array(only).map{|f| f.to_s.downcase}
33
+
34
+ dir_path.children.each do |child|
35
+ next unless ".rb" == child.extname.downcase
36
+
37
+ basename = child.basename.to_s.split('.').first.downcase
38
+
39
+ next if except.include? basename
40
+ next if !only.empty? && !only.include?(basename)
41
+
42
+ print "loading #{basename} ... "
43
+ load child
44
+ puts "done"
45
+ end
46
+
47
+ nil
48
+ end
49
+
50
+ def available
51
+ ObjectSpace.each_object(Class).select { |klass|
52
+ klass.to_s.start_with?("SQA::Strategy::")
53
+ }
54
+ end
55
+ end
56
+
57
+
@@ -0,0 +1,54 @@
1
+ # Trading Strategies
2
+
3
+ A **strategy** is implemented as a class within the namespace of SQA::Strategy -- see the file lib/sqa/strategy/random.rb as an example. All strategy classes should have a class method **trade** which is the primary entry point into the strategy. The class can be extended using the SQA::Strategy::Common module which adds common class methods such as **trade_against** and **desc**
4
+
5
+ ## The **trade** Class Method
6
+
7
+ The method accepts a single parameter called a vector. The vector parameter is an object that contains the information required by the strategy in order to make a recommendation. In the examples provided this object is an OpenStruct. You can either follow this pattern or use your own; however, at this time only one parameter is allowed for the trade method.
8
+
9
+ ## The **trade_against** Class Method
10
+
11
+ If **trade** is consistently wrong many more times than it is right, don't throw it out. Just start using the **trade_against** class method instead. This method takes the recommendation of the **trade** class method and suggests the opposite.
12
+
13
+ ## The **desc** Class Method
14
+
15
+ You can document your strategy in a markdown formatted file. The **desc** class method will look for the filename in the same directory as the strategy file. This is typically in the lib/sqa/strategy directory.
16
+
17
+ The **desc** class method will find the markdown file and return its contents as a String so that you can do with it as you please.
18
+
19
+ ## Example Strategies
20
+
21
+ The follow examples are provided:
22
+
23
+ * ema.rb
24
+ * mp.rb
25
+ * mr.rb
26
+ * random.rb
27
+ * rsi.rb
28
+ * sma.rb
29
+
30
+ ## Usage
31
+
32
+ Tge SQA::Strategy class manages an Array of trading strategies. You can add to the Array multiple strategies like this:
33
+
34
+ ```ruby
35
+ require 'sqa'
36
+ ss = SQA::Strategy.new
37
+ ss.add SQA::Strategy::Random.method(:trade)
38
+ ss.add SQA::Strategy::SMA.method(:trade)
39
+ ss.add SQA::Strategy::EMA
40
+ ```
41
+
42
+ Note that if your primary entry point to the trading strategy class is **trade** then the parameter to the **add** function does not have to include the ".method(:entry_point)" -- just use the class name by itself. If you do not use **trade** as as class method in you strategy class, then the **trade_against** method added by the SQA::Strategy::Common module will not work.
43
+
44
+ If you want to evaluate the **trade_against** class method in a strategy then you must the "ss.add SQA::Strategy::Random.method(:trade_against)" pattern.
45
+
46
+ Then for any specific stock create your vector object that contains all the information that the trading strategies need in order to make their recommendations. Once you have you vector you can then **execute** the Array of strategies.
47
+
48
+ ```ruby
49
+ vector = ...
50
+ result = ss.execute(vector)
51
+ ```
52
+
53
+ **result** will be an Array of recommendations from the different strategies on whether to :bur, :sell or :hold.
54
+
data/lib/sqa/version.rb CHANGED
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module SQA
4
- VERSION = "0.0.2"
4
+ module Version
5
+ VERSION = "0.0.4"
6
+ end
5
7
  end
data/lib/sqa.rb CHANGED
@@ -12,22 +12,39 @@ require 'descriptive_statistics'
12
12
  require 'mixlib/config'
13
13
  require 'nenv'
14
14
  require 'pathname'
15
+ require "version_gem"
15
16
 
16
17
  unless defined?(HOME)
17
18
  HOME = Pathname.new(Nenv.home)
18
19
  end
19
20
 
21
+
20
22
  module SQA
21
23
  module Config
22
24
  extend Mixlib::Config
23
25
  config_strict_mode true
24
26
 
25
- default :data_dir, HOME + "sqa_data"
27
+ default :data_dir, HOME + "sqa_data"
28
+ default :plotting_library, :gruff
29
+ default :lazy_update, false
30
+ end
31
+
32
+ def self.init
33
+ Daru.lazy_update = Config.lazy_update
34
+ Daru.plotting_library = Config.plotting_library
35
+
36
+ nil
26
37
  end
27
38
  end
28
39
 
29
40
  require_relative "sqa/data_frame"
30
41
  require_relative "sqa/errors"
31
42
  require_relative "sqa/indicator"
43
+ require_relative "sqa/strategy"
32
44
  require_relative "sqa/stock"
33
45
  require_relative "sqa/version"
46
+
47
+
48
+ SQA::Version.class_eval do
49
+ extend VersionGem::Basic
50
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sqa
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.2
4
+ version: 0.0.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Dewayne VanHoozer
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-08-16 00:00:00.000000000 Z
11
+ date: 2023-08-17 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: Simplistic playpen (e.g. not for serious use) for doing technical analysis
14
14
  of stock prices.
@@ -26,6 +26,9 @@ files:
26
26
  - Rakefile
27
27
  - bin/sqa
28
28
  - checksums/sqa-0.0.1.gem.sha512
29
+ - checksums/sqa-0.0.2.gem.sha512
30
+ - checksums/sqa-0.0.3.gem.sha512
31
+ - checksums/sqa-0.0.4.gem.sha512
29
32
  - docs/README.md
30
33
  - docs/average_true_range.md
31
34
  - docs/bollinger_bands.md
@@ -46,6 +49,7 @@ files:
46
49
  - docs/requirements.md
47
50
  - docs/simple_moving_average.md
48
51
  - docs/stochastic_oscillator.md
52
+ - docs/strategy.md
49
53
  - docs/true_range.md
50
54
  - lib/sqa.rb
51
55
  - lib/sqa/activity.rb
@@ -76,6 +80,17 @@ files:
76
80
  - lib/sqa/indicator/true_range.rb
77
81
  - lib/sqa/protfolio.rb
78
82
  - lib/sqa/stock.rb
83
+ - lib/sqa/strategy.rb
84
+ - lib/sqa/strategy/common.rb
85
+ - lib/sqa/strategy/consensus.rb
86
+ - lib/sqa/strategy/ema.rb
87
+ - lib/sqa/strategy/mp.rb
88
+ - lib/sqa/strategy/mr.rb
89
+ - lib/sqa/strategy/random.md
90
+ - lib/sqa/strategy/random.rb
91
+ - lib/sqa/strategy/rsi.rb
92
+ - lib/sqa/strategy/sma.rb
93
+ - lib/sqa/strategyREADME.md
79
94
  - lib/sqa/version.rb
80
95
  homepage: https://github.com/MadBomber/sqa
81
96
  licenses: