Skip to content

Commit

Permalink
0.1.0.999
Browse files Browse the repository at this point in the history
  • Loading branch information
ShichenXie committed Apr 14, 2019
1 parent 2e8eb4f commit 8b6f736
Show file tree
Hide file tree
Showing 19 changed files with 661 additions and 319 deletions.
4 changes: 2 additions & 2 deletions CRAN-RELEASE
@@ -1,2 +1,2 @@
This package was submitted to CRAN on 2019-03-14.
Once it is accepted, delete this file and tag the release (commit 517699421c).
This package was submitted to CRAN on 2019-03-15.
Once it is accepted, delete this file and tag the release (commit 77fe8e348e).
2 changes: 1 addition & 1 deletion DESCRIPTION
@@ -1,5 +1,5 @@
Package: pedquant
Version: 0.1.0
Version: 0.1.0.999
Title: Public Economic Data and Quantitative Analysis
Description:
Provides an interface to access public economic and financial data for
Expand Down
2 changes: 2 additions & 0 deletions NAMESPACE
Expand Up @@ -13,9 +13,11 @@ export(md_stock_financials)
export(md_stock_symbol)
export(pd_code)
export(pq_addti)
export(pq_backtest)
export(pq_index)
export(pq_perf)
export(pq_plot)
export(pq_portfolio)
export(pq_return)
export(pq_to_freq)
import(TTR)
Expand Down
5 changes: 5 additions & 0 deletions NEWS.md
@@ -1,3 +1,8 @@
# pedquant 0.1.0.99

* Added functions of pq_portfolio and pq_backtest
* Fixed multiple bugs

# pedquant 0.1.0

