Browse Source

first commit

will.hu 1 tháng trước cách đây
commit
36c6c80434

+ 15 - 0
code/DolphinDB/modules/fundit/_test.dos

@@ -0,0 +1,15 @@
+login("admin", "123456")
+listRemotePlugins()
+
+
+installPlugin("mysql")
+loadPlugin("mysql")
+
+conn = mysql::connect(`172.19.3.22, 3306, `pf_user, `MzBlMDA0OG, `mfdb)
+mysql::showTables(conn)
+
+
+installPlugin("odbc")
+loadPlugin("odbc")
+
+conn = odbc::connect("Dsn=FunditDB-dev-mfdb")

+ 292 - 0
code/DolphinDB/modules/fundit/dataPuller.dos

@@ -0,0 +1,292 @@
+module fundit::dataPuller
+ 
+/*
+ *  MySQL 连接,使用前应确保 loadPlugin("ODBC")已经被运行过
+ *
+ *  Create  20240711  使用ODBC连接MySQL数据库                                     Joey
+ *  
+ */
+def connect_mysql() {
+
+    // 阿里云的mysql被魔改过,当前DolphinDB无法支持MySQL插件,只能用ODBC
+    // loadPlugin("ODBC")
+
+    // conn = odbc::connect("Driver={MySQL ODBC 9.0 UNICODE Driver};Server=funditdb-dev.mysql.rds.aliyuncs.com;Database=mfdb;User=pf_user;Password=MzBlMDA0OG", "MySQL")
+
+    // 使用Windows的ODBC数据源事先设置号的连接
+    // conn = odbc::connect("Dsn=FunditDB-mfdb")
+    conn = odbc::connect("Dsn=FunditDB-dev-mfdb")
+
+//    t = odbc::query(conn, "SELECT * FROM pfdb.pf_portfolio_nav LIMIT 100")
+
+    return conn
+}
+
+/*
+ *  取指数周收益
+ *
+ *  get_index_weekly_rets(today(), 12)
+ */
+def get_index_weekly_rets(date_end, win) {
+
+    week_end = date_end.year()$STRING + date_end.weekOfYear()$STRING
+    week_start = date_end.year()$STRING + (date_end - 7 * (win-1)).weekOfYear()$STRING
+
+    s_query = "SELECT factor_id AS index_id, year_week, ret_1w
+               FROM pfdb.cm_factor_performance_weekly
+               WHERE isvalid = 1
+                 AND factor_id IN ('FA00000WKG', 'FA00000WKH')
+                 AND ret_1w IS NOT NULL
+                 AND year_week BETWEEN " + week_start + " AND " + week_end + "
+               UNION
+               SELECT fund_id AS index_id, year_week, ret_1w
+               FROM mfdb.fund_performance_weekly
+               WHERE isvalid = 1
+                 AND fund_id = 'IN0000007G'
+                 AND ret_1w IS NOT NULL
+                 AND year_week BETWEEN " + week_start + " AND " + week_end + "
+               ORDER BY year_week"
+
+    conn = connect_mysql()
+    m = odbc::query(conn, s_query)
+    
+    // 去掉year_week列
+    mt = (SELECT ret_1w FROM m PIVOT BY year_week, index_Id).slice(0:win, 1:)
+
+    conn.close()
+
+    return mt
+}
+
+/*
+ *  取组合周收益
+ *
+ *  这个脚本在mysql里就运行很慢,要1分钟以上
+ *
+ *  get_portfolio_weekly_rets(today(), 12)
+ */
+def get_portfolio_weekly_rets(date_end, win) {
+
+    week_end = date_end.year()$STRING + date_end.weekOfYear()$STRING
+    week_start = date_end.year()$STRING + (date_end - 7 * (win-1)).weekOfYear()$STRING
+
+    s_query = "SELECT portfolio_id, GROUP_CONCAT(ret_1w SEPARATOR ' ') AS rets
+               FROM ( SELECT portfolio_id, ret_1w 
+                      FROM pfdb.pf_portfolio_performance_weekly
+                      WHERE isvalid = 1
+                        AND ret_1w IS NOT NULL
+                        AND year_week BETWEEN " + week_start + " AND " + week_end + "
+                      ORDER BY portfolio_id, year_week ) t
+               GROUP BY portfolio_id
+               HAVING COUNT(1) = " + win$STRING + "
+               ORDER BY portfolio_id"
+
+    conn = connect_mysql()
+    m = odbc::query(conn, s_query)
+
+    conn.close()
+
+    return m
+}
+
+/*
+ *  取公私募基金月收益
+ *
+ *  get_fund_monthly_ret("'MF00003PW1','MF00003PW1'", 1990.01.01, today(), true)
+ */
+
+def get_fund_monthly_ret(fund_ids, start_date, end_date, isFromMySQL) {
+
+    yyyymm_start = start_date.temporalFormat("yyyy-MM")
+    yyyymm_end = end_date.temporalFormat("yyyy-MM")
+
+    if(isFromMySQL == true) {
+
+        ret_table_name = "mfdb.fund_performance"
+        
+        s_query = "SELECT fund_id, end_date, ret_1m AS ret, cumulative_nav AS nav, ret_ytd_a, ret_incep_a
+                   FROM " + ret_table_name + "
+                   WHERE fund_id IN (" + fund_ids + ")
+                      AND isvalid = 1
+                      AND ret_1m IS NOT NULL
+                      AND end_date BETWEEN '" + yyyymm_start + "' AND '" + yyyymm_end + "'
+                   ORDER BY fund_id, end_date"
+     
+        conn = connect_mysql()
+     
+        t = odbc::query(conn, s_query)
+     
+        conn.close()
+
+    } else {
+        
+        db = database(directory="/home/DolphinDB/server/database/fundit/")
+        
+        tb_local = loadTable(db, "fund_performance",,false)
+
+        s_col = (sqlCol("fund_id"), sqlCol("end_date"), sqlColAlias(<ret_1m>, "ret"), sqlColAlias(<cumulative_nav>, "nav"), sqlCol("ret_ytd_a"), sqlCol("ret_incep_a"))
+
+        s_where = expr(<fund_id>, in, fund_ids.strReplace("'", "").split(","))
+        
+        t = sql(s_col, tb_local, s_where).eval()
+    
+    }
+
+    return t
+}
+
+/*
+ * 取无风险月度利率
+ *
+ * get_risk_free_rate("IN0000000M", 1990.01.01, today())
+ */
+def get_risk_free_rate(index_id, start_date, end_date) {
+    
+    return get_fund_monthly_ret(index_id, start_date, end_date, true)
+}
+
+
+/*
+ * 取私募基金净值
+ *
+ * get_hedge_fund_nav_by_price_date("'HF000004KN','HF00018WXG'", 2024.05.01, true)
+ */
+def get_hedge_fund_nav_by_price_date(fund_ids, price_date, isFromMySQL) {
+
+    if(isFromMySQL == true) {
+
+        nav_table_name = "mfdb.nav"
+    
+        s_query = "SELECT fund_id, price_date, cumulative_nav AS cnav
+                   FROM " + nav_table_name + "
+                   WHERE fund_id IN (" + fund_ids + ")
+                   AND isvalid = 1
+                   AND cumulative_nav > 0
+                   AND price_date >= '" + price_date + "'
+                   ORDER BY price_date"
+    
+        conn = connect_mysql()
+    
+        t = odbc::query(conn, s_query)
+    
+        conn.close()
+    } else {
+    
+        db = database(directory="/home/DolphinDB/server/database/fundit/")
+        
+        tb_nav = loadTable(db, "nav",,false)
+
+        s_sql = "SELECT fund_id, price_date, cumulative_nav AS cnav FROM tb_nav WHERE fund_id IN ('" + fund_ids + ")"
+        
+        t = s_sql.parseExpr().eval()
+    
+    }
+
+    return t
+
+}
+
+/*
+ * 存私募基金净值到本地
+ *
+ * save_hedge_fund_nav(tb_nav)
+ */
+
+def save_hedge_fund_nav(tb_nav) {
+
+    db = database(directory="/home/DolphinDB/server/database/fundit/")
+
+    saveTable(db, tb_nav, "nav")
+}
+
+/*
+ * 取指数因子点位
+ *
+ * get_index_nav_by_price_date("'IN00000008','FA00000WKG'", 2024.06.01)
+ */
+def get_index_nav_by_price_date(index_ids, price_date) {
+
+    s_query = "SELECT index_id, price_date, close AS cnav
+               FROM mfdb.market_indexes
+               WHERE index_id IN (" + index_ids + ")
+                 AND isvalid = 1
+                 AND close > 0
+                 AND price_date >= '" + price_date + "'
+               UNION
+               SELECT index_id AS index_id, price_date, index_value AS cnav
+               FROM mfdb.indexes_ty_index
+               WHERE index_id IN (" + index_ids + ")
+                 AND isvalid = 1
+                 AND index_value > 0
+                 AND price_date >= '" + price_date + "'
+               UNION
+               SELECT factor_id AS index_id, price_date, factor_value AS cnav
+               FROM pfdb.cm_factor_value
+               WHERE factor_id IN (" + index_ids + ")
+                 AND isvalid = 1
+                 AND factor_value > 0
+                 AND price_date >= '" + price_date + "'
+               ORDER BY price_date"
+
+    conn = connect_mysql()
+
+    t = odbc::query(conn, s_query)
+
+    conn.close()
+
+    return t
+
+}
+
+
+/*
+ * 取有效基金基本信息
+ *
+ * get_fund_info("'HF000004KN','HF00018WXG'")
+ */
+def get_fund_info(fund_ids) {
+
+    s_query = "SELECT fi.fund_id, fi.inception_date, fi.primary_benchmark_id AS benchmark_id, IFNULL(fi.initial_unit_value, 1) AS ini_value, fs.strategy, fs.substrategy
+               FROM mfdb.fund_information fi
+               INNER JOIN mfdb.fund_strategy fs ON fi.fund_id = fs.fund_id AND fs.isvalid = 1
+               WHERE fi.fund_id IN (" + fund_ids + ")
+               AND fi.isvalid = 1
+               ORDER BY fi.fund_id"
+
+    conn = connect_mysql()
+
+    t = odbc::query(conn, s_query)
+
+    conn.close()
+
+    return t
+
+}
+
+
+/*
+ * 取私募基金净值更新信息, 返回基金及其净值更新的最早净值日期
+ *
+ * get_fund_list_by_nav_updatetime(2024.07.19T10:00:00)
+ */
+def get_fund_list_by_nav_updatetime(updatetime) {
+
+    s_query = "SELECT fi.fund_id, MIN(nav.price_date) AS price_date
+               -- fi.inception_date, fi.primary_benchmark_id AS benchmark_id, IFNULL(fi.initial_unit_value, 1) AS ini_value, 
+               FROM mfdb.fund_information fi
+               INNER JOIN mfdb.nav ON fi.fund_id = nav.fund_id
+               WHERE fi.isvalid = 1
+                 AND nav.cumulative_nav > 0
+                 AND nav.updatetime >= '" + updatetime + "'
+               GROUP BY fi.fund_id
+               ORDER BY fi.fund_id"
+
+    conn = connect_mysql()
+
+    t = odbc::query(conn, s_query)
+
+    conn.close()
+
+    return t
+
+}

