Getting Started with R

最近开始学习用R做数据分析与量化投资,于是写了一篇R语言的入门教程,希望能帮助大家快速上手。我的知乎|简书|CSDN|微信公众号PurePlay 会不定期分享量化投资与R干货, 欢迎大家关注!

文章目录

  • 1.1 安装
  • 1.2 获取帮助
  • 1.3 工作空间
  • 1.4 R文件的输入/输出
  • 1.5 变量
  • 1.6 运算符
  • 1.7 数据容器
  • 1.8 plot()绘图
  • 1.9 函数
  • 1.10 控制结构
  • 1.11 实例:下载指定股票价格数据,计算收益率相关性

1. R语言

R是一种开源的解释型脚本语言,多年来在统计学家,数据科学从业人员和学者中非常流行。 它本质上是一种函数式编程语言( functional programming language),同时支持面向对象和命令式编程范例。 R具有数以千计的附加统计和分析库可供下载,在定量金融和学术领域非常受欢迎。

R中的所有内容(包括函数)都是一个对象,可以直接传递给其他函数。

1.1 安装

1.1.1 R安装

进入官网:http://www.r-project.org/。 单击“Getting Started”部分中的下载R链接,或者单击左侧菜单中的“ CRAN”链接。 选择最接近目标计算机所在位置的适当镜像站点。 有关安装R的更多详细说明

推荐的R代码编辑器:Rstudio,VScode

1.1.2 扩展包安装和载入

R扩展包官方汇总链接:https://mirrors.tuna.tsinghua.edu.cn/CRAN/

(1)安装扩展包

  • R与Rstuidio窗口操作均可完成安装,以Rsudio为例:点击Rstudio界面右下角文件区的Packages,点击Install,在弹出对话框中 , 填入需要安装的包
  • 命令安装
# 获取R存放已经安装的包的位置,默认位置是R的安装路径下的library文件夹
.libPaths()

# 安装:扩展包名称需要加引号
# destdir:指定下载的二进制zip文件存放的位置,默认是临时会话的downloaded_packages文件夹下
# lib:指定下载的二进制zip软件包被解压后的安装位置,默认值的是.libPaths()获取的路劲
install.packages("ggplot2")
install.packages("ggplot2",
                 destdir="D:/Download/Package/R",
                 lib="D:/Program Files/R/R-3.6.1/library")

(2)载入扩展包

  • Rstudio界面:点击Rstudio界面右下角文件区的 Packages,找到你要载入的包 , 在
    前面的方框中打上勾
  • R界面:点击R界面上方的“程序包”,点击 加载程序包,在弹出的窗口中选择需要,载入的包
  • 命令载入
# 扩展包名称可以不加引号
library("ggplot2")
library("ggplot2", lib.loc="C:/Program Files/R/R3.2.2/library")

(3)量化常用扩展包

  • 数据管理:包括数据集抓取、存储、读取、时间序列、数据处理等,涉及的R包有zoo(时间序列对象)、xts(时间序列处理)、timeSeries(Rmetrics系时间序列对象)、timeDate(Rmetrics系时间序列处理)、data.table(数据处理)、quantmod(数据下载和图形可视化)、RQuantLib(QuantLib数据接口)、WindR(Wind数据接口)、RJDBC(数据库访问接口)rhadoop(Hadoop访问接口)、rhive(Hive访问接口)、rredis(Redis访问接口)、rmongodb(MongoDB访问接口)、SparkR(Spark访问接口)、fImport(Rmetrics系数据访问接口)等

  • 指标计算:包括金融市场的技术指标的各种计算方法,涉及的R包有TTR(技术指标)、TSA(时间序列计算)、urca(单位根检验)、fArma(Rmetrics系ARMA计算)、fAsianOptions(Rmetrics系亚洲期权定价)、fBasics(Rmetrics系计算工具)、fCopulae(Rmetrics系财务分析)、fExoticOptions(Rmetrics系期权计算)、fGarch(Rmetrics系Garch模型)、fNonlinear(Rmetrics系非线模型)、fOptions(Rmetrics系期权定价)、fRegression(Rmetrics系回归分析)、fUnitRoots(Rmetrics系单位根检验)等

  • 回测交易:包括金融数据建模,并验证历史数据验证模型的可靠性,涉及的R包有FinancialInstrument(金融产品)、quantstrat(策略模型和回测)、blotter(账户管理)、fTrading(Rmetrics系交易分析)等

  • 投资组合:对多策略或多模型进行管理和优化,涉及的R包有PortfolioAnalytics(组合分析和优化)、stockPortfolio(股票组合管理)、fAssets(Rmetrics系组合管理)等。

  • 风险管理:对持仓进行风险指标的计算和风险提示,涉及的R包有Performance-Analytics(风险分析)、fPortfolio(Rmetrics系组合优化)、fExtremes(Rmetrics系数据处理)