* Added a `NEWS.md` file to track changes to the package.
Expand Down
4 changes: 4 additions & 0 deletions R/condition_helper_fun.R
Expand Up @@ -46,6 +46,7 @@ check_fromto = function(fromto, type="date", shift = 0) {
get_fromto = function(date_range, from, to, min_date, default_date_range = 'max') {
date_range = check_date_range(date_range, default = default_date_range)
to = check_fromto(to)
min_date = check_fromto(min_date)

if (is.null(from)) {
if (date_range == "max") {
Expand Down Expand Up @@ -77,6 +78,7 @@ get_fromto = function(date_range, from, to, min_date, default_date_range = 'max'
} else {
from = check_fromto(from)
}
if (from < min_date) from = min_date

# set class
if (class(to) == "Date") {
Expand Down Expand Up @@ -447,6 +449,8 @@ api_key = function(src){
select_rows_df = function(dt, column=NULL, input_string=NULL, onerow=FALSE) {
seleted_rows = NULL

if (onerow) input_string = input_string[1]

while (is.null(seleted_rows) || nrow(seleted_rows) == 0) { # stop looping, if selected rows >=1
if (is.null(input_string)) {
print(setDT(copy(dt))[,lapply(.SD, format)], topn = 50)
Expand Down
2 changes: 1 addition & 1 deletion R/md_2bond.R
Expand Up @@ -111,7 +111,7 @@ md_bond1_fred = function(syb, from, to) {

# libor in history
dt_bond_hist = ed_fred(
bond_symbol_fred[symbol == syb, symbol_fred], from=from, to=to, print_step=0L
bond_symbol_fred[symbol %in% syb, symbol_fred], from=from, to=to, print_step=0L
)[[1]][,`:=`(symbol_fred = symbol, symbol = NULL, name = NULL
)][bond_symbol_fred, on='symbol_fred', nomatch=0
][, .(symbol, name, date, value, geo, unit)
Expand Down
7 changes: 7 additions & 0 deletions R/md_stock1_.R
Expand Up @@ -85,5 +85,12 @@ md_stock = function(symbol, source = "yahoo", freq = "daily", date_range = "3y",

# data
dat = try(do.call(paste0("md_stock_", source), args=list(symbol = syb, freq = freq, from = from, to = to, print_step = print_step, env = env, adjust=adjust, zero_rm=zero_rm, na_rm=na_rm, type=type, ...)), silent = TRUE)

# remove error symbols
error_symbols = names(dat)[which(sapply(dat, function(x) inherits(x, 'try-error')))]
if (length(error_symbols) > 0) {
warning(sprintf('The following symbols can\'t imported:\n%s', paste0(error_symbols, collapse=', ')))
dat = dat[setdiff(names(dat), error_symbols)]
}
return(dat)
}
167 changes: 94 additions & 73 deletions R/md_stock1_163.R

Large diffs are not rendered by default.

81 changes: 23 additions & 58 deletions R/md_stock_symbol.R
Expand Up @@ -209,71 +209,36 @@ md_stock_symbol_hk = function(source="163", return_price=FALSE) {
return(stock_list_hk)
}

#' @import data.table
#' @importFrom jsonlite fromJSON
md_stock_symbol_163 = function() {
fun_symbol_163 = function(link, mkt) {
plate_ids = . = symbol = name = sec = province = industry = sector = NULL
# rt = GET(link, add_headers(
# 'Connection' = 'keep-alive',
# 'User-Agent' = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36',
# 'Upgrade-Insecure-Requests'='1'
# ))
jsonDat = fromJSON(link)
jsonDF = setDT(jsonDat$list)[,date:=as.Date(jsonDat$time)]
setnames(jsonDF, sub("\\.", "", tolower(names(jsonDF))) )

# industry sector for stock
if (mkt == "stock") {
jsonDF2 = jsonDF[,`:=`(
province = sub(".*(dy\\d+).*", "\\1", plate_ids),
plate_ids = sub("dy\\d+","",plate_ids)
)][,`:=`(
sector = sub(".*(hy\\d{3}0{3}).*", "\\1", plate_ids),
industry = sub(".*(hy\\d+).*", "\\1", sub("hy\\d{3}0{3}","",plate_ids))
)][,.(symbol, name, province, sector, industry)]
} else {
jsonDF2 = jsonDF[, symbol := paste0("^",symbol)][,.(symbol, name)]
}

return(jsonDF2)
}

symbol_163_format = function(df_symbol) {
type = . = id = name = symbol = tags = market = submarket = region = exchange = board = prov = indu = sec = NULL

symbol_url_163 = list(
A = "http://quotes.money.163.com/hs/service/diyrank.php?host=http%3A%2F%2Fquotes.money.163.com%2Fhs%2Fservice%2Fdiyrank.php&page=0&query=STYPE%3AEQA&fields=NO%2CSYMBOL%2CNAME%2CPLATE_IDS%2CSNAME%2CCODE&sort=CODE&order=desc&count=100000&type=query",
B = "http://quotes.money.163.com/hs/service/diyrank.php?host=http%3A%2F%2Fquotes.money.163.com%2Fhs%2Fservice%2Fdiyrank.php&page=0&query=STYPE%3AEQB&fields=NO%2CSYMBOL%2CNAME%2CPLATE_IDS%2CSNAME%2CCODE&sort=PERCENT&order=desc&count=100000&type=query",
ind_sse = "http://quotes.money.163.com/hs/service/hsindexrank.php?host=/hs/service/hsindexrank.php&page=0&query=IS_INDEX:true;EXCHANGE:CNSESH&fields=SYMBOL,NAME&sort=SYMBOL&order=asc&count=10000&type=query",
ind_szse = "http://quotes.money.163.com/hs/service/hsindexrank.php?host=/hs/service/hsindexrank.php&page=0&query=IS_INDEX:true;EXCHANGE:CNSESZ&fields=SYMBOL,NAME&sort=SYMBOL&order=asc&count=10000&type=query"
)

df_symbol = try(rbindlist(mapply(fun_symbol_163, symbol_url_163, c("stock","stock","index","index"), SIMPLIFY = FALSE), fill = TRUE), silent = TRUE)
if ('try-error' %in% class(df_symbol)) {
df_symbol = setDT(copy(symbol_stock_163))
}
# merge jsonDF with prov_indu_163
prov_indu_163 = setDT(copy(prov_indu_163))

symbol_163_format = function(df_symbol) {
type = . = id = name = symbol = tags = market = submarket = region = exchange = board = prov = indu = sec = NULL

# merge jsonDF with prov_indu_163
prov_indu_163 = setDT(copy(prov_indu_163))

if (all(c('province', 'industry', 'sector') %in% names(df_symbol))) {
df_symbol = merge(df_symbol, prov_indu_163[type=="prov",.(province=id, prov=name)], by = "province", all.x = TRUE)
df_symbol = merge(df_symbol, prov_indu_163[type=="indu",.(industry=id, indu=name)], by = "industry", all.x = TRUE)
df_symbol = merge(df_symbol, prov_indu_163[type=="indu",.(sector=id, sec=name)], by = "sector", all.x = TRUE)

df_symbol = copy(df_symbol)[, `:=`(
market = ifelse(grepl("\\^", symbol), "index", "stock")#,
# region = "cn"
)][, tags:=mapply(tags_symbol_stockcn, symbol, market)
][, c("exchange","submarket","board"):=tstrsplit(tags,",")
][, .(market, submarket, exchange, board, symbol, name, province=prov, sector=sec, industry=indu)
][order(-market, exchange, symbol)]

return(df_symbol)
}

return(symbol_163_format(df_symbol))

if (!('market' %in% names(df_symbol))) {
df_symbol = copy(df_symbol)[, `:=`(market = ifelse(grepl("\\^", symbol), "index", "stock") )]
}
df_symbol = df_symbol[, tags := tags_symbol_stockcn(symbol, market)[,tags]
][, c("exchange","submarket","board") := tstrsplit(tags,",")
# ][, .(market, submarket, exchange, board, symbol, name, province=prov, sector=sec, industry=indu)
][order(-market, exchange, symbol)
][, symbol := check_symbol_for_yahoo(symbol)
][, tags := NULL]

return(df_symbol)
}
#' @import data.table
#' @importFrom jsonlite fromJSON
md_stock_symbol_163 = function() {
df_syb = dat = md_stock_spotall_163(symbol = c('a', 'b', 'index'), only_symbol=TRUE)
return(df_syb)
}

# stock list of nasdaq
Expand Down
19 changes: 15 additions & 4 deletions R/pq_addti.R
Expand Up @@ -31,7 +31,15 @@
# return(roll_max(x, n = n, align = "right", fill = NA, ...))
# }

bias = function(x, n=10, maType='SMA') {
bias_orig = (x/do.call(maType, list(x=x, n=n))-1)*100

bias_orig/runSD(bias_orig, n = 1, cumulative = TRUE)
}

maroc = function(x, n=10, m=3, maType='SMA') {
ROC(do.call(maType, list(x=x, n=n)), n=m)*100
}

# [1] "adjRatios" "growth" "lags" "rollSFM" "runPercentRank"
# Technical Overlays / Indicators
Expand All @@ -41,7 +49,7 @@ ti_overlays_indicators = function() {
'runMin', 'runMax', 'runMean', 'runMedian',
'BBands', 'PBands',
'DonchianChannel', 'SAR', 'ZigZag'),
indicators = c('runSD', 'runMAD', 'aroon', 'CCI', 'VHF', 'TDI', 'ADX', 'ATR', 'EMV', 'chaikinVolatility', 'volatility', 'OBV', 'chaikinAD', 'CLV', 'CMF', 'MFI', 'williamsAD', 'ROC', 'momentum', 'KST', 'TRIX', 'MACD', 'DPO', 'DVI', 'ultimateOscillator', 'RSI', 'CMO', 'stoch', 'SMI', 'WPR')
indicators = c('runSD', 'runMAD', 'aroon', 'CCI', 'VHF', 'TDI', 'ADX', 'ATR', 'EMV', 'chaikinVolatility', 'volatility', 'OBV', 'chaikinAD', 'CLV', 'CMF', 'MFI', 'williamsAD', 'ROC', 'momentum', 'KST', 'TRIX', 'MACD', 'DPO', 'DVI', 'ultimateOscillator', 'RSI', 'CMO', 'stoch', 'SMI', 'WPR', 'bias', 'maroc')
)
}

Expand Down Expand Up @@ -186,7 +194,7 @@ ti_fst_arg = function() {
HL = c('aroon', 'EMV', 'chaikinVolatility', 'DonchianChannel', 'SAR', 'ZigZag'),
price = c('TDI', 'VHF', 'OBV', 'KST', 'TRIX', 'EVWMA', 'VWAP', 'DVI', 'RSI'),
prices = 'PBands',
x = c('ROC','momentum','SMA','EMA','DEMA','WMA','ZLEMA','VMA','HMA','ALMA','GMMA','runSum','runMin','runMax','runMean','runMedian','runCov','runCor','runVar','runSD','runMAD','wilderSum','MACD','DPO','CMO')
x = c('ROC','momentum','SMA','EMA','DEMA','WMA','ZLEMA','VMA','HMA','ALMA','GMMA','runSum','runMin','runMax','runMean','runMedian','runCov','runCor','runVar','runSD','runMAD','wilderSum','MACD','DPO','CMO', 'bias', 'maroc')
)
}
ti_sec_arg = function() {
Expand Down Expand Up @@ -242,7 +250,10 @@ addti1 = function(dt, ti, col_formula = FALSE, ...) {
if (length(w)==1 & inherits(w, "character")) w = dt[,w,with=FALSE]
arg_lst = c(arg_lst, list(w=w))
}
arg_lst = c(arg_lst, list(...))
arg_lst = c(
arg_lst,
list(...)[setdiff(names(list(...)), c('color', 'position', 'hline', 'height'))]
)
arg_lst = arg_lst[unique(names(arg_lst))]
dtti = data.table(do.call(ti, args = arg_lst))

Expand Down Expand Up @@ -337,7 +348,7 @@ pq1_addti = function(dt, ...) {
#' \item volatility measures: ATR, chaikinVolatility, volatility, SNR
#' \item money flowing into/out: OBV, chaikinAD, CLV, CMF, MFI, williamsAD
#' \item rate of change / momentum: ROC, momentum, KST, TRIX
#' \item oscillator: MACD, DPO, DVI, ultimateOscillator; RSI, CMO; stoch, WPR, SMI
#' \item oscillator: MACD, DPO, DVI, ultimateOscillator; RSI, CMO; stoch, SMI, WPR
#' }
#'
#' @examples
Expand Down
122 changes: 114 additions & 8 deletions R/pq_backtest.R
@@ -1,6 +1,6 @@
# back testing ------
# [Backtesting Strategies with R](https://timtrice.github.io/backtesting-strategies/)
#
# https://www.google.com/url?q=https://docs.google.com/presentation/d/1fGzDc-LFfCQJKHHzaonspuX1_TTm1EB5hlvCEDsz7zw/pub?&sa=D&ust=1553613215848000&usg=AFQjCNGxM3xCdMK4Lao1yUo_9lblAqFqEA

# - quantstrat 0.9.1739
# - blotter 0.9.1741
Expand All @@ -22,10 +22,10 @@

# Terminology ------
# - BTO: Buy to Open (open long positions)
# - STC: Sell to close (close long positions)
# - STO: Sell to open (open short positions)
# - BTC: Buy to close (close short positions)
# - SL: Stop-limit order
# - STO: Sell to open (open short positions)
# - STC: Sell to close (close long positions)
# - TS: Trailing-stop order


Expand All @@ -43,19 +43,125 @@

# signal
# - sigComparison
## - gt greater than
## - lt less than
## - eq equal to
## - gte greater than or equal to
## - lte less than or equal to
# - sigCrossover
# - sigFormula
# - sigPeak
# - sigThreshold
# - sigTimestamp

# rule
# greater than
gt = function(a, b) setDT(list(a=a,b=b))[, a > b]
# less than
lt = function(a, b) setDT(list(a=a,b=b))[, a < b]
# equal than
eq = function(a, b) setDT(list(a=a,b=b))[, a == b]
# greater than or equal to
gte = function(a, b) setDT(list(a=a,b=b))[, a >= b]
# less than or equal to
lte = function(a, b) setDT(list(a=a,b=b))[, a <= b]
# crossover
co = function(a, b) setDT(list(a=a,b=b))[, a>b & shift(b>=a,type='lag')]

# rule type: risk, order, rebalance, exit, enter, chain
# order type: limit, stoplimit, stoptrailing, market, iceberg


#' Strategy backtesting
#'
#' \code{pq_backtest} provides a simple way to backtest a trade strategy.
#'
#' @param dt a list/dataframe of time series dataset
#' @param addti list of technical indicators or numerical columes in dt. For technical indicator, it is calculated via \code{pq_addti}, which including overlay and oscillator indicators.
#' @param init_equity initial equity
#' @param date_range date range. Available value includes '1m'-'11m', 'ytd', 'max' and '1y'-'ny'. Default is max.
#' @param from the start date. Default is NULL. If it is NULL, then calculate using date_range and end date.
#' @param to the end date. Default is the current date.
#' @param rule rules of trade strategy.
#' @param stp_lmt_pct stop limit percent
#' @param show_plot show plot
#' @param ... Additional parameters
#'
#' @export
pq_backtest = function(
type = 'long', # 'short'

stop_limit
dt,
addti = NULL,
init_equity = NULL,
date_range = 'max',
from = NULL,
to = Sys.Date(),
rule = list(
long = list(
enter = NULL,
exit = NULL,
price = NULL,
stop_limit = NULL,
position = NULL
)
),
stp_lmt_pct = 0.05,
show_plot = TRUE, ...
) {
. = symbol = price = type = ssec = value = NULL

if (inherits(dt, 'list')) dt = rbindlist(dt)
dat = dt
if (!is.null(addti) & inherits(addti, 'list')) dat = do.call(pq_addti, args = c(list(dt=dat), addti))
if (inherits(dat, 'list')) dat = rbindlist(dat)

if ('long' %in% names(rule)) {
cat('[INFO] backtesting ... \n')

dat = dat[, bto := eval(parse(text = rule$long$enter))
][, stc := eval(parse(text = rule$long$exit))
][, stp := eval(parse(text = rule$long$stop_limit))]

position = rule$long$position


w_long = setDT(list(long_short='0',type='0',symbol='0',date=as.Date('1-1-1-1'),price=0,position=0))[.0]
bto = NULL
stc = NULL
stp = NULL
stplmt = NULL

for (i in dat[,.I]) {
if (!is.null(bto) && bto[,.N==1]) {
w_long = rbind(w_long, dat[i, .(long_short='long', type='bto', symbol, date, price=open, position = position)])
stplmt = dat[i,open*(1-stp_lmt_pct)]
}
if (((!is.null(stc) && stc[,.N==1])
|| (!is.null(stp) && stp[,.N==1])
|| (dat[i, open < stplmt]))
&& any(w_summary(w_long)[,position>0])) w_long = rbind(w_long, dat[i, .(long_short='long', type='stc', symbol, date, price=open, position = w_summary(w_long)[, -position])])


bto = dat[i][which(bto)]
stc = dat[i][which(stc)]
stp = dat[i][which(stp)]
}

w = copy(w_long)
}

}
if (show_plot) {
w2 = w[,.(date, w_price=price, w_position=position, w_type=type)]
perf = pq_perf(pq_portfolio(ssec, w))[['equity']][,.(date, performance=value)]

addti_lst = list(w=list(), performance=list())
show_ti = list(...)$show_ti
if (show_ti) addti_lst = c(addti_lst, addti)

p = pq_plot(
dt = Reduce(function(x,y) merge(x,y, all=TRUE, by='date'), list(dt, w2, perf)),
addti = addti_lst, ...)
print(p)
}
return(w)
}


0 comments on commit 8b6f736

Please sign in to comment.