+ 69 - 0
code/DolphinDB/modules/fundit/fundCalculator.dos

@@ -0,0 +1,69 @@
+module fundit::fundCalculator
+/*
+ *   RBSA 计算
+ * 
+ *   Create  20240703  模仿python代码在Dolphin中实现,具体计算逻辑完全不懂                          Joey
+ *                     原代码见: http://gogs.fundit.cn/FundIt/FinanceCalcPython/src/dev36/pf_scical/v1/calc_rbsa_use_osqp.py
+ *                     Python官方示例见:https://osqp.org/docs/examples/least-squares.html
+ *                     Dolphin官方示例见:https://docs.dolphindb.cn/zh/funcs/o/osqp.html
+ *                     取数据脚本:SELECT fund_id, GROUP_CONCAT(ret_1w SEPARATOR ' ')
+ *                                 FROM mfdb.`fund_performance_weekly` 
+ *                                 WHERE fund_id IN ( 'MF00003PW1', 'IN00000008', 'IN00000077', 'IN0000009M', 'IN0000007G')
+ *                                 AND year_week BETWEEN 202411 AND 202422
+ *                                 GROUP BY fund_id
+ *
+ */
+def cal_rbsa(fund_ret, index_ret, isLongShort) {
+
+    // 窗口长度
+    m = fund_ret.size()
+    // 指数个数
+    n = index_ret.cols()
+    
+    P0 = matrix(float, n, m+n)
+    P1 = concatMatrix([matrix(float, m, n), eye(m)])
+    P = concatMatrix([P0, P1], false)
+    q = array(float, m+n, (m+n)*10, 0)
+    
+    A0 = concatMatrix( [matrix(index_ret), -eye(m)])
+    A1 = concatMatrix( [matrix(take(1, n)).transpose(), matrix(float, 1, m)])
+    A2 = concatMatrix( [eye(n), matrix(float, n, m)])
+    A = concatMatrix( [A0, A1, A2], false)
+    
+    // join 1 是为了限制所有权重加总为100%
+    // 下限
+    lb =(fund_ret join 1) join array(float, n, n*10, iif(isLongShort == false, 0, -2))
+    // 上限
+    ub=(fund_ret join 1) join array(float, n, n*10, iif(isLongShort == false, 1, 2))
+    
+    res = osqp( q, P, A, lb, ub)
+    
+    return res
+ }
+
+/*
+ * 年化收益率计算(只支持月收益)
+ *
+ * date_rets 是个日期排正序的收益率表
+ */
+def cal_ret_annualized(date_rets, isGIPS) {
+
+    date_format = "y-M"
+    
+    t = SELECT fund_id, (1 + ret).prod() - 1 AS ret_a, end_date.max().datetimeParse(date_format) - end_date.min().datetimeParse(date_format) AS date_diff
+        FROM date_rets
+        GROUP BY fund_id
+   
+    // GIPS 规则是不够一年不年化
+    if(isGIPS == true) {
+    
+        UPDATE t SET ret_a = (1 + ret_a).pow(12 \ date_diff) - 1
+        WHERE date_diff > 12
+    } else {
+    
+        UPDATE t SET ret_a = (1 + ret_a).pow(12 \ date_diff) - 1
+    }
+
+    return t
+    
+}