1.2 获取帮助

1.2.1 帮助文档

(1)Rstudio界面

点击Rstudio界面右下角文件区的 Help选项,在最后的搜索框中输入想了解的R包或者函数

(2)R界面

点击R界面的“帮助”,开启一个帮助网页

(3)输入命令

# 获取扩展包信息
help("readr")
? readr

# 获取函数信息
help(read_csv)
? read_csv

#搜索与关键词read_csv相关的帮助文档的信息
help.search('ggplot2')
?? ggplot2

# 获取函数ggplot的使用例子  
example("ggplot") 

# 给出有关键词ggplot的所有函数  
apropos("ggplot",mode="function")

# 打开搜索首页
RSiteSearch("") 

# 搜索有关键词ggplot的所有文档和邮件列表存档 
RSiteSearch("ggplot")
RSiteSearch("empirical")   
 
# 获取函数源代码
read_csv

# 列出当前已加载包中所含有的所有可用示例数据集
data()

1.2.2 实用网站

1.3 工作空间

工作空间就是当前R的工作环境

# 获取当前工作空间
getwd()

# 设定工作空间
# 路径用双引号或者单引号,必须完整,斜杠为\\或者/
# 文件夹必须存在,如果不存在,可以用dir.create()函数先创建,但文件夹需要逐个建立。
dir.create('D:/Workfiles/Finance/Quants/R')
setwd('D:/Workfiles/Finance/Quants/R')

list.dirs("D:/Workfiles/Finance/Quants/R") #列出当前工作目录下的文件夹
list.files("D:/Workfiles/Finance/Quants/R") #列出当前工作目录下的文件
ls()  #列出当前工作空间中的对象 
rm(x) #移除(删除)一个或者多个对象 
history(num) #显示最近使用过的num个命令(默认值为25) 
savehistory(“aaa”)   #保存命令历史到当前路径下文件aaa中(默认值为.Rhistory) 
loadhistory(“aaa”)   #载入一个命令历史文件aaa(默认值为.Rhistory) 
options()  ###显示或设置当前选项 
q() #退出R或者Rstudio

1.4 R文件的输入/输出

1.4.1 保存文件

利用Rstudio/R界面保存脚本文件;保存其他文件代码如下:

# 将运行结果保存成.R或者.txt文件,相对/绝对路径均可
sink("D:/Workfiles/Finance/Quants/R/try.R")
...program
sink()

# 将工作空间里的对象(结果、变量等)保存成.Rdata
save.image('try.RData')

# 图形文件保存:可输出成pdf/png/jpeg/bmp,方法类似
pdf('xxx.pdf') 
...program
dev.off()

1.4.2 打开文件

(1)利用Rstudio/R界面
(2)输入命令

# 打开.R文件/R的脚本文件
file.edit("D:/Workfiles/Finance/Quants/R/1.4如何获得帮助.R",fileEncoding = "UTF-8") 

# 运行R脚本文件
source("xxx.R",encoding = "UTF-8")

# 打开.Rdata文件
load('try.RData')

1.5 变量

变量为我们提供了我们的程序可以操作的命名存储。 变量名称由字母(区分大小写),数字和点或下划线字符组成,不允许使用空格或特殊字符。 变量名以字母或不以数字后跟的点开头。

变量名 合法性 原因
var_name2. 有效 有字母,数字,点和下划线
VAR_NAME% 无效 有字符'%'。只有点(.)和下划线允许的。
2var_name 无效 以数字开头
.var_name, var.name 有效 可以用一个点(.),但启动点(.),不应该后跟一个数字。
.2var_name 无效 起始点后面是数字使其无效。
_var_name 无效 开头_这是无效的

可以使用向左,向右和等于运算符来为变量赋值:

# 等号赋值
var1 = c(0,1,2,3)           
# 左赋值
var2 <- c("learn","R")   
# 右赋值  
c(TRUE,1) -> var3           

在R语言中,数据类型有:数值型Numeric,整型Integer,复数型Complex,字符串Character,逻辑型Logical,原型Raw。R中的变量不必声明变量类型,R将动态地找出运行时变量的类型。