+ 86 - 0
code/DolphinDB/modules/fundit/returnCalculator.dos

@@ -0,0 +1,86 @@
+module fundit::returnCalculator
+use fundit::fundCalculator
+use fundit::dataPuller
+
+/*
+ *  批量计算私募基金收益
+ *
+ *
+ *  cal_hedge_fund_returns("'HF000004KN','HF00018WXG','HF000103EU'")
+ *
+ */
+def cal_hedge_fund_returns(fund_ids) {
+
+    // 用于保证老基金也能取到所有历史净值
+    very_old_price_date = 1990.01.01
+
+    // 基金基本信息,包括初始净值
+    tb_fund_info = get_fund_info(fund_ids)
+    
+    // 基金净值
+    tb_nav = get_hedge_fund_nav_by_price_date(fund_ids, very_old_price_date, true)
+    
+    // NOTE: mySQL currently uses calendar day, while the codes below takes business day. it might cause a few different numbers calcuated
+    tb_monthly_nav = SELECT fund_id, businessMonthEnd(price_date).month().last() AS end_date, price_date.last() AS price_date, cnav.last() AS cnav
+                     FROM tb_nav
+                     GROUP BY fund_id, businessMonthEnd(price_date)
+    
+    // 为了把不数据库里不存在的nav记录填空,不得不先做个pivot;然后才能正确计算ratios (ret_1m)
+    tb_rets_1m = (SELECT cnav FROM tb_monthly_nav PIVOT BY end_date, fund_id).ffill!().ratios()-1
+    
+    // 取被pivot掉的fund_Ids
+    v_col_name = tb_rets_1m.columnNames()[1:]
+    tb_tmp = tb_rets_1m.unpivot("end_date", v_col_name).rename!("valueType" "value", "fund_id" "ret_1m")
+                         
+    tb_rets = SELECT fund_id, end_date, ret_1m,
+                     (1 + ret_1m).mprod(3) - 1 AS ret_3m, (1 + ret_1m).mprod(6) - 1 AS ret_6m, (1 + ret_1m).mprod(12) - 1 AS ret_1y,
+                     (1 + ret_1m).mprod(24) - 1 AS ret_2y, (1 + ret_1m).mprod(36) - 1 AS ret_3y, (1 + ret_1m).mprod(48) - 1 AS ret_4y,
+                     (1 + ret_1m).mprod(60) - 1 AS ret_5y, (1 + ret_1m).mprod(120) - 1 AS ret_10y
+              FROM tb_tmp
+              // WHERE ret_1m IS NOT NULL
+              CONTEXT BY fund_id
+    
+    UPDATE tb_rets SET ret_1m_a = (1 + ret_1m).pow(12) - 1, ret_3m_a = (1 + ret_3m).pow(4) - 1, ret_6m_a = (1 + ret_6m).pow(2) - 1, ret_1y_a= ret_1y,
+                       ret_2y_a = (1 + ret_2y).pow(1\2) - 1, ret_3y_a = (1 + ret_3y).pow(1\3) - 1, ret_4y_a = (1 + ret_4y).pow(1\4) - 1, 
+                       ret_5y_a = (1 + ret_5y).pow(1\5) - 1, ret_10y_a = (1 + ret_10y).pow(1\10) - 1
+    
+    // ytd 不会用上面的CONTEXT BY语句实现
+    tb_ret_ytd = SELECT a.fund_id, a.end_date, a.price_date, a.cnav, -1 + a.cnav \ b.cnav AS ret_ytd, (a.cnav \ b.cnav).pow(12\(a.end_date - b.end_date)) - 1 AS ret_ytd_a
+                 FROM tb_monthly_nav a
+                 INNER JOIN tb_monthly_nav b ON a.fund_Id = b.fund_id 
+                   AND b.end_date = a.price_date.yearEnd().datetimeAdd(-1y).month()
+    
+    // since inception 不会用上面的CONTEXT BY语句实现
+    tb_ret_incep = SELECT a.fund_id, a.end_date, a.price_date,  cnav, -1 + cnav \ ini_value AS ret_incep
+                   FROM tb_monthly_nav a
+                   INNER JOIN tb_fund_info fi ON a.fund_id = fi.fund_id
+    
+    UPDATE tb_ret_incep SET ret_incep_a = (1 + ret_incep).pow(12\(end_date - end_date.first())) - 1 CONTEXT BY fund_Id
+    UPDATE tb_ret_incep SET ret_incep_a_gips = iif( end_date - end_date.first() < 12, ret_incep, ret_incep_a ), ret_incep_a_all = ret_incep_a CONTEXT BY fund_id
+    
+    // 只选需要更新的记录
+    tb_fund_performance = SELECT a.fund_id, a.end_date.datetimeFormat("yyyy-MM") AS end_date, c.price_date, c.cnav AS cumulative_nav,
+                                 a.ret_1m, a.ret_1m_a, a.ret_3m, a.ret_3m_a, a.ret_6m, a.ret_6m_a,
+                                 a.ret_1y, a.ret_1y_a, a.ret_2y, a.ret_2y_a, a.ret_3y, a.ret_3y_a,
+                                 a.ret_4y, a.ret_4y_a, a.ret_5y, a.ret_5y_a, a.ret_10y, a.ret_10y_a,
+                                 b.ret_ytd, b.ret_ytd_a, c.ret_incep, c.ret_incep_a, c.ret_incep_a_all, c.ret_incep_a_gips
+                                 // , 123 AS creatorid, now() AS createtime, 123 AS updaterid, now() AS updatetime, 1 AS isvalid
+                          FROM tb_rets a 
+                          LEFT JOIN tb_ret_ytd b ON a.fund_id = b.fund_id AND a.end_date = b.end_date
+                          LEFT JOIN tb_ret_incep c ON a.fund_id = c.fund_id AND a.end_date = c.end_date
+                          WHERE c.price_date IS NOT NULL
+
+/*    
+    // 把这些数据写回mySQL数据表
+    conn = connect_mysql()
+    odbc::append(conn, tb_fund_performance, "mfdb.fund_performance_dolphin", false)
+    conn.close()
+
+
+    // 把这些数据写到本地数据表
+    db =database(directory="D:/Program Files/DolphinDB/server/database/Fundit/")
+    saveTable(db, tb_fund_performance, "fund_performance")
+*/
+
+    return tb_fund_performance
+}

+ 421 - 0
code/DolphinDB/modules/fundit/sample_cal_indicator.dos

@@ -0,0 +1,421 @@
+login(`admin, `123456)
+loadPlugin("ODBC")
+clearCachedModules()
+use fundit::fundCalculator
+use fundit::dataPuller
+use fundit::returnCalculator
+
+
+//end_day = 2024.06.28
+end_day = today()
+
+bmk_ret = get_fund_monthly_ret("'IN00000008'", 1990.01.01, end_day, true)
+risk_free_rate = get_risk_free_rate("'IN0000000M'", 1990.01.01, end_day)
+
+/* TEST CASE 1  */
+// tb_ret = get_fund_monthly_ret("'MF00003PW1','MF00003PW2'", 1990.01.01, end_day, true)
+// hedge fund test
+tb_ret = get_fund_monthly_ret("'HF000004KN','HF000103EU','HF00018WXG'", 1990.01.01, end_day, true)
+
+
+/* TEST CASE 2 
+tb_updated_funds = get_fund_list_by_nav_updatetime(2024.07.19T10:00:00)
+
+// take 1000 funds for testing
+fund_ids = "'" + tb_updated_funds.fund_id[0:1000].concat("','") + "'"
+
+tb_ret = get_fund_monthly_ret(fund_ids, 1990.01.01, end_day, false)
+*/
+
+
+/* annualized return GIPS  */
+// 0.122622 -0.163869 -0.077696
+ret_incep_a = SELECT fund_id, ret_incep_a FROM tb_ret WHERE end_date = end_day.datetimeFormat("yyyy-MM")
+ret_ytd_a = SELECT fund_id, ret_ytd_a FROM tb_ret WHERE end_date = end_day.datetimeFormat("yyyy-MM")
+
+/* annualized standard deviation  */
+// 0.210105 null null
+std_incep = ( SELECT ret.std() FROM tb_ret GROUP BY fund_id ) 
+std_incep_a = std_incep * pow(12, 0.5)
+// 0.357842 null null
+std_ytd = (SELECT ret.std() FROM tb_ret WHERE end_date >= end_day.year()$STRING + "-01" GROUP BY fund_id)
+std_ytd_a = std_ytd * pow(12, 0.5)
+// 0.357842 null null
+std_6m = (SELECT ret.std() FROM tb_ret WHERE end_date > end_day.temporalAdd(-6, "M").temporalFormat("yyyy-MM") GROUP BY fund_id) 
+std_6m_a = std_6m * pow(12, 0.5)
+// 0.270642 null null
+std_1y = (SELECT ret.std() FROM tb_ret WHERE end_date > end_day.temporalAdd(-1, "y").temporalFormat("yyyy-MM") GROUP BY fund_id) 
+std_1y_a = std_1y * pow(12, 0.5)
+// 0.249324 null null
+std_2y = (SELECT ret.std() FROM tb_ret WHERE end_date > end_day.temporalAdd(-2, "y").temporalFormat("yyyy-MM") GROUP BY fund_id)
+std_2y_a = std_2y * pow(12, 0.5)
+// 0.248138 null null
+std_3y = (SELECT ret.std() FROM tb_ret WHERE end_date > end_day.temporalAdd(-3, "y").temporalFormat("yyyy-MM") GROUP BY fund_id)
+std_3y_a = std_3y * pow(12, 0.5)
+// 0.242779 null null
+std_4y = (SELECT ret.std() FROM tb_ret WHERE end_date > end_day.temporalAdd(-4, "y").temporalFormat("yyyy-MM") GROUP BY fund_id)
+std_4y_a = std_4y * pow(12, 0.5)
+// 0.224387 null null
+std_5y = (SELECT ret.std() FROM tb_ret WHERE end_date > end_day.temporalAdd(-5, "y").temporalFormat("yyyy-MM") GROUP BY fund_id)
+std_5y_a = std_5y * pow(12, 0.5)
+// 0.210569 null null
+std_10y = (SELECT ret.std() FROM tb_ret WHERE end_date > end_day.temporalAdd(-10, "y").temporalFormat("yyyy-MM") GROUP BY fund_id)
+std_10y_a = std_10y * pow(12, 0.5)
+
+/* annualized downside stdev  numbers are slightly off !
+   NOTE: not sure why cnt[0] need to minus 1 copied from Java implementation*/
+// risk_free_rate = 0.001208
+// 0.130429 null null
+t = SELECT *, count(fund_id) AS cnt FROM tb_ret CONTEXT BY fund_id
+ds_std_dev_incep = SELECT t.fund_id, (sum2(t.ret - rfr.ret) / (t.cnt[0]-1)).pow(0.5) AS ds_std_dev 
+                              FROM t 
+                              INNER JOIN risk_free_rate rfr ON t.end_date = rfr.end_date
+                              WHERE t.ret < rfr.ret
+                              GROUP BY t.fund_id
+ds_std_dev_incep_a = ds_std_dev_incep * pow(12, 0.5)
+
+// 0.277208 null null
+t = SELECT *, count(fund_id) AS cnt FROM tb_ret WHERE end_date >= end_day.year()$STRING + "-01"  CONTEXT BY fund_id
+ds_std_dev_ytd = SELECT fund_id, (sum2(ret - rfr.ret) / (t.cnt[0]-1)).pow(0.5) AS ds_std_dev
+                           FROM t
+                           INNER JOIN risk_free_rate rfr ON t.end_date = rfr.end_date
+                           WHERE t.ret < rfr.ret
+                           GROUP BY fund_id 
+ds_std_dev_ytd_a = ds_std_dev_ytd * pow(12, 0.5)
+
+
+/* beta  */
+//  0.530483 null null
+t = SELECT t.fund_id, t.end_date, t.ret, bmk.ret AS ret_bmk
+     FROM tb_ret t
+     INNER JOIN bmk_ret bmk ON t.end_date = bmk.end_date
+
+beta_incep = SELECT ret.beta(ret_bmk) AS beta FROM t GROUP BY fund_id
+
+// 1.819822 null null
+t = SELECT t.fund_id, t.end_date, t.ret, bmk.ret AS ret_bmk
+     FROM tb_ret t
+     INNER JOIN bmk_ret bmk ON t.end_date = bmk.end_date
+     WHERE t.end_date >= end_day.datetimeFormat("yyyy-01")
+
+beta_ytd =SELECT ret.beta(ret_bmk) AS beta FROM t GROUP BY fund_id
+
+/* annualized alpha -- numbers are off ! because Java doesn't substract risk free rate */
+
+ //  0.110500 null null
+t = SELECT t.fund_id, t.end_date, t.ret, bmk.ret AS ret_bmk
+      FROM tb_ret t
+      INNER JOIN bmk_ret bmk ON t.end_date = bmk.end_date
+
+alpha_incep = SELECT t.fund_id, (t.ret - rfr.ret).mean() - beta.beta[0] * (t.ret_bmk - rfr.ret).mean() AS alpha
+                      FROM t 
+                      INNER JOIN beta_incep beta ON t.fund_id = beta.fund_id
+                      INNER JOIN risk_free_rate rfr ON t.end_date = rfr.end_date
+                      GROUP BY t.fund_id
+alpha_incep_a = alpha_incep * 12
+
+ // -0.11019 null null
+t = SELECT t.fund_id, t.end_date, t.ret, bmk.ret AS ret_bmk
+      FROM tb_ret t
+      INNER JOIN bmk_ret bmk ON t.end_date = bmk.end_date
+      WHERE t.end_date >= end_day.datetimeFormat("yyyy-01")
+
+ alpha_ytd   = SELECT t.fund_id, (t.ret - rfr.ret).mean() - beta.beta[0] * (t.ret_bmk - rfr.ret).mean() AS alpha
+                      FROM t 
+                      INNER JOIN beta_ytd beta ON t.fund_id = beta.fund_id
+                      INNER JOIN risk_free_rate rfr ON t.end_date = rfr.end_date
+                      GROUP BY t.fund_id
+alpha_ytd_a = alpha_ytd * 12
+
+/* win rate -- numbers are slightly way off !effective data count issue? */
+
+ // 0.585185 null null
+ t = SELECT t.fund_id, t.end_date, t.ret, bmk.ret AS ret_bmk, count(fund_id) AS cnt
+      FROM tb_ret t
+      INNER JOIN bmk_ret bmk ON t.end_date = bmk.end_date
+      CONTEXT BY t.fund_id
+
+winrate_incep = SELECT fund_id, count(fund_id) \ cnt[0] AS winrate
+                         FROM t
+                         WHERE ret > ret_bmk
+                         GROUP BY fund_id
+
+// 0.666667 null null
+ t = SELECT t.fund_id, t.end_date, t.ret, bmk.ret AS ret_bmk, count(fund_id) AS cnt
+      FROM tb_ret t
+      INNER JOIN bmk_ret bmk ON t.end_date = bmk.end_date
+      WHERE t.end_date >= end_day.datetimeFormat("yyyy-01")
+      CONTEXT BY t.fund_id
+
+winrate_ytd = SELECT fund_id, count(fund_id) \ cnt[0] AS winrate
+                      FROM t
+                      WHERE ret > ret_bmk
+                      GROUP BY fund_id
+
+/* skewness */
+// -0.090416
+skewness_incep = SELECT fund_id, ret.skew(false) AS skewness FROM tb_ret GROUP BY fund_id
+// -0.513185
+skewness_ytd = SELECT fund_id, ret.skew(false) AS skewness FROM tb_ret WHERE end_date >= end_day.datetimeFormat("yyyy-01") GROUP BY fund_id
+
+/* kurtosis */
+// 0.527901
+kurtosis_incep = SELECT fund_id, ret.kurtosis(false) - 3 AS kurtosis FROM tb_ret GROUP BY fund_id
+// 0.961329
+kurtosis_ytd = SELECT fund_id, ret.kurtosis(false) - 3 AS kurtosis FROM tb_ret WHERE end_date >= end_day.datetimeFormat("yyyy-01") GROUP BY fund_id
+
+/* worst month */
+// --0.172580
+wrst_month_incep = SELECT ret.min() AS ret FROM tb_ret GROUP BY fund_id
+// -0.172580
+wrst_month_ytd = SELECT ret.min() AS ret FROM tb_ret WHERE end_date >= end_day.datetimeFormat("yyyy-01") GROUP BY fund_id
+
+/* longest down month*/
+
+/* max drawdown */
+// 0.305496 0.5621 0.120200
+drawdown_incep = SELECT fund_id, max( 1 - nav \ nav.cummax() ) AS drawdown FROM tb_ret GROUP BY fund_id
+
+// 0.05645 0 0.06553
+drawdown_ytd = SELECT fund_id, max( 1 - nav \ nav.cummax() ) AS drawdown FROM tb_ret WHERE end_date >= end_day.datetimeFormat("yyyy-01") GROUP BY fund_id
+
+/* sharpe ratio */
+// 0.568739
+sharpe_incep = SELECT t.fund_id, (t.ret - rfr.ret).mean() / std.std_ret[0] AS sharpe
+                        FROM tb_ret t
+                        INNER JOIN std_incep std ON t.fund_id = std.fund_id
+                        INNER JOIN risk_free_rate rfr ON t.end_date = rfr.end_date
+                        GROUP BY t.fund_id
+sharpe_incep_a = sharpe_incep * pow(12, 0.5)
+
+// -0.186892
+sharpe_ytd    = SELECT t.fund_id, (t.ret - rfr.ret).mean() / std.std_ret[0] AS sharpe
+                        FROM tb_ret t
+                        INNER JOIN std_ytd std ON t.fund_id = std.fund_id
+                        INNER JOIN risk_free_rate rfr ON t.end_date = rfr.end_date
+                        WHERE t.end_date >= end_day.datetimeFormat("yyyy-01")
+                        GROUP BY t.fund_id
+sharpe_ytd_a = sharpe_ytd * pow(12, 0.5)
+
+// -0.568024
+sharpe_1y =     SELECT t.fund_id, (t.ret - rfr.ret).mean() / std.std_ret[0] AS sharpe
+                        FROM tb_ret t
+                        INNER JOIN std_1y std ON t.fund_id = std.fund_id
+                        INNER JOIN risk_free_rate rfr ON t.end_date = rfr.end_date
+                        WHERE t.end_date > end_day.temporalAdd(-1, "y").temporalFormat("yyyy-MM")
+                        GROUP BY t.fund_id
+sharpe_1y_a = sharpe_1y * pow(12, 0.5)
+
+// -0.131570
+sharpe_2y =     SELECT t.fund_id, (t.ret - rfr.ret).mean() / std.std_ret[0] AS sharpe
+                        FROM tb_ret t
+                        INNER JOIN std_2y std ON t.fund_id = std.fund_id
+                        INNER JOIN risk_free_rate rfr ON t.end_date = rfr.end_date
+                        WHERE t.end_date > end_day.temporalAdd(-2, "y").temporalFormat("yyyy-MM")
+                        GROUP BY t.fund_id
+sharpe_2y_a = sharpe_2y * pow(12, 0.5)
+
+
+/* sortino ratio  NOTE: sortino is LPM2*/
+
+// 0.916167
+sortino_incep = SELECT t.fund_id, (t.ret - rfr.ret ).mean() / std.ds_std_dev[0] AS sortino
+                         FROM tb_ret t
+                         INNER JOIN ds_std_dev_incep std ON t.fund_id = std.fund_id
+                         INNER JOIN risk_free_rate rfr ON t.end_date = rfr.end_date
+                         GROUP BY t.fund_id
+sortino_incep_a = sortino_incep * pow(12, 0.5)
+
+// -0.241256
+sortino_ytd    =  SELECT t.fund_id, (t.ret - rfr.ret ).mean() / std.ds_std_dev[0] AS sortino
+                         FROM tb_ret t
+                         INNER JOIN ds_std_dev_ytd std ON t.fund_id = std.fund_id
+                         INNER JOIN risk_free_rate rfr ON t.end_date = rfr.end_date
+                        WHERE t.end_date >= end_day.datetimeFormat("yyyy-01")
+                        GROUP BY t.fund_id
+sortino_ytd_a = sortino_ytd * pow(12, 0.5)
+
+/* sortino MAR  -- what's the MAR number? */
+
+
+
+/* treynor ratio  ytd numbers are way off ! because current java calcuation is not follow GIPS annualization rule*/
+// 0.195812
+t= SELECT *, count(fund_id) AS cnt FROM tb_ret t INNER JOIN risk_free_rate rfr ON t.end_date = rfr.end_date CONTEXT BY t.fund_id
+treynor_incep = SELECT t.fund_id, ((1 + t.ret).prod().pow(12\iif(t.cnt[0]<12, 12, t.cnt[0])) - (1 + t.rfr_ret).prod().pow(12\iif(t.cnt[0]<12, 12, t.cnt[0]))) / beta.beta[0] AS treynor
+                         FROM t
+                         INNER JOIN beta_incep beta ON t.fund_id = beta.fund_id
+                         GROUP BY t.fund_id
+
+// -0.064390
+t= SELECT *, count(fund_id) AS cnt FROM tb_ret t INNER JOIN risk_free_rate rfr ON t.end_date = rfr.end_date WHERE t.end_date >= end_day.datetimeFormat("yyyy-01") CONTEXT BY t.fund_id
+treynor_ytd =    SELECT t.fund_id, ((1 + t.ret).prod().pow(12\iif(t.cnt[0]<12, 12, t.cnt[0])) - (1 + t.rfr_ret).prod().pow(12\iif(t.cnt[0]<12, 12, t.cnt[0]))) / beta.beta[0] AS treynor
+                         FROM t
+                         INNER JOIN beta_ytd beta ON t.fund_id = beta.fund_id
+                         GROUP BY t.fund_id
+
+
+/* jensen's alpha  numbers are slightly off ! */
+// 0.101781
+jensen_incep = SELECT t.fund_id, t.ret.mean() - rfr.ret.mean() - beta.beta[0] * (bmk.ret.mean() - rfr.ret.mean()) AS jensen
+                        FROM tb_ret t
+                        INNER JOIN bmk_ret bmk ON t.end_date = bmk.end_date
+                        INNER JOIN risk_free_rate rfr ON t.end_date = rfr.end_date
+                        INNER JOIN beta_incep beta ON t.fund_id = beta.fund_id
+                        GROUP BY t.fund_id
+jensen_incep_a = jensen_incep * 12
+
+// -0.098310
+jensen_ytd     = SELECT t.fund_id, t.ret.mean() - rfr.ret.mean() - beta.beta[0] * (bmk.ret.mean() - rfr.ret.mean()) AS jensen
+                        FROM tb_ret t
+                        INNER JOIN bmk_ret bmk ON t.end_date = bmk.end_date
+                        INNER JOIN risk_free_rate rfr ON t.end_date = rfr.end_date
+                        INNER JOIN beta_ytd beta ON t.fund_id = beta.fund_id
+                        WHERE t.end_date >= end_day.datetimeFormat("yyyy-01")
+                        GROUP BY t.fund_id
+jensen_ytd_a = jensen_ytd * 12
+
+/* calmar ratio   numbers are way off !  */
+// 0.376378 -0.279307 -0.81280
+calmar_incep = SELECT fund_id, ar.ret_incep_a \ dd.drawdown AS calmar
+                        FROM ret_incep_a ar
+                        INNER JOIN drawdown_incep dd ON ar.fund_id = dd.fund_id
+// 2.567034 999999. -0.81280
+calmar_ytd =    SELECT fund_id, ar.ret_ytd_a \ dd.drawdown  AS calmar
+                        FROM ret_ytd_a ar
+                        INNER JOIN drawdown_ytd dd ON ar.fund_id = dd.fund_id
+
+/* omega ratio   numbers are off ! could because Java uses annualized returns and cnt-1  NOTE: omega is LPM1 */
+// 1.471981
+t0 = SELECT *, fund_id.count() AS cnt FROM tb_ret t INNER JOIN risk_free_rate rfr ON t.end_date = rfr.end_date CONTEXT BY t.fund_id
+       
+t1 = SELECT fund_id, (ret - rfr_ret).sum() \ cnt[0] AS ex_ret
+       FROM t0
+       WHERE ret > rfr_ret
+       GROUP BY fund_id
+t2 = SELECT fund_id, (rfr_ret - ret).sum() \ cnt[0] AS ds_ret
+       FROM t0
+       WHERE ret < rfr_ret
+       GROUP BY fund_id
+
+omega_incep = SELECT t1.fund_id,  t1.ex_ret \ t2.ds_ret AS omega
+                         FROM t1
+                         INNER JOIN t2 ON t1.fund_id = t2.fund_id
+
+// 0.790864
+t0 = SELECT *, fund_id.count() AS cnt FROM tb_ret t INNER JOIN risk_free_rate rfr ON t.end_date = rfr.end_date WHERE t.end_date >= end_day.datetimeFormat("yyyy-01") CONTEXT BY t.fund_id
+       
+t1 = SELECT fund_id, (ret - rfr_ret).mean() AS ex_ret
+       FROM t0
+       GROUP BY fund_id
+t2 = SELECT fund_id, (rfr_ret - ret).sum() \ cnt[0] AS ds_ret
+       FROM t0
+       WHERE ret < rfr_ret
+       GROUP BY fund_id
+
+omega_ytd =     SELECT t1.fund_id,  1 + t1.ex_ret \ t2.ds_ret AS omega
+                         FROM t1
+                         INNER JOIN t2 ON t1.fund_id = t2.fund_id
+
+/* kappa ratio NOTE: kappa is LMP3  numbers are off ! Java's implementation could be very wrong */ 
+
+// 0.848648
+t0 = SELECT *, fund_id.count() AS cnt FROM tb_ret t INNER JOIN risk_free_rate rfr ON t.end_date = rfr.end_date CONTEXT BY t.fund_id
+t1 = SELECT fund_id, (ret - rfr_ret).mean() AS ex_ret
+       FROM t0
+       GROUP BY fund_id
+t2 = SELECT fund_id, (rfr_ret - ret).sum3() \ cnt[0] AS ds_ret
+       FROM t0
+       WHERE ret < rfr_ret
+       GROUP BY fund_id
+
+kappa_incep = SELECT t1.fund_id,  1 + t1.ex_ret \ t2.ds_ret.pow(1\3) AS kappa
+                         FROM t1
+                         INNER JOIN t2 ON t1.fund_id = t2.fund_id
+// -0.501813
+t0 = SELECT *, fund_id.count() AS cnt FROM tb_ret t INNER JOIN risk_free_rate rfr ON t.end_date = rfr.end_date WHERE t.end_date >= end_day.datetimeFormat("yyyy-01") CONTEXT BY t.fund_id
+t1 = SELECT fund_id, (ret - rfr_ret).mean() AS ex_ret
+       FROM t0
+       GROUP BY fund_id
+t2 = SELECT fund_id, (rfr_ret - ret).sum3() \ cnt[0] AS ds_ret
+       FROM t0
+       WHERE ret < rfr_ret
+       GROUP BY fund_id
+
+kappa_ytd = SELECT t1.fund_id,  1+ t1.ex_ret \ t2.ds_ret.pow(1\3) AS kappa
+                         FROM t1
+                         INNER JOIN t2 ON t1.fund_id = t2.fund_id
+
+/* tracking error */
+// 0.203268
+track_error_incep = SELECT fund_id,  (t.ret - bmk.ret).std() AS track_error
+                              FROM tb_ret t
+                              INNER JOIN bmk_ret bmk ON t.end_date = bmk.end_date
+                              GROUP BY t.fund_id
+track_error_incep_a = track_error_incep * pow(12, 0.5)
+// 0.193291
+track_error_ytd =    SELECT fund_id,  (t.ret - bmk.ret).std() AS track_error
+                              FROM tb_ret t
+                              INNER JOIN bmk_ret bmk ON t.end_date = bmk.end_date
+                              WHERE t.end_date >= end_day.datetimeFormat("yyyy-01")
+                              GROUP BY t.fund_id
+track_error_ytd_a = track_error_ytd * pow(12, 0.5)
+
+/* information ratio  numbers are way off! */
+// 5.472180
+info_incep = SELECT fund_id, (t.ret - bmk.ret).mean() / (t.ret - bmk.ret).std() AS info
+                    FROM tb_ret t
+                    INNER JOIN bmk_ret bmk ON t.end_date = bmk.end_date
+                    GROUP BY t.fund_id
+info_incep_a = info_incep * pow(12, 0.5)
+
+
+/* modigliani  = sharpe * std(benchmark) + risk_free_rate )*/ 
+// 0.141025
+m2_incep = SELECT t.fund_id, (t.ret - rfr.ret).mean() / t.ret.std() * bmk.ret.std() + rfr.ret.mean()
+                   FROM tb_ret t
+                   INNER JOIN bmk_ret bmk ON t.end_date = bmk.end_date
+                   INNER JOIN risk_free_rate rfr ON t.end_date = rfr.end_date
+                   GROUP BY t.fund_id
+m2_incep_a = m2_incep * 12
+// -0.020145
+m2_ytd     = SELECT t.fund_id, (t.ret - rfr.ret).mean() / t.ret.std() * bmk.ret.std() + rfr.ret.mean()
+                   FROM tb_ret t
+                   INNER JOIN bmk_ret bmk ON t.end_date = bmk.end_date
+                   INNER JOIN risk_free_rate rfr ON t.end_date = rfr.end_date
+                   WHERE t.end_date >= end_day.datetimeFormat("yyyy-01")
+                   GROUP BY t.fund_id
+m2_ytd_a = m2_ytd * 12
+
+/* historical var  numbers are off ! due to different implementations */
+CONFIDENCE_LEVAL = 95
+// 0.093206
+var_incep = SELECT fund_id, - ret.percentile(100-CONFIDENCE_LEVAL) AS var
+                  FROM tb_ret
+                  GROUP BY fund_id
+// 0.162951
+var_2y    = SELECT fund_id, - ret.percentile(100-CONFIDENCE_LEVAL) AS var
+                  FROM tb_ret
+                  WHERE end_date > end_day.temporalAdd(-2, "y").datetimeFormat("yyyy-MM")
+                  GROUP BY fund_id
+
+
+
+/* historical cvar  numbers are slightly off ! due to different implementations of var */
+// 0.129327
+cvar_incep = SELECT t.fund_id, - t.ret.mean() AS cvar
+                     FROM tb_ret t
+                     INNER JOIN var_incep var ON t.fund_id = var.fund_id 
+                     WHERE t.ret < - var.var
+                     GROUP BY t.fund_id
+
+// 0.17258
+cvar_2y     = SELECT t.fund_id, - t.ret.mean() AS cvar
+                     FROM tb_ret t
+                     INNER JOIN var_2y var ON t.fund_id = var.fund_id 
+                     WHERE end_date > end_day.temporalAdd(-2, "y").datetimeFormat("yyyy-MM")
+                        AND t.ret < - var.var
+                     GROUP BY t.fund_id
+
+/* SMDD var */
+