展示工作空间中的变量:

# 展示工作空间中的所有变量
ls()
# 展示工作空间中变量名包含“var”的变量
ls(pattern = "var")
# 以点(.)开头的变量默认被隐藏,设置“all.names = TRUE”参数列出
ls(all.name = TRUE)

删除变量:

# 删除指定变量
rm(var1)
# 删除所有变量
rm(list = ls())

1.6 运算符

R语言中拥有如下几种运算符类型:算术运算符,关系运算符,逻辑运算符,赋值运算符等。

1.6.1 算术运算符

下表显示了R语言支持的算术运算符。 操作符对向量的每个元素起作用。

运算符 描述
+ 两个向量相加
- 两个向量相减
* 两个向量相乘
/ 将第一个向量与第二个向量相除
%% 两个向量求余
%/% 两个向量相除求商
^ 将第二向量作为第一向量的指数

1.6.2 关系运算符

下表显示了R语言支持的关系运算符。 将第一向量的每个元素与第二向量的相应元素进行比较。 比较的结果是布尔值。

运算符 描述
> 检查第一个向量的每个元素是否大于第二向量的相应元素。
< 检查第一个向量的每个元素是否小于第二个向量的相应元素。
== 检查第一个向量的每个元素是否等于第二个向量的相应元素。
<= 检查第一个向量的每个元素是否小于或等于第二向量的相应元素。
> = 检查第一个向量的每个元素是否大于或等于第二向量的相应元素。
!= 检查第一个向量的每个元素是否不等于第二个向量的相应元素。

1.6.3 逻辑运算符

下表显示了R语言支持的逻辑运算符。 它只适用于逻辑,数字或复杂类型的向量。 所有大于1的数字被认为是逻辑值TRUE。
将第一向量的每个元素与第二向量的相应元素进行比较。 比较的结果是布尔值。

运算符 描述
& 它被称为元素逻辑AND运算符。 它将第一向量的每个元素与第二向量的相应元素组合,并且如果两个元素都为TRUE,则给出输出TRUE。
| 它被称为元素逻辑或运算符。 它将第一向量的每个元素与第二向量的相应元素组合,并且如果元素为真,则给出输出TRUE。
! 它被称为逻辑非运算符。 取得向量的每个元素,并给出相反的逻辑值。

逻辑运算符&&和|| 只考虑向量的第一个元素,给出单个元素的向量作为输出。

运算符 描述
&& 逻辑AND运算符。 取两个向量的第一个元素,并且只有两个都为TRUE时才给出TRUE。
|| 逻辑OR运算符。 取两个向量的第一个元素,如果其中一个为TRUE,则给出TRUE。

1.6.4 赋值运算符

这些运算符用于向量赋值。

运算符 描述
<−=<<− 左分配
->->> 右分配

1.6.5 其他运算符

这些运算符用于特定目的,而不是一般的数学或逻辑计算。

运算符 描述
: 冒号运算符。 它为向量按顺序创建一系列数字。
%in% 此运算符用于标识元素是否属于向量。
%*% 此运算符用于将矩阵与其转置相乘。

1.7 数据容器

数据容器data container:向量Vectors,因子Factors,列表Lists,矩阵Matrices,数据帧Data Frames,数组Arrays

1.7.1 向量Vectors

向量类似一维数组,仅保存类型相同的数据, 如果将数字与字符混合,R会将任何数值的类型转换为字符。运算符c()用于创建存储数字,字符或布尔值的向量

first_vector <- c(1,2,3,4,5,6)
second_vector <- c('a', 'b', 'c','d')
third_vector <- c('a', 1, 2, 3)
# 向量合并
new_vector <- c(first_vector,second_vector)

向量索引:

vector <- c(1,2,3,4,5,6,7,8,9)
# 获取向量第四个元素
vector[4]
# 获取第四和第八个元素
# vector[4,8]是错误的
vector[c(4,8)]
# 获取第四到第八个元素
# 第四和第八个元素都会被取出
vector[4:8]

向量化允许我们同时对所有元素执行相同的操作。

vector <- c(1,2,3,4,5,6,7,8,9)
# 向量中所有元素除以3,并保留两位小数
round(vector/3,2)

1.7.2 列表Lists

列表可以同时包含不同类型的元素,如数字、字符串、向量、列表、矩阵、函数等。

myList <- list(a = c(1,2,3,4,5),
              b = matrix(1:10, nrow = 2, ncol = 5),
              c = data.frame(price = c(89.3, 98.2, 21.2),
              stock = c("MOT", "IBM", "CSCO"))
              )

通过位置或者元素名索引:

# 返回元素列表
list1 <- myList[1]
list2 <- myList[c(1,3)]
# 返回单个元素
element1 <- myList[[1]]
element2 <- myList[["b"]]

1.7.3 矩阵Matrix

矩阵类似一维数组,只能保存类型相同的数据。

# 按列排列
mat1 <- matrix(c(1,2,3,4,5,6),
              nrow=2, ncol=3) 

# 按行排列
mat2 <- matrix(c(1,2,3,4,5,6),
              nrow=2, ncol=3, byrow=TRUE)

# 定义行名和列名
dimnames(mat2) <- list(c('one','two'),
                      c('col1','col2','col3'))

# 展示矩阵的特征
attributes(mat2)

索引方法与向量类似,需要增加一个维度

mat2[1,3]
mat2[c(1,2),3]
mat2[1:2,2:3]
mat2[,2:3]

矩阵操作也是向量化的:

# 元素服从标准正太分布的100*10矩阵
mat <- matrix(rnorm(1000),nrow=100)
# 保留三位小数,展示前9行
head(round(mat,3),9)

1.7.4 数据框Dataframe

数据框是一个混合的二维数据容器,可以包含数字,字符,布尔值和因子类型。每当从外部环境将数据读入R时,结果对象最终都可能是 一个数据帧,格式类似于一个Excel表格。

df1 <- data.frame(price = c(89.2, 23.2, 21.2),
                symbol = c('MOT','AAPL','IBM'),
                action = c('Buy','Sell','Buy'))

数据框索引[,], $

df1[1,1]
# dataframe_name $ column_name
df1$symbol

创建数据框时,字符向量将自动转换为因子,它是离散变量的向量。 为了禁止将任何字符向量转换为因子,我们可以使用stringsAsFactors = FALSE参数。

df2 <- data.frame(price = c(89.2, 23.2, 21.2),
                symbol = c('MOT','AAPL','IBM'),
                action = c('Buy','Sell','Buy'),
                stringsAsFactors = FALSE)
df1$symbol
## [1] MOT  AAPL IBM 
## Levels: AAPL IBM MOT
df2$symbol
## [1] "MOT"  "AAPL" "IBM" 
class(df1$symbol)
## [1] "factor"
class(df2$symbol)
## [1] "character"

1.7.5 环境Environment

创建环境空间,定义环境空间中的变量:

env <- new.env()
# [[]]与$均可实现环境空间中的变量定义
env[["first"]] <- 5
env[["second"]] <- 6
env$third <- 7

查看环境空间

env
# 查看环境空间中的变量名
ls(env)
# 获取环境空间中的某一变量
get("first", envir = env)

删除环境空间中的变量

rm("second", envir = env)
ls(env)

环境空间的存储特征:不按值存储对象,而是将对象位置的地址存储在内存中。 由于较大的对象不必在每次传递时都进行复制,因此可以使用更有效的代码。

env1 <- env
env1$third <- 42
get("third", envir = env)

1.8 plot()绘图

R提供了许多高级的绘图扩展包,如ggplot2, ggvis, rCharts, rgi等,此处只介绍最基本的plot()函数绘图。

x <- c(1,2,3.2,4,3,2.1,9,19)
plot(x) # 散点图
plot(x, type = "l") # 折线图

?plot # 查看更多图表类型

绘制一个有标题、横纵轴名称、基本网格的图

plot(rnorm(100), # 正太分布随机数
     main = "Some Returns", # 图标题
     cex.main = 0.9, 
     xlab = "Time",# x轴标题
     ylab = "Returns", # y轴标题
    )
# 添加基本网格
grid()
# 添加水平线与垂直线
abline(v = 40, # x = 40处绘制垂直线
      lwd = 2, # 线宽
      lty = 1, # 线型
      )
abline(h = 1, # y = 1处绘制水平线
      lwd = 3, 
      lty = 3)
# 添加额外的文本
mtext("Some text at the bottom", side = 1)
# 添加图例
legend(50, -1, "A legend")

绘制子图

# 设定2行,2列子图格式
par(mfrow = c(2, 2))
# 第一张图
plot(rnorm(100), main = "Graph 1")
# 第二张图
plot(rnorm(100), main = "Graph 2", type = "l")
# 第三张图
plot(rnorm(100), main = "Graph3", type = "s")
abline(v = 50, lwd = 4)
# 第四张图
plot(rnorm(100), type = "h", main = "Graph4")
# 重置绘图窗口
par(mfrow = c(1,1))