+ 28 - 0
code/DolphinDB/modules/fundit/sample_cal_return.dos

@@ -0,0 +1,28 @@
+login(`admin, `123456)
+//loadPlugin("odbc")
+clearCachedModules()
+use fundit::fundCalculator
+use fundit::dataPuller
+use fundit::returnCalculator
+
+// TEST CASE 1
+// fund_ids = "'HF000004KN','HF00018WXG','HF000103EU'"
+
+// TEST CASE 2 取有净值更新的所有私募基金
+tb_fund_list = get_fund_list_by_nav_updatetime(2024.07.19T10:00:00)
+fund_ids = tb_fund_list.fund_id.concat("','")$STRING
+fund_ids = "'" + fund_ids$STRING + "'"
+
+
+tb_fund_performance = cal_hedge_fund_returns(fund_ids)
+
+// 把这些数据写回mySQL数据表
+//conn = connect_mysql()
+//odbc::append(conn, tb_fund_performance, "mfdb.fund_performance_dolphin", false)
+//conn.close()
+
+// 把这些数据写到本地数据表
+db =database(directory="/home/DolphinDB/server/database/fundit/")
+saveTable(db, tb_fund_performance, "fund_performance")
+
+

+ 68 - 0
code/DolphinDB/modules/fundit/sample_rbsa.dos