查看绘图所用参数

# 法一
?plot.default
# 法二
formals(plot.default)

1.9 函数

R支持命令式、函数式、面向对象编程。

1.9.1 内置函数

R语言具有大量内置函数,以下展示一些常用内置函数

# 创建标准正态分布随机数
x <- rnorm(100, mean = 0, sd = 1)
# 返回向量长度
length(x)
# 计算均值、方差、中位数
mean(x)
sd(x)
median(x)
# 计算变量值域
range(x)
# 求和/累计求和
sum(x)
cumsum(x)
# 展示前3个元素
head(x, 3)
# 获取描述统计结果
summary(x)
# 将x从大到小排序
sort(x, decreasing = TRUE)
# 计算一阶差分
diff(x)
# 以1为步长创建1至10序列
seq(1, 10, 1)
# 打印:注意两种打印的区别
cat("hello\n")
print("hello\n")
# 展示最近输入的10行命令
history(10)
# 数值计算
sqrt(2)
exp(1)
cos(1)
sin(1)
# 求函数积分
integrand <- function(x) 1/((x+1)*sqrt(x))
integrate(integrand, lower=0, upper=Inf)

1.9.2 自定义函数

自定义函数的基本语法如下

function_name <- function(arg_1, arg_2, ...) {
    Function body
    return value
}

函数的不同部分

  • 函数名称:作为具有此名称的对象存储在R环境中。
  • 参数 :当函数被调用时,需要传递具体值到参数。 参数是可选的,一个函数可能不包含参数;参数可以有默认值。
  • 函数体 :函数体包含定义函数的功能语句集合。
  • 返回值 :返回值即函数的输出。

1.10 控制结构

1.10.1 分支

x = 1
if(x) {
    print("true")
} else {
    print("false")
}

1.10.2 循环

for(i in 1:5) {
    cat(i, "\n")
}

1.11 实例:下载指定股票价格数据,计算收益率相关性

过滤无效的股票代码:

FiltSymbols <- function(symbols) {
  # 将小写字母转换为大写
  symbols <- toupper(symbols)
  # 正则表达式验证证券代码是否有效
  valid <- regexpr("^[A-Z]{2,4}$", symbols)
  #返回
  return(sort(symbols[valid == 1]))
}

symbols <- FiltSymbols(c("xom", "aapl", "cvx","ibm", "GS","600000HS" ))
cat(symbols)

下载股票价格数据:

library("quantmod")
getPrices <- function(symbols) {
  # 输入:向量类型的股票代码
  # 输出:dataframe,包含多只股票的价格数据
  for (i in 1:length(symbols)) {
    if (i != 1) {
      # 获取第i支股票的价格数据
      tempData <- getSymbols(symbols[i], src="yahoo", 
                    from="2007-01-01",to="2017-08-31",
                    auto.assign = FALSE)
      # 将收盘价合并到prices中
      prices <- cbind(prices, tempData[,4])
      cat(paste("第", i, "支股票下载完成…\r"))
    } else {
      # 获取第一支股票的价格数据
      data <- getSymbols(symbols[i], src="yahoo", 
                          from="2007-01-01",to="2017-08-31",
                          auto.assign = FALSE)
      # 仅保留收盘价
      prices <- data[,4]
      cat("第1支股票下载完成…\r")
    }
  }
  return(prices)
}

prices <- getPrices(symbols)
print(prices, max = length(symbols)*5)

计算股票收益率相关性:

showCorrelations <- function(prices) {
  # 输入:dataframe,股票价格数据
  # 输出:相关系数矩阵,两两散点图矩阵
  
  # 将股价转换为对数收益率,参数2表示对列进行计算
  returns <- apply(prices, 2, function(x) diff(log(x)))
  # 计算相关系数矩阵
  print(cor(returns, use = "complete.obs"))
  # 绘制两两散点图矩阵
  pairs(returns, main = "Pairwise Return Scatter Plot")
}
showCorrelations(prices)


欢迎关注我的知乎|简书|CSDN|微信公众号PurePlay , 会不定期分享量化金融与R干货。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,445评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,889评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,047评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,760评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,745评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,638评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,011评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,669评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,923评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,655评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,740评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,406评论 4 320
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,995评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,961评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,197评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,023评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,483评论 2 342