@@ -0,0 +1,68 @@
+/*
+ *   RBSA 计算
+ * 
+ *   Create  20240703  模仿python代码在Dolphin中实现,具体计算逻辑完全不懂                          Joey
+ *                     原代码见: http://gogs.fundit.cn/FundIt/FinanceCalcPython/src/dev36/pf_scical/v1/calc_rbsa_use_osqp.py
+ *                     Python官方示例见:https://osqp.org/docs/examples/least-squares.html
+ *                     Dolphin官方示例见:https://docs.dolphindb.cn/zh/funcs/o/osqp.html
+ *                     取数据脚本:SELECT fund_id, GROUP_CONCAT(ret_1w SEPARATOR ' ')
+ *                                 FROM mfdb.`fund_performance_weekly` 
+ *                                 WHERE fund_id IN ( 'MF00003PW1', 'IN00000008', 'IN00000077', 'IN0000009M', 'IN0000007G')
+ *                                 AND year_week BETWEEN 202411 AND 202422
+ *                                 GROUP BY fund_id
+ *
+ */
+ 
+ 
+/*
+ *   RBSA 计算
+ * 
+ *   Create  20240703  模仿python代码在 Dolphin 中实现                          Joey
+ *                               原代码见: http://gogs.fundit.cn/FundIt/FinanceCalcPython/src/dev36/pf_scical/v1/calc_rbsa_use_osqp.py
+ *                               Python官方示例见:https://osqp.org/docs/examples/least-squares.html
+ *                               Dolphin官方示例见:https://docs.dolphindb.cn/zh/funcs/o/osqp.html
+ */
+ 
+ def cal_rbsa(fund_ret, index_ret, isLongShort)
+ {
+  // 窗口长度
+  m = fund_ret.size()
+  // 指数个数
+  n = index_ret.cols()
+  
+  P0 = matrix(float, n, m+n)
+  P1 = concatMatrix([matrix(float, m, n), eye(m)])
+  P = concatMatrix([P0, P1], false)
+  q = array(float, m+n, (m+n)*10, 0)
+
+  A0 = concatMatrix( [matrix(index_ret), -eye(m)])
+  A1 = concatMatrix( [matrix(take(1, n)).transpose(), matrix(float, 1, m)])
+  A2 = concatMatrix( [eye(n), matrix(float, n, m)])
+  A = concatMatrix( [A0, A1, A2], false)
+  
+  // join 1 是为了限制所有权重加总为100%
+  // 下限
+  lb =(fund_ret join 1) join array(float, n, n*10, iif(isLongShort == false, 0, -2))
+  // 上限
+  ub=(fund_ret join 1) join array(float, n, n*10, iif(isLongShort == false, 1, 2))
+
+  res = osqp( q, P, A, lb, ub)
+
+  return res
+ }
+// 基金或组合的收益
+MF00003PW1 = (0.017450 0.002639 -0.026316 -0.005405 -0.013587 -0.001377 0.024828 0.014805 0.011936 -0.003932 -0.021053 0.006721);
+// 指数收益
+IN00000008 = (0.007075 -0.007000 -0.002121 0.008571 -0.025775 0.018936 0.012031 0.005613 0.017171 0.003189 -0.020797 -0.005986);
+IN00000077 = (-0.002598 0.001488 0.001404 0.001031 0.002389 0.002999 -0.000246 -0.000533 -0.000656 0.001313 0.001352 0.001514);
+IN0000007G =(0.000373 0.000367 0.000379 0.000272 0.000461 0.000355 0.000349 0.000195 0.000478 0.000331 0.000330 0.000324);
+IN0000009M = (0.010477 0.010086 0.019914 0.027885 0.061534 0.011593 -0.032239 -0.003192 0.011932 0.024807 0.006475 0.014500);
+mt = table(IN00000008, IN00000077, IN0000007G, IN0000009M);
+
+res = cal_rbsa(MF00003PW1, mt, false)
+
+// lb = 0 时仍有微笑负数,估计可以换成0
+// 不清除后面的12维数字是干啥的
+beta = res[1][0:4]
+print(beta)